@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.
@@ -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 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: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
  
67
53
  --- Subcommand: login ---
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
  
635
+ --- Subcommand: runner:daemon:install ---
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
+ 
651
+ --- Subcommand: runner:daemon:status ---
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
+ 
660
+ --- Subcommand: runner:daemon:logs ---
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
+ 
670
+ --- Subcommand: runner:daemon:uninstall ---
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
+ 
649
679
  --- Subcommand: runner:list ---
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
 
@@ -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`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gapi/gcli",
3
- "version": "1.8.239",
3
+ "version": "1.8.240",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/Stradivario/gapi.git"