@agility/create-next-app 1.0.0-beta.2

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.
Files changed (213) hide show
  1. package/.claude/settings.json +7 -0
  2. package/.claude/settings.local.json +24 -0
  3. package/FEATURE_ROADMAP.md +343 -0
  4. package/README.md +205 -0
  5. package/TESTING.md +131 -0
  6. package/bin/create-agility-app.js +48 -0
  7. package/dist/agility/api-keys/generateApiKeys.d.ts +9 -0
  8. package/dist/agility/api-keys/generateApiKeys.d.ts.map +1 -0
  9. package/dist/agility/api-keys/generateApiKeys.js +99 -0
  10. package/dist/agility/api-keys/generateApiKeys.js.map +1 -0
  11. package/dist/agility/api-keys/getApiKeys.d.ts +9 -0
  12. package/dist/agility/api-keys/getApiKeys.d.ts.map +1 -0
  13. package/dist/agility/api-keys/getApiKeys.js +14 -0
  14. package/dist/agility/api-keys/getApiKeys.js.map +1 -0
  15. package/dist/agility/index.d.ts +3 -0
  16. package/dist/agility/index.d.ts.map +1 -0
  17. package/dist/agility/index.js +8 -0
  18. package/dist/agility/index.js.map +1 -0
  19. package/dist/agility/instance/createNewInstance.d.ts +8 -0
  20. package/dist/agility/instance/createNewInstance.d.ts.map +1 -0
  21. package/dist/agility/instance/createNewInstance.js +65 -0
  22. package/dist/agility/instance/createNewInstance.js.map +1 -0
  23. package/dist/agility/instance/getAvailableInstances.d.ts +8 -0
  24. package/dist/agility/instance/getAvailableInstances.d.ts.map +1 -0
  25. package/dist/agility/instance/getAvailableInstances.js +43 -0
  26. package/dist/agility/instance/getAvailableInstances.js.map +1 -0
  27. package/dist/agility/instance/manageInstance.d.ts +9 -0
  28. package/dist/agility/instance/manageInstance.d.ts.map +1 -0
  29. package/dist/agility/instance/manageInstance.js +82 -0
  30. package/dist/agility/instance/manageInstance.js.map +1 -0
  31. package/dist/agility/utils/getMgmtAPIUrl.d.ts +20 -0
  32. package/dist/agility/utils/getMgmtAPIUrl.d.ts.map +1 -0
  33. package/dist/agility/utils/getMgmtAPIUrl.js +61 -0
  34. package/dist/agility/utils/getMgmtAPIUrl.js.map +1 -0
  35. package/dist/auth/api-key/authenticateWithApiKey.d.ts +6 -0
  36. package/dist/auth/api-key/authenticateWithApiKey.d.ts.map +1 -0
  37. package/dist/auth/api-key/authenticateWithApiKey.js +28 -0
  38. package/dist/auth/api-key/authenticateWithApiKey.js.map +1 -0
  39. package/dist/auth/index.d.ts +3 -0
  40. package/dist/auth/index.d.ts.map +1 -0
  41. package/dist/auth/index.js +8 -0
  42. package/dist/auth/index.js.map +1 -0
  43. package/dist/auth/oauth/authenticate.d.ts +6 -0
  44. package/dist/auth/oauth/authenticate.d.ts.map +1 -0
  45. package/dist/auth/oauth/authenticate.js +162 -0
  46. package/dist/auth/oauth/authenticate.js.map +1 -0
  47. package/dist/auth/oauth/constants.d.ts +5 -0
  48. package/dist/auth/oauth/constants.d.ts.map +1 -0
  49. package/dist/auth/oauth/constants.js +9 -0
  50. package/dist/auth/oauth/constants.js.map +1 -0
  51. package/dist/auth/oauth/exchangeCodeForToken.d.ts +7 -0
  52. package/dist/auth/oauth/exchangeCodeForToken.d.ts.map +1 -0
  53. package/dist/auth/oauth/exchangeCodeForToken.js +39 -0
  54. package/dist/auth/oauth/exchangeCodeForToken.js.map +1 -0
  55. package/dist/cli/index.d.ts +3 -0
  56. package/dist/cli/index.d.ts.map +1 -0
  57. package/dist/cli/index.js +290 -0
  58. package/dist/cli/index.js.map +1 -0
  59. package/dist/cli/promptForMissingOptions.d.ts +8 -0
  60. package/dist/cli/promptForMissingOptions.d.ts.map +1 -0
  61. package/dist/cli/promptForMissingOptions.js +92 -0
  62. package/dist/cli/promptForMissingOptions.js.map +1 -0
  63. package/dist/config/env/createEnvFile.d.ts +6 -0
  64. package/dist/config/env/createEnvFile.d.ts.map +1 -0
  65. package/dist/config/env/createEnvFile.js +31 -0
  66. package/dist/config/env/createEnvFile.js.map +1 -0
  67. package/dist/config/index.d.ts +2 -0
  68. package/dist/config/index.d.ts.map +1 -0
  69. package/dist/config/index.js +6 -0
  70. package/dist/config/index.js.map +1 -0
  71. package/dist/config/mcp/createMcpConfig.d.ts +5 -0
  72. package/dist/config/mcp/createMcpConfig.d.ts.map +1 -0
  73. package/dist/config/mcp/createMcpConfig.js +32 -0
  74. package/dist/config/mcp/createMcpConfig.js.map +1 -0
  75. package/dist/config/packages/installAgilityPackages.d.ts +6 -0
  76. package/dist/config/packages/installAgilityPackages.d.ts.map +1 -0
  77. package/dist/config/packages/installAgilityPackages.js +61 -0
  78. package/dist/config/packages/installAgilityPackages.js.map +1 -0
  79. package/dist/config/setupProject.d.ts +8 -0
  80. package/dist/config/setupProject.d.ts.map +1 -0
  81. package/dist/config/setupProject.js +32 -0
  82. package/dist/config/setupProject.js.map +1 -0
  83. package/dist/create-next-app/createNextApp.d.ts +9 -0
  84. package/dist/create-next-app/createNextApp.d.ts.map +1 -0
  85. package/dist/create-next-app/createNextApp.js +83 -0
  86. package/dist/create-next-app/createNextApp.js.map +1 -0
  87. package/dist/create-next-app/index.d.ts +3 -0
  88. package/dist/create-next-app/index.d.ts.map +1 -0
  89. package/dist/create-next-app/index.js +8 -0
  90. package/dist/create-next-app/index.js.map +1 -0
  91. package/dist/scaffold/components/createPageComponents.d.ts +6 -0
  92. package/dist/scaffold/components/createPageComponents.d.ts.map +1 -0
  93. package/dist/scaffold/components/createPageComponents.js +62 -0
  94. package/dist/scaffold/components/createPageComponents.js.map +1 -0
  95. package/dist/scaffold/containers/createContainers.d.ts +6 -0
  96. package/dist/scaffold/containers/createContainers.d.ts.map +1 -0
  97. package/dist/scaffold/containers/createContainers.js +48 -0
  98. package/dist/scaffold/containers/createContainers.js.map +1 -0
  99. package/dist/scaffold/index.d.ts +2 -0
  100. package/dist/scaffold/index.d.ts.map +1 -0
  101. package/dist/scaffold/index.js +6 -0
  102. package/dist/scaffold/index.js.map +1 -0
  103. package/dist/scaffold/instance/createBlankInstance.d.ts +8 -0
  104. package/dist/scaffold/instance/createBlankInstance.d.ts.map +1 -0
  105. package/dist/scaffold/instance/createBlankInstance.js +51 -0
  106. package/dist/scaffold/instance/createBlankInstance.js.map +1 -0
  107. package/dist/scaffold/models/createContentModels.d.ts +6 -0
  108. package/dist/scaffold/models/createContentModels.d.ts.map +1 -0
  109. package/dist/scaffold/models/createContentModels.js +70 -0
  110. package/dist/scaffold/models/createContentModels.js.map +1 -0
  111. package/dist/templates/copyDirectory.d.ts +5 -0
  112. package/dist/templates/copyDirectory.d.ts.map +1 -0
  113. package/dist/templates/copyDirectory.js +28 -0
  114. package/dist/templates/copyDirectory.js.map +1 -0
  115. package/dist/templates/copyTemplates.d.ts +8 -0
  116. package/dist/templates/copyTemplates.d.ts.map +1 -0
  117. package/dist/templates/copyTemplates.js +58 -0
  118. package/dist/templates/copyTemplates.js.map +1 -0
  119. package/dist/templates/index.d.ts +2 -0
  120. package/dist/templates/index.d.ts.map +1 -0
  121. package/dist/templates/index.js +6 -0
  122. package/dist/templates/index.js.map +1 -0
  123. package/dist/types/index.d.ts +50 -0
  124. package/dist/types/index.d.ts.map +1 -0
  125. package/dist/types/index.js +3 -0
  126. package/dist/types/index.js.map +1 -0
  127. package/dist/utils/git.d.ts +9 -0
  128. package/dist/utils/git.d.ts.map +1 -0
  129. package/dist/utils/git.js +71 -0
  130. package/dist/utils/git.js.map +1 -0
  131. package/dist/utils/validation.d.ts +45 -0
  132. package/dist/utils/validation.d.ts.map +1 -0
  133. package/dist/utils/validation.js +180 -0
  134. package/dist/utils/validation.js.map +1 -0
  135. package/package.json +45 -0
  136. package/src/agility/api-keys/generateApiKeys.ts +100 -0
  137. package/src/agility/api-keys/getApiKeys.ts +13 -0
  138. package/src/agility/index.ts +3 -0
  139. package/src/agility/instance/createNewInstance.ts +67 -0
  140. package/src/agility/instance/getAvailableInstances.ts +49 -0
  141. package/src/agility/instance/manageInstance.ts +90 -0
  142. package/src/agility/utils/getMgmtAPIUrl.ts +68 -0
  143. package/src/auth/api-key/authenticateWithApiKey.ts +24 -0
  144. package/src/auth/index.ts +3 -0
  145. package/src/auth/oauth/authenticate.ts +165 -0
  146. package/src/auth/oauth/constants.ts +6 -0
  147. package/src/auth/oauth/exchangeCodeForToken.ts +43 -0
  148. package/src/cli/index.ts +281 -0
  149. package/src/cli/promptForMissingOptions.ts +104 -0
  150. package/src/config/env/createEnvFile.ts +30 -0
  151. package/src/config/index.ts +2 -0
  152. package/src/config/mcp/createMcpConfig.ts +30 -0
  153. package/src/config/packages/installAgilityPackages.ts +63 -0
  154. package/src/config/setupProject.ts +31 -0
  155. package/src/create-next-app/createNextApp.ts +75 -0
  156. package/src/create-next-app/index.ts +3 -0
  157. package/src/scaffold/components/createPageComponents.ts +74 -0
  158. package/src/scaffold/containers/createContainers.ts +55 -0
  159. package/src/scaffold/index.ts +2 -0
  160. package/src/scaffold/instance/createBlankInstance.ts +55 -0
  161. package/src/scaffold/models/createContentModels.ts +83 -0
  162. package/src/templates/copyDirectory.ts +24 -0
  163. package/src/templates/copyTemplates.ts +57 -0
  164. package/src/templates/index.ts +2 -0
  165. package/src/types/index.ts +55 -0
  166. package/src/utils/git.ts +74 -0
  167. package/src/utils/validation.ts +184 -0
  168. package/templates/.claude/QUICK-START.md +230 -0
  169. package/templates/.claude/README.md +32 -0
  170. package/templates/.claude/settings.json +8 -0
  171. package/templates/BLANK-INSTANCE-SETUP.md +375 -0
  172. package/templates/DEVELOPMENT.md +160 -0
  173. package/templates/EXAMPLE-PROMPTS.md +643 -0
  174. package/templates/PROMPTS.md +410 -0
  175. package/templates/README.md +281 -0
  176. package/templates/agents.md +429 -0
  177. package/templates/app/[locale]/[...slug]/error.tsx +17 -0
  178. package/templates/app/[locale]/[...slug]/not-found.tsx +9 -0
  179. package/templates/app/[locale]/[...slug]/page.tsx +102 -0
  180. package/templates/app/[locale]/layout.tsx +22 -0
  181. package/templates/app/[locale]/page.tsx +12 -0
  182. package/templates/app/api/dynamic-redirect/route.ts +24 -0
  183. package/templates/app/api/preview/exit/route.ts +34 -0
  184. package/templates/app/api/preview/route.ts +63 -0
  185. package/templates/app/api/revalidate/route.ts +118 -0
  186. package/templates/components/agility-components/RichTextArea.tsx +66 -0
  187. package/templates/components/agility-components/index.ts +30 -0
  188. package/templates/components/agility-pages/MainTemplate.tsx +36 -0
  189. package/templates/components/agility-pages/index.ts +11 -0
  190. package/templates/docs/01-agility-cms-overview.md +139 -0
  191. package/templates/docs/02-page-routing.md +251 -0
  192. package/templates/docs/03-creating-components.md +462 -0
  193. package/templates/docs/04-data-fetching.md +484 -0
  194. package/templates/docs/05-containers-and-lists.md +596 -0
  195. package/templates/docs/06-localization.md +561 -0
  196. package/templates/docs/07-caching-strategies.md +410 -0
  197. package/templates/docs/08-common-components.md +756 -0
  198. package/templates/docs/09-whats-included.md +279 -0
  199. package/templates/docs/10-mcp-server-setup.md +153 -0
  200. package/templates/docs/11-linked-nested-content.md +611 -0
  201. package/templates/docs/README.md +164 -0
  202. package/templates/lib/cms/getAgilityContext.ts +28 -0
  203. package/templates/lib/cms/getAgilityPage.ts +51 -0
  204. package/templates/lib/cms/getAgilitySDK.ts +22 -0
  205. package/templates/lib/cms/getContentItem.ts +20 -0
  206. package/templates/lib/cms/getContentList.ts +19 -0
  207. package/templates/lib/cms/getRedirections.ts +85 -0
  208. package/templates/lib/cms/getSitemapFlat.ts +19 -0
  209. package/templates/lib/cms/getSitemapNested.ts +19 -0
  210. package/templates/lib/env.ts +99 -0
  211. package/templates/lib/i18n/config.ts +28 -0
  212. package/templates/proxy.ts +101 -0
  213. package/tsconfig.json +21 -0
@@ -0,0 +1,165 @@
1
+ import http from 'http';
2
+ import open from 'open';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import inquirer from 'inquirer';
6
+ import { AGILITY_OAUTH_AUTHORIZE_URL, REDIRECT_URI, PORT } from './constants';
7
+ import { exchangeCodeForToken } from './exchangeCodeForToken';
8
+
9
+ /**
10
+ * Authenticates user with Agility CMS using OAuth 2.0
11
+ * @returns Access token
12
+ */
13
+ export async function authenticate(): Promise<string> {
14
+ return new Promise(async (resolve, reject) => {
15
+ // Build OAuth URL - Agility CMS OAuth doesn't require client_id
16
+ const authUrl = new URL(AGILITY_OAUTH_AUTHORIZE_URL);
17
+ authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
18
+ authUrl.searchParams.set('response_type', 'code');
19
+ authUrl.searchParams.set('scope', 'openid profile email offline_access');
20
+
21
+ // Ask user for confirmation before opening browser
22
+ try {
23
+ const answer = await inquirer.prompt<{ proceed: boolean }>([
24
+ {
25
+ type: 'confirm',
26
+ name: 'proceed',
27
+ message: 'We need to open a browser window for authentication. Proceed?',
28
+ default: true
29
+ }
30
+ ]);
31
+
32
+ if (!answer.proceed) {
33
+ reject(new Error('Authentication cancelled by user'));
34
+ return;
35
+ }
36
+ } catch (error) {
37
+ reject(new Error('Failed to get user confirmation'));
38
+ return;
39
+ }
40
+
41
+ const spinner = ora('Opening browser for authentication...').start();
42
+
43
+ let timeoutId: NodeJS.Timeout | null = null;
44
+ let isResolved = false;
45
+
46
+ const cleanup = () => {
47
+ if (timeoutId) {
48
+ clearTimeout(timeoutId);
49
+ timeoutId = null;
50
+ }
51
+ server.closeAllConnections();
52
+ server.close(() => {
53
+ // Server closed, allow process to exit
54
+ });
55
+ };
56
+
57
+ // Start local server to receive callback
58
+ const server = http.createServer((req, res) => {
59
+ if (req.url?.startsWith('/callback')) {
60
+ const url = new URL(req.url, `http://localhost:${PORT}`);
61
+ const code = url.searchParams.get('code');
62
+ const error = url.searchParams.get('error');
63
+
64
+ if (error) {
65
+ res.writeHead(400, { 'Content-Type': 'text/html' });
66
+ res.end(`
67
+ <html>
68
+ <body>
69
+ <h1>Authentication Failed</h1>
70
+ <p>${error}</p>
71
+ <p>You can close this window.</p>
72
+ </body>
73
+ </html>
74
+ `);
75
+ if (!isResolved) {
76
+ isResolved = true;
77
+ cleanup();
78
+ spinner.fail('Authentication failed');
79
+ reject(new Error(`OAuth error: ${error}`));
80
+ }
81
+ return;
82
+ }
83
+
84
+ if (code) {
85
+ res.writeHead(200, { 'Content-Type': 'text/html' });
86
+ res.end(`
87
+ <html>
88
+ <body>
89
+ <h1>Authentication Successful!</h1>
90
+ <p>You can close this window and return to the terminal.</p>
91
+ </body>
92
+ </html>
93
+ `);
94
+ if (!isResolved) {
95
+ isResolved = true;
96
+ cleanup();
97
+
98
+ // Exchange code for token
99
+ spinner.text = 'Exchanging authorization code for token...';
100
+ exchangeCodeForToken(code)
101
+ .then((token) => {
102
+ spinner.succeed('Authentication successful');
103
+ resolve(token);
104
+ })
105
+ .catch((err) => {
106
+ spinner.fail('Failed to exchange code for token');
107
+ const errorMessage = err instanceof Error ? err.message : String(err);
108
+ console.error(chalk.red('\n❌ Token exchange error:'));
109
+ console.error(chalk.red(` ${errorMessage}`));
110
+ console.error(chalk.gray(' You can skip authentication and configure Agility CMS manually.\n'));
111
+ reject(err);
112
+ });
113
+ }
114
+ } else {
115
+ res.writeHead(400, { 'Content-Type': 'text/html' });
116
+ res.end(`
117
+ <html>
118
+ <body>
119
+ <h1>Authentication Failed</h1>
120
+ <p>No authorization code received.</p>
121
+ <p>You can close this window.</p>
122
+ </body>
123
+ </html>
124
+ `);
125
+ if (!isResolved) {
126
+ isResolved = true;
127
+ cleanup();
128
+ spinner.fail('Authentication failed');
129
+ reject(new Error('No authorization code received'));
130
+ }
131
+ }
132
+ }
133
+ });
134
+
135
+ server.listen(PORT, async () => {
136
+ try {
137
+ // Open browser
138
+ await open(authUrl.toString());
139
+ spinner.text = 'Waiting for authentication in browser...';
140
+ console.log(chalk.cyan('\nA browser window should open for authentication.'));
141
+ console.log(chalk.gray('If it doesn\'t, please open this URL manually:'));
142
+ console.log(chalk.blue(authUrl.toString()) + '\n');
143
+ } catch (err) {
144
+ if (!isResolved) {
145
+ isResolved = true;
146
+ cleanup();
147
+ spinner.fail('Failed to open browser');
148
+ console.log(chalk.yellow(`\nPlease open this URL in your browser: ${authUrl.toString()}\n`));
149
+ reject(err);
150
+ }
151
+ }
152
+ });
153
+
154
+ // Timeout after 5 minutes
155
+ timeoutId = setTimeout(() => {
156
+ if (!isResolved) {
157
+ isResolved = true;
158
+ cleanup();
159
+ spinner.fail('Authentication timeout');
160
+ reject(new Error('Authentication timeout - please try again'));
161
+ }
162
+ }, 5 * 60 * 1000);
163
+ });
164
+ }
165
+
@@ -0,0 +1,6 @@
1
+ // OAuth configuration for Agility CMS
2
+ export const AGILITY_OAUTH_AUTHORIZE_URL = 'https://mgmt.aglty.io/oauth/authorize';
3
+ export const AGILITY_OAUTH_TOKEN_URL = 'https://mgmt.aglty.io/oauth/token';
4
+ export const REDIRECT_URI = 'http://localhost:3001/callback';
5
+ export const PORT = 3001;
6
+
@@ -0,0 +1,43 @@
1
+ import { AGILITY_OAUTH_TOKEN_URL, REDIRECT_URI } from './constants';
2
+
3
+ /**
4
+ * Exchanges authorization code for access token
5
+ * @param code - Authorization code
6
+ * @returns Access token
7
+ */
8
+ export async function exchangeCodeForToken(code: string): Promise<string> {
9
+ // Exchange authorization code for access token using Agility CMS OAuth token endpoint
10
+ // Agility CMS OAuth doesn't require client_id or client_secret
11
+
12
+ // Use Node.js built-in fetch (Node 18+)
13
+ if (typeof globalThis.fetch !== 'function') {
14
+ throw new Error('OAuth token exchange requires Node.js 18+ (with built-in fetch).');
15
+ }
16
+
17
+ // Make token exchange request to Agility CMS OAuth token endpoint
18
+ const response = await fetch(AGILITY_OAUTH_TOKEN_URL, {
19
+ method: 'POST',
20
+ headers: {
21
+ 'Content-Type': 'application/x-www-form-urlencoded',
22
+ },
23
+ body: new URLSearchParams({
24
+ grant_type: 'authorization_code',
25
+ code: code,
26
+ redirect_uri: REDIRECT_URI,
27
+ }).toString(),
28
+ });
29
+
30
+ if (!response.ok) {
31
+ const errorText = await response.text();
32
+ throw new Error(`Token exchange failed: ${response.status} ${response.statusText} - ${errorText}`);
33
+ }
34
+
35
+ const data = await response.json() as { access_token?: string; refresh_token?: string; expires_in?: number };
36
+
37
+ if (!data.access_token) {
38
+ throw new Error('No access token received from OAuth server');
39
+ }
40
+
41
+ return data.access_token;
42
+ }
43
+
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import inquirer from 'inquirer';
6
+ import open from 'open';
7
+ import boxen from 'boxen';
8
+ import updateNotifier from 'update-notifier';
9
+ import path from 'path';
10
+ import { createNextApp } from '../create-next-app';
11
+ import * as auth from '../auth';
12
+ import * as agility from '../agility';
13
+ import * as config from '../config';
14
+ import * as templates from '../templates';
15
+ import type { CliOptions } from '../types';
16
+ import { promptForMissingOptions } from './promptForMissingOptions';
17
+ import {
18
+ validateProjectName,
19
+ validateProjectPath,
20
+ validateNodeVersion,
21
+ displayValidationError,
22
+ isGitInstalled,
23
+ } from '../utils/validation';
24
+ import { initializeGit, isGitAvailable } from '../utils/git';
25
+
26
+ // Check for updates
27
+ const pkg = require('../../package.json');
28
+ updateNotifier({ pkg }).notify({
29
+ isGlobal: true,
30
+ message: 'Update available: {currentVersion} → {latestVersion}\nRun {updateCommand} to update',
31
+ });
32
+
33
+ /**
34
+ * Handle errors with helpful recovery suggestions
35
+ */
36
+ function handleError(error: Error): void {
37
+ console.error('\n' + chalk.red.bold('✖ Error occurred:'));
38
+ console.error(chalk.red(error.message) + '\n');
39
+
40
+ // Provide specific recovery suggestions based on error type
41
+ if (error.message.includes('EACCES') || error.message.includes('permission')) {
42
+ console.error(chalk.yellow('Possible solutions:'));
43
+ console.error(chalk.white(' • Check directory permissions'));
44
+ console.error(chalk.white(' • Try running without sudo (not recommended)'));
45
+ console.error(chalk.white(' • Choose a different directory\n'));
46
+ } else if (error.message.includes('ENOSPC')) {
47
+ console.error(chalk.yellow('Possible solutions:'));
48
+ console.error(chalk.white(' • Free up disk space'));
49
+ console.error(chalk.white(' • Choose a different disk/directory\n'));
50
+ } else if (error.message.includes('ENOTFOUND') || error.message.includes('network')) {
51
+ console.error(chalk.yellow('Possible solutions:'));
52
+ console.error(chalk.white(' • Check your internet connection'));
53
+ console.error(chalk.white(' • Try again in a few moments'));
54
+ console.error(chalk.white(' • Check if you are behind a proxy\n'));
55
+ } else if (error.message.includes('authentication') || error.message.includes('OAuth')) {
56
+ console.error(chalk.yellow('Possible solutions:'));
57
+ console.error(chalk.white(' • Choose "manual setup" option'));
58
+ console.error(chalk.white(' • Configure .env.local manually after project creation'));
59
+ console.error(chalk.white(' • Get API keys from: https://manager.agilitycms.com\n'));
60
+ } else {
61
+ console.error(chalk.yellow('Need help?'));
62
+ console.error(chalk.white(' • Check the documentation: https://github.com/agility/create-next-agility-app'));
63
+ console.error(chalk.white(' • Open an issue: https://github.com/agility/create-next-agility-app/issues\n'));
64
+ }
65
+
66
+ if (process.env.DEBUG) {
67
+ console.error(chalk.gray('Stack trace:'));
68
+ console.error(chalk.gray(error.stack || 'No stack trace available'));
69
+ } else {
70
+ console.error(chalk.gray('Run with DEBUG=1 for detailed error information\n'));
71
+ }
72
+
73
+ process.exit(1);
74
+ }
75
+
76
+ /**
77
+ * Display success message with next steps
78
+ */
79
+ function displaySuccessMessage(
80
+ projectName: string,
81
+ instanceGuid: string | undefined,
82
+ apiKeys: { fetchKey: string; previewKey: string; securityKey: string } | null
83
+ ): void {
84
+ const hasAgilityConfig = Boolean(instanceGuid && apiKeys);
85
+
86
+ const message =
87
+ chalk.green.bold('✓ Success!') +
88
+ chalk.white(` Your project is ready at: ${chalk.cyan(projectName)}\n\n`) +
89
+ chalk.cyan.bold('Next steps:\n') +
90
+ chalk.white(` 1. ${chalk.bold(`cd ${projectName}`)}\n`) +
91
+ chalk.white(` 2. ${chalk.bold('npm run dev')}\n`) +
92
+ chalk.white(` 3. Open ${chalk.cyan('http://localhost:3000')}\n\n`) +
93
+ chalk.cyan.bold('Documentation:\n') +
94
+ chalk.white(` • ${chalk.bold('.claude/agents.md')} - AI assistant guide\n`) +
95
+ chalk.white(` • ${chalk.bold('docs/')} - Full documentation\n`) +
96
+ chalk.white(` • ${chalk.bold('README.md')} - Getting started\n\n`) +
97
+ (hasAgilityConfig
98
+ ? chalk.green('✓ Agility CMS is configured and ready!\n\n')
99
+ : chalk.yellow('⚠ Configure Agility CMS:\n') +
100
+ chalk.white(` Edit ${chalk.bold('.env.local')} with your credentials\n`) +
101
+ chalk.gray(' Get your API keys from: https://manager.agilitycms.com\n\n')) +
102
+ chalk.cyan.bold('Get help:\n') +
103
+ chalk.white(` • ${chalk.underline('https://agilitycms.com/docs')}\n`) +
104
+ chalk.white(` • ${chalk.underline('https://github.com/agility/create-next-agility-app')}`);
105
+
106
+ console.log('\n' + boxen(message, {
107
+ padding: 1,
108
+ margin: 1,
109
+ borderStyle: 'round',
110
+ borderColor: 'green',
111
+ }) + '\n');
112
+ }
113
+
114
+ const program = new Command();
115
+
116
+ program
117
+ .name('create-next-agility-app')
118
+ .description('Create a new Next.js project with Agility CMS integration')
119
+ .version('1.0.0')
120
+ .argument('[project-name]', 'Name of the project')
121
+ .option('--typescript, --ts', 'Initialize as a TypeScript project')
122
+ .option('--javascript, --js', 'Initialize as a JavaScript project')
123
+ .option('--tailwind', 'Initialize with Tailwind CSS')
124
+ .option('--no-tailwind', 'Initialize without Tailwind CSS')
125
+ .option('--eslint', 'Initialize with ESLint')
126
+ .option('--no-eslint', 'Initialize without ESLint')
127
+ .option('--app', 'Initialize with App Router')
128
+ .option('--src-dir', 'Initialize with src/ directory')
129
+ .option('--import-alias <alias>', 'Specify import alias (default: @/*)')
130
+ .option('--use-npm', 'Use npm as package manager')
131
+ .option('--use-pnpm', 'Use pnpm as package manager')
132
+ .option('--use-yarn', 'Use yarn as package manager')
133
+ .option('--use-bun', 'Use bun as package manager')
134
+ .option('--skip-install', 'Skip package installation')
135
+ .action(async (projectName: string | undefined, options: CliOptions) => {
136
+ try {
137
+ console.log(chalk.blue.bold('\n🚀 Creating Next.js project with Agility CMS...\n'));
138
+
139
+ // Validate Node.js version
140
+ const nodeValidation = validateNodeVersion();
141
+ if (!nodeValidation.valid) {
142
+ displayValidationError(nodeValidation.message!);
143
+ process.exit(1);
144
+ }
145
+
146
+ // Step 1: Agility CMS setup (ask questions first)
147
+ console.log(chalk.magenta.bold('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
148
+ console.log(chalk.magenta.bold('✨ STEP 1: Agility CMS integration'));
149
+ console.log(chalk.magenta.bold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
150
+ let instanceGuid: string | undefined;
151
+ let apiKeys: { fetchKey: string; previewKey: string; securityKey: string } | null = null;
152
+ let instanceName: string | undefined;
153
+
154
+ // Step 1a: Ask user what they want to do
155
+ const agilityChoice = await inquirer.prompt<{ action: 'existing' | 'new' | 'manual' }>([
156
+ {
157
+ type: 'list',
158
+ name: 'action',
159
+ message: 'Choose an option:',
160
+ choices: [
161
+ { name: '1. Connect to an existing instance', value: 'existing' },
162
+ { name: '2. Create a new Agility instance', value: 'new' },
163
+ { name: '3. Let me connect it manually later', value: 'manual' }
164
+ ]
165
+ }
166
+ ]);
167
+
168
+ if (agilityChoice.action === 'existing') {
169
+ try {
170
+ console.log(chalk.magenta('🔐 Authenticating with Agility CMS...\n'));
171
+ const accessToken = await auth.authenticate();
172
+
173
+ // Step 1b: Instance management
174
+ console.log(chalk.magenta('📋 Setting up Agility CMS instance...\n'));
175
+ const instanceData = await agility.manageInstance(accessToken, options);
176
+ instanceGuid = instanceData.guid;
177
+ apiKeys = instanceData.apiKeys;
178
+ instanceName = instanceData.name;
179
+ } catch (authError) {
180
+ console.log(chalk.yellow('\n⚠️ Authentication failed. You can configure Agility CMS later.'));
181
+ console.log(chalk.gray(' Edit .env.local with your Agility credentials when ready.\n'));
182
+ }
183
+ } else if (agilityChoice.action === 'new') {
184
+ // Will open browser after project is created
185
+ console.log(chalk.gray('Skipping Agility instance connection. You can configure it manually later.\n'));
186
+ } else {
187
+ // Manual connection later
188
+ console.log(chalk.gray('Skipping Agility instance connection. You can configure it manually later.\n'));
189
+ }
190
+
191
+ // Sanitize instance name to use as default project name
192
+ const defaultProjectName = instanceName
193
+ ? instanceName
194
+ .toLowerCase()
195
+ .replace(/\s+/g, '-')
196
+ .replace(/[^a-z0-9-_]/g, '')
197
+ .replace(/-+/g, '-')
198
+ .replace(/^-|-$/g, '') || undefined
199
+ : undefined;
200
+
201
+ // Prompt for missing information if not provided via flags
202
+ // Use instance name as default if available and project name not provided
203
+ const finalOptions = await promptForMissingOptions(
204
+ projectName, // Only pass projectName if explicitly provided
205
+ options,
206
+ defaultProjectName // Use sanitized instance name as default
207
+ );
208
+ const finalProjectName = finalOptions.projectName || 'my-app';
209
+
210
+ // Validate project name
211
+ const nameValidation = validateProjectName(finalProjectName);
212
+ if (!nameValidation.valid) {
213
+ displayValidationError(nameValidation.message!);
214
+ process.exit(1);
215
+ }
216
+
217
+ // Validate project path
218
+ const projectPath = path.resolve(process.cwd(), finalProjectName);
219
+ const pathValidation = validateProjectPath(projectPath);
220
+ if (!pathValidation.valid) {
221
+ displayValidationError(pathValidation.message!);
222
+ process.exit(1);
223
+ }
224
+
225
+ // Step 2: Create Next.js app
226
+ console.log(chalk.cyan.bold('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
227
+ console.log(chalk.cyan.bold('📦 STEP 2: Creating base Next.js project'));
228
+ console.log(chalk.cyan.bold('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
229
+ const createdProjectPath = await createNextApp(finalProjectName, finalOptions);
230
+
231
+ // Step 3: Configure project (with Agility settings)
232
+ try {
233
+ await config.setupProject(createdProjectPath, {
234
+ instanceGuid,
235
+ apiKeys,
236
+ ...options
237
+ });
238
+ } catch (configError) {
239
+ console.log(chalk.yellow('\n⚠️ Some configuration steps failed. You may need to configure manually.\n'));
240
+ }
241
+
242
+ // Step 4: Copy template files
243
+ try {
244
+ await templates.copyTemplates(createdProjectPath, options);
245
+ } catch (templateError) {
246
+ console.log(chalk.yellow('\n⚠️ Some template files could not be copied. Check the project structure.\n'));
247
+ }
248
+
249
+ // Step 5: Initialize git repository
250
+ if (isGitAvailable()) {
251
+ await initializeGit(createdProjectPath);
252
+ } else {
253
+ console.log(chalk.yellow('⚠️ Git is not installed. Skipping git initialization.'));
254
+ console.log(chalk.gray(' Install git and run: git init\n'));
255
+ }
256
+
257
+ // Display success message
258
+ displaySuccessMessage(finalProjectName, instanceGuid, apiKeys);
259
+
260
+ // If user chose to create a new instance, open browser now
261
+ if (agilityChoice.action === 'new') {
262
+ console.log(chalk.cyan('\n🌐 Opening Agility CMS signup page in your browser...\n'));
263
+ try {
264
+ await open('https://agilitycms.com/free');
265
+ console.log(chalk.green('✅ Browser opened. Please create your instance.\n'));
266
+ console.log(chalk.yellow('After creating your instance, you can link it by manually configuring your .env.local file.\n'));
267
+ } catch (openError) {
268
+ console.log(chalk.yellow('\n⚠️ Could not open browser automatically.'));
269
+ console.log(chalk.cyan('Please visit: https://agilitycms.com/free\n'));
270
+ }
271
+ }
272
+
273
+ // Ensure process exits cleanly
274
+ process.exit(0);
275
+ } catch (error) {
276
+ handleError(error as Error);
277
+ }
278
+ });
279
+
280
+ program.parse();
281
+
@@ -0,0 +1,104 @@
1
+ import inquirer from 'inquirer';
2
+ import type { CliOptions } from '../types';
3
+
4
+ /**
5
+ * Prompts for missing options interactively
6
+ */
7
+ export async function promptForMissingOptions(
8
+ projectName: string | undefined,
9
+ options: CliOptions,
10
+ defaultProjectName?: string
11
+ ): Promise<CliOptions & { projectName?: string }> {
12
+ const questions: any[] = [];
13
+
14
+ // Prompt for project name if not provided
15
+ if (!projectName) {
16
+ questions.push({
17
+ type: 'input',
18
+ name: 'projectName',
19
+ message: 'What is your project named?',
20
+ default: defaultProjectName || 'my-app',
21
+ validate: (input: string) => {
22
+ if (!input || input.trim().length === 0) {
23
+ return 'Project name is required';
24
+ }
25
+ // Check for valid project name (no spaces, special chars)
26
+ if (!/^[a-z0-9-_]+$/i.test(input)) {
27
+ return 'Project name can only contain letters, numbers, hyphens, and underscores';
28
+ }
29
+ return true;
30
+ }
31
+ });
32
+ }
33
+
34
+ // TypeScript and App Router are always enabled by default (no prompt)
35
+ // Only override if explicitly set via flags
36
+
37
+ // Prompt for Tailwind CSS if not specified
38
+ if (options.tailwind === undefined) {
39
+ questions.push({
40
+ type: 'confirm',
41
+ name: 'tailwind',
42
+ message: 'Would you like to use Tailwind CSS?',
43
+ default: true
44
+ });
45
+ }
46
+
47
+ // Prompt for ESLint if not specified
48
+ if (options.eslint === undefined) {
49
+ questions.push({
50
+ type: 'confirm',
51
+ name: 'eslint',
52
+ message: 'Would you like to use ESLint?',
53
+ default: true
54
+ });
55
+ }
56
+
57
+ // Prompt for src directory if not specified
58
+ if (options.srcDir === undefined) {
59
+ questions.push({
60
+ type: 'confirm',
61
+ name: 'srcDir',
62
+ message: 'Would you like to use the src directory?',
63
+ default: true
64
+ });
65
+ }
66
+
67
+ // Default to TypeScript and App Router (unless explicitly overridden)
68
+ const mergedOptions: CliOptions & { projectName?: string } = {
69
+ ...options,
70
+ projectName: projectName
71
+ };
72
+
73
+ if (!options.typescript && !options.javascript) {
74
+ mergedOptions.typescript = true;
75
+ mergedOptions.javascript = false;
76
+ }
77
+ if (options.app === undefined) {
78
+ mergedOptions.app = true;
79
+ }
80
+
81
+ // Only prompt if we have questions
82
+ if (questions.length === 0) {
83
+ return mergedOptions;
84
+ }
85
+
86
+ const answers = await inquirer.prompt(questions);
87
+
88
+ // Merge answers with existing options
89
+ mergedOptions.projectName = projectName || answers.projectName;
90
+
91
+ // Handle boolean options from prompts
92
+ if (answers.tailwind !== undefined) {
93
+ mergedOptions.tailwind = answers.tailwind;
94
+ }
95
+ if (answers.eslint !== undefined) {
96
+ mergedOptions.eslint = answers.eslint;
97
+ }
98
+ if (answers.srcDir !== undefined) {
99
+ mergedOptions.srcDir = answers.srcDir;
100
+ }
101
+
102
+ return mergedOptions;
103
+ }
104
+
@@ -0,0 +1,30 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+ import type { ProjectConfig } from '../../types';
5
+
6
+ /**
7
+ * Creates .env.local file with Agility configuration
8
+ */
9
+ export async function createEnvFile(projectPath: string, config: ProjectConfig): Promise<void> {
10
+ const envPath = path.join(projectPath, '.env.local');
11
+
12
+ const envVars: Record<string, string> = {
13
+ AGILITY_GUID: config.instanceGuid || '',
14
+ AGILITY_API_FETCH_KEY: config.apiKeys?.fetchKey || '',
15
+ AGILITY_API_PREVIEW_KEY: config.apiKeys?.previewKey || '',
16
+ AGILITY_SECURITY_KEY: config.apiKeys?.securityKey || '',
17
+ AGILITY_LOCALES: 'en-us',
18
+ AGILITY_SITEMAP: 'website',
19
+ AGILITY_FETCH_CACHE_DURATION: '60',
20
+ AGILITY_PATH_REVALIDATE_DURATION: '60'
21
+ };
22
+
23
+ const envContent = Object.entries(envVars)
24
+ .map(([key, value]) => `${key}=${value}`)
25
+ .join('\n');
26
+
27
+ fs.writeFileSync(envPath, envContent);
28
+ console.log(chalk.green('✓ Created .env.local'));
29
+ }
30
+
@@ -0,0 +1,2 @@
1
+ export { setupProject } from './setupProject';
2
+
@@ -0,0 +1,30 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+
5
+ /**
6
+ * Creates .vscode/mcp.json file with Agility CMS MCP server configuration
7
+ */
8
+ export async function createMcpConfig(projectPath: string): Promise<void> {
9
+ const vscodeDir = path.join(projectPath, '.vscode');
10
+ const mcpConfigPath = path.join(vscodeDir, 'mcp.json');
11
+
12
+ // Ensure .vscode directory exists
13
+ if (!fs.existsSync(vscodeDir)) {
14
+ fs.mkdirSync(vscodeDir, { recursive: true });
15
+ }
16
+
17
+ const mcpConfig = {
18
+ servers: {
19
+ 'Agility CMS': {
20
+ url: 'https://mcp.agilitycms.com/api/mcp',
21
+ type: 'http'
22
+ }
23
+ },
24
+ inputs: []
25
+ };
26
+
27
+ fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, '\t'));
28
+ console.log(chalk.green('✓ Created .vscode/mcp.json'));
29
+ }
30
+