@rubixkube/rubix 0.0.2 → 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 +36 -0
- package/LICENSE +33 -0
- package/README.md +29 -12
- package/dist/cli.js +7 -0
- package/dist/commands/chat.js +2 -1
- package/dist/commands/login.js +23 -2
- package/dist/commands/model.js +84 -0
- package/dist/config/env.js +2 -0
- package/dist/core/device-auth.js +39 -1
- package/dist/core/rubix-api.js +211 -27
- package/dist/core/session-store.js +36 -0
- package/dist/core/settings.js +30 -0
- package/dist/core/update-check.js +51 -0
- package/dist/core/whats-new.js +56 -0
- package/dist/ui/App.js +453 -141
- package/dist/ui/components/BrandPanel.js +1 -1
- package/dist/ui/components/ChatTranscript.js +108 -22
- package/dist/ui/components/Composer.js +67 -8
- package/dist/ui/components/DashboardPanel.js +32 -9
- package/dist/ui/components/SplashScreen.js +2 -8
- package/dist/ui/hooks/useBracketedPaste.js +27 -0
- package/dist/ui/theme.js +3 -1
- package/package.json +6 -4
- package/patches/ink-multiline-input+0.1.0.patch +246 -16
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org).
|
|
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
|
+
|
|
16
|
+
## [0.0.3]
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- /models to switch between models
|
|
21
|
+
- Persistent user settings
|
|
22
|
+
- Bugfixes and performance improvements
|
|
23
|
+
|
|
24
|
+
## [0.0.2]
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- Slash commands
|
|
29
|
+
- Session history and auto-resume
|
|
30
|
+
- Streaming workflow event tracing
|
|
31
|
+
|
|
32
|
+
## [0.0.1]
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- Initial release
|
package/LICENSE
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
Copyright (c) 2026 Rubixkube Intelligence Pvt. Ltd. All rights reserved.
|
|
2
|
+
|
|
3
|
+
RUBIXKUBE PROPRIETARY SOFTWARE LICENSE AGREEMENT
|
|
4
|
+
|
|
5
|
+
IMPORTANT: PLEASE READ THIS LICENSE AGREEMENT CAREFULLY. BY INSTALLING OR USING THE RUBIX CLI ("SOFTWARE"), YOU AGREE TO BE BOUND BY THE TERMS OF THIS AGREEMENT.
|
|
6
|
+
|
|
7
|
+
RubixKube and Site Reliability Intelligence are trademarks of Rubixkube Intelligence Pvt. Ltd.
|
|
8
|
+
|
|
9
|
+
1. GRANT OF LICENSE
|
|
10
|
+
Rubixkube Intelligence Pvt. Ltd. ("Company") grants you a non-exclusive, non-transferable, limited license to use the Software solely for your internal business purposes, provided that you have a valid and active account on the RubixKube platform (https://console.rubixkube.ai).
|
|
11
|
+
|
|
12
|
+
2. RESTRICTIONS
|
|
13
|
+
You shall not, and shall not permit any third party to:
|
|
14
|
+
(a) copy, modify, or create derivative works of the Software;
|
|
15
|
+
(b) redistribute, sell, lease, sublicense, or otherwise transfer the Software to any third party;
|
|
16
|
+
(c) reverse engineer, decompile, disassemble, or attempt to derive the source code of the Software;
|
|
17
|
+
(d) remove or alter any copyright, trademark, or other proprietary notices;
|
|
18
|
+
(e) use the Software in any manner that violates applicable laws or regulations.
|
|
19
|
+
|
|
20
|
+
3. INTELLECTUAL PROPERTY
|
|
21
|
+
The Software is licensed, not sold. All title, ownership rights, and intellectual property rights in and to the Software (including but not limited to any images, animations, video, audio, music, and text incorporated into the Software) are owned by Rubixkube Intelligence Pvt. Ltd. This Agreement does not grant you any rights to trademarks or service marks of the Company.
|
|
22
|
+
|
|
23
|
+
4. TERMINATION
|
|
24
|
+
This license is effective until terminated. Your rights under this license will terminate automatically without notice from the Company if you fail to comply with any term(s) of this Agreement or if your RubixKube account is suspended or terminated. Upon termination, you must cease all use of the Software and destroy all copies.
|
|
25
|
+
|
|
26
|
+
5. DISCLAIMER OF WARRANTIES
|
|
27
|
+
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE COMPANY DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
|
|
28
|
+
|
|
29
|
+
6. LIMITATION OF LIABILITY
|
|
30
|
+
IN NO EVENT SHALL THE COMPANY BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, OR LOSS OF BUSINESS INFORMATION) ARISING OUT OF THE USE OF OR INABILITY TO USE THE SOFTWARE.
|
|
31
|
+
|
|
32
|
+
7. CONTACT
|
|
33
|
+
For any questions regarding this license, please contact: connect@rubixkube.ai.
|
package/README.md
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
# Rubix CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@rubixkube/rubix)
|
|
4
|
+
[](https://www.npmjs.com/package/@rubixkube/rubix)
|
|
5
|
+
[](https://rubixkube.ai)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Built for SREs and platform engineers who own production reliability. **Rubix** brings RubixKube's Site Reliability Intelligence directly into your terminal — investigate incidents, understand blast radius, and get AI-driven RCA without leaving the command line.
|
|
8
|
+
|
|
9
|
+
Rubix connects to the RubixKube cloud platform — no local setup beyond a free account.
|
|
10
|
+
|
|
11
|
+

|
|
6
12
|
|
|
7
13
|
## Install
|
|
8
14
|
|
|
@@ -18,24 +24,35 @@ npx @rubixkube/rubix
|
|
|
18
24
|
|
|
19
25
|
## Quick Start
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
```bash
|
|
28
|
+
rubix
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
On first run, you'll be prompted to log in via a browser link — takes 30 seconds. After that, you're in.
|
|
32
|
+
|
|
33
|
+
Start asking:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
> What's causing the latency spike in production?
|
|
37
|
+
> Show me recent incidents for the payments service
|
|
38
|
+
> What changed before this alert fired?
|
|
39
|
+
```
|
|
24
40
|
|
|
25
|
-
##
|
|
41
|
+
## What You Can Do
|
|
26
42
|
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
- **
|
|
30
|
-
- **
|
|
43
|
+
- **Investigate incidents** — ask natural language questions about your live infrastructure
|
|
44
|
+
- **Get RCA** — AI-generated root cause analysis, right in the terminal
|
|
45
|
+
- **Track multiple environments** — switch context without leaving the chat
|
|
46
|
+
- **Resume sessions** — pick up where you left off across terminal sessions
|
|
47
|
+
- **Slash commands** — `/new`, `/resume`, `/environments`, `/status`, `/send`, `/clear`, `/help`, `/exit`
|
|
31
48
|
|
|
32
49
|
## Requirements
|
|
33
50
|
|
|
34
51
|
- Node.js 18+
|
|
35
|
-
- [RubixKube account](https://console.rubixkube.ai)
|
|
52
|
+
- [RubixKube account](https://console.rubixkube.ai) — free to sign up
|
|
36
53
|
|
|
37
54
|
## Links
|
|
38
55
|
|
|
39
|
-
- [
|
|
56
|
+
- [rubixkube.ai](https://rubixkube.ai) — Site Reliability Intelligence platform
|
|
40
57
|
- [Documentation](https://docs.rubixkube.ai)
|
|
41
58
|
- [Console](https://console.rubixkube.ai)
|
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import { VERSION } from "./version.js";
|
|
|
5
5
|
import { runLoginCommand } from "./commands/login.js";
|
|
6
6
|
import { runLogoutCommand } from "./commands/logout.js";
|
|
7
7
|
import { runSessionsCommand } from "./commands/sessions.js";
|
|
8
|
+
import { runModelCommand } from "./commands/model.js";
|
|
8
9
|
async function main() {
|
|
9
10
|
if (process.argv.length <= 2) {
|
|
10
11
|
await runChatCommand({});
|
|
@@ -41,6 +42,12 @@ async function main() {
|
|
|
41
42
|
.action(async () => {
|
|
42
43
|
await runSessionsCommand();
|
|
43
44
|
});
|
|
45
|
+
program
|
|
46
|
+
.command("model [subcommand] [modelId]")
|
|
47
|
+
.description("Manage AI models")
|
|
48
|
+
.action(async (subcommand, modelId) => {
|
|
49
|
+
await runModelCommand({ subcommand, modelId });
|
|
50
|
+
});
|
|
44
51
|
await program.parseAsync(process.argv);
|
|
45
52
|
}
|
|
46
53
|
main().catch((error) => {
|
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/commands/login.js
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
|
-
import { saveAuthConfig } from "../core/auth-store.js";
|
|
2
|
-
import { authenticateWithDeviceFlow } from "../core/device-auth.js";
|
|
1
|
+
import { loadAuthConfig, saveAuthConfig } from "../core/auth-store.js";
|
|
2
|
+
import { authenticateWithDeviceFlow, isTokenNearExpiry } from "../core/device-auth.js";
|
|
3
|
+
import { refreshAndUpdateAuth } from "../core/rubix-api.js";
|
|
3
4
|
export async function runLoginCommand() {
|
|
5
|
+
let existing = await loadAuthConfig();
|
|
6
|
+
if (existing?.isAuthenticated && (existing.idToken || existing.authToken)) {
|
|
7
|
+
let tokenValid = !isTokenNearExpiry(existing.idToken ?? existing.authToken);
|
|
8
|
+
if (!tokenValid && existing.refreshToken) {
|
|
9
|
+
try {
|
|
10
|
+
console.log("Session token expired, attempting to refresh...");
|
|
11
|
+
existing = await refreshAndUpdateAuth(existing);
|
|
12
|
+
await saveAuthConfig(existing);
|
|
13
|
+
tokenValid = true;
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
console.log("Failed to refresh token. Need to log in again.");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (tokenValid) {
|
|
20
|
+
console.log(`Already authenticated as ${existing?.userName ?? existing?.userEmail ?? "user"}.`);
|
|
21
|
+
console.log("Run `rubix logout` first if you want to switch accounts.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
4
25
|
console.log("Starting Rubix device authentication...");
|
|
5
26
|
try {
|
|
6
27
|
const authConfig = await authenticateWithDeviceFlow((message) => {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { loadAuthConfig } from "../core/auth-store.js";
|
|
2
|
+
import { listModels, setSessionModel, listSessions } from "../core/rubix-api.js";
|
|
3
|
+
export async function runModelCommand(args) {
|
|
4
|
+
try {
|
|
5
|
+
const auth = await loadAuthConfig();
|
|
6
|
+
if (!auth?.isAuthenticated) {
|
|
7
|
+
console.error("Not authenticated. Run `rubix login` first.");
|
|
8
|
+
process.exitCode = 1;
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const subcommand = args.subcommand?.toLowerCase() ?? "list";
|
|
12
|
+
switch (subcommand) {
|
|
13
|
+
case "list": {
|
|
14
|
+
const models = await listModels(auth);
|
|
15
|
+
if (models.length === 0) {
|
|
16
|
+
console.log("No models available.");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
console.log("\nAvailable models:\n");
|
|
20
|
+
for (const model of models) {
|
|
21
|
+
const marker = model.default ? "●" : " ";
|
|
22
|
+
const experimental = model.experimental ? " (experimental)" : "";
|
|
23
|
+
const thinking = model.thinking_supported ? " · thinking" : "";
|
|
24
|
+
console.log(`${marker} ${model.id.padEnd(12)} ${model.display_name}`);
|
|
25
|
+
if (model.description) {
|
|
26
|
+
console.log(` ${model.description}`);
|
|
27
|
+
}
|
|
28
|
+
if (experimental || thinking) {
|
|
29
|
+
console.log(` ${experimental}${thinking}`.trim());
|
|
30
|
+
}
|
|
31
|
+
console.log();
|
|
32
|
+
}
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
case "set": {
|
|
36
|
+
if (!args.modelId) {
|
|
37
|
+
console.error("Usage: rubix model set <model_id>");
|
|
38
|
+
console.error("Example: rubix model set expert");
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const sessions = await listSessions(auth, 1, 0);
|
|
43
|
+
if (sessions.length === 0) {
|
|
44
|
+
console.error("No active session. Use `rubix chat` to create one.");
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const sessionId = sessions[0].id;
|
|
49
|
+
const result = await setSessionModel(auth, sessionId, args.modelId);
|
|
50
|
+
console.log(`Switched to: ${result.displayName}`);
|
|
51
|
+
if (result.thinkingSupported) {
|
|
52
|
+
console.log("(This model supports extended thinking)");
|
|
53
|
+
}
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case "current": {
|
|
57
|
+
const sessions = await listSessions(auth, 1, 0);
|
|
58
|
+
if (sessions.length === 0) {
|
|
59
|
+
console.log("No active session.");
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const models = await listModels(auth);
|
|
64
|
+
const defaultModel = models.find((m) => m.default);
|
|
65
|
+
if (defaultModel) {
|
|
66
|
+
console.log(`Current model: ${defaultModel.display_name} (${defaultModel.id})`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log("No model information available.");
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
default:
|
|
74
|
+
console.error(`Unknown subcommand: ${subcommand}`);
|
|
75
|
+
console.error("Usage: rubix model [list|set|current]");
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
81
|
+
console.error(`Failed: ${message}`);
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
}
|
|
84
|
+
}
|
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/device-auth.js
CHANGED
|
@@ -19,6 +19,19 @@ function decodeJwtClaims(token) {
|
|
|
19
19
|
const decoded = Buffer.from(padded, "base64").toString("utf8");
|
|
20
20
|
return JSON.parse(decoded);
|
|
21
21
|
}
|
|
22
|
+
export function isTokenNearExpiry(token, thresholdMs = 5 * 60 * 1000) {
|
|
23
|
+
if (!token)
|
|
24
|
+
return false;
|
|
25
|
+
try {
|
|
26
|
+
const claims = decodeJwtClaims(token);
|
|
27
|
+
if (!claims.exp)
|
|
28
|
+
return false;
|
|
29
|
+
return Date.now() + thresholdMs > claims.exp * 1000;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
22
35
|
export async function startDeviceAuth() {
|
|
23
36
|
const missing = getMissingEnv(AUTH_REQUIRED_ENV);
|
|
24
37
|
if (missing.length > 0) {
|
|
@@ -27,7 +40,7 @@ export async function startDeviceAuth() {
|
|
|
27
40
|
const { auth0ClientId, auth0Audience } = getConfig();
|
|
28
41
|
const body = new URLSearchParams();
|
|
29
42
|
body.set("client_id", auth0ClientId);
|
|
30
|
-
body.set("scope", "openid profile email");
|
|
43
|
+
body.set("scope", "openid profile email offline_access");
|
|
31
44
|
if (auth0Audience) {
|
|
32
45
|
body.set("audience", auth0Audience);
|
|
33
46
|
}
|
|
@@ -153,3 +166,28 @@ export async function authenticateWithDeviceFlow(log) {
|
|
|
153
166
|
timestamp: Date.now(),
|
|
154
167
|
};
|
|
155
168
|
}
|
|
169
|
+
export async function refreshAccessToken(auth) {
|
|
170
|
+
if (!auth.refreshToken) {
|
|
171
|
+
throw new Error("No refresh token available. Please login again.");
|
|
172
|
+
}
|
|
173
|
+
const body = new URLSearchParams();
|
|
174
|
+
body.set("grant_type", "refresh_token");
|
|
175
|
+
body.set("refresh_token", auth.refreshToken);
|
|
176
|
+
body.set("client_id", getConfig().auth0ClientId);
|
|
177
|
+
const response = await fetch(`${auth0BaseUrl()}/oauth/token`, {
|
|
178
|
+
method: "POST",
|
|
179
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
180
|
+
body,
|
|
181
|
+
});
|
|
182
|
+
const raw = (await response.json());
|
|
183
|
+
if (!response.ok || !raw.id_token || !raw.access_token) {
|
|
184
|
+
throw new Error(`Token refresh failed: ${raw.error ?? response.statusText}`);
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
...auth,
|
|
188
|
+
authToken: raw.access_token,
|
|
189
|
+
idToken: raw.id_token,
|
|
190
|
+
refreshToken: raw.refresh_token ?? auth.refreshToken,
|
|
191
|
+
timestamp: Date.now(),
|
|
192
|
+
};
|
|
193
|
+
}
|