@catheadowl/ukp 0.2.5 → 0.3.0
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/README.md +93 -21
- package/SECURITY.md +69 -0
- package/bin/ukp.js +31 -7
- package/docs/remote-deployment.md +175 -19
- package/package.json +11 -6
- package/skills/ukp/SKILL.md +32 -0
- package/src/capabilities/door-health.ts +480 -0
- package/src/capabilities/nav.ts +209 -444
- package/src/capabilities/qmd.ts +19 -0
- package/src/capabilities/read.ts +18 -2
- package/src/capabilities/remote-client.ts +4 -2
- package/src/capabilities/search.ts +23 -7
- package/src/capabilities/session.ts +455 -0
- package/src/capabilities/tls-identity.ts +55 -3
- package/src/cli.ts +38 -64
- package/src/commands/diagnose.ts +80 -2
- package/src/commands/guide-content/client.txt +19 -6
- package/src/commands/guide-content/remote.txt +22 -1
- package/src/commands/guide-content/rg.txt +2 -2
- package/src/commands/init.ts +243 -1
- package/src/commands/inventory.ts +277 -17
- package/src/commands/nav.ts +17 -12
- package/src/commands/read.ts +1 -1
- package/src/commands/rg.ts +2 -2
- package/src/commands/search.ts +19 -3
- package/src/commands/serve-task.ts +334 -44
- package/src/commands/serve.ts +410 -44
- package/src/commands/session.ts +541 -0
- package/src/commands/use.ts +178 -0
- package/src/config/agent-backed.ts +136 -0
- package/src/config/manifest.ts +39 -3
- package/src/manifest.yml +15 -0
- package/src/path-safety.ts +9 -4
- package/src/plumbing/token-file.ts +368 -0
- package/src/registry.ts +78 -1
- package/src/server.ts +291 -75
- package/src/spawn-relay.ts +21 -3
package/README.md
CHANGED
|
@@ -12,6 +12,10 @@ memorize, no provider-specific commands to relearn.
|
|
|
12
12
|
> [!NOTE]
|
|
13
13
|
> Early-stage CLI (MVP) — commands and behavior may still change.
|
|
14
14
|
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g @catheadowl/ukp # or: bun add -g @catheadowl/ukp
|
|
17
|
+
```
|
|
18
|
+
|
|
15
19
|
```bash
|
|
16
20
|
$ ukp init service --name notes && ukp register
|
|
17
21
|
initialized Service: notes
|
|
@@ -27,11 +31,15 @@ $ ukp read ukp://notes/2026/q3-plan.md # read straight from the reference
|
|
|
27
31
|
# Q3 Plan
|
|
28
32
|
Goals for Q3: ship the knowledge plane.
|
|
29
33
|
|
|
30
|
-
$ echo '[capabilities.propose]' >> .ukp/service.toml # the write
|
|
34
|
+
$ echo '[capabilities.propose]' >> .ukp/service.toml # the write path is opt-in: declare propose
|
|
35
|
+
$ echo '# typo fix' > typo-fix.md # a proposal is any Markdown draft
|
|
31
36
|
$ ukp propose --endpoint notes --id typo-fix --file typo-fix.md
|
|
32
37
|
proposal typo-fix created (revision 1)
|
|
33
38
|
```
|
|
34
39
|
|
|
40
|
+
A `ukp://` reference is not a web link — it names a registered endpoint
|
|
41
|
+
(local or remote); open one with `ukp read <uri>`.
|
|
42
|
+
|
|
35
43
|
## Why UKP
|
|
36
44
|
|
|
37
45
|
Knowledge work starts in folders, but repeated use needs stable names,
|
|
@@ -50,8 +58,9 @@ predictable scope, and a single command surface. Use UKP when:
|
|
|
50
58
|
|
|
51
59
|
- Register local folders as named knowledge endpoints — a registered folder
|
|
52
60
|
is a *Service*, addressed by its endpoint name.
|
|
53
|
-
- `ukp inspect` shows which endpoints the current scope selects
|
|
54
|
-
|
|
61
|
+
- `ukp inspect` shows which endpoints the current scope selects (the
|
|
62
|
+
nearest `.ukp/client.toml`; every registered endpoint when there is
|
|
63
|
+
none) — see what a command will touch before running it.
|
|
55
64
|
- Navigate the Markdown structure of an endpoint — folders, descriptions,
|
|
56
65
|
depth — with zero provider dependency.
|
|
57
66
|
- Read endpoint files through the built-in `read/file` capability — if a
|
|
@@ -71,13 +80,15 @@ predictable scope, and a single command surface. Use UKP when:
|
|
|
71
80
|
- Propose changes as an idempotent, reviewable suggestion — the proposal
|
|
72
81
|
lands in the endpoint's inbox and the verdict stays with its owner.
|
|
73
82
|
- Update provider-owned indexes through a stable UKP command (QMD-backed).
|
|
74
|
-
- Serve an endpoint over HTTP — a single endpoint,
|
|
75
|
-
a host door — when you want it reachable from other
|
|
83
|
+
- Serve an endpoint over HTTP — a single endpoint, a `--select`ed subset, or
|
|
84
|
+
every local binding as a host door — when you want it reachable from other
|
|
85
|
+
machines.
|
|
76
86
|
- Want a read-only endpoint? Declare zero capabilities — an empty
|
|
77
87
|
`[capabilities]` table still registers one (derived `read`/`nav` only).
|
|
78
88
|
- Give agents JSON output (`--json`; `ukp read` uses `--format json`),
|
|
79
89
|
reference sidecars, docid handoff keys, and durable `ukp://` references
|
|
80
|
-
when they need machine-readable results
|
|
90
|
+
when they need machine-readable results; every `ukp://` reference opens
|
|
91
|
+
with `ukp read <uri>`.
|
|
81
92
|
|
|
82
93
|
UKP owns endpoint names, Registry bindings, scope, and output shape; the
|
|
83
94
|
provider owns collection setup, indexes, ranking, and maintenance. UKP
|
|
@@ -125,14 +136,17 @@ endpoint without it; `propose` works wherever it's declared.
|
|
|
125
136
|
|
|
126
137
|
An agent can carry out the same flow on your behalf.
|
|
127
138
|
|
|
128
|
-
For workspace defaults, use `ukp guide client`
|
|
139
|
+
For workspace defaults, run `ukp use <endpoint>` (see `ukp guide client`) —
|
|
140
|
+
it writes `.ukp/client.toml` and keeps it committable in one step.
|
|
129
141
|
|
|
130
142
|
## Commands
|
|
131
143
|
|
|
132
|
-
Grouping below follows `ukp --help`;
|
|
133
|
-
`ukp <verb
|
|
134
|
-
(`ukp
|
|
135
|
-
|
|
144
|
+
Grouping below follows `ukp --help`; primary capability commands stay a flat
|
|
145
|
+
`ukp <verb>`, while multi-verb capability families appear as object command
|
|
146
|
+
groups (`ukp init service`, `ukp guide <topic>`). `--endpoint <name>`
|
|
147
|
+
is the canonical endpoint selector (`ukp unregister <name>` still takes the
|
|
148
|
+
endpoint as a positional legacy form); `-g` selects the full local Host
|
|
149
|
+
Registry where supported.
|
|
136
150
|
|
|
137
151
|
The same five commands — search, read, nav, rg, propose — work on local and
|
|
138
152
|
remote endpoints; consuming a remote endpoint is a one-command gesture
|
|
@@ -142,7 +156,7 @@ Endpoint commands:
|
|
|
142
156
|
|
|
143
157
|
| Command | What it does |
|
|
144
158
|
|---|---|
|
|
145
|
-
| `ukp search` | Indexed search through the endpoint's provider (QMD today); `--recursive` expands the manifest's declared `dependencies`. Results hand off
|
|
159
|
+
| `ukp search` | Indexed search through the endpoint's provider (QMD today); `--recursive` expands the manifest's declared `dependencies`. Results hand off a docid handoff key and verified `ukp://` references. |
|
|
146
160
|
| `ukp read` | Reads an endpoint-scoped resource — exact path, `ukp://` URI, or a `docid` handed off by search results. |
|
|
147
161
|
| `ukp nav` | Markdown outline of an endpoint (`--depth`, respects `.gitignore`); on by default, configurable via `[capabilities.nav]`. |
|
|
148
162
|
| `ukp rg` | Lexical grep (ripgrep) across endpoint files — provider-free, on by default; `--files` enumerates them instead of searching. Results become `read`-ready `ukp://` references. A missing rg binary skips the endpoint with a warning. |
|
|
@@ -152,9 +166,14 @@ Registry commands:
|
|
|
152
166
|
|
|
153
167
|
| Command | What it does |
|
|
154
168
|
|---|---|
|
|
155
|
-
| `ukp init service` | Creates a minimal `.ukp/service.toml`. |
|
|
156
|
-
| `ukp
|
|
157
|
-
| `ukp
|
|
169
|
+
| `ukp init service` | Creates a minimal `.ukp/service.toml`. The `.ukp/.gitignore` it drops ignores machine-local state only (the instance identity; the serve TLS identity lives in `~/.ukp/tls/`) — `service.toml` and `client.toml` are pure declarations and committable by design (a fresh clone re-registers in one `ukp register`). |
|
|
170
|
+
| `ukp use <endpoint> [endpoint...]` | One-step consumer gesture: sets this workspace's `default_endpoints` (`.ukp/client.toml`, explicit replace, idempotent — every name must be registered) and keeps the config version-visible in the same step. `ukp inspect` confirms the active scope. |
|
|
171
|
+
| `ukp register` / `ukp unregister --endpoint <name>` | Manages Host Registry bindings — local folders and remote endpoints alike (`ukp register --url`, see Remote in 60 seconds). `--name <handle>` disambiguates collisions: `notes` vs `ali-notes`. A folder that moved re-binds in one step: `ukp register --rebind` from the new location (local bindings). |
|
|
172
|
+
| `ukp list` | Lists endpoint bindings and capabilities under the **workspace scope** by default (nearest `.ukp/client.toml`; every registered endpoint when there is none); `-g` lists the full Host Registry without reading the client config. `--json` emits the offline machine snapshot (`ukp.list.v1`) riding the same scope — stable field names, the same data core as the text listing, zero network (remote bindings are registry facts only, so their capability fields are `null`); pin `-g` for the full snapshot. New endpoints on a host door show as a stderr note (`door <origin>: N unimported endpoint(s) …`) — importing stays an explicit gesture. |
|
|
173
|
+
|
|
174
|
+
A legacy `.ukp/.gitignore` (the retired `*` self-ignore template) migrates
|
|
175
|
+
to this posture in passing whenever `ukp register` (local folders) or
|
|
176
|
+
`ukp use` meets one.
|
|
158
177
|
|
|
159
178
|
Operations commands:
|
|
160
179
|
|
|
@@ -163,7 +182,7 @@ Operations commands:
|
|
|
163
182
|
| `ukp diagnose` | Checks a Service folder or endpoint scope for wiring problems. |
|
|
164
183
|
| `ukp inspect` | Explains current scope, bindings, manifest capabilities, and provider availability. |
|
|
165
184
|
| `ukp update` | Runs provider-owned maintenance when `update/qmd` is declared (local endpoints today). |
|
|
166
|
-
| `ukp serve` | Serves one endpoint — or, without `--endpoint`,
|
|
185
|
+
| `ukp serve` | Serves one endpoint — or, without `--endpoint`, local bindings as a **host door** (every one of them, or just the ones `--select` names — unselected endpoints are not served, routed, or declared) — over HTTP (a discovery document plus the five command routes; loopback by default). |
|
|
167
186
|
|
|
168
187
|
Help commands:
|
|
169
188
|
|
|
@@ -180,9 +199,11 @@ Help commands:
|
|
|
180
199
|
## Remote in 60 seconds
|
|
181
200
|
|
|
182
201
|
`ukp serve` exposes an endpoint over HTTP. Authentication is deny-by-default:
|
|
183
|
-
serving with a `--host` beyond loopback requires
|
|
184
|
-
|
|
185
|
-
|
|
202
|
+
serving with a `--host` beyond loopback requires a credential — the
|
|
203
|
+
`UKP_SERVE_TOKEN` env token (full power) or a labeled key issued into
|
|
204
|
+
the `~/.ukp/tokens` file (tokenless serving needs an explicit
|
|
205
|
+
`--allow-anonymous` and is refused off loopback). The two fastest
|
|
206
|
+
paths — each block notes where its commands run:
|
|
186
207
|
|
|
187
208
|
```bash
|
|
188
209
|
# From the consumer machine — SSH between two personal machines; the door is
|
|
@@ -198,15 +219,34 @@ UKP_SERVE_TOKEN=<token> ukp serve --endpoint <name> --host 0.0.0.0 --port 8570 -
|
|
|
198
219
|
ukp register --url https://<ip>:8570 --endpoint <name> --token <token>
|
|
199
220
|
```
|
|
200
221
|
|
|
222
|
+
Handing a second machine or an agent a narrower key? Issue labeled, scoped,
|
|
223
|
+
revocable keys instead of sharing the env token (ON THE HOST):
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
ukp serve token add liku "notes:ro,cad:rw" # prints the raw key ONCE
|
|
227
|
+
ukp serve token list # labels, key ids, scopes
|
|
228
|
+
ukp serve token revoke liku # next request is a 401
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Scopes are `name:ro|rw` comma-separated (full power is the explicit `*:rw`);
|
|
232
|
+
the file stores hashes only, and the door re-reads it on every request —
|
|
233
|
+
deleting a line revokes without a restart. A scoped key sees only its own
|
|
234
|
+
endpoints: out-of-scope names answer exactly like nonexistent ones. While
|
|
235
|
+
`UKP_SERVE_TOKEN` is set it takes precedence and the file is not consulted.
|
|
236
|
+
|
|
201
237
|
NAT/EIP cloud host (public IP on no NIC)? Name it with
|
|
202
238
|
`ukp serve --tls --tls-san <public-ip>` — the persisted certificate re-signs
|
|
203
239
|
over the same key, so the pin (and every registration) survives; details in
|
|
204
240
|
the deployment guide.
|
|
205
241
|
|
|
206
|
-
Registration mechanics: a **host door** url imports every endpoint
|
|
242
|
+
Registration mechanics: a **host door** url imports every endpoint it declares
|
|
207
243
|
(`--endpoint <name>` checks the served name and narrows the import to that
|
|
208
244
|
one, `--select` narrows to a subset); `--token` is stored with the binding;
|
|
209
|
-
re-running a registration refreshes it idempotently.
|
|
245
|
+
re-running a registration refreshes it idempotently. A host whose registry
|
|
246
|
+
mixes shareable knowledge with internal projects can serve
|
|
247
|
+
`ukp serve --select a,b,c` — one port for exactly those endpoints; everything
|
|
248
|
+
the door does not select behaves as if it did not exist (discovery, routes,
|
|
249
|
+
and 404 rosters alike).
|
|
210
250
|
|
|
211
251
|
Remote endpoints take the same commands as local ones — search, read, nav,
|
|
212
252
|
rg, propose. The full guide — the host door (one port for the whole
|
|
@@ -214,6 +254,36 @@ registry), Caddy and Tailscale fronting, the certificate pinning lifecycle,
|
|
|
214
254
|
and the hardening posture — lives in the
|
|
215
255
|
[deployment guide](https://github.com/CatheadOwl/ukp/blob/main/docs/remote-deployment.md).
|
|
216
256
|
|
|
257
|
+
## Security
|
|
258
|
+
|
|
259
|
+
UKP is local-first: nothing listens on the network until you run
|
|
260
|
+
`ukp serve`. When you do, the surface is deny-by-default — serving beyond
|
|
261
|
+
loopback without a credential is refused, and anonymous serving is an
|
|
262
|
+
explicit flag that only works on loopback. Credentials come in two forms:
|
|
263
|
+
the `UKP_SERVE_TOKEN` env token (full power, takes precedence while set)
|
|
264
|
+
and labeled keys in the 0600 `~/.ukp/tokens` file (hash-only storage,
|
|
265
|
+
required per-key scopes, revocation by deleting the line — effective on
|
|
266
|
+
the next request). Transport is `https://`, pinned
|
|
267
|
+
automatically at registration (a certificate that renews on the same key
|
|
268
|
+
re-pins silently; a different key hard-blocks with both fingerprints), or
|
|
269
|
+
`ssh://`, where SSH carries both encryption and authentication. A
|
|
270
|
+
`--select` door neither routes, declares, nor lists unselected endpoints —
|
|
271
|
+
excluded and unknown names return byte-identical 404s, and a scoped key's
|
|
272
|
+
out-of-scope endpoints answer the same way.
|
|
273
|
+
|
|
274
|
+
Accepted tradeoffs, disclosed rather than hidden: tokens are long-lived
|
|
275
|
+
static bearer secrets stored client-side in the 0600 registry file or an
|
|
276
|
+
environment variable (the same tradeoff as `~/.aws/credentials`); the
|
|
277
|
+
Windows door generator (`ukp serve --print-task`) embeds the token in a
|
|
278
|
+
0600 `start-door.cmd` it never prints; identity pinning is
|
|
279
|
+
trust-on-first-use (an SSH-style first-connect window). Hardening
|
|
280
|
+
guidance — token strength, reverse proxies and their same-machine
|
|
281
|
+
blind spot, firewalls — is in the
|
|
282
|
+
[deployment guide](https://github.com/CatheadOwl/ukp/blob/main/docs/remote-deployment.md).
|
|
283
|
+
|
|
284
|
+
Found something? Report it privately — see [SECURITY.md](SECURITY.md);
|
|
285
|
+
please don't use public issues for security reports.
|
|
286
|
+
|
|
217
287
|
## Not Yet
|
|
218
288
|
|
|
219
289
|
- remote operation of `update`, and a formally specified network protocol
|
|
@@ -226,6 +296,8 @@ and the hardening posture — lives in the
|
|
|
226
296
|
## Documentation
|
|
227
297
|
|
|
228
298
|
- [Remote deployment](https://github.com/CatheadOwl/ukp/blob/main/docs/remote-deployment.md) — serving endpoints across machines: the full guide.
|
|
299
|
+
Also ships inside the npm package (`docs/remote-deployment.md` in the
|
|
300
|
+
installed package); the other docs live in the GitHub repository.
|
|
229
301
|
- [Contributing](https://github.com/CatheadOwl/ukp/blob/main/CONTRIBUTING.md) — development setup and release packaging.
|
|
230
302
|
- [Changelog](https://github.com/CatheadOwl/ukp/blob/main/CHANGELOG.md) — what changed per release.
|
|
231
303
|
- Repository: https://github.com/CatheadOwl/ukp ·
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a vulnerability
|
|
4
|
+
|
|
5
|
+
Report privately using GitHub security advisories:
|
|
6
|
+
|
|
7
|
+
https://github.com/CatheadOwl/ukp/security/advisories/new
|
|
8
|
+
|
|
9
|
+
Please do not open public issues for security reports. We follow
|
|
10
|
+
coordinated vulnerability disclosure (the CERT process, with the 90-day
|
|
11
|
+
convention as the default clock) and credit reporters unless they prefer
|
|
12
|
+
to stay anonymous. Good-faith research on your own deployments is welcome.
|
|
13
|
+
|
|
14
|
+
## Supported versions
|
|
15
|
+
|
|
16
|
+
Only the latest published release line receives security fixes.
|
|
17
|
+
|
|
18
|
+
## Scope
|
|
19
|
+
|
|
20
|
+
In scope:
|
|
21
|
+
|
|
22
|
+
- the `ukp` CLI runtime: local capability commands, `ukp serve`
|
|
23
|
+
(transport, authentication, TLS identity, door routing), the generated
|
|
24
|
+
door artifacts (`ukp serve --print-task`), and credential handling on
|
|
25
|
+
consumer machines;
|
|
26
|
+
- the published package supply chain (npm tarball, GitHub repository).
|
|
27
|
+
|
|
28
|
+
Out of scope:
|
|
29
|
+
|
|
30
|
+
- hardening of your own host, SSH daemon, or reverse proxy — deployment
|
|
31
|
+
guidance exists, but operator-side misconfiguration is not a UKP
|
|
32
|
+
vulnerability;
|
|
33
|
+
- private working materials that are not part of the published package.
|
|
34
|
+
|
|
35
|
+
## Posture summary
|
|
36
|
+
|
|
37
|
+
- Local-first: no network listener exists until you run `ukp serve`.
|
|
38
|
+
- Deny-by-default serving: serving beyond loopback without
|
|
39
|
+
`UKP_SERVE_TOKEN` is refused; anonymous serving is an explicit flag and
|
|
40
|
+
loopback-only.
|
|
41
|
+
- Transport: `https://` with registration-time pinning (self-signed
|
|
42
|
+
certificates are pinned by SPKI; a renewal on the same key re-pins
|
|
43
|
+
transparently, a different key hard-blocks) or `ssh://`, where SSH
|
|
44
|
+
carries both encryption and authentication.
|
|
45
|
+
- Door scoping: a `--select` door neither routes, declares, nor lists
|
|
46
|
+
unselected endpoints; excluded and unknown names return byte-identical
|
|
47
|
+
404s.
|
|
48
|
+
|
|
49
|
+
Deployment hardening (token strength, reverse proxies, firewalls,
|
|
50
|
+
systemd) is covered in the
|
|
51
|
+
[deployment guide](https://github.com/CatheadOwl/ukp/blob/main/docs/remote-deployment.md).
|
|
52
|
+
|
|
53
|
+
## Known, accepted tradeoffs
|
|
54
|
+
|
|
55
|
+
Disclosed rather than hidden; each has a documented trigger for change:
|
|
56
|
+
|
|
57
|
+
- Tokens are long-lived static bearer secrets. OAuth flows are deferred
|
|
58
|
+
until a multi-tenant or hosted scenario exists.
|
|
59
|
+
- Read and write use the same token today; separation becomes mandatory
|
|
60
|
+
with the first remote content-write capability.
|
|
61
|
+
- Client-side tokens live in the 0600 registry file or an environment
|
|
62
|
+
variable, in plaintext — the same tradeoff as `~/.aws/credentials`.
|
|
63
|
+
- The Windows door artifact embeds the token in a 0600 `start-door.cmd`
|
|
64
|
+
and never prints it; a file that ever left the machine burns the token.
|
|
65
|
+
- Identity pinning is trust-on-first-use, with the SSH-style
|
|
66
|
+
first-connect window that implies.
|
|
67
|
+
- npm provenance attestations prove where and how a package was built,
|
|
68
|
+
not that the code is safe — verify with `npm audit signatures` and
|
|
69
|
+
treat provenance as one signal among several.
|
package/bin/ukp.js
CHANGED
|
@@ -56,28 +56,52 @@ function bunCandidates() {
|
|
|
56
56
|
return candidates;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
function fail(detail) {
|
|
59
|
+
function fail(detail, hint = "") {
|
|
60
60
|
process.stderr.write(
|
|
61
61
|
"ukp: no bun executable found (" + detail + ").\n"
|
|
62
62
|
+ "ukp runs on bun — install it (https://bun.sh), or point UKP_BUN at the\n"
|
|
63
63
|
+ "bun executable. Standard install locations (~/.bun/bin and the npm\n"
|
|
64
|
-
+ "global prefix) are checked before PATH.\n"
|
|
64
|
+
+ "global prefix) are checked before PATH.\n"
|
|
65
|
+
+ hint,
|
|
65
66
|
);
|
|
66
67
|
process.exit(1);
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
/** Windows-only diagnosis for a UKP_BUN that the user's shell accepts but
|
|
71
|
+
* existsSync rejects: name the shape mismatch instead of implying the file
|
|
72
|
+
* is absent. Two real shapes (0.3.0 release review): Git Bash converts an
|
|
73
|
+
* exported MSYS path (/c/.../bun) to a native form WITHOUT the .exe
|
|
74
|
+
* extension before this launcher sees it, so the .exe sibling exists while
|
|
75
|
+
* the named path does not; and a variable set outside a converting shell
|
|
76
|
+
* (cmd/PowerShell) reaches us as the raw /c/... or /cygdrive/... form,
|
|
77
|
+
* which Windows file APIs cannot resolve at all. */
|
|
78
|
+
function ukpBunHint() {
|
|
79
|
+
const value = process.env.UKP_BUN;
|
|
80
|
+
if (!isWindows || typeof value !== "string") return "";
|
|
81
|
+
if (/^\/(?:[a-z]|cygdrive\/[a-z])\//i.test(value)) {
|
|
82
|
+
return "ukp: UKP_BUN looks like an MSYS-style path; Windows needs a native "
|
|
83
|
+
+ "form like C:/path/to/bun.exe\n";
|
|
84
|
+
}
|
|
85
|
+
if (!/\.exe$/i.test(value) && existsSync(value + ".exe")) {
|
|
86
|
+
return "ukp: UKP_BUN omits the .exe extension (the executable sits next to "
|
|
87
|
+
+ "it); use its full name, e.g. C:/path/to/bun.exe\n";
|
|
88
|
+
}
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
|
|
69
92
|
const bunPath = bunCandidates().find((candidate) => existsSync(candidate));
|
|
70
93
|
if (bunPath === undefined) {
|
|
71
|
-
fail("looked at UKP_BUN, the standard install locations, and PATH");
|
|
94
|
+
fail("looked at UKP_BUN, the standard install locations, and PATH", ukpBunHint());
|
|
72
95
|
}
|
|
73
96
|
|
|
74
97
|
// In-process handoff when this launcher already runs under bun (bun's own
|
|
75
98
|
// bin links and `bunx`): importing the CLI keeps it in THIS process, where
|
|
76
|
-
// stdout/stderr keep their real TTY identity (
|
|
99
|
+
// stdout/stderr keep their real TTY identity (isTTY) — a spawnSync
|
|
77
100
|
// grandchild re-inherits duplicated handles, and on some hosts that layer
|
|
78
|
-
// degraded TTY detection (the third dogfood round: progress
|
|
79
|
-
// skipped while a direct run on the same console had
|
|
80
|
-
// also starts faster. The node-interpreted path (npm shims) keeps
|
|
101
|
+
// degraded TTY detection (the third dogfood round: progress frames
|
|
102
|
+
// silently skipped while a direct run on the same console had them). One
|
|
103
|
+
// process also starts faster. The node-interpreted path (npm shims) keeps
|
|
104
|
+
// spawning.
|
|
81
105
|
if (runningUnderBun) {
|
|
82
106
|
const { runCli } = await import(pathToFileURL(cli).href);
|
|
83
107
|
const exit = runCli(process.argv.slice(2));
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Operational recipes for serving UKP endpoints remotely — ssh wake, native TLS, Windows resident door, socket activation, fronting proxies, scoped keys, and hardening posture
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Remote deployment
|
|
2
6
|
|
|
3
7
|
Operational guide for exposing UKP endpoints over the network. For the
|
|
@@ -28,6 +32,17 @@ the same host forwards from the public side to the loopback bind, so proxied
|
|
|
28
32
|
deployments treat the token as mandatory (serve cannot see past its own bind
|
|
29
33
|
address).
|
|
30
34
|
|
|
35
|
+
The complete route list (ukp-remote v1) — what a client or a fronting proxy's
|
|
36
|
+
path allowlist must account for: `GET /.well-known/ukp.json` (the public
|
|
37
|
+
discovery document — the door's roster document in host door mode, where each
|
|
38
|
+
endpoint also serves its own at `/e/<name>/.well-known/ukp.json`), the read
|
|
39
|
+
face — `POST /v1/search`, `GET /v1/read`, `GET /v1/nav`, `GET /v1/rg` (each
|
|
40
|
+
prefixed `/e/<name>` at a door) — and the one write route, `PUT
|
|
41
|
+
/v1/propose/<id>` (`PUT /e/<name>/v1/propose/<id>` at a door): the proposal
|
|
42
|
+
upsert. Propose needs the endpoint to declare the capability, and a scoped
|
|
43
|
+
key must carry that endpoint at `rw` (`ro` keys reach the read face only —
|
|
44
|
+
see Scoped keys below).
|
|
45
|
+
|
|
31
46
|
## Via SSH (two personal machines — zero extra components, zero ceremony)
|
|
32
47
|
|
|
33
48
|
The `ssh://` URL scheme makes the transport transparent AND wakes the door
|
|
@@ -80,9 +95,12 @@ supported (its `-c` quoting drops the wake form).
|
|
|
80
95
|
|
|
81
96
|
## Native TLS (bare IP, no domain — zero extra components)
|
|
82
97
|
|
|
83
|
-
`--tls` self-signs on first start (identity = keypair, persisted
|
|
84
|
-
|
|
85
|
-
|
|
98
|
+
`--tls` self-signs on first start (identity = keypair, persisted in the
|
|
99
|
+
machine home beside the host registry — `~/.ukp/tls/<endpoint-name>/` for a
|
|
100
|
+
single endpoint, `~/.ukp/tls/` for a host door; SAN covers the host's
|
|
101
|
+
addresses, so the identity never travels with the knowledge folder and a
|
|
102
|
+
re-cloned endpoint on the same host keeps its pin). Clients pin the
|
|
103
|
+
certificate automatically at registration — same command as any other
|
|
86
104
|
remote, no flags added:
|
|
87
105
|
|
|
88
106
|
```bash
|
|
@@ -115,6 +133,57 @@ only grows: the re-sign keeps everything the certificate already carried
|
|
|
115
133
|
narrower requests reuse the certificate untouched. Explicit `--tls-cert` certificates carry their own SAN, so
|
|
116
134
|
`--tls-san` next to them is a usage error, not a no-op.
|
|
117
135
|
|
|
136
|
+
## Scoped keys (second machines, agents — labeled, revocable)
|
|
137
|
+
|
|
138
|
+
`UKP_SERVE_TOKEN` is the full-power shortcut: any listed token reaches every
|
|
139
|
+
endpoint and the write face. When you hand access to a second machine or an
|
|
140
|
+
agent, issue a **labeled, scoped key** instead — the issuance face is a serve
|
|
141
|
+
subcommand, and the credential store is a plain file you can audit:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# ON THE HOST (run without UKP_SERVE_TOKEN set — while the env token is
|
|
145
|
+
# present it takes precedence and the file is not consulted at all):
|
|
146
|
+
ukp serve token add liku "notes:ro,cad:rw"
|
|
147
|
+
# issued key for 'liku' (scopes: notes:ro,cad:rw)
|
|
148
|
+
# ukp_liku_3f9c...TQ <- the raw key, shown ONCE, never stored
|
|
149
|
+
# stored in ~/.ukp/tokens (0600, hash-only)...
|
|
150
|
+
|
|
151
|
+
# serve the door with NO env token — the key file arms the gate:
|
|
152
|
+
ukp serve --host 0.0.0.0 --tls
|
|
153
|
+
|
|
154
|
+
# consumer machine registers with the raw key as the token:
|
|
155
|
+
ukp register --url https://<ip>:8570 --token ukp_liku_...
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
How the pieces behave:
|
|
159
|
+
|
|
160
|
+
- **Scopes** are `name:ro|rw`, comma-separated (Docker-registry style); full
|
|
161
|
+
power is the explicit `*:rw` — a key with no scopes is a usage error, not a
|
|
162
|
+
wildcard. `rw` adds the propose route; `ro` keys get read/search/nav/rg
|
|
163
|
+
only. An endpoint outside the key's scope answers exactly like a name that
|
|
164
|
+
does not exist (same 404, same roster) — the key cannot even learn what it
|
|
165
|
+
cannot see.
|
|
166
|
+
- **The file** is `~/.ukp/tokens`, one line per key (`label key-id sha256$
|
|
167
|
+
hash scopes`), 0600, hash-only — a leaked file is not a leaked credential.
|
|
168
|
+
On POSIX, group/other permissions make the file unusable (machine refusal,
|
|
169
|
+
OpenSSH-style); Windows relies on the %USERPROFILE% ACLs instead.
|
|
170
|
+
- **Revocation is deleting the line.** `ukp serve token revoke <label>`
|
|
171
|
+
removes every key carrying that label; the running door re-reads the file
|
|
172
|
+
on every request, so the next call is a 401 — no restart, no deploy step.
|
|
173
|
+
Hand-editing (deleting or `#`-commenting a line) works too.
|
|
174
|
+
- **Rotation** is zero-downtime: `token add` with an existing label keeps the
|
|
175
|
+
old key active; hand out the new one, then revoke the label when the old
|
|
176
|
+
key's last consumer has switched. `ukp serve token list` shows labels, key
|
|
177
|
+
ids, and scopes (never hashes).
|
|
178
|
+
- **Chain order**: while `UKP_SERVE_TOKEN` is set, the env token wins and the
|
|
179
|
+
file is ignored — the env is the shortcut form of the same gate (the same
|
|
180
|
+
order as `AWS_*` env vars over `~/.aws/credentials`, or `GH_TOKEN` over the
|
|
181
|
+
stored gh login). An env var set to an empty value counts as unset.
|
|
182
|
+
|
|
183
|
+
The Windows resident form (`--print-task --write`) interpolates the env token
|
|
184
|
+
into `start-door.cmd`; a file-key door instead wants the cmd's `set
|
|
185
|
+
"UKP_SERVE_TOKEN=..."` line removed so the key file arms the gate.
|
|
186
|
+
|
|
118
187
|
## Windows host (https — resident form)
|
|
119
188
|
|
|
120
189
|
Windows has no socket-activation equivalent, so the https path on a
|
|
@@ -123,12 +192,23 @@ one bun process always running — with **no visible window** (a hidden
|
|
|
123
192
|
launcher owns the console; a popping cmd box is not an acceptable product
|
|
124
193
|
form) and bounded crash retry. Verified end to end (register/TOFU →
|
|
125
194
|
read → nav, plus zero-flash and crash-recovery drills) on a Windows 11
|
|
126
|
-
host with a plain user-level install
|
|
195
|
+
host with a plain user-level install; the task recipe and the log
|
|
196
|
+
ownership were re-verified end to end on 2026-09-23 (unelevated
|
|
197
|
+
per-user registration, no execution time limit, whole-tree restart).
|
|
198
|
+
Steps 3–6 are exactly what
|
|
127
199
|
`ukp serve --print-task --host 0.0.0.0 --tls`
|
|
128
200
|
(or `--tls-cert/--tls-key`) prints for you — the generator assembles the
|
|
129
201
|
mechanical half (start script, hidden launcher, Task Scheduler command,
|
|
130
202
|
firewall rule, token placeholder); this section keeps the judgment calls
|
|
131
|
-
and the pitfalls the verification run caught
|
|
203
|
+
and the pitfalls the verification run caught. A fast path exists for the
|
|
204
|
+
two file artifacts: `ukp serve --print-task --write ...` writes
|
|
205
|
+
`start-door.cmd` (token interpolated from your `UKP_SERVE_TOKEN` — never
|
|
206
|
+
displayed) and the hidden launcher straight into `%USERPROFILE%\.ukp\`,
|
|
207
|
+
CRLF by construction; existing files are never overwritten (manual edits
|
|
208
|
+
— a PATH patch, say — are safe; regeneration means removing the old files
|
|
209
|
+
first, as the removal section prescribes). The task-registration and
|
|
210
|
+
firewall steps stay on stdout for you to run; the print-only form remains
|
|
211
|
+
the full playbook (its Notes and the removal section):
|
|
132
212
|
|
|
133
213
|
1. Install the usual way (bun's official installer + `npm i -g
|
|
134
214
|
@catheadowl/ukp` — the npm shim needs bun on PATH, both land in
|
|
@@ -143,8 +223,11 @@ and the pitfalls the verification run caught:
|
|
|
143
223
|
-subj "/CN=ukp" -addext "subjectAltName=IP:<lan-ip>,DNS:localhost"`
|
|
144
224
|
4. `%USERPROFILE%\.ukp\start-door.cmd` (CRLF!) — set UKP_SERVE_TOKEN,
|
|
145
225
|
run `ukp serve --host 0.0.0.0 --port 8570 --tls-cert ... --tls-key ...`
|
|
146
|
-
with the door.log
|
|
147
|
-
|
|
226
|
+
with **no redirect on the serve line** — door.log is owned solely by
|
|
227
|
+
the vbs launcher's outer redirect; a second redirect here deadlocks
|
|
228
|
+
the nested append-open and the door never starts. No PATH export: the
|
|
229
|
+
launcher finds bun itself, and the logged-on task carries the
|
|
230
|
+
per-user PATH. Write it
|
|
148
231
|
with a real editor (writing cmd files over ssh echo mangles `%`
|
|
149
232
|
escaping — copy the file instead).
|
|
150
233
|
5. `%USERPROFILE%\.ukp\start-door-hidden.vbs` (CRLF, real editor) — the
|
|
@@ -152,11 +235,16 @@ and the pitfalls the verification run caught:
|
|
|
152
235
|
at process creation (nothing ever flashes), captures everything to
|
|
153
236
|
door.log, propagates the exit code, and retries a crashed door
|
|
154
237
|
3 times, 30s apart. `ukp serve --print-task` prints it verbatim.
|
|
155
|
-
6.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
ukp-
|
|
238
|
+
6. Scheduled task — register from a **plain PowerShell shell** (no
|
|
239
|
+
elevation needed: a per-user logon trigger is the OneDrive/Teams-class
|
|
240
|
+
autostart path; a machine-level logon trigger or another user's
|
|
241
|
+
session still needs the elevated schtasks route). Use the
|
|
242
|
+
`Register-ScheduledTask` block `ukp serve --print-task` prints —
|
|
243
|
+
per-user `AtLogOn -User` trigger, battery flags, `IgnoreNew`, and
|
|
244
|
+
deliberately **no execution time limit**
|
|
245
|
+
(`-ExecutionTimeLimit ([TimeSpan]::Zero)`). Then the firewall rule
|
|
246
|
+
for the port (that one does need an elevated shell), and
|
|
247
|
+
`Start-ScheduledTask -TaskName ukp-door` to start now.
|
|
160
248
|
7. Client: `ukp register --url https://<host>:8570 --token <token>` —
|
|
161
249
|
TOFU pins the certificate; re-registering refreshes the pin.
|
|
162
250
|
|
|
@@ -168,16 +256,75 @@ Pitfalls the verification run caught:
|
|
|
168
256
|
- ONLOGON starts at LOGON, not boot, and the launcher's crash retry is
|
|
169
257
|
bounded (3×30s): after an unattended reboot nobody logs into, or after
|
|
170
258
|
exhausted retries, the door is down until the next logon or a manual
|
|
171
|
-
`
|
|
172
|
-
wrapping — or prefer ssh:// there. (Task
|
|
173
|
-
restart-on-failure setting does not fire on exit
|
|
174
|
-
on it.)
|
|
175
|
-
-
|
|
176
|
-
|
|
177
|
-
|
|
259
|
+
`Start-ScheduledTask -TaskName ukp-door`. Unattended-boot always-on
|
|
260
|
+
would need WinSW/NSSM-class wrapping — or prefer ssh:// there. (Task
|
|
261
|
+
Scheduler's own restart-on-failure setting does not fire on exit
|
|
262
|
+
codes — do not rely on it.)
|
|
263
|
+
- **The 72-hour default**: a task without an explicit execution time
|
|
264
|
+
limit is stopped by Task Scheduler 72 hours after it starts — for a
|
|
265
|
+
resident door that is a silent kill (no window, no log line). The
|
|
266
|
+
recipe pins `-ExecutionTimeLimit ([TimeSpan]::Zero)` (PT0S in the task
|
|
267
|
+
XML); keep it when editing the task by hand.
|
|
268
|
+
- To restart the door by hand (new token, ukp upgrade, new cert): kill
|
|
269
|
+
the **launcher root**, not just the serve leaf — `taskkill /PID
|
|
270
|
+
<wscript-pid> /T /F` on the `wscript.exe` running
|
|
271
|
+
`start-door-hidden.vbs`, then `Start-ScheduledTask -TaskName
|
|
272
|
+
ukp-door`. A leaf-only kill leaves the launcher's bounded retry to
|
|
273
|
+
re-grab the port within 30 seconds, racing the manual start (which
|
|
274
|
+
`IgnoreNew` turns into a no-op while the old launcher lives); the
|
|
275
|
+
scheduler's End-Task does not reliably kill the tree either. Each
|
|
276
|
+
door has its own wscript — in PowerShell, `Get-CimInstance
|
|
277
|
+
Win32_Process -Filter "Name='wscript.exe'" | Select-Object
|
|
278
|
+
ProcessId,CommandLine` and match the vbs name.
|
|
279
|
+
- **Git Bash path conversion**: MSYS rewrites slash-style arguments into
|
|
280
|
+
Git paths (`taskkill /PID …` fails with a misleading
|
|
281
|
+
Invalid-argument error that reads like a permission problem). Prefix
|
|
282
|
+
such commands with `MSYS_NO_PATHCONV=1`.
|
|
283
|
+
- **Do not edit start-door.cmd while the door runs**: cmd.exe resumes
|
|
284
|
+
reading a running batch file at a stale byte offset, so an in-place
|
|
285
|
+
edit above the current line makes it execute garbled fragments when
|
|
286
|
+
the current command returns (observed live: a mid-comment `a` ran as
|
|
287
|
+
a command). Stop the door first, edit, then restart.
|
|
178
288
|
- `rg` on the host is the host's business — no ripgrep installed means
|
|
179
289
|
the remote `rg` capability reports unavailable (everything else works).
|
|
180
290
|
|
|
291
|
+
`ukp diagnose --door` is the read-only health check for this resident form:
|
|
292
|
+
for every door it reports the task state, the wscript->cmd->bun process tree,
|
|
293
|
+
the port (and who holds it), and the door.log tail — and exits 1 when a door
|
|
294
|
+
is down (a host with no doors is healthy). Each finding names its repair;
|
|
295
|
+
removal and restart moves live in the `ukp serve --print-task` §5 checklist
|
|
296
|
+
(the three-level teardown below) and the pitfalls above.
|
|
297
|
+
|
|
298
|
+
To remove the door — the inverse of steps 4–0, in three levels (stopping
|
|
299
|
+
is not disabling, and disabling is not erasing; `ukp serve --print-task`
|
|
300
|
+
prints this checklist as its §5):
|
|
301
|
+
|
|
302
|
+
1. **Stop now**: `taskkill /PID <wscript-pid> /T /F` on the launcher
|
|
303
|
+
root (same lookup as the restart pitfall above). With the task still
|
|
304
|
+
registered, the next logon brings the door back.
|
|
305
|
+
2. **Stop for good**:
|
|
306
|
+
`Unregister-ScheduledTask -TaskName ukp-door -Confirm:$false` from a
|
|
307
|
+
plain shell — the same zero-elevation path as registering it. The
|
|
308
|
+
cmdlet confirms by default (hence the flag), and unregistering does
|
|
309
|
+
**not** stop a running instance: the kill in step 1 is not optional.
|
|
310
|
+
3. **Erase everything**: `netsh advfirewall firewall delete rule
|
|
311
|
+
name="ukp-door"` (the elevated shell again), then delete
|
|
312
|
+
`%USERPROFILE%\.ukp\start-door.cmd` (**it carries the token in
|
|
313
|
+
plaintext** — deleting it destroys this copy; if it ever left the
|
|
314
|
+
machine, treat the token as burned), `start-door-hidden.vbs`, and
|
|
315
|
+
`door.log`, plus the certificate material: your `--tls-cert` /
|
|
316
|
+
`--tls-key` paths, or — with `--tls` — the self-signed identity beside
|
|
317
|
+
the host registry: `%USERPROFILE%\.ukp\tls\<endpoint-name>\` for a
|
|
318
|
+
single-endpoint door, `%USERPROFILE%\.ukp\tls\` for a host door (it
|
|
319
|
+
never lives inside the knowledge folder, so git or cloud sync cannot
|
|
320
|
+
carry the private key out; `ukp unregister` leaves it in place on
|
|
321
|
+
purpose — a re-registered endpoint keeps its pin — so delete it by
|
|
322
|
+
hand when you are done). The endpoints stay registered
|
|
323
|
+
on the host for local use — `ukp unregister --endpoint <name>` per
|
|
324
|
+
endpoint only if you are done with them; and every consumer that
|
|
325
|
+
registered holds the token in plaintext in its own registry —
|
|
326
|
+
`ukp unregister --endpoint <name>` there too.
|
|
327
|
+
|
|
181
328
|
## Socket activation (Linux — no resident process on the https path either)
|
|
182
329
|
|
|
183
330
|
systemd can hold the listening port itself and spawn the door on first
|
|
@@ -236,6 +383,15 @@ Notes:
|
|
|
236
383
|
addresses — a public EIP is not among them, and registration would fail
|
|
237
384
|
CERT_ALTNAME_INVALID. Add `--tls-san <public-ip>` next to `--tls`
|
|
238
385
|
(see Native TLS above): the SAN grows, the key (and pin) does not.
|
|
386
|
+
- **Removing the door** (the setup block's inverse — the same three
|
|
387
|
+
levels as the Windows teardown above, in one gesture):
|
|
388
|
+
`systemctl --user disable --now ukp-door.socket` (a door still
|
|
389
|
+
running stops with `systemctl --user stop ukp-door.service`), then
|
|
390
|
+
delete the two unit files and the token env file, and
|
|
391
|
+
`rm -rf ~/.ukp/tls` if you are done with the self-signed identity;
|
|
392
|
+
`loginctl disable-linger "$USER"` too if nothing else on the host
|
|
393
|
+
needs it. Nothing else is resident — consumer and host registry
|
|
394
|
+
bindings come off exactly as on the Windows side.
|
|
239
395
|
|
|
240
396
|
## Host door (one host, many endpoints — one gesture, zero tokens)
|
|
241
397
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catheadowl/ukp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"bun": ">=1.3.14"
|
|
@@ -18,10 +18,12 @@
|
|
|
18
18
|
"keywords": [
|
|
19
19
|
"knowledge",
|
|
20
20
|
"cli",
|
|
21
|
-
"bun",
|
|
22
|
-
"qmd",
|
|
23
21
|
"search",
|
|
24
|
-
"agent"
|
|
22
|
+
"agent",
|
|
23
|
+
"ai-agents",
|
|
24
|
+
"knowledge-base",
|
|
25
|
+
"local-first",
|
|
26
|
+
"self-hosted"
|
|
25
27
|
],
|
|
26
28
|
"bin": {
|
|
27
29
|
"ukp": "bin/ukp.js"
|
|
@@ -30,16 +32,19 @@
|
|
|
30
32
|
"bin",
|
|
31
33
|
"src",
|
|
32
34
|
"docs",
|
|
35
|
+
"skills/ukp/SKILL.md",
|
|
33
36
|
"README.md",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
37
|
+
"SECURITY.md",
|
|
38
|
+
"LICENSE"
|
|
36
39
|
],
|
|
37
40
|
"scripts": {
|
|
38
41
|
"dev": "bun run src/cli.ts",
|
|
42
|
+
"prepack": "bun ./scripts/pack-audit.mjs",
|
|
39
43
|
"test": "bun test",
|
|
40
44
|
"typecheck": "tsc --noEmit"
|
|
41
45
|
},
|
|
42
46
|
"dependencies": {
|
|
47
|
+
"@any-nav/scan": "^0.2.0",
|
|
43
48
|
"commander": "15.0.0",
|
|
44
49
|
"smol-toml": "1.7.1",
|
|
45
50
|
"zod": "4.4.3"
|