@pleri/olam-cli 0.1.180 → 0.1.182
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/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +67 -19
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +93 -0
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/destroy.d.ts +41 -0
- package/dist/commands/destroy.d.ts.map +1 -1
- package/dist/commands/destroy.js +81 -33
- package/dist/commands/destroy.js.map +1 -1
- package/dist/commands/dispatch-resolve.d.ts +54 -0
- package/dist/commands/dispatch-resolve.d.ts.map +1 -0
- package/dist/commands/dispatch-resolve.js +105 -0
- package/dist/commands/dispatch-resolve.js.map +1 -0
- package/dist/commands/dispatch.d.ts.map +1 -1
- package/dist/commands/dispatch.js +40 -9
- package/dist/commands/dispatch.js.map +1 -1
- package/dist/commands/flywheel/k5-validate.d.ts +31 -0
- package/dist/commands/flywheel/k5-validate.d.ts.map +1 -1
- package/dist/commands/flywheel/k5-validate.js +80 -19
- package/dist/commands/flywheel/k5-validate.js.map +1 -1
- package/dist/commands/kg-classify.d.ts.map +1 -1
- package/dist/commands/kg-classify.js +20 -0
- package/dist/commands/kg-classify.js.map +1 -1
- package/dist/commands/kg-doctor.d.ts +67 -6
- package/dist/commands/kg-doctor.d.ts.map +1 -1
- package/dist/commands/kg-doctor.js +126 -46
- package/dist/commands/kg-doctor.js.map +1 -1
- package/dist/commands/list.d.ts +27 -0
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +67 -19
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/memory/status.d.ts +18 -0
- package/dist/commands/memory/status.d.ts.map +1 -1
- package/dist/commands/memory/status.js +38 -2
- package/dist/commands/memory/status.js.map +1 -1
- package/dist/commands/memory-service-container.d.ts +44 -0
- package/dist/commands/memory-service-container.d.ts.map +1 -1
- package/dist/commands/memory-service-container.js +49 -0
- package/dist/commands/memory-service-container.js.map +1 -1
- package/dist/commands/ps.d.ts +32 -0
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +34 -0
- package/dist/commands/ps.js.map +1 -1
- package/dist/commands/runbooks.d.ts +32 -0
- package/dist/commands/runbooks.d.ts.map +1 -1
- package/dist/commands/runbooks.js +79 -22
- package/dist/commands/runbooks.js.map +1 -1
- package/dist/commands/skills-source.d.ts.map +1 -1
- package/dist/commands/skills-source.js +77 -2
- package/dist/commands/skills-source.js.map +1 -1
- package/dist/commands/upgrade-history.d.ts +0 -2
- package/dist/commands/upgrade-history.d.ts.map +1 -1
- package/dist/commands/upgrade-history.js +0 -6
- package/dist/commands/upgrade-history.js.map +1 -1
- package/dist/commands/upgrade-lock.d.ts +0 -9
- package/dist/commands/upgrade-lock.d.ts.map +1 -1
- package/dist/commands/upgrade-lock.js +1 -1
- package/dist/commands/upgrade-lock.js.map +1 -1
- package/dist/commands/world-snapshot.d.ts +13 -0
- package/dist/commands/world-snapshot.d.ts.map +1 -1
- package/dist/commands/world-snapshot.js +81 -1
- package/dist/commands/world-snapshot.js.map +1 -1
- package/dist/commands/yolo.d.ts +0 -4
- package/dist/commands/yolo.d.ts.map +1 -1
- package/dist/commands/yolo.js +2 -2
- package/dist/commands/yolo.js.map +1 -1
- package/dist/image-digests.json +8 -8
- package/dist/index.js +3403 -2459
- package/dist/lib/anthropic-base-url-file.d.ts +37 -0
- package/dist/lib/anthropic-base-url-file.d.ts.map +1 -0
- package/dist/lib/anthropic-base-url-file.js +46 -0
- package/dist/lib/anthropic-base-url-file.js.map +1 -0
- package/dist/lib/auth-remote.d.ts +9 -0
- package/dist/lib/auth-remote.d.ts.map +1 -1
- package/dist/lib/auth-remote.js +19 -4
- package/dist/lib/auth-remote.js.map +1 -1
- package/dist/lib/cf-access-token.d.ts +32 -0
- package/dist/lib/cf-access-token.d.ts.map +1 -0
- package/dist/lib/cf-access-token.js +52 -0
- package/dist/lib/cf-access-token.js.map +1 -0
- package/dist/lib/config.d.ts +17 -3
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +28 -4
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/kubectl-context.d.ts +49 -0
- package/dist/lib/kubectl-context.d.ts.map +1 -1
- package/dist/lib/kubectl-context.js +64 -2
- package/dist/lib/kubectl-context.js.map +1 -1
- package/dist/lib/upgrade-kubernetes.d.ts +7 -0
- package/dist/lib/upgrade-kubernetes.d.ts.map +1 -1
- package/dist/lib/upgrade-kubernetes.js +35 -8
- package/dist/lib/upgrade-kubernetes.js.map +1 -1
- package/dist/mcp-server.js +1457 -1004
- package/hermes-bundle/version.json +1 -1
- package/host-cp/k8s/manifests/45-pvc.yaml +6 -2
- package/host-cp/k8s/manifests/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +1 -1
- package/host-cp/observability/trace-summary.mjs +267 -0
- package/host-cp/src/bootstrap-selective.mjs +30 -28
- package/host-cp/src/host-stream.mjs +52 -0
- package/host-cp/src/redirect.mjs +7 -0
- package/host-cp/src/router.mjs +168 -0
- package/host-cp/src/serve-only-config.mjs +85 -0
- package/host-cp/src/server.mjs +346 -217
- package/host-cp/src/world-services.mjs +136 -0
- package/package.json +1 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// host-cp request router.
|
|
2
|
+
//
|
|
3
|
+
// Replaces the long linear `if (url.pathname === ...)` dispatch chain in
|
|
4
|
+
// server.mjs with an ordered route table. The table is walked in
|
|
5
|
+
// registration order, so route PRECEDENCE is preserved exactly as it was
|
|
6
|
+
// in the original if-ladder: the first matching route wins, later routes
|
|
7
|
+
// are never consulted once a match handles the request.
|
|
8
|
+
//
|
|
9
|
+
// Why a table and not a framework:
|
|
10
|
+
// - host-cp ships with no external HTTP framework (no express/fastify);
|
|
11
|
+
// this matches the existing zero-dep style.
|
|
12
|
+
// - The table is a plain data structure, so it is importable + unit
|
|
13
|
+
// testable WITHOUT booting server.mjs (which spawns docker-events,
|
|
14
|
+
// the auth poller, and the worlds.db reconciler at import time).
|
|
15
|
+
// - A route is now a table entry instead of a `return` buried in a
|
|
16
|
+
// 1700-line ladder. That kills the silent route-shadowing class: a
|
|
17
|
+
// misplaced `return` can no longer swallow a later route, and the
|
|
18
|
+
// full set of routes is enumerable (see `router.routes()`).
|
|
19
|
+
//
|
|
20
|
+
// Behavior-preservation contract (load-bearing — see
|
|
21
|
+
// __tests__/router.test.mjs):
|
|
22
|
+
// 1. Walk order == registration order == original source order.
|
|
23
|
+
// 2. A route MATCHES when its matcher returns a truthy match value AND
|
|
24
|
+
// (no method filter OR the method matches). The matcher receives
|
|
25
|
+
// ({ pathname, method, url }) and returns either a boolean or, for
|
|
26
|
+
// regex routes, the RegExpMatchArray (truthy) so the handler can read
|
|
27
|
+
// capture groups.
|
|
28
|
+
// 3. The FIRST matching route is invoked and dispatch STOPS — identical
|
|
29
|
+
// to `if (cond) { ...; return; }`. The handler owns the response.
|
|
30
|
+
// 4. A route whose path matches but whose METHOD does not is SKIPPED,
|
|
31
|
+
// and the walk continues — identical to the original
|
|
32
|
+
// `if (pathMatch && req.method === 'X')` blocks, where a path hit
|
|
33
|
+
// with the wrong method fell through to the next `if`.
|
|
34
|
+
// 5. If no route matches, dispatch returns `false` so the caller runs
|
|
35
|
+
// its terminal 404 — identical to the original fall-through.
|
|
36
|
+
//
|
|
37
|
+
// The router does NOT add auth, body parsing, or any middleware semantics.
|
|
38
|
+
// Those stay exactly where they were in server.mjs (pre-auth routes, the
|
|
39
|
+
// auth gate, the plan-chat bypass) — the router only models the part of
|
|
40
|
+
// the chain that was a flat sequence of `if` blocks.
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {object} RouteContext
|
|
44
|
+
* @property {string} pathname url.pathname
|
|
45
|
+
* @property {string} method req.method (already normalized by node to uppercase)
|
|
46
|
+
* @property {URL} url parsed request URL
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A matcher decides whether a route applies to a request, ignoring method.
|
|
51
|
+
* Returning a non-boolean truthy value (e.g. a RegExpMatchArray) is
|
|
52
|
+
* forwarded to the handler as `ctx.match` so regex routes can read groups.
|
|
53
|
+
*
|
|
54
|
+
* @typedef {(ctx: RouteContext) => (boolean | RegExpMatchArray | null | undefined)} RouteMatcher
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A handler receives the node req/res plus the parsed url, the matched
|
|
59
|
+
* value (for regex routes), and is responsible for writing the response.
|
|
60
|
+
* It mirrors the body of an original `if` block. Return value is ignored;
|
|
61
|
+
* matching alone terminates dispatch (preserving the `if ... return`
|
|
62
|
+
* semantics where reaching the block always handled the request).
|
|
63
|
+
*
|
|
64
|
+
* @typedef {(req: import('node:http').IncomingMessage, res: import('node:http').ServerResponse, ctx: RouteContext & { match: any }) => unknown | Promise<unknown>} RouteHandler
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @typedef {object} Route
|
|
69
|
+
* @property {string} name human label for diagnostics / tests
|
|
70
|
+
* @property {string[] | null} methods allowed methods, or null for "any method"
|
|
71
|
+
* @property {RouteMatcher} match
|
|
72
|
+
* @property {RouteHandler} handler
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Create an ordered router. Routes are matched in the order they are
|
|
77
|
+
* registered — register in the SAME order the original if-ladder ran.
|
|
78
|
+
*/
|
|
79
|
+
export function createRouter() {
|
|
80
|
+
/** @type {Route[]} */
|
|
81
|
+
const routes = [];
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Register a route. Returns the router for chaining.
|
|
85
|
+
*
|
|
86
|
+
* @param {object} spec
|
|
87
|
+
* @param {string} spec.name
|
|
88
|
+
* @param {string | string[] | null} [spec.method] single method, list, or null/omitted for any
|
|
89
|
+
* @param {string} [spec.path] exact pathname match (mutually exclusive with prefix/match)
|
|
90
|
+
* @param {string} [spec.prefix] pathname.startsWith(prefix) match
|
|
91
|
+
* @param {RegExp} [spec.pattern] pathname.match(pattern) — match value passed to handler
|
|
92
|
+
* @param {RouteMatcher} [spec.match] custom matcher (overrides path/prefix/pattern)
|
|
93
|
+
* @param {RouteHandler} spec.handler
|
|
94
|
+
*/
|
|
95
|
+
function register(spec) {
|
|
96
|
+
const { name, method, path, prefix, pattern } = spec;
|
|
97
|
+
const handler = spec.handler;
|
|
98
|
+
if (typeof handler !== 'function') {
|
|
99
|
+
throw new TypeError(`route "${name}" requires a handler function`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** @type {string[] | null} */
|
|
103
|
+
let methods = null;
|
|
104
|
+
if (Array.isArray(method)) methods = method.slice();
|
|
105
|
+
else if (typeof method === 'string') methods = [method];
|
|
106
|
+
// method omitted or null → any method
|
|
107
|
+
|
|
108
|
+
/** @type {RouteMatcher} */
|
|
109
|
+
let match;
|
|
110
|
+
if (typeof spec.match === 'function') {
|
|
111
|
+
match = spec.match;
|
|
112
|
+
} else if (typeof path === 'string') {
|
|
113
|
+
match = (ctx) => ctx.pathname === path;
|
|
114
|
+
} else if (typeof prefix === 'string') {
|
|
115
|
+
match = (ctx) => ctx.pathname.startsWith(prefix);
|
|
116
|
+
} else if (pattern instanceof RegExp) {
|
|
117
|
+
match = (ctx) => ctx.pathname.match(pattern);
|
|
118
|
+
} else {
|
|
119
|
+
throw new TypeError(
|
|
120
|
+
`route "${name}" requires one of: path, prefix, pattern, or match`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
routes.push({ name, methods, match, handler });
|
|
125
|
+
return api;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Walk the table in registration order. Invokes the first route whose
|
|
130
|
+
* matcher is truthy AND whose method filter admits the request, then
|
|
131
|
+
* stops. A path-match with a non-admitted method is skipped (the walk
|
|
132
|
+
* continues), preserving the original `if (pathMatch && method===X)`
|
|
133
|
+
* fall-through.
|
|
134
|
+
*
|
|
135
|
+
* @param {import('node:http').IncomingMessage} req
|
|
136
|
+
* @param {import('node:http').ServerResponse} res
|
|
137
|
+
* @param {URL} url
|
|
138
|
+
* @returns {Promise<boolean>} true if a route handled the request, false to fall through to 404
|
|
139
|
+
*/
|
|
140
|
+
async function dispatch(req, res, url) {
|
|
141
|
+
const ctx = { pathname: url.pathname, method: req.method ?? 'GET', url };
|
|
142
|
+
for (const route of routes) {
|
|
143
|
+
const matched = route.match(ctx);
|
|
144
|
+
if (!matched) continue;
|
|
145
|
+
// Path matched. Now gate on method — a mismatch is a SKIP, not a
|
|
146
|
+
// 405, exactly mirroring the original if-ladder fall-through.
|
|
147
|
+
if (route.methods !== null && !route.methods.includes(ctx.method)) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
await route.handler(req, res, { ...ctx, match: matched });
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Enumerate registered routes (name + methods + matcher kind) for
|
|
158
|
+
* diagnostics, audits, and tests. Pure read of the table.
|
|
159
|
+
*
|
|
160
|
+
* @returns {Array<{ name: string, methods: string[] | null }>}
|
|
161
|
+
*/
|
|
162
|
+
function list() {
|
|
163
|
+
return routes.map((r) => ({ name: r.name, methods: r.methods }));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const api = { register, dispatch, list, get size() { return routes.length; } };
|
|
167
|
+
return api;
|
|
168
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// serve-only-config.mjs — host-cp SERVE-ONLY mode gate (Phase A of
|
|
2
|
+
// host-cp-gke-serve-only-mode).
|
|
3
|
+
//
|
|
4
|
+
// host-cp normally runs as a local operator sidecar coupled to the host's
|
|
5
|
+
// docker daemon + operator-repo + gh-config. On a managed GKE cluster those
|
|
6
|
+
// host-couplings are absent: host-cp only serves plan-chat-spa + the
|
|
7
|
+
// host-native `/api/*` surface; world orchestration runs elsewhere.
|
|
8
|
+
//
|
|
9
|
+
// `OLAM_HOST_CP_SERVE_ONLY=true` switches host-cp into that degraded shape:
|
|
10
|
+
// - no docker transport connect, no world discovery
|
|
11
|
+
// - no PlanOrchestrator docker wiring, no pr-merge-poller docker/repo deps
|
|
12
|
+
// - world-orchestration routes (`/api/world/*`) return a structured 503
|
|
13
|
+
// - version-status degrades to 'unknown' (no operator-repo)
|
|
14
|
+
//
|
|
15
|
+
// The flag defaults OFF — the local docker/k3d FULL mode is byte-for-byte
|
|
16
|
+
// unchanged. This module is a tiny pure seam so the gate decision can be
|
|
17
|
+
// unit-tested WITHOUT booting server.mjs (which connects docker + binds a
|
|
18
|
+
// port at module load and therefore can't be imported in a test).
|
|
19
|
+
//
|
|
20
|
+
// ONE coarse flag — no granular per-subsystem toggles (plan S1 / YAGNI).
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Decide whether host-cp runs in SERVE-ONLY mode.
|
|
24
|
+
*
|
|
25
|
+
* Strict `=== 'true'` parse (mirrors the HOST_CP_MODE env-flag convention
|
|
26
|
+
* in server.mjs): only the literal string `'true'` enables it. Any other
|
|
27
|
+
* value — unset, `'1'`, `'false'`, `''`, `'TRUE'` — keeps FULL mode so the
|
|
28
|
+
* default stays OFF and operators can't half-enable it by accident.
|
|
29
|
+
*
|
|
30
|
+
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>} [env]
|
|
31
|
+
* Environment to read `OLAM_HOST_CP_SERVE_ONLY` from. Defaults to
|
|
32
|
+
* `process.env`.
|
|
33
|
+
* @returns {boolean} `true` when serve-only mode is active.
|
|
34
|
+
*/
|
|
35
|
+
export function isServeOnly(env = process.env) {
|
|
36
|
+
return env?.OLAM_HOST_CP_SERVE_ONLY === 'true';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Structured 503 body for world-orchestration routes that are unavailable
|
|
41
|
+
* in serve-only mode. Reuses the host-cp `/api/*` JSON-error shape
|
|
42
|
+
* (`{ error, message }`) so SPA error handling treats it uniformly.
|
|
43
|
+
*
|
|
44
|
+
* @type {{ error: 'orchestration_unavailable', message: string }}
|
|
45
|
+
*/
|
|
46
|
+
export const ORCHESTRATION_UNAVAILABLE = Object.freeze({
|
|
47
|
+
error: 'orchestration_unavailable',
|
|
48
|
+
message:
|
|
49
|
+
'host-cp is in serve-only mode (managed cluster); world orchestration runs elsewhere',
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* True when `pathname` (+ `method`) is a world-ORCHESTRATION route that must
|
|
54
|
+
* degrade to a structured 503 in serve-only mode. The surface is wider than
|
|
55
|
+
* the singular `/api/world/` proxy: it also covers the plural `/api/worlds/`
|
|
56
|
+
* per-world mutation/read routes (e.g. `POST /api/worlds/<id>/tunnels` which
|
|
57
|
+
* spawns a real cloudflare tunnel, `DELETE /api/worlds/<id>` which destroys a
|
|
58
|
+
* world), world creation (`POST /api/worlds`), and the CLI `/v1/worlds/`
|
|
59
|
+
* routes. Without this breadth a serve-only host-cp on a shared cluster would
|
|
60
|
+
* execute tunnel/destroy mutations — the opposite of honest degradation.
|
|
61
|
+
* (CP3 finding: the singular-only guard let POST /api/worlds/<id>/tunnels
|
|
62
|
+
* open a live public tunnel in serve-only.)
|
|
63
|
+
*
|
|
64
|
+
* Deliberately NOT orchestration: `GET`/`HEAD /api/worlds` (the bare LIST
|
|
65
|
+
* endpoint) — it returns an empty array in serve-only, which is honest.
|
|
66
|
+
*
|
|
67
|
+
* @param {unknown} pathname URL.pathname (no querystring).
|
|
68
|
+
* @param {string} [method] HTTP method (defaults 'GET').
|
|
69
|
+
* @returns {boolean}
|
|
70
|
+
*/
|
|
71
|
+
export function isOrchestrationRoute(pathname, method = 'GET') {
|
|
72
|
+
if (typeof pathname !== 'string') return false;
|
|
73
|
+
// Singular /api/world/<id>/... — the per-world CP proxy + /progress.
|
|
74
|
+
if (pathname.startsWith('/api/world/')) return true;
|
|
75
|
+
// CLI per-world routes (olam status/logs <world>).
|
|
76
|
+
if (pathname.startsWith('/v1/worlds/')) return true;
|
|
77
|
+
// Plural /api/worlds:
|
|
78
|
+
// bare LIST (GET/HEAD /api/worlds) → honest [] in serve-only, NOT blocked.
|
|
79
|
+
// create (POST /api/worlds) + any per-world subpath (/api/worlds/<id>...) → 503.
|
|
80
|
+
if (pathname === '/api/worlds') {
|
|
81
|
+
return method !== 'GET' && method !== 'HEAD';
|
|
82
|
+
}
|
|
83
|
+
if (/^\/api\/worlds\/[^/?#]+/.test(pathname)) return true;
|
|
84
|
+
return false;
|
|
85
|
+
}
|