kern0-cli 0.1.0

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.
@@ -0,0 +1,126 @@
1
+ import {
2
+ setCurrentOrg,
3
+ deleteToken,
4
+ setUser,
5
+ setAuth,
6
+ setOrganizations,
7
+ printContext,
8
+ isAuthenticated,
9
+ getUser,
10
+ } from "../config";
11
+ import {
12
+ initializeDeviceFlow,
13
+ pollForToken,
14
+ completeLogin,
15
+ } from "../auth-flow";
16
+ import open from "open";
17
+
18
+ export async function login(): Promise<void> {
19
+ console.log("\nšŸ” Kern0 Login\n");
20
+
21
+ // Check if already authenticated
22
+ if (isAuthenticated()) {
23
+ const user = getUser();
24
+ console.log(`Already authenticated as ${user?.email}.\n`);
25
+ console.log(`Run 'kern0 logout' if you want to log in as a different user.\n`);
26
+ return;
27
+ }
28
+
29
+ try {
30
+ console.log("ā³ Initializing device flow...\n");
31
+
32
+ // Step 1: Initialize device flow
33
+ const deviceFlow = await initializeDeviceFlow();
34
+
35
+ console.log("āœ… Device code generated!\n");
36
+ console.log("šŸ“± To authenticate:\n");
37
+ console.log(` 1. Go to: ${deviceFlow.verification_uri}`);
38
+ console.log(` 2. Enter code: ${deviceFlow.user_code}\n`);
39
+ console.log("ā³ Waiting for approval in browser...\n");
40
+
41
+ // Try to open browser automatically
42
+ try {
43
+ await open(deviceFlow.verification_uri);
44
+ console.log("🌐 Browser opened automatically\n");
45
+ } catch {
46
+ console.log("šŸ“Œ Copy the link above to open in your browser\n");
47
+ }
48
+
49
+ // Step 2: Poll for token
50
+ const tokenResponse = await pollForToken(deviceFlow.device_code);
51
+
52
+ console.log("āœ… Approved!\n");
53
+ console.log("ā³ Completing login...\n");
54
+
55
+ // Step 3: Complete login (fetch user, orgs, save config)
56
+ const { user, organizations } = await completeLogin(
57
+ tokenResponse.access_token,
58
+ tokenResponse.expires_in,
59
+ tokenResponse.user,
60
+ );
61
+
62
+ console.log("āœ… Login successful!\n");
63
+ console.log(`šŸ‘¤ User: ${user.email}\n`);
64
+
65
+ // Step 4: Select organization
66
+ if (organizations.length === 0) {
67
+ console.log("āš ļø No organizations found. Create one with 'kern0 org create'.\n");
68
+ return;
69
+ }
70
+
71
+ if (organizations.length === 1) {
72
+ // Auto-select if only one org
73
+ setCurrentOrg(organizations[0].id);
74
+ console.log(`šŸ¢ Organization: ${organizations[0].name}\n`);
75
+ } else {
76
+ // Show org selection
77
+ console.log("šŸ¢ Available Organizations:\n");
78
+ organizations.forEach((org, index) => {
79
+ console.log(` ${index + 1}. ${org.name}`);
80
+ });
81
+ console.log(
82
+ `\nšŸ“Œ Run 'kern0 org select <orgId>' to switch organizations\n`,
83
+ );
84
+ console.log(`Selecting first organization: ${organizations[0].name}\n`);
85
+ setCurrentOrg(organizations[0].id);
86
+ }
87
+
88
+ console.log("šŸŽ‰ Ready to use Kern0!\n");
89
+ printContext();
90
+ } catch (error) {
91
+ if (error instanceof Error) {
92
+ console.error("āŒ Login failed:", error.message);
93
+ }
94
+ process.exit(1);
95
+ }
96
+ }
97
+
98
+ export async function logout(): Promise<void> {
99
+ console.log("\nšŸ”“ Logging out...\n");
100
+
101
+ try {
102
+ // Clear credentials
103
+ deleteToken();
104
+
105
+ // Clear auth state
106
+ setAuth({
107
+ device_token: "",
108
+ expires_at: 0,
109
+ token_type: "device",
110
+ });
111
+
112
+ // Clear user
113
+ setUser({ id: "", email: "" });
114
+
115
+ // Clear organizations
116
+ setOrganizations([]);
117
+
118
+ console.log("āœ… Logged out successfully\n");
119
+ console.log("Run 'kern0 login' to authenticate again\n");
120
+ } catch (error) {
121
+ if (error instanceof Error) {
122
+ console.error("āŒ Logout failed:", error.message);
123
+ }
124
+ process.exit(1);
125
+ }
126
+ }
@@ -0,0 +1,68 @@
1
+ import {
2
+ requireAuthentication,
3
+ requireOrganization,
4
+ getCurrentCustomer,
5
+ setCurrentCustomer,
6
+ printContext,
7
+ } from "../config";
8
+ import {
9
+ listCustomers,
10
+ } from "../api";
11
+
12
+ export async function listCustomersCmd(): Promise<void> {
13
+ try {
14
+ requireAuthentication();
15
+ const orgId = requireOrganization();
16
+
17
+ console.log("\nšŸ¢ Customers:\n");
18
+
19
+ const customers = await listCustomers(orgId);
20
+ const currentCustomerId = getCurrentCustomer();
21
+
22
+ if (customers.length === 0) {
23
+ console.log(" No customers found.\n");
24
+ return;
25
+ }
26
+
27
+ console.log(" ID Name");
28
+ console.log(" " + "─".repeat(50));
29
+
30
+ customers.forEach(customer => {
31
+ const marker = customer.id === currentCustomerId ? "* " : " ";
32
+ const idDisplay = customer.id.padEnd(25);
33
+ console.log(` ${marker}${idDisplay} ${customer.name}`);
34
+ });
35
+
36
+ console.log("");
37
+ } catch (error) {
38
+ if (error instanceof Error) {
39
+ console.error("Error:", error.message);
40
+ }
41
+ process.exit(1);
42
+ }
43
+ }
44
+
45
+ export async function switchCustomerCmd(customerId: string): Promise<void> {
46
+ try {
47
+ requireAuthentication();
48
+ const orgId = requireOrganization();
49
+
50
+ const customers = await listCustomers(orgId);
51
+ const customer = customers.find(c => c.id === customerId);
52
+
53
+ if (!customer) {
54
+ console.error(`\nāŒ Customer "${customerId}" not found in current organization.\n`);
55
+ process.exit(1);
56
+ }
57
+
58
+ setCurrentCustomer(customerId);
59
+
60
+ console.log(`\nāœ… Switched to: ${customer.name}\n`);
61
+ printContext();
62
+ } catch (error) {
63
+ if (error instanceof Error) {
64
+ console.error("Error:", error.message);
65
+ }
66
+ process.exit(1);
67
+ }
68
+ }
@@ -0,0 +1,74 @@
1
+ import {
2
+ requireAuthentication,
3
+ requireOrganization,
4
+ } from "../config";
5
+ import { apiCall } from "../api";
6
+
7
+ export interface Environment {
8
+ id: string;
9
+ customerId: string;
10
+ customerName: string;
11
+ label: string;
12
+ status: string;
13
+ product: string;
14
+ updatedAt: string;
15
+ }
16
+
17
+ export async function listEnvironments(): Promise<void> {
18
+ try {
19
+ requireAuthentication();
20
+ const orgId = requireOrganization();
21
+
22
+ console.log("\nšŸ—ļø Environments:\n");
23
+
24
+ const response = await apiCall<{ environments: Environment[] }>(
25
+ "GET",
26
+ `/api/v1/orgs/${orgId}/environments`,
27
+ );
28
+
29
+ const environments = response.environments || [];
30
+
31
+ if (environments.length === 0) {
32
+ console.log(" No environments found.\n");
33
+ return;
34
+ }
35
+
36
+ console.log(" Label Status Customer Product Updated");
37
+ console.log(" " + "─".repeat(100));
38
+
39
+ environments.forEach((env) => {
40
+ const label = env.label.substring(0, 28).padEnd(28);
41
+ const status = (env.status || "unknown").substring(0, 12).padEnd(12);
42
+ const customer = (env.customerName || env.customerId)
43
+ .substring(0, 24)
44
+ .padEnd(24);
45
+ const product = (env.product || "unknown").substring(0, 8).padEnd(8);
46
+ const updated = new Date(env.updatedAt).toLocaleDateString();
47
+
48
+ console.log(` ${label}${status}${customer}${product}${updated}`);
49
+ });
50
+
51
+ console.log("");
52
+ } catch (error) {
53
+ if (error instanceof Error) {
54
+ console.error("Error:", error.message);
55
+ }
56
+ process.exit(1);
57
+ }
58
+ }
59
+
60
+ export async function getEnvironmentInfo(envId: string): Promise<void> {
61
+ try {
62
+ requireAuthentication();
63
+ const orgId = requireOrganization();
64
+
65
+ // TODO: Implement env info endpoint
66
+ console.error("Not yet implemented");
67
+ process.exit(1);
68
+ } catch (error) {
69
+ if (error instanceof Error) {
70
+ console.error("Error:", error.message);
71
+ }
72
+ process.exit(1);
73
+ }
74
+ }
@@ -0,0 +1,130 @@
1
+ import {
2
+ requireAuthentication,
3
+ requireOrganization,
4
+ getCurrentOrg,
5
+ setCurrentOrg,
6
+ getOrganizations,
7
+ setOrganizations,
8
+ addOrganization,
9
+ printContext,
10
+ } from "../config";
11
+ import {
12
+ listOrganizations,
13
+ getOrganization,
14
+ createOrganization as apiCreateOrganization,
15
+ } from "../api";
16
+
17
+ export async function listOrgs(): Promise<void> {
18
+ try {
19
+ requireAuthentication();
20
+
21
+ console.log("\nšŸ“¦ Your Organizations:\n");
22
+
23
+ const orgs = await listOrganizations();
24
+ const currentOrgId = getCurrentOrg();
25
+
26
+ if (orgs.length === 0) {
27
+ console.log(" No organizations found. Run `kern0 org create` to create one.\n");
28
+ return;
29
+ }
30
+
31
+ // Update config with fetched orgs
32
+ const orgData = orgs.map(o => ({ id: o.id, name: o.name }));
33
+ setOrganizations(orgData);
34
+
35
+ orgs.forEach(org => {
36
+ if (!org.id || !org.name) return; // Skip orgs without proper data
37
+ const marker = org.id === currentOrgId ? "* " : " ";
38
+ const type = org.type === "msp" ? " (MSP)" : "";
39
+ console.log(` ${marker}${org.id.substring(0, 8)}... ${org.name}${type}`);
40
+ });
41
+
42
+ console.log("");
43
+ } catch (error) {
44
+ if (error instanceof Error) {
45
+ console.error("Error:", error.message);
46
+ }
47
+ process.exit(1);
48
+ }
49
+ }
50
+
51
+ export async function selectOrg(orgId: string): Promise<void> {
52
+ try {
53
+ requireAuthentication();
54
+
55
+ // Verify org exists by fetching it
56
+ const org = await getOrganization(orgId);
57
+
58
+ // Update current org
59
+ setCurrentOrg(orgId);
60
+
61
+ // Add to organizations list if not already there
62
+ addOrganization({ id: org.id, name: org.name });
63
+
64
+ console.log(`\nāœ… Switched to: ${org.name}\n`);
65
+ printContext();
66
+ } catch (error) {
67
+ if (error instanceof Error) {
68
+ console.error("Error:", error.message);
69
+ }
70
+ process.exit(1);
71
+ }
72
+ }
73
+
74
+ export async function getOrgInfo(): Promise<void> {
75
+ try {
76
+ requireAuthentication();
77
+ const orgId = requireOrganization();
78
+
79
+ const org = await getOrganization(orgId);
80
+
81
+ console.log("\nšŸ“‹ Organization Details:\n");
82
+ console.log(` ID: ${org.id}`);
83
+ console.log(` Name: ${org.name}`);
84
+ console.log(` Type: ${org.type === "msp" ? "MSP (Managed Services Provider)" : "Direct"}`);
85
+ console.log(` Created: ${new Date(org.createdAt).toLocaleDateString()}`);
86
+ console.log("");
87
+ } catch (error) {
88
+ if (error instanceof Error) {
89
+ console.error("Error:", error.message);
90
+ }
91
+ process.exit(1);
92
+ }
93
+ }
94
+
95
+ export async function createOrg(
96
+ name: string,
97
+ type: "direct" | "msp" = "direct",
98
+ ): Promise<void> {
99
+ try {
100
+ requireAuthentication();
101
+
102
+ console.log(`\nā³ Creating organization "${name}"...`);
103
+
104
+ const org = await apiCreateOrganization(name, type);
105
+
106
+ // Add to organizations list
107
+ addOrganization({ id: org.id, name: org.name });
108
+
109
+ // Set as current org if it's the first one
110
+ const currentOrgId = getCurrentOrg();
111
+ if (!currentOrgId) {
112
+ setCurrentOrg(org.id);
113
+ }
114
+
115
+ console.log(`āœ… Organization created!\n`);
116
+ console.log(` ID: ${org.id}`);
117
+ console.log(` Name: ${org.name}`);
118
+ console.log(` Type: ${org.type}\n`);
119
+
120
+ if (!currentOrgId) {
121
+ console.log(`šŸŽ‰ Set as current organization\n`);
122
+ printContext();
123
+ }
124
+ } catch (error) {
125
+ if (error instanceof Error) {
126
+ console.error("Error:", error.message);
127
+ }
128
+ process.exit(1);
129
+ }
130
+ }
@@ -0,0 +1,124 @@
1
+ import {
2
+ requireAuthentication,
3
+ requireOrganization,
4
+ } from "../config";
5
+ import {
6
+ listTokens,
7
+ createToken,
8
+ revokeToken,
9
+ } from "../api";
10
+
11
+ export async function listTokensCmd(): Promise<void> {
12
+ try {
13
+ requireAuthentication();
14
+ const orgId = requireOrganization();
15
+
16
+ console.log("\nšŸ”‘ API Tokens:\n");
17
+
18
+ const tokens = await listTokens(orgId);
19
+
20
+ if (tokens.length === 0) {
21
+ console.log(" No tokens found. Run `kern0 tokens create` to create one.\n");
22
+ return;
23
+ }
24
+
25
+ console.log(" ID Name Scopes Expires");
26
+ console.log(" " + "─".repeat(95));
27
+
28
+ tokens.forEach(token => {
29
+ const now = Date.now();
30
+ const expiresIn = token.expiresAt - now;
31
+ const daysLeft = Math.ceil(expiresIn / (1000 * 60 * 60 * 24));
32
+
33
+ let expireStatus = `${daysLeft}d`;
34
+ if (expiresIn < 0) {
35
+ expireStatus = "EXPIRED";
36
+ } else if (daysLeft <= 7) {
37
+ expireStatus = `${daysLeft}d āš ļø`;
38
+ }
39
+
40
+ const scopes = token.scopes.join(", ");
41
+ const id = token.id.substring(0, 10);
42
+ const nameDisplay = token.name.substring(0, 26).padEnd(26);
43
+ const scopesDisplay = scopes.substring(0, 28).padEnd(28);
44
+
45
+ console.log(` ${id} ${nameDisplay} ${scopesDisplay} ${expireStatus}`);
46
+ });
47
+
48
+ console.log("");
49
+ } catch (error) {
50
+ if (error instanceof Error) {
51
+ console.error("Error:", error.message);
52
+ }
53
+ process.exit(1);
54
+ }
55
+ }
56
+
57
+ export async function createTokenCmd(
58
+ name: string,
59
+ scopes?: string[],
60
+ customerIds?: string[],
61
+ expiresIn?: number,
62
+ ): Promise<void> {
63
+ try {
64
+ requireAuthentication();
65
+ const orgId = requireOrganization();
66
+
67
+ console.log(`\nā³ Creating token "${name}"...`);
68
+
69
+ const defaultScopes = scopes || ["read:readiness", "read:dependencies"];
70
+ const defaultExpires = expiresIn || 2592000; // 30 days
71
+
72
+ const token = await createToken(
73
+ orgId,
74
+ name,
75
+ defaultScopes,
76
+ customerIds,
77
+ defaultExpires,
78
+ );
79
+
80
+ console.log(`\nāœ… Token created!\n`);
81
+ console.log(` ID: ${token.id}`);
82
+ console.log(` Name: ${token.name}`);
83
+ console.log(` Scopes: ${token.scopes.join(", ")}`);
84
+ console.log(` Expires: ${new Date(token.expiresAt).toLocaleDateString()}`);
85
+ console.log("");
86
+ console.log(" āš ļø Token value (shown only once):");
87
+ console.log(` ${token.token}\n`);
88
+ console.log(" šŸ’¾ Save this token somewhere safe. You won't be able to see it again!\n");
89
+
90
+ // Try to copy to clipboard if available
91
+ try {
92
+ const clipboardy = require("clipboardy");
93
+ if (clipboardy) {
94
+ clipboardy.writeSync(token.token);
95
+ console.log(" šŸ“‹ Token copied to clipboard\n");
96
+ }
97
+ } catch {
98
+ // Clipboard not available, that's ok
99
+ }
100
+ } catch (error) {
101
+ if (error instanceof Error) {
102
+ console.error("Error:", error.message);
103
+ }
104
+ process.exit(1);
105
+ }
106
+ }
107
+
108
+ export async function revokeTokenCmd(tokenId: string): Promise<void> {
109
+ try {
110
+ requireAuthentication();
111
+ const orgId = requireOrganization();
112
+
113
+ console.log(`\nā³ Revoking token ${tokenId.substring(0, 10)}...`);
114
+
115
+ await revokeToken(orgId, tokenId);
116
+
117
+ console.log(`āœ… Token revoked!\n`);
118
+ } catch (error) {
119
+ if (error instanceof Error) {
120
+ console.error("Error:", error.message);
121
+ }
122
+ process.exit(1);
123
+ }
124
+ }