@sanjay5114/cdx 1.0.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.
package/bin/cdx.js ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+
3
+
4
+ const path = require('path');
5
+
6
+ // Handle unexpected crashes
7
+ process.on('uncaughtException', (err) => {
8
+ console.error('❌ Unexpected Error:', err.message);
9
+ process.exit(1);
10
+ });
11
+
12
+ process.on('unhandledRejection', (err) => {
13
+ console.error('❌ Promise Rejection:', err.message);
14
+ process.exit(1);
15
+ });
16
+
17
+ try {
18
+ // Resolve main CLI logic
19
+ const cliPath = path.resolve(__dirname, '../index.js');
20
+
21
+ // Execute main CLI
22
+ require(cliPath);
23
+
24
+ } catch (error) {
25
+ console.error('❌ Failed to start cdx CLI:', error.message);
26
+ process.exit(1);
27
+ }
@@ -0,0 +1,197 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * CDX Auth Command
5
+ * Handles user registration, sign-in, sign-out, and session display
6
+ * via Firebase Authentication REST API with secure local JWT storage.
7
+ */
8
+
9
+ const { T, section, ok, warn, err, info, note, spinner, table, badge, ICONS, footer } = require("../lib/ui");
10
+ const auth = require("../lib/auth");
11
+ const store = require("../lib/store");
12
+
13
+ module.exports = async function authCommand(action) {
14
+ const inquirer = (await import("inquirer").catch(() => null))?.default;
15
+ if (!inquirer) { err("'inquirer' package is required. Run: npm install inquirer"); return; }
16
+
17
+ const apiKey = store.get("firebaseApiKey");
18
+
19
+ // ── whoami ─────────────────────────────────────────────────────────────────
20
+ if (action === "whoami") {
21
+ const user = await auth.restoreSession().catch(() => null);
22
+ if (!user) {
23
+ warn("No active session. Sign in with: cdx auth login");
24
+ } else {
25
+ section("Active Session");
26
+ table([
27
+ ["Status", badge("Authenticated", "success")],
28
+ ["Display Name", user.displayName || "—"],
29
+ ["Email", user.email],
30
+ ["User ID", user.uid || "—"],
31
+ ["Session", badge("Valid", "success")],
32
+ ]);
33
+ }
34
+ footer();
35
+ return;
36
+ }
37
+
38
+ // ── logout ─────────────────────────────────────────────────────────────────
39
+ if (action === "logout") {
40
+ auth.signOut();
41
+ ok("Successfully signed out. Session cleared from device.");
42
+ footer();
43
+ return;
44
+ }
45
+
46
+ // ── Require Firebase API key for login/signup ──────────────────────────────
47
+ if (!apiKey) {
48
+ warn("Firebase API key is not configured.");
49
+ info("Run cdx config to set your Firebase Web API key first.");
50
+ footer();
51
+ return;
52
+ }
53
+
54
+ // ── login ──────────────────────────────────────────────────────────────────
55
+ if (action === "login") {
56
+ section("Sign In", "Firebase Authentication");
57
+
58
+ // Check for existing valid session
59
+ const existing = await auth.restoreSession().catch(() => null);
60
+ if (existing) {
61
+ ok(`Already signed in as ${existing.email}`);
62
+ note("Run cdx auth logout to sign out.");
63
+ footer();
64
+ return;
65
+ }
66
+
67
+ const { email } = await inquirer.prompt([{
68
+ type : "input",
69
+ name : "email",
70
+ message: T.brand("Email address:"),
71
+ prefix : ` ${T.accent(ICONS.arrow)}`,
72
+ validate: v => v.includes("@") || "Please enter a valid email address.",
73
+ }]);
74
+
75
+ const { password } = await inquirer.prompt([{
76
+ type : "password",
77
+ name : "password",
78
+ message: T.brand("Password:"),
79
+ mask : "•",
80
+ prefix : ` ${T.accent(ICONS.arrow)}`,
81
+ validate: v => v.length >= 6 || "Password must be at least 6 characters.",
82
+ }]);
83
+
84
+ console.log();
85
+ const spin = spinner("Authenticating with Firebase…").start();
86
+
87
+ try {
88
+ const session = await auth.signIn(apiKey, email, password);
89
+ auth.persistSession(session);
90
+ spin.ok(`Welcome back, ${session.displayName}!`);
91
+ console.log();
92
+ table([
93
+ ["Email", session.email],
94
+ ["Display Name", session.displayName],
95
+ ["User ID", session.uid],
96
+ ["Session", badge("Active — stored securely on device", "success")],
97
+ ]);
98
+ } catch (e) {
99
+ spin.fail("Authentication failed");
100
+ err(e.message);
101
+ }
102
+
103
+ footer();
104
+ return;
105
+ }
106
+
107
+ // ── signup ─────────────────────────────────────────────────────────────────
108
+ if (action === "signup") {
109
+ section("Create Account", "Firebase Authentication");
110
+
111
+ const answers = await inquirer.prompt([
112
+ {
113
+ type : "input",
114
+ name : "displayName",
115
+ message: T.brand("Display name:"),
116
+ prefix : ` ${T.accent(ICONS.arrow)}`,
117
+ validate: v => v.trim().length >= 2 || "Display name must be at least 2 characters.",
118
+ },
119
+ {
120
+ type : "input",
121
+ name : "email",
122
+ message: T.brand("Email address:"),
123
+ prefix : ` ${T.accent(ICONS.arrow)}`,
124
+ validate: v => v.includes("@") || "Please enter a valid email address.",
125
+ },
126
+ {
127
+ type : "password",
128
+ name : "password",
129
+ message: T.brand("Password (min. 8 characters):"),
130
+ mask : "•",
131
+ prefix : ` ${T.accent(ICONS.arrow)}`,
132
+ validate: v => v.length >= 8 || "Password must be at least 8 characters.",
133
+ },
134
+ {
135
+ type : "password",
136
+ name : "confirm",
137
+ message: T.brand("Confirm password:"),
138
+ mask : "•",
139
+ prefix : ` ${T.accent(ICONS.arrow)}`,
140
+ validate: (v, a) => v === a.password || "Passwords do not match.",
141
+ },
142
+ ]);
143
+
144
+ console.log();
145
+ const spin = spinner("Creating your account…").start();
146
+
147
+ try {
148
+ const session = await auth.signUp(apiKey, answers.email, answers.password, answers.displayName);
149
+ auth.persistSession(session);
150
+ spin.ok("Account created successfully!");
151
+ console.log();
152
+ table([
153
+ ["Display Name", session.displayName],
154
+ ["Email", session.email],
155
+ ["User ID", session.uid],
156
+ ["Session", badge("Active — JWT stored securely on device", "success")],
157
+ ]);
158
+ note("Your session token is encrypted and stored at ~/.cdx/.session");
159
+ } catch (e) {
160
+ spin.fail("Registration failed");
161
+ err(e.message);
162
+ }
163
+
164
+ footer();
165
+ return;
166
+ }
167
+
168
+ // ── reset-password ─────────────────────────────────────────────────────────
169
+ if (action === "reset-password") {
170
+ section("Reset Password", "Firebase Authentication");
171
+
172
+ const { email } = await inquirer.prompt([{
173
+ type : "input",
174
+ name : "email",
175
+ message: T.brand("Email address:"),
176
+ prefix : ` ${T.accent(ICONS.arrow)}`,
177
+ validate: v => v.includes("@") || "Please enter a valid email address.",
178
+ }]);
179
+
180
+ const spin = spinner("Sending password reset email…").start();
181
+ try {
182
+ await auth.sendPasswordReset(apiKey, email);
183
+ spin.ok("Password reset email sent. Check your inbox.");
184
+ } catch (e) {
185
+ spin.fail("Failed to send reset email");
186
+ err(e.message);
187
+ }
188
+
189
+ footer();
190
+ return;
191
+ }
192
+
193
+ // Unknown sub-action
194
+ warn(`Unknown auth action: ${action}`);
195
+ info("Valid actions: login, signup, logout, whoami, reset-password");
196
+ footer();
197
+ };
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+
3
+ const { T, section, ok, warn, info, note, spinner, table, badge, panel, ICONS, footer } = require("../lib/ui");
4
+ const store = require("../lib/store");
5
+
6
+ const CATEGORIES = [
7
+ { key: "ai", label: "AI & Model Settings", desc: "Gemini API key, model selection", icon: "◈" },
8
+ { key: "firebase", label: "Firebase Authentication", desc: "Web API key for login/signup", icon: "◉" },
9
+ { key: "github", label: "GitHub Integration", desc: "PAT and target repository", icon: "◆" },
10
+ { key: "output", label: "Output & Formatting", desc: "Documentation output preferences", icon: "★" },
11
+ { key: "show", label: "Show Current Config", desc: "Display all settings (secrets masked)", icon: "ℹ" },
12
+ { key: "reset", label: "Reset All Settings", desc: "Clear all config and sessions", icon: "⚠", danger: true },
13
+ ];
14
+
15
+ const AI_MODELS = [
16
+ { name: "gemini-2.5-pro-preview-03-25 (Latest — highest quality)", value: "gemini-2.5-pro-preview-03-25" },
17
+ { name: "gemini-2.0-flash (Fast — good for large repos)", value: "gemini-2.0-flash" },
18
+ { name: "gemini-1.5-pro (Stable Pro)", value: "gemini-1.5-pro" },
19
+ { name: "gemini-1.5-flash (Fast & economical)", value: "gemini-1.5-flash" },
20
+ ];
21
+
22
+ function mask(val) {
23
+ if (!val) return T.dim("not set");
24
+ const s = String(val);
25
+ if (s.length <= 8) return T.warn("•".repeat(s.length));
26
+ return T.muted(s.slice(0, 4)) + T.dim("•".repeat(Math.min(s.length - 8, 20))) + T.muted(s.slice(-4));
27
+ }
28
+
29
+ module.exports = async function configCommand(category) {
30
+ const inquirer = (await import("inquirer").catch(() => null))?.default;
31
+ if (!inquirer) { warn("'inquirer' package is required."); return; }
32
+
33
+ section("Configuration Manager", "CDX Enterprise Settings");
34
+
35
+ let cat = category;
36
+ if (!cat) {
37
+ const choices = CATEGORIES.map(c => ({
38
+ name : ` ${c.danger ? T.warn(c.icon) : T.brand(c.icon)} ${T.white(c.label)} ${T.dim(c.desc)}`,
39
+ value: c.key,
40
+ }));
41
+ const { selected } = await inquirer.prompt([{
42
+ type: "list", name: "selected",
43
+ message: T.brand("Select configuration category:"),
44
+ choices, prefix: ` ${T.accent(ICONS.arrow)}`,
45
+ }]);
46
+ cat = selected;
47
+ }
48
+
49
+ console.log();
50
+
51
+ if (cat === "ai") {
52
+ section("AI & Model Settings");
53
+ const { apiKey } = await inquirer.prompt([{
54
+ type: "password", name: "apiKey",
55
+ message: T.brand("Gemini API key") + T.dim(" (blank to keep current):"),
56
+ mask: "•", prefix: ` ${T.accent(ICONS.arrow)}`,
57
+ }]);
58
+ const { model } = await inquirer.prompt([{
59
+ type: "list", name: "model",
60
+ message: T.brand("Preferred Gemini model:"),
61
+ choices: AI_MODELS, default: store.get("geminiModel") || AI_MODELS[0].value,
62
+ prefix: ` ${T.accent(ICONS.arrow)}`,
63
+ }]);
64
+ const spin = spinner("Saving AI configuration…").start();
65
+ await new Promise(r => setTimeout(r, 300));
66
+ if (apiKey?.trim()) store.set("geminiApiKey", apiKey.trim());
67
+ store.set("geminiModel", model);
68
+ spin.ok("AI configuration saved.");
69
+ note(`Model set to: ${model}`);
70
+ }
71
+
72
+ else if (cat === "firebase") {
73
+ section("Firebase Authentication");
74
+ panel([
75
+ T.muted("Setup instructions:"),
76
+ T.dim("1. Create project at console.firebase.google.com"),
77
+ T.dim("2. Project Settings → General → Web API Key"),
78
+ T.dim("3. Authentication → Sign-in method → Enable Email/Password"),
79
+ ], "Firebase Setup");
80
+ const { fbKey } = await inquirer.prompt([{
81
+ type: "password", name: "fbKey",
82
+ message: T.brand("Firebase Web API key") + T.dim(" (blank to keep):"),
83
+ mask: "•", prefix: ` ${T.accent(ICONS.arrow)}`,
84
+ }]);
85
+ if (fbKey?.trim()) {
86
+ const spin = spinner("Saving Firebase configuration…").start();
87
+ await new Promise(r => setTimeout(r, 300));
88
+ store.set("firebaseApiKey", fbKey.trim());
89
+ spin.ok("Firebase API key saved securely (0o600).");
90
+ } else { info("No changes made."); }
91
+ }
92
+
93
+ else if (cat === "github") {
94
+ section("GitHub Integration");
95
+ panel([
96
+ T.muted("Required token scopes:"),
97
+ T.dim("• repo — read/write repository contents"),
98
+ T.dim("• read:user — read profile"),
99
+ T.dim("Generate at: github.com/settings/tokens"),
100
+ ], "Token Permissions");
101
+ const answers = await inquirer.prompt([
102
+ { type: "password", name: "token", message: T.brand("GitHub PAT") + T.dim(" (blank to keep):"), mask: "•", prefix: ` ${T.accent(ICONS.arrow)}` },
103
+ { type: "input", name: "repo", message: T.brand("Target repo") + T.dim(` (${store.get("githubRepo") || "not set"}) owner/repo:`), prefix: ` ${T.accent(ICONS.arrow)}` },
104
+ ]);
105
+ const spin = spinner("Saving GitHub configuration…").start();
106
+ await new Promise(r => setTimeout(r, 300));
107
+ let updated = false;
108
+ if (answers.token?.trim()) { store.set("githubToken", answers.token.trim()); updated = true; }
109
+ if (answers.repo?.trim()) { store.set("githubRepo", answers.repo.trim()); updated = true; }
110
+ updated ? spin.ok("GitHub configuration saved.") : spin.warn("No changes made.");
111
+ }
112
+
113
+ else if (cat === "output") {
114
+ section("Output & Formatting Preferences");
115
+ const answers = await inquirer.prompt([
116
+ { type: "list", name: "defaultFile", message: T.brand("Default output filename:"), choices: ["README.md","DOCS.md","DOCUMENTATION.md","docs/index.md"], default: store.get("defaultOutput") || "README.md", prefix: ` ${T.accent(ICONS.arrow)}` },
117
+ { type: "confirm", name: "autoPush", message: T.brand("Auto-push to GitHub after generation?"), default: store.get("autoPush") ?? false, prefix: ` ${T.accent(ICONS.arrow)}` },
118
+ { type: "confirm", name: "generateSubDocs", message: T.brand("Generate sub-documents (architecture.md, onboarding.md, etc.)?"), default: store.get("generateSubDocs") ?? true, prefix: ` ${T.accent(ICONS.arrow)}` },
119
+ ]);
120
+ const spin = spinner("Saving preferences…").start();
121
+ await new Promise(r => setTimeout(r, 300));
122
+ store.set("defaultOutput", answers.defaultFile);
123
+ store.set("autoPush", answers.autoPush);
124
+ store.set("generateSubDocs", answers.generateSubDocs);
125
+ spin.ok("Output preferences saved.");
126
+ }
127
+
128
+ else if (cat === "show") {
129
+ section("Current Configuration");
130
+ const cfg = store.load();
131
+ const session = store.loadSession();
132
+ const alive = store.sessionIsAlive(session);
133
+ table([
134
+ ["Gemini API Key", mask(cfg.geminiApiKey)],
135
+ ["Gemini Model", cfg.geminiModel || T.dim("auto")],
136
+ ["Firebase API Key", mask(cfg.firebaseApiKey)],
137
+ ["GitHub Token", mask(cfg.githubToken)],
138
+ ["GitHub Repo", cfg.githubRepo || T.dim("not set")],
139
+ ["Default Output", cfg.defaultOutput || T.dim("README.md")],
140
+ ["Auto Push", cfg.autoPush ? badge("ON", "success") : badge("OFF", "default")],
141
+ ["Sub-documents", cfg.generateSubDocs !== false ? badge("ON", "success") : badge("OFF", "default")],
142
+ ["Auth Session", alive ? badge("Active", "success") : badge("No session", "default")],
143
+ ["Signed In As", cfg.userEmail || T.dim("not signed in")],
144
+ ["Config Path", "~/.cdx/config.json (0o600)"],
145
+ ]);
146
+ }
147
+
148
+ else if (cat === "reset") {
149
+ section("Reset Configuration");
150
+ warn("This will permanently delete all stored credentials and session tokens.");
151
+ const { confirm } = await inquirer.prompt([{
152
+ type: "confirm", name: "confirm",
153
+ message: T.warn("Are you absolutely sure?"),
154
+ default: false, prefix: ` ${T.accent(ICONS.arrow)}`,
155
+ }]);
156
+ if (confirm) {
157
+ const spin = spinner("Clearing all configuration…").start();
158
+ await new Promise(r => setTimeout(r, 500));
159
+ store.clear();
160
+ spin.ok("All settings cleared. CDX has been reset.");
161
+ } else { info("Reset cancelled."); }
162
+ }
163
+
164
+ footer();
165
+ };