Skip to content

Commit 357059b

Browse files
committed
Add possibility to start cli commands with own server. Restore possibility to override configs in CLI commands.
1 parent a5999af commit 357059b

9 files changed

Lines changed: 560 additions & 232 deletions

File tree

docs/api-protocol.md

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,51 @@ and starts extension runners.
129129

130130
---
131131

132+
#### `workspace/setConfigOverrides`
133+
134+
Set persistent handler config overrides on the server. Overrides are stored for
135+
the lifetime of the server and applied to all subsequent action runs — unlike the
136+
`config_overrides` field that was previously accepted by `actions/runBatch`, which
137+
required runners to be stopped first.
138+
139+
- **Type:** request
140+
- **Clients:** CLI
141+
- **Status:** implemented
142+
143+
**Params:**
144+
145+
```json
146+
{
147+
"overrides": {
148+
"lint": {
149+
"ruff": {"line_length": 120},
150+
"": {"some_action_level_param": "value"}
151+
}
152+
}
153+
}
154+
```
155+
156+
`overrides` format: `{action_name: {handler_name_or_"": {param: value}}}`.
157+
The empty-string key `""` means the override applies to all handlers of that action.
158+
159+
**Result:** `{}`
160+
161+
**Behaviour:**
162+
163+
- Overrides are stored in the server's workspace context and applied to all
164+
subsequent action runs.
165+
- If extension runners are already running, they receive a config update
166+
immediately; initialized handlers are dropped and will be re-initialized with
167+
the new config on the next run.
168+
- The CLI `run` command sends this message **before** `workspace/addDir` in
169+
standalone mode (`--own-server`), so runners always start with the correct
170+
config and no update push is required.
171+
- Config overrides are **not supported** in `--shared-server` mode: the CLI
172+
will print a warning and ignore them.
173+
- Calling this method again replaces the previous overrides entirely.
174+
175+
---
176+
132177
#### `workspace/removeDir`
133178

134179
Remove a workspace directory. Stops runners for affected projects and removes them
@@ -240,7 +285,6 @@ Execute a single action on a project.
240285
"action": "lint",
241286
"project": "finecode",
242287
"params": {"file_paths": ["/path/to/file.py"]},
243-
"config_overrides": {"ruff": {"line_length": 120}},
244288
"options": {
245289
"result_formats": ["json", "string"],
246290
"trigger": "user",
@@ -284,7 +328,6 @@ Execute multiple actions across multiple projects. Used for batch operations.
284328
"actions": ["lint", "check_formatting"],
285329
"projects": ["finecode", "finecode_extension_api"],
286330
"params": {},
287-
"config_overrides": {},
288331
"options": {
289332
"concurrent": false,
290333
"result_formats": ["json", "string"],

docs/cli.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,36 @@ python -m finecode <command> [options]
99

1010
---
1111

12+
## Usage modes
13+
14+
The `run` command supports two usage modes.
15+
16+
### Standalone (one-shot) — default
17+
18+
Each `run` invocation is fully independent. FineCode starts a dedicated API server subprocess for the duration of the command, then shuts it down on exit. This is the default behavior.
19+
20+
```bash
21+
python -m finecode run lint
22+
```
23+
24+
Use this in CI/CD pipelines or any context where you don't want persistent background processes. Results from one action can be saved to the file cache and referenced by a later action via `--map-payload-fields` (see the `run` reference below).
25+
26+
### Persistent server
27+
28+
A long-lived API server holds warm state — loaded configuration, started runners — across multiple `run` calls. Use `--shared-server` to connect to a running shared instance instead of starting a dedicated one.
29+
30+
```bash
31+
# Connect to the shared server (start it first if needed):
32+
python -m finecode run --shared-server lint
33+
python -m finecode run --shared-server format
34+
```
35+
36+
This mode is used automatically by the LSP and MCP integrations. It gives faster repeated runs because configuration loading and runner startup are amortized across calls.
37+
38+
The server waits 30 seconds after the last client disconnects before shutting down (configurable via `--disconnect-timeout` on `start-api-server`).
39+
40+
---
41+
1242
## `run`
1343

1444
Run one or more actions across projects.
@@ -24,6 +54,7 @@ python -m finecode run [options] <action> [<action> ...] [payload] [--config.<ke
2454
| `--workdir=<path>` | Use `<path>` as the workspace root instead of `cwd` |
2555
| `--project=<name>` | Run only in this project. Repeatable for multiple projects. |
2656
| `--concurrently` | Run actions concurrently within each project |
57+
| `--shared-server` | Connect to the shared persistent API server instead of starting a dedicated one |
2758
| `--trace` | Enable verbose (trace-level) logging |
2859
| `--no-env-config` | Ignore `FINECODE_CONFIG_*` environment variables |
2960
| `--no-save-results` | Do not write action results to the cache directory |
@@ -155,14 +186,15 @@ Typically started automatically by MCP-compatible clients (for example, Claude C
155186

156187
## `start-api-server`
157188

158-
Start the FineCode API server standalone (TCP JSON-RPC), listen for client connections. Auto-stops when the last client disconnects.
189+
Start the FineCode API server standalone (TCP JSON-RPC), listen for client connections. Shuts down after the last client disconnects and the disconnect timeout expires.
159190

160191
```text
161-
python -m finecode start-api-server [--trace]
192+
python -m finecode start-api-server [--trace] [--disconnect-timeout=<seconds>]
162193
```
163194

164195
| Option | Description |
165196
| --- | --- |
166197
| `--trace` | Enable verbose logging |
198+
| `--disconnect-timeout=<seconds>` | Seconds to wait after the last client disconnects before shutting down (default: 30) |
167199

168200
Usually started automatically by `start-lsp` or `start-mcp`. Can also be started manually for debugging.

src/finecode/api_client.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,50 @@ async def get_tree(self, parent_node_id: str | None = None) -> dict:
135135
result = await self.request("actions/getTree", params)
136136
return result
137137

138+
async def set_config_overrides(
139+
self, overrides: dict
140+
) -> None:
141+
"""Set persistent handler config overrides on the server.
142+
143+
Overrides are stored for the lifetime of the server and applied to all
144+
subsequent action runs. Call this before ``add_dir`` if possible so that runners
145+
always start with the correct config and no update push is required.
146+
147+
overrides format: {action_name: {handler_name_or_"": {param: value}}}
148+
The empty-string key "" means the override applies to all handlers of
149+
that action.
150+
"""
151+
await self.request("workspace/setConfigOverrides", {"overrides": overrides})
152+
153+
async def run_batch(
154+
self,
155+
actions: list[str],
156+
projects: list[str] | None = None,
157+
params: dict | None = None,
158+
params_by_project: dict[str, dict] | None = None,
159+
options: dict | None = None,
160+
) -> dict:
161+
"""Run multiple actions across multiple (or all) projects.
162+
163+
Results are keyed by project path string, then action name.
164+
All result keys use snake_case (return_code, result_by_format).
165+
"""
166+
body: dict = {"actions": actions}
167+
if projects is not None:
168+
body["projects"] = projects
169+
if params:
170+
body["params"] = params
171+
if params_by_project:
172+
body["params_by_project"] = params_by_project
173+
if options:
174+
body["options"] = options
175+
return await self.request("actions/runBatch", body)
176+
138177
async def run_action(
139178
self,
140179
action: str,
141180
project: str,
142181
params: dict | None = None,
143-
config_overrides: dict | None = None,
144182
options: dict | None = None,
145183
) -> dict:
146184
"""Run an action on a project."""
@@ -151,8 +189,6 @@ async def run_action(
151189
}
152190
if params:
153191
body["params"] = params
154-
if config_overrides:
155-
body["config_overrides"] = config_overrides
156192
return await self.request("actions/run", body)
157193

158194
async def add_dir(self, dir_path: pathlib.Path) -> dict:

0 commit comments

Comments
 (0)