@l4yercak3/cli 1.0.0 → 1.0.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.
@@ -12,7 +12,9 @@
12
12
  "Bash(git add:*)",
13
13
  "Bash(git commit:*)",
14
14
  "Bash(gh repo create:*)",
15
- "Bash(npm whoami:*)"
15
+ "Bash(npm whoami:*)",
16
+ "Bash(npm pkg fix:*)",
17
+ "Bash(git push:*)"
16
18
  ]
17
19
  }
18
20
  }
package/CLAUDE.md ADDED
@@ -0,0 +1,100 @@
1
+ # L4YERCAK3 CLI - Claude Code Configuration
2
+
3
+ ## About This Project
4
+
5
+ This is **L4YERCAK3 CLI** (`@l4yercak3/cli`) - the official CLI tool for integrating Next.js projects with the Layer Cake platform. It handles authentication, project setup, and generates boilerplate code for L4YERCAK3 features.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install -g @l4yercak3/cli
11
+ ```
12
+
13
+ ## CLI Commands
14
+
15
+ | Command | Description |
16
+ |---------|-------------|
17
+ | `l4yercak3 login` | Authenticate with the L4YERCAK3 platform (opens browser) |
18
+ | `l4yercak3 logout` | Log out and clear session |
19
+ | `l4yercak3 status` | Show authentication status and session info |
20
+ | `l4yercak3 spread` | Initialize L4YERCAK3 in a Next.js project (interactive setup) |
21
+ | `icing` | Alias for `l4yercak3` command |
22
+
23
+ ## Project Structure
24
+
25
+ ```
26
+ src/
27
+ ├── api/ # Backend API client
28
+ │ └── backend-client.js
29
+ ├── commands/ # CLI command handlers
30
+ │ ├── login.js # Browser-based OAuth login
31
+ │ ├── logout.js # Session cleanup
32
+ │ ├── spread.js # Project initialization wizard
33
+ │ └── status.js # Auth status display
34
+ ├── config/ # Configuration management
35
+ │ └── config-manager.js
36
+ ├── detectors/ # Project analysis
37
+ │ ├── nextjs-detector.js
38
+ │ ├── github-detector.js
39
+ │ ├── oauth-detector.js
40
+ │ └── api-client-detector.js
41
+ ├── generators/ # Code generators
42
+ │ ├── api-client-generator.js
43
+ │ ├── env-generator.js
44
+ │ ├── nextauth-generator.js
45
+ │ ├── oauth-guide-generator.js
46
+ │ └── gitignore-generator.js
47
+ ├── index.js # Main entry point
48
+ └── logo.js # ASCII art branding
49
+ ```
50
+
51
+ ## Development Commands
52
+
53
+ ```bash
54
+ npm run build # Build for production
55
+ npm run lint # Run ESLint
56
+ npm run lint:fix # Fix ESLint issues
57
+ npm run type-check # TypeScript type checking (via JSDoc)
58
+ npm test # Run Jest tests
59
+ npm run test:watch # Run tests in watch mode
60
+ npm run test:coverage # Run tests with coverage report
61
+ npm run verify # Run lint + type-check + test + build
62
+ ```
63
+
64
+ ## Code Style
65
+
66
+ - JavaScript with JSDoc type annotations
67
+ - ESLint with Prettier formatting
68
+ - Jest for testing with mocks for fs, fetch, and external dependencies
69
+ - Keep files focused and under 500 lines
70
+ - Use async/await for asynchronous operations
71
+
72
+ ## Testing Patterns
73
+
74
+ When writing tests:
75
+ - Mock `fs` module for file operations
76
+ - Mock `node-fetch` for API calls
77
+ - Mock `chalk` to return plain strings
78
+ - Use `jest.spyOn(console, 'error').mockImplementation(() => {})` to silence expected errors
79
+ - For singleton modules that instantiate on require, set up mocks BEFORE requiring the module
80
+
81
+ ## Configuration Storage
82
+
83
+ User config is stored at `~/.l4yercak3/config.json` with:
84
+ - Session tokens and expiration
85
+ - Organization memberships
86
+ - Project configurations by path
87
+ - Backend URL settings
88
+
89
+ ## Available Features
90
+
91
+ The `spread` command can set up:
92
+ - **CRM** - Customer relationship management integration
93
+ - **OAuth** - Social login with Google, GitHub, Discord, etc.
94
+ - **Stripe** - Payment processing integration
95
+ - **Analytics** - Usage tracking and metrics
96
+
97
+ ## Links
98
+
99
+ - npm: https://www.npmjs.com/package/@l4yercak3/cli
100
+ - GitHub: https://github.com/voundbrand/l4yercak3-cli
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@l4yercak3/cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Icing on the L4yercak3 - The sweet finishing touch for your Layer Cake integration",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
- "l4yercak3": "./bin/cli.js",
8
- "icing": "./bin/cli.js"
7
+ "l4yercak3": "bin/cli.js",
8
+ "icing": "bin/cli.js"
9
9
  },
10
10
  "scripts": {
11
11
  "start": "node bin/cli.js",
@@ -21,9 +21,15 @@
21
21
  },
22
22
  "jest": {
23
23
  "testEnvironment": "node",
24
- "testMatch": ["**/tests/**/*.test.js"],
25
- "collectCoverageFrom": ["src/**/*.js"],
26
- "coveragePathIgnorePatterns": ["/node_modules/"]
24
+ "testMatch": [
25
+ "**/tests/**/*.test.js"
26
+ ],
27
+ "collectCoverageFrom": [
28
+ "src/**/*.js"
29
+ ],
30
+ "coveragePathIgnorePatterns": [
31
+ "/node_modules/"
32
+ ]
27
33
  },
28
34
  "keywords": [
29
35
  "l4yercak3",
@@ -3,6 +3,7 @@
3
3
  * Handles communication with L4YERCAK3 backend API
4
4
  */
5
5
 
6
+ const crypto = require('crypto');
6
7
  const fetch = require('node-fetch');
7
8
  const configManager = require('../config/config-manager');
8
9
 
@@ -11,6 +12,13 @@ class BackendClient {
11
12
  this.baseUrl = configManager.getBackendUrl();
12
13
  }
13
14
 
15
+ /**
16
+ * Generate a cryptographically secure state token for CSRF protection
17
+ */
18
+ generateState() {
19
+ return crypto.randomBytes(32).toString('hex');
20
+ }
21
+
14
22
  /**
15
23
  * Get headers for API requests
16
24
  */
@@ -98,18 +106,21 @@ class BackendClient {
98
106
  }
99
107
 
100
108
  /**
101
- * Get CLI login URL (uses unified OAuth signup endpoint)
109
+ * Get CLI login URL with state parameter for CSRF protection
110
+ * @param {string} state - The state token generated by the CLI
111
+ * @param {string|null} provider - Optional OAuth provider for direct auth
112
+ * @returns {string} The login URL
102
113
  */
103
- getLoginUrl(provider = null) {
114
+ getLoginUrl(state, provider = null) {
104
115
  const backendUrl = configManager.getBackendUrl();
105
- const callbackUrl = 'http://localhost:3001/callback';
106
-
116
+ const callbackUrl = 'http://localhost:3000/callback';
117
+
107
118
  if (provider) {
108
119
  // Direct OAuth provider URL
109
- return `${backendUrl}/api/auth/oauth-signup?provider=${provider}&sessionType=cli&callback=${encodeURIComponent(callbackUrl)}`;
120
+ return `${backendUrl}/api/auth/oauth-signup?provider=${provider}&sessionType=cli&state=${state}&callback=${encodeURIComponent(callbackUrl)}`;
110
121
  } else {
111
- // Provider selection page (still uses old endpoint for now, but could be updated)
112
- return `${backendUrl}/auth/cli-login?callback=${encodeURIComponent(callbackUrl)}`;
122
+ // Provider selection page
123
+ return `${backendUrl}/auth/cli-login?state=${state}&callback=${encodeURIComponent(callbackUrl)}`;
113
124
  }
114
125
  }
115
126
 
@@ -10,17 +10,37 @@ const chalk = require('chalk');
10
10
 
11
11
  /**
12
12
  * Start local server to receive OAuth callback
13
+ * @param {string} expectedState - The state token to verify against
13
14
  */
14
- function startCallbackServer() {
15
+ function startCallbackServer(expectedState) {
15
16
  return new Promise((resolve, reject) => {
16
17
  const http = require('http');
17
-
18
+
18
19
  const server = http.createServer((req, res) => {
19
- const url = new URL(req.url, 'http://localhost:3001');
20
-
20
+ const url = new URL(req.url, 'http://localhost:3000');
21
+
21
22
  if (url.pathname === '/callback') {
22
23
  const token = url.searchParams.get('token');
23
-
24
+ const returnedState = url.searchParams.get('state');
25
+
26
+ // Verify state to prevent CSRF attacks
27
+ if (returnedState !== expectedState) {
28
+ res.writeHead(400, { 'Content-Type': 'text/html' });
29
+ res.end(`
30
+ <html>
31
+ <head><title>CLI Login Error</title></head>
32
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
33
+ <h1 style="color: #EF4444;">❌ Security Error</h1>
34
+ <p>State mismatch - possible CSRF attack. Please try again.</p>
35
+ </body>
36
+ </html>
37
+ `);
38
+
39
+ server.close();
40
+ reject(new Error('State mismatch - security validation failed'));
41
+ return;
42
+ }
43
+
24
44
  if (token) {
25
45
  res.writeHead(200, { 'Content-Type': 'text/html' });
26
46
  res.end(`
@@ -32,7 +52,7 @@ function startCallbackServer() {
32
52
  </body>
33
53
  </html>
34
54
  `);
35
-
55
+
36
56
  server.close();
37
57
  resolve(token);
38
58
  } else {
@@ -46,7 +66,7 @@ function startCallbackServer() {
46
66
  </body>
47
67
  </html>
48
68
  `);
49
-
69
+
50
70
  server.close();
51
71
  reject(new Error('No token received'));
52
72
  }
@@ -56,7 +76,7 @@ function startCallbackServer() {
56
76
  }
57
77
  });
58
78
 
59
- server.listen(3001, 'localhost', () => {
79
+ server.listen(3000, 'localhost', () => {
60
80
  console.log(chalk.gray(' Waiting for authentication...'));
61
81
  });
62
82
 
@@ -85,13 +105,16 @@ async function handleLogin() {
85
105
 
86
106
  console.log(chalk.cyan(' 🔐 Opening browser for authentication...\n'));
87
107
 
88
- // Start callback server
89
- const callbackPromise = startCallbackServer();
108
+ // Generate state for CSRF protection
109
+ const state = backendClient.generateState();
110
+
111
+ // Start callback server with expected state
112
+ const callbackPromise = startCallbackServer(state);
90
113
 
91
- // Open browser
92
- const loginUrl = backendClient.getLoginUrl();
114
+ // Open browser with state parameter
115
+ const loginUrl = backendClient.getLoginUrl(state);
93
116
  console.log(chalk.gray(` Login URL: ${loginUrl}\n`));
94
-
117
+
95
118
  await open(loginUrl);
96
119
 
97
120
  // Wait for callback
@@ -33,7 +33,7 @@ class ConfigManager {
33
33
  session: null,
34
34
  organizations: [],
35
35
  settings: {
36
- backendUrl: process.env.L4YERCAK3_BACKEND_URL || 'https://backend.l4yercak3.com',
36
+ backendUrl: process.env.L4YERCAK3_BACKEND_URL || 'https://app.l4yercak3.com',
37
37
  },
38
38
  };
39
39
  }
@@ -118,7 +118,7 @@ class ConfigManager {
118
118
  */
119
119
  getBackendUrl() {
120
120
  const config = this.getConfig();
121
- return config.settings?.backendUrl || process.env.L4YERCAK3_BACKEND_URL || 'https://backend.l4yercak3.com';
121
+ return config.settings?.backendUrl || process.env.L4YERCAK3_BACKEND_URL || 'https://app.l4yercak3.com';
122
122
  }
123
123
 
124
124
  /**
@@ -267,7 +267,7 @@ Once OAuth is set up:
267
267
 
268
268
  ---
269
269
 
270
- **Need Help?** Check the [L4YERCAK3 Documentation](https://docs.l4yercak3.com) or contact support.
270
+ **Need Help?** Check the [L4YERCAK3 Documentation](https://www.l4yercak3.com/docs) or contact support.
271
271
  `;
272
272
 
273
273
  return content;
@@ -0,0 +1,86 @@
1
+ # L4YERCAK3 Integration - Claude Code Configuration
2
+
3
+ ## About This Integration
4
+
5
+ This project uses **L4YERCAK3** for backend services. The integration was set up using `@l4yercak3/cli`.
6
+
7
+ ## L4YERCAK3 CLI
8
+
9
+ Install the CLI globally to manage your L4YERCAK3 integration:
10
+
11
+ ```bash
12
+ npm install -g @l4yercak3/cli
13
+ ```
14
+
15
+ ### Commands
16
+
17
+ | Command | Description |
18
+ |---------|-------------|
19
+ | `l4yercak3 login` | Authenticate with L4YERCAK3 platform |
20
+ | `l4yercak3 logout` | Log out from L4YERCAK3 |
21
+ | `l4yercak3 status` | Check authentication and session status |
22
+ | `l4yercak3 spread` | Re-run setup wizard to add/update features |
23
+ | `icing` | Shorthand alias for `l4yercak3` |
24
+
25
+ ## Generated Files
26
+
27
+ The CLI generates these files based on selected features:
28
+
29
+ | File | Purpose |
30
+ |------|---------|
31
+ | `.env.local` | Environment variables (API keys, secrets) |
32
+ | `src/lib/api-client.js` | API client for L4YERCAK3 backend (or `.ts` for TypeScript) |
33
+ | `src/app/api/auth/[...nextauth]/route.js` | NextAuth.js configuration (if OAuth enabled) |
34
+ | `OAUTH_SETUP_GUIDE.md` | OAuth provider setup instructions (if OAuth enabled) |
35
+
36
+ ## Environment Variables
37
+
38
+ Required variables in `.env.local`:
39
+
40
+ ```bash
41
+ L4YERCAK3_API_KEY= # Your L4YERCAK3 API key
42
+ L4YERCAK3_BACKEND_URL= # Backend API URL
43
+ L4YERCAK3_ORG_ID= # Your organization ID
44
+ NEXTAUTH_SECRET= # NextAuth secret (if using OAuth)
45
+ NEXTAUTH_URL= # Your app URL (if using OAuth)
46
+ ```
47
+
48
+ ## Using the API Client
49
+
50
+ ```javascript
51
+ import L4YERCAK3Client from '@/lib/api-client';
52
+
53
+ // Create client instance (uses env defaults if no args)
54
+ const client = new L4YERCAK3Client();
55
+
56
+ // CRM Methods
57
+ const contacts = await client.getContacts();
58
+ const contact = await client.getContact('contact-id');
59
+ const newContact = await client.createContact({ email: 'user@example.com', name: 'John Doe' });
60
+ await client.updateContact('contact-id', { name: 'Jane Doe' });
61
+ await client.deleteContact('contact-id');
62
+
63
+ // Projects Methods
64
+ const projects = await client.getProjects();
65
+ const project = await client.createProject({ name: 'New Project' });
66
+
67
+ // Invoices Methods
68
+ const invoices = await client.getInvoices();
69
+ const invoice = await client.createInvoice({ amount: 100, contactId: 'contact-id' });
70
+ ```
71
+
72
+ ## Re-running Setup
73
+
74
+ To add new features or update configuration:
75
+
76
+ ```bash
77
+ l4yercak3 spread
78
+ ```
79
+
80
+ This will detect existing setup and allow you to add additional features without overwriting current configuration.
81
+
82
+ ## Support
83
+
84
+ - Documentation: https://docs.l4yercak3.com
85
+ - CLI GitHub: https://github.com/voundbrand/l4yercak3-cli
86
+ - npm: https://www.npmjs.com/package/@l4yercak3/cli
@@ -242,27 +242,47 @@ describe('BackendClient', () => {
242
242
  });
243
243
  });
244
244
 
245
+ describe('generateState', () => {
246
+ it('generates a 64-character hex string', () => {
247
+ const state = BackendClient.generateState();
248
+
249
+ expect(state).toMatch(/^[a-f0-9]{64}$/);
250
+ });
251
+
252
+ it('generates unique values each time', () => {
253
+ const state1 = BackendClient.generateState();
254
+ const state2 = BackendClient.generateState();
255
+
256
+ expect(state1).not.toBe(state2);
257
+ });
258
+ });
259
+
245
260
  describe('getLoginUrl', () => {
246
- it('returns provider selection URL when no provider specified', () => {
247
- const url = BackendClient.getLoginUrl();
261
+ it('returns provider selection URL with state when no provider specified', () => {
262
+ const state = 'test-state-token';
263
+ const url = BackendClient.getLoginUrl(state);
248
264
 
249
265
  expect(url).toContain('https://backend.test.com');
250
266
  expect(url).toContain('/auth/cli-login');
267
+ expect(url).toContain('state=test-state-token');
251
268
  expect(url).toContain('callback=');
252
269
  });
253
270
 
254
271
  it('returns direct OAuth URL when provider specified', () => {
255
- const url = BackendClient.getLoginUrl('google');
272
+ const state = 'test-state-token';
273
+ const url = BackendClient.getLoginUrl(state, 'google');
256
274
 
257
275
  expect(url).toContain('/api/auth/oauth-signup');
258
276
  expect(url).toContain('provider=google');
259
277
  expect(url).toContain('sessionType=cli');
278
+ expect(url).toContain('state=test-state-token');
260
279
  });
261
280
 
262
281
  it('includes encoded callback URL', () => {
263
- const url = BackendClient.getLoginUrl('github');
282
+ const state = 'test-state-token';
283
+ const url = BackendClient.getLoginUrl(state, 'github');
264
284
 
265
- expect(url).toContain(encodeURIComponent('http://localhost:3001/callback'));
285
+ expect(url).toContain(encodeURIComponent('http://localhost:3000/callback'));
266
286
  });
267
287
  });
268
288
 
@@ -36,7 +36,7 @@ describe('ConfigManager', () => {
36
36
  session: null,
37
37
  organizations: [],
38
38
  settings: {
39
- backendUrl: 'https://backend.l4yercak3.com',
39
+ backendUrl: 'https://app.l4yercak3.com',
40
40
  },
41
41
  });
42
42
  });
@@ -61,6 +61,8 @@ describe('ConfigManager', () => {
61
61
  fs.existsSync.mockReturnValue(true);
62
62
  fs.readFileSync.mockReturnValue('invalid json');
63
63
 
64
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
65
+
64
66
  const config = ConfigManager.getConfig();
65
67
 
66
68
  expect(config).toEqual({
@@ -68,6 +70,8 @@ describe('ConfigManager', () => {
68
70
  organizations: [],
69
71
  settings: {},
70
72
  });
73
+
74
+ consoleSpy.mockRestore();
71
75
  });
72
76
  });
73
77
 
@@ -106,9 +110,13 @@ describe('ConfigManager', () => {
106
110
  throw new Error('Write failed');
107
111
  });
108
112
 
113
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
114
+
109
115
  const result = ConfigManager.saveConfig({ test: true });
110
116
 
111
117
  expect(result).toBe(false);
118
+
119
+ consoleSpy.mockRestore();
112
120
  });
113
121
  });
114
122
 
@@ -196,7 +204,7 @@ describe('ConfigManager', () => {
196
204
 
197
205
  const url = ConfigManager.getBackendUrl();
198
206
 
199
- expect(url).toBe('https://backend.l4yercak3.com');
207
+ expect(url).toBe('https://app.l4yercak3.com');
200
208
  });
201
209
 
202
210
  it('returns configured URL from settings', () => {
@@ -266,7 +266,7 @@ describe('OAuthGuideGenerator', () => {
266
266
 
267
267
  expect(guide).toContain('## Next Steps');
268
268
  expect(guide).toContain('L4YERCAK3 Documentation');
269
- expect(guide).toContain('docs.l4yercak3.com');
269
+ expect(guide).toContain('www.l4yercak3.com/docs');
270
270
  });
271
271
  });
272
272
  });