@astralkit/cli 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Blue Beacon Creative LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,198 @@
1
+ # @astralkit/cli
2
+
3
+ Install and manage AstralKit UI components in your project.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Initialize AstralKit in your project
9
+ npx @astralkit/cli init
10
+
11
+ # Add a component
12
+ npx @astralkit/cli add button-sizes
13
+
14
+ # Search for components
15
+ npx @astralkit/cli search "pricing table"
16
+ ```
17
+
18
+ ## Commands
19
+
20
+ ### `astralkit init`
21
+
22
+ Initialize AstralKit in your project. Detects your framework and creates `astralkit.config.json`.
23
+
24
+ ```bash
25
+ astralkit init
26
+ astralkit init --framework=react
27
+ astralkit init --dir=src/components/ui
28
+ ```
29
+
30
+ ### `astralkit add <component>`
31
+
32
+ Add a component to your project. Fetches code from AstralKit and writes it to your components directory.
33
+
34
+ ```bash
35
+ astralkit add avatar-variants
36
+ astralkit add pricing-table --framework=react
37
+ astralkit add data-table --overwrite
38
+ ```
39
+
40
+ Options:
41
+ - `-f, --framework` — Framework version (html, react, vue, nextjs, angular, svelte, astro)
42
+ - `--dir` — Override output directory
43
+ - `--overwrite` — Overwrite existing files
44
+
45
+ ### `astralkit list`
46
+
47
+ List all available components.
48
+
49
+ ```bash
50
+ astralkit list
51
+ astralkit list --category=forms
52
+ astralkit list --framework=react
53
+ astralkit list --pro
54
+ astralkit list --free
55
+ ```
56
+
57
+ ### `astralkit search <query>`
58
+
59
+ Search for components by name or description.
60
+
61
+ ```bash
62
+ astralkit search "data table"
63
+ astralkit search "pricing" --framework=react
64
+ ```
65
+
66
+ ### `astralkit diff <component>`
67
+
68
+ Show differences between your local component and the latest version.
69
+
70
+ ```bash
71
+ astralkit diff button-sizes
72
+ ```
73
+
74
+ ### `astralkit update <component>`
75
+
76
+ Update a component to the latest version from AstralKit.
77
+
78
+ ```bash
79
+ astralkit update button-sizes
80
+ astralkit update button-sizes --force
81
+ ```
82
+
83
+ ### `astralkit login`
84
+
85
+ Authenticate with AstralKit to access premium components. Opens your browser for secure login.
86
+
87
+ ```bash
88
+ astralkit login
89
+ ```
90
+
91
+ ### `astralkit logout`
92
+
93
+ Remove stored authentication.
94
+
95
+ ### `astralkit whoami`
96
+
97
+ Show current authentication status and subscription plan. Verifies your token with the server.
98
+
99
+ ## Premium Components
100
+
101
+ Some components require a Pro subscription. When you try to add a pro component without authentication:
102
+
103
+ ```bash
104
+ $ astralkit add pro-data-table
105
+ ✖ Failed to add component.
106
+ Authentication required. Run `astralkit login` to authenticate.
107
+ ```
108
+
109
+ After logging in, pro components install normally:
110
+
111
+ ```bash
112
+ $ astralkit login
113
+ ✔ Authenticated successfully!
114
+ Email: you@example.com
115
+ Plan: pro
116
+
117
+ $ astralkit add pro-data-table
118
+ ✔ Added Pro Data Table (react)
119
+ ```
120
+
121
+ ## Configuration
122
+
123
+ ### `astralkit.config.json`
124
+
125
+ Created by `astralkit init` in your project root:
126
+
127
+ ```json
128
+ {
129
+ "$schema": "https://astralkit.com/schema/config.json",
130
+ "framework": "nextjs",
131
+ "componentsDir": "src/components/ui",
132
+ "tokensImport": "@astralkit/tokens/css"
133
+ }
134
+ ```
135
+
136
+ ### `~/.astralkit/config.json`
137
+
138
+ Stores your authentication token (created by `astralkit login`). This file is created with restricted permissions (owner read/write only).
139
+
140
+ ## CI/CD Usage
141
+
142
+ For automated environments (CI/CD pipelines, Docker builds), set the `ASTRALKIT_TOKEN` environment variable instead of using browser login:
143
+
144
+ ```bash
145
+ # Set token from your AstralKit dashboard
146
+ export ASTRALKIT_TOKEN="your-api-token"
147
+
148
+ # Commands automatically use the env var
149
+ astralkit add button-sizes
150
+ astralkit whoami
151
+ ```
152
+
153
+ ## Environment Variables
154
+
155
+ | Variable | Description |
156
+ |----------|-------------|
157
+ | `ASTRALKIT_TOKEN` | API token for CI/CD (skips browser login) |
158
+ | `ASTRALKIT_API_URL` | Override API base URL (development only) |
159
+ | `ASTRALKIT_LOGIN_PORT` | Override callback port for login (default: 9876) |
160
+
161
+ ## Security
162
+
163
+ - Authentication tokens are stored at `~/.astralkit/config.json` with `0600` permissions (owner read/write only)
164
+ - The config directory is created with `0700` permissions
165
+ - Browser login uses CSRF state tokens to prevent cross-site attacks
166
+ - CORS is restricted to `astralkit.com` during authentication
167
+ - All API requests use HTTPS with 30-second timeouts
168
+ - No tokens, secrets, or credentials are ever hardcoded in the package
169
+
170
+ ## Troubleshooting
171
+
172
+ ### Port 9876 is in use
173
+
174
+ If `astralkit login` fails because the port is busy:
175
+
176
+ ```bash
177
+ # Use a different port
178
+ ASTRALKIT_LOGIN_PORT=9877 astralkit login
179
+ ```
180
+
181
+ ### Token expired
182
+
183
+ If you see "Session expired", run `astralkit login` again to get a fresh token.
184
+
185
+ ### Network errors
186
+
187
+ If you see timeout or connection errors, check your internet connection. The CLI requires access to `astralkit.com`.
188
+
189
+ ## Requirements
190
+
191
+ - Node.js 18+
192
+ - An AstralKit account for premium components
193
+
194
+ ## Links
195
+
196
+ - [AstralKit](https://astralkit.com)
197
+ - [Documentation](https://astralkit.com/docs)
198
+ - [Pricing](https://astralkit.com/pricing)
package/bin/index.js ADDED
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const chalk = require('chalk');
5
+ const path = require('path');
6
+
7
+ const pkg = require(path.join(__dirname, '..', 'package.json'));
8
+
9
+ const { initCommand } = require('../src/commands/init');
10
+ const { addCommand } = require('../src/commands/add');
11
+ const { loginCommand } = require('../src/commands/login');
12
+ const { listCommand } = require('../src/commands/list');
13
+ const { searchCommand } = require('../src/commands/search');
14
+ const { diffCommand } = require('../src/commands/diff');
15
+ const { updateCommand } = require('../src/commands/update');
16
+
17
+ const program = new Command();
18
+
19
+ program
20
+ .name('astralkit')
21
+ .description('AstralKit CLI — Install and manage UI components')
22
+ .version(pkg.version);
23
+
24
+ // ─── init ────────────────────────────────────────────
25
+ program
26
+ .command('init')
27
+ .description('Initialize AstralKit in your project')
28
+ .option('--framework <framework>', 'Framework to use (auto-detected if omitted)')
29
+ .option('--dir <directory>', 'Component output directory', 'src/components/ui')
30
+ .action(initCommand);
31
+
32
+ // ─── add ─────────────────────────────────────────────
33
+ program
34
+ .command('add <component>')
35
+ .description('Add a component to your project')
36
+ .option('-f, --framework <framework>', 'Framework version (html, react, vue, nextjs)')
37
+ .option('--dir <directory>', 'Override output directory')
38
+ .option('--overwrite', 'Overwrite existing files', false)
39
+ .action(addCommand);
40
+
41
+ // ─── login ───────────────────────────────────────────
42
+ program
43
+ .command('login')
44
+ .description('Authenticate with AstralKit for premium components')
45
+ .action(loginCommand);
46
+
47
+ // ─── logout ──────────────────────────────────────────
48
+ program
49
+ .command('logout')
50
+ .description('Remove stored authentication')
51
+ .action(async () => {
52
+ const { clearToken } = require('../src/lib/auth');
53
+ clearToken();
54
+ console.log(chalk.green('Logged out successfully.'));
55
+ });
56
+
57
+ // ─── list ────────────────────────────────────────────
58
+ program
59
+ .command('list')
60
+ .description('List available components')
61
+ .option('-c, --category <category>', 'Filter by category')
62
+ .option('--framework <framework>', 'Filter by supported framework')
63
+ .option('--pro', 'Show only pro components', false)
64
+ .option('--free', 'Show only free components', false)
65
+ .action(listCommand);
66
+
67
+ // ─── search ──────────────────────────────────────────
68
+ program
69
+ .command('search <query>')
70
+ .description('Search for components')
71
+ .option('--framework <framework>', 'Filter by supported framework')
72
+ .action(searchCommand);
73
+
74
+ // ─── diff ────────────────────────────────────────────
75
+ program
76
+ .command('diff <component>')
77
+ .description('Show differences between local and remote component')
78
+ .option('-f, --framework <framework>', 'Framework version')
79
+ .option('--dir <directory>', 'Override component directory')
80
+ .action(diffCommand);
81
+
82
+ // ─── update ──────────────────────────────────────────
83
+ program
84
+ .command('update <component>')
85
+ .description('Update a component to the latest version')
86
+ .option('-f, --framework <framework>', 'Framework version')
87
+ .option('--dir <directory>', 'Override component directory')
88
+ .option('--force', 'Skip confirmation prompt', false)
89
+ .action(updateCommand);
90
+
91
+ // ─── whoami ──────────────────────────────────────────
92
+ program
93
+ .command('whoami')
94
+ .description('Show current authentication status and subscription plan')
95
+ .action(async () => {
96
+ const { getAuth } = require('../src/lib/auth');
97
+ const ora = require('ora');
98
+
99
+ const auth = getAuth();
100
+
101
+ if (!auth.token) {
102
+ if (auth.expired) {
103
+ console.log(chalk.yellow('Session expired.'));
104
+ console.log(chalk.gray('Run'), chalk.cyan('astralkit login'), chalk.gray('to re-authenticate.'));
105
+ } else {
106
+ console.log(chalk.yellow('Not authenticated.'));
107
+ console.log(chalk.gray('Run'), chalk.cyan('astralkit login'), chalk.gray('to authenticate.'));
108
+ }
109
+ return;
110
+ }
111
+
112
+ // Verify with server
113
+ const spinner = ora('Verifying authentication...').start();
114
+ try {
115
+ const { verifyAuth } = require('../src/lib/api');
116
+ const data = await verifyAuth();
117
+ spinner.stop();
118
+
119
+ console.log(chalk.green('Authenticated as:'), data.email || 'unknown');
120
+ console.log(chalk.gray('Plan:'), data.plan || 'free');
121
+ if (auth.source === 'env') {
122
+ console.log(chalk.gray('Source: ASTRALKIT_TOKEN environment variable'));
123
+ }
124
+ } catch (err) {
125
+ spinner.stop();
126
+
127
+ if (err.code === 'AUTH_REQUIRED') {
128
+ console.log(chalk.yellow('Token is invalid or expired.'));
129
+ console.log(chalk.gray('Run'), chalk.cyan('astralkit login'), chalk.gray('to re-authenticate.'));
130
+ } else {
131
+ // Offline fallback — show cached info
132
+ console.log(chalk.green('Authenticated as:'), auth.email || 'unknown');
133
+ console.log(chalk.gray('Plan:'), auth.plan || 'unknown', chalk.gray('(cached — unable to verify)'));
134
+ }
135
+ }
136
+ });
137
+
138
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@astralkit/cli",
3
+ "version": "2.0.0",
4
+ "description": "AstralKit CLI — Install and manage UI components in your project",
5
+ "license": "MIT",
6
+ "bin": {
7
+ "astralkit": "bin/index.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "LICENSE"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18.0.0"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public",
19
+ "registry": "https://registry.npmjs.org"
20
+ },
21
+ "scripts": {
22
+ "prepublishOnly": "node -e \"require('./package.json')\""
23
+ },
24
+ "keywords": [
25
+ "astralkit",
26
+ "ui",
27
+ "components",
28
+ "tailwind",
29
+ "cli",
30
+ "react",
31
+ "nextjs",
32
+ "vue",
33
+ "svelte"
34
+ ],
35
+ "homepage": "https://astralkit.com",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/efpacc/Astral-new.git",
39
+ "directory": "packages/cli"
40
+ },
41
+ "bugs": {
42
+ "url": "https://github.com/efpacc/Astral-new/issues"
43
+ },
44
+ "dependencies": {
45
+ "commander": "^12.0.0",
46
+ "chalk": "^4.1.2",
47
+ "ora": "^5.4.1",
48
+ "inquirer": "^8.2.6",
49
+ "open": "^8.4.2"
50
+ }
51
+ }
@@ -0,0 +1,104 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const ora = require('ora');
5
+ const { getComponent } = require('../lib/api');
6
+ const { getProjectConfig, detectFramework } = require('../lib/config');
7
+ const { validateSlug, validateFramework, isSafePath } = require('../lib/validators');
8
+
9
+ async function addCommand(componentSlug, options) {
10
+ // Validate inputs
11
+ if (!validateSlug(componentSlug)) process.exit(1);
12
+ if (!validateFramework(options.framework)) process.exit(1);
13
+
14
+ // Resolve framework
15
+ const config = getProjectConfig();
16
+ const framework = options.framework || config?.framework || detectFramework();
17
+ const componentsDir = options.dir || config?.componentsDir || 'src/components/ui';
18
+ const targetDir = path.resolve(process.cwd(), componentsDir, componentSlug);
19
+
20
+ // Check for existing files
21
+ if (fs.existsSync(targetDir) && !options.overwrite) {
22
+ console.log(chalk.yellow(`Component already exists at ${componentsDir}/${componentSlug}/`));
23
+ console.log(chalk.gray('Use --overwrite to replace existing files.'));
24
+ return;
25
+ }
26
+
27
+ const spinner = ora(`Fetching ${componentSlug} (${framework})...`).start();
28
+
29
+ try {
30
+ const data = await getComponent(componentSlug, framework);
31
+
32
+ if (!data.files || data.files.length === 0) {
33
+ spinner.fail(`No ${framework} code available for "${componentSlug}".`);
34
+ if (data.supported_frameworks?.length) {
35
+ console.log(chalk.gray(`Available frameworks: ${data.supported_frameworks.join(', ')}`));
36
+ }
37
+ return;
38
+ }
39
+
40
+ // Create component directory
41
+ fs.mkdirSync(targetDir, { recursive: true });
42
+
43
+ // Write each file (with path traversal protection)
44
+ for (const file of data.files) {
45
+ if (!isSafePath(targetDir, file.path)) {
46
+ console.log(chalk.red(` Skipping suspicious file path: ${file.path}`));
47
+ continue;
48
+ }
49
+ const filePath = path.resolve(targetDir, file.path);
50
+ const fileDir = path.dirname(filePath);
51
+ if (!fs.existsSync(fileDir)) {
52
+ fs.mkdirSync(fileDir, { recursive: true });
53
+ }
54
+ fs.writeFileSync(filePath, file.content);
55
+ }
56
+
57
+ spinner.succeed(`Added ${chalk.cyan(data.name || componentSlug)} (${framework})`);
58
+
59
+ // Show files written
60
+ console.log(chalk.gray(` ${componentsDir}/${componentSlug}/`));
61
+ for (const file of data.files) {
62
+ console.log(chalk.gray(` ${file.path}`));
63
+ }
64
+
65
+ // Show dependencies to install
66
+ if (data.dependencies?.length) {
67
+ console.log();
68
+ console.log(chalk.yellow('Install required dependencies:'));
69
+ console.log(chalk.cyan(` npm install ${data.dependencies.join(' ')}`));
70
+ }
71
+
72
+ // Pro badge
73
+ if (data.is_pro) {
74
+ console.log();
75
+ console.log(chalk.magenta('Pro component — thank you for your subscription!'));
76
+ }
77
+
78
+ } catch (err) {
79
+ spinner.fail('Failed to add component.');
80
+
81
+ if (err.code === 'AUTH_REQUIRED') {
82
+ console.log(chalk.yellow(err.message));
83
+ } else if (err.code === 'PRO_REQUIRED') {
84
+ console.log(chalk.yellow(err.message));
85
+ } else {
86
+ // Try to parse API error for framework suggestions
87
+ try {
88
+ const body = JSON.parse(err.message.replace(/^API error \d+: /, ''));
89
+ if (body.supported_frameworks) {
90
+ console.log(chalk.yellow(`No ${framework} code available for "${componentSlug}".`));
91
+ console.log(chalk.gray(`Available frameworks: ${body.supported_frameworks.join(', ')}`));
92
+ console.log(chalk.gray(`Try: astralkit add ${componentSlug} --framework=${body.supported_frameworks[0]}`));
93
+ process.exit(1);
94
+ }
95
+ } catch {
96
+ // Not a JSON parse error, show raw message
97
+ }
98
+ console.error(chalk.red(err.message));
99
+ }
100
+ process.exit(1);
101
+ }
102
+ }
103
+
104
+ module.exports = { addCommand };
@@ -0,0 +1,107 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+ const ora = require('ora');
5
+ const { getComponent } = require('../lib/api');
6
+ const { getProjectConfig, detectFramework } = require('../lib/config');
7
+ const { validateSlug, validateFramework, isSafePath } = require('../lib/validators');
8
+
9
+ async function diffCommand(componentSlug, options) {
10
+ // Validate inputs
11
+ if (!validateSlug(componentSlug)) process.exit(1);
12
+ if (!validateFramework(options.framework)) process.exit(1);
13
+
14
+ const config = getProjectConfig();
15
+ const framework = options.framework || config?.framework || detectFramework();
16
+ const componentsDir = options.dir || config?.componentsDir || 'src/components/ui';
17
+ const targetDir = path.resolve(process.cwd(), componentsDir, componentSlug);
18
+
19
+ // Check if component exists locally
20
+ if (!fs.existsSync(targetDir)) {
21
+ console.log(chalk.yellow(`Component "${componentSlug}" not found locally at ${componentsDir}/${componentSlug}/`));
22
+ console.log(chalk.gray('Nothing to diff.'));
23
+ return;
24
+ }
25
+
26
+ const spinner = ora(`Fetching latest ${componentSlug} (${framework})...`).start();
27
+
28
+ try {
29
+ const data = await getComponent(componentSlug, framework);
30
+
31
+ if (!data.files || data.files.length === 0) {
32
+ spinner.fail(`No ${framework} code available for "${componentSlug}" on remote.`);
33
+ return;
34
+ }
35
+
36
+ spinner.stop();
37
+
38
+ console.log(chalk.blue.bold(`Diff: ${componentSlug} (${framework})`));
39
+ console.log();
40
+
41
+ let hasChanges = false;
42
+
43
+ for (const remoteFile of data.files) {
44
+ // Path traversal protection
45
+ if (!isSafePath(targetDir, remoteFile.path)) {
46
+ console.log(chalk.red(` Skipping suspicious file path: ${remoteFile.path}`));
47
+ continue;
48
+ }
49
+
50
+ const localPath = path.resolve(targetDir, remoteFile.path);
51
+
52
+ if (!fs.existsSync(localPath)) {
53
+ console.log(chalk.green(`+ ${remoteFile.path}`), chalk.gray('(new file on remote)'));
54
+ hasChanges = true;
55
+ continue;
56
+ }
57
+
58
+ const localContent = fs.readFileSync(localPath, 'utf8');
59
+ const remoteContent = remoteFile.content;
60
+
61
+ if (localContent === remoteContent) {
62
+ console.log(chalk.gray(` ${remoteFile.path}`), chalk.green('(up to date)'));
63
+ } else {
64
+ hasChanges = true;
65
+ console.log(chalk.yellow(`~ ${remoteFile.path}`), chalk.yellow('(modified)'));
66
+
67
+ // Show simple line count diff
68
+ const localLines = localContent.split('\n').length;
69
+ const remoteLines = remoteContent.split('\n').length;
70
+ const diff = remoteLines - localLines;
71
+ if (diff > 0) {
72
+ console.log(chalk.gray(` Remote has ${diff} more line(s)`));
73
+ } else if (diff < 0) {
74
+ console.log(chalk.gray(` Local has ${Math.abs(diff)} more line(s)`));
75
+ } else {
76
+ console.log(chalk.gray(` Same line count, content differs`));
77
+ }
78
+ }
79
+ }
80
+
81
+ // Check for local files not on remote
82
+ if (fs.existsSync(targetDir)) {
83
+ const localFiles = fs.readdirSync(targetDir);
84
+ const remoteFilenames = new Set(data.files.map(f => f.path));
85
+ for (const localFile of localFiles) {
86
+ if (!remoteFilenames.has(localFile)) {
87
+ console.log(chalk.red(`- ${localFile}`), chalk.gray('(local only, not on remote)'));
88
+ hasChanges = true;
89
+ }
90
+ }
91
+ }
92
+
93
+ console.log();
94
+ if (hasChanges) {
95
+ console.log(chalk.yellow('Changes detected.'));
96
+ console.log(chalk.gray(`Run ${chalk.cyan(`astralkit update ${componentSlug}`)} to update.`));
97
+ } else {
98
+ console.log(chalk.green('All files are up to date.'));
99
+ }
100
+ } catch (err) {
101
+ spinner.fail('Diff failed.');
102
+ console.error(chalk.red(err.message));
103
+ process.exit(1);
104
+ }
105
+ }
106
+
107
+ module.exports = { diffCommand };