@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.
Files changed (110) hide show
  1. package/dist/commands/auth.d.ts.map +1 -1
  2. package/dist/commands/auth.js +67 -19
  3. package/dist/commands/auth.js.map +1 -1
  4. package/dist/commands/config.d.ts.map +1 -1
  5. package/dist/commands/config.js +93 -0
  6. package/dist/commands/config.js.map +1 -1
  7. package/dist/commands/destroy.d.ts +41 -0
  8. package/dist/commands/destroy.d.ts.map +1 -1
  9. package/dist/commands/destroy.js +81 -33
  10. package/dist/commands/destroy.js.map +1 -1
  11. package/dist/commands/dispatch-resolve.d.ts +54 -0
  12. package/dist/commands/dispatch-resolve.d.ts.map +1 -0
  13. package/dist/commands/dispatch-resolve.js +105 -0
  14. package/dist/commands/dispatch-resolve.js.map +1 -0
  15. package/dist/commands/dispatch.d.ts.map +1 -1
  16. package/dist/commands/dispatch.js +40 -9
  17. package/dist/commands/dispatch.js.map +1 -1
  18. package/dist/commands/flywheel/k5-validate.d.ts +31 -0
  19. package/dist/commands/flywheel/k5-validate.d.ts.map +1 -1
  20. package/dist/commands/flywheel/k5-validate.js +80 -19
  21. package/dist/commands/flywheel/k5-validate.js.map +1 -1
  22. package/dist/commands/kg-classify.d.ts.map +1 -1
  23. package/dist/commands/kg-classify.js +20 -0
  24. package/dist/commands/kg-classify.js.map +1 -1
  25. package/dist/commands/kg-doctor.d.ts +67 -6
  26. package/dist/commands/kg-doctor.d.ts.map +1 -1
  27. package/dist/commands/kg-doctor.js +126 -46
  28. package/dist/commands/kg-doctor.js.map +1 -1
  29. package/dist/commands/list.d.ts +27 -0
  30. package/dist/commands/list.d.ts.map +1 -1
  31. package/dist/commands/list.js +67 -19
  32. package/dist/commands/list.js.map +1 -1
  33. package/dist/commands/memory/status.d.ts +18 -0
  34. package/dist/commands/memory/status.d.ts.map +1 -1
  35. package/dist/commands/memory/status.js +38 -2
  36. package/dist/commands/memory/status.js.map +1 -1
  37. package/dist/commands/memory-service-container.d.ts +44 -0
  38. package/dist/commands/memory-service-container.d.ts.map +1 -1
  39. package/dist/commands/memory-service-container.js +49 -0
  40. package/dist/commands/memory-service-container.js.map +1 -1
  41. package/dist/commands/ps.d.ts +32 -0
  42. package/dist/commands/ps.d.ts.map +1 -1
  43. package/dist/commands/ps.js +34 -0
  44. package/dist/commands/ps.js.map +1 -1
  45. package/dist/commands/runbooks.d.ts +32 -0
  46. package/dist/commands/runbooks.d.ts.map +1 -1
  47. package/dist/commands/runbooks.js +79 -22
  48. package/dist/commands/runbooks.js.map +1 -1
  49. package/dist/commands/skills-source.d.ts.map +1 -1
  50. package/dist/commands/skills-source.js +77 -2
  51. package/dist/commands/skills-source.js.map +1 -1
  52. package/dist/commands/upgrade-history.d.ts +0 -2
  53. package/dist/commands/upgrade-history.d.ts.map +1 -1
  54. package/dist/commands/upgrade-history.js +0 -6
  55. package/dist/commands/upgrade-history.js.map +1 -1
  56. package/dist/commands/upgrade-lock.d.ts +0 -9
  57. package/dist/commands/upgrade-lock.d.ts.map +1 -1
  58. package/dist/commands/upgrade-lock.js +1 -1
  59. package/dist/commands/upgrade-lock.js.map +1 -1
  60. package/dist/commands/world-snapshot.d.ts +13 -0
  61. package/dist/commands/world-snapshot.d.ts.map +1 -1
  62. package/dist/commands/world-snapshot.js +81 -1
  63. package/dist/commands/world-snapshot.js.map +1 -1
  64. package/dist/commands/yolo.d.ts +0 -4
  65. package/dist/commands/yolo.d.ts.map +1 -1
  66. package/dist/commands/yolo.js +2 -2
  67. package/dist/commands/yolo.js.map +1 -1
  68. package/dist/image-digests.json +8 -8
  69. package/dist/index.js +3403 -2459
  70. package/dist/lib/anthropic-base-url-file.d.ts +37 -0
  71. package/dist/lib/anthropic-base-url-file.d.ts.map +1 -0
  72. package/dist/lib/anthropic-base-url-file.js +46 -0
  73. package/dist/lib/anthropic-base-url-file.js.map +1 -0
  74. package/dist/lib/auth-remote.d.ts +9 -0
  75. package/dist/lib/auth-remote.d.ts.map +1 -1
  76. package/dist/lib/auth-remote.js +19 -4
  77. package/dist/lib/auth-remote.js.map +1 -1
  78. package/dist/lib/cf-access-token.d.ts +32 -0
  79. package/dist/lib/cf-access-token.d.ts.map +1 -0
  80. package/dist/lib/cf-access-token.js +52 -0
  81. package/dist/lib/cf-access-token.js.map +1 -0
  82. package/dist/lib/config.d.ts +17 -3
  83. package/dist/lib/config.d.ts.map +1 -1
  84. package/dist/lib/config.js +28 -4
  85. package/dist/lib/config.js.map +1 -1
  86. package/dist/lib/kubectl-context.d.ts +49 -0
  87. package/dist/lib/kubectl-context.d.ts.map +1 -1
  88. package/dist/lib/kubectl-context.js +64 -2
  89. package/dist/lib/kubectl-context.js.map +1 -1
  90. package/dist/lib/upgrade-kubernetes.d.ts +7 -0
  91. package/dist/lib/upgrade-kubernetes.d.ts.map +1 -1
  92. package/dist/lib/upgrade-kubernetes.js +35 -8
  93. package/dist/lib/upgrade-kubernetes.js.map +1 -1
  94. package/dist/mcp-server.js +1457 -1004
  95. package/hermes-bundle/version.json +1 -1
  96. package/host-cp/k8s/manifests/45-pvc.yaml +6 -2
  97. package/host-cp/k8s/manifests/50-deployment.yaml +1 -1
  98. package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +1 -1
  99. package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +1 -1
  100. package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +1 -1
  101. package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +1 -1
  102. package/host-cp/observability/trace-summary.mjs +267 -0
  103. package/host-cp/src/bootstrap-selective.mjs +30 -28
  104. package/host-cp/src/host-stream.mjs +52 -0
  105. package/host-cp/src/redirect.mjs +7 -0
  106. package/host-cp/src/router.mjs +168 -0
  107. package/host-cp/src/serve-only-config.mjs +85 -0
  108. package/host-cp/src/server.mjs +346 -217
  109. package/host-cp/src/world-services.mjs +136 -0
  110. 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
+ }