@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 +27 -0
- package/commands/auth.js +197 -0
- package/commands/config.js +165 -0
- package/commands/create.js +474 -0
- package/index.js +530 -0
- package/lib/ai.js +249 -0
- package/lib/auth.js +120 -0
- package/lib/fetch.js +46 -0
- package/lib/scanner.js +351 -0
- package/lib/store.js +83 -0
- package/lib/ui.js +477 -0
- package/package.json +35 -0
package/index.js
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CDX v3.0.0 Enterprise
|
|
6
|
+
* Always checks auth on startup. Gates all features behind login.
|
|
7
|
+
* Dex (animated mascot) greets and guides the user throughout.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { Command } = require("commander");
|
|
11
|
+
const chalk = require("chalk");
|
|
12
|
+
|
|
13
|
+
const UI = require("./lib/ui");
|
|
14
|
+
const {
|
|
15
|
+
T, W, rule, center, printBanner, section, ok, warn, err, info, note,
|
|
16
|
+
spinner, table, badge, panel, ICONS, footer, typewrite,
|
|
17
|
+
dex, dexAnimate, dexGreet,
|
|
18
|
+
} = UI;
|
|
19
|
+
|
|
20
|
+
const store = require("./lib/store");
|
|
21
|
+
const auth = require("./lib/auth");
|
|
22
|
+
|
|
23
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
|
24
|
+
|
|
25
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
// AUTH GATE — runs before any command action
|
|
27
|
+
// Returns the logged-in user, or null if login was cancelled/failed
|
|
28
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
29
|
+
async function requireAuth(inquirer) {
|
|
30
|
+
// 1. Try to restore existing session silently
|
|
31
|
+
const spin = spinner("Checking session…").start();
|
|
32
|
+
await sleep(300);
|
|
33
|
+
const user = await auth.restoreSession().catch(() => null);
|
|
34
|
+
|
|
35
|
+
if (user) {
|
|
36
|
+
spin.ok(`Session active — ${user.displayName || user.email}`);
|
|
37
|
+
await dexGreet(user.displayName || user.email.split("@")[0]);
|
|
38
|
+
return user;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
spin.warn("No active session — authentication required");
|
|
42
|
+
console.log();
|
|
43
|
+
|
|
44
|
+
// 2. Show Dex prompting login
|
|
45
|
+
dex("Hey there! I'm Dex, your documentation assistant. You'll need to sign in before we can get started.", "idle");
|
|
46
|
+
|
|
47
|
+
// 3. Login / signup choice
|
|
48
|
+
const { authChoice } = await inquirer.prompt([{
|
|
49
|
+
type : "list",
|
|
50
|
+
name : "authChoice",
|
|
51
|
+
message: T.brand("What would you like to do?"),
|
|
52
|
+
choices: [
|
|
53
|
+
{ name: ` ${T.brand("◉")} ${T.whiteBold("Sign In")} ${T.dim("I already have an account")}`, value: "login" },
|
|
54
|
+
{ name: ` ${T.success("◉")} ${T.whiteBold("Sign Up")} ${T.dim("Create a new CDX account")}`, value: "signup" },
|
|
55
|
+
{ name: ` ${T.dim("◉")} ${T.dim("Exit")}`, value: "exit" },
|
|
56
|
+
],
|
|
57
|
+
prefix: ` ${T.accent(ICONS.arrow)}`,
|
|
58
|
+
}]);
|
|
59
|
+
|
|
60
|
+
if (authChoice === "exit") {
|
|
61
|
+
await typewrite(`\n ${T.dim("Goodbye! Come back when you're ready.")}`, 18);
|
|
62
|
+
footer();
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const apiKey = store.get("firebaseApiKey"); // always returns bundled key
|
|
67
|
+
|
|
68
|
+
if (authChoice === "login") {
|
|
69
|
+
return await doLogin(inquirer, apiKey);
|
|
70
|
+
} else {
|
|
71
|
+
return await doSignup(inquirer, apiKey);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function doLogin(inquirer, apiKey) {
|
|
76
|
+
section("Sign In", "Firebase Authentication");
|
|
77
|
+
dex("Enter your credentials and I'll get you right in!", "think");
|
|
78
|
+
|
|
79
|
+
let attempts = 0;
|
|
80
|
+
while (attempts < 3) {
|
|
81
|
+
const { email } = await inquirer.prompt([{
|
|
82
|
+
type : "input",
|
|
83
|
+
name : "email",
|
|
84
|
+
message : T.brand("Email address:"),
|
|
85
|
+
prefix : ` ${T.accent(ICONS.arrow)}`,
|
|
86
|
+
validate: v => v.includes("@") || "Please enter a valid email address.",
|
|
87
|
+
}]);
|
|
88
|
+
const { password } = await inquirer.prompt([{
|
|
89
|
+
type : "password",
|
|
90
|
+
name : "password",
|
|
91
|
+
message : T.brand("Password:"),
|
|
92
|
+
mask : "•",
|
|
93
|
+
prefix : ` ${T.accent(ICONS.arrow)}`,
|
|
94
|
+
validate: v => v.length >= 6 || "Password must be at least 6 characters.",
|
|
95
|
+
}]);
|
|
96
|
+
|
|
97
|
+
console.log();
|
|
98
|
+
const anim = dexAnimate("Authenticating with Firebase…", "think");
|
|
99
|
+
await sleep(300);
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const session = await auth.signIn(apiKey, email, password);
|
|
103
|
+
auth.persistSession(session);
|
|
104
|
+
anim.stop("Authentication successful!", "happy");
|
|
105
|
+
console.log();
|
|
106
|
+
await dexGreet(session.displayName || session.email.split("@")[0]);
|
|
107
|
+
return session;
|
|
108
|
+
} catch (e) {
|
|
109
|
+
anim.stop("Authentication failed — " + e.message, "error");
|
|
110
|
+
attempts++;
|
|
111
|
+
if (attempts < 3) {
|
|
112
|
+
dex("No worries! Double-check your credentials and let's try again.", "idle");
|
|
113
|
+
const { retry } = await inquirer.prompt([{
|
|
114
|
+
type : "confirm",
|
|
115
|
+
name : "retry",
|
|
116
|
+
message: T.brand("Try again?"),
|
|
117
|
+
default: true,
|
|
118
|
+
prefix : ` ${T.accent(ICONS.arrow)}`,
|
|
119
|
+
}]);
|
|
120
|
+
if (!retry) break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
err("Could not authenticate. Run cdx auth signup to create a new account.");
|
|
126
|
+
footer();
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function doSignup(inquirer, apiKey) {
|
|
131
|
+
section("Create Account", "Firebase Authentication");
|
|
132
|
+
dex("Awesome! Let me set up your CDX account. This only takes a moment.", "happy");
|
|
133
|
+
|
|
134
|
+
const answers = await inquirer.prompt([
|
|
135
|
+
{
|
|
136
|
+
type : "input",
|
|
137
|
+
name : "displayName",
|
|
138
|
+
message : T.brand("Display name:"),
|
|
139
|
+
prefix : ` ${T.accent(ICONS.arrow)}`,
|
|
140
|
+
validate: v => v.trim().length >= 2 || "Display name must be at least 2 characters.",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
type : "input",
|
|
144
|
+
name : "email",
|
|
145
|
+
message : T.brand("Email address:"),
|
|
146
|
+
prefix : ` ${T.accent(ICONS.arrow)}`,
|
|
147
|
+
validate: v => v.includes("@") || "Please enter a valid email address.",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
type : "password",
|
|
151
|
+
name : "password",
|
|
152
|
+
message : T.brand("Password (min. 8 characters):"),
|
|
153
|
+
mask : "•",
|
|
154
|
+
prefix : ` ${T.accent(ICONS.arrow)}`,
|
|
155
|
+
validate: v => v.length >= 8 || "Password must be at least 8 characters.",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
type : "password",
|
|
159
|
+
name : "confirm",
|
|
160
|
+
message : T.brand("Confirm password:"),
|
|
161
|
+
mask : "•",
|
|
162
|
+
prefix : ` ${T.accent(ICONS.arrow)}`,
|
|
163
|
+
validate: (v, a) => v === a.password || "Passwords do not match.",
|
|
164
|
+
},
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
console.log();
|
|
168
|
+
const anim = dexAnimate("Creating your account…", "think");
|
|
169
|
+
await sleep(400);
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const session = await auth.signUp(apiKey, answers.email, answers.password, answers.displayName);
|
|
173
|
+
auth.persistSession(session);
|
|
174
|
+
anim.stop("Account created successfully!", "happy");
|
|
175
|
+
console.log();
|
|
176
|
+
ok(`Welcome to CDX, ${session.displayName}!`);
|
|
177
|
+
note("Your session is stored securely at ~/.cdx/.session");
|
|
178
|
+
console.log();
|
|
179
|
+
await dexGreet(session.displayName);
|
|
180
|
+
return session;
|
|
181
|
+
} catch (e) {
|
|
182
|
+
anim.stop("Registration failed — " + e.message, "error");
|
|
183
|
+
err(e.message);
|
|
184
|
+
footer();
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
190
|
+
// COMMANDER
|
|
191
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
192
|
+
const program = new Command();
|
|
193
|
+
|
|
194
|
+
program
|
|
195
|
+
.name("cdx")
|
|
196
|
+
.description("CDX Enterprise — SaaS-grade AI documentation generation")
|
|
197
|
+
.version("3.0.0", "-v, --version")
|
|
198
|
+
.addHelpText("beforeAll", () => { printBanner(); return ""; })
|
|
199
|
+
.configureHelp({ helpWidth: W, sortSubcommands: true });
|
|
200
|
+
|
|
201
|
+
// ── create ─────────────────────────────────────────────────────────────────────
|
|
202
|
+
program
|
|
203
|
+
.command("create <filename>")
|
|
204
|
+
.description("Generate comprehensive documentation for the current project")
|
|
205
|
+
.option("-a, --all", "Include hidden files and directories")
|
|
206
|
+
.option("--push", "Push generated docs to GitHub immediately")
|
|
207
|
+
.option("--no-interactive", "Skip post-generation improvement loop")
|
|
208
|
+
.option("--model <model>", "Override Gemini model for this run")
|
|
209
|
+
.action(async (filename, opts) => {
|
|
210
|
+
printBanner();
|
|
211
|
+
const inquirer = (await import("inquirer").catch(() => null))?.default;
|
|
212
|
+
if (!inquirer) { err("'inquirer' not installed."); process.exit(1); }
|
|
213
|
+
|
|
214
|
+
const user = await requireAuth(inquirer);
|
|
215
|
+
|
|
216
|
+
section("Create Documentation", filename);
|
|
217
|
+
dex("Let me scan this project and generate enterprise-grade docs for you!", "think");
|
|
218
|
+
|
|
219
|
+
table([
|
|
220
|
+
["Target file", chalk.cyan(filename)],
|
|
221
|
+
["Signed in as", user.displayName || user.email],
|
|
222
|
+
["Include hidden", opts.all ? badge("YES", "info") : badge("NO", "default")],
|
|
223
|
+
["GitHub push", opts.push ? badge("YES", "success") : badge("deferred", "default")],
|
|
224
|
+
["Interactive", opts.interactive !== false ? badge("YES", "success") : badge("NO", "default")],
|
|
225
|
+
]);
|
|
226
|
+
console.log();
|
|
227
|
+
|
|
228
|
+
if (opts.model) store.set("geminiModel", opts.model);
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
await require("./commands/create")(filename, {
|
|
232
|
+
all: opts.all, push: opts.push,
|
|
233
|
+
noInteractive: opts.interactive === false,
|
|
234
|
+
onProgress: msg => process.stdout.write(`\r ${T.dim(msg)} `),
|
|
235
|
+
});
|
|
236
|
+
} catch (e) {
|
|
237
|
+
dex("Hmm, something went wrong. Check the error below.", "error");
|
|
238
|
+
err("Generation failed: " + e.message);
|
|
239
|
+
if (process.env.CDX_DEBUG) console.error(e.stack);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// ── config ─────────────────────────────────────────────────────────────────────
|
|
245
|
+
program
|
|
246
|
+
.command("config [category]")
|
|
247
|
+
.description("Configure CDX settings — categories: ai, firebase, github, output, show, reset")
|
|
248
|
+
.action(async (category) => {
|
|
249
|
+
printBanner();
|
|
250
|
+
const inquirer = (await import("inquirer").catch(() => null))?.default;
|
|
251
|
+
if (!inquirer) { err("'inquirer' not installed."); process.exit(1); }
|
|
252
|
+
|
|
253
|
+
await requireAuth(inquirer);
|
|
254
|
+
try { await require("./commands/config")(category); }
|
|
255
|
+
catch (e) { err("Config error: " + e.message); process.exit(1); }
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// ── auth (direct sub-commands, no gate — needed to log in) ────────────────────
|
|
259
|
+
program
|
|
260
|
+
.command("auth <action>")
|
|
261
|
+
.description("Firebase auth — actions: login, signup, logout, whoami, reset-password")
|
|
262
|
+
.action(async (action) => {
|
|
263
|
+
printBanner();
|
|
264
|
+
try { await require("./commands/auth")(action); }
|
|
265
|
+
catch (e) { err("Auth error: " + e.message); process.exit(1); }
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// ── start ──────────────────────────────────────────────────────────────────────
|
|
269
|
+
program
|
|
270
|
+
.command("start")
|
|
271
|
+
.description("Launch interactive CDX terminal UI")
|
|
272
|
+
.action(async () => {
|
|
273
|
+
printBanner();
|
|
274
|
+
|
|
275
|
+
const inquirer = (await import("inquirer").catch(() => null))?.default;
|
|
276
|
+
if (!inquirer) { err("'inquirer' not installed."); return; }
|
|
277
|
+
|
|
278
|
+
// ── ALWAYS authenticate first ─────────────────────────────────────────────
|
|
279
|
+
const user = await requireAuth(inquirer);
|
|
280
|
+
|
|
281
|
+
// ── System info ───────────────────────────────────────────────────────────
|
|
282
|
+
const bootSpin = spinner("Initialising CDX Enterprise shell…").start();
|
|
283
|
+
await sleep(350);
|
|
284
|
+
bootSpin.ok("Shell ready");
|
|
285
|
+
console.log();
|
|
286
|
+
|
|
287
|
+
section("System Information");
|
|
288
|
+
const cfg = store.load();
|
|
289
|
+
table([
|
|
290
|
+
["CDX Version", "3.0.0 Enterprise"],
|
|
291
|
+
["Signed In As", user.displayName || user.email],
|
|
292
|
+
["Node.js", process.version],
|
|
293
|
+
["Platform", process.platform + " / " + process.arch],
|
|
294
|
+
["Working Dir", process.cwd().length > 42 ? "…" + process.cwd().slice(-40) : process.cwd()],
|
|
295
|
+
["Gemini Key", cfg.geminiApiKey ? badge("Configured", "success") : badge("Not set — run cdx config ai", "warn")],
|
|
296
|
+
["GitHub Token", cfg.githubToken ? badge("Configured", "success") : badge("Not set", "default")],
|
|
297
|
+
["Firebase", badge("Auto-configured (mkchit)", "info")],
|
|
298
|
+
]);
|
|
299
|
+
|
|
300
|
+
if (!cfg.geminiApiKey) {
|
|
301
|
+
console.log();
|
|
302
|
+
dex("Quick heads up — you haven't configured a Gemini API key yet. Without it, I can only generate basic local docs. Run config → AI Settings to add your key!", "idle");
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ── Main menu loop ─────────────────────────────────────────────────────────
|
|
306
|
+
while (true) {
|
|
307
|
+
const freshCfg = store.load();
|
|
308
|
+
const freshUser = await auth.restoreSession().catch(() => null);
|
|
309
|
+
|
|
310
|
+
// Re-auth if session expired mid-session
|
|
311
|
+
if (!freshUser) {
|
|
312
|
+
warn("Session expired. Please sign in again.");
|
|
313
|
+
const reUser = await requireAuth(inquirer);
|
|
314
|
+
if (!reUser) { process.exit(0); }
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const MENU = [
|
|
319
|
+
{
|
|
320
|
+
name : ` ${T.success("◆")} ${T.whiteBold("Create Docs")} ${T.success("(local)")} ${T.dim("Analyze current directory & generate")}`,
|
|
321
|
+
value: "create-local",
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name : ` ${T.brand("◆")} ${T.whiteBold("Create Docs")} ${T.brand("(GitHub)")} ${T.dim("Remote repo → analyze → push")}` +
|
|
325
|
+
(freshCfg.githubToken ? "" : T.warn(" ⚠ token needed")),
|
|
326
|
+
value: "create-github",
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name : ` ${T.violet("◆")} ${T.whiteBold("Configuration")} ${T.dim("Manage AI, GitHub & output settings")}`,
|
|
330
|
+
value: "config",
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
name : ` ${T.warn("◆")} ${T.whiteBold("Account")} ${T.dim("Session info / sign out ")}` +
|
|
334
|
+
badge("● " + (freshUser.email || "").slice(0, 20), "success"),
|
|
335
|
+
value: "account",
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name : ` ${T.muted("◆")} ${T.muted("Exit")}`,
|
|
339
|
+
value: "exit",
|
|
340
|
+
},
|
|
341
|
+
];
|
|
342
|
+
|
|
343
|
+
console.log();
|
|
344
|
+
console.log(rule("─", T.dim));
|
|
345
|
+
console.log();
|
|
346
|
+
|
|
347
|
+
const { action } = await inquirer.prompt([{
|
|
348
|
+
type : "list",
|
|
349
|
+
name : "action",
|
|
350
|
+
message : T.brand("What would you like to do?"),
|
|
351
|
+
choices : MENU,
|
|
352
|
+
prefix : ` ${T.accent(ICONS.arrow)}`,
|
|
353
|
+
pageSize: 10,
|
|
354
|
+
}]);
|
|
355
|
+
console.log();
|
|
356
|
+
|
|
357
|
+
// ── CREATE LOCAL ──────────────────────────────────────────────────────────
|
|
358
|
+
if (action === "create-local") {
|
|
359
|
+
section("Create Documentation", "Local Project");
|
|
360
|
+
dex("I'll scan your project, detect the stack, and produce detailed docs across multiple passes. Let's go!", "think");
|
|
361
|
+
|
|
362
|
+
const answers = await inquirer.prompt([
|
|
363
|
+
{
|
|
364
|
+
type : "input",
|
|
365
|
+
name : "filename",
|
|
366
|
+
message : T.brand("Output filename:"),
|
|
367
|
+
default : freshCfg.defaultOutput || "README.md",
|
|
368
|
+
prefix : ` ${T.accent(ICONS.arrow)}`,
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
type : "confirm",
|
|
372
|
+
name : "all",
|
|
373
|
+
message : T.brand("Include hidden files/directories?"),
|
|
374
|
+
default : false,
|
|
375
|
+
prefix : ` ${T.accent(ICONS.arrow)}`,
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
type : "confirm",
|
|
379
|
+
name : "push",
|
|
380
|
+
message : T.brand("Push to GitHub after generation?"),
|
|
381
|
+
default : freshCfg.autoPush || false,
|
|
382
|
+
prefix : ` ${T.accent(ICONS.arrow)}`,
|
|
383
|
+
when : () => !!(freshCfg.githubToken && freshCfg.githubRepo),
|
|
384
|
+
},
|
|
385
|
+
]);
|
|
386
|
+
console.log();
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
await require("./commands/create")(answers.filename, {
|
|
390
|
+
all : answers.all,
|
|
391
|
+
push : answers.push,
|
|
392
|
+
onProgress : msg => process.stdout.write(`\r ${T.dim(msg)} `),
|
|
393
|
+
});
|
|
394
|
+
dex("Docs generated and saved! Check the improvement menu that just appeared to refine them further.", "happy");
|
|
395
|
+
} catch (e) {
|
|
396
|
+
dex("Something went wrong during generation. Check the error below.", "error");
|
|
397
|
+
err("Generation failed: " + e.message);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ── CREATE GITHUB ──────────────────────────────────────────────────────────
|
|
401
|
+
} else if (action === "create-github") {
|
|
402
|
+
if (!freshCfg.githubToken) {
|
|
403
|
+
dex("You'll need to add a GitHub token first. Head to Configuration → GitHub Integration.", "idle");
|
|
404
|
+
warn("GitHub token not configured.");
|
|
405
|
+
} else {
|
|
406
|
+
section("Create Documentation", "GitHub Repository");
|
|
407
|
+
|
|
408
|
+
const fetchSpin = spinner("Fetching your GitHub repositories…").start();
|
|
409
|
+
let repos = [];
|
|
410
|
+
try {
|
|
411
|
+
const { fetchGitHubRepos } = require("./lib/scanner");
|
|
412
|
+
repos = await fetchGitHubRepos(freshCfg.githubToken);
|
|
413
|
+
fetchSpin.ok(`Fetched ${repos.length} repositories`);
|
|
414
|
+
} catch (e) {
|
|
415
|
+
fetchSpin.fail("Failed: " + e.message);
|
|
416
|
+
dex("Couldn't reach GitHub. Check your token and internet connection.", "error");
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const repoChoices = [
|
|
421
|
+
...repos.map(r => ({
|
|
422
|
+
name : ` ${r.private ? T.dim("🔒") : T.accent("◆")} ${T.white(r.full_name)} ${T.dim((r.description || "").slice(0, 48))}`,
|
|
423
|
+
value: r.full_name,
|
|
424
|
+
})),
|
|
425
|
+
new inquirer.Separator(),
|
|
426
|
+
{ name: ` ${T.dim("↩ Cancel")}`, value: null },
|
|
427
|
+
];
|
|
428
|
+
|
|
429
|
+
const { selectedRepo } = await inquirer.prompt([{
|
|
430
|
+
type : "list",
|
|
431
|
+
name : "selectedRepo",
|
|
432
|
+
message : T.brand("Select a repository:"),
|
|
433
|
+
choices : repoChoices,
|
|
434
|
+
prefix : ` ${T.accent(ICONS.arrow)}`,
|
|
435
|
+
pageSize: 16,
|
|
436
|
+
}]);
|
|
437
|
+
|
|
438
|
+
if (!selectedRepo) { info("Cancelled."); continue; }
|
|
439
|
+
|
|
440
|
+
const [owner, repo] = selectedRepo.split("/");
|
|
441
|
+
store.set("githubRepo", selectedRepo);
|
|
442
|
+
ok(`Target → ${chalk.cyan(selectedRepo)}`);
|
|
443
|
+
console.log();
|
|
444
|
+
|
|
445
|
+
dex("Great choice! I'll fetch the source tree, analyze every module, and push the docs back automatically.", "think");
|
|
446
|
+
|
|
447
|
+
const { confirmGen } = await inquirer.prompt([{
|
|
448
|
+
type : "confirm",
|
|
449
|
+
name : "confirmGen",
|
|
450
|
+
message : T.brand(`Generate & push docs for ${selectedRepo}?`),
|
|
451
|
+
default : true,
|
|
452
|
+
prefix : ` ${T.accent(ICONS.arrow)}`,
|
|
453
|
+
}]);
|
|
454
|
+
|
|
455
|
+
if (!confirmGen) { info("Cancelled."); continue; }
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
await require("./commands/create")("README.md", {
|
|
459
|
+
remoteGithub: { owner, repo, token: freshCfg.githubToken },
|
|
460
|
+
push : true,
|
|
461
|
+
onProgress : msg => process.stdout.write(`\r ${T.dim(msg)} `),
|
|
462
|
+
});
|
|
463
|
+
} catch (e) {
|
|
464
|
+
dex("Hmm, the remote generation hit an issue.", "error");
|
|
465
|
+
err("Generation failed: " + e.message);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ── CONFIG ─────────────────────────────────────────────────────────────────
|
|
470
|
+
} else if (action === "config") {
|
|
471
|
+
await require("./commands/config")();
|
|
472
|
+
|
|
473
|
+
// ── ACCOUNT ────────────────────────────────────────────────────────────────
|
|
474
|
+
} else if (action === "account") {
|
|
475
|
+
section("Account", freshUser.email);
|
|
476
|
+
|
|
477
|
+
table([
|
|
478
|
+
["Display Name", freshUser.displayName || T.dim("—")],
|
|
479
|
+
["Email", freshUser.email],
|
|
480
|
+
["User ID", freshUser.uid || T.dim("—")],
|
|
481
|
+
["Session", badge("Active", "success")],
|
|
482
|
+
["Firebase", badge("mkchit project", "info")],
|
|
483
|
+
]);
|
|
484
|
+
console.log();
|
|
485
|
+
|
|
486
|
+
const { accountAction } = await inquirer.prompt([{
|
|
487
|
+
type : "list",
|
|
488
|
+
name : "accountAction",
|
|
489
|
+
message: T.brand("Account options:"),
|
|
490
|
+
choices: [
|
|
491
|
+
{ name: ` ${T.warn("◆")} ${T.white("Sign Out")} ${T.dim("Clear session and return to login")}`, value: "logout" },
|
|
492
|
+
{ name: ` ${T.dim("◆")} ${T.dim("Back")}`, value: "back" },
|
|
493
|
+
],
|
|
494
|
+
prefix : ` ${T.accent(ICONS.arrow)}`,
|
|
495
|
+
}]);
|
|
496
|
+
|
|
497
|
+
if (accountAction === "logout") {
|
|
498
|
+
auth.signOut();
|
|
499
|
+
dex("Signed out successfully. Come back soon!", "happy");
|
|
500
|
+
ok("Session cleared.");
|
|
501
|
+
console.log();
|
|
502
|
+
// Force re-auth on next loop iteration
|
|
503
|
+
const reUser = await requireAuth(inquirer);
|
|
504
|
+
if (!reUser) process.exit(0);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// ── EXIT ───────────────────────────────────────────────────────────────────
|
|
508
|
+
} else if (action === "exit") {
|
|
509
|
+
dex("Great work today! Your docs are in safe hands. See you next time!", "happy");
|
|
510
|
+
await sleep(400);
|
|
511
|
+
await typewrite(` ${T.dim("Goodbye, " + (freshUser.displayName || "friend") + "! Happy shipping! 🚀")}`, 18);
|
|
512
|
+
console.log();
|
|
513
|
+
footer();
|
|
514
|
+
process.exit(0);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// ── unknown commands ──────────────────────────────────────────────────────────
|
|
520
|
+
program.on("command:*", cmds => {
|
|
521
|
+
printBanner();
|
|
522
|
+
err(`Unknown command: ${chalk.bold(cmds[0])}`);
|
|
523
|
+
console.log();
|
|
524
|
+
info(`Valid commands: ${["create","config","auth","start"].map(c => T.accent(c)).join(" ")}`);
|
|
525
|
+
note(`Run ${T.accent("cdx --help")} for full usage.`);
|
|
526
|
+
console.log();
|
|
527
|
+
process.exit(1);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
program.parse(process.argv);
|