@rubixkube/rubix 0.0.3 → 0.0.5

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,20 @@ 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.5] - 2025-03-06
9
+
10
+ ### Added
11
+
12
+ - Bugfixes and performance improvements
13
+
14
+ ## [0.0.4] - 2025-03-03
15
+
16
+ ### Added
17
+
18
+ - Environment support — `/environments` to switch
19
+ - System Stats Panel in dashboard with live insights and RCA
20
+ - Bugfixes and performance improvements
21
+
8
22
  ## [0.0.3]
9
23
 
10
24
  ### Added
@@ -13,7 +27,6 @@ and this project adheres to [Semantic Versioning](https://semver.org).
13
27
  - Persistent user settings
14
28
  - Bugfixes and performance improvements
15
29
 
16
-
17
30
  ## [0.0.2]
18
31
 
19
32
  ### 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
  }
@@ -1,7 +1,7 @@
1
1
  import { getConfig } from "../config/env.js";
2
2
  import { refreshAccessToken } from "./device-auth.js";
3
3
  import { saveAuthConfig } from "./auth-store.js";
4
- const DEFAULT_APP_NAME = "SRI Agent";
4
+ const DEFAULT_APP_NAME = "Rubix";
5
5
  export class StreamError extends Error {
6
6
  reason;
7
7
  status;
@@ -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;
@@ -58,7 +87,8 @@ export async function refreshAndUpdateAuth(auth) {
58
87
  }
59
88
  catch (error) {
60
89
  const msg = error instanceof Error ? error.message : String(error);
61
- throw new StreamError(`Token refresh failed: ${msg}. Please run /login again.`, {
90
+ const fullMsg = msg.includes("Token refresh failed") ? msg : `Token refresh failed: ${msg}`;
91
+ throw new StreamError(`${fullMsg}. Please run /login again.`, {
62
92
  reason: "http_error",
63
93
  status: 401,
64
94
  });
@@ -322,7 +352,7 @@ export async function getOrCreateSession(auth, preferredId, clusterId, appName =
322
352
  return createSession(auth, appName, clusterId);
323
353
  }
324
354
  const HEALTHY_STATUSES = new Set(["connected", "healthy", "active"]);
325
- export async function listClusters(auth) {
355
+ export async function listEnvironments(auth) {
326
356
  const authBase = getConfig().authApiBase;
327
357
  if (!authBase)
328
358
  throw new Error("RUBIXKUBE_AUTH_API_BASE is not set.");
@@ -341,22 +371,22 @@ export async function listClusters(auth) {
341
371
  });
342
372
  if (!response.ok) {
343
373
  const text = await response.text();
344
- throw new Error(`Failed to load clusters (${response.status}): ${text}`);
374
+ throw new Error(`Failed to load environments (${response.status}): ${text}`);
345
375
  }
346
376
  const payload = (await response.json());
347
377
  return (payload.clusters ?? [])
348
378
  .filter((c) => !!c?.cluster_id)
349
379
  .map((c) => ({
350
380
  id: c.id ?? c.cluster_id ?? "",
351
- cluster_id: c.cluster_id ?? "",
352
- name: c.name ?? c.cluster_id ?? "Unnamed cluster",
381
+ environment_id: c.cluster_id ?? "",
382
+ name: c.name ?? c.cluster_id ?? "Unnamed environment",
353
383
  status: (c.status ?? "unknown"),
354
384
  region: c.region,
355
- cluster_type: c.cluster_type,
385
+ environment_type: c.cluster_type,
356
386
  }));
357
387
  }
358
- export function firstHealthyCluster(clusters) {
359
- return clusters.find((c) => HEALTHY_STATUSES.has(c.status)) ?? clusters[0] ?? null;
388
+ export function firstHealthyEnvironment(environments) {
389
+ return environments.find((c) => HEALTHY_STATUSES.has(c.status)) ?? environments[0] ?? null;
360
390
  }
361
391
  export async function listModels(auth) {
362
392
  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;