@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 +14 -1
- package/README.md +2 -2
- package/dist/commands/chat.js +2 -1
- package/dist/config/env.js +2 -0
- package/dist/core/rubix-api.js +39 -9
- package/dist/core/settings.js +6 -1
- package/dist/ui/App.js +212 -167
- package/dist/ui/components/ChatTranscript.js +68 -35
- package/dist/ui/components/Composer.js +1 -1
- package/dist/ui/components/DashboardPanel.js +32 -9
- package/dist/ui/theme.js +3 -1
- package/package.json +1 -1
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
|
|
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`, `/
|
|
47
|
+
- **Slash commands** — `/new`, `/resume`, `/environments`, `/status`, `/send`, `/clear`, `/help`, `/exit`
|
|
48
48
|
|
|
49
49
|
## Requirements
|
|
50
50
|
|
package/dist/commands/chat.js
CHANGED
|
@@ -11,7 +11,8 @@ export async function runChatCommand(options) {
|
|
|
11
11
|
seedPrompt: options.prompt,
|
|
12
12
|
}),
|
|
13
13
|
}), {
|
|
14
|
-
|
|
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
|
}
|
package/dist/config/env.js
CHANGED
|
@@ -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
|
}
|
package/dist/core/rubix-api.js
CHANGED
|
@@ -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 = "
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
352
|
-
name: c.name ?? c.cluster_id ?? "Unnamed
|
|
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
|
-
|
|
385
|
+
environment_type: c.cluster_type,
|
|
356
386
|
}));
|
|
357
387
|
}
|
|
358
|
-
export function
|
|
359
|
-
return
|
|
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`;
|
package/dist/core/settings.js
CHANGED
|
@@ -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
|
-
|
|
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;
|