@klyb/cli 0.1.19

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 (48) hide show
  1. package/README.md +82 -0
  2. package/bin/klyb.js +4 -0
  3. package/dist/commands/deploy.js +141 -0
  4. package/dist/commands/dev.js +87 -0
  5. package/dist/commands/init.js +77 -0
  6. package/dist/commands/login.js +121 -0
  7. package/dist/credentials.js +35 -0
  8. package/dist/index.js +31 -0
  9. package/dist/simulator/client/src/SimulatorApp.js +133 -0
  10. package/dist/simulator/client/src/main.js +11 -0
  11. package/dist/simulator/client/vite.config.js +15 -0
  12. package/dist/templates/react-vite-ts/src/App.js +27 -0
  13. package/dist/templates/react-vite-ts/src/main.js +11 -0
  14. package/dist/templates/react-vite-ts/vite.config.js +22 -0
  15. package/dist/templates/templates/react-vite-ts/_env.example +12 -0
  16. package/dist/templates/templates/react-vite-ts/clubz.json +20 -0
  17. package/dist/templates/templates/react-vite-ts/index.html +13 -0
  18. package/dist/templates/templates/react-vite-ts/klyb.json +20 -0
  19. package/dist/templates/templates/react-vite-ts/package.json +24 -0
  20. package/dist/templates/templates/react-vite-ts/public/clubz.json +20 -0
  21. package/dist/templates/templates/react-vite-ts/public/preview.png +0 -0
  22. package/dist/templates/templates/react-vite-ts/src/App.tsx +35 -0
  23. package/dist/templates/templates/react-vite-ts/src/main.tsx +9 -0
  24. package/dist/templates/templates/react-vite-ts/tsconfig.json +37 -0
  25. package/dist/templates/templates/react-vite-ts/tsconfig.node.json +12 -0
  26. package/dist/templates/templates/react-vite-ts/vite.config.ts +25 -0
  27. package/package.json +37 -0
  28. package/src/commands/deploy.ts +155 -0
  29. package/src/commands/dev.ts +94 -0
  30. package/src/commands/init.ts +88 -0
  31. package/src/commands/login.ts +94 -0
  32. package/src/credentials.ts +36 -0
  33. package/src/index.ts +37 -0
  34. package/src/simulator/client/index.html +16 -0
  35. package/src/simulator/client/src/SimulatorApp.tsx +163 -0
  36. package/src/simulator/client/src/main.tsx +9 -0
  37. package/src/simulator/client/vite.config.ts +12 -0
  38. package/src/templates/react-vite-ts/_env.example +12 -0
  39. package/src/templates/react-vite-ts/index.html +13 -0
  40. package/src/templates/react-vite-ts/klyb.json +20 -0
  41. package/src/templates/react-vite-ts/package.json +24 -0
  42. package/src/templates/react-vite-ts/public/preview.png +0 -0
  43. package/src/templates/react-vite-ts/src/App.tsx +35 -0
  44. package/src/templates/react-vite-ts/src/main.tsx +9 -0
  45. package/src/templates/react-vite-ts/tsconfig.json +37 -0
  46. package/src/templates/react-vite-ts/tsconfig.node.json +12 -0
  47. package/src/templates/react-vite-ts/vite.config.ts +25 -0
  48. package/tsconfig.json +19 -0
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # @klyb/cli
2
+
3
+ The official Command Line Interface for building widgets and pages for the **Klyb Community Builder**.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @klyb/cli
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### 1. Create a new Widget
14
+
15
+ Scaffold a new widget project with the official React + Vite + TypeScript template.
16
+
17
+ ```bash
18
+ klyb init my-awesome-widget
19
+ cd my-awesome-widget
20
+ npm install
21
+ ```
22
+
23
+ ### 2. Develop Locally (Simulator)
24
+
25
+ Start the local development server. This launches a **Local Simulator** that mimics the Klyb mobile environment (IFrame, SDK communication, etc.), allowing you to test your widget in isolation.
26
+
27
+ ```bash
28
+ npm run dev
29
+ # OR
30
+ klyb dev
31
+ ```
32
+
33
+ The simulator will open at `http://localhost:3000` (or the next available port).
34
+
35
+ ### 3. Deploy
36
+
37
+ Package your widget and deploy it to the Klyb platform.
38
+
39
+ ```bash
40
+ npm run deploy
41
+ # OR
42
+ klyb deploy
43
+ ```
44
+
45
+ ## Project Structure
46
+
47
+ A standard Klyb widget project looks like this:
48
+
49
+ ```
50
+ my-widget/
51
+ ├── src/
52
+ │ ├── App.tsx # Your main widget code
53
+ │ └── main.tsx # Entry point
54
+ ├── klyb.json # Widget metadata (name, version, permissions)
55
+ ├── package.json
56
+ └── vite.config.ts # Vite configuration
57
+ ```
58
+
59
+ ## SDK Integration
60
+
61
+ The CLI templates come pre-configured with `@klyb/sdk`. Use it to interact with the host application:
62
+
63
+ ```ts
64
+ import { bridge } from '@klyb/sdk';
65
+
66
+ // Get user info
67
+ const user = await bridge.getUser();
68
+
69
+ // Show a native toast
70
+ await bridge.showToast({ message: 'Hello from Widget!', type: 'success' });
71
+ ```
72
+
73
+ ## Contributing
74
+
75
+ This CLI is part of the Klyb monorepo.
76
+
77
+ ### Local Setup
78
+
79
+ 1. Clone the repository.
80
+ 2. Install dependencies: `npm install`
81
+ 3. Build the CLI: `npm run build`
82
+ 4. Link globally for testing: `npm link`
package/bin/klyb.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+
3
+ // This will require the built source code
4
+ require('../dist/index.js');
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.deployCommand = deployCommand;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ const child_process_1 = require("child_process");
11
+ const archiver_1 = __importDefault(require("archiver"));
12
+ const axios_1 = __importDefault(require("axios"));
13
+ const form_data_1 = __importDefault(require("form-data"));
14
+ const credentials_1 = require("../credentials");
15
+ async function deployCommand() {
16
+ console.log(chalk_1.default.blue('🚀 Deploying Widget to Klyb...'));
17
+ const cwd = process.cwd();
18
+ const configPath = path_1.default.join(cwd, 'klyb.json');
19
+ // On vérifie qu'on est bien à la racine d'un projet de widget (présence du klyb.json)
20
+ if (!fs_extra_1.default.existsSync(configPath)) {
21
+ console.error(chalk_1.default.red('❌ klyb.json not found. Are you in a Klyb project?'));
22
+ process.exit(1);
23
+ }
24
+ const config = await fs_extra_1.default.readJson(configPath);
25
+ console.log(chalk_1.default.dim(` Project: ${config.name} v${config.version}`));
26
+ // On compile le code source du projet (TypeScript, React, etc.)
27
+ console.log(chalk_1.default.yellow('\n📦 Building project...'));
28
+ try {
29
+ (0, child_process_1.execSync)('npm run build', { stdio: 'inherit' });
30
+ }
31
+ catch (e) {
32
+ console.error(chalk_1.default.red('❌ Build failed. Fix errors and try again.'));
33
+ process.exit(1);
34
+ }
35
+ // On prépare l'archive contenant uniquement les fichiers compilés (le dossier dist)
36
+ console.log(chalk_1.default.yellow('\n🤐 Zipping assets...'));
37
+ const distDir = path_1.default.join(cwd, 'dist');
38
+ const zipPath = path_1.default.join(cwd, 'bundle.zip');
39
+ if (!fs_extra_1.default.existsSync(distDir)) {
40
+ console.error(chalk_1.default.red('❌ dist/ directory not found after build.'));
41
+ process.exit(1);
42
+ }
43
+ await zipDirectory(distDir, zipPath, { source: configPath, name: 'klyb.json' });
44
+ console.log(chalk_1.default.green(` Bundle created: ${getSize(zipPath)}`));
45
+ // Étape finale : on envoie tout ça vers la plateforme Klyb
46
+ console.log(chalk_1.default.yellow('\n☁️ Uploading to Klyb Registry...'));
47
+ // Récupération des accès enregistrés lors du `klyb login`
48
+ const credentials = (0, credentials_1.getCredentials)();
49
+ const apiUrl = credentials?.apiUrl || process.env.KLYB_API_URL || 'http://localhost:3000';
50
+ if (!credentials?.token) {
51
+ console.error(chalk_1.default.red('❌ Not authenticated.'));
52
+ console.error(chalk_1.default.yellow(' Run: ') + chalk_1.default.cyan('klyb login'));
53
+ process.exit(1);
54
+ }
55
+ const token = credentials.token;
56
+ try {
57
+ const isSubmission = process.argv.includes('--submit');
58
+ const status = isSubmission ? 'published' : 'draft';
59
+ const form = new form_data_1.default();
60
+ form.append('file', fs_extra_1.default.createReadStream(zipPath));
61
+ form.append('manifest', JSON.stringify(config));
62
+ // Appel direct à l'API pour lancer la publication
63
+ const deployUrl = `${apiUrl}/api/widget-library/developer/deploy`;
64
+ console.log(chalk_1.default.blue(`\n🚀 Deploying to ${deployUrl}...`));
65
+ const response = await axios_1.default.post(deployUrl, form, {
66
+ headers: {
67
+ ...form.getHeaders(),
68
+ 'Authorization': `Bearer ${token}`
69
+ },
70
+ maxContentLength: Infinity,
71
+ maxBodyLength: Infinity
72
+ });
73
+ console.log(chalk_1.default.green('\n✅ Deployment Successful!'));
74
+ console.log(chalk_1.default.cyan(` Widget ID: ${response.data.widgetId}`));
75
+ console.log(chalk_1.default.cyan(` Version: ${response.data.version}`));
76
+ console.log(chalk_1.default.dim(` Download URL: ${response.data.url}`));
77
+ // On s'assure que le projet local garde bien l'identifiant définitif du widget
78
+ if (config.id !== response.data.widgetId) {
79
+ config.id = response.data.widgetId;
80
+ await fs_extra_1.default.writeJson(configPath, config, { spaces: 2 });
81
+ console.log(chalk_1.default.dim(` (Updated local klyb.json with new ID)`));
82
+ }
83
+ if (isSubmission) {
84
+ console.log(chalk_1.default.magenta(' 🚀 Submitted for validation (Status: Pending)'));
85
+ console.log(chalk_1.default.dim(' An admin will review your widget shortly.'));
86
+ }
87
+ else {
88
+ console.log(chalk_1.default.dim(' Saved as Draft. PRO TIP: Use --submit to send for validation.'));
89
+ }
90
+ console.log(chalk_1.default.dim(' Available in your "Submissions" tab.'));
91
+ // On supprime le fichier zip qui ne sert plus à rien
92
+ await fs_extra_1.default.remove(zipPath);
93
+ }
94
+ catch (error) {
95
+ console.error(chalk_1.default.red('\n❌ Upload failed'));
96
+ if (error.response) {
97
+ console.error(chalk_1.default.red(` Server Error: ${error.response.status} ${error.response.statusText}`));
98
+ console.error(chalk_1.default.yellow(` Response Data: ${JSON.stringify(error.response.data, null, 2)}`));
99
+ }
100
+ else if (error.request) {
101
+ console.error(chalk_1.default.red(` No response from server`));
102
+ console.error(chalk_1.default.yellow(` API URL: ${apiUrl}`));
103
+ console.error(chalk_1.default.dim(` Check if the API is running on ${apiUrl}`));
104
+ }
105
+ else {
106
+ console.error(chalk_1.default.red(` Error: ${error.message}`));
107
+ }
108
+ // On n'oublie pas de supprimer le fichier zip même si l'envoi a échoué
109
+ try {
110
+ if (await fs_extra_1.default.pathExists(zipPath)) {
111
+ await fs_extra_1.default.remove(zipPath);
112
+ }
113
+ }
114
+ catch (cleanupError) {
115
+ // Ignore cleanup errors
116
+ }
117
+ process.exit(1);
118
+ }
119
+ }
120
+ function zipDirectory(source, out, extraFile) {
121
+ const archive = (0, archiver_1.default)('zip', { zlib: { level: 9 } });
122
+ const stream = fs_extra_1.default.createWriteStream(out);
123
+ return new Promise((resolve, reject) => {
124
+ archive
125
+ .directory(source, false)
126
+ .on('error', err => reject(err))
127
+ .pipe(stream);
128
+ if (extraFile) {
129
+ archive.file(extraFile.source, { name: extraFile.name });
130
+ }
131
+ stream.on('close', () => resolve());
132
+ archive.finalize();
133
+ });
134
+ }
135
+ function getSize(filePath) {
136
+ const stats = fs_extra_1.default.statSync(filePath);
137
+ const bytes = stats.size;
138
+ if (bytes < 1024)
139
+ return bytes + ' B';
140
+ return (bytes / 1024).toFixed(2) + ' KB';
141
+ }
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.devCommand = devCommand;
7
+ const path_1 = __importDefault(require("path"));
8
+ const express_1 = __importDefault(require("express"));
9
+ const cross_spawn_1 = __importDefault(require("cross-spawn"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const dotenv_1 = __importDefault(require("dotenv"));
13
+ const axios_1 = __importDefault(require("axios"));
14
+ async function devCommand() {
15
+ console.log(chalk_1.default.blue('🚀 Starting Klyb Development Environment...'));
16
+ // 0. Load Environment Variables from the User's Project
17
+ const userEnvPath = path_1.default.join(process.cwd(), '.env');
18
+ if (fs_1.default.existsSync(userEnvPath)) {
19
+ dotenv_1.default.config({ path: userEnvPath });
20
+ console.log(chalk_1.default.green('✅ Loaded .env file'));
21
+ }
22
+ else {
23
+ console.log(chalk_1.default.yellow('⚠️ No .env file found. API Key integration will be disabled.'));
24
+ }
25
+ // 1. Start User's Widget Server (Vite)
26
+ const widgetPort = 3001;
27
+ console.log(chalk_1.default.dim(` Starting widget server on port ${widgetPort}...`));
28
+ const widgetProcess = (0, cross_spawn_1.default)('npm', ['run', 'dev', '--', '--port', widgetPort.toString()], {
29
+ stdio: 'inherit',
30
+ env: { ...process.env, FORCE_COLOR: 'true' }
31
+ });
32
+ widgetProcess.on('error', (err) => {
33
+ console.error(chalk_1.default.red('❌ Failed to start widget server'), err);
34
+ });
35
+ // 2. Start Simulator Server
36
+ const app = (0, express_1.default)();
37
+ const simulatorPort = 3002; // Changed from 3000 to avoid conflict with API
38
+ const realApiUrl = process.env.KLYB_API_URL || 'http://localhost:3000';
39
+ // API Route for Simulator to get Configuration
40
+ app.get('/simulator/api/config', async (req, res) => {
41
+ const apiKey = process.env.KLYB_API_KEY;
42
+ let user = null;
43
+ if (apiKey) {
44
+ try {
45
+ // Determine if apiKey is a JWT or a special key.
46
+ // For now, assuming it's a Bearer Token (Personal Access Token)
47
+ const response = await axios_1.default.get(`${realApiUrl}/api/auth/me`, {
48
+ headers: { Authorization: `Bearer ${apiKey}` }
49
+ });
50
+ user = response.data;
51
+ // Add role/permissions if needed
52
+ }
53
+ catch (error) {
54
+ console.error(chalk_1.default.red('❌ Failed to validate API Key:'), error.message);
55
+ // Don't fail the request, just return guest mode with error
56
+ }
57
+ }
58
+ res.json({
59
+ user: user || { id: 'guest', name: 'Guest Developer', role: 'guest' },
60
+ mode: apiKey ? 'authenticated' : 'guest',
61
+ apiUrl: realApiUrl
62
+ });
63
+ });
64
+ // Resolve path to built simulator client assets
65
+ let simulatorDist = path_1.default.join(__dirname, '../../simulator/client');
66
+ if (!fs_1.default.existsSync(simulatorDist) || !fs_1.default.existsSync(path_1.default.join(simulatorDist, 'index.html'))) {
67
+ console.warn(chalk_1.default.yellow('⚠️ Simulator build not found. Ensure CLI is built.'));
68
+ simulatorDist = path_1.default.join(__dirname, '../../../dist/simulator/client');
69
+ }
70
+ app.use(express_1.default.static(simulatorDist));
71
+ app.get('*', (req, res) => {
72
+ // handle api routes not found
73
+ if (req.path.startsWith('/simulator/api')) {
74
+ return res.status(404).json({ error: 'Not Found' });
75
+ }
76
+ res.sendFile(path_1.default.join(simulatorDist, 'index.html'));
77
+ });
78
+ app.listen(simulatorPort, () => {
79
+ console.log(chalk_1.default.green(`\n📱 Simulator running at http://localhost:${simulatorPort}`));
80
+ console.log(chalk_1.default.cyan(` Widget running at http://localhost:${widgetPort}`));
81
+ console.log(chalk_1.default.dim(' Press Ctrl+C to stop.'));
82
+ });
83
+ process.on('SIGINT', () => {
84
+ widgetProcess.kill();
85
+ process.exit();
86
+ });
87
+ }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.initCommand = initCommand;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const inquirer_1 = __importDefault(require("inquirer"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const child_process_1 = require("child_process");
12
+ // In CommonJS, __dirname is available globally
13
+ async function initCommand(name) {
14
+ const targetDir = path_1.default.resolve(process.cwd(), name);
15
+ if (fs_extra_1.default.existsSync(targetDir)) {
16
+ console.error(chalk_1.default.red(`❌ Directory ${name} already exists.`));
17
+ process.exit(1);
18
+ }
19
+ console.log(chalk_1.default.blue(`🚀 Creating new Klyb widget project: ${name}`));
20
+ // 1. Copy Template
21
+ // In dev (ts-node/src), template is in src/templates
22
+ // In prod (dist), template is in dist/templates (need to ensure build copies it)
23
+ // For now we assume we are running from src or built dist where structure is preserved.
24
+ // Adjust path based on where we are executing.
25
+ // We need to resolve the template path relative to this file.
26
+ // If we are in dist/commands/init.js, template should be in ../templates/react-vite-ts relative to this file?
27
+ // No, standard is usually to keep templates as assets.
28
+ // Let's assume we are in src/commands/init.ts for dev flow or dist/commands/init.js
29
+ // Quick fix for path resolution in both envs: look for templates dir up the tree
30
+ const templateName = 'react-vite-ts';
31
+ let templateDir = path_1.default.join(__dirname, '../../templates', templateName); // from dist/commands or src/commands
32
+ // Fallback if structure is different (e.g. src vs dist) - explicit check
33
+ if (!fs_extra_1.default.existsSync(templateDir)) {
34
+ // Try source path if we are running from dist but templates are in src (common in local dev)
35
+ templateDir = path_1.default.join(__dirname, '../../src/templates', templateName);
36
+ }
37
+ if (!fs_extra_1.default.existsSync(templateDir)) {
38
+ console.error(chalk_1.default.red(`❌ Template not found at ${templateDir}`));
39
+ process.exit(1);
40
+ }
41
+ try {
42
+ await fs_extra_1.default.copy(templateDir, targetDir);
43
+ // 2. Customize package.json
44
+ const pkgPath = path_1.default.join(targetDir, 'package.json');
45
+ const pkg = await fs_extra_1.default.readJson(pkgPath);
46
+ pkg.name = name;
47
+ await fs_extra_1.default.writeJson(pkgPath, pkg, { spaces: 2 });
48
+ // 3. Rename files starting with _ (to avoid npm stripping them)
49
+ const gitignorePath = path_1.default.join(targetDir, '_gitignore');
50
+ if (fs_extra_1.default.existsSync(gitignorePath)) {
51
+ await fs_extra_1.default.move(gitignorePath, path_1.default.join(targetDir, '.gitignore'));
52
+ }
53
+ const envExamplePath = path_1.default.join(targetDir, '_env.example');
54
+ if (fs_extra_1.default.existsSync(envExamplePath)) {
55
+ await fs_extra_1.default.move(envExamplePath, path_1.default.join(targetDir, '.env.example'));
56
+ }
57
+ console.log(chalk_1.default.green(`✅ Project created in ${targetDir}`));
58
+ // 4. Prompt for install
59
+ const { install } = await inquirer_1.default.prompt([{
60
+ type: 'confirm',
61
+ name: 'install',
62
+ message: 'Install dependencies now?',
63
+ default: true
64
+ }]);
65
+ if (install) {
66
+ console.log(chalk_1.default.yellow('📦 Installing dependencies...'));
67
+ (0, child_process_1.execSync)('npm install', { cwd: targetDir, stdio: 'inherit' });
68
+ console.log(chalk_1.default.green('✅ Dependencies installed'));
69
+ }
70
+ console.log('\nNext steps:');
71
+ console.log(chalk_1.default.cyan(` cd ${name}`));
72
+ console.log(chalk_1.default.cyan(' npm run dev'));
73
+ }
74
+ catch (e) {
75
+ console.error(chalk_1.default.red('❌ Failed to create project'), e);
76
+ }
77
+ }
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.loginCommand = loginCommand;
40
+ const chalk_1 = __importDefault(require("chalk"));
41
+ const http_1 = __importDefault(require("http"));
42
+ const credentials_1 = require("../credentials");
43
+ const DEFAULT_API_URL = process.env.KLYB_API_URL || 'http://localhost:3000';
44
+ /**
45
+ * `klyb login`
46
+ *
47
+ * 1. Starts a one-shot local HTTP server on a random port
48
+ * 2. Opens the browser to the backend's CLI login page
49
+ * 3. After the user logs in, the backend redirects back with the JWT
50
+ * 4. CLI saves the token to ~/.klyb/credentials.json
51
+ */
52
+ async function loginCommand() {
53
+ const apiUrl = DEFAULT_API_URL;
54
+ console.log(chalk_1.default.blue('🔑 Logging in to Klyb...'));
55
+ console.log(chalk_1.default.dim(` API: ${apiUrl}\n`));
56
+ const token = await waitForToken(apiUrl);
57
+ // Persist credentials
58
+ (0, credentials_1.saveCredentials)({
59
+ token,
60
+ apiUrl,
61
+ savedAt: new Date().toISOString(),
62
+ });
63
+ console.log(chalk_1.default.green('\n✅ Logged in successfully!'));
64
+ console.log(chalk_1.default.dim(` Credentials saved to: ${(0, credentials_1.credentialsPath)()}`));
65
+ console.log(chalk_1.default.dim(' You can now run: klyb deploy'));
66
+ }
67
+ function waitForToken(apiUrl) {
68
+ return new Promise((resolve, reject) => {
69
+ // One-shot HTTP server to receive the callback
70
+ const server = http_1.default.createServer((req, res) => {
71
+ const url = new URL(req.url, `http://localhost`);
72
+ if (url.pathname === '/callback') {
73
+ const token = url.searchParams.get('token');
74
+ const error = url.searchParams.get('error');
75
+ if (token) {
76
+ // Success — show a nice page to the user
77
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
78
+ res.end(`
79
+ <!DOCTYPE html>
80
+ <html>
81
+ <head><title>Klyb CLI</title></head>
82
+ <body style="font-family:sans-serif;text-align:center;padding:4rem;background:#0f172a;color:white">
83
+ <h1 style="color:#22d3ee">✅ Logged in!</h1>
84
+ <p style="color:#94a3b8">You can close this window and return to your terminal.</p>
85
+ </body>
86
+ </html>
87
+ `);
88
+ server.close();
89
+ resolve(token);
90
+ }
91
+ else {
92
+ res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
93
+ res.end(`<p>Error: ${error || 'unknown'}</p>`);
94
+ server.close();
95
+ reject(new Error(error || 'Login failed'));
96
+ }
97
+ }
98
+ else {
99
+ res.writeHead(404);
100
+ res.end();
101
+ }
102
+ });
103
+ // Listen on a random available port
104
+ server.listen(0, '127.0.0.1', () => {
105
+ const address = server.address();
106
+ const callbackUrl = `http://127.0.0.1:${address.port}/callback`;
107
+ const loginUrl = `${apiUrl}/api/auth/cli-login?redirect=${encodeURIComponent(callbackUrl)}`;
108
+ console.log(chalk_1.default.cyan(' Opening browser for authentication...'));
109
+ console.log(chalk_1.default.dim(` If the browser doesn't open, visit:\n ${loginUrl}\n`));
110
+ // Dynamic import to handle ESM-only open package
111
+ Promise.resolve().then(() => __importStar(require('open'))).then(({ default: openBrowser }) => openBrowser(loginUrl)).catch(() => { });
112
+ });
113
+ // Timeout after 5 minutes
114
+ const timeout = setTimeout(() => {
115
+ server.close();
116
+ reject(new Error('Login timed out. Please try again.'));
117
+ }, 5 * 60 * 1000);
118
+ server.on('close', () => clearTimeout(timeout));
119
+ server.on('error', reject);
120
+ });
121
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getCredentials = getCredentials;
7
+ exports.saveCredentials = saveCredentials;
8
+ exports.clearCredentials = clearCredentials;
9
+ exports.credentialsPath = credentialsPath;
10
+ const os_1 = __importDefault(require("os"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const fs_extra_1 = __importDefault(require("fs-extra"));
13
+ const CREDENTIALS_PATH = path_1.default.join(os_1.default.homedir(), '.klyb', 'credentials.json');
14
+ function getCredentials() {
15
+ try {
16
+ if (!fs_extra_1.default.existsSync(CREDENTIALS_PATH))
17
+ return null;
18
+ return fs_extra_1.default.readJsonSync(CREDENTIALS_PATH);
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ function saveCredentials(creds) {
25
+ fs_extra_1.default.ensureDirSync(path_1.default.dirname(CREDENTIALS_PATH));
26
+ fs_extra_1.default.writeJsonSync(CREDENTIALS_PATH, creds, { spaces: 2 });
27
+ }
28
+ function clearCredentials() {
29
+ if (fs_extra_1.default.existsSync(CREDENTIALS_PATH)) {
30
+ fs_extra_1.default.removeSync(CREDENTIALS_PATH);
31
+ }
32
+ }
33
+ function credentialsPath() {
34
+ return CREDENTIALS_PATH;
35
+ }
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const commander_1 = require("commander");
4
+ const init_1 = require("./commands/init");
5
+ const dev_1 = require("./commands/dev");
6
+ const deploy_1 = require("./commands/deploy");
7
+ const login_1 = require("./commands/login");
8
+ const program = new commander_1.Command();
9
+ program
10
+ .name('klyb')
11
+ .description('Klyb Developer CLI')
12
+ .version('0.1.0');
13
+ program
14
+ .command('login')
15
+ .description('Authenticate with Klyb (opens browser)')
16
+ .action(login_1.loginCommand);
17
+ program
18
+ .command('init')
19
+ .description('Initialize a new Klyb widget project')
20
+ .argument('<name>', 'Project name')
21
+ .action(init_1.initCommand);
22
+ program
23
+ .command('dev')
24
+ .description('Start development server with Simulator')
25
+ .action(dev_1.devCommand);
26
+ program
27
+ .command('deploy')
28
+ .description('Build and deploy widget to Klyb')
29
+ .option('--submit', 'Submit for validation immediately')
30
+ .action(deploy_1.deployCommand);
31
+ program.parse();