@gapi/gcli 1.8.239 → 1.8.240
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/.agents/skills/local-ai-runners/SKILL.md +6 -0
- package/README-CLI.md +87 -57
- package/README.md +20 -0
- package/docs/changelog.d/2026-10-11-local-ai-runners-run-the-runner-as-a-systemd-user-service.md +36 -0
- package/docs/features/local-ai-runners.md +11 -0
- package/package.json +1 -1
- package/release/index.js +434 -204
|
@@ -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,54 @@
|
|
|
1
1
|
Usage: gcli [options] [command]
|
|
2
2
|
|
|
3
3
|
Options:
|
|
4
|
-
-V, --version
|
|
5
|
-
-h, --help
|
|
4
|
+
-V, --version output the version number
|
|
5
|
+
-h, --help display help for command
|
|
6
6
|
|
|
7
7
|
Commands:
|
|
8
|
-
login [options]
|
|
9
|
-
lambda:list [options]
|
|
10
|
-
lambda:get [options]
|
|
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]
|
|
14
|
-
lambda:log [options]
|
|
15
|
-
lambda:build:log [options]
|
|
16
|
-
lambda:test [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
|
|
17
17
|
lambda:package [options]
|
|
18
|
-
project:list
|
|
19
|
-
project:use <project>
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
environment:
|
|
26
|
-
environment:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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:list [options] List every private cluster this project has (it may have several - dev/stage/prod, ...)
|
|
34
|
+
cluster:teardown [options] Tear down one of a project's clusters (runs in the background - use --wait to block)
|
|
35
|
+
cluster:kubeconfig [options] Print (or save) a cluster's raw kubeconfig YAML - only available once it's 'ready'
|
|
36
|
+
rabbitmq:create [options] Provision a RabbitMQ broker for a project (a project may have several)
|
|
37
|
+
rabbitmq:list [options] List every RabbitMQ broker for a project
|
|
38
|
+
rabbitmq:delete [options] Delete a RabbitMQ broker
|
|
39
|
+
plugin:list List every plugin available in the marketplace catalog
|
|
40
|
+
plugin:installed [options] List plugins installed for a project (may include the same plugin on several clusters)
|
|
41
|
+
plugin:install [options] Install a marketplace plugin for a project
|
|
42
|
+
plugin:uninstall [options] Uninstall a plugin from a project (--cluster picks which installation, if it's on several)
|
|
43
|
+
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)
|
|
44
|
+
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)
|
|
45
|
+
runner:daemon:status [options] Show the runner service status (systemctl --user status)
|
|
46
|
+
runner:daemon:logs [options] Follow the runner service log (journalctl --user -f)
|
|
47
|
+
runner:daemon:uninstall [options] Stop and remove the runner service (the runner stays registered)
|
|
48
|
+
runner:list List your local AI runners and the models they serve
|
|
49
|
+
runner:models [options] Choose which of a runner's models appear in the chat model picker (no flags: list them)
|
|
50
|
+
runner:delete [options] Delete a local AI runner and revoke its token
|
|
51
|
+
help [command] display help for command
|
|
66
52
|
[36m
|
|
67
53
|
--- Subcommand: login ---[0m
|
|
68
54
|
Usage: gcli login [options]
|
|
@@ -646,6 +632,50 @@ Options:
|
|
|
646
632
|
--reset Forget the saved runner and register a new one
|
|
647
633
|
-h, --help display help for command
|
|
648
634
|
[36m
|
|
635
|
+
--- Subcommand: runner:daemon:install ---[0m
|
|
636
|
+
Usage: gcli runner:daemon:install|runner:start:daemon [options]
|
|
637
|
+
|
|
638
|
+
Run the runner in the background as a systemd user service: starts with your session, restarts on failure (Linux)
|
|
639
|
+
|
|
640
|
+
Options:
|
|
641
|
+
-n, --name <name> Runner name when registering (default: hostname)
|
|
642
|
+
-t, --token <token> Runner token for a machine without `gcli login`
|
|
643
|
+
--ollama <url> Ollama URL (default: $OLLAMA_HOST or
|
|
644
|
+
http://127.0.0.1:11434)
|
|
645
|
+
--wsUrl <url> Lambforge subscriptions endpoint override
|
|
646
|
+
--reset Forget the saved runner and register a new one
|
|
647
|
+
--service <name> systemd unit name (default: lambforge-runner)
|
|
648
|
+
--dry-run Print the unit file instead of installing it
|
|
649
|
+
-h, --help display help for command
|
|
650
|
+
[36m
|
|
651
|
+
--- Subcommand: runner:daemon:status ---[0m
|
|
652
|
+
Usage: gcli runner:daemon:status [options]
|
|
653
|
+
|
|
654
|
+
Show the runner service status (systemctl --user status)
|
|
655
|
+
|
|
656
|
+
Options:
|
|
657
|
+
--service <name> systemd unit name (default: lambforge-runner)
|
|
658
|
+
-h, --help display help for command
|
|
659
|
+
[36m
|
|
660
|
+
--- Subcommand: runner:daemon:logs ---[0m
|
|
661
|
+
Usage: gcli runner:daemon:logs [options]
|
|
662
|
+
|
|
663
|
+
Follow the runner service log (journalctl --user -f)
|
|
664
|
+
|
|
665
|
+
Options:
|
|
666
|
+
--service <name> systemd unit name (default: lambforge-runner)
|
|
667
|
+
--lines <n> Lines of history to show first (default 50)
|
|
668
|
+
-h, --help display help for command
|
|
669
|
+
[36m
|
|
670
|
+
--- Subcommand: runner:daemon:uninstall ---[0m
|
|
671
|
+
Usage: gcli runner:daemon:uninstall [options]
|
|
672
|
+
|
|
673
|
+
Stop and remove the runner service (the runner stays registered)
|
|
674
|
+
|
|
675
|
+
Options:
|
|
676
|
+
--service <name> systemd unit name (default: lambforge-runner)
|
|
677
|
+
-h, --help display help for command
|
|
678
|
+
[36m
|
|
649
679
|
--- Subcommand: runner:list ---[0m
|
|
650
680
|
Usage: gcli runner:list [options]
|
|
651
681
|
|
package/README.md
CHANGED
|
@@ -744,6 +744,26 @@ running until Ctrl+C and reconnects on its own.
|
|
|
744
744
|
| `gcli runner:list` | Your runners: online status, last seen, models |
|
|
745
745
|
| `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
746
|
| `gcli runner:delete` | Delete a runner and revoke its token (`--id`, default: this machine's) |
|
|
747
|
+
| `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 |
|
|
748
|
+
|
|
749
|
+
### Run it in the background (systemd, Linux)
|
|
750
|
+
|
|
751
|
+
```bash
|
|
752
|
+
npm i -g @gapi/gcli # a stable install - not the npx cache
|
|
753
|
+
gcli login
|
|
754
|
+
gcli runner:start:daemon # = runner:daemon:install; same options as runner:start
|
|
755
|
+
gcli runner:daemon:status
|
|
756
|
+
gcli runner:daemon:logs # follow the log (Ctrl+C to stop following)
|
|
757
|
+
gcli runner:daemon:uninstall # stop + remove the service (runner stays registered)
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
It installs a systemd **user** service (`~/.config/systemd/user/lambforge-runner.service`,
|
|
761
|
+
no `sudo`) that starts with your session and restarts on failure. The runner
|
|
762
|
+
token stays in `~/.graphql-server/runner.json`, never in the unit file; a
|
|
763
|
+
revoked token stops the service instead of restart-looping. To keep it
|
|
764
|
+
running after logout and start it at boot, enable lingering once:
|
|
765
|
+
`sudo loginctl enable-linger $USER`. Preview the unit with `--dry-run`;
|
|
766
|
+
`--service <name>` installs several runners side by side.
|
|
747
767
|
|
|
748
768
|
Notes:
|
|
749
769
|
|
package/docs/changelog.d/2026-10-11-local-ai-runners-run-the-runner-as-a-systemd-user-service.md
ADDED
|
@@ -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.
|
|
@@ -13,6 +13,8 @@ what lives in gcli.
|
|
|
13
13
|
| `gcli runner:list` | Your runners: online, last seen, models |
|
|
14
14
|
| `gcli runner:models [--id] [--show a,b \| --all \| --none]` | Which models appear in the chat picker; no flag = list |
|
|
15
15
|
| `gcli runner:delete [--id]` | Delete a runner and revoke its token (default: the saved one) |
|
|
16
|
+
| `gcli runner:daemon:install` (alias `runner:start:daemon`) `[runner:start options] [--service] [--dry-run]` | Resolve/register the runner now (saves `runner.json`), then install + enable + restart a systemd USER unit running `runner:start --ollama <resolved>` (Linux) |
|
|
17
|
+
| `gcli runner:daemon:status` / `:logs` / `:uninstall` | `systemctl --user status`, `journalctl --user -f`, disable --now + remove the unit |
|
|
16
18
|
|
|
17
19
|
Endpoints: `--wsUrl` > `runner.json` > derived from the `gcli login` URL
|
|
18
20
|
(`https://api.lambforge.com` -> `wss://api.lambforge.com/subscriptions`;
|
|
@@ -42,6 +44,15 @@ Ollama: `--ollama` > `$OLLAMA_HOST` (scheme optional, `0.0.0.0` -> `127.0.0.1`)
|
|
|
42
44
|
The backend fits every request to it, because Ollama silently drops the
|
|
43
45
|
oldest messages of a prompt that is too long (see the canonical doc,
|
|
44
46
|
"Context window").
|
|
47
|
+
- `daemon.ts` - `buildUnit` (pure; systemd quoting; `Restart=always`,
|
|
48
|
+
`RestartPreventExitStatus=78`), `resolveGcliCommand` (node + script, or
|
|
49
|
+
the SEA binary; warns for the npx cache), `SystemdUserService`
|
|
50
|
+
(`systemctl --user` via execFile, never a shell; home dir injectable so
|
|
51
|
+
tests can't touch the real `~/.config`). `daemon-commands.ts` - the
|
|
52
|
+
`runner:daemon:*` actions. Adapted from cli-builder's `SystemctlService`,
|
|
53
|
+
but a USER unit (no root) and no secrets in the unit. `runner:start` exits
|
|
54
|
+
78 (EX_CONFIG) when the token is refused, so systemd stops instead of
|
|
55
|
+
restart-looping.
|
|
45
56
|
- `config.ts` (saved runner, URL derivation), `start.ts`, `list.ts`,
|
|
46
57
|
`models.ts`, `delete.ts`, `index.ts` (command registration), `types.ts`.
|
|
47
58
|
- `src/services/gql-client.ts` - `createLocalRunner`, `listLocalRunners`,
|