@rubixkube/rubix 0.0.3 → 0.0.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/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org).
7
7
 
8
+ ## [0.0.4] - 2025-03-03
9
+
10
+ ### Added
11
+
12
+ - Environment support — `/environments` to switch
13
+ - System Stats Panel in dashboard with live insights and RCA
14
+ - Bugfixes and performance improvements
15
+
8
16
  ## [0.0.3]
9
17
 
10
18
  ### Added
@@ -13,7 +21,6 @@ and this project adheres to [Semantic Versioning](https://semver.org).
13
21
  - Persistent user settings
14
22
  - Bugfixes and performance improvements
15
23
 
16
-
17
24
  ## [0.0.2]
18
25
 
19
26
  ### Added
package/README.md CHANGED
@@ -42,9 +42,9 @@ Start asking:
42
42
 
43
43
  - **Investigate incidents** — ask natural language questions about your live infrastructure
44
44
  - **Get RCA** — AI-generated root cause analysis, right in the terminal
45
- - **Track multiple clusters** — switch context without leaving the chat
45
+ - **Track multiple environments** — switch context without leaving the chat
46
46
  - **Resume sessions** — pick up where you left off across terminal sessions
47
- - **Slash commands** — `/new`, `/sessions`, `/status`, `/clear`, `/help`, `/exit`
47
+ - **Slash commands** — `/new`, `/resume`, `/environments`, `/status`, `/send`, `/clear`, `/help`, `/exit`
48
48
 
49
49
  ## Requirements
50
50
 
@@ -11,7 +11,8 @@ export async function runChatCommand(options) {
11
11
  seedPrompt: options.prompt,
12
12
  }),
13
13
  }), {
14
- exitOnCtrlC: true,
14
+ // false so App's useInput receives Ctrl+C and can implement two-press exit
15
+ exitOnCtrlC: false,
15
16
  });
16
17
  await instance.waitUntilExit();
17
18
  }
@@ -7,6 +7,7 @@ const DEFAULTS = {
7
7
  RUBIXKUBE_AUTH0_AUDIENCE: "",
8
8
  RUBIXKUBE_AUTH_API_BASE: "https://console.rubixkube.ai/api/orchestrator",
9
9
  RUBIXKUBE_OPEL_API_BASE: "https://console.rubixkube.ai/api/opel",
10
+ RUBIXKUBE_MEMORY_API_BASE: "https://console.rubixkube.ai/api/memory",
10
11
  RUBIXKUBE_STREAM_TIMEOUT_MS: "600000",
11
12
  };
12
13
  export const REQUIRED_ENV = [
@@ -27,6 +28,7 @@ export function getConfig() {
27
28
  auth0Audience: (process.env.RUBIXKUBE_AUTH0_AUDIENCE ?? DEFAULTS.RUBIXKUBE_AUTH0_AUDIENCE) || undefined,
28
29
  authApiBase: process.env.RUBIXKUBE_AUTH_API_BASE ?? DEFAULTS.RUBIXKUBE_AUTH_API_BASE,
29
30
  opelApiBase: process.env.RUBIXKUBE_OPEL_API_BASE ?? DEFAULTS.RUBIXKUBE_OPEL_API_BASE,
31
+ memoryApiBase: process.env.RUBIXKUBE_MEMORY_API_BASE ?? DEFAULTS.RUBIXKUBE_MEMORY_API_BASE,
30
32
  streamTimeoutMs: Number(process.env.RUBIXKUBE_STREAM_TIMEOUT_MS ?? DEFAULTS.RUBIXKUBE_STREAM_TIMEOUT_MS) || 600000,
31
33
  };
32
34
  }
@@ -21,6 +21,35 @@ function opelBase() {
21
21
  }
22
22
  return value.replace(/\/+$/, "");
23
23
  }
24
+ function memoryBase() {
25
+ const value = getConfig().memoryApiBase;
26
+ if (!value) {
27
+ throw new Error("RUBIXKUBE_MEMORY_API_BASE is not set.");
28
+ }
29
+ return value.replace(/\/+$/, "");
30
+ }
31
+ export async function fetchSystemStats(auth) {
32
+ try {
33
+ const url = `${memoryBase()}/api/v1/observability/stats`;
34
+ const response = await fetchWithAutoRefresh(auth, url, {
35
+ method: "GET",
36
+ headers: headers(auth, true),
37
+ });
38
+ if (!response.ok)
39
+ return null;
40
+ const payload = (await response.json());
41
+ return {
42
+ insights: Number(payload.insights ?? 0),
43
+ activeInsights: Number(payload.activeInsights ?? 0),
44
+ resolvedInsights: Number(payload.resolvedInsights ?? 0),
45
+ rcaReports: Number(payload.rcaReports ?? 0),
46
+ lastUpdated: String(payload.lastUpdated ?? new Date().toISOString()),
47
+ };
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
24
53
  function ensureAuth(auth) {
25
54
  const token = auth.idToken ?? auth.authToken;
26
55
  const userId = auth.userEmail ?? auth.userId;
@@ -322,7 +351,7 @@ export async function getOrCreateSession(auth, preferredId, clusterId, appName =
322
351
  return createSession(auth, appName, clusterId);
323
352
  }
324
353
  const HEALTHY_STATUSES = new Set(["connected", "healthy", "active"]);
325
- export async function listClusters(auth) {
354
+ export async function listEnvironments(auth) {
326
355
  const authBase = getConfig().authApiBase;
327
356
  if (!authBase)
328
357
  throw new Error("RUBIXKUBE_AUTH_API_BASE is not set.");
@@ -341,22 +370,22 @@ export async function listClusters(auth) {
341
370
  });
342
371
  if (!response.ok) {
343
372
  const text = await response.text();
344
- throw new Error(`Failed to load clusters (${response.status}): ${text}`);
373
+ throw new Error(`Failed to load environments (${response.status}): ${text}`);
345
374
  }
346
375
  const payload = (await response.json());
347
376
  return (payload.clusters ?? [])
348
377
  .filter((c) => !!c?.cluster_id)
349
378
  .map((c) => ({
350
379
  id: c.id ?? c.cluster_id ?? "",
351
- cluster_id: c.cluster_id ?? "",
352
- name: c.name ?? c.cluster_id ?? "Unnamed cluster",
380
+ environment_id: c.cluster_id ?? "",
381
+ name: c.name ?? c.cluster_id ?? "Unnamed environment",
353
382
  status: (c.status ?? "unknown"),
354
383
  region: c.region,
355
- cluster_type: c.cluster_type,
384
+ environment_type: c.cluster_type,
356
385
  }));
357
386
  }
358
- export function firstHealthyCluster(clusters) {
359
- return clusters.find((c) => HEALTHY_STATUSES.has(c.status)) ?? clusters[0] ?? null;
387
+ export function firstHealthyEnvironment(environments) {
388
+ return environments.find((c) => HEALTHY_STATUSES.has(c.status)) ?? environments[0] ?? null;
360
389
  }
361
390
  export async function listModels(auth) {
362
391
  const url = `${opelBase()}/models`;
@@ -9,7 +9,12 @@ export function getSettingsPath() {
9
9
  export async function loadSettings() {
10
10
  try {
11
11
  const data = await fs.readFile(getSettingsPath(), "utf8");
12
- return JSON.parse(data);
12
+ const raw = JSON.parse(data);
13
+ if (raw.clusterId && !raw.environmentId) {
14
+ raw.environmentId = raw.clusterId;
15
+ delete raw.clusterId;
16
+ }
17
+ return raw;
13
18
  }
14
19
  catch (error) {
15
20
  const asNodeError = error;