@junwu168/openshell 0.1.3 → 0.1.4
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/dist/core/audit/log-store.js +1 -1
- package/dist/core/orchestrator.d.ts +2 -2
- package/dist/core/orchestrator.js +3 -3
- package/dist/core/result.d.ts +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/opencode/plugin.d.ts +1 -1
- package/dist/opencode/plugin.js +8 -8
- package/package.json +6 -1
- package/.claude/settings.local.json +0 -25
- package/bun.lock +0 -368
- package/docs/superpowers/notes/2026-03-25-opencode-remote-tools-handoff.md +0 -81
- package/docs/superpowers/notes/2026-03-26-openshell-pre-release-review.md +0 -174
- package/docs/superpowers/plans/2026-03-25-opencode-remote-tools.md +0 -1656
- package/docs/superpowers/plans/2026-03-25-server-registry-cli.md +0 -54
- package/docs/superpowers/plans/2026-03-26-config-backed-credential-registry.md +0 -494
- package/docs/superpowers/plans/2026-03-26-openshell-release-prep.md +0 -639
- package/docs/superpowers/specs/2026-03-25-opencode-remote-tools-design.md +0 -378
- package/docs/superpowers/specs/2026-03-26-config-backed-credential-registry-design.md +0 -272
- package/docs/superpowers/specs/2026-03-26-openshell-release-prep-design.md +0 -197
- package/examples/opencode-local/opencode.json +0 -19
- package/scripts/openshell.ts +0 -3
- package/scripts/server-registry.ts +0 -3
- package/src/cli/openshell.ts +0 -65
- package/src/cli/server-registry.ts +0 -476
- package/src/core/audit/git-audit-repo.ts +0 -42
- package/src/core/audit/log-store.ts +0 -20
- package/src/core/audit/redact.ts +0 -4
- package/src/core/contracts.ts +0 -51
- package/src/core/orchestrator.ts +0 -1082
- package/src/core/patch.ts +0 -11
- package/src/core/paths.ts +0 -32
- package/src/core/policy.ts +0 -30
- package/src/core/registry/server-registry.ts +0 -505
- package/src/core/result.ts +0 -16
- package/src/core/ssh/ssh-runtime.ts +0 -355
- package/src/index.ts +0 -3
- package/src/opencode/plugin.ts +0 -242
- package/src/product/install.ts +0 -43
- package/src/product/opencode-config.ts +0 -118
- package/src/product/uninstall.ts +0 -47
- package/src/product/workspace-tracker.ts +0 -69
- package/tests/integration/fake-ssh-server.ts +0 -97
- package/tests/integration/install-lifecycle.test.ts +0 -85
- package/tests/integration/orchestrator.test.ts +0 -767
- package/tests/integration/ssh-runtime.test.ts +0 -122
- package/tests/unit/audit.test.ts +0 -221
- package/tests/unit/build-layout.test.ts +0 -28
- package/tests/unit/opencode-config.test.ts +0 -100
- package/tests/unit/opencode-plugin.test.ts +0 -358
- package/tests/unit/openshell-cli.test.ts +0 -60
- package/tests/unit/paths.test.ts +0 -64
- package/tests/unit/plugin-export.test.ts +0 -10
- package/tests/unit/policy.test.ts +0 -53
- package/tests/unit/release-docs.test.ts +0 -31
- package/tests/unit/result.test.ts +0 -28
- package/tests/unit/server-registry-cli.test.ts +0 -673
- package/tests/unit/server-registry.test.ts +0 -452
- package/tests/unit/workspace-tracker.test.ts +0 -57
- package/tsconfig.json +0 -14
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
# Open Code v1 Design: OpenCode Remote Tools
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
Open Code is a terminal-only plugin for AI coding CLIs. In v1, the first host integration is `opencode`.
|
|
6
|
-
|
|
7
|
-
The product goal is to let an AI model operate on one or more remote Linux servers over SSH while keeping credentials isolated from the model, enforcing strict user approvals for risky actions, and preserving an audit trail on the client machine.
|
|
8
|
-
|
|
9
|
-
This document defines the design boundary for v1 so implementation planning can proceed without revisiting scope or architecture.
|
|
10
|
-
|
|
11
|
-
## Problem Statement
|
|
12
|
-
|
|
13
|
-
AI coding CLIs are useful for local development, but infrastructure and operations work often happens across multiple remote machines. Existing CLI tools do not provide a safe, structured way for a model to:
|
|
14
|
-
|
|
15
|
-
- work against multiple remote servers in one session,
|
|
16
|
-
- use isolated SSH credentials that the model cannot read,
|
|
17
|
-
- require explicit approval before risky remote actions,
|
|
18
|
-
- preserve a local audit trail of commands and file changes, and
|
|
19
|
-
- evolve cleanly to support additional host CLIs later.
|
|
20
|
-
|
|
21
|
-
## Goals
|
|
22
|
-
|
|
23
|
-
- Integrate with `opencode` as the first supported host CLI.
|
|
24
|
-
- Expose explicit remote tools rather than pretending the remote machine is the local filesystem.
|
|
25
|
-
- Support multiple registered servers from the start.
|
|
26
|
-
- Store server credentials in a local encrypted registry controlled by Open Code, not by model-visible config files.
|
|
27
|
-
- Allow clearly safe Linux inspection commands to run without approval.
|
|
28
|
-
- Require per-action approval for every write operation.
|
|
29
|
-
- Require approval for middleware-oriented commands even when they appear read-only.
|
|
30
|
-
- Keep a structured local audit log for every action.
|
|
31
|
-
- Keep a local git-backed audit repository for file changes made through dedicated remote file write tools.
|
|
32
|
-
- Preserve a host-agnostic core so future adapters for `codex`, `claude code`, or similar CLIs can reuse the same runtime and policy logic.
|
|
33
|
-
|
|
34
|
-
## Non-Goals
|
|
35
|
-
|
|
36
|
-
- Supporting GUI editors or IDE plugins in v1.
|
|
37
|
-
- Hiding remote execution behind local-looking file or shell tools.
|
|
38
|
-
- Auto-approving writes for a whole session or server.
|
|
39
|
-
- Using git on the remote server for audit or rollback.
|
|
40
|
-
- Guaranteeing full file-level reconstruction for arbitrary shell commands that mutate remote state indirectly.
|
|
41
|
-
- Supporting every possible remote protocol beyond SSH in v1.
|
|
42
|
-
|
|
43
|
-
## Scope Summary
|
|
44
|
-
|
|
45
|
-
V1 is a terminal plugin for `opencode` backed by a host-agnostic local core library that runs in-process with the plugin. The plugin defines explicit remote tools. The core handles encrypted credentials, SSH execution, policy enforcement, audit logging, and local git snapshots for dedicated file-write operations.
|
|
46
|
-
|
|
47
|
-
## Architecture
|
|
48
|
-
|
|
49
|
-
### High-Level Shape
|
|
50
|
-
|
|
51
|
-
The system is split into two layers:
|
|
52
|
-
|
|
53
|
-
1. `opencode` host adapter
|
|
54
|
-
2. host-agnostic local core
|
|
55
|
-
|
|
56
|
-
In v1, the local core is an in-process library rather than a separate background daemon or long-lived local service. This keeps packaging, lifecycle, and approval flow simple for the first release while preserving clear boundaries that can later be extracted behind an IPC layer if needed.
|
|
57
|
-
|
|
58
|
-
The `opencode` adapter is responsible for:
|
|
59
|
-
|
|
60
|
-
- registering tool definitions with `opencode`,
|
|
61
|
-
- receiving tool calls,
|
|
62
|
-
- surfacing approval prompts to the user,
|
|
63
|
-
- returning structured tool results in the shape expected by `opencode`.
|
|
64
|
-
|
|
65
|
-
The local core is responsible for:
|
|
66
|
-
|
|
67
|
-
- encrypted server registry,
|
|
68
|
-
- SSH connection and session reuse,
|
|
69
|
-
- command classification and policy enforcement,
|
|
70
|
-
- remote file and command execution,
|
|
71
|
-
- audit log persistence,
|
|
72
|
-
- local git-backed file snapshotting.
|
|
73
|
-
|
|
74
|
-
### Core Module Boundaries
|
|
75
|
-
|
|
76
|
-
The local core should be split into the following modules:
|
|
77
|
-
|
|
78
|
-
- `host-adapter/opencode`
|
|
79
|
-
- The `opencode`-specific plugin layer.
|
|
80
|
-
- `tool-orchestrator`
|
|
81
|
-
- Entry point for all tool calls.
|
|
82
|
-
- Applies common sequencing: validate, resolve server, classify, request approval when needed, execute, audit, return result.
|
|
83
|
-
- `server-registry`
|
|
84
|
-
- Stores encrypted server definitions and authentication material.
|
|
85
|
-
- `ssh-runtime`
|
|
86
|
-
- Owns connection lifecycle, command execution, file transfer, and remote patch/write operations.
|
|
87
|
-
- `policy-engine`
|
|
88
|
-
- Classifies tool actions into auto-allow, approval-required, or reject.
|
|
89
|
-
- `audit-engine`
|
|
90
|
-
- Writes structured action logs and manages local git-backed snapshots for file changes.
|
|
91
|
-
|
|
92
|
-
### Design Rule
|
|
93
|
-
|
|
94
|
-
All host tools must call the `tool-orchestrator`. No tool may bypass policy or audit directly. This keeps behavior consistent across commands and file tools and prevents policy drift as more adapters are added later.
|
|
95
|
-
|
|
96
|
-
## Tool Surface
|
|
97
|
-
|
|
98
|
-
The v1 tool set is explicit and server-targeted:
|
|
99
|
-
|
|
100
|
-
- `list_servers()`
|
|
101
|
-
- `remote_exec(server, command, cwd?, timeout?)`
|
|
102
|
-
- `remote_read_file(server, path, offset?, length?)`
|
|
103
|
-
- `remote_write_file(server, path, content, mode?)`
|
|
104
|
-
- `remote_patch_file(server, path, diff_or_patch)`
|
|
105
|
-
- `remote_list_dir(server, path, recursive?, limit?)`
|
|
106
|
-
- `remote_stat(server, path)`
|
|
107
|
-
- `remote_find(server, path, pattern, glob?, limit?)`
|
|
108
|
-
|
|
109
|
-
### Tool Surface Principles
|
|
110
|
-
|
|
111
|
-
- Every operation names a `server` explicitly.
|
|
112
|
-
- The default behavior must favor clarity over convenience.
|
|
113
|
-
- Session-local implicit "current server" behavior is out of scope for v1.
|
|
114
|
-
- Dedicated file tools are preferred for file inspection and file mutation.
|
|
115
|
-
- `remote_exec` remains available for shell-oriented work that does not map cleanly to a dedicated file tool.
|
|
116
|
-
|
|
117
|
-
## Data Flow
|
|
118
|
-
|
|
119
|
-
For every tool invocation, the runtime sequence is:
|
|
120
|
-
|
|
121
|
-
1. The `opencode` adapter receives the tool call.
|
|
122
|
-
2. `tool-orchestrator` validates arguments and resolves the target server via `server-registry`.
|
|
123
|
-
3. `policy-engine` classifies the action.
|
|
124
|
-
4. If the action requires approval, the adapter presents the exact server, path, and command or write intent to the user.
|
|
125
|
-
5. `ssh-runtime` executes the command or file action.
|
|
126
|
-
6. `audit-engine` records the action and, when relevant, creates local snapshots.
|
|
127
|
-
7. A structured result is returned to the adapter and then back to `opencode`.
|
|
128
|
-
|
|
129
|
-
## Security And Permissions
|
|
130
|
-
|
|
131
|
-
### Permission Classes
|
|
132
|
-
|
|
133
|
-
V1 uses three policy outcomes:
|
|
134
|
-
|
|
135
|
-
- `auto-allow`
|
|
136
|
-
- `approval-required`
|
|
137
|
-
- `reject`
|
|
138
|
-
|
|
139
|
-
### Auto-Allow
|
|
140
|
-
|
|
141
|
-
The following operations may run directly:
|
|
142
|
-
|
|
143
|
-
- dedicated read-focused file tools such as `remote_read_file`, `remote_list_dir`, `remote_stat`, and `remote_find`,
|
|
144
|
-
- clearly safe Linux inspection commands executed through `remote_exec`.
|
|
145
|
-
|
|
146
|
-
Examples of Linux inspection commands include:
|
|
147
|
-
|
|
148
|
-
- `cat`
|
|
149
|
-
- `grep`
|
|
150
|
-
- `find`
|
|
151
|
-
- `ls`
|
|
152
|
-
- `pwd`
|
|
153
|
-
- `uname`
|
|
154
|
-
- `df`
|
|
155
|
-
- `free`
|
|
156
|
-
- `ps`
|
|
157
|
-
- `systemctl status`
|
|
158
|
-
|
|
159
|
-
The exact allowlist belongs in implementation, but the design intent is a conservative rule-based allowlist.
|
|
160
|
-
|
|
161
|
-
### Approval-Required
|
|
162
|
-
|
|
163
|
-
The following actions must require explicit user approval for each execution:
|
|
164
|
-
|
|
165
|
-
- every file mutation,
|
|
166
|
-
- every shell command that may mutate remote state,
|
|
167
|
-
- every middleware-oriented command family even when the specific invocation appears read-only.
|
|
168
|
-
|
|
169
|
-
Examples of middleware-oriented command families include:
|
|
170
|
-
|
|
171
|
-
- `psql`
|
|
172
|
-
- `mysql`
|
|
173
|
-
- `redis-cli`
|
|
174
|
-
- `kubectl`
|
|
175
|
-
- `docker`
|
|
176
|
-
- `helm`
|
|
177
|
-
- cloud provider CLIs
|
|
178
|
-
|
|
179
|
-
Unknown commands must default to `approval-required`.
|
|
180
|
-
|
|
181
|
-
### Reject
|
|
182
|
-
|
|
183
|
-
The runtime may reject requests that are malformed or unsupported, such as:
|
|
184
|
-
|
|
185
|
-
- unknown server ids,
|
|
186
|
-
- invalid file paths or missing required arguments,
|
|
187
|
-
- unsupported patch formats,
|
|
188
|
-
- actions that violate hard safety rules defined by the implementation.
|
|
189
|
-
|
|
190
|
-
### Classification Strategy
|
|
191
|
-
|
|
192
|
-
Policy classification must be deterministic and rule-based. V1 must not rely on a model to decide whether a command is safe. The model may propose commands, but the core decides whether they run directly, require approval, or are rejected.
|
|
193
|
-
|
|
194
|
-
For `remote_exec`, the auto-allow path is intentionally narrow. Only simple invocations of allowlisted Linux inspection commands may bypass approval. Commands that include shell composition or higher-risk syntax must default to `approval-required`, including:
|
|
195
|
-
|
|
196
|
-
- pipes,
|
|
197
|
-
- redirection,
|
|
198
|
-
- command chaining,
|
|
199
|
-
- subshells,
|
|
200
|
-
- compound shell expressions,
|
|
201
|
-
- other command forms that require shell parsing beyond a single straightforward invocation.
|
|
202
|
-
|
|
203
|
-
## Credential Model
|
|
204
|
-
|
|
205
|
-
### Storage
|
|
206
|
-
|
|
207
|
-
Open Code manages its own encrypted local server registry.
|
|
208
|
-
|
|
209
|
-
Each server record stores:
|
|
210
|
-
|
|
211
|
-
- server id,
|
|
212
|
-
- host,
|
|
213
|
-
- port,
|
|
214
|
-
- username,
|
|
215
|
-
- labels or grouping metadata,
|
|
216
|
-
- authentication method,
|
|
217
|
-
- authentication material,
|
|
218
|
-
- optional non-secret metadata.
|
|
219
|
-
|
|
220
|
-
Supported v1 authentication methods:
|
|
221
|
-
|
|
222
|
-
- username plus password,
|
|
223
|
-
- imported private key or certificate material.
|
|
224
|
-
|
|
225
|
-
The registry encryption key should be protected by a client-controlled unlock mechanism such as an OS keychain entry or an equivalent local secret source selected during implementation. The design requirement is that registry decryption stays on the client machine and is independent of the model runtime.
|
|
226
|
-
|
|
227
|
-
### Isolation Requirements
|
|
228
|
-
|
|
229
|
-
- Credentials must not be stored in model-visible prompt files.
|
|
230
|
-
- Raw credentials must never be returned by tools.
|
|
231
|
-
- Tool calls reference servers by logical id, not by raw secret material.
|
|
232
|
-
- Decryption and secret handling stay inside the core.
|
|
233
|
-
|
|
234
|
-
## Multi-Server Model
|
|
235
|
-
|
|
236
|
-
Multi-server support is a first-class requirement.
|
|
237
|
-
|
|
238
|
-
Design implications:
|
|
239
|
-
|
|
240
|
-
- every tool call requires an explicit server target,
|
|
241
|
-
- the registry supports many named servers,
|
|
242
|
-
- the SSH runtime may reuse connections per server during a session,
|
|
243
|
-
- audit artifacts must be partitioned by server so changes remain attributable.
|
|
244
|
-
|
|
245
|
-
Grouping, tags, or labels may be stored in the registry for future filtering, but group-based orchestration is not required in v1.
|
|
246
|
-
|
|
247
|
-
## Audit Model
|
|
248
|
-
|
|
249
|
-
Audit artifacts are stored on the client machine where `opencode` runs, not on the remote server.
|
|
250
|
-
|
|
251
|
-
### Structured Action Log
|
|
252
|
-
|
|
253
|
-
Every tool action writes a structured local log entry that includes:
|
|
254
|
-
|
|
255
|
-
- timestamp,
|
|
256
|
-
- server id,
|
|
257
|
-
- tool name,
|
|
258
|
-
- sanitized arguments,
|
|
259
|
-
- approval status,
|
|
260
|
-
- execution result metadata,
|
|
261
|
-
- changed path metadata when available.
|
|
262
|
-
|
|
263
|
-
Sensitive values must be redacted before persistence.
|
|
264
|
-
|
|
265
|
-
Action logging is a hard requirement, not a best-effort feature. Before any remote operation runs, the runtime must verify that the action log sink is writable. If structured logging cannot be persisted, the tool call must fail closed and the remote action must not execute.
|
|
266
|
-
|
|
267
|
-
### Git-Backed File Snapshot Audit
|
|
268
|
-
|
|
269
|
-
Dedicated file mutation tools participate in local git-backed snapshotting.
|
|
270
|
-
|
|
271
|
-
For `remote_write_file` and `remote_patch_file`, the audit flow is:
|
|
272
|
-
|
|
273
|
-
1. capture the pre-change remote file content if the file exists,
|
|
274
|
-
2. perform the write or patch,
|
|
275
|
-
3. capture the post-change remote file content,
|
|
276
|
-
4. store snapshots locally under an audit directory organized by server and remote path,
|
|
277
|
-
5. commit the snapshot change into a local git repository.
|
|
278
|
-
|
|
279
|
-
This repository exists only on the client machine and is used for inspection, history, and rollback support.
|
|
280
|
-
|
|
281
|
-
For dedicated file mutation tools, snapshotting is also a hard requirement. Before a mutating file action executes, the runtime must verify that the snapshot workspace and local git repository are writable. If pre-change snapshot preparation cannot succeed, the write must not run.
|
|
282
|
-
|
|
283
|
-
If the remote write succeeds but post-change snapshot persistence or git commit creation fails, the tool result must be returned as a partial failure rather than a clean success. The result must make clear that the remote state changed but audit completion failed, so the user can treat the operation as security-relevant follow-up work.
|
|
284
|
-
|
|
285
|
-
### Limit Of Audit Guarantees
|
|
286
|
-
|
|
287
|
-
`remote_exec` always creates structured command audit logs, but v1 does not promise file-level snapshots for arbitrary shell side effects. If a user wants file-level diffs and recovery guarantees, they should prefer dedicated file mutation tools.
|
|
288
|
-
|
|
289
|
-
## Error Handling
|
|
290
|
-
|
|
291
|
-
The system must return structured failures rather than flattening all problems into a generic error string.
|
|
292
|
-
|
|
293
|
-
Important error classes:
|
|
294
|
-
|
|
295
|
-
- server resolution failure,
|
|
296
|
-
- credential decrypt or load failure,
|
|
297
|
-
- SSH connection failure,
|
|
298
|
-
- authentication failure,
|
|
299
|
-
- timeout,
|
|
300
|
-
- policy rejection,
|
|
301
|
-
- approval denial,
|
|
302
|
-
- remote file-not-found,
|
|
303
|
-
- remote permission denied,
|
|
304
|
-
- non-zero command exit,
|
|
305
|
-
- patch apply failure,
|
|
306
|
-
- local audit persistence failure,
|
|
307
|
-
- local git snapshot failure.
|
|
308
|
-
|
|
309
|
-
Tool results should distinguish:
|
|
310
|
-
|
|
311
|
-
- whether the action was attempted,
|
|
312
|
-
- whether it completed,
|
|
313
|
-
- command exit status when applicable,
|
|
314
|
-
- stdout and stderr payloads or truncation metadata,
|
|
315
|
-
- audit/logging status when relevant.
|
|
316
|
-
|
|
317
|
-
## Testing Strategy
|
|
318
|
-
|
|
319
|
-
### Unit Tests
|
|
320
|
-
|
|
321
|
-
- `policy-engine` command classification
|
|
322
|
-
- argument validation and path validation
|
|
323
|
-
- secret redaction
|
|
324
|
-
- snapshot path mapping
|
|
325
|
-
- `tool-orchestrator` sequencing logic
|
|
326
|
-
|
|
327
|
-
### Integration Tests
|
|
328
|
-
|
|
329
|
-
- encrypted registry load and server resolution
|
|
330
|
-
- SSH connection against disposable local test targets
|
|
331
|
-
- read-only command execution
|
|
332
|
-
- approval-required command flow
|
|
333
|
-
- dedicated file read and write flows
|
|
334
|
-
- multi-server session isolation
|
|
335
|
-
|
|
336
|
-
### Audit Tests
|
|
337
|
-
|
|
338
|
-
- structured log persistence
|
|
339
|
-
- pre-change and post-change snapshot capture
|
|
340
|
-
- git commit creation for dedicated file writes
|
|
341
|
-
- server-specific audit partitioning
|
|
342
|
-
|
|
343
|
-
### Adapter Contract Tests
|
|
344
|
-
|
|
345
|
-
- tool registration shape for `opencode`
|
|
346
|
-
- argument-to-core mapping
|
|
347
|
-
- structured result mapping back to the host runtime
|
|
348
|
-
|
|
349
|
-
## Out Of Scope For V1
|
|
350
|
-
|
|
351
|
-
- session-wide approval grants
|
|
352
|
-
- group fan-out execution across multiple servers in one tool call
|
|
353
|
-
- background daemon deployment model
|
|
354
|
-
- remote git integration
|
|
355
|
-
- full TUI management experience for registry and audit browsing
|
|
356
|
-
- automatic command correction or command suggestion
|
|
357
|
-
- support for non-SSH transports
|
|
358
|
-
|
|
359
|
-
## Implementation Constraints For Planning
|
|
360
|
-
|
|
361
|
-
- The architecture must preserve a strict boundary between the `opencode` adapter and the host-agnostic core.
|
|
362
|
-
- The initial implementation should optimize for clear module ownership and extension over completeness of the tool catalog.
|
|
363
|
-
- Policy logic must be centralized and deterministic.
|
|
364
|
-
- File-change audit guarantees should attach only to dedicated file mutation tools in v1.
|
|
365
|
-
- The plan should assume future host adapters are likely, even though only `opencode` is in scope now.
|
|
366
|
-
|
|
367
|
-
## Success Criteria
|
|
368
|
-
|
|
369
|
-
The design is successful for v1 when all of the following are true:
|
|
370
|
-
|
|
371
|
-
- A user can register multiple remote servers locally with encrypted credentials.
|
|
372
|
-
- `opencode` can invoke explicit remote tools against any registered server.
|
|
373
|
-
- Clearly safe Linux inspection commands can run without approval.
|
|
374
|
-
- Every write requires explicit approval.
|
|
375
|
-
- Middleware-oriented commands require explicit approval even when nominally read-only.
|
|
376
|
-
- Dedicated remote file writes create local git-backed before/after audit history.
|
|
377
|
-
- Every tool action creates a structured local audit log.
|
|
378
|
-
- The implementation plan can be written without reopening architecture or scope decisions.
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
# Config-Backed Credential Registry Design
|
|
2
|
-
|
|
3
|
-
Date: 2026-03-26
|
|
4
|
-
Branch: `registry-cli`
|
|
5
|
-
Status: Approved for planning
|
|
6
|
-
|
|
7
|
-
## Summary
|
|
8
|
-
|
|
9
|
-
Replace the current encrypted registry plus OS keychain dependency with a simpler layered config model:
|
|
10
|
-
|
|
11
|
-
- Read server definitions from two config files: user-global first, then workspace override.
|
|
12
|
-
- Let workspace entries override global entries with the same `id`.
|
|
13
|
-
- Store password auth in plain text, documented as unsafe.
|
|
14
|
-
- Store certificate and private-key auth as filesystem paths only.
|
|
15
|
-
- Remove reliance on macOS Keychain, `keytar`, or any other OS credential store.
|
|
16
|
-
|
|
17
|
-
This change optimizes for operational simplicity and predictable cross-platform behavior over secret-at-rest protection.
|
|
18
|
-
|
|
19
|
-
## Goals
|
|
20
|
-
|
|
21
|
-
- Eliminate the current OS keychain dependency.
|
|
22
|
-
- Keep the credential model easy to inspect and edit manually.
|
|
23
|
-
- Support repo-local and user-global server definitions.
|
|
24
|
-
- Never store PEM or private-key contents in plugin-managed config.
|
|
25
|
-
- Preserve the current explicit remote-tool model and SSH runtime shape.
|
|
26
|
-
|
|
27
|
-
## Non-Goals
|
|
28
|
-
|
|
29
|
-
- Secret management or secure password storage.
|
|
30
|
-
- Automatic migration from the current encrypted registry.
|
|
31
|
-
- Environment-variable interpolation in v1 of this config-backed model.
|
|
32
|
-
- Support for more than two config scopes.
|
|
33
|
-
|
|
34
|
-
## Current Problem
|
|
35
|
-
|
|
36
|
-
The current branch stores the registry payload encrypted at rest and uses a `SecretProvider` backed by `keytar` to read or create a master key in the OS credential store. That behavior is acceptable on macOS but creates avoidable complexity, prompts, and portability concerns.
|
|
37
|
-
|
|
38
|
-
The user preference is to simplify:
|
|
39
|
-
|
|
40
|
-
- Plain-text passwords are acceptable if clearly documented as unsafe.
|
|
41
|
-
- Key and certificate auth should reference files already managed by the user.
|
|
42
|
-
- The plugin should not attempt to manage or protect key material itself.
|
|
43
|
-
|
|
44
|
-
## Proposed Design
|
|
45
|
-
|
|
46
|
-
### Config Scopes
|
|
47
|
-
|
|
48
|
-
Two config scopes will be supported:
|
|
49
|
-
|
|
50
|
-
1. Workspace config
|
|
51
|
-
2. User-global config
|
|
52
|
-
|
|
53
|
-
Read order:
|
|
54
|
-
|
|
55
|
-
1. Read the user-global config if present.
|
|
56
|
-
2. Read the workspace config if present.
|
|
57
|
-
3. Merge entries by `id`.
|
|
58
|
-
4. When both scopes define the same `id`, the workspace entry wins.
|
|
59
|
-
|
|
60
|
-
When a workspace entry overrides a global entry, CLI output should make that explicit so users understand which effective server is active.
|
|
61
|
-
|
|
62
|
-
### File Locations
|
|
63
|
-
|
|
64
|
-
Global config should live under the existing user config directory resolved by `env-paths`, for example:
|
|
65
|
-
|
|
66
|
-
- macOS: `~/Library/Preferences/open-code/servers.json`
|
|
67
|
-
- Linux: `~/.config/open-code/servers.json`
|
|
68
|
-
- Windows: `%AppData%/open-code/servers.json`
|
|
69
|
-
|
|
70
|
-
Workspace config should live in the repo/workspace root:
|
|
71
|
-
|
|
72
|
-
- `<workspace>/.open-code/servers.json`
|
|
73
|
-
|
|
74
|
-
The runtime should treat the presence of a workspace file as opt-in local override behavior.
|
|
75
|
-
|
|
76
|
-
### Data Model
|
|
77
|
-
|
|
78
|
-
The config file should remain intentionally small and explicit. Each file is a top-level JSON array of server records:
|
|
79
|
-
|
|
80
|
-
```json
|
|
81
|
-
[
|
|
82
|
-
{
|
|
83
|
-
"id": "prod-a",
|
|
84
|
-
"host": "10.0.0.5",
|
|
85
|
-
"port": 22,
|
|
86
|
-
"username": "ubuntu",
|
|
87
|
-
"labels": ["prod"],
|
|
88
|
-
"groups": ["cluster-a"],
|
|
89
|
-
"auth": {
|
|
90
|
-
"kind": "privateKey",
|
|
91
|
-
"privateKeyPath": "./keys/prod-a.pem"
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
]
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Supported auth shapes:
|
|
98
|
-
|
|
99
|
-
- `password`
|
|
100
|
-
- fields: `kind`, `secret`
|
|
101
|
-
- `privateKey`
|
|
102
|
-
- fields: `kind`, `privateKeyPath`, optional `passphrase`
|
|
103
|
-
- `certificate`
|
|
104
|
-
- fields: `kind`, `certificatePath`, `privateKeyPath`, optional `passphrase`
|
|
105
|
-
|
|
106
|
-
Notes:
|
|
107
|
-
|
|
108
|
-
- `password.secret` is stored in plain text.
|
|
109
|
-
- `privateKey` and `certificate` entries store paths only.
|
|
110
|
-
- No PEM, private key, or certificate contents are copied into config.
|
|
111
|
-
|
|
112
|
-
### Path Rules
|
|
113
|
-
|
|
114
|
-
Workspace config:
|
|
115
|
-
|
|
116
|
-
- may use relative key/cert paths
|
|
117
|
-
- relative paths resolve from workspace root
|
|
118
|
-
|
|
119
|
-
Global config:
|
|
120
|
-
|
|
121
|
-
- key/cert paths must be absolute
|
|
122
|
-
- relative paths in global config should be rejected as invalid
|
|
123
|
-
|
|
124
|
-
At runtime, the effective server record should be normalized before SSH use:
|
|
125
|
-
|
|
126
|
-
1. determine effective scope for the selected record
|
|
127
|
-
2. resolve relative workspace paths
|
|
128
|
-
3. validate that referenced files exist and are readable
|
|
129
|
-
4. pass normalized auth inputs to the SSH runtime
|
|
130
|
-
|
|
131
|
-
### Registry Backend Changes
|
|
132
|
-
|
|
133
|
-
The current encrypted registry backend should be replaced with a config-backed registry implementation.
|
|
134
|
-
|
|
135
|
-
That means:
|
|
136
|
-
|
|
137
|
-
- remove the `SecretProvider` dependency from the active registry path
|
|
138
|
-
- stop encrypting the server registry payload
|
|
139
|
-
- stop calling `keytar`
|
|
140
|
-
|
|
141
|
-
The registry abstraction can remain, but it should now operate on layered config files rather than an encrypted blob.
|
|
142
|
-
|
|
143
|
-
Recommended interfaces:
|
|
144
|
-
|
|
145
|
-
- `list()`: return effective records with scope metadata
|
|
146
|
-
- `resolve(id)`: return effective record with scope metadata
|
|
147
|
-
- `upsert(scope, record)`: write to the chosen scope
|
|
148
|
-
- `remove(scope, id)`: remove from the chosen scope
|
|
149
|
-
- `listRaw(scope)`: read one scope without merge logic when the CLI needs direct scope inspection
|
|
150
|
-
|
|
151
|
-
## CLI Design
|
|
152
|
-
|
|
153
|
-
The existing CLI entrypoint should remain:
|
|
154
|
-
|
|
155
|
-
- `bun run server-registry add`
|
|
156
|
-
- `bun run server-registry list`
|
|
157
|
-
- `bun run server-registry remove`
|
|
158
|
-
|
|
159
|
-
### Add
|
|
160
|
-
|
|
161
|
-
Prompt sequence:
|
|
162
|
-
|
|
163
|
-
1. choose target scope
|
|
164
|
-
2. enter server identity fields
|
|
165
|
-
3. choose auth kind
|
|
166
|
-
4. prompt for auth-specific fields
|
|
167
|
-
5. warn if storing a plain-text password
|
|
168
|
-
|
|
169
|
-
Default scope behavior:
|
|
170
|
-
|
|
171
|
-
- if workspace config exists, default to workspace
|
|
172
|
-
- otherwise default to global
|
|
173
|
-
|
|
174
|
-
If adding a workspace entry whose `id` already exists in global config:
|
|
175
|
-
|
|
176
|
-
- print that the workspace entry will override the global entry
|
|
177
|
-
|
|
178
|
-
### List
|
|
179
|
-
|
|
180
|
-
Listing should show effective records and source scope, for example:
|
|
181
|
-
|
|
182
|
-
- `workspace`
|
|
183
|
-
- `global`
|
|
184
|
-
|
|
185
|
-
If an entry is overridden, the effective list should show the workspace record and should indicate that it shadows a global record when relevant.
|
|
186
|
-
|
|
187
|
-
### Remove
|
|
188
|
-
|
|
189
|
-
Removal must be scope-aware.
|
|
190
|
-
|
|
191
|
-
If the same `id` exists in both scopes:
|
|
192
|
-
|
|
193
|
-
- prompt which scope to remove from
|
|
194
|
-
- never remove both implicitly
|
|
195
|
-
|
|
196
|
-
This keeps layered config behavior explicit and avoids destructive surprises.
|
|
197
|
-
|
|
198
|
-
## Runtime Flow
|
|
199
|
-
|
|
200
|
-
For remote tool execution:
|
|
201
|
-
|
|
202
|
-
1. resolve the requested server `id` from the layered config registry
|
|
203
|
-
2. determine whether the effective record came from workspace or global scope
|
|
204
|
-
3. normalize auth paths if needed
|
|
205
|
-
4. validate file existence and readability for path-based auth
|
|
206
|
-
5. construct SSH auth options
|
|
207
|
-
6. continue through the existing orchestrator, policy, SSH, and audit flow
|
|
208
|
-
|
|
209
|
-
This keeps policy, SSH execution, and auditing unchanged while simplifying only the credential-storage layer.
|
|
210
|
-
|
|
211
|
-
## Errors And Diagnostics
|
|
212
|
-
|
|
213
|
-
The new registry path should return structured errors for:
|
|
214
|
-
|
|
215
|
-
- missing config file when a write target is expected
|
|
216
|
-
- malformed JSON
|
|
217
|
-
- invalid schema
|
|
218
|
-
- duplicate or conflicting records within one file if validation disallows them
|
|
219
|
-
- relative key/cert path in global config
|
|
220
|
-
- missing key/cert file
|
|
221
|
-
- unreadable key/cert file
|
|
222
|
-
- missing required auth fields
|
|
223
|
-
|
|
224
|
-
Helpful CLI messaging matters here because users are now directly editing config.
|
|
225
|
-
|
|
226
|
-
## Migration
|
|
227
|
-
|
|
228
|
-
No automatic migration in this pass.
|
|
229
|
-
|
|
230
|
-
If the old encrypted registry exists, the new config-backed flow should ignore it. Documentation should state that the credential backend changed and that users need to define servers in config files going forward.
|
|
231
|
-
|
|
232
|
-
This avoids partial migration logic and keeps the new behavior obvious.
|
|
233
|
-
|
|
234
|
-
## Testing
|
|
235
|
-
|
|
236
|
-
Required test coverage:
|
|
237
|
-
|
|
238
|
-
- parse and load a global-only config
|
|
239
|
-
- parse and load a workspace-only config
|
|
240
|
-
- workspace overrides global by `id`
|
|
241
|
-
- workspace relative path resolution works
|
|
242
|
-
- global relative path rejection works
|
|
243
|
-
- path validation failures are surfaced cleanly
|
|
244
|
-
- password auth records round-trip correctly through the config registry
|
|
245
|
-
- `add` chooses the expected default scope
|
|
246
|
-
- `add` warns when workspace overrides global
|
|
247
|
-
- `list` reports source scope
|
|
248
|
-
- `remove` prompts for scope when both scopes contain the same `id`
|
|
249
|
-
|
|
250
|
-
Integration smoke coverage should continue to validate:
|
|
251
|
-
|
|
252
|
-
- `list_servers`
|
|
253
|
-
- safe `remote_exec`
|
|
254
|
-
- `remote_write_file` approval behavior
|
|
255
|
-
|
|
256
|
-
with at least one real SSH target, including the disposable Docker SSH path already used for manual smoke testing.
|
|
257
|
-
|
|
258
|
-
## Risks
|
|
259
|
-
|
|
260
|
-
- Plain-text passwords are intentionally weaker than the current encrypted model.
|
|
261
|
-
- Users may accidentally commit workspace config files if ignore rules are not clear.
|
|
262
|
-
- File path auth depends on the target machine having stable path layout and permissions.
|
|
263
|
-
|
|
264
|
-
These are acceptable tradeoffs for the requested simplicity, but they must be documented directly.
|
|
265
|
-
|
|
266
|
-
## Implementation Note
|
|
267
|
-
|
|
268
|
-
Task 4 removed `keytar` from package metadata and synchronized the README with the config-backed registry model. The runtime design above remains unchanged.
|
|
269
|
-
|
|
270
|
-
## Recommendation
|
|
271
|
-
|
|
272
|
-
Implement this as a clean replacement of the current encrypted/keychain-backed registry path. Do not keep both systems active in parallel. The simpler model is easier to understand, easier to operate, and more likely to behave consistently across macOS, Linux, and Windows.
|