@gapi/gcli 1.8.239 → 1.8.241

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.
@@ -68,3 +68,26 @@ shared with or generated from gql-fission's copy.
68
68
  `jest.config.js` has no `moduleNameMapper` for the `~/` tsconfig path alias
69
69
  — new test files must use relative imports (e.g. `'../src/helpers'`), not
70
70
  `'~/helpers'`, or the test silently fails to resolve the module.
71
+
72
+ ## Managing a private cluster itself (scaling, topology, autoscale)
73
+
74
+ The `cluster:*` commands that change or watch ONE cluster
75
+ (`status`, `health`, `scale-events`, `cost`, `scale`,
76
+ `scale:control-planes`, `multi-node`, `single-node`, `autoscale`,
77
+ `load-balancer`) share `commands/cluster/helpers/cluster-change.ts`:
78
+ `resolveClusterTarget` (`--cluster <id>` or `--clusterName <name>`,
79
+ required), `parseIntOption`, and `runClusterChange` (mutate → print →
80
+ `--wait` via `pollUntilReady`; ending in `error` exits non-zero). Their
81
+ selections and types live in `commands/cluster/types.ts` (newer than the
82
+ hand-kept `@introspection`).
83
+
84
+ `cluster:apply` is the declarative path: the `cluster:` section of
85
+ lambforge.yaml (`ClusterDesired` in `helpers/plan.ts`, also the type of
86
+ `load-spec.ts`'s `cluster`). Keep `planClusterChanges` pure and ordered
87
+ (create | topology → workers → control planes → load balancer →
88
+ autoscale): each step is one background job and the cluster must be
89
+ `ready` before the next. A new cluster setting = a key in `ClusterDesired`,
90
+ a check in `validateDesired`, a step in the plan, a test in
91
+ `test/cluster-apply.spec.ts`, and the README "Declarative clusters" block.
92
+ The server merges partial autoscale input into the stored policy, so the
93
+ plan sends only changed keys.
@@ -23,6 +23,12 @@ security, symptom table): `graphql-server/docs/features/local-ai-runners.md`.
23
23
  - Socket close aborts every in-flight Ollama request; a refused token is
24
24
  fatal (`RunnerAuthError`), everything else reconnects with backoff.
25
25
  - Global `WebSocket` -> Node >= 22.
26
+ - Daemon (`runner:daemon:*`, `daemon.ts`): systemd USER unit; never put the
27
+ token or other secrets in the unit (it's world-readable-ish config) - it
28
+ runs `runner:start`, which reads the 0600 `runner.json`. Keep exit code 78
29
+ for "token refused" in sync with `RestartPreventExitStatus`. Tests must
30
+ pass an explicit home dir to `SystemdUserService` (os.homedir() ignored a
31
+ changed HOME under jest and wrote into the real `~/.config` once).
26
32
 
27
33
  ## Run it against a dev stack
28
34
 
package/README-CLI.md CHANGED
@@ -1,68 +1,65 @@
1
1
  Usage: gcli [options] [command]
2
2
 
3
3
  Options:
4
- -V, --version output the version number
5
- -h, --help display help for command
4
+ -V, --version output the version number
5
+ -h, --help display help for command
6
6
 
7
7
  Commands:
8
- login [options] Login to graphql-server
9
- lambda:list [options] List of all lambdas for project
10
- lambda:get [options] Get lambda by id
8
+ login [options] Login to graphql-server
9
+ lambda:list [options] List of all lambdas for project
10
+ lambda:get [options] Get lambda by id
11
11
  lambda:create [options]
12
12
  lambda:update [options]
13
- lambda:delete [options] Get lambda by id
14
- lambda:log [options] Get lambda log
15
- lambda:build:log [options] Get build log
16
- lambda:test [options] Test lambda
13
+ lambda:delete [options] Get lambda by id
14
+ lambda:log [options] Get lambda log
15
+ lambda:build:log [options] Get build log
16
+ lambda:test [options] Test lambda
17
17
  lambda:package [options]
18
- project:list List of all projects
19
- project:use <project> Adds default project to be used all around the
20
- CLI
21
- project:clear Clears default project to be used all around
22
- the CLI
23
- build [options] Build bundle using esbuild
24
- https://esbuild.github.io
25
- environment:list [options] List of all environments for project
26
- environment:create [options] Create environment for project
27
- environment:update [options] Update environment for project
28
- environment:delete [options] Delete environment for project
29
- environment:get [options] Get environment for project
30
- mcp:start [options] Start mcp proxy server
31
- start [options] Start bundle using esbuild
32
- https://esbuild.github.io
33
- cron:create [options] Create cron
34
- cron:delete [options] Delete cron
35
- cron:list [options] List of all cron jobs for project
36
- cluster:create [options] Provision a private Talos cluster for a project
37
- (runs in the background - takes several
38
- minutes; use --wait to block)
39
- cluster:list [options] List every private cluster this project has (it
40
- may have several - dev/stage/prod, ...)
41
- cluster:teardown [options] Tear down one of a project's clusters (runs in
42
- the background - use --wait to block)
43
- cluster:kubeconfig [options] Print (or save) a cluster's raw kubeconfig YAML
44
- - only available once it's 'ready'
45
- rabbitmq:create [options] Provision a RabbitMQ broker for a project (a
46
- project may have several)
47
- rabbitmq:list [options] List every RabbitMQ broker for a project
48
- rabbitmq:delete [options] Delete a RabbitMQ broker
49
- plugin:list List every plugin available in the marketplace
50
- catalog
51
- plugin:installed [options] List plugins installed for a project (may
52
- include the same plugin on several clusters)
53
- plugin:install [options] Install a marketplace plugin for a project
54
- plugin:uninstall [options] Uninstall a plugin from a project (--cluster
55
- picks which installation, if it's on several)
56
- runner:start [options] Connect this machine's Ollama to the Lambforge
57
- chat as a local AI runner (outbound connection
58
- only - works behind NAT, Ollama is never
59
- exposed)
60
- runner:list List your local AI runners and the models they
61
- serve
62
- runner:models [options] Choose which of a runner's models appear in the
63
- chat model picker (no flags: list them)
64
- runner:delete [options] Delete a local AI runner and revoke its token
65
- help [command] display help for command
18
+ project:list List of all projects
19
+ project:use <project> Adds default project to be used all around the CLI
20
+ project:clear Clears default project to be used all around the CLI
21
+ build [options] Build bundle using esbuild https://esbuild.github.io
22
+ environment:list [options] List of all environments for project
23
+ environment:create [options] Create environment for project
24
+ environment:update [options] Update environment for project
25
+ environment:delete [options] Delete environment for project
26
+ environment:get [options] Get environment for project
27
+ mcp:start [options] Start mcp proxy server
28
+ start [options] Start bundle using esbuild https://esbuild.github.io
29
+ cron:create [options] Create cron
30
+ cron:delete [options] Delete cron
31
+ cron:list [options] List of all cron jobs for project
32
+ cluster:create [options] Provision a private Talos cluster for a project (runs in the background - takes several minutes; use --wait to block)
33
+ cluster:apply [options] Make a private cluster match the `cluster:` section of lambforge.yaml: creates it if missing, then applies topology, workers, control planes, load balancer and autoscaling
34
+ cluster:list [options] List every private cluster this project has (it may have several - dev/stage/prod, ...)
35
+ cluster:teardown [options] Tear down one of a project's clusters (runs in the background - use --wait to block)
36
+ cluster:kubeconfig [options] Print (or save) a cluster's raw kubeconfig YAML - only available once it's 'ready'
37
+ cluster:status [options] Every cluster's status, agent, nodes, CPU/memory load, autoscaling and open issues (or one with --cluster)
38
+ cluster:health [options] The latest health report of a cluster's agent, with every node
39
+ cluster:scale-events [options] Manual and autoscaler scale history, newest first
40
+ cluster:cost [options] Month-to-date cost and the monthly estimate of the project's private clusters
41
+ cluster:scale [options] Set the number of worker nodes (1-10) of a multi-node cluster - billed per node-hour
42
+ cluster:scale:control-planes [options] Set the number of control-plane nodes (1, 3 or 5)
43
+ cluster:autoscale [options] Change the worker autoscaling policy - only the flags you pass change
44
+ cluster:multi-node [options] Convert a single-node cluster to dedicated worker nodes (required for autoscaling)
45
+ cluster:single-node [options] Convert a multi-node cluster back to a single node (removes the worker nodes)
46
+ cluster:load-balancer [options] Add or remove the cluster's load balancer (billed per hour while enabled)
47
+ rabbitmq:create [options] Provision a RabbitMQ broker for a project (a project may have several)
48
+ rabbitmq:list [options] List every RabbitMQ broker for a project
49
+ rabbitmq:delete [options] Delete a RabbitMQ broker
50
+ plugin:list List every plugin available in the marketplace catalog
51
+ plugin:installed [options] List plugins installed for a project (may include the same plugin on several clusters)
52
+ plugin:install [options] Install a marketplace plugin for a project
53
+ plugin:uninstall [options] Uninstall a plugin from a project (--cluster picks which installation, if it's on several)
54
+ runner:start [options] Connect this machine's Ollama to the Lambforge chat as a local AI runner (outbound connection only - works behind NAT, Ollama is never exposed)
55
+ runner:daemon:install|runner:start:daemon [options] Run the runner in the background as a systemd user service: starts with your session, restarts on failure (Linux)
56
+ runner:daemon:status [options] Show the runner service status (systemctl --user status)
57
+ runner:daemon:logs [options] Follow the runner service log (journalctl --user -f)
58
+ runner:daemon:uninstall [options] Stop and remove the runner service (the runner stays registered)
59
+ runner:list List your local AI runners and the models they serve
60
+ runner:models [options] Choose which of a runner's models appear in the chat model picker (no flags: list them)
61
+ runner:delete [options] Delete a local AI runner and revoke its token
62
+ help [command] display help for command
66
63
  
67
64
  --- Subcommand: login ---
68
65
  Usage: gcli login [options]
@@ -522,6 +519,19 @@ Options:
522
519
  --wait Block until the cluster is ready (or errors)
523
520
  -h, --help display help for command
524
521
  
522
+ --- Subcommand: cluster:apply ---
523
+ Usage: gcli cluster:apply [options]
524
+
525
+ Make a private cluster match the `cluster:` section of lambforge.yaml: creates it if missing, then applies topology, workers, control planes, load balancer and autoscaling
526
+
527
+ Options:
528
+ -p, --project <project>
529
+ -spec, --spec <spec> Spec file (default: lambforge.yaml / cluster.yaml)
530
+ --dry-run Print the plan, change nothing
531
+ --yes Also apply steps that remove capacity or the load
532
+ balancer IP
533
+ -h, --help display help for command
534
+ 
525
535
  --- Subcommand: cluster:list ---
526
536
  Usage: gcli cluster:list [options]
527
537
 
@@ -553,6 +563,135 @@ Options:
553
563
  -o, --output <output> Write to this file instead of stdout
554
564
  -h, --help display help for command
555
565
  
566
+ --- Subcommand: cluster:status ---
567
+ Usage: gcli cluster:status [options]
568
+
569
+ Every cluster's status, agent, nodes, CPU/memory load, autoscaling and open issues (or one with --cluster)
570
+
571
+ Options:
572
+ -p, --project <project>
573
+ -c, --cluster <cluster> Cluster id (see cluster:list)
574
+ --clusterName <clusterName> Cluster name instead of --cluster
575
+ -h, --help display help for command
576
+ 
577
+ --- Subcommand: cluster:health ---
578
+ Usage: gcli cluster:health [options]
579
+
580
+ The latest health report of a cluster's agent, with every node
581
+
582
+ Options:
583
+ -p, --project <project>
584
+ -c, --cluster <cluster> Cluster id (see cluster:list)
585
+ --clusterName <clusterName> Cluster name instead of --cluster
586
+ -h, --help display help for command
587
+ 
588
+ --- Subcommand: cluster:scale-events ---
589
+ Usage: gcli cluster:scale-events [options]
590
+
591
+ Manual and autoscaler scale history, newest first
592
+
593
+ Options:
594
+ -p, --project <project>
595
+ -c, --cluster <cluster> Cluster id (see cluster:list)
596
+ --clusterName <clusterName> Cluster name instead of --cluster
597
+ --limit <limit> How many events (default 20)
598
+ -h, --help display help for command
599
+ 
600
+ --- Subcommand: cluster:cost ---
601
+ Usage: gcli cluster:cost [options]
602
+
603
+ Month-to-date cost and the monthly estimate of the project's private clusters
604
+
605
+ Options:
606
+ -p, --project <project>
607
+ -h, --help display help for command
608
+ 
609
+ --- Subcommand: cluster:scale ---
610
+ Usage: gcli cluster:scale [options]
611
+
612
+ Set the number of worker nodes (1-10) of a multi-node cluster - billed per node-hour
613
+
614
+ Options:
615
+ -p, --project <project>
616
+ -c, --cluster <cluster> Cluster id (see cluster:list)
617
+ --clusterName <clusterName> Cluster name instead of --cluster
618
+ --workers <workers> Worker nodes, 1-10
619
+ --wait Block until the cluster settles
620
+ -h, --help display help for command
621
+ 
622
+ --- Subcommand: cluster:scale:control-planes ---
623
+ Usage: gcli cluster:scale:control-planes [options]
624
+
625
+ Set the number of control-plane nodes (1, 3 or 5)
626
+
627
+ Options:
628
+ -p, --project <project>
629
+ -c, --cluster <cluster> Cluster id (see cluster:list)
630
+ --clusterName <clusterName> Cluster name instead of --cluster
631
+ --count <count> Control planes: 1, 3 or 5
632
+ --wait Block until the cluster settles
633
+ -h, --help display help for command
634
+ 
635
+ --- Subcommand: cluster:autoscale ---
636
+ Usage: gcli cluster:autoscale [options]
637
+
638
+ Change the worker autoscaling policy - only the flags you pass change
639
+
640
+ Options:
641
+ -p, --project <project>
642
+ -c, --cluster <cluster> Cluster id (see cluster:list)
643
+ --clusterName <clusterName> Cluster name instead of --cluster
644
+ --enable Turn autoscaling on (multi-node clusters only)
645
+ --disable Turn autoscaling off
646
+ --min <min> Minimum workers (1-10)
647
+ --max <max> Maximum workers (1-10)
648
+ --cpuUp <percent> Scale up at this CPU % (1-100)
649
+ --memUp <percent> Scale up at this memory % (1-100)
650
+ --cpuDown <percent> Scale down below this CPU % (1-100)
651
+ --memDown <percent> Scale down below this memory % (1-100)
652
+ --samples <n> Consecutive readings before acting (1-20)
653
+ --cooldown <minutes> Minutes between scale actions (1-1440)
654
+ -h, --help display help for command
655
+ 
656
+ --- Subcommand: cluster:multi-node ---
657
+ Usage: gcli cluster:multi-node [options]
658
+
659
+ Convert a single-node cluster to dedicated worker nodes (required for autoscaling)
660
+
661
+ Options:
662
+ -p, --project <project>
663
+ -c, --cluster <cluster> Cluster id (see cluster:list)
664
+ --clusterName <clusterName> Cluster name instead of --cluster
665
+ --workers <workers> Worker nodes, 1-10
666
+ --wait Block until the cluster settles
667
+ -h, --help display help for command
668
+ 
669
+ --- Subcommand: cluster:single-node ---
670
+ Usage: gcli cluster:single-node [options]
671
+
672
+ Convert a multi-node cluster back to a single node (removes the worker nodes)
673
+
674
+ Options:
675
+ -p, --project <project>
676
+ -c, --cluster <cluster> Cluster id (see cluster:list)
677
+ --clusterName <clusterName> Cluster name instead of --cluster
678
+ --wait Block until the cluster settles
679
+ -h, --help display help for command
680
+ 
681
+ --- Subcommand: cluster:load-balancer ---
682
+ Usage: gcli cluster:load-balancer [options]
683
+
684
+ Add or remove the cluster's load balancer (billed per hour while enabled)
685
+
686
+ Options:
687
+ -p, --project <project>
688
+ -c, --cluster <cluster> Cluster id (see cluster:list)
689
+ --clusterName <clusterName> Cluster name instead of --cluster
690
+ --enable Add the load balancer
691
+ --disable Remove the load balancer
692
+ --wait Block until the cluster settles
693
+ -h, --help display help for command
694
+ 
556
695
  --- Subcommand: rabbitmq:create ---
557
696
  Usage: gcli rabbitmq:create [options]
558
697
 
@@ -646,6 +785,50 @@ Options:
646
785
  --reset Forget the saved runner and register a new one
647
786
  -h, --help display help for command
648
787
  
788
+ --- Subcommand: runner:daemon:install ---
789
+ Usage: gcli runner:daemon:install|runner:start:daemon [options]
790
+
791
+ Run the runner in the background as a systemd user service: starts with your session, restarts on failure (Linux)
792
+
793
+ Options:
794
+ -n, --name <name> Runner name when registering (default: hostname)
795
+ -t, --token <token> Runner token for a machine without `gcli login`
796
+ --ollama <url> Ollama URL (default: $OLLAMA_HOST or
797
+ http://127.0.0.1:11434)
798
+ --wsUrl <url> Lambforge subscriptions endpoint override
799
+ --reset Forget the saved runner and register a new one
800
+ --service <name> systemd unit name (default: lambforge-runner)
801
+ --dry-run Print the unit file instead of installing it
802
+ -h, --help display help for command
803
+ 
804
+ --- Subcommand: runner:daemon:status ---
805
+ Usage: gcli runner:daemon:status [options]
806
+
807
+ Show the runner service status (systemctl --user status)
808
+
809
+ Options:
810
+ --service <name> systemd unit name (default: lambforge-runner)
811
+ -h, --help display help for command
812
+ 
813
+ --- Subcommand: runner:daemon:logs ---
814
+ Usage: gcli runner:daemon:logs [options]
815
+
816
+ Follow the runner service log (journalctl --user -f)
817
+
818
+ Options:
819
+ --service <name> systemd unit name (default: lambforge-runner)
820
+ --lines <n> Lines of history to show first (default 50)
821
+ -h, --help display help for command
822
+ 
823
+ --- Subcommand: runner:daemon:uninstall ---
824
+ Usage: gcli runner:daemon:uninstall [options]
825
+
826
+ Stop and remove the runner service (the runner stays registered)
827
+
828
+ Options:
829
+ --service <name> systemd unit name (default: lambforge-runner)
830
+ -h, --help display help for command
831
+ 
649
832
  --- Subcommand: runner:list ---
650
833
  Usage: gcli runner:list [options]
651
834
 
package/README.md CHANGED
@@ -11,6 +11,7 @@ Whether you are deploying scalable serverless functions, managing cloud environm
11
11
  - [Managing Infrastructure](#managing-infrastructure)
12
12
  - [Serverless Functions (Lambdas)](#serverless-functions-lambdas)
13
13
  - [AI Integration (Model Context Protocol)](#ai-integration-model-context-protocol)
14
+ - [AI agent skill](#ai-agent-skill)
14
15
  - [Local AI Models (Ollama runner)](#local-ai-models-ollama-runner)
15
16
  - [Build System](#build-system)
16
17
  - [Command Reference](#command-reference)
@@ -24,7 +25,7 @@ You can install `gcli` using a pre-built binary or via NPM.
24
25
  For a standalone installation without Node.js dependencies:
25
26
 
26
27
  ```bash
27
- curl -L "https://github.com/Stradivario/gapi/releases/download/v1.8.198/gcli-linux" -o ~/.local/bin/gcli
28
+ curl -L "https://github.com/Stradivario/gapi/releases/latest/download/gcli-linux" -o ~/.local/bin/gcli
28
29
  chmod +x ~/.local/bin/gcli
29
30
  ```
30
31
 
@@ -95,6 +96,10 @@ gcli environment:get --name 'staging'
95
96
  # (omit --clusterId to use the shared cluster - the default). Immutable
96
97
  # after creation.
97
98
  gcli environment:create --name 'staging' --clusterId '<clusterId>'
99
+
100
+ # Resize it, or remove it
101
+ gcli environment:update --name 'staging' --maxCpu 800 --maxMemory 1024
102
+ gcli environment:delete --name 'staging'
98
103
  ```
99
104
 
100
105
  ### Private Clusters
@@ -119,17 +124,79 @@ gcli cluster:kubeconfig --cluster '<clusterId>' --output ./kubeconfig.yaml
119
124
  gcli cluster:teardown --cluster '<clusterId>' --wait
120
125
  ```
121
126
 
122
- `cluster.yaml`:
127
+ Scale, reshape and watch a cluster (the same operations as the dashboard's
128
+ cluster page). Changes run in the background; `--wait` blocks until the
129
+ cluster is `ready` again. `--clusterName <name>` works instead of
130
+ `--cluster <id>` everywhere.
131
+
132
+ ```bash
133
+ # Health, load, autoscaling and open issues of every cluster (or one)
134
+ gcli cluster:status [--cluster '<clusterId>']
135
+ gcli cluster:health --cluster '<clusterId>' # latest agent report, per node
136
+ gcli cluster:scale-events --cluster '<clusterId>' # manual + autoscaler history
137
+ gcli cluster:cost # month-to-date + monthly estimate
138
+
139
+ # Worker nodes (1-10, billed per node-hour) and control planes (1, 3 or 5)
140
+ gcli cluster:scale --cluster '<clusterId>' --workers 3 --wait
141
+ gcli cluster:scale:control-planes --cluster '<clusterId>' --count 3 --wait
142
+
143
+ # Topology: dedicated workers (needed for autoscaling) or back to one node
144
+ gcli cluster:multi-node --cluster '<clusterId>' --workers 2 --wait
145
+ gcli cluster:single-node --cluster '<clusterId>' --wait
146
+
147
+ # Autoscaling - only the flags you pass change
148
+ gcli cluster:autoscale --cluster '<clusterId>' --enable --min 2 --max 6
149
+ gcli cluster:autoscale --cluster '<clusterId>' --cpuUp 75 --cpuDown 25 --cooldown 20
150
+ gcli cluster:autoscale --cluster '<clusterId>' --disable
151
+
152
+ # Load balancer (billed per hour while enabled)
153
+ gcli cluster:load-balancer --cluster '<clusterId>' --enable --wait
154
+ ```
155
+
156
+ #### Declarative clusters (`cluster:apply`)
157
+
158
+ Write the cluster you want in the `cluster:` section of `lambforge.yaml`
159
+ (or a standalone `cluster.yaml`) and let `cluster:apply` make it so: it
160
+ creates the cluster when it doesn't exist, then applies topology, workers,
161
+ control planes, the load balancer and autoscaling - one change at a time,
162
+ waiting for the cluster to be ready between them. Keys you leave out are
163
+ left as they are.
123
164
 
124
165
  ```yaml
125
- name: prod
126
- imageId: <hcloud-image-id>
127
- serverType: cpx22
128
- location: nbg1
129
- workers: 2
130
- singleNode: false
166
+ cluster:
167
+ name: prod
168
+ # needed only to create it
169
+ imageId: <hcloud-image-id>
170
+ serverType: cpx22
171
+ location: nbg1
172
+ # desired state
173
+ singleNode: false # true = one node runs everything (cheapest, dev/stage)
174
+ workers: 3 # 1-10; only the initial size while autoscale is enabled
175
+ controlPlanes: 1 # 1, 3 or 5
176
+ loadBalancer: true
177
+ autoscale:
178
+ enabled: true # needs singleNode: false
179
+ minWorkers: 2
180
+ maxWorkers: 6
181
+ cpuScaleUpPercent: 70 # scale up above this (of allocatable)
182
+ memScaleUpPercent: 70
183
+ cpuScaleDownPercent: 30
184
+ memScaleDownPercent: 30
185
+ samplesToScale: 3 # consecutive readings before acting
186
+ cooldownMinutes: 15
131
187
  ```
132
188
 
189
+ ```bash
190
+ gcli cluster:apply --dry-run # print the plan, change nothing
191
+ gcli cluster:apply # apply it
192
+ gcli cluster:apply --yes # also steps that remove capacity or the LB IP
193
+ gcli cluster:apply --spec cluster.yaml
194
+ ```
195
+
196
+ Steps that remove capacity or release the load balancer's IP (going back to
197
+ `singleNode: true`, `loadBalancer: false`) are skipped unless you pass
198
+ `--yes`. Nodes and the load balancer are billed per started hour.
199
+
133
200
  ### RabbitMQ Brokers
134
201
 
135
202
  A project may have several RabbitMQ brokers, each optionally scoped to one of its private clusters.
@@ -285,15 +352,25 @@ options:
285
352
  external: []
286
353
  ```
287
354
 
288
- The same file can also carry `cluster:`, `rabbitmq:`, and `plugin:` sections, read by `cluster:create`, `rabbitmq:create`, and `plugin:install` respectively (each also accepts its own dedicated `--spec` file, e.g. `cluster.yaml`, if you'd rather keep them separate):
355
+ The same file can also carry `cluster:`, `rabbitmq:`, `plugin:` and `triggers:` sections, read by `cluster:apply` / `cluster:create`, `rabbitmq:create`, `plugin:install` and `cron:create` respectively (each also accepts its own dedicated `--spec` file, e.g. `cluster.yaml`, if you'd rather keep them separate):
289
356
 
290
357
  ```yaml
291
- cluster:
358
+ cluster: # every key: see "Declarative clusters" above
292
359
  name: prod
293
360
  imageId: <hcloud-image-id>
294
361
  serverType: cpx22
295
362
  location: nbg1
296
363
  workers: 2
364
+ loadBalancer: false
365
+ autoscale:
366
+ enabled: true
367
+ minWorkers: 2
368
+ maxWorkers: 5
369
+
370
+ triggers:
371
+ time:
372
+ triggerName: nightly
373
+ cron: '0 2 * * *' # cron syntax or @every 5m / @daily
297
374
 
298
375
  rabbitmq:
299
376
  name: events-broker
@@ -361,7 +438,7 @@ gcli lambda:create --project <projectId> --spec ./lambforge.yaml
361
438
  `--mcp` and `--mcpGraph <name>` are also available as CLI flags; the array fields
362
439
  (`mcpOperations`, `mcpHeaders`, `mcpForwardHeaders`) are spec-only. A full annotated example lives in
363
440
  [`example/mcp/lambforge.yaml`](./example/mcp/lambforge.yaml). Client connects at
364
- `https://<host>/<projectId>/<route>/mcp`.
441
+ `https://<host>/<projectId>/<route>` (with `route: mcp`: `https://<host>/<projectId>/mcp`).
365
442
 
366
443
  ### Conditional Configuration (Environment-Aware Specs)
367
444
 
@@ -638,10 +715,23 @@ gcli environment:update
638
715
 
639
716
  ### Function Lifecycle
640
717
 
718
+ - **List**: `gcli lambda:list` (`--clusterName <name>` for one cluster's lambdas)
641
719
  - **Update**: `gcli lambda:update --name 'my-function' --file ./new-index.ts`
642
720
  - **Delete**: `gcli lambda:delete --name 'my-function'`
643
721
  - **Get Details**: `gcli lambda:get --name 'my-function'`
644
722
 
723
+ ### Schedules (cron)
724
+
725
+ One time trigger per lambda. The platform calls the lambda's root with a
726
+ GET and an `X-Fission-Timer-Name` header.
727
+
728
+ ```bash
729
+ gcli cron:create --lambdaName 'my-function' --triggerName nightly --cron '0 2 * * *'
730
+ gcli cron:create # reads triggers.time (and function.name) from lambforge.yaml
731
+ gcli cron:list
732
+ gcli cron:delete --lambdaName 'my-function'
733
+ ```
734
+
645
735
  ### Monitoring & Testing
646
736
 
647
737
  Debug your functions directly from the terminal.
@@ -715,6 +805,33 @@ To use this with Claude Desktop or other MCP-compatible tools, add the following
715
805
  }
716
806
  ```
717
807
 
808
+ ### AI agent skill
809
+
810
+ Give a coding agent (Claude Code and other agents that read skills) the
811
+ whole platform. Paste this into the agent:
812
+
813
+ ```text
814
+ Install the Lambforge skill from https://lambforge.com/skills/platform.md and follow its setup.
815
+ ```
816
+
817
+ Or by hand for Claude Code:
818
+
819
+ ```bash
820
+ mkdir -p ~/.claude/skills/lambforge && cd ~/.claude/skills/lambforge
821
+ for f in $(curl -fsSL https://lambforge.com/skills/files.txt); do
822
+ curl -fsSLO "https://lambforge.com/skills/$f"
823
+ done
824
+ cp platform.md SKILL.md
825
+ claude mcp add --scope user lambforge -- gcli mcp:start
826
+ ```
827
+
828
+ `platform.md` covers setup (install gcli, the login command you run from
829
+ Profile -> CLI - the agent never handles your token, `gcli mcp:start`) and
830
+ the rules, and indexes topic files the agent reads on demand: gcli, MCP
831
+ tools, deploy, REST code and testing, GraphQL and federation, websites,
832
+ MCP servers, secrets, cron and queues, clusters and plugins, operations and
833
+ costs, memory and plans. Source: gql-fission `src/skills/`.
834
+
718
835
  ## Local AI Models (Ollama runner)
719
836
 
720
837
  Use models running on your own machine in the Lambforge chat - including
@@ -744,6 +861,26 @@ running until Ctrl+C and reconnects on its own.
744
861
  | `gcli runner:list` | Your runners: online status, last seen, models |
745
862
  | `gcli runner:models` | List which models are shown in the chat; `--show a,b` shows only those, `--all` shows every model, `--none` hides them all; `--id` picks a runner (default: this machine's) |
746
863
  | `gcli runner:delete` | Delete a runner and revoke its token (`--id`, default: this machine's) |
864
+ | `gcli runner:start:daemon` / `runner:daemon:install` | Install + start the runner as a systemd user service (Linux); `runner:daemon:status`, `runner:daemon:logs`, `runner:daemon:uninstall` manage it |
865
+
866
+ ### Run it in the background (systemd, Linux)
867
+
868
+ ```bash
869
+ npm i -g @gapi/gcli # a stable install - not the npx cache
870
+ gcli login
871
+ gcli runner:start:daemon # = runner:daemon:install; same options as runner:start
872
+ gcli runner:daemon:status
873
+ gcli runner:daemon:logs # follow the log (Ctrl+C to stop following)
874
+ gcli runner:daemon:uninstall # stop + remove the service (runner stays registered)
875
+ ```
876
+
877
+ It installs a systemd **user** service (`~/.config/systemd/user/lambforge-runner.service`,
878
+ no `sudo`) that starts with your session and restarts on failure. The runner
879
+ token stays in `~/.graphql-server/runner.json`, never in the unit file; a
880
+ revoked token stops the service instead of restart-looping. To keep it
881
+ running after logout and start it at boot, enable lingering once:
882
+ `sudo loginctl enable-linger $USER`. Preview the unit with `--dry-run`;
883
+ `--service <name>` installs several runners side by side.
747
884
 
748
885
  Notes:
749
886
 
@@ -0,0 +1,36 @@
1
+ ## 2026-10-11 - local-ai-runners: run the runner as a systemd user service
2
+
3
+ **Feature:** local-ai-runners · **Type:** feat
4
+
5
+ - `gcli runner:daemon:install` (alias `runner:start:daemon`) resolves or
6
+ registers the runner first (gcli login session, or `--token` saved to the
7
+ 0600 `~/.graphql-server/runner.json`), then writes
8
+ `~/.config/systemd/user/lambforge-runner.service` and runs
9
+ `systemctl --user daemon-reload / enable / restart`. The unit runs
10
+ `<node> <gcli script> runner:start --ollama <resolved URL>` (or the SEA
11
+ binary), `Restart=always`, `RestartSec=5`, `WantedBy=default.target`.
12
+ `runner:daemon:status`, `runner:daemon:logs` (journalctl -f) and
13
+ `runner:daemon:uninstall` manage it; `--service` allows several,
14
+ `--dry-run` prints the unit.
15
+ - Adapted from packages/cli-builder `SystemctlService`; differences: a user
16
+ unit (no root, runs as the owner of runner.json), the token never goes
17
+ into the unit, commands run via execFile (no shell), and
18
+ `runner:start` now exits 78 on a refused token with
19
+ `RestartPreventExitStatus=78` so systemd doesn't restart-loop. Warns when
20
+ gcli runs from the npx cache and when lingering is off (stops at logout).
21
+ - Linux-only for now (clear error elsewhere).
22
+ - **Test:** `npx jest test/runner-daemon.spec.ts --coverage=false` (unit
23
+ content/quoting, no token, command resolution incl. SEA and npx warning,
24
+ the systemctl call sequence against a fake exec and a temp home).
25
+ Manual: `gcli runner:start:daemon --dry-run`, then install,
26
+ `systemctl --user status lambforge-runner`, reboot or log out/in.
27
+ - **Debug:** `gcli runner:daemon:logs` (= `journalctl --user -u
28
+ lambforge-runner -f`); service "inactive (dead)" with status 78 = token
29
+ refused -> `gcli runner:start --reset` then reinstall; stops at logout =
30
+ `sudo loginctl enable-linger $USER`; ExecStart path gone = gcli was in the
31
+ npx cache -> install globally and reinstall.
32
+ - **Verification:** gcli 38/38, tsc + eslint clean; `--dry-run` of the built
33
+ CLI prints the expected unit. Not installed live on the dev machine. A
34
+ first test version leaked a stub unit into the real ~/.config (home dir
35
+ not injectable) - removed, and the service now takes the home dir
36
+ explicitly.