@docyrus/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.
package/dist/cli.js ADDED
@@ -0,0 +1,2925 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { RestApiClient, AuthenticationError as AuthenticationError$1 } from '@docyrus/api-client';
4
+ import { createServer } from 'http';
5
+ import { URL, fileURLToPath } from 'url';
6
+ import { randomBytes, createDecipheriv, createCipheriv, scryptSync, createHash } from 'crypto';
7
+ import open from 'open';
8
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync, constants as constants$1 } from 'fs';
9
+ import { join, resolve, dirname } from 'path';
10
+ import { homedir, arch, release, platform, hostname, userInfo } from 'os';
11
+ import Conf from 'conf';
12
+ import chalk4 from 'chalk';
13
+ import ora from 'ora';
14
+ import { select, input, password } from '@inquirer/prompts';
15
+ import { exec } from 'child_process';
16
+ import { promisify } from 'util';
17
+ import { access, constants, writeFile, readFile, mkdir, cp, rm, readdir } from 'fs/promises';
18
+ import { downloadTemplate } from 'giget';
19
+ import { generateFromOpenAPI } from '@docyrus/tanstack-db-generator';
20
+
21
+ // src/config/constants.ts
22
+ var DOCYRUS_API_URL = "https://alpha-api.docyrus.com";
23
+ var OAUTH_CLIENT_ID = "90565525-8283-4881-82a9-8613eb82ae27";
24
+ var OAUTH_SCOPES = "offline_access Read.All Users.Read Users.Read.All DS.Read.All".split(" ");
25
+ var OAUTH_CALLBACK_PORT_MIN = 9876;
26
+ var OAUTH_CALLBACK_PORT_MAX = 9899;
27
+ var OAUTH_REDIRECT_URI = (port) => `http://localhost:${port}/callback`;
28
+ var OAUTH_TIMEOUT_MS = 5 * 60 * 1e3;
29
+ var KEYCHAIN_SERVICE = "docyrus-cli";
30
+ var CONFIG_DIR = ".docyrus";
31
+ var CREDENTIALS_FILE = "credentials.enc";
32
+ var TOKEN_KEYS = {
33
+ ACCESS_TOKEN: "accessToken",
34
+ REFRESH_TOKEN: "refreshToken",
35
+ USER_EMAIL: "userEmail",
36
+ GITHUB_TOKEN: "githubToken"
37
+ // For private template access - persists after logout
38
+ };
39
+ var CLI_NAME = "docyrus";
40
+ var CLI_VERSION = "0.0.1";
41
+ var NPM_PACKAGE_NAME = "docyrus";
42
+
43
+ // src/utils/errors.ts
44
+ var CliError = class extends Error {
45
+ exitCode;
46
+ suggestion;
47
+ constructor(message, exitCode = 1, suggestion) {
48
+ super(message);
49
+ this.name = "CliError";
50
+ this.exitCode = exitCode;
51
+ this.suggestion = suggestion;
52
+ }
53
+ };
54
+ var AuthenticationError = class extends CliError {
55
+ constructor(message = "Authentication failed", suggestion) {
56
+ super(message, 1, suggestion || "Please check your credentials and try again.");
57
+ this.name = "AuthenticationError";
58
+ }
59
+ };
60
+ var NotLoggedInError = class extends CliError {
61
+ constructor(message = "You are not logged in.") {
62
+ super(message, 1, "Run `docyrus login` to authenticate.");
63
+ this.name = "NotLoggedInError";
64
+ }
65
+ };
66
+ var NetworkError = class extends CliError {
67
+ constructor(message = "Network request failed", suggestion) {
68
+ super(message, 1, suggestion || "Please check your internet connection and try again.");
69
+ this.name = "NetworkError";
70
+ }
71
+ };
72
+ var TimeoutError = class extends CliError {
73
+ constructor(message = "Operation timed out") {
74
+ super(message, 1, "Please try again.");
75
+ this.name = "TimeoutError";
76
+ }
77
+ };
78
+ var OAuthError = class extends CliError {
79
+ constructor(message, suggestion) {
80
+ super(message, 1, suggestion || "Please try again or use email/password login.");
81
+ this.name = "OAuthError";
82
+ }
83
+ };
84
+ var TemplateError = class extends CliError {
85
+ constructor(message) {
86
+ super(message, 1, "Check your internet connection and try again.");
87
+ this.name = "TemplateError";
88
+ }
89
+ };
90
+ var ProjectExistsError = class extends CliError {
91
+ constructor(projectName) {
92
+ super(
93
+ `Directory "${projectName}" already exists.`,
94
+ 1,
95
+ "Choose a different project name or delete the existing directory."
96
+ );
97
+ this.name = "ProjectExistsError";
98
+ }
99
+ };
100
+ var ConflictingFlagsError = class extends CliError {
101
+ constructor(flag1, flag2) {
102
+ super(
103
+ `Cannot use ${flag1} and ${flag2} together.`,
104
+ 1,
105
+ "Please use only one option from each group."
106
+ );
107
+ this.name = "ConflictingFlagsError";
108
+ }
109
+ };
110
+
111
+ // src/auth/credential-auth.ts
112
+ function getApiClient() {
113
+ return new RestApiClient({ baseURL: DOCYRUS_API_URL });
114
+ }
115
+ async function loginWithCredentials(credentials) {
116
+ const client = getApiClient();
117
+ try {
118
+ const response = await client.post("/v1/auth/token", {
119
+ email: credentials.email,
120
+ password: credentials.password
121
+ });
122
+ const tokens = {
123
+ accessToken: response.data.access_token,
124
+ refreshToken: response.data.refresh_token,
125
+ expiresIn: response.data.expires_in
126
+ };
127
+ if (tokens.expiresIn) {
128
+ tokens.expiresAt = Date.now() + tokens.expiresIn * 1e3;
129
+ }
130
+ return {
131
+ tokens,
132
+ user: response.data.user
133
+ };
134
+ } catch (error) {
135
+ if (error instanceof AuthenticationError$1) {
136
+ throw new AuthenticationError("Invalid email or password.");
137
+ }
138
+ if (error instanceof Error && error.message.includes("fetch")) {
139
+ throw new NetworkError();
140
+ }
141
+ throw error;
142
+ }
143
+ }
144
+ async function getUserInfo(accessToken) {
145
+ const client = new RestApiClient({ baseURL: DOCYRUS_API_URL });
146
+ await client.setAccessToken(accessToken);
147
+ try {
148
+ const response = await client.get("/v1/users/me");
149
+ return response.data;
150
+ } catch (error) {
151
+ if (error instanceof AuthenticationError$1) {
152
+ throw new AuthenticationError("Session expired. Please log in again.");
153
+ }
154
+ throw error;
155
+ }
156
+ }
157
+ async function refreshAccessToken(refreshToken) {
158
+ const tokenUrl = `${DOCYRUS_API_URL}/v1/oauth2/token`;
159
+ try {
160
+ const response = await fetch(tokenUrl, {
161
+ method: "POST",
162
+ headers: {
163
+ "Content-Type": "application/x-www-form-urlencoded"
164
+ },
165
+ body: new URLSearchParams({
166
+ grant_type: "refresh_token",
167
+ client_id: OAUTH_CLIENT_ID,
168
+ refresh_token: refreshToken
169
+ }).toString()
170
+ });
171
+ if (!response.ok) {
172
+ const errorData = await response.json().catch(() => ({}));
173
+ throw new AuthenticationError(
174
+ errorData.error_description || "Failed to refresh token"
175
+ );
176
+ }
177
+ const data = await response.json();
178
+ const tokens = {
179
+ accessToken: data.access_token,
180
+ refreshToken: data.refresh_token || refreshToken,
181
+ expiresIn: data.expires_in,
182
+ tokenType: data.token_type
183
+ };
184
+ if (tokens.expiresIn) {
185
+ tokens.expiresAt = Date.now() + tokens.expiresIn * 1e3;
186
+ }
187
+ return tokens;
188
+ } catch (error) {
189
+ if (error instanceof AuthenticationError) {
190
+ throw error;
191
+ }
192
+ if (error instanceof Error && error.message.includes("fetch")) {
193
+ throw new NetworkError();
194
+ }
195
+ throw new AuthenticationError("Session expired. Please log in again.");
196
+ }
197
+ }
198
+ var SUCCESS_HTML = `
199
+ <!DOCTYPE html>
200
+ <html>
201
+ <head>
202
+ <title>Authentication Successful - Docyrus CLI</title>
203
+ <style>
204
+ * { box-sizing: border-box; }
205
+ body {
206
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
207
+ display: flex;
208
+ justify-content: center;
209
+ align-items: center;
210
+ min-height: 100vh;
211
+ margin: 0;
212
+ background: linear-gradient(to bottom right, #f8fafc, #f1f5f9);
213
+ }
214
+ .container {
215
+ width: 100%;
216
+ max-width: 400px;
217
+ padding: 32px;
218
+ background: white;
219
+ border-radius: 16px;
220
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.15);
221
+ text-align: center;
222
+ }
223
+ .logo {
224
+ margin-bottom: 24px;
225
+ }
226
+ .logo svg {
227
+ height: 40px;
228
+ width: auto;
229
+ }
230
+ h1 {
231
+ margin: 0 0 8px;
232
+ font-size: 20px;
233
+ font-weight: 600;
234
+ color: #1e293b;
235
+ }
236
+ p {
237
+ margin: 0;
238
+ color: #64748b;
239
+ font-size: 14px;
240
+ }
241
+ </style>
242
+ </head>
243
+ <body>
244
+ <div class="container">
245
+ <div class="logo">
246
+ <svg viewBox="0 0 1325.62 253.55" xmlns="http://www.w3.org/2000/svg">
247
+ <path fill="#dc2626" d="M169.13,133.22l-.07.06v29.48c0,7.81-6.35,14.17-14.16,14.17h-29.48c-2.54,0-4.9-.68-6.96-1.85l-33.13,33.14c2.16,3.21,3.43,7.09,3.43,11.21,0,5.53-2.22,10.54-5.84,14.17-3.62,3.62-8.64,5.84-14.17,5.84s-10.55-2.22-14.17-5.84c-3.62-3.63-5.84-8.64-5.84-14.17,0-11.06,8.96-20.08,20.07-20.08,4.22,0,8.09,1.29,11.3,3.48l33.11-33.11c-1.22-2.07-1.91-4.46-1.91-7.02v-29.48c0-3.24,1.11-6.26,2.97-8.65l-27.31-37.16H19.96V15.5h71.85v65.82l28.44,38.71c1.62-.65,3.39-.98,5.23-.98h29.48c7.81,0,14.17,6.36,14.17,14.17Z"/>
248
+ <path fill="#1c1c1c" d="M241.24,60.57h48.58c12.19,0,23,2.42,32.44,7.25,9.44,4.83,16.77,11.61,21.98,20.34,5.21,8.73,7.82,18.75,7.82,30.05s-2.61,21.32-7.82,30.05c-5.22,8.73-12.54,15.51-21.98,20.34-9.44,4.83-20.25,7.25-32.44,7.25h-48.58V60.57ZM288.83,161.51c9.33,0,17.54-1.81,24.62-5.43,7.08-3.62,12.54-8.7,16.38-15.23,3.84-6.53,5.76-14.08,5.76-22.64s-1.92-16.11-5.76-22.64c-3.84-6.53-9.3-11.61-16.38-15.23-7.08-3.62-15.29-5.43-24.62-5.43h-31.12v86.61h31.12Z"/>
249
+ <path fill="#1c1c1c" d="M430.77,169.5c-9.33-5.1-16.66-12.16-21.98-21.16-5.33-9-7.99-19.04-7.99-30.13s2.66-21.13,7.99-30.13c5.32-9,12.65-16.06,21.98-21.16,9.33-5.1,19.81-7.66,31.45-7.66s21.96,2.55,31.29,7.66c9.33,5.1,16.63,12.13,21.9,21.08,5.27,8.95,7.9,19.02,7.9,30.22s-2.63,21.27-7.9,30.22c-5.27,8.95-12.57,15.97-21.9,21.08-9.33,5.1-19.76,7.66-31.29,7.66s-22.12-2.55-31.45-7.66ZM485.03,156.74c6.75-3.84,12.07-9.14,15.97-15.89,3.9-6.75,5.85-14.3,5.85-22.64s-1.95-15.89-5.85-22.64c-3.9-6.75-9.22-12.05-15.97-15.89-6.75-3.84-14.35-5.76-22.81-5.76s-16.11,1.92-22.97,5.76c-6.86,3.84-12.24,9.14-16.14,15.89-3.9,6.75-5.85,14.3-5.85,22.64s1.95,15.89,5.85,22.64c3.9,6.75,9.28,12.05,16.14,15.89,6.86,3.84,14.52,5.76,22.97,5.76s16.06-1.92,22.81-5.76Z"/>
250
+ <path fill="#1c1c1c" d="M601.78,169.5c-9.28-5.1-16.55-12.13-21.82-21.08-5.27-8.95-7.9-19.02-7.9-30.22s2.63-21.27,7.9-30.22c5.27-8.95,12.57-15.97,21.9-21.08,9.33-5.1,19.76-7.66,31.29-7.66,9,0,17.23,1.51,24.7,4.53,7.46,3.02,13.83,7.49,19.1,13.42l-10.7,10.37c-8.67-9.11-19.49-13.67-32.44-13.67-8.56,0-16.3,1.92-23.22,5.76-6.92,3.84-12.32,9.14-16.22,15.89-3.9,6.75-5.85,14.3-5.85,22.64s1.95,15.89,5.85,22.64c3.9,6.75,9.3,12.05,16.22,15.89,6.92,3.84,14.66,5.76,23.22,5.76,12.84,0,23.66-4.61,32.44-13.83l10.7,10.37c-5.27,5.93-11.67,10.43-19.18,13.5-7.52,3.07-15.78,4.61-24.78,4.61-11.53,0-21.93-2.55-31.2-7.66Z"/>
251
+ <path fill="#1c1c1c" d="M775.59,135.99v39.85h-16.3v-40.18l-45.78-75.09h17.62l36.89,60.76,37.05-60.76h16.3l-45.78,75.42Z"/>
252
+ <path fill="#1c1c1c" d="M946.01,175.84l-24.87-35.4c-3.07.22-5.49.33-7.25.33h-28.49v35.07h-16.47V60.57h44.95c14.93,0,26.68,3.57,35.24,10.7,8.56,7.14,12.84,16.96,12.84,29.48,0,8.89-2.2,16.47-6.59,22.72-4.39,6.26-10.65,10.81-18.77,13.67l27.33,38.7h-17.95ZM937.29,120.02c5.49-4.5,8.23-10.92,8.23-19.27s-2.75-14.74-8.23-19.18c-5.49-4.45-13.45-6.67-23.88-6.67h-27.99v51.87h27.99c10.43,0,18.39-2.25,23.88-6.75Z"/>
253
+ <path fill="#1c1c1c" d="M1033.45,163.98c-8.56-8.78-12.84-21.41-12.84-37.87V60.57h16.47v64.88c0,24.7,10.81,37.05,32.44,37.05,10.54,0,18.61-3.05,24.21-9.14s8.4-15.4,8.4-27.91V60.57h15.97v65.54c0,16.58-4.28,29.23-12.84,37.96-8.56,8.73-20.53,13.09-35.9,13.09s-27.33-4.39-35.9-13.17Z"/>
254
+ <path fill="#1c1c1c" d="M1193.26,173.12c-8.07-2.69-14.41-6.18-19.02-10.46l6.09-12.84c4.39,3.95,10.02,7.16,16.88,9.63,6.86,2.47,13.91,3.7,21.16,3.7,9.55,0,16.69-1.62,21.41-4.86,4.72-3.24,7.08-7.55,7.08-12.93,0-3.95-1.29-7.16-3.87-9.63-2.58-2.47-5.76-4.36-9.55-5.68s-9.14-2.8-16.05-4.45c-8.67-2.08-15.67-4.17-21-6.26-5.33-2.08-9.88-5.29-13.67-9.63-3.79-4.34-5.68-10.18-5.68-17.54,0-6.15,1.62-11.69,4.86-16.63,3.24-4.94,8.15-8.89,14.74-11.86,6.59-2.96,14.76-4.45,24.54-4.45,6.81,0,13.5.88,20.09,2.63,6.59,1.76,12.24,4.28,16.96,7.57l-5.43,13.17c-4.83-3.07-9.99-5.41-15.48-7-5.49-1.59-10.87-2.39-16.14-2.39-9.33,0-16.33,1.7-21,5.1-4.67,3.4-7,7.8-7,13.17,0,3.95,1.32,7.16,3.95,9.63,2.63,2.47,5.9,4.39,9.8,5.76,3.9,1.37,9.19,2.83,15.89,4.36,8.67,2.09,15.64,4.17,20.91,6.26,5.27,2.09,9.8,5.27,13.58,9.55,3.79,4.28,5.68,10.04,5.68,17.29,0,6.04-1.65,11.55-4.94,16.55-3.29,5-8.29,8.95-14.99,11.86-6.7,2.91-14.93,4.36-24.7,4.36-8.67,0-17.04-1.34-25.11-4.03Z"/>
255
+ </svg>
256
+ </div>
257
+ <h1>Authentication Successful</h1>
258
+ <p>You can close this tab and return to the CLI.</p>
259
+ </div>
260
+ </body>
261
+ </html>
262
+ `;
263
+ var ERROR_HTML = (message) => `
264
+ <!DOCTYPE html>
265
+ <html>
266
+ <head>
267
+ <title>Authentication Failed - Docyrus CLI</title>
268
+ <style>
269
+ * { box-sizing: border-box; }
270
+ body {
271
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
272
+ display: flex;
273
+ justify-content: center;
274
+ align-items: center;
275
+ min-height: 100vh;
276
+ margin: 0;
277
+ background: linear-gradient(to bottom right, #f8fafc, #f1f5f9);
278
+ }
279
+ .container {
280
+ width: 100%;
281
+ max-width: 400px;
282
+ padding: 32px;
283
+ background: white;
284
+ border-radius: 16px;
285
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.15);
286
+ text-align: center;
287
+ }
288
+ .logo {
289
+ margin-bottom: 24px;
290
+ }
291
+ .logo svg {
292
+ height: 40px;
293
+ width: auto;
294
+ }
295
+ h1 {
296
+ margin: 0 0 8px;
297
+ font-size: 20px;
298
+ font-weight: 600;
299
+ color: #1e293b;
300
+ }
301
+ p {
302
+ margin: 0;
303
+ color: #64748b;
304
+ font-size: 14px;
305
+ }
306
+ .error-message {
307
+ margin-top: 16px;
308
+ padding: 12px;
309
+ background: #fef2f2;
310
+ border-radius: 8px;
311
+ color: #dc2626;
312
+ font-size: 13px;
313
+ }
314
+ </style>
315
+ </head>
316
+ <body>
317
+ <div class="container">
318
+ <div class="logo">
319
+ <svg viewBox="0 0 1325.62 253.55" xmlns="http://www.w3.org/2000/svg">
320
+ <path fill="#dc2626" d="M169.13,133.22l-.07.06v29.48c0,7.81-6.35,14.17-14.16,14.17h-29.48c-2.54,0-4.9-.68-6.96-1.85l-33.13,33.14c2.16,3.21,3.43,7.09,3.43,11.21,0,5.53-2.22,10.54-5.84,14.17-3.62,3.62-8.64,5.84-14.17,5.84s-10.55-2.22-14.17-5.84c-3.62-3.63-5.84-8.64-5.84-14.17,0-11.06,8.96-20.08,20.07-20.08,4.22,0,8.09,1.29,11.3,3.48l33.11-33.11c-1.22-2.07-1.91-4.46-1.91-7.02v-29.48c0-3.24,1.11-6.26,2.97-8.65l-27.31-37.16H19.96V15.5h71.85v65.82l28.44,38.71c1.62-.65,3.39-.98,5.23-.98h29.48c7.81,0,14.17,6.36,14.17,14.17Z"/>
321
+ <path fill="#1c1c1c" d="M241.24,60.57h48.58c12.19,0,23,2.42,32.44,7.25,9.44,4.83,16.77,11.61,21.98,20.34,5.21,8.73,7.82,18.75,7.82,30.05s-2.61,21.32-7.82,30.05c-5.22,8.73-12.54,15.51-21.98,20.34-9.44,4.83-20.25,7.25-32.44,7.25h-48.58V60.57ZM288.83,161.51c9.33,0,17.54-1.81,24.62-5.43,7.08-3.62,12.54-8.7,16.38-15.23,3.84-6.53,5.76-14.08,5.76-22.64s-1.92-16.11-5.76-22.64c-3.84-6.53-9.3-11.61-16.38-15.23-7.08-3.62-15.29-5.43-24.62-5.43h-31.12v86.61h31.12Z"/>
322
+ <path fill="#1c1c1c" d="M430.77,169.5c-9.33-5.1-16.66-12.16-21.98-21.16-5.33-9-7.99-19.04-7.99-30.13s2.66-21.13,7.99-30.13c5.32-9,12.65-16.06,21.98-21.16,9.33-5.1,19.81-7.66,31.45-7.66s21.96,2.55,31.29,7.66c9.33,5.1,16.63,12.13,21.9,21.08,5.27,8.95,7.9,19.02,7.9,30.22s-2.63,21.27-7.9,30.22c-5.27,8.95-12.57,15.97-21.9,21.08-9.33,5.1-19.76,7.66-31.29,7.66s-22.12-2.55-31.45-7.66ZM485.03,156.74c6.75-3.84,12.07-9.14,15.97-15.89,3.9-6.75,5.85-14.3,5.85-22.64s-1.95-15.89-5.85-22.64c-3.9-6.75-9.22-12.05-15.97-15.89-6.75-3.84-14.35-5.76-22.81-5.76s-16.11,1.92-22.97,5.76c-6.86,3.84-12.24,9.14-16.14,15.89-3.9,6.75-5.85,14.3-5.85,22.64s1.95,15.89,5.85,22.64c3.9,6.75,9.28,12.05,16.14,15.89,6.86,3.84,14.52,5.76,22.97,5.76s16.06-1.92,22.81-5.76Z"/>
323
+ <path fill="#1c1c1c" d="M601.78,169.5c-9.28-5.1-16.55-12.13-21.82-21.08-5.27-8.95-7.9-19.02-7.9-30.22s2.63-21.27,7.9-30.22c5.27-8.95,12.57-15.97,21.9-21.08,9.33-5.1,19.76-7.66,31.29-7.66,9,0,17.23,1.51,24.7,4.53,7.46,3.02,13.83,7.49,19.1,13.42l-10.7,10.37c-8.67-9.11-19.49-13.67-32.44-13.67-8.56,0-16.3,1.92-23.22,5.76-6.92,3.84-12.32,9.14-16.22,15.89-3.9,6.75-5.85,14.3-5.85,22.64s1.95,15.89,5.85,22.64c3.9,6.75,9.3,12.05,16.22,15.89,6.92,3.84,14.66,5.76,23.22,5.76,12.84,0,23.66-4.61,32.44-13.83l10.7,10.37c-5.27,5.93-11.67,10.43-19.18,13.5-7.52,3.07-15.78,4.61-24.78,4.61-11.53,0-21.93-2.55-31.2-7.66Z"/>
324
+ <path fill="#1c1c1c" d="M775.59,135.99v39.85h-16.3v-40.18l-45.78-75.09h17.62l36.89,60.76,37.05-60.76h16.3l-45.78,75.42Z"/>
325
+ <path fill="#1c1c1c" d="M946.01,175.84l-24.87-35.4c-3.07.22-5.49.33-7.25.33h-28.49v35.07h-16.47V60.57h44.95c14.93,0,26.68,3.57,35.24,10.7,8.56,7.14,12.84,16.96,12.84,29.48,0,8.89-2.2,16.47-6.59,22.72-4.39,6.26-10.65,10.81-18.77,13.67l27.33,38.7h-17.95ZM937.29,120.02c5.49-4.5,8.23-10.92,8.23-19.27s-2.75-14.74-8.23-19.18c-5.49-4.45-13.45-6.67-23.88-6.67h-27.99v51.87h27.99c10.43,0,18.39-2.25,23.88-6.75Z"/>
326
+ <path fill="#1c1c1c" d="M1033.45,163.98c-8.56-8.78-12.84-21.41-12.84-37.87V60.57h16.47v64.88c0,24.7,10.81,37.05,32.44,37.05,10.54,0,18.61-3.05,24.21-9.14s8.4-15.4,8.4-27.91V60.57h15.97v65.54c0,16.58-4.28,29.23-12.84,37.96-8.56,8.73-20.53,13.09-35.9,13.09s-27.33-4.39-35.9-13.17Z"/>
327
+ <path fill="#1c1c1c" d="M1193.26,173.12c-8.07-2.69-14.41-6.18-19.02-10.46l6.09-12.84c4.39,3.95,10.02,7.16,16.88,9.63,6.86,2.47,13.91,3.7,21.16,3.7,9.55,0,16.69-1.62,21.41-4.86,4.72-3.24,7.08-7.55,7.08-12.93,0-3.95-1.29-7.16-3.87-9.63-2.58-2.47-5.76-4.36-9.55-5.68s-9.14-2.8-16.05-4.45c-8.67-2.08-15.67-4.17-21-6.26-5.33-2.08-9.88-5.29-13.67-9.63-3.79-4.34-5.68-10.18-5.68-17.54,0-6.15,1.62-11.69,4.86-16.63,3.24-4.94,8.15-8.89,14.74-11.86,6.59-2.96,14.76-4.45,24.54-4.45,6.81,0,13.5.88,20.09,2.63,6.59,1.76,12.24,4.28,16.96,7.57l-5.43,13.17c-4.83-3.07-9.99-5.41-15.48-7-5.49-1.59-10.87-2.39-16.14-2.39-9.33,0-16.33,1.7-21,5.1-4.67,3.4-7,7.8-7,13.17,0,3.95,1.32,7.16,3.95,9.63,2.63,2.47,5.9,4.39,9.8,5.76,3.9,1.37,9.19,2.83,15.89,4.36,8.67,2.09,15.64,4.17,20.91,6.26,5.27,2.09,9.8,5.27,13.58,9.55,3.79,4.28,5.68,10.04,5.68,17.29,0,6.04-1.65,11.55-4.94,16.55-3.29,5-8.29,8.95-14.99,11.86-6.7,2.91-14.93,4.36-24.7,4.36-8.67,0-17.04-1.34-25.11-4.03Z"/>
328
+ </svg>
329
+ </div>
330
+ <h1>Authentication Failed</h1>
331
+ <p>Something went wrong during authentication.</p>
332
+ <div class="error-message">${message}</div>
333
+ </div>
334
+ </body>
335
+ </html>
336
+ `;
337
+ function generateState() {
338
+ return randomBytes(32).toString("hex");
339
+ }
340
+ function generateCodeVerifier() {
341
+ return randomBytes(32).toString("base64url");
342
+ }
343
+ async function generateCodeChallenge(verifier) {
344
+ const encoder = new TextEncoder();
345
+ const data = encoder.encode(verifier);
346
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
347
+ return Buffer.from(hashBuffer).toString("base64url");
348
+ }
349
+ async function findAvailablePort() {
350
+ return new Promise((resolve3, reject) => {
351
+ const tryPort = (port) => {
352
+ if (port > OAUTH_CALLBACK_PORT_MAX) {
353
+ reject(new OAuthError("Could not find an available port for OAuth callback."));
354
+ return;
355
+ }
356
+ const server = createServer();
357
+ server.listen(port, "127.0.0.1");
358
+ server.once("listening", () => {
359
+ server.close(() => resolve3(port));
360
+ });
361
+ server.once("error", () => {
362
+ tryPort(port + 1);
363
+ });
364
+ };
365
+ tryPort(OAUTH_CALLBACK_PORT_MIN);
366
+ });
367
+ }
368
+ async function startCallbackServer(expectedState) {
369
+ const port = await findAvailablePort();
370
+ let resolveResult;
371
+ let rejectResult;
372
+ const resultPromise = new Promise((resolve3, reject) => {
373
+ resolveResult = resolve3;
374
+ rejectResult = reject;
375
+ });
376
+ const timeoutId = setTimeout(() => {
377
+ rejectResult(new TimeoutError("OAuth authentication timed out."));
378
+ }, OAUTH_TIMEOUT_MS);
379
+ const server = createServer((req, res) => {
380
+ const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
381
+ if (url.pathname === "/callback") {
382
+ const code = url.searchParams.get("code");
383
+ const state2 = url.searchParams.get("state");
384
+ const error = url.searchParams.get("error");
385
+ const errorDescription = url.searchParams.get("error_description");
386
+ if (error) {
387
+ res.writeHead(400, { "Content-Type": "text/html" });
388
+ res.end(ERROR_HTML(errorDescription || error));
389
+ rejectResult(new OAuthError(errorDescription || error));
390
+ return;
391
+ }
392
+ if (!code || !state2) {
393
+ res.writeHead(400, { "Content-Type": "text/html" });
394
+ res.end(ERROR_HTML("Missing authorization code or state."));
395
+ rejectResult(new OAuthError("Missing authorization code or state."));
396
+ return;
397
+ }
398
+ if (state2 !== expectedState) {
399
+ res.writeHead(400, { "Content-Type": "text/html" });
400
+ res.end(ERROR_HTML("Invalid state parameter. Possible CSRF attack."));
401
+ rejectResult(new OAuthError("Invalid state parameter."));
402
+ return;
403
+ }
404
+ res.writeHead(200, { "Content-Type": "text/html" });
405
+ res.end(SUCCESS_HTML);
406
+ resolveResult({ code, state: state2 });
407
+ } else {
408
+ res.writeHead(404);
409
+ res.end("Not Found");
410
+ }
411
+ });
412
+ server.listen(port, "127.0.0.1");
413
+ const cleanup = () => {
414
+ clearTimeout(timeoutId);
415
+ server.close();
416
+ };
417
+ return {
418
+ server,
419
+ port,
420
+ resultPromise,
421
+ cleanup
422
+ };
423
+ }
424
+ async function openBrowser(url) {
425
+ await open(url);
426
+ }
427
+
428
+ // src/auth/oauth-auth.ts
429
+ function getOAuthConfig(port) {
430
+ return {
431
+ authorizationUrl: `${DOCYRUS_API_URL}/v1/oauth2/authorize`,
432
+ tokenUrl: `${DOCYRUS_API_URL}/v1/oauth2/token`,
433
+ clientId: OAUTH_CLIENT_ID,
434
+ redirectUri: OAUTH_REDIRECT_URI(port),
435
+ scopes: OAUTH_SCOPES
436
+ };
437
+ }
438
+ function buildAuthorizationUrl(config, state2, codeChallenge) {
439
+ const params = new URLSearchParams({
440
+ response_type: "code",
441
+ client_id: config.clientId,
442
+ redirect_uri: config.redirectUri,
443
+ scope: config.scopes.join(" "),
444
+ state: state2,
445
+ code_challenge: codeChallenge,
446
+ code_challenge_method: "S256"
447
+ });
448
+ return `${config.authorizationUrl}?${params.toString()}`;
449
+ }
450
+ async function exchangeCodeForTokens(config, code, codeVerifier) {
451
+ const response = await fetch(config.tokenUrl, {
452
+ method: "POST",
453
+ headers: {
454
+ "Content-Type": "application/x-www-form-urlencoded"
455
+ },
456
+ body: new URLSearchParams({
457
+ grant_type: "authorization_code",
458
+ client_id: config.clientId,
459
+ code,
460
+ redirect_uri: config.redirectUri,
461
+ code_verifier: codeVerifier
462
+ }).toString()
463
+ });
464
+ if (!response.ok) {
465
+ const errorText = await response.text();
466
+ throw new OAuthError(`Failed to exchange authorization code: ${errorText}`);
467
+ }
468
+ const data = await response.json();
469
+ const tokens = {
470
+ accessToken: data.access_token,
471
+ refreshToken: data.refresh_token,
472
+ expiresIn: data.expires_in,
473
+ tokenType: data.token_type
474
+ };
475
+ if (tokens.expiresIn) {
476
+ tokens.expiresAt = Date.now() + tokens.expiresIn * 1e3;
477
+ }
478
+ return tokens;
479
+ }
480
+ async function getUserInfoFromToken(accessToken) {
481
+ const client = new RestApiClient({ baseURL: DOCYRUS_API_URL });
482
+ await client.setAccessToken(accessToken);
483
+ try {
484
+ return await client.get("/v1/users/me");
485
+ } catch {
486
+ return void 0;
487
+ }
488
+ }
489
+ async function loginWithOAuth(options = {}) {
490
+ const state2 = generateState();
491
+ const codeVerifier = generateCodeVerifier();
492
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
493
+ const { port, resultPromise, cleanup } = await startCallbackServer(state2);
494
+ const config = getOAuthConfig(port);
495
+ const authUrl = buildAuthorizationUrl(config, state2, codeChallenge);
496
+ try {
497
+ options.onOpeningBrowser?.();
498
+ await openBrowser(authUrl);
499
+ options.onWaitingForAuth?.(authUrl);
500
+ const { code } = await resultPromise;
501
+ const tokens = await exchangeCodeForTokens(config, code, codeVerifier);
502
+ const user = await getUserInfoFromToken(tokens.accessToken);
503
+ return { tokens, user };
504
+ } finally {
505
+ cleanup();
506
+ }
507
+ }
508
+ function getHomeDir() {
509
+ return homedir();
510
+ }
511
+ function getConfigDir() {
512
+ return join(getHomeDir(), CONFIG_DIR);
513
+ }
514
+ var ALGORITHM = "aes-256-gcm";
515
+ var KEY_LENGTH = 32;
516
+ var IV_LENGTH = 16;
517
+ var AUTH_TAG_LENGTH = 16;
518
+ var SALT_LENGTH = 32;
519
+ function getMachineId() {
520
+ const parts = [
521
+ hostname(),
522
+ userInfo().username,
523
+ process.platform,
524
+ process.arch
525
+ ];
526
+ return createHash("sha256").update(parts.join("-")).digest("hex");
527
+ }
528
+ function deriveKey(salt) {
529
+ const machineId = getMachineId();
530
+ return scryptSync(machineId, salt, KEY_LENGTH);
531
+ }
532
+ function encrypt(data) {
533
+ const salt = randomBytes(SALT_LENGTH);
534
+ const key = deriveKey(salt);
535
+ const iv = randomBytes(IV_LENGTH);
536
+ const cipher = createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
537
+ const encrypted = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
538
+ const authTag = cipher.getAuthTag();
539
+ return [
540
+ salt.toString("base64"),
541
+ iv.toString("base64"),
542
+ authTag.toString("base64"),
543
+ encrypted.toString("base64")
544
+ ].join(":");
545
+ }
546
+ function decrypt(encryptedData) {
547
+ const parts = encryptedData.split(":");
548
+ if (parts.length !== 4) {
549
+ throw new Error("Invalid encrypted data format");
550
+ }
551
+ const [
552
+ saltB64,
553
+ ivB64,
554
+ authTagB64,
555
+ dataB64
556
+ ] = parts;
557
+ const salt = Buffer.from(saltB64, "base64");
558
+ const iv = Buffer.from(ivB64, "base64");
559
+ const authTag = Buffer.from(authTagB64, "base64");
560
+ const encrypted = Buffer.from(dataB64, "base64");
561
+ const key = deriveKey(salt);
562
+ const decipher = createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
563
+ decipher.setAuthTag(authTag);
564
+ const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
565
+ return decrypted.toString("utf8");
566
+ }
567
+
568
+ // src/storage/file-storage.ts
569
+ var FileStorage = class {
570
+ filePath;
571
+ constructor() {
572
+ const configDir = getConfigDir();
573
+ this.filePath = join(configDir, CREDENTIALS_FILE);
574
+ }
575
+ ensureConfigDir() {
576
+ const configDir = getConfigDir();
577
+ if (!existsSync(configDir)) {
578
+ mkdirSync(configDir, { recursive: true, mode: 448 });
579
+ }
580
+ }
581
+ readData() {
582
+ if (!existsSync(this.filePath)) {
583
+ return {};
584
+ }
585
+ try {
586
+ const encrypted = readFileSync(this.filePath, "utf8");
587
+ const decrypted = decrypt(encrypted);
588
+ return JSON.parse(decrypted);
589
+ } catch {
590
+ return {};
591
+ }
592
+ }
593
+ writeData(data) {
594
+ this.ensureConfigDir();
595
+ const json = JSON.stringify(data);
596
+ const encrypted = encrypt(json);
597
+ writeFileSync(this.filePath, encrypted, { mode: 384 });
598
+ }
599
+ async get(key) {
600
+ const data = this.readData();
601
+ return data[key] ?? null;
602
+ }
603
+ async set(key, value) {
604
+ const data = this.readData();
605
+ data[key] = value;
606
+ this.writeData(data);
607
+ }
608
+ async delete(key) {
609
+ const data = this.readData();
610
+ delete data[key];
611
+ if (Object.keys(data).length === 0) {
612
+ this.clear();
613
+ } else {
614
+ this.writeData(data);
615
+ }
616
+ }
617
+ async clear() {
618
+ if (existsSync(this.filePath)) {
619
+ unlinkSync(this.filePath);
620
+ }
621
+ }
622
+ async has(key) {
623
+ const data = this.readData();
624
+ return key in data;
625
+ }
626
+ };
627
+
628
+ // src/storage/keychain-storage.ts
629
+ var keytar = null;
630
+ async function getKeytar() {
631
+ if (keytar !== null) {
632
+ return keytar;
633
+ }
634
+ try {
635
+ keytar = await import('keytar');
636
+ return keytar;
637
+ } catch {
638
+ keytar = null;
639
+ return null;
640
+ }
641
+ }
642
+ var KeychainStorage = class {
643
+ service;
644
+ constructor(service = KEYCHAIN_SERVICE) {
645
+ this.service = service;
646
+ }
647
+ async isAvailable() {
648
+ const kt = await getKeytar();
649
+ if (!kt) return false;
650
+ try {
651
+ await kt.findCredentials(this.service);
652
+ return true;
653
+ } catch {
654
+ return false;
655
+ }
656
+ }
657
+ async get(key) {
658
+ const kt = await getKeytar();
659
+ if (!kt) return null;
660
+ try {
661
+ return await kt.getPassword(this.service, key);
662
+ } catch {
663
+ return null;
664
+ }
665
+ }
666
+ async set(key, value) {
667
+ const kt = await getKeytar();
668
+ if (!kt) return false;
669
+ try {
670
+ await kt.setPassword(this.service, key, value);
671
+ return true;
672
+ } catch {
673
+ return false;
674
+ }
675
+ }
676
+ async delete(key) {
677
+ const kt = await getKeytar();
678
+ if (!kt) return false;
679
+ try {
680
+ return await kt.deletePassword(this.service, key);
681
+ } catch {
682
+ return false;
683
+ }
684
+ }
685
+ async clear() {
686
+ const kt = await getKeytar();
687
+ if (!kt) return;
688
+ try {
689
+ const credentials = await kt.findCredentials(this.service);
690
+ for (const cred of credentials) {
691
+ await kt.deletePassword(this.service, cred.account);
692
+ }
693
+ } catch {
694
+ }
695
+ }
696
+ };
697
+
698
+ // src/storage/cli-token-manager.ts
699
+ var CliTokenManager = class {
700
+ keychain;
701
+ fileStorage;
702
+ keychainAvailable = null;
703
+ constructor() {
704
+ this.keychain = new KeychainStorage();
705
+ this.fileStorage = new FileStorage();
706
+ }
707
+ async useKeychain() {
708
+ if (this.keychainAvailable === null) {
709
+ this.keychainAvailable = await this.keychain.isAvailable();
710
+ }
711
+ return this.keychainAvailable;
712
+ }
713
+ async getToken() {
714
+ if (await this.useKeychain()) {
715
+ const token = await this.keychain.get(TOKEN_KEYS.ACCESS_TOKEN);
716
+ if (token) return token;
717
+ }
718
+ return await this.fileStorage.get(TOKEN_KEYS.ACCESS_TOKEN);
719
+ }
720
+ async setToken(token) {
721
+ if (await this.useKeychain()) {
722
+ const success = await this.keychain.set(TOKEN_KEYS.ACCESS_TOKEN, token);
723
+ if (success) return;
724
+ }
725
+ await this.fileStorage.set(TOKEN_KEYS.ACCESS_TOKEN, token);
726
+ }
727
+ async clearToken() {
728
+ await this.keychain.delete(TOKEN_KEYS.ACCESS_TOKEN);
729
+ await this.fileStorage.delete(TOKEN_KEYS.ACCESS_TOKEN);
730
+ }
731
+ async getRefreshToken() {
732
+ if (await this.useKeychain()) {
733
+ const token = await this.keychain.get(TOKEN_KEYS.REFRESH_TOKEN);
734
+ if (token) return token;
735
+ }
736
+ return await this.fileStorage.get(TOKEN_KEYS.REFRESH_TOKEN);
737
+ }
738
+ async setRefreshToken(token) {
739
+ if (await this.useKeychain()) {
740
+ const success = await this.keychain.set(TOKEN_KEYS.REFRESH_TOKEN, token);
741
+ if (success) return;
742
+ }
743
+ await this.fileStorage.set(TOKEN_KEYS.REFRESH_TOKEN, token);
744
+ }
745
+ async clearRefreshToken() {
746
+ await this.keychain.delete(TOKEN_KEYS.REFRESH_TOKEN);
747
+ await this.fileStorage.delete(TOKEN_KEYS.REFRESH_TOKEN);
748
+ }
749
+ async getUserEmail() {
750
+ if (await this.useKeychain()) {
751
+ const email = await this.keychain.get(TOKEN_KEYS.USER_EMAIL);
752
+ if (email) return email;
753
+ }
754
+ return await this.fileStorage.get(TOKEN_KEYS.USER_EMAIL);
755
+ }
756
+ async setUserEmail(email) {
757
+ if (await this.useKeychain()) {
758
+ const success = await this.keychain.set(TOKEN_KEYS.USER_EMAIL, email);
759
+ if (success) return;
760
+ }
761
+ await this.fileStorage.set(TOKEN_KEYS.USER_EMAIL, email);
762
+ }
763
+ // GitHub token for private template access (persists after logout)
764
+ async getGithubToken() {
765
+ if (await this.useKeychain()) {
766
+ const token = await this.keychain.get(TOKEN_KEYS.GITHUB_TOKEN);
767
+ if (token) return token;
768
+ }
769
+ return await this.fileStorage.get(TOKEN_KEYS.GITHUB_TOKEN);
770
+ }
771
+ async setGithubToken(token) {
772
+ if (await this.useKeychain()) {
773
+ const success = await this.keychain.set(TOKEN_KEYS.GITHUB_TOKEN, token);
774
+ if (success) return;
775
+ }
776
+ await this.fileStorage.set(TOKEN_KEYS.GITHUB_TOKEN, token);
777
+ }
778
+ async clearAll() {
779
+ const githubToken = await this.getGithubToken();
780
+ await this.keychain.clear();
781
+ await this.fileStorage.clear();
782
+ if (githubToken) {
783
+ await this.setGithubToken(githubToken);
784
+ }
785
+ }
786
+ async isLoggedIn() {
787
+ const token = await this.getToken();
788
+ return token !== null;
789
+ }
790
+ };
791
+ var instance = null;
792
+ function getTokenManager() {
793
+ if (!instance) {
794
+ instance = new CliTokenManager();
795
+ }
796
+ return instance;
797
+ }
798
+ var defaults = {
799
+ telemetryEnabled: true
800
+ };
801
+ var ConfigManager = class {
802
+ config;
803
+ constructor() {
804
+ this.config = new Conf({
805
+ projectName: CLI_NAME,
806
+ defaults
807
+ });
808
+ }
809
+ get(key) {
810
+ return this.config.get(key);
811
+ }
812
+ set(key, value) {
813
+ this.config.set(key, value);
814
+ }
815
+ delete(key) {
816
+ this.config.delete(key);
817
+ }
818
+ getAll() {
819
+ return this.config.store;
820
+ }
821
+ clear() {
822
+ this.config.clear();
823
+ }
824
+ get path() {
825
+ return this.config.path;
826
+ }
827
+ };
828
+ var configManager = new ConfigManager();
829
+ var logger = {
830
+ success(message) {
831
+ console.info(chalk4.green("\u2713"), message);
832
+ },
833
+ error(message) {
834
+ console.error(chalk4.red("\u2717"), message);
835
+ },
836
+ warn(message) {
837
+ console.warn(chalk4.yellow("\u26A0"), message);
838
+ },
839
+ info(message) {
840
+ console.info(chalk4.blue("\u2139"), message);
841
+ },
842
+ debug(message) {
843
+ if (process.env.DEBUG) {
844
+ console.debug(chalk4.gray("\u22EF"), chalk4.gray(message));
845
+ }
846
+ },
847
+ log(message) {
848
+ console.info(message);
849
+ },
850
+ newline() {
851
+ console.info();
852
+ },
853
+ dim(message) {
854
+ console.info(chalk4.dim(message));
855
+ },
856
+ bold(message) {
857
+ console.info(chalk4.bold(message));
858
+ },
859
+ link(url) {
860
+ return chalk4.cyan.underline(url);
861
+ }
862
+ };
863
+ var state = {
864
+ format: "text",
865
+ data: {}
866
+ };
867
+ var output = {
868
+ setFormat(format) {
869
+ state.format = format;
870
+ },
871
+ getFormat() {
872
+ return state.format;
873
+ },
874
+ isJson() {
875
+ return state.format === "json";
876
+ },
877
+ // Accumulate data for JSON output
878
+ set(key, value) {
879
+ state.data[key] = value;
880
+ },
881
+ // Clear accumulated data
882
+ clear() {
883
+ state.data = {};
884
+ },
885
+ // Print success message or accumulate for JSON
886
+ success(message, data) {
887
+ if (state.format === "json") {
888
+ state.data = {
889
+ ...state.data,
890
+ ...data,
891
+ success: true,
892
+ message
893
+ };
894
+ } else {
895
+ console.info(chalk4.green("\u2713"), message);
896
+ }
897
+ },
898
+ // Print error message or accumulate for JSON
899
+ error(message, data) {
900
+ if (state.format === "json") {
901
+ state.data = {
902
+ ...state.data,
903
+ ...data,
904
+ success: false,
905
+ error: message
906
+ };
907
+ } else {
908
+ console.error(chalk4.red("\u2717"), message);
909
+ }
910
+ },
911
+ // Print warning message
912
+ warn(message) {
913
+ if (state.format === "json") {
914
+ state.data.warning = message;
915
+ } else {
916
+ console.warn(chalk4.yellow("\u26A0"), message);
917
+ }
918
+ },
919
+ // Print info message (only in text mode)
920
+ info(message) {
921
+ if (state.format !== "json") {
922
+ console.info(chalk4.blue("\u2139"), message);
923
+ }
924
+ },
925
+ // Print dim message (only in text mode)
926
+ dim(message) {
927
+ if (state.format !== "json") {
928
+ console.info(chalk4.dim(message));
929
+ }
930
+ },
931
+ // Print the final JSON output
932
+ flush() {
933
+ if (state.format === "json" && Object.keys(state.data).length > 0) {
934
+ console.info(JSON.stringify(state.data, null, 2));
935
+ state.data = {};
936
+ }
937
+ },
938
+ // Direct JSON output (for commands that return structured data)
939
+ json(data) {
940
+ if (state.format === "json") {
941
+ console.info(JSON.stringify(data, null, 2));
942
+ }
943
+ }
944
+ };
945
+
946
+ // src/ui/messages.ts
947
+ var MESSAGES = {
948
+ // Login
949
+ LOGIN_SUCCESS: (email) => `Successfully logged in as ${email}`,
950
+ LOGIN_FAILED: "Login failed. Please check your credentials.",
951
+ LOGIN_PROMPT_EMAIL: "Email:",
952
+ LOGIN_PROMPT_PASSWORD: "Password:",
953
+ LOGIN_AUTHENTICATING: "Authenticating...",
954
+ // SSO
955
+ SSO_OPENING_BROWSER: "Opening browser for authentication...",
956
+ SSO_WAITING: "Waiting for browser authentication...",
957
+ SSO_SUCCESS: "Browser authentication successful!",
958
+ SSO_TIMEOUT: "Authentication timed out. Please try again.",
959
+ SSO_CANCELLED: "Authentication cancelled.",
960
+ // Logout
961
+ LOGOUT_SUCCESS: "Successfully logged out.",
962
+ LOGOUT_CONFIRM: "Are you sure you want to log out?",
963
+ LOGOUT_IN_PROGRESS: "Logging out...",
964
+ // Whoami
965
+ WHOAMI_NOT_LOGGED_IN: "You are not logged in.",
966
+ WHOAMI_SUGGESTION: "Run `docyrus login` to authenticate.",
967
+ WHOAMI_FETCHING: "Fetching user info...",
968
+ // General
969
+ NOT_LOGGED_IN: "You are not logged in.",
970
+ NETWORK_ERROR: "Network request failed. Please check your internet connection.",
971
+ UNKNOWN_ERROR: "An unexpected error occurred.",
972
+ // Errors
973
+ INVALID_CREDENTIALS: "Invalid email or password.",
974
+ SESSION_EXPIRED: "Your session has expired. Please log in again.",
975
+ SERVER_ERROR: "Server error. Please try again later.",
976
+ // Create command
977
+ CREATE_SELECT_FRAMEWORK: "Select a framework:",
978
+ CREATE_SELECT_UI_LIBRARY: "Select UI library:",
979
+ CREATE_SELECT_LINTER: "Select linter/formatter:",
980
+ CREATE_PROJECT_NAME: "Project name:",
981
+ CREATE_SELECT_PACKAGE_MANAGER: "Select package manager:",
982
+ CREATE_CUSTOMIZE_ALIAS: "Customize import alias? (default: @/)",
983
+ CREATE_CUSTOM_ALIAS: "Enter import alias prefix (must start with @):",
984
+ CREATE_SETTING_UP: "Creating project...",
985
+ CREATE_DOWNLOADING: "Downloading project template...",
986
+ CREATE_COPYING_LOCAL: "Copying local template...",
987
+ CREATE_CONFIGURING_LINTER: (linter) => `Configuring ${linter}...`,
988
+ CREATE_CONFIGURING_ALIAS: (alias) => `Configuring import alias (${alias}/)...`,
989
+ CREATE_INSTALLING: "Installing dependencies...",
990
+ CREATE_SUCCESS: "Project created successfully!",
991
+ CREATE_DOWNLOAD_OPENAPI: "Download OpenAPI spec for code generation?",
992
+ CREATE_DOWNLOADING_OPENAPI: "Downloading OpenAPI specification...",
993
+ CREATE_OPENAPI_SUCCESS: (path) => `OpenAPI spec saved to ${path}`,
994
+ // Linter Setup
995
+ LINTER_SETUP_TITLE: "Setting up Linter...",
996
+ // State Management
997
+ CREATE_SELECT_STATE_MANAGEMENT: "Select state management:",
998
+ STATE_MANAGEMENT_SETUP_TITLE: "Setting up State Management...",
999
+ // OpenAPI Setup
1000
+ OPENAPI_SETUP_TITLE: "Setting up API...",
1001
+ // API Client
1002
+ API_CLIENT_DOCS: "API Client: https://www.npmjs.com/package/@docyrus/api-client",
1003
+ // UI Library Setup
1004
+ UI_SETUP_TITLE: "Setting up UI Library...",
1005
+ UI_MULTIPLE_LIBRARIES: "Cannot use multiple UI libraries together",
1006
+ // Docyrus Token (for private template access)
1007
+ DOCYRUS_TOKEN_REQUIRED: "Docyrus token required for template download",
1008
+ DOCYRUS_TOKEN_PROMPT: "Enter Docyrus token:",
1009
+ DOCYRUS_TOKEN_SAVED: "Docyrus token saved successfully",
1010
+ DOCYRUS_TOKEN_INVALID: "Invalid Docyrus token. Please check and try again."
1011
+ };
1012
+ function createSpinner(text) {
1013
+ return ora({
1014
+ text,
1015
+ spinner: "dots"
1016
+ });
1017
+ }
1018
+ async function withSpinner(text, fn, options) {
1019
+ if (options?.silent) {
1020
+ return fn();
1021
+ }
1022
+ const spinner = createSpinner(text).start();
1023
+ try {
1024
+ const result = await fn();
1025
+ if (options?.successText) {
1026
+ spinner.succeed(options.successText);
1027
+ } else {
1028
+ spinner.stop();
1029
+ }
1030
+ return result;
1031
+ } catch (error) {
1032
+ if (options?.failText) {
1033
+ spinner.fail(options.failText);
1034
+ } else {
1035
+ spinner.stop();
1036
+ }
1037
+ throw error;
1038
+ }
1039
+ }
1040
+ async function promptEmail(defaultValue) {
1041
+ return input({
1042
+ message: MESSAGES.LOGIN_PROMPT_EMAIL,
1043
+ default: defaultValue,
1044
+ validate: (value) => {
1045
+ if (!value || !value.includes("@")) {
1046
+ return "Please enter a valid email address.";
1047
+ }
1048
+ return true;
1049
+ }
1050
+ });
1051
+ }
1052
+ async function promptPassword() {
1053
+ return password({
1054
+ message: MESSAGES.LOGIN_PROMPT_PASSWORD,
1055
+ mask: "*",
1056
+ validate: (value) => {
1057
+ if (!value || value.length < 1) {
1058
+ return "Please enter your password.";
1059
+ }
1060
+ return true;
1061
+ }
1062
+ });
1063
+ }
1064
+ ({
1065
+ pending: chalk4.dim("\u25CB"),
1066
+ in_progress: chalk4.cyan("\u25D0"),
1067
+ completed: chalk4.green("\u2713"),
1068
+ failed: chalk4.red("\u2717")
1069
+ });
1070
+ function createSimpleProgress() {
1071
+ let spinner = null;
1072
+ return (step, current, total) => {
1073
+ if (spinner) {
1074
+ spinner.succeed();
1075
+ spinner = null;
1076
+ }
1077
+ const text = current && total ? `${step} (${current}/${total})` : step;
1078
+ spinner = ora({
1079
+ text,
1080
+ color: "cyan"
1081
+ }).start();
1082
+ if (current && total && current === total) {
1083
+ spinner.succeed();
1084
+ spinner = null;
1085
+ }
1086
+ };
1087
+ }
1088
+
1089
+ // src/commands/login.ts
1090
+ async function handleCredentialLogin(email) {
1091
+ const lastEmail = configManager.get("lastLoginEmail");
1092
+ const userEmail = email || await promptEmail(lastEmail);
1093
+ const password4 = await promptPassword();
1094
+ const result = await withSpinner(
1095
+ MESSAGES.LOGIN_AUTHENTICATING,
1096
+ () => loginWithCredentials({ email: userEmail, password: password4 })
1097
+ );
1098
+ const tokenManager = getTokenManager();
1099
+ await tokenManager.setToken(result.tokens.accessToken);
1100
+ if (result.tokens.refreshToken) {
1101
+ await tokenManager.setRefreshToken(result.tokens.refreshToken);
1102
+ }
1103
+ const finalEmail = result.user?.email || userEmail;
1104
+ await tokenManager.setUserEmail(finalEmail);
1105
+ configManager.set("lastLoginEmail", finalEmail);
1106
+ output.success(MESSAGES.LOGIN_SUCCESS(finalEmail), { email: finalEmail });
1107
+ }
1108
+ async function handleSsoLogin() {
1109
+ const result = await loginWithOAuth({
1110
+ onOpeningBrowser: () => {
1111
+ logger.info(MESSAGES.SSO_OPENING_BROWSER);
1112
+ },
1113
+ onWaitingForAuth: (url) => {
1114
+ logger.dim(` ${url}`);
1115
+ logger.info(MESSAGES.SSO_WAITING);
1116
+ }
1117
+ });
1118
+ const tokenManager = getTokenManager();
1119
+ await tokenManager.setToken(result.tokens.accessToken);
1120
+ if (result.tokens.refreshToken) {
1121
+ await tokenManager.setRefreshToken(result.tokens.refreshToken);
1122
+ }
1123
+ let email = result.user?.email;
1124
+ if (!email && result.tokens.accessToken) {
1125
+ try {
1126
+ const userInfo2 = await getUserInfo(result.tokens.accessToken);
1127
+ ({ email } = userInfo2);
1128
+ } catch {
1129
+ }
1130
+ }
1131
+ if (email) {
1132
+ await tokenManager.setUserEmail(email);
1133
+ configManager.set("lastLoginEmail", email);
1134
+ output.success(MESSAGES.LOGIN_SUCCESS(email), { email });
1135
+ } else {
1136
+ output.success("Successfully logged in.");
1137
+ }
1138
+ }
1139
+ function registerLoginCommand(program2) {
1140
+ program2.command("login").description("Log in to Docyrus (uses browser-based SSO by default)").option("-e, --email [email]", "Log in with email/password instead of SSO").action(async (options) => {
1141
+ try {
1142
+ const tokenManager = getTokenManager();
1143
+ if (await tokenManager.isLoggedIn()) {
1144
+ const email = await tokenManager.getUserEmail();
1145
+ output.warn(`Already logged in${email ? ` as ${email}` : ""}. Run \`docyrus logout\` first to log in as a different user.`);
1146
+ return;
1147
+ }
1148
+ if (options.email !== void 0) {
1149
+ const emailValue = typeof options.email === "string" ? options.email : void 0;
1150
+ await handleCredentialLogin(emailValue);
1151
+ } else {
1152
+ await handleSsoLogin();
1153
+ }
1154
+ } catch (error) {
1155
+ if (error instanceof AuthenticationError) {
1156
+ output.error(error.message);
1157
+ if (error.suggestion && !output.isJson()) {
1158
+ logger.dim(error.suggestion);
1159
+ }
1160
+ process.exit(error.exitCode);
1161
+ }
1162
+ throw error;
1163
+ }
1164
+ });
1165
+ }
1166
+
1167
+ // src/commands/logout.ts
1168
+ function registerLogoutCommand(program2) {
1169
+ program2.command("logout").description("Log out from Docyrus").action(async () => {
1170
+ const tokenManager = getTokenManager();
1171
+ if (!await tokenManager.isLoggedIn()) {
1172
+ output.warn(MESSAGES.WHOAMI_NOT_LOGGED_IN);
1173
+ return;
1174
+ }
1175
+ const email = await tokenManager.getUserEmail();
1176
+ await withSpinner(
1177
+ MESSAGES.LOGOUT_IN_PROGRESS,
1178
+ async () => {
1179
+ await tokenManager.clearAll();
1180
+ },
1181
+ { silent: output.isJson() }
1182
+ );
1183
+ output.success(MESSAGES.LOGOUT_SUCCESS, email ? { email } : void 0);
1184
+ if (email && !output.isJson()) {
1185
+ logger.dim(`Logged out from ${email}`);
1186
+ }
1187
+ });
1188
+ }
1189
+
1190
+ // src/utils/auth-guard.ts
1191
+ function decodeJwtPayload(token) {
1192
+ try {
1193
+ const parts = token.split(".");
1194
+ if (parts.length !== 3) return null;
1195
+ const payload = Buffer.from(parts[1], "base64url").toString("utf8");
1196
+ return JSON.parse(payload);
1197
+ } catch {
1198
+ return null;
1199
+ }
1200
+ }
1201
+ function isTokenExpired(token) {
1202
+ const payload = decodeJwtPayload(token);
1203
+ if (!payload?.exp) return false;
1204
+ const expirationTime = payload.exp * 1e3;
1205
+ const now = Date.now();
1206
+ return now >= expirationTime - 3e4;
1207
+ }
1208
+ async function requireAuth() {
1209
+ const tokenManager = getTokenManager();
1210
+ if (!await tokenManager.isLoggedIn()) {
1211
+ throw new NotLoggedInError();
1212
+ }
1213
+ let token = await tokenManager.getToken();
1214
+ if (!token) {
1215
+ throw new NotLoggedInError();
1216
+ }
1217
+ if (isTokenExpired(token)) {
1218
+ const refreshToken = await tokenManager.getRefreshToken();
1219
+ if (!refreshToken) {
1220
+ await tokenManager.clearAll();
1221
+ throw new NotLoggedInError("Your session has expired. Please log in again.");
1222
+ }
1223
+ try {
1224
+ const newTokens = await refreshAccessToken(refreshToken);
1225
+ await tokenManager.setToken(newTokens.accessToken);
1226
+ if (newTokens.refreshToken) {
1227
+ await tokenManager.setRefreshToken(newTokens.refreshToken);
1228
+ }
1229
+ token = newTokens.accessToken;
1230
+ } catch (error) {
1231
+ await tokenManager.clearAll();
1232
+ if (error instanceof AuthenticationError) {
1233
+ throw new NotLoggedInError("Your session has expired. Please log in again.");
1234
+ }
1235
+ throw error;
1236
+ }
1237
+ }
1238
+ return token;
1239
+ }
1240
+
1241
+ // src/commands/whoami.ts
1242
+ function registerWhoamiCommand(program2) {
1243
+ program2.command("whoami").description("Display the current logged-in user").action(async () => {
1244
+ const tokenManager = getTokenManager();
1245
+ const token = await requireAuth();
1246
+ try {
1247
+ const user = await withSpinner(
1248
+ MESSAGES.WHOAMI_FETCHING,
1249
+ () => getUserInfo(token),
1250
+ { silent: output.isJson() }
1251
+ );
1252
+ if (output.isJson()) {
1253
+ output.set("user", {
1254
+ email: user.email,
1255
+ firstname: user.firstname,
1256
+ lastname: user.lastname
1257
+ });
1258
+ } else {
1259
+ logger.newline();
1260
+ logger.log(`Email: ${user.email}`);
1261
+ if (user.firstname || user.lastname) {
1262
+ const fullName = [user.firstname, user.lastname].filter(Boolean).join(" ");
1263
+ logger.log(`Name: ${fullName}`);
1264
+ }
1265
+ logger.newline();
1266
+ }
1267
+ } catch (error) {
1268
+ const cachedEmail = await tokenManager.getUserEmail();
1269
+ if (cachedEmail) {
1270
+ if (output.isJson()) {
1271
+ output.set("user", { email: cachedEmail, cached: true });
1272
+ } else {
1273
+ logger.newline();
1274
+ logger.log(`Email: ${cachedEmail}`);
1275
+ logger.dim("(Unable to fetch full profile from server)");
1276
+ logger.newline();
1277
+ }
1278
+ } else {
1279
+ throw error;
1280
+ }
1281
+ }
1282
+ });
1283
+ }
1284
+ var ESLINT_CONFIGS = {
1285
+ react: {
1286
+ imports: ["baseConfig", "reactConfig"],
1287
+ configs: ["...baseConfig", "...reactConfig"]
1288
+ },
1289
+ nextjs: {
1290
+ imports: [
1291
+ "baseConfig",
1292
+ "reactConfig",
1293
+ "nextjsConfig"
1294
+ ],
1295
+ configs: [
1296
+ "...baseConfig",
1297
+ "...reactConfig",
1298
+ "...nextjsConfig"
1299
+ ]
1300
+ },
1301
+ vue: {
1302
+ imports: ["baseConfig", "vueConfig"],
1303
+ configs: ["...baseConfig", "...vueConfig"]
1304
+ },
1305
+ electron: {
1306
+ imports: ["baseConfig", "electronConfig"],
1307
+ configs: ["...baseConfig", "...electronConfig"]
1308
+ }
1309
+ };
1310
+ function generateEslintConfig(framework) {
1311
+ const { imports, configs } = ESLINT_CONFIGS[framework];
1312
+ return `import { ${imports.join(", ")} } from '@docyrus/rules/eslint';
1313
+
1314
+ export default [${configs.join(", ")}];
1315
+ `;
1316
+ }
1317
+ function generateBiomeConfig(framework) {
1318
+ return `${JSON.stringify({ extends: [`@docyrus/rules/biome/${framework}`] }, null, 2)}
1319
+ `;
1320
+ }
1321
+ async function applyLinterConfig(targetDir, framework, linter, onProgress) {
1322
+ if (linter === "none") return;
1323
+ const linterName = linter === "eslint" ? "ESLint" : "Biome";
1324
+ onProgress(`Generating ${linterName} config...`, 1, 2);
1325
+ if (linter === "eslint") {
1326
+ await writeFile(join(targetDir, "eslint.config.mjs"), generateEslintConfig(framework));
1327
+ } else {
1328
+ await writeFile(join(targetDir, "biome.json"), generateBiomeConfig(framework));
1329
+ }
1330
+ onProgress("Updating package.json...", 2, 2);
1331
+ const targetPkgPath = join(targetDir, "package.json");
1332
+ const targetPkg = JSON.parse(await readFile(targetPkgPath, "utf-8"));
1333
+ let devDependencies;
1334
+ let scripts;
1335
+ if (linter === "eslint") {
1336
+ devDependencies = { "@docyrus/rules": "latest", eslint: "latest" };
1337
+ scripts = { lint: "eslint .", format: "eslint --fix ." };
1338
+ } else {
1339
+ devDependencies = { "@docyrus/rules": "latest", "@biomejs/biome": "latest" };
1340
+ scripts = { lint: "biome check .", format: "biome check --fix ." };
1341
+ }
1342
+ targetPkg.devDependencies = {
1343
+ ...targetPkg.devDependencies,
1344
+ ...devDependencies
1345
+ };
1346
+ targetPkg.scripts = {
1347
+ ...targetPkg.scripts,
1348
+ ...scripts
1349
+ };
1350
+ await writeFile(targetPkgPath, JSON.stringify(targetPkg, null, 2));
1351
+ }
1352
+ async function applyAliasConfig(targetDir, framework, aliasPrefix) {
1353
+ if (aliasPrefix === "@" || aliasPrefix === "@/") return;
1354
+ const config = {
1355
+ prefix: aliasPrefix,
1356
+ pattern: `${aliasPrefix}/*`,
1357
+ replacement: "./src/*"
1358
+ };
1359
+ await updateTsConfig(targetDir, config);
1360
+ if (framework === "react" || framework === "vue" || framework === "electron") {
1361
+ await updateViteConfig(targetDir, config);
1362
+ }
1363
+ await updateImports(targetDir, aliasPrefix, framework);
1364
+ }
1365
+ async function updateTsConfig(targetDir, config) {
1366
+ const tsconfigPath = join(targetDir, "tsconfig.json");
1367
+ const tsconfig = JSON.parse(await readFile(tsconfigPath, "utf-8"));
1368
+ if (tsconfig.compilerOptions?.paths) {
1369
+ delete tsconfig.compilerOptions.paths["@/*"];
1370
+ tsconfig.compilerOptions.paths[config.pattern] = [config.replacement];
1371
+ }
1372
+ await writeFile(tsconfigPath, JSON.stringify(tsconfig, null, 2));
1373
+ }
1374
+ async function updateViteConfig(targetDir, config) {
1375
+ const vitePath = join(targetDir, "vite.config.ts");
1376
+ try {
1377
+ let content = await readFile(vitePath, "utf-8");
1378
+ content = content.replace(
1379
+ /'@':\s*resolve\(/g,
1380
+ `'${config.prefix}': resolve(`
1381
+ );
1382
+ await writeFile(vitePath, content);
1383
+ } catch {
1384
+ }
1385
+ }
1386
+ async function updateImports(targetDir, newPrefix, framework) {
1387
+ const srcDir = join(targetDir, "src");
1388
+ try {
1389
+ await updateImportsRecursive(srcDir, newPrefix, framework);
1390
+ } catch {
1391
+ }
1392
+ }
1393
+ function getFileExtensions(framework) {
1394
+ switch (framework) {
1395
+ case "vue":
1396
+ return [
1397
+ ".ts",
1398
+ ".tsx",
1399
+ ".vue"
1400
+ ];
1401
+ default:
1402
+ return [".ts", ".tsx"];
1403
+ }
1404
+ }
1405
+ async function updateImportsRecursive(dir, newPrefix, framework) {
1406
+ const entries = await readdir(dir, { withFileTypes: true });
1407
+ const extensions = getFileExtensions(framework);
1408
+ for (const entry of entries) {
1409
+ const fullPath = join(dir, entry.name);
1410
+ if (entry.isDirectory()) {
1411
+ await updateImportsRecursive(fullPath, newPrefix, framework);
1412
+ } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
1413
+ const content = await readFile(fullPath, "utf-8");
1414
+ const updatedContent = content.replace(
1415
+ /from\s+['"]@\//g,
1416
+ `from '${newPrefix}/`
1417
+ );
1418
+ if (updatedContent !== content) {
1419
+ await writeFile(fullPath, updatedContent);
1420
+ }
1421
+ }
1422
+ }
1423
+ }
1424
+ function createAuthenticatedClient(accessToken) {
1425
+ const apiUrl = configManager.get("apiUrl") || DOCYRUS_API_URL;
1426
+ const client = new RestApiClient({ baseURL: apiUrl });
1427
+ client.setAccessToken(accessToken);
1428
+ return client;
1429
+ }
1430
+ async function downloadOpenApiSpec(accessToken, targetDir, filename = "openapi.json") {
1431
+ const client = createAuthenticatedClient(accessToken);
1432
+ const spec = await client.get("/v1/api/openapi.json");
1433
+ const targetPath = join(targetDir, filename);
1434
+ const targetDirPath = dirname(targetPath);
1435
+ await mkdir(targetDirPath, { recursive: true });
1436
+ await writeFile(targetPath, JSON.stringify(spec, null, 2));
1437
+ return targetPath;
1438
+ }
1439
+ async function applyUIVariants(targetDir, uiLibrary) {
1440
+ const variantsDir = join(targetDir, "__ui-variants__");
1441
+ try {
1442
+ await access(variantsDir, constants$1.F_OK);
1443
+ } catch {
1444
+ return;
1445
+ }
1446
+ if (uiLibrary !== "none") {
1447
+ const libraryDir = join(variantsDir, uiLibrary);
1448
+ try {
1449
+ await access(libraryDir, constants$1.F_OK);
1450
+ await cp(libraryDir, targetDir, { recursive: true, force: true });
1451
+ } catch {
1452
+ }
1453
+ }
1454
+ await rm(variantsDir, { recursive: true, force: true });
1455
+ }
1456
+ var execAsync = promisify(exec);
1457
+ function parseEnvArray(value) {
1458
+ if (!value) {
1459
+ throw new Error("Missing environment variable");
1460
+ }
1461
+ return value.split(",").map((s) => s.trim());
1462
+ }
1463
+ var SHADCN_BASE_COLORS = parseEnvArray("zinc,slate,neutral,stone,gray");
1464
+ parseEnvArray("vega,nova,maia,lyra,mira");
1465
+ var DICEUI_COMPONENTS = parseEnvArray("action-bar,avatar-group,badge-overflow,checkbox-group,circular-progress,color-picker,color-swatch,combobox,compare-slider,cropper,editable,file-upload,gauge,kanban,key-value,listbox,mask-input,media-player,mention,phone-input,qr-code,rating,relative-time-card,responsive-dialog,scroll-spy,scroller,segmented-input,sortable,speed-dial,stack,stat,status,stepper,swap,tags-input,time-picker,timeline,tour");
1466
+ var UI_LIBRARY_COMPATIBILITY = {
1467
+ nextjs: [
1468
+ "shadcn",
1469
+ "diceui",
1470
+ "heroui",
1471
+ "none"
1472
+ ],
1473
+ react: [
1474
+ "shadcn",
1475
+ "diceui",
1476
+ "heroui",
1477
+ "none"
1478
+ ],
1479
+ vue: ["shadcn-vue", "none"],
1480
+ electron: [
1481
+ "shadcn",
1482
+ "diceui",
1483
+ "none"
1484
+ ]
1485
+ };
1486
+ function isUILibraryCompatible(framework, uiLibrary) {
1487
+ return UI_LIBRARY_COMPATIBILITY[framework].includes(uiLibrary);
1488
+ }
1489
+ function getUILibraryErrorMessage(framework, uiLibrary) {
1490
+ switch (uiLibrary) {
1491
+ case "heroui":
1492
+ if (framework === "electron") {
1493
+ return "HeroUI is not supported with Electron due to framer-motion compatibility issues. Use --shadcn or --diceui instead";
1494
+ }
1495
+ return `HeroUI only supports Next.js and React frameworks. Current: ${framework}`;
1496
+ case "shadcn":
1497
+ case "diceui":
1498
+ if (framework === "vue") {
1499
+ return "Use --shadcn-vue for Vue projects instead of --shadcn or --diceui";
1500
+ }
1501
+ return `shadcn/ui is not supported with ${framework}`;
1502
+ case "shadcn-vue":
1503
+ return `shadcn-vue is only for Vue projects. Use --shadcn or --diceui for ${framework}`;
1504
+ default:
1505
+ return `${uiLibrary} is not compatible with ${framework}`;
1506
+ }
1507
+ }
1508
+ async function setupShadcn(targetDir, packageManager, style, baseColor, onProgress) {
1509
+ const dlx = getDlxCommand(packageManager);
1510
+ onProgress("Initializing shadcn/ui (Base UI)...", 1, 4);
1511
+ await execAsync(`${dlx} shadcn@latest init --yes --defaults -b ${baseColor}`, {
1512
+ cwd: targetDir,
1513
+ timeout: 3e5
1514
+ });
1515
+ onProgress("Migrating to Radix UI...", 2, 4);
1516
+ await execAsync(`${dlx} shadcn@latest migrate radix --yes`, {
1517
+ cwd: targetDir,
1518
+ timeout: 12e4
1519
+ });
1520
+ onProgress("Updating style configuration...", 3, 4);
1521
+ const componentsJsonPath = join(targetDir, "components.json");
1522
+ const componentsJson = JSON.parse(await readFile(componentsJsonPath, "utf-8"));
1523
+ componentsJson.style = `radix-${style}`;
1524
+ await writeFile(componentsJsonPath, JSON.stringify(componentsJson, null, 2));
1525
+ onProgress("Installing shadcn components...", 4, 4);
1526
+ await execAsync(`${dlx} shadcn@latest add --all --yes --overwrite`, {
1527
+ cwd: targetDir,
1528
+ timeout: 3e5
1529
+ });
1530
+ }
1531
+ async function setupDiceUI(targetDir, packageManager, style, baseColor, onProgress) {
1532
+ const dlx = getDlxCommand(packageManager);
1533
+ onProgress("Initializing shadcn/ui (Base UI)...", 1, 5);
1534
+ await execAsync(`${dlx} shadcn@latest init --yes --defaults -b ${baseColor}`, {
1535
+ cwd: targetDir,
1536
+ timeout: 3e5
1537
+ });
1538
+ onProgress("Migrating to Radix UI...", 2, 5);
1539
+ await execAsync(`${dlx} shadcn@latest migrate radix --yes`, {
1540
+ cwd: targetDir,
1541
+ timeout: 12e4
1542
+ });
1543
+ onProgress("Updating style configuration...", 3, 5);
1544
+ const componentsJsonPath = join(targetDir, "components.json");
1545
+ const componentsJson = JSON.parse(await readFile(componentsJsonPath, "utf-8"));
1546
+ componentsJson.style = `radix-${style}`;
1547
+ await writeFile(componentsJsonPath, JSON.stringify(componentsJson, null, 2));
1548
+ onProgress("Installing shadcn components...", 4, 5);
1549
+ await execAsync(`${dlx} shadcn@latest add --all --yes --overwrite`, {
1550
+ cwd: targetDir,
1551
+ timeout: 3e5
1552
+ });
1553
+ onProgress("Installing DiceUI components...", 5, 5);
1554
+ for (const component of DICEUI_COMPONENTS) {
1555
+ await execAsync(`${dlx} shadcn@latest add https://shadcn-extension.vercel.app/r/diceui/${component}.json --yes --overwrite`, {
1556
+ cwd: targetDir,
1557
+ timeout: 6e4
1558
+ });
1559
+ }
1560
+ }
1561
+ async function setupShadcnVue(targetDir, packageManager, onProgress) {
1562
+ const dlx = getDlxCommand(packageManager);
1563
+ onProgress("Initializing shadcn-vue...", 1, 2);
1564
+ await execAsync(`${dlx} shadcn-vue@latest init -y -d`, {
1565
+ cwd: targetDir,
1566
+ timeout: 12e4
1567
+ });
1568
+ onProgress("Installing shadcn-vue components...", 2, 2);
1569
+ await execAsync(`${dlx} shadcn-vue@latest add -a -y -o`, {
1570
+ cwd: targetDir,
1571
+ timeout: 3e5
1572
+ });
1573
+ }
1574
+ async function setupHeroUI(targetDir, framework, packageManager, onProgress) {
1575
+ onProgress("Adding HeroUI dependencies...", 1, 3);
1576
+ const installCmd = getInstallCommand(packageManager);
1577
+ await execAsync(`${installCmd} @heroui/react framer-motion`, {
1578
+ cwd: targetDir,
1579
+ timeout: 12e4
1580
+ });
1581
+ onProgress("Configuring styles...", 2, 3);
1582
+ await writeHeroUIStyles(targetDir);
1583
+ onProgress("Adding HeroUIProvider...", 3, 3);
1584
+ await addHeroUIProvider(targetDir, framework);
1585
+ }
1586
+ async function setupUILibrary(targetDir, framework, uiLibrary, packageManager, onProgress, style, baseColor) {
1587
+ if (uiLibrary === "none") {
1588
+ return;
1589
+ }
1590
+ if (!isUILibraryCompatible(framework, uiLibrary)) {
1591
+ throw new Error(getUILibraryErrorMessage(framework, uiLibrary));
1592
+ }
1593
+ switch (uiLibrary) {
1594
+ case "shadcn":
1595
+ await setupShadcn(
1596
+ targetDir,
1597
+ packageManager,
1598
+ style ?? "vega",
1599
+ baseColor ?? "zinc",
1600
+ onProgress
1601
+ );
1602
+ break;
1603
+ case "diceui":
1604
+ await setupDiceUI(
1605
+ targetDir,
1606
+ packageManager,
1607
+ style ?? "vega",
1608
+ baseColor ?? "zinc",
1609
+ onProgress
1610
+ );
1611
+ break;
1612
+ case "shadcn-vue":
1613
+ await setupShadcnVue(targetDir, packageManager, onProgress);
1614
+ break;
1615
+ case "heroui":
1616
+ await setupHeroUI(targetDir, framework, packageManager, onProgress);
1617
+ break;
1618
+ }
1619
+ }
1620
+ function getDlxCommand(packageManager) {
1621
+ switch (packageManager) {
1622
+ case "pnpm":
1623
+ return "pnpx";
1624
+ case "yarn":
1625
+ return "yarn dlx";
1626
+ case "bun":
1627
+ return "bunx";
1628
+ default:
1629
+ return "npx";
1630
+ }
1631
+ }
1632
+ function getInstallCommand(packageManager) {
1633
+ switch (packageManager) {
1634
+ case "pnpm":
1635
+ return "pnpm add";
1636
+ case "yarn":
1637
+ return "yarn add";
1638
+ case "bun":
1639
+ return "bun add";
1640
+ default:
1641
+ return "npm install";
1642
+ }
1643
+ }
1644
+ async function writeHeroUIStyles(targetDir) {
1645
+ const possibleCssPaths = [join(targetDir, "src/styles/globals.css"), join(targetDir, "src/styles/global.css")];
1646
+ const css = `@import "tailwindcss";
1647
+ @plugin "@heroui/react/tailwind";
1648
+ `;
1649
+ for (const cssPath of possibleCssPaths) {
1650
+ try {
1651
+ await readFile(cssPath, "utf-8");
1652
+ await writeFile(cssPath, css, "utf-8");
1653
+ return;
1654
+ } catch {
1655
+ }
1656
+ }
1657
+ }
1658
+ async function addHeroUIProvider(targetDir, framework) {
1659
+ if (framework === "nextjs") {
1660
+ await addHeroUIProviderNextjs(targetDir);
1661
+ } else {
1662
+ await addHeroUIProviderReact(targetDir);
1663
+ }
1664
+ }
1665
+ async function addHeroUIProviderNextjs(targetDir) {
1666
+ const providersPath = join(targetDir, "src/app/providers.tsx");
1667
+ const providersContent = `'use client';
1668
+
1669
+ import { HeroUIProvider } from '@heroui/react';
1670
+
1671
+ export function Providers({ children }: { children: React.ReactNode }) {
1672
+ return <HeroUIProvider>{children}</HeroUIProvider>;
1673
+ }
1674
+ `;
1675
+ await writeFile(providersPath, providersContent, "utf-8");
1676
+ const layoutPath = join(targetDir, "src/app/layout.tsx");
1677
+ try {
1678
+ let layoutContent = await readFile(layoutPath, "utf-8");
1679
+ if (!layoutContent.includes("Providers")) {
1680
+ layoutContent = layoutContent.replace(
1681
+ /^(import.*\n)+/m,
1682
+ (match) => `${match}import { Providers } from './providers';
1683
+ `
1684
+ );
1685
+ layoutContent = layoutContent.replace(
1686
+ /(<body[^>]*>)([\s\S]*?)({children})([\s\S]*?)(<\/body>)/,
1687
+ "$1$2<Providers>$3</Providers>$4$5"
1688
+ );
1689
+ await writeFile(layoutPath, layoutContent, "utf-8");
1690
+ }
1691
+ } catch {
1692
+ }
1693
+ }
1694
+ async function addHeroUIProviderReact(targetDir) {
1695
+ const mainPath = join(targetDir, "src/main.tsx");
1696
+ try {
1697
+ let content = await readFile(mainPath, "utf-8");
1698
+ if (!content.includes("HeroUIProvider")) {
1699
+ content = content.replace(
1700
+ /^(import.*\n)+/m,
1701
+ (match) => `${match}import { HeroUIProvider } from '@heroui/react';
1702
+ `
1703
+ );
1704
+ content = content.replace(
1705
+ /(<App\s*\/>)/,
1706
+ "<HeroUIProvider>\n $1\n </HeroUIProvider>"
1707
+ );
1708
+ await writeFile(mainPath, content, "utf-8");
1709
+ }
1710
+ } catch {
1711
+ }
1712
+ }
1713
+ var STATE_MANAGEMENT_VERSIONS = {
1714
+ zustand: { name: "zustand", version: "5.0.11" },
1715
+ "tanstack-query": { name: "@tanstack/react-query", version: "5.90.20" },
1716
+ "tanstack-vue-query": { name: "@tanstack/vue-query", version: "5.92.9" }
1717
+ };
1718
+ function getCompatibleStateManagement(framework) {
1719
+ switch (framework) {
1720
+ case "react":
1721
+ case "nextjs":
1722
+ case "electron":
1723
+ return [
1724
+ "zustand",
1725
+ "tanstack-query",
1726
+ "none"
1727
+ ];
1728
+ case "vue":
1729
+ return ["tanstack-vue-query", "none"];
1730
+ default:
1731
+ return [];
1732
+ }
1733
+ }
1734
+ async function setupStateManagement(targetDir, framework, stateManagement, onProgress) {
1735
+ if (stateManagement === "none") return;
1736
+ const totalSteps = stateManagement === "zustand" ? 2 : 3;
1737
+ onProgress("Adding dependency...", 1, totalSteps);
1738
+ const pkg = STATE_MANAGEMENT_VERSIONS[stateManagement];
1739
+ await addDependencyToPackageJson(targetDir, pkg.name, pkg.version);
1740
+ onProgress("Creating configuration...", 2, totalSteps);
1741
+ switch (stateManagement) {
1742
+ case "zustand":
1743
+ await createZustandStore(targetDir);
1744
+ break;
1745
+ case "tanstack-query":
1746
+ await createReactQueryClient(targetDir);
1747
+ break;
1748
+ case "tanstack-vue-query":
1749
+ await createVueQueryConfig(targetDir);
1750
+ break;
1751
+ }
1752
+ if (stateManagement !== "zustand") {
1753
+ onProgress("Configuring provider...", 3, totalSteps);
1754
+ if (stateManagement === "tanstack-query") {
1755
+ await wrapReactQueryProvider(targetDir, framework);
1756
+ } else if (stateManagement === "tanstack-vue-query") {
1757
+ await wrapVueQueryPlugin(targetDir);
1758
+ }
1759
+ }
1760
+ }
1761
+ async function addDependencyToPackageJson(targetDir, name, version) {
1762
+ const pkgPath = join(targetDir, "package.json");
1763
+ const pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
1764
+ pkg.dependencies = pkg.dependencies ?? {};
1765
+ pkg.dependencies[name] = version;
1766
+ await writeFile(pkgPath, JSON.stringify(pkg, null, 2), "utf-8");
1767
+ }
1768
+ async function createZustandStore(targetDir) {
1769
+ const storePath = join(targetDir, "src/store/app-store.ts");
1770
+ await mkdir(dirname(storePath), { recursive: true });
1771
+ await writeFile(storePath, `import { create } from 'zustand';
1772
+
1773
+ interface AppState {
1774
+ count: number;
1775
+ increment: () => void;
1776
+ decrement: () => void;
1777
+ }
1778
+
1779
+ export const useAppStore = create<AppState>((set) => ({
1780
+ count: 0,
1781
+ increment: () => set((state) => ({ count: state.count + 1 })),
1782
+ decrement: () => set((state) => ({ count: state.count - 1 }))
1783
+ }));
1784
+ `, "utf-8");
1785
+ }
1786
+ async function createReactQueryClient(targetDir) {
1787
+ const clientPath = join(targetDir, "src/lib/query-client.ts");
1788
+ await mkdir(dirname(clientPath), { recursive: true });
1789
+ await writeFile(clientPath, `import { QueryClient } from '@tanstack/react-query';
1790
+
1791
+ export const queryClient = new QueryClient({
1792
+ defaultOptions: {
1793
+ queries: {
1794
+ staleTime: 60 * 1000,
1795
+ retry: 1
1796
+ }
1797
+ }
1798
+ });
1799
+ `, "utf-8");
1800
+ }
1801
+ async function createVueQueryConfig(targetDir) {
1802
+ const configPath = join(targetDir, "src/lib/query-client.ts");
1803
+ await mkdir(dirname(configPath), { recursive: true });
1804
+ await writeFile(configPath, `import type { VueQueryPluginOptions } from '@tanstack/vue-query';
1805
+
1806
+ export const vueQueryOptions: VueQueryPluginOptions = {
1807
+ queryClientConfig: {
1808
+ defaultOptions: {
1809
+ queries: {
1810
+ staleTime: 60 * 1000,
1811
+ retry: 1
1812
+ }
1813
+ }
1814
+ }
1815
+ };
1816
+ `, "utf-8");
1817
+ }
1818
+ async function wrapReactQueryProvider(targetDir, framework) {
1819
+ if (framework === "nextjs") {
1820
+ await wrapNextjsQueryProvider(targetDir);
1821
+ } else {
1822
+ await wrapReactEntryQueryProvider(targetDir, framework);
1823
+ }
1824
+ }
1825
+ async function wrapNextjsQueryProvider(targetDir) {
1826
+ const providersPath = join(targetDir, "src/app/providers.tsx");
1827
+ await writeFile(providersPath, `'use client';
1828
+
1829
+ import { QueryClientProvider } from '@tanstack/react-query';
1830
+
1831
+ import { queryClient } from '@/lib/query-client';
1832
+
1833
+ export function Providers({ children }: { children: React.ReactNode }) {
1834
+ return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
1835
+ }
1836
+ `, "utf-8");
1837
+ const layoutPath = join(targetDir, "src/app/layout.tsx");
1838
+ try {
1839
+ let content = await readFile(layoutPath, "utf-8");
1840
+ if (!content.includes("Providers")) {
1841
+ content = content.replace(
1842
+ /^(import\s.*\n)+/m,
1843
+ (match) => `${match}import { Providers } from './providers';
1844
+ `
1845
+ );
1846
+ content = content.replace(
1847
+ /\{children\}/,
1848
+ "<Providers>{children}</Providers>"
1849
+ );
1850
+ await writeFile(layoutPath, content, "utf-8");
1851
+ }
1852
+ } catch {
1853
+ }
1854
+ }
1855
+ async function wrapReactEntryQueryProvider(targetDir, framework) {
1856
+ const mainPath = join(targetDir, "src/main.tsx");
1857
+ try {
1858
+ let content = await readFile(mainPath, "utf-8");
1859
+ if (content.includes("QueryClientProvider")) return;
1860
+ content = content.replace(
1861
+ /^(import\s.*\n)+/m,
1862
+ (match) => `${match}import { QueryClientProvider } from '@tanstack/react-query';
1863
+ import { queryClient } from './lib/query-client';
1864
+ `
1865
+ );
1866
+ if (framework === "electron") {
1867
+ content = content.replace(
1868
+ /(<BrowserRouter>)/,
1869
+ "<QueryClientProvider client={queryClient}>\n $1"
1870
+ );
1871
+ content = content.replace(
1872
+ /(<\/BrowserRouter>)/,
1873
+ "$1\n </QueryClientProvider>"
1874
+ );
1875
+ } else {
1876
+ content = content.replace(
1877
+ /(<App\s*\/>)/,
1878
+ "<QueryClientProvider client={queryClient}>\n $1\n </QueryClientProvider>"
1879
+ );
1880
+ }
1881
+ await writeFile(mainPath, content, "utf-8");
1882
+ } catch {
1883
+ }
1884
+ }
1885
+ async function wrapVueQueryPlugin(targetDir) {
1886
+ const mainPath = join(targetDir, "src/main.ts");
1887
+ try {
1888
+ let content = await readFile(mainPath, "utf-8");
1889
+ if (content.includes("VueQueryPlugin")) return;
1890
+ content = content.replace(
1891
+ /^(import\s.*\n)+/m,
1892
+ (match) => `${match}import { VueQueryPlugin } from '@tanstack/vue-query';
1893
+ import { vueQueryOptions } from './lib/query-client';
1894
+ `
1895
+ );
1896
+ content = content.replace(
1897
+ /app\.use\(router\)/,
1898
+ "app.use(VueQueryPlugin, vueQueryOptions);\napp.use(router)"
1899
+ );
1900
+ await writeFile(mainPath, content, "utf-8");
1901
+ } catch {
1902
+ }
1903
+ }
1904
+
1905
+ // src/commands/create.ts
1906
+ var execAsync2 = promisify(exec);
1907
+ function validateConflictingFlags(options) {
1908
+ const frameworkFlags = [
1909
+ options.nextjs,
1910
+ options.react,
1911
+ options.vue,
1912
+ options.electron
1913
+ ].filter(Boolean);
1914
+ if (frameworkFlags.length > 1) {
1915
+ throw new ConflictingFlagsError("--nextjs, --react, --vue, --electron", "(multiple frameworks)");
1916
+ }
1917
+ const uiLibraryFlags = [
1918
+ options.shadcn,
1919
+ options.diceui,
1920
+ options.shadcnVue,
1921
+ options.heroui
1922
+ ].filter(Boolean);
1923
+ if (uiLibraryFlags.length > 1) {
1924
+ throw new ConflictingFlagsError(
1925
+ "--shadcn, --diceui, --shadcn-vue, --heroui",
1926
+ MESSAGES.UI_MULTIPLE_LIBRARIES
1927
+ );
1928
+ }
1929
+ const stateFlags = [
1930
+ options.zustand,
1931
+ options.tanstackQuery,
1932
+ options.tanstackVueQuery
1933
+ ].filter(Boolean);
1934
+ if (stateFlags.length > 1) {
1935
+ throw new ConflictingFlagsError("--zustand, --tanstack-query, --tanstack-vue-query", "(multiple state management options)");
1936
+ }
1937
+ const linterFlags = [
1938
+ options.eslint,
1939
+ options.biome,
1940
+ options.noLinter
1941
+ ].filter(Boolean);
1942
+ if (linterFlags.length > 1) {
1943
+ throw new ConflictingFlagsError("--eslint, --biome, --no-linter", "(multiple linter options)");
1944
+ }
1945
+ const pmFlags = [
1946
+ options.pnpm,
1947
+ options.npm,
1948
+ options.yarn,
1949
+ options.bun
1950
+ ].filter(Boolean);
1951
+ if (pmFlags.length > 1) {
1952
+ throw new ConflictingFlagsError("--pnpm, --npm, --yarn, --bun", "(multiple package managers)");
1953
+ }
1954
+ }
1955
+ function validateFrameworkUILibrary(framework, uiLibrary) {
1956
+ if (uiLibrary === "none") return;
1957
+ if (!isUILibraryCompatible(framework, uiLibrary)) {
1958
+ throw new ConflictingFlagsError(
1959
+ `--${framework}`,
1960
+ getUILibraryErrorMessage(framework, uiLibrary)
1961
+ );
1962
+ }
1963
+ }
1964
+ function getFrameworkFromFlags(options) {
1965
+ if (options.nextjs) return "nextjs";
1966
+ if (options.react) return "react";
1967
+ if (options.vue) return "vue";
1968
+ if (options.electron) return "electron";
1969
+ return void 0;
1970
+ }
1971
+ function getUiLibraryFromFlags(options) {
1972
+ if (options.shadcn) return "shadcn";
1973
+ if (options.diceui) return "diceui";
1974
+ if (options.shadcnVue) return "shadcn-vue";
1975
+ if (options.heroui) return "heroui";
1976
+ return void 0;
1977
+ }
1978
+ function getLinterFromFlags(options) {
1979
+ if (options.eslint) return "eslint";
1980
+ if (options.biome) return "biome";
1981
+ if (options.noLinter) return "none";
1982
+ return void 0;
1983
+ }
1984
+ function getPackageManagerFromFlags(options) {
1985
+ if (options.pnpm) return "pnpm";
1986
+ if (options.npm) return "npm";
1987
+ if (options.yarn) return "yarn";
1988
+ if (options.bun) return "bun";
1989
+ return void 0;
1990
+ }
1991
+ function getStateManagementFromFlags(options) {
1992
+ if (options.zustand) return "zustand";
1993
+ if (options.tanstackQuery) return "tanstack-query";
1994
+ if (options.tanstackVueQuery) return "tanstack-vue-query";
1995
+ return void 0;
1996
+ }
1997
+ var FRAMEWORKS = [
1998
+ { name: "nextjs", label: "Next.js (App Router)", description: "Next.js 16+, TypeScript, Tailwind v4" },
1999
+ { name: "react", label: "React + Vite", description: "React 19+, Vite, TypeScript, Tailwind v4" },
2000
+ { name: "vue", label: "Vue + Vite", description: "Vue 3.5+, Vite, TypeScript, Tailwind v4" },
2001
+ { name: "electron", label: "Electron + React", description: "Desktop app, React 19+, Vite, Tailwind v4" }
2002
+ ];
2003
+ var UI_LIBRARIES_NEXTJS = [
2004
+ { name: "shadcn", label: "shadcn/ui (Recommended)", description: "Beautifully designed components from shadcn" },
2005
+ { name: "diceui", label: "DiceUI", description: "shadcn/ui + 5 premium components" },
2006
+ { name: "heroui", label: "HeroUI", description: "Modern React UI library" },
2007
+ { name: "none", label: "None (Plain Tailwind)", description: "Just Tailwind CSS, no component library" }
2008
+ ];
2009
+ var UI_LIBRARIES_REACT = [
2010
+ { name: "shadcn", label: "shadcn/ui (Recommended)", description: "Beautifully designed components from shadcn" },
2011
+ { name: "diceui", label: "DiceUI", description: "shadcn/ui + 5 premium components" },
2012
+ { name: "heroui", label: "HeroUI", description: "Modern React UI library" },
2013
+ { name: "none", label: "None (Plain Tailwind)", description: "Just Tailwind CSS, no component library" }
2014
+ ];
2015
+ var UI_LIBRARIES_VUE = [{ name: "shadcn-vue", label: "shadcn-vue (Recommended)", description: "Vue port of shadcn/ui" }, { name: "none", label: "None (Plain Tailwind)", description: "Just Tailwind CSS, no component library" }];
2016
+ var UI_LIBRARIES_ELECTRON = [
2017
+ { name: "shadcn", label: "shadcn/ui", description: "Beautifully designed components from shadcn" },
2018
+ { name: "diceui", label: "DiceUI", description: "shadcn/ui + 5 premium components" },
2019
+ { name: "none", label: "None (Plain Tailwind)", description: "Just Tailwind CSS, no component library" }
2020
+ ];
2021
+ function getUiLibrariesForFramework(framework) {
2022
+ switch (framework) {
2023
+ case "nextjs":
2024
+ return UI_LIBRARIES_NEXTJS;
2025
+ case "react":
2026
+ return UI_LIBRARIES_REACT;
2027
+ case "vue":
2028
+ return UI_LIBRARIES_VUE;
2029
+ case "electron":
2030
+ return UI_LIBRARIES_ELECTRON;
2031
+ default:
2032
+ return UI_LIBRARIES_REACT;
2033
+ }
2034
+ }
2035
+ var STATE_MGMT_REACT = [
2036
+ { name: "zustand", label: "Zustand (Recommended)", description: "Lightweight client state" },
2037
+ { name: "tanstack-query", label: "TanStack Query", description: "Server state & data fetching" },
2038
+ { name: "none", label: "None", description: "No state management" }
2039
+ ];
2040
+ var STATE_MGMT_VUE = [{ name: "tanstack-vue-query", label: "TanStack Vue Query (Recommended)", description: "Server state & data fetching" }, { name: "none", label: "None", description: "No state management" }];
2041
+ function getStateManagementForFramework(framework) {
2042
+ switch (framework) {
2043
+ case "vue":
2044
+ return STATE_MGMT_VUE;
2045
+ default:
2046
+ return STATE_MGMT_REACT;
2047
+ }
2048
+ }
2049
+ var SHADCN_STYLE_CHOICES = [
2050
+ { name: "vega", label: "Vega (Recommended)", description: "Classic shadcn/ui design" },
2051
+ { name: "nova", label: "Nova", description: "Compact layouts" },
2052
+ { name: "maia", label: "Maia", description: "Soft and rounded" },
2053
+ { name: "lyra", label: "Lyra", description: "Boxy and sharp" },
2054
+ { name: "mira", label: "Mira", description: "Dense interfaces" }
2055
+ ];
2056
+ var SHADCN_BASE_COLOR_CHOICES = [
2057
+ { name: "zinc", label: "Zinc (Default)", description: "Cool neutral gray" },
2058
+ { name: "slate", label: "Slate", description: "Blue-tinted gray" },
2059
+ { name: "neutral", label: "Neutral", description: "Pure gray" },
2060
+ { name: "stone", label: "Stone", description: "Warm gray" },
2061
+ { name: "gray", label: "Gray", description: "Balanced gray" }
2062
+ ];
2063
+ var LINTERS = [
2064
+ { name: "eslint", label: "ESLint (Recommended)", description: "Industry standard, widely supported" },
2065
+ { name: "biome", label: "Biome", description: "Fast, all-in-one linter and formatter" },
2066
+ { name: "none", label: "None", description: "No linter, configure your own later" }
2067
+ ];
2068
+ var PACKAGE_MANAGERS = [
2069
+ { name: "pnpm", label: "pnpm (Recommended)", description: "Fast, disk space efficient" },
2070
+ { name: "npm", label: "npm", description: "Node.js default package manager" },
2071
+ { name: "yarn", label: "yarn", description: "Fast, reliable, and secure" },
2072
+ { name: "bun", label: "bun", description: "All-in-one JavaScript runtime" }
2073
+ ];
2074
+ var TEMPLATE_REPO_BASE = "Docyrus/docyrus-devkit/packages/cli-templates";
2075
+ function getTemplateRepo(framework) {
2076
+ return `${TEMPLATE_REPO_BASE}/${framework}`;
2077
+ }
2078
+ async function directoryExists(path) {
2079
+ try {
2080
+ await access(path, constants.F_OK);
2081
+ return true;
2082
+ } catch {
2083
+ return false;
2084
+ }
2085
+ }
2086
+ function registerCreateCommand(program2) {
2087
+ program2.command("create [name]").description("Create a new Docyrus project from template").option("-p, --path <path>", "Target directory path").option("--nextjs", "Use Next.js framework").option("--react", "Use React + Vite framework").option("--vue", "Use Vue + Vite framework").option("--electron", "Use Electron + React framework (desktop app)").option("--shadcn", "Use shadcn/ui (Next.js, React, Electron)").option("--diceui", "Use DiceUI (Next.js, React, Electron)").option("--shadcn-vue", "Use shadcn-vue (Vue)").option("--heroui", "Use HeroUI (Next.js, React)").option("--zustand", "Use Zustand for state management (React, Next.js, Electron)").option("--tanstack-query", "Use TanStack Query for state management (React, Next.js, Electron)").option("--tanstack-vue-query", "Use TanStack Vue Query for state management (Vue)").option("--base-color <color>", "Base color theme for shadcn (zinc, slate, neutral, stone, gray)").option("--eslint", "Use ESLint for linting").option("--biome", "Use Biome for linting").option("--no-linter", "Skip linter configuration").option("--alias <prefix>", "Custom import alias prefix (must start with @)").option("--pnpm", "Use pnpm package manager").option("--npm", "Use npm package manager").option("--yarn", "Use yarn package manager").option("--bun", "Use bun package manager").option("--local", "Use local templates instead of downloading from GitHub (development only)").addHelpText("after", `
2088
+ UI Library Compatibility:
2089
+ Next.js \u2192 shadcn, diceui, heroui
2090
+ React \u2192 shadcn, diceui, heroui
2091
+ Vue \u2192 shadcn-vue
2092
+ Electron \u2192 shadcn, diceui
2093
+
2094
+ State Management:
2095
+ React / Next.js / Electron \u2192 zustand, tanstack-query
2096
+ Vue \u2192 tanstack-vue-query
2097
+
2098
+ Examples:
2099
+ $ docyrus create my-app Interactive mode
2100
+ $ docyrus create my-app --nextjs Next.js + plain Tailwind
2101
+ $ docyrus create my-app --nextjs --shadcn Next.js + shadcn/ui
2102
+ $ docyrus create my-app --react --shadcn --zustand React + shadcn + Zustand
2103
+ $ docyrus create my-app --react --tanstack-query React + TanStack Query
2104
+ $ docyrus create my-app --vue --shadcn-vue Vue + shadcn-vue
2105
+ $ docyrus create my-app --vue --tanstack-vue-query Vue + TanStack Vue Query
2106
+ $ docyrus create my-app --electron --shadcn --zustand Electron + shadcn + Zustand
2107
+ `).action(async (name, options) => {
2108
+ const accessToken = await requireAuth();
2109
+ if (options) {
2110
+ validateConflictingFlags(options);
2111
+ }
2112
+ const frameworkFromFlags = options ? getFrameworkFromFlags(options) : void 0;
2113
+ const uiLibraryFromFlags = options ? getUiLibraryFromFlags(options) : void 0;
2114
+ const linterFromFlags = options ? getLinterFromFlags(options) : void 0;
2115
+ const packageManagerFromFlags = options ? getPackageManagerFromFlags(options) : void 0;
2116
+ const aliasFromFlags = options?.alias;
2117
+ const framework = frameworkFromFlags ?? await select({
2118
+ message: MESSAGES.CREATE_SELECT_FRAMEWORK,
2119
+ choices: FRAMEWORKS.map((f) => ({
2120
+ name: f.label,
2121
+ value: f.name
2122
+ }))
2123
+ });
2124
+ let uiLibrary;
2125
+ const availableUiLibraries = getUiLibrariesForFramework(framework);
2126
+ if (uiLibraryFromFlags) {
2127
+ validateFrameworkUILibrary(framework, uiLibraryFromFlags);
2128
+ uiLibrary = uiLibraryFromFlags;
2129
+ } else if (frameworkFromFlags) {
2130
+ uiLibrary = "none";
2131
+ } else {
2132
+ uiLibrary = await select({
2133
+ message: MESSAGES.CREATE_SELECT_UI_LIBRARY,
2134
+ choices: availableUiLibraries.map((u) => ({
2135
+ name: u.label,
2136
+ value: u.name
2137
+ }))
2138
+ });
2139
+ }
2140
+ let shadcnStyle = "vega";
2141
+ let baseColor = "zinc";
2142
+ if (uiLibrary === "shadcn" || uiLibrary === "diceui") {
2143
+ if (!frameworkFromFlags) {
2144
+ shadcnStyle = await select({
2145
+ message: "Select component style:",
2146
+ choices: SHADCN_STYLE_CHOICES.map((c) => ({
2147
+ name: c.label,
2148
+ value: c.name,
2149
+ description: c.description
2150
+ }))
2151
+ });
2152
+ baseColor = await select({
2153
+ message: "Select base color:",
2154
+ choices: SHADCN_BASE_COLOR_CHOICES.map((c) => ({
2155
+ name: c.label,
2156
+ value: c.name,
2157
+ description: c.description
2158
+ }))
2159
+ });
2160
+ } else {
2161
+ const validColors = SHADCN_BASE_COLORS;
2162
+ if (options?.baseColor) {
2163
+ if (!validColors.includes(options.baseColor)) {
2164
+ throw new ConflictingFlagsError("--base-color", `must be one of: ${validColors.join(", ")}`);
2165
+ }
2166
+ baseColor = options.baseColor;
2167
+ }
2168
+ }
2169
+ }
2170
+ const stateManagementFromFlags = options ? getStateManagementFromFlags(options) : void 0;
2171
+ const stateMgmtChoices = getStateManagementForFramework(framework);
2172
+ let stateManagement = "none";
2173
+ if (stateMgmtChoices) {
2174
+ if (stateManagementFromFlags) {
2175
+ const compatible = getCompatibleStateManagement(framework);
2176
+ if (!compatible.includes(stateManagementFromFlags)) {
2177
+ throw new ConflictingFlagsError(
2178
+ `--${stateManagementFromFlags}`,
2179
+ `not compatible with ${framework}`
2180
+ );
2181
+ }
2182
+ stateManagement = stateManagementFromFlags;
2183
+ } else if (!frameworkFromFlags) {
2184
+ stateManagement = await select({
2185
+ message: MESSAGES.CREATE_SELECT_STATE_MANAGEMENT,
2186
+ choices: stateMgmtChoices.map((s) => ({
2187
+ name: s.label,
2188
+ value: s.name,
2189
+ description: s.description
2190
+ }))
2191
+ });
2192
+ }
2193
+ }
2194
+ const linter = linterFromFlags ?? await select({
2195
+ message: MESSAGES.CREATE_SELECT_LINTER,
2196
+ choices: LINTERS.map((l) => ({
2197
+ name: l.label,
2198
+ value: l.name
2199
+ }))
2200
+ });
2201
+ let projectName = name;
2202
+ if (!projectName) {
2203
+ projectName = await input({
2204
+ message: MESSAGES.CREATE_PROJECT_NAME,
2205
+ default: `my-${framework}-app`,
2206
+ validate: (value) => {
2207
+ if (!value.trim()) return "Project name is required";
2208
+ if (!/^[a-z0-9-]+$/.test(value)) return "Only lowercase letters, numbers and hyphens allowed";
2209
+ return true;
2210
+ }
2211
+ });
2212
+ }
2213
+ let aliasPrefix = "@";
2214
+ if (aliasFromFlags) {
2215
+ if (!aliasFromFlags.startsWith("@")) {
2216
+ throw new ConflictingFlagsError("--alias", "value must start with @");
2217
+ }
2218
+ if (!/^@[a-zA-Z0-9]*$/.test(aliasFromFlags)) {
2219
+ throw new ConflictingFlagsError("--alias", "only @, @app, @src format allowed");
2220
+ }
2221
+ aliasPrefix = aliasFromFlags;
2222
+ } else {
2223
+ const customizeAlias = await select({
2224
+ message: MESSAGES.CREATE_CUSTOMIZE_ALIAS,
2225
+ choices: [{ name: "No, use @/ (Recommended)", value: false }, { name: "Yes, customize", value: true }]
2226
+ });
2227
+ if (customizeAlias) {
2228
+ aliasPrefix = await input({
2229
+ message: MESSAGES.CREATE_CUSTOM_ALIAS,
2230
+ default: "@",
2231
+ validate: (value) => {
2232
+ if (!value.trim()) return "Alias prefix is required";
2233
+ if (!value.startsWith("@")) return "Alias must start with @";
2234
+ if (!/^@[a-zA-Z0-9]*$/.test(value)) return "Only @, @app, @src format allowed";
2235
+ return true;
2236
+ }
2237
+ });
2238
+ }
2239
+ }
2240
+ const packageManager = packageManagerFromFlags ?? await select({
2241
+ message: MESSAGES.CREATE_SELECT_PACKAGE_MANAGER,
2242
+ choices: PACKAGE_MANAGERS.map((pm) => ({
2243
+ name: pm.label,
2244
+ value: pm.name
2245
+ }))
2246
+ });
2247
+ const targetDir = options?.path ? join(options.path, projectName) : projectName;
2248
+ if (await directoryExists(targetDir)) {
2249
+ throw new ProjectExistsError(projectName);
2250
+ }
2251
+ const isLocalMode = options?.local === true;
2252
+ const templateRepo = getTemplateRepo(framework);
2253
+ logger.newline();
2254
+ logger.log(MESSAGES.CREATE_SETTING_UP);
2255
+ logger.newline();
2256
+ if (isLocalMode) {
2257
+ await withSpinner(
2258
+ MESSAGES.CREATE_COPYING_LOCAL,
2259
+ async () => {
2260
+ const cliPackageDir = dirname(dirname(fileURLToPath(import.meta.url)));
2261
+ const localTemplateDir = resolve(cliPackageDir, "..", "cli-templates", framework);
2262
+ try {
2263
+ await access(localTemplateDir, constants.F_OK);
2264
+ } catch {
2265
+ throw new TemplateError(
2266
+ `Local template not found at: ${localTemplateDir}
2267
+ --local requires running from the monorepo development environment.`
2268
+ );
2269
+ }
2270
+ await cp(localTemplateDir, targetDir, { recursive: true });
2271
+ const pkgJsonPath = join(targetDir, "package.json");
2272
+ const pkgJson = JSON.parse(await readFile(pkgJsonPath, "utf-8"));
2273
+ const packagesDir = resolve(cliPackageDir, "..");
2274
+ for (const depType of ["dependencies", "devDependencies"]) {
2275
+ const deps = pkgJson[depType];
2276
+ if (!deps) continue;
2277
+ for (const [name2, version] of Object.entries(deps)) {
2278
+ if (name2.startsWith("@docyrus/") && version === "latest") {
2279
+ const pkgName = name2.replace("@docyrus/", "");
2280
+ const localPkgDir = resolve(packagesDir, pkgName);
2281
+ try {
2282
+ await access(localPkgDir, constants.F_OK);
2283
+ deps[name2] = `file:${localPkgDir}`;
2284
+ } catch {
2285
+ logger.warn(`Local package not found for ${name2}, keeping "latest"`);
2286
+ }
2287
+ }
2288
+ }
2289
+ }
2290
+ await writeFile(pkgJsonPath, `${JSON.stringify(pkgJson, null, 2)}
2291
+ `);
2292
+ }
2293
+ );
2294
+ } else {
2295
+ const tokenManager = getTokenManager();
2296
+ let githubToken = await tokenManager.getGithubToken();
2297
+ if (!githubToken) {
2298
+ logger.log(MESSAGES.DOCYRUS_TOKEN_REQUIRED);
2299
+ githubToken = await password({
2300
+ message: MESSAGES.DOCYRUS_TOKEN_PROMPT,
2301
+ mask: "*"
2302
+ });
2303
+ await tokenManager.setGithubToken(githubToken);
2304
+ logger.success(MESSAGES.DOCYRUS_TOKEN_SAVED);
2305
+ }
2306
+ await withSpinner(
2307
+ MESSAGES.CREATE_DOWNLOADING,
2308
+ async () => {
2309
+ const gigetCachePath = resolve(homedir(), ".cache/giget/gh/Docyrus-docyrus-devkit");
2310
+ if (existsSync(gigetCachePath)) {
2311
+ await rm(gigetCachePath, { recursive: true, force: true });
2312
+ }
2313
+ try {
2314
+ await downloadTemplate(`gh:${templateRepo}`, {
2315
+ dir: targetDir,
2316
+ install: false,
2317
+ auth: githubToken
2318
+ });
2319
+ } catch (error) {
2320
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
2321
+ if (errorMessage.includes("401") || errorMessage.includes("403")) {
2322
+ await tokenManager.setGithubToken("");
2323
+ throw new TemplateError(
2324
+ `${MESSAGES.DOCYRUS_TOKEN_INVALID}
2325
+ ${errorMessage}`
2326
+ );
2327
+ }
2328
+ if (errorMessage.includes("404")) {
2329
+ throw new TemplateError(
2330
+ `Template repository not found or token lacks access.
2331
+ Ensure your Docyrus token has correct permissions.
2332
+ ${errorMessage}`
2333
+ );
2334
+ }
2335
+ throw new TemplateError(`Failed to download template: ${errorMessage}`);
2336
+ }
2337
+ try {
2338
+ await access(join(targetDir, "package.json"), constants.F_OK);
2339
+ } catch {
2340
+ throw new TemplateError(
2341
+ "Template download failed \u2014 no files were downloaded.\nThis usually means your Docyrus token is expired or invalid.\nRun: docyrus config --token"
2342
+ );
2343
+ }
2344
+ }
2345
+ );
2346
+ }
2347
+ if (aliasPrefix !== "@" && aliasPrefix !== "@/") {
2348
+ await withSpinner(
2349
+ MESSAGES.CREATE_CONFIGURING_ALIAS(aliasPrefix),
2350
+ async () => {
2351
+ await applyAliasConfig(targetDir, framework, aliasPrefix);
2352
+ }
2353
+ );
2354
+ }
2355
+ if (linter !== "none") {
2356
+ logger.newline();
2357
+ logger.log(MESSAGES.LINTER_SETUP_TITLE);
2358
+ const linterProgress = createSimpleProgress();
2359
+ try {
2360
+ await applyLinterConfig(targetDir, framework, linter, linterProgress);
2361
+ } catch (error) {
2362
+ throw new TemplateError(
2363
+ `Failed to configure linter: ${error instanceof Error ? error.message : "Unknown error"}`
2364
+ );
2365
+ }
2366
+ }
2367
+ if (stateManagement !== "none") {
2368
+ logger.newline();
2369
+ logger.log(MESSAGES.STATE_MANAGEMENT_SETUP_TITLE);
2370
+ const stateProgress = createSimpleProgress();
2371
+ try {
2372
+ await setupStateManagement(
2373
+ targetDir,
2374
+ framework,
2375
+ stateManagement,
2376
+ stateProgress
2377
+ );
2378
+ } catch (error) {
2379
+ throw new TemplateError(
2380
+ `Failed to setup state management: ${error instanceof Error ? error.message : "Unknown error"}`
2381
+ );
2382
+ }
2383
+ }
2384
+ await withSpinner(
2385
+ MESSAGES.CREATE_INSTALLING,
2386
+ async () => {
2387
+ try {
2388
+ await execAsync2(`${packageManager} install`, { cwd: targetDir });
2389
+ } catch (error) {
2390
+ throw new TemplateError(
2391
+ `Failed to install dependencies: ${error instanceof Error ? error.message : "Unknown error"}`
2392
+ );
2393
+ }
2394
+ }
2395
+ );
2396
+ if (uiLibrary !== "none") {
2397
+ logger.newline();
2398
+ logger.log(MESSAGES.UI_SETUP_TITLE);
2399
+ const progressCallback = createSimpleProgress();
2400
+ try {
2401
+ await setupUILibrary(
2402
+ targetDir,
2403
+ framework,
2404
+ uiLibrary,
2405
+ packageManager,
2406
+ progressCallback,
2407
+ shadcnStyle,
2408
+ baseColor
2409
+ );
2410
+ } catch (error) {
2411
+ throw new TemplateError(
2412
+ `Failed to setup UI library: ${error instanceof Error ? error.message : "Unknown error"}`
2413
+ );
2414
+ }
2415
+ }
2416
+ await withSpinner(
2417
+ "Applying page variants...",
2418
+ async () => applyUIVariants(targetDir, uiLibrary)
2419
+ );
2420
+ logger.newline();
2421
+ logger.log(MESSAGES.OPENAPI_SETUP_TITLE);
2422
+ try {
2423
+ const savedPath = await withSpinner(
2424
+ "Downloading OpenAPI spec...",
2425
+ async () => downloadOpenApiSpec(accessToken, targetDir, "openapi-spec.json"),
2426
+ { successText: "OpenAPI spec downloaded" }
2427
+ );
2428
+ logger.dim(MESSAGES.CREATE_OPENAPI_SUCCESS(savedPath));
2429
+ } catch (error) {
2430
+ logger.warn(`Failed to download OpenAPI spec: ${error instanceof Error ? error.message : "Unknown error"}`);
2431
+ }
2432
+ logger.newline();
2433
+ logger.success(MESSAGES.CREATE_SUCCESS);
2434
+ logger.newline();
2435
+ logger.log(` cd ${projectName}`);
2436
+ logger.log(` ${packageManager} run dev`);
2437
+ logger.newline();
2438
+ logger.dim(MESSAGES.API_CLIENT_DOCS);
2439
+ });
2440
+ }
2441
+ function registerGenerateCommand(program2) {
2442
+ const generate = program2.command("generate").description("Code generation commands");
2443
+ generate.command("api-spec").description("Download OpenAPI specification from Docyrus API").option("-o, --output <path>", "Output file path", "openapi.json").action(async (options) => {
2444
+ const token = await requireAuth();
2445
+ const outputPath = resolve(options.output);
2446
+ if (!output.isJson()) {
2447
+ logger.info("Downloading OpenAPI specification...");
2448
+ }
2449
+ try {
2450
+ const savedPath = await withSpinner(
2451
+ "Fetching OpenAPI spec from API...",
2452
+ async () => downloadOpenApiSpec(token, dirname(outputPath), outputPath.split("/").pop()),
2453
+ { silent: output.isJson() }
2454
+ );
2455
+ output.success("OpenAPI specification downloaded!", { path: savedPath });
2456
+ if (!output.isJson()) {
2457
+ logger.dim(`Saved to: ${savedPath}`);
2458
+ }
2459
+ } catch (error) {
2460
+ const message = error instanceof Error ? error.message : "Unknown error";
2461
+ output.error(`Failed to download OpenAPI spec: ${message}`);
2462
+ process.exit(1);
2463
+ }
2464
+ });
2465
+ generate.command("db").description("Generate TanStack Query collections from OpenAPI spec").argument("[spec]", "Path to OpenAPI specification file (JSON)").option("-o, --output <dir>", "Output directory (defaults to src in same folder as spec)").option("-w, --watch", "Watch for changes in the spec file").action(async (specPath, options) => {
2466
+ const resolvedPath = findSpecFile(specPath);
2467
+ if (!resolvedPath) {
2468
+ output.error("OpenAPI spec file not found.");
2469
+ if (!output.isJson()) {
2470
+ logger.dim("Provide a path: docyrus generate db <spec-file>");
2471
+ logger.dim("Or create one of: openapi.json, api.json, spec.json");
2472
+ }
2473
+ process.exit(1);
2474
+ }
2475
+ if (!existsSync(resolvedPath)) {
2476
+ output.error(`OpenAPI spec file not found: ${resolvedPath}`);
2477
+ process.exit(1);
2478
+ }
2479
+ const outputDir = options.output || join(dirname(resolvedPath), "src");
2480
+ if (!output.isJson()) {
2481
+ logger.info("TanStack DB Generator");
2482
+ logger.dim(` Spec: ${resolvedPath}`);
2483
+ logger.dim(` Output: ${outputDir}`);
2484
+ logger.newline();
2485
+ }
2486
+ try {
2487
+ const specContent = readFileSync(resolvedPath, "utf-8");
2488
+ const spec = JSON.parse(specContent);
2489
+ await withSpinner(
2490
+ "Generating TanStack Query collections...",
2491
+ async () => {
2492
+ await generateFromOpenAPI(spec, outputDir);
2493
+ },
2494
+ {
2495
+ silent: output.isJson(),
2496
+ successText: "Generation completed!"
2497
+ }
2498
+ );
2499
+ output.success("TanStack Query collections generated successfully!", {
2500
+ spec: resolvedPath,
2501
+ output: outputDir
2502
+ });
2503
+ if (options.watch) {
2504
+ if (!output.isJson()) {
2505
+ logger.info("Watch mode is not yet implemented.");
2506
+ }
2507
+ }
2508
+ } catch (error) {
2509
+ const message = error instanceof Error ? error.message : "Unknown error";
2510
+ output.error(`Generation failed: ${message}`);
2511
+ process.exit(1);
2512
+ }
2513
+ });
2514
+ }
2515
+ function findSpecFile(specPath) {
2516
+ if (specPath) {
2517
+ return resolve(specPath);
2518
+ }
2519
+ const defaultSpec = resolve("openapi.json");
2520
+ if (existsSync(defaultSpec)) {
2521
+ return defaultSpec;
2522
+ }
2523
+ return null;
2524
+ }
2525
+ var execAsync3 = promisify(exec);
2526
+ async function getLatestVersion() {
2527
+ try {
2528
+ const response = await fetch(`https://registry.npmjs.org/${NPM_PACKAGE_NAME}`);
2529
+ if (!response.ok) return null;
2530
+ const data = await response.json();
2531
+ return data["dist-tags"]?.latest || null;
2532
+ } catch {
2533
+ return null;
2534
+ }
2535
+ }
2536
+ function isNewerVersion(current, latest) {
2537
+ const currentParts = current.replace(/^v/, "").split(".").map(Number);
2538
+ const latestParts = latest.replace(/^v/, "").split(".").map(Number);
2539
+ for (let i = 0; i < 3; i++) {
2540
+ const curr = currentParts[i] || 0;
2541
+ const lat = latestParts[i] || 0;
2542
+ if (lat > curr) return true;
2543
+ if (lat < curr) return false;
2544
+ }
2545
+ return false;
2546
+ }
2547
+ async function detectPackageManager() {
2548
+ const npmExecpath = process.env.npm_execpath || "";
2549
+ if (npmExecpath.includes("pnpm")) return "pnpm";
2550
+ if (npmExecpath.includes("yarn")) return "yarn";
2551
+ if (npmExecpath.includes("bun")) return "bun";
2552
+ try {
2553
+ await execAsync3("pnpm --version");
2554
+ return "pnpm";
2555
+ } catch {
2556
+ }
2557
+ try {
2558
+ await execAsync3("yarn --version");
2559
+ return "yarn";
2560
+ } catch {
2561
+ }
2562
+ return "npm";
2563
+ }
2564
+ function registerUpgradeCommand(program2) {
2565
+ program2.command("upgrade").description("Upgrade Docyrus CLI to the latest version").action(async () => {
2566
+ const latestVersion = await withSpinner(
2567
+ "Checking for updates...",
2568
+ getLatestVersion,
2569
+ { silent: output.isJson() }
2570
+ );
2571
+ if (!latestVersion) {
2572
+ output.error("Failed to check for updates. Please try again later.");
2573
+ process.exit(1);
2574
+ }
2575
+ if (!isNewerVersion(CLI_VERSION, latestVersion)) {
2576
+ output.success(`You're already on the latest version (${CLI_VERSION})`);
2577
+ return;
2578
+ }
2579
+ if (!output.isJson()) {
2580
+ logger.info(`New version available: ${CLI_VERSION} \u2192 ${latestVersion}`);
2581
+ logger.newline();
2582
+ }
2583
+ const pm = await detectPackageManager();
2584
+ const commands = {
2585
+ npm: `npm install -g ${NPM_PACKAGE_NAME}@latest`,
2586
+ pnpm: `pnpm add -g ${NPM_PACKAGE_NAME}@latest`,
2587
+ yarn: `yarn global add ${NPM_PACKAGE_NAME}@latest`,
2588
+ bun: `bun add -g ${NPM_PACKAGE_NAME}@latest`
2589
+ };
2590
+ const command = commands[pm];
2591
+ try {
2592
+ await withSpinner(
2593
+ `Upgrading via ${pm}...`,
2594
+ async () => {
2595
+ await execAsync3(command);
2596
+ },
2597
+ {
2598
+ silent: output.isJson(),
2599
+ successText: "Upgrade completed!"
2600
+ }
2601
+ );
2602
+ output.success(`Successfully upgraded to v${latestVersion}`, {
2603
+ previousVersion: CLI_VERSION,
2604
+ newVersion: latestVersion,
2605
+ packageManager: pm
2606
+ });
2607
+ } catch (error) {
2608
+ const message = error instanceof Error ? error.message : "Unknown error";
2609
+ output.error(`Upgrade failed: ${message}`);
2610
+ if (!output.isJson()) {
2611
+ logger.dim(`Try manually: ${command}`);
2612
+ }
2613
+ process.exit(1);
2614
+ }
2615
+ });
2616
+ }
2617
+
2618
+ // src/commands/completion.ts
2619
+ var BASH_COMPLETION = `
2620
+ ###-begin-${CLI_NAME}-completions-###
2621
+ _${CLI_NAME}_completions() {
2622
+ local cur_word args type_list
2623
+ cur_word="\${COMP_WORDS[COMP_CWORD]}"
2624
+ args=("\${COMP_WORDS[@]}")
2625
+
2626
+ # Commands
2627
+ type_list="login logout whoami create generate upgrade completion info help"
2628
+
2629
+ # Subcommands for generate
2630
+ if [[ \${args[1]} == "generate" ]]; then
2631
+ type_list="db"
2632
+ fi
2633
+
2634
+ COMPREPLY=($(compgen -W "\${type_list}" -- \${cur_word}))
2635
+ return 0
2636
+ }
2637
+ complete -F _${CLI_NAME}_completions ${CLI_NAME}
2638
+ ###-end-${CLI_NAME}-completions-###
2639
+ `.trim();
2640
+ var ZSH_COMPLETION = `
2641
+ ###-begin-${CLI_NAME}-completions-###
2642
+ _${CLI_NAME}() {
2643
+ local -a commands
2644
+ commands=(
2645
+ 'login:Log in to Docyrus'
2646
+ 'logout:Log out from Docyrus'
2647
+ 'whoami:Display the current logged-in user'
2648
+ 'create:Create a new Docyrus project from template'
2649
+ 'generate:Code generation commands'
2650
+ 'upgrade:Upgrade Docyrus CLI to the latest version'
2651
+ 'completion:Generate shell completion script'
2652
+ 'info:Display CLI and environment information'
2653
+ 'help:Display help for command'
2654
+ )
2655
+
2656
+ local -a generate_commands
2657
+ generate_commands=(
2658
+ 'db:Generate TanStack Query collections from OpenAPI spec'
2659
+ )
2660
+
2661
+ _arguments -C \\
2662
+ '1: :->command' \\
2663
+ '*::arg:->args'
2664
+
2665
+ case "$state" in
2666
+ command)
2667
+ _describe 'command' commands
2668
+ ;;
2669
+ args)
2670
+ case $words[1] in
2671
+ generate)
2672
+ _describe 'subcommand' generate_commands
2673
+ ;;
2674
+ esac
2675
+ ;;
2676
+ esac
2677
+ }
2678
+ compdef _${CLI_NAME} ${CLI_NAME}
2679
+ ###-end-${CLI_NAME}-completions-###
2680
+ `.trim();
2681
+ var FISH_COMPLETION = `
2682
+ ###-begin-${CLI_NAME}-completions-###
2683
+ complete -c ${CLI_NAME} -f
2684
+
2685
+ # Commands
2686
+ complete -c ${CLI_NAME} -n "__fish_use_subcommand" -a "login" -d "Log in to Docyrus"
2687
+ complete -c ${CLI_NAME} -n "__fish_use_subcommand" -a "logout" -d "Log out from Docyrus"
2688
+ complete -c ${CLI_NAME} -n "__fish_use_subcommand" -a "whoami" -d "Display the current logged-in user"
2689
+ complete -c ${CLI_NAME} -n "__fish_use_subcommand" -a "create" -d "Create a new Docyrus project"
2690
+ complete -c ${CLI_NAME} -n "__fish_use_subcommand" -a "generate" -d "Code generation commands"
2691
+ complete -c ${CLI_NAME} -n "__fish_use_subcommand" -a "upgrade" -d "Upgrade Docyrus CLI"
2692
+ complete -c ${CLI_NAME} -n "__fish_use_subcommand" -a "completion" -d "Generate shell completion"
2693
+ complete -c ${CLI_NAME} -n "__fish_use_subcommand" -a "info" -d "Display CLI and environment information"
2694
+ complete -c ${CLI_NAME} -n "__fish_use_subcommand" -a "help" -d "Display help"
2695
+
2696
+ # generate subcommands
2697
+ complete -c ${CLI_NAME} -n "__fish_seen_subcommand_from generate" -a "db" -d "Generate TanStack Query collections"
2698
+ ###-end-${CLI_NAME}-completions-###
2699
+ `.trim();
2700
+ function registerCompletionCommand(program2) {
2701
+ program2.command("completion").description("Generate shell completion script").argument("<shell>", "Shell type: bash, zsh, or fish").action((shell) => {
2702
+ const shellLower = shell.toLowerCase();
2703
+ let script;
2704
+ let instructions;
2705
+ switch (shellLower) {
2706
+ case "bash":
2707
+ script = BASH_COMPLETION;
2708
+ instructions = `Add to ~/.bashrc:
2709
+ eval "$(${CLI_NAME} completion bash)"`;
2710
+ break;
2711
+ case "zsh":
2712
+ script = ZSH_COMPLETION;
2713
+ instructions = `Add to ~/.zshrc:
2714
+ eval "$(${CLI_NAME} completion zsh)"`;
2715
+ break;
2716
+ case "fish":
2717
+ script = FISH_COMPLETION;
2718
+ instructions = `Save to ~/.config/fish/completions/${CLI_NAME}.fish:
2719
+ ${CLI_NAME} completion fish > ~/.config/fish/completions/${CLI_NAME}.fish`;
2720
+ break;
2721
+ default:
2722
+ output.error(`Unknown shell: ${shell}`);
2723
+ if (!output.isJson()) {
2724
+ logger.dim("Supported shells: bash, zsh, fish");
2725
+ }
2726
+ process.exit(1);
2727
+ }
2728
+ if (output.isJson()) {
2729
+ output.set("shell", shellLower);
2730
+ output.set("script", script);
2731
+ output.set("instructions", instructions);
2732
+ } else {
2733
+ console.warn(script);
2734
+ logger.dim(instructions);
2735
+ }
2736
+ });
2737
+ }
2738
+ function registerInfoCommand(program2) {
2739
+ program2.command("info").description("Display CLI and environment information").action(async () => {
2740
+ const tokenManager = getTokenManager();
2741
+ const isLoggedIn = await tokenManager.isLoggedIn();
2742
+ const email = isLoggedIn ? await tokenManager.getUserEmail() : void 0;
2743
+ const info = {
2744
+ cli: {
2745
+ name: CLI_NAME,
2746
+ version: CLI_VERSION
2747
+ },
2748
+ node: {
2749
+ version: process.version
2750
+ },
2751
+ os: {
2752
+ platform: platform(),
2753
+ release: release(),
2754
+ arch: arch()
2755
+ },
2756
+ environment: {
2757
+ apiUrl: DOCYRUS_API_URL,
2758
+ configDir: join(homedir(), ".docyrus")
2759
+ },
2760
+ auth: {
2761
+ loggedIn: isLoggedIn,
2762
+ ...email && { email }
2763
+ }
2764
+ };
2765
+ if (output.isJson()) {
2766
+ output.set("info", info);
2767
+ } else {
2768
+ logger.bold(`${CLI_NAME} v${CLI_VERSION}`);
2769
+ logger.newline();
2770
+ logger.log("System:");
2771
+ logger.dim(` Node.js: ${info.node.version}`);
2772
+ logger.dim(` OS: ${info.os.platform} ${info.os.release} (${info.os.arch})`);
2773
+ logger.newline();
2774
+ logger.log("Environment:");
2775
+ logger.dim(` API URL: ${info.environment.apiUrl}`);
2776
+ logger.dim(` Config: ${info.environment.configDir}`);
2777
+ logger.newline();
2778
+ logger.log("Authentication:");
2779
+ if (info.auth.loggedIn) {
2780
+ logger.dim(` Status: Logged in`);
2781
+ if (info.auth.email) {
2782
+ logger.dim(` Email: ${info.auth.email}`);
2783
+ }
2784
+ } else {
2785
+ logger.dim(` Status: Not logged in`);
2786
+ }
2787
+ }
2788
+ });
2789
+ }
2790
+ function registerConfigCommand(program2) {
2791
+ program2.command("config").description("Manage CLI configuration").option("--token", "Update Docyrus token for private template access").option("--show", "Show current configuration").action(async (options) => {
2792
+ const tokenManager = getTokenManager();
2793
+ if (options.show) {
2794
+ const githubToken2 = await tokenManager.getGithubToken();
2795
+ const isLoggedIn2 = await tokenManager.isLoggedIn();
2796
+ const email2 = await tokenManager.getUserEmail();
2797
+ logger.log("Current configuration:");
2798
+ logger.newline();
2799
+ logger.log(` Docyrus Login: ${isLoggedIn2 ? `\u2713 ${email2}` : "\u2717 Not logged in"}`);
2800
+ logger.log(` Docyrus Token: ${githubToken2 ? "\u2713 Configured" : "\u2717 Not configured"}`);
2801
+ logger.newline();
2802
+ return;
2803
+ }
2804
+ if (options.token) {
2805
+ const currentToken = await tokenManager.getGithubToken();
2806
+ if (currentToken) {
2807
+ logger.log("Current Docyrus token will be replaced.");
2808
+ }
2809
+ const newToken = await password({
2810
+ message: MESSAGES.DOCYRUS_TOKEN_PROMPT,
2811
+ mask: "*"
2812
+ });
2813
+ if (newToken) {
2814
+ await tokenManager.setGithubToken(newToken);
2815
+ logger.success(MESSAGES.DOCYRUS_TOKEN_SAVED);
2816
+ } else {
2817
+ logger.warn("No token provided. Token not updated.");
2818
+ }
2819
+ return;
2820
+ }
2821
+ const githubToken = await tokenManager.getGithubToken();
2822
+ const isLoggedIn = await tokenManager.isLoggedIn();
2823
+ const email = await tokenManager.getUserEmail();
2824
+ logger.log("Current configuration:");
2825
+ logger.newline();
2826
+ logger.log(` Docyrus Login: ${isLoggedIn ? `\u2713 ${email}` : "\u2717 Not logged in"}`);
2827
+ logger.log(` Docyrus Token: ${githubToken ? "\u2713 Configured" : "\u2717 Not configured"}`);
2828
+ logger.newline();
2829
+ logger.log("To update token: docyrus config --token");
2830
+ });
2831
+ }
2832
+
2833
+ // src/commands/index.ts
2834
+ function registerCommands(program2) {
2835
+ registerLoginCommand(program2);
2836
+ registerLogoutCommand(program2);
2837
+ registerWhoamiCommand(program2);
2838
+ registerCreateCommand(program2);
2839
+ registerGenerateCommand(program2);
2840
+ registerUpgradeCommand(program2);
2841
+ registerCompletionCommand(program2);
2842
+ registerInfoCommand(program2);
2843
+ registerConfigCommand(program2);
2844
+ }
2845
+ function isNewerVersion2(current, latest) {
2846
+ const currentParts = current.replace(/^v/, "").split(".").map(Number);
2847
+ const latestParts = latest.replace(/^v/, "").split(".").map(Number);
2848
+ for (let i = 0; i < 3; i++) {
2849
+ const curr = currentParts[i] || 0;
2850
+ const lat = latestParts[i] || 0;
2851
+ if (lat > curr) return true;
2852
+ if (lat < curr) return false;
2853
+ }
2854
+ return false;
2855
+ }
2856
+ async function checkForUpdates(packageName, currentVersion) {
2857
+ try {
2858
+ const controller = new AbortController();
2859
+ const timeout = setTimeout(() => controller.abort(), 3e3);
2860
+ const response = await fetch(`https://registry.npmjs.org/${packageName}`, {
2861
+ signal: controller.signal,
2862
+ headers: { Accept: "application/json" }
2863
+ });
2864
+ clearTimeout(timeout);
2865
+ if (!response.ok) return;
2866
+ const data = await response.json();
2867
+ const latestVersion = data["dist-tags"]?.latest;
2868
+ if (latestVersion && isNewerVersion2(currentVersion, latestVersion)) {
2869
+ console.info();
2870
+ console.info(chalk4.yellow("\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
2871
+ console.info(chalk4.yellow("\u2502") + chalk4.bold(" Update available! ") + chalk4.dim(`${currentVersion} \u2192 `) + chalk4.green(latestVersion) + chalk4.yellow(" \u2502"));
2872
+ console.info(chalk4.yellow("\u2502") + chalk4.dim(` Run `) + chalk4.cyan(`npm i -g ${packageName}`) + chalk4.dim(" to update") + chalk4.yellow(" \u2502"));
2873
+ console.info(chalk4.yellow("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
2874
+ console.info();
2875
+ }
2876
+ } catch {
2877
+ }
2878
+ }
2879
+
2880
+ // src/cli.ts
2881
+ var program = new Command();
2882
+ program.name(CLI_NAME).description("Docyrus CLI - Authentication and project management tools").version(CLI_VERSION, "-v, --version", "Display version number").option("--json", "Output results in JSON format").hook("preAction", (thisCommand) => {
2883
+ const opts = thisCommand.opts();
2884
+ if (opts.json) {
2885
+ output.setFormat("json");
2886
+ }
2887
+ }).hook("postAction", () => {
2888
+ output.flush();
2889
+ });
2890
+ registerCommands(program);
2891
+ program.configureOutput({
2892
+ outputError: (str, write) => write(str)
2893
+ });
2894
+ async function main() {
2895
+ try {
2896
+ if (!process.argv.includes("--json")) {
2897
+ checkForUpdates(NPM_PACKAGE_NAME, CLI_VERSION).catch(() => {
2898
+ });
2899
+ }
2900
+ await program.parseAsync(process.argv);
2901
+ } catch (error) {
2902
+ if (error instanceof CliError) {
2903
+ output.error(error.message);
2904
+ if (error.suggestion && !output.isJson()) {
2905
+ logger.dim(error.suggestion);
2906
+ }
2907
+ output.flush();
2908
+ process.exit(error.exitCode);
2909
+ }
2910
+ if (error instanceof Error) {
2911
+ output.error(error.message);
2912
+ if (process.env.DEBUG) {
2913
+ console.error(error.stack);
2914
+ }
2915
+ output.flush();
2916
+ process.exit(1);
2917
+ }
2918
+ output.error("An unexpected error occurred.");
2919
+ output.flush();
2920
+ process.exit(1);
2921
+ }
2922
+ }
2923
+ main();
2924
+ //# sourceMappingURL=cli.js.map
2925
+ //# sourceMappingURL=cli.js.map