@gapi/gcli 1.8.243 → 1.8.245

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.
@@ -29,6 +29,11 @@ security, symptom table): `graphql-server/docs/features/local-ai-runners.md`.
29
29
  for "token refused" in sync with `RestartPreventExitStatus`. Tests must
30
30
  pass an explicit home dir to `SystemdUserService` (os.homedir() ignored a
31
31
  changed HOME under jest and wrote into the real `~/.config` once).
32
+ `runner:daemon:start`/`:restart` check the outcome after ~2 s
33
+ (`reportStarted`, exit 78 -> the `--reset` hint) - a refused runner exits
34
+ within a second, so "started" alone would be a lie. Your local `gcli` and
35
+ the installed unit may both run this repo's `release/index.js` (npm link),
36
+ so a build changes what the service runs on its next restart.
32
37
 
33
38
  ## Run it against a dev stack
34
39
 
@@ -44,3 +49,12 @@ node release/index.js runner:models --show qwen3.8:27b # keep the picker short
44
49
  Lambforge socket (acks, operation replies, pushes requests), `fakeOllama`
45
50
  plays Ollama (tags/show/streamed body, optional hold). Add a case there for
46
51
  any protocol change.
52
+
53
+ ## Shared with `workspace:*`
54
+
55
+ The WebSocket client is `TunnelConnection` (`src/commands/runner/tunnel.ts`);
56
+ `RunnerConnection` (Ollama) and `WorkspaceConnection` (local workspaces,
57
+ `src/commands/workspace/`, feature `local-workspaces`) are its two
58
+ subclasses. Protocol changes (frames, ack/ping, reconnect) go in the base
59
+ class and must keep both test files green (`test/runner.spec.ts`,
60
+ `test/workspace.spec.ts`).
package/AGENTS.md CHANGED
@@ -34,7 +34,7 @@ fragment in every repo the change touches.
34
34
  |---|---|
35
35
  | `gcli-command` | Adding a command: registration, lazy actions, `GraphqlClienAPI`, tests, build/release, regenerating `README-CLI.md` |
36
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 |
37
+ | `local-ai-runners` | `runner:*` - the outbound WS client bridging a user's Ollama to the chat (its `TunnelConnection` base also serves `workspace:*`) |
38
38
  | `changelog` | Feature-keyed changelog fragments + feature docs |
39
39
 
40
40
  ## Documentation map
@@ -46,6 +46,7 @@ fragment in every repo the change touches.
46
46
  | Features (current state) | [`docs/features/README.md`](docs/features/README.md) |
47
47
  | Change history of a feature | `node scripts/changelog.mjs render --feature <slug>` |
48
48
  | Local AI runner (slice; canonical in graphql-server) | [`docs/features/local-ai-runners.md`](docs/features/local-ai-runners.md) |
49
+ | Local workspaces - `workspace:*` (slice; canonical in graphql-server) | [`docs/features/local-workspaces.md`](docs/features/local-workspaces.md) |
49
50
  | Claude Code config in this package | [`.claude/README.md`](.claude/README.md) |
50
51
 
51
52
  ## Key patterns
@@ -102,4 +103,4 @@ to debug" live.
102
103
  gateway points every later command (and `runner:start`) at dev.
103
104
  - Jest needs the `moduleNameMapper` in `jest.config.js` for the `~/` alias -
104
105
  without it any spec importing command code fails to resolve.
105
- - `runner:start` needs Node >= 22 (global `WebSocket`).
106
+ - `runner:start` and `workspace:start` need Node >= 22 (global `WebSocket`).
package/README-CLI.md CHANGED
@@ -58,11 +58,17 @@ Commands:
58
58
  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)
59
59
  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)
60
60
  runner:daemon:status [options] Show the runner service status (systemctl --user status)
61
+ runner:daemon:start [options] Start the installed runner service, and report whether it stays up
62
+ runner:daemon:stop [options] Stop the runner service (it stays installed and starts at your next login)
63
+ runner:daemon:restart [options] Restart the runner service (e.g. after `gcli login` to another server)
61
64
  runner:daemon:logs [options] Follow the runner service log (journalctl --user -f)
62
65
  runner:daemon:uninstall [options] Stop and remove the runner service (the runner stays registered)
63
66
  runner:list List your local AI runners and the models they serve
64
67
  runner:models [options] Choose which of a runner's models appear in the chat model picker (no flags: list them)
65
68
  runner:delete [options] Delete a local AI runner and revoke its token
69
+ workspace:start [options] Connect the current directory to the Lambforge chat as a local workspace: the AI agents can read/edit its files and run commands here (outbound connection only - works behind NAT)
70
+ workspace:list [options] List your local workspaces (online status, directory, processes)
71
+ workspace:delete [options] Delete a local workspace and revoke its token (default: the one registered for this directory)
66
72
  help [command] display help for command
67
73
  
68
74
  --- Subcommand: login ---
@@ -132,6 +138,7 @@ Options:
132
138
  --configs <configs...> List of kubernetes config names
133
139
  --secrets <secrets...> List of kubernetes secret names
134
140
  --envSecrets <envSecrets...> List of kubernetes secret names projected into process.env (graphql environment only)
141
+ --bodyLimit <bodyLimit> Largest request body the lambda accepts, e.g. 20mb (default 10mb, max 100mb)
135
142
  --network <network> Can be private or public meaning exposed to the router or just for private available can be both also --network private --network public
136
143
  --route <route> Lambda route in which will be accessible
137
144
  --code <code> URL or local path for single file source code
@@ -174,6 +181,7 @@ Options:
174
181
  --configs <configs...> List of kubernetes config names
175
182
  --secrets <secrets...> List of kubernetes secret names
176
183
  --envSecrets <envSecrets...> List of kubernetes secret names projected into process.env (graphql environment only)
184
+ --bodyLimit <bodyLimit> Largest request body the lambda accepts, e.g. 20mb (default 10mb, max 100mb)
177
185
  --network <network> Can be private or public meaning exposed to the router or just for private available can be both also --network private --network public
178
186
  --route <route> Lambda route in which will be accessible
179
187
  --code <code> URL or local path for single file source code
@@ -289,6 +297,7 @@ Options:
289
297
  --configs <configs...> List of kubernetes config names
290
298
  --secrets <secrets...> List of kubernetes secret names
291
299
  --envSecrets <envSecrets...> List of kubernetes secret names projected into process.env (graphql environment only)
300
+ --bodyLimit <bodyLimit> Largest request body the lambda accepts, e.g. 20mb (default 10mb, max 100mb)
292
301
  --network <network> Can be private or public meaning exposed to the router or just for private available can be both also --network private --network public
293
302
  --route <route> Lambda route in which will be accessible
294
303
  --code <code> URL or local path for single file source code
@@ -873,6 +882,33 @@ Usage: gcli runner:daemon:status [options]
873
882
 
874
883
  Show the runner service status (systemctl --user status)
875
884
 
885
+ Options:
886
+ --service <name> systemd unit name (default: lambforge-runner)
887
+ -h, --help display help for command
888
+ 
889
+ --- Subcommand: runner:daemon:start ---
890
+ Usage: gcli runner:daemon:start [options]
891
+
892
+ Start the installed runner service, and report whether it stays up
893
+
894
+ Options:
895
+ --service <name> systemd unit name (default: lambforge-runner)
896
+ -h, --help display help for command
897
+ 
898
+ --- Subcommand: runner:daemon:stop ---
899
+ Usage: gcli runner:daemon:stop [options]
900
+
901
+ Stop the runner service (it stays installed and starts at your next login)
902
+
903
+ Options:
904
+ --service <name> systemd unit name (default: lambforge-runner)
905
+ -h, --help display help for command
906
+ 
907
+ --- Subcommand: runner:daemon:restart ---
908
+ Usage: gcli runner:daemon:restart [options]
909
+
910
+ Restart the runner service (e.g. after `gcli login` to another server)
911
+
876
912
  Options:
877
913
  --service <name> systemd unit name (default: lambforge-runner)
878
914
  -h, --help display help for command
@@ -924,3 +960,52 @@ Delete a local AI runner and revoke its token
924
960
  Options:
925
961
  --id <id> Runner id (default: the runner saved on this machine)
926
962
  -h, --help display help for command
963
+ 
964
+ --- Subcommand: workspace:start ---
965
+ Usage: gcli workspace:start [options]
966
+
967
+ Connect the current directory to the Lambforge chat as a local workspace: the AI agents can read/edit its files and run commands here (outbound connection only - works behind NAT)
968
+
969
+ Options:
970
+ -n, --name <name> Workspace name shown in the chat (default:
971
+ directory name)
972
+ -p, --project <projectId> Project the workspace belongs to (default: `gcli
973
+ project:use`)
974
+ -t, --token <token> Workspace token (instead of registering with `gcli
975
+ login`)
976
+ --allow <commands> Comma-separated commands that run without
977
+ approval; * matches anything (e.g. "npm test,npm
978
+ run *")
979
+ --no-default-allow Do not pre-allow read-only commands (git
980
+ status/diff/log, ls, ...)
981
+ --yolo Run EVERY command without asking (only in a
982
+ sandbox / a repo you can reset)
983
+ --no-terminal-approval Approve commands only in the chat, never prompt in
984
+ this terminal
985
+ --env <names> Comma-separated environment variables to pass to
986
+ commands (default: a minimal environment)
987
+ --inherit-env Pass your whole shell environment to commands
988
+ --allow-secrets Let the chat read/write .env files, keys and other
989
+ secrets files
990
+ --wsUrl <url> Lambforge subscriptions endpoint override
991
+ --reset Forget the workspace saved for this directory and
992
+ register a new one
993
+ -h, --help display help for command
994
+ 
995
+ --- Subcommand: workspace:list ---
996
+ Usage: gcli workspace:list [options]
997
+
998
+ List your local workspaces (online status, directory, processes)
999
+
1000
+ Options:
1001
+ -p, --project <projectId> Only this project
1002
+ -h, --help display help for command
1003
+ 
1004
+ --- Subcommand: workspace:delete ---
1005
+ Usage: gcli workspace:delete [options]
1006
+
1007
+ Delete a local workspace and revoke its token (default: the one registered for this directory)
1008
+
1009
+ Options:
1010
+ --id <id> Workspace id (see `gcli workspace:list`)
1011
+ -h, --help display help for command
package/README.md CHANGED
@@ -890,7 +890,7 @@ running until Ctrl+C and reconnects on its own.
890
890
  | `gcli runner:list` | Your runners: online status, last seen, models |
891
891
  | `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) |
892
892
  | `gcli runner:delete` | Delete a runner and revoke its token (`--id`, default: this machine's) |
893
- | `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 |
893
+ | `gcli runner:start:daemon` / `runner:daemon:install` | Install + start the runner as a systemd user service (Linux); `runner:daemon:start`, `:stop`, `:restart`, `:status`, `:logs`, `:uninstall` manage it |
894
894
 
895
895
  ### Run it in the background (systemd, Linux)
896
896
 
@@ -899,6 +899,9 @@ npm i -g @gapi/gcli # a stable install - not the npx cache
899
899
  gcli login
900
900
  gcli runner:start:daemon # = runner:daemon:install; same options as runner:start
901
901
  gcli runner:daemon:status
902
+ gcli runner:daemon:stop # stop it (stays installed, starts at next login)
903
+ gcli runner:daemon:start # start it again; tells you if it does not stay up
904
+ gcli runner:daemon:restart # e.g. after `gcli login` to another server
902
905
  gcli runner:daemon:logs # follow the log (Ctrl+C to stop following)
903
906
  gcli runner:daemon:uninstall # stop + remove the service (runner stays registered)
904
907
  ```
@@ -920,6 +923,56 @@ Notes:
920
923
  - Use Profile -> Local AI runners in the web app to see runners, choose
921
924
  models with checkboxes, and create tokens.
922
925
 
926
+ ## Local Workspaces (the chat works in your own project)
927
+
928
+ `gcli workspace:start` connects the **current directory** to the Lambforge
929
+ chat: the AI agents can read and edit its files, run commands (`npm install`,
930
+ `npm test`, `git diff`, `tsc`) and keep dev servers running (`npm start`) -
931
+ on your machine, in your real (git) project. Same **outbound** WebSocket as
932
+ the Ollama runner: works behind NAT, opens no port. Web workspaces keep
933
+ working as before; this is an additional, local kind.
934
+
935
+ ```bash
936
+ gcli login
937
+ gcli project:use <projectId> # the workspace belongs to one project
938
+ cd ~/code/my-app
939
+ gcli workspace:start # --allow "npm test,npm run *" to skip approvals for those
940
+ ```
941
+
942
+ Then, in a chat of that project (mode Auto): "run the tests in my local
943
+ project and fix what fails", "start the dev server and check it boots". The
944
+ agents only touch files under that directory.
945
+
946
+ **Commands need your approval** - in this terminal (`[y]es / [a]lways for
947
+ this session / [N]o`) or on the card in the chat; whichever you answer first
948
+ wins. Read-only commands (`git status/diff/log/show`, `ls`, `pwd`, ...) and
949
+ your `--allow` rules run unasked. Destructive ones (`git push`, `git reset
950
+ --hard`, `git clean`, `git commit`, `rm -r`, `sudo`, `curl ... | sh`, ...)
951
+ always ask. `--yolo` runs everything unasked - only for a sandbox or a repo
952
+ you can reset. Nobody answers in 5 minutes = denied.
953
+
954
+ | Command | Description |
955
+ |---|---|
956
+ | `gcli workspace:start` | Connect this directory. First run registers it (saved in `~/.graphql-server/workspaces.json`, per directory). Options: `--name`, `--project <id>` (default: `gcli project:use`), `--allow <list>` (comma-separated, `*` wildcard), `--no-default-allow`, `--yolo`, `--no-terminal-approval` (approve only in the chat), `--env A,B` (pass these variables to commands), `--inherit-env` (pass your whole environment), `--allow-secrets` (let the chat read `.env`/keys), `--token`, `--wsUrl`, `--reset` |
957
+ | `gcli workspace:list` | Your local workspaces: online, project, directory, running processes (`--project`) |
958
+ | `gcli workspace:delete` | Remove a workspace and revoke its token (`--id`, default: this directory's). Nothing on disk is deleted |
959
+
960
+ What the chat can and can't do:
961
+
962
+ - Files: only under the directory (symlinks resolved); `.git` is read-only;
963
+ `.env*`, private keys, `.npmrc`, ... are invisible unless `--allow-secrets`;
964
+ `node_modules` and `.gitignore`d files are not listed or searched.
965
+ - Commands run as you, in a minimal environment (PATH, HOME, nvm/pnpm
966
+ dirs - none of your shell's tokens) unless `--env` / `--inherit-env`.
967
+ - Background processes (`npm start`) live until the chat stops them or you
968
+ stop gcli - Ctrl+C stops every one of them.
969
+ - Every operation is printed here (`->` read, `✎` write, `$` command,
970
+ `▶` process), so you can watch what the agent does.
971
+ - Review with `git diff` and commit yourself - the agents are told not to
972
+ commit, push or reset.
973
+
974
+ Requires Node.js >= 22.
975
+
923
976
  ## Build System & Local Development
924
977
 
925
978
  `gcli` includes a high-performance bundler powered by **esbuild**.
@@ -0,0 +1,13 @@
1
+ ## 2026-10-23 - local-workspaces: gcli workspace:start/list/delete - the chat agents work in a local directory
2
+
3
+ **Feature:** local-workspaces · **Type:** feat
4
+
5
+ - New `workspace:start` (connect the current directory; registers it with the `gcli login` account in the `--project` / `project:use` project, token saved per directory in `~/.graphql-server/workspaces.json`), `workspace:list`, `workspace:delete`. Options: `--allow`, `--no-default-allow`, `--yolo`, `--no-terminal-approval`, `--env`, `--inherit-env`, `--allow-secrets`, `--token`, `--wsUrl`, `--reset`.
6
+ - The runner's WebSocket client is now a shared base class `TunnelConnection` (`src/commands/runner/tunnel.ts`); `RunnerConnection` keeps its behaviour (same tests) and `WorkspaceConnection` (`src/commands/workspace/connection.ts`) serves `POST /workspace/<op>`.
7
+ - `WorkspaceFiles`: everything confined to the root (symlinks resolved), `.git` read-only, secrets files hidden unless `--allow-secrets`, git-aware listing/search (`.gitignore` respected, `node_modules` skipped), edits by exact unique match with content-hash conflict checks.
8
+ - `CommandPolicy` + `ApprovalGate`: read-only defaults and `--allow` rules run unasked; everything else asks in the terminal AND on the chat card (first answer wins, 5-min expiry); destructive commands always ask unless `--yolo`; "always" remembers program + verb for the session.
9
+ - `runCommand` / `ProcessManager`: process groups (children die too), timeouts, output tails, background processes with a ring buffer and `waitFor`; all stopped when gcli exits. Commands get a minimal environment unless `--env` / `--inherit-env`.
10
+ - Backend half in graphql-server (registry, tunnel, agent tools, LOCAL worker), UI in gql-fission. Canonical doc: `graphql-server/docs/features/local-workspaces.md`.
11
+ - **Test:** `npx jest --coverage=false test/workspace test/runner` - files (escape, symlink, secrets, .gitignore, edit/hash), policy, approvals (web wins, early web answer, terminal deny, expiry), commands/processes (exit codes, timeout kill, waitFor/logs/stop), the connection end to end against a fake server.
12
+ - **Debug:** gcli's own log (`->`, `✎`, `$`, `▶`, `?`, `x`); "refused this workspace" = deleted -> `--reset`; wrong environment -> `~/.graphql-server/url`.
13
+ - **Verification:** full gcli suite 99/99 passing, `npx tsc --noEmit -p .` clean, `npx eslint src test` clean. Not yet run against a live backend.
@@ -0,0 +1,25 @@
1
+ ## 2026-10-24 - local-ai-runners: runner:daemon:start / stop / restart, and start reports whether the runner stays up
2
+
3
+ **Feature:** local-ai-runners · **Type:** feat
4
+
5
+ Why: after `runner:daemon:install` there was no gcli way to start or stop the service (only raw `systemctl --user`), and a service whose token Lambforge refuses failed silently: `runner:daemon:status` showed `status=78` with no explanation of what to do.
6
+
7
+ - New commands (`src/commands/runner/index.ts`, `daemon-commands.ts`), all with `--service`:
8
+ - `runner:daemon:start` and `runner:daemon:restart` run `systemctl --user start/restart`. After ~2 s, `reportStarted` reads `ActiveState`/`ExecMainStatus` (`SystemdUserService.outcome`) and reports:
9
+ - running: says so;
10
+ - exit 78 (`RUNNER_AUTH_EXIT_CODE`): the token was refused - the runner was deleted, or registered on another server than `~/.graphql-server/url` - with the hint to re-register via `gcli runner:daemon:install --reset`;
11
+ - any other exit: points to `runner:daemon:logs`.
12
+ - `runner:daemon:stop`: `systemctl --user stop`. The unit stays enabled and starts at the next login; `uninstall` removes it.
13
+ - All three refuse with "not installed - run `gcli runner:daemon:install` first" when the unit file is missing.
14
+ - `SystemdUserService`: `installed()`, `start()`, `stop()`, `restart()`, `outcome()`.
15
+ - README, `README-CLI.md` (regenerated) and `docs/features/local-ai-runners.md` updated.
16
+ - **Test:**
17
+ - `npx jest --coverage=false test/runner-daemon.spec.ts` covers: the systemctl calls, `installed()`, and the three `reportStarted` outcomes.
18
+ - Live: `gcli runner:daemon:stop`, `gcli runner:daemon:start`, then `gcli runner:daemon:status`.
19
+ - **Debug:**
20
+ - "did not stay up": `gcli runner:daemon:logs`.
21
+ - "refused this runner's token": compare `~/.graphql-server/url` with the server the runner was registered on, then `runner:daemon:install --reset`.
22
+ - **Verification:**
23
+ - jest: 11 suites, 103 tests pass (4 new). `tsc --noEmit` and eslint are clean.
24
+ - `npm run build`: `--help` lists the new commands.
25
+ - Not run against the real systemd unit.
@@ -31,6 +31,7 @@ Outline (drop sections that don't apply):
31
31
  | Feature | Doc | Notes |
32
32
  |---|---|---|
33
33
  | `local-ai-runners` | [local-ai-runners.md](local-ai-runners.md) | `runner:*` commands; canonical doc in graphql-server |
34
+ | `local-workspaces` | [local-workspaces.md](local-workspaces.md) | `workspace:*` commands - the chat agents edit files and run commands in a local directory; canonical doc in graphql-server |
34
35
  | `lambforge-agent-skill` | [lambforge-agent-skill.md](lambforge-agent-skill.md) | The agent skill that teaches gcli + `mcp:start`; canonical doc in gql-fission |
35
36
  | `dedicated-platform-cluster` | [dedicated-platform-cluster.md](dedicated-platform-cluster.md) | `cluster:status/health/scale/autoscale/...` commands; canonical doc in lambforge-agent |
36
37
  | `ci-token-scopes` | [ci-token-scopes.md](ci-token-scopes.md) | Which permission each command needs; `mq-trigger:*` commands; canonical doc in graphql-server |
@@ -14,6 +14,8 @@ what lives in gcli.
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
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:start` / `:restart` `[--service]` | `systemctl --user start/restart`, then after ~2 s reads `ActiveState`/`ExecMainStatus` (`SystemdUserService.outcome`): running, or an error - exit 78 = token refused (deleted, or registered on another server than `~/.graphql-server/url`) with the `runner:daemon:install --reset` hint. Refuses when the unit is not installed |
18
+ | `gcli runner:daemon:stop` `[--service]` | `systemctl --user stop` (stays enabled: starts again at the next login) |
17
19
  | `gcli runner:daemon:status` / `:logs` / `:uninstall` | `systemctl --user status`, `journalctl --user -f`, disable --now + remove the unit |
18
20
 
19
21
  Endpoints: `--wsUrl` > `runner.json` > derived from the `gcli login` URL
@@ -0,0 +1,42 @@
1
+ # Local workspaces (gcli slice)
2
+
3
+ **Status:** built 2026-10-23. **Feature slug:** `local-workspaces`.
4
+ Canonical doc (flow, backend, security model, debugging):
5
+ `graphql-server/docs/features/local-workspaces.md`.
6
+
7
+ `gcli workspace:start` serves the current directory to the chat agents over
8
+ the outbound WebSocket tunnel local AI runners use.
9
+
10
+ ## Files
11
+
12
+ | Path | Responsibility |
13
+ |---|---|
14
+ | `src/commands/runner/tunnel.ts` | `TunnelConnection` - the shared subscriptions-transport-ws client (connect/reconnect, heartbeat timer, request dispatch, ack/ping, `FrameSender`); `RunnerConnection` and `WorkspaceConnection` extend it |
15
+ | `src/commands/workspace/connection.ts` | `WorkspaceConnection`: `workspaceToken`, `localWorkspaceHeartbeat`, the ops allow-list (`WORKSPACE_OPS`, mirrors the backend), `dispatch(op)`, approval frames |
16
+ | `src/commands/workspace/files.ts` | `WorkspaceFiles`: root confinement (incl. symlinks), `.git` read-only, secrets files, git-aware list/search, read/write/edit with content hashes |
17
+ | `src/commands/workspace/policy.ts` | `CommandPolicy`: default allow rules, `--allow`, always-ask patterns, segment matching, "always" rules |
18
+ | `src/commands/workspace/approvals.ts` | `ApprovalGate` (policy -> web card + terminal prompt race, timeout, settle), `TerminalPrompt` |
19
+ | `src/commands/workspace/processes.ts` | `runCommand` (timeout, process-group kill, output tails), `ProcessManager` (background processes, ring buffer, waitFor), `buildEnv` |
20
+ | `src/commands/workspace/{start,list,delete,config,index}.ts` | the commands; `~/.graphql-server/workspaces.json` (0600, keyed by directory) |
21
+
22
+ ## Protocol (what the backend sends)
23
+
24
+ `POST /workspace/<op>` with a JSON body, answered `head(status) + chunks + end`
25
+ with a JSON body; errors are `{ error }` with 400/403/404/409/500. Ops:
26
+ `info list read write edit delete search exec process/start process/list
27
+ process/logs process/stop`. Anything else is refused before the ack.
28
+ `type: 'approval'` frames carry `{ decision: allow|always|deny }` for an
29
+ approval id this client created with `requestLocalWorkspaceApproval`.
30
+
31
+ ## How to test
32
+
33
+ `npx jest --coverage=false test/workspace` - real files and processes in a
34
+ temp dir; the connection runs against a fake subscriptions server
35
+ (edit over the tunnel, refused paths, web approval -> command runs, denial).
36
+
37
+ ## How to debug
38
+
39
+ gcli prints every op: `->` read/list/search, `✎` write/edit/delete, `$`
40
+ command (+ exit code and duration), `▶` / `■` processes, `?` approval
41
+ needed, `x` failed/cancelled. "refused this workspace" = deleted on the web
42
+ (`--reset`). Pointing at the wrong environment: `~/.graphql-server/url`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gapi/gcli",
3
- "version": "1.8.243",
3
+ "version": "1.8.245",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/Stradivario/gapi.git"