@archetypeai/ds-cli 0.3.7

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 (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/bin.js +77 -0
  4. package/commands/add.js +42 -0
  5. package/commands/create.js +238 -0
  6. package/commands/init.js +199 -0
  7. package/files/AGENTS.md +63 -0
  8. package/files/CLAUDE.md +63 -0
  9. package/files/LICENSE +21 -0
  10. package/files/rules/accessibility.md +219 -0
  11. package/files/rules/charts.md +352 -0
  12. package/files/rules/components.md +267 -0
  13. package/files/rules/design-principles.md +56 -0
  14. package/files/rules/linting.md +31 -0
  15. package/files/rules/state.md +405 -0
  16. package/files/rules/styling.md +245 -0
  17. package/files/skills/apply-ds/SKILL.md +117 -0
  18. package/files/skills/apply-ds/scripts/setup.sh +271 -0
  19. package/files/skills/build-pattern/SKILL.md +202 -0
  20. package/files/skills/create-dashboard/SKILL.md +189 -0
  21. package/files/skills/deploy-worker/SKILL.md +231 -0
  22. package/files/skills/deploy-worker/references/wrangler-commands.md +327 -0
  23. package/files/skills/fix-accessibility/SKILL.md +184 -0
  24. package/files/skills/fix-metadata/SKILL.md +118 -0
  25. package/files/skills/fix-metadata/assets/favicon.ico +0 -0
  26. package/files/skills/setup-chart/SKILL.md +225 -0
  27. package/files/skills/setup-chart/data/embedding.csv +42 -0
  28. package/files/skills/setup-chart/data/timeseries.csv +173 -0
  29. package/files/skills/setup-chart/references/scatter-chart.md +229 -0
  30. package/files/skills/setup-chart/references/sensor-chart.md +156 -0
  31. package/lib/add-ds-config-codeagent.js +154 -0
  32. package/lib/add-ds-ui-svelte.js +93 -0
  33. package/lib/scaffold-ds-svelte-project.js +272 -0
  34. package/lib/use-package-manager.js +65 -0
  35. package/lib/use-shadcn-svelte-registry.js +26 -0
  36. package/lib/validate-url.js +31 -0
  37. package/package.json +34 -0
@@ -0,0 +1,156 @@
1
+ # SensorChart Pattern Reference
2
+
3
+ Line charts for time series data, sensor readings, and streaming signals.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npx shadcn-svelte@latest add --registry https://design-system.archetypeai.workers.dev/r sensor-chart
9
+ ```
10
+
11
+ This installs `SensorChart.svelte` and its dependencies (`card`, `chart`, `utils`, `layerchart`, `d3-scale`, `d3-shape`).
12
+
13
+ ## Props
14
+
15
+ | Prop | Type | Default | Description |
16
+ | ----------- | -------------------------------------- | ------------------ | ------------------------------------------------- |
17
+ | `title` | string | `"UNKNOWN SENSOR"` | Card header label identifying the sensor |
18
+ | `icon` | Component | undefined | Lucide icon component for header |
19
+ | `data` | Array<Record<string, any>> | `[]` | Array of data points to chart |
20
+ | `signals` | Record<string, string> | `{}` | Map of data key to display label (defines series) |
21
+ | `xKey` | string | `"timestamp"` | Data key for x-axis values |
22
+ | `maxPoints` | number | undefined | Enables streaming mode with sliding window |
23
+ | `yMin` | number | **required** | Minimum y-axis value |
24
+ | `yMax` | number | **required** | Maximum y-axis value |
25
+ | `yTicks` | number[] | undefined | Explicit y-axis tick values |
26
+ | `axis` | `"both"` \| `"x"` \| `"y"` \| `"none"` | `"both"` | Which axes to display |
27
+ | `class` | string | undefined | Additional CSS classes |
28
+
29
+ ## Key Behaviors
30
+
31
+ - **Static mode** (no `maxPoints`): Uses `scaleUtc()` for x-axis with timestamp-based positioning. X-axis shows MM:SS relative to first data point.
32
+ - **Streaming mode** (`maxPoints` set): Uses `scaleLinear()` with index-based x positioning (`_index`). Data window slides as new points arrive.
33
+ - **Series**: Derived from `signals` prop. Each entry maps a data key to a display label and auto-assigns theme colors (`--chart-1` through `--chart-5`).
34
+ - **Curve**: Uses `curveNatural` from d3-shape for smooth lines.
35
+ - **Chart type**: Uses `LineChart` from layerchart — NOT `AreaChart` (which renders with unwanted fill).
36
+
37
+ ## Complete Example — Static Data
38
+
39
+ ```svelte
40
+ <script>
41
+ import SensorChart from '$lib/components/ui/SensorChart.svelte';
42
+ import AudioWaveformIcon from '@lucide/svelte/icons/audio-waveform';
43
+ import timeseriesCsv from '$lib/data/timeseries.csv?raw';
44
+
45
+ function parseCsv(csvText) {
46
+ const lines = csvText
47
+ .trim()
48
+ .split('\n')
49
+ .filter((line) => line.trim());
50
+ if (lines.length === 0) return [];
51
+ const headers = lines[0].split(',').map((h) => h.trim());
52
+ return lines.slice(1).map((line) => {
53
+ const values = line.split(',');
54
+ const row = {};
55
+ headers.forEach((header, index) => {
56
+ row[header] = values[index]?.trim() || '';
57
+ });
58
+ return row;
59
+ });
60
+ }
61
+
62
+ const rawRows = parseCsv(timeseriesCsv);
63
+ const data = rawRows.map((row) => ({
64
+ timestamp: new Date(row.timestamp),
65
+ accel_x: parseFloat(row.accel_x),
66
+ accel_y: parseFloat(row.accel_y),
67
+ accel_z: parseFloat(row.accel_z)
68
+ }));
69
+ </script>
70
+
71
+ <SensorChart
72
+ title="ACCELERATION SENSOR"
73
+ icon={AudioWaveformIcon}
74
+ {data}
75
+ signals={{ accel_x: 'Accel X', accel_y: 'Accel Y', accel_z: 'Accel Z' }}
76
+ yMin={-30}
77
+ yMax={30}
78
+ yTicks={[-30, -15, 0, 15, 30]}
79
+ />
80
+ ```
81
+
82
+ ## Complete Example — Streaming Data
83
+
84
+ For real-time data with a sliding window, use the `maxPoints` prop and feed data incrementally:
85
+
86
+ ```svelte
87
+ <script>
88
+ import { onMount, onDestroy } from 'svelte';
89
+ import SensorChart from '$lib/components/ui/SensorChart.svelte';
90
+ import ThermometerIcon from '@lucide/svelte/icons/thermometer';
91
+
92
+ let streamingData = $state([]);
93
+ let interval;
94
+
95
+ onMount(() => {
96
+ interval = setInterval(() => {
97
+ streamingData = [
98
+ ...streamingData,
99
+ {
100
+ timestamp: new Date(),
101
+ gyro_x: (Math.random() - 0.5) * 4
102
+ }
103
+ ];
104
+ }, 120);
105
+ });
106
+
107
+ onDestroy(() => clearInterval(interval));
108
+ </script>
109
+
110
+ <SensorChart
111
+ title="GYROSCOPE SENSOR"
112
+ icon={ThermometerIcon}
113
+ data={streamingData}
114
+ signals={{ gyro_x: 'Gyro X' }}
115
+ maxPoints={50}
116
+ yMin={-2}
117
+ yMax={2}
118
+ yTicks={[-2, -1, 0, 1, 2]}
119
+ axis="y"
120
+ />
121
+ ```
122
+
123
+ ## Building Inline (Without Pattern)
124
+
125
+ If the `sensor-chart` pattern is not installed, build the equivalent using `card` + `chart` primitives with `LineChart` from layerchart:
126
+
127
+ ```svelte
128
+ <script>
129
+ import * as Chart from '$lib/components/ui/chart/index.js';
130
+ import { LineChart } from 'layerchart';
131
+ import { scaleUtc, scaleLinear } from 'd3-scale';
132
+ import { curveNatural } from 'd3-shape';
133
+
134
+ let { data } = $props();
135
+
136
+ const chartConfig = {
137
+ value: { label: 'Value', color: 'var(--chart-1)' }
138
+ };
139
+ </script>
140
+
141
+ <Chart.Container config={chartConfig} class="h-[220px] w-full">
142
+ <LineChart
143
+ {data}
144
+ x="timestamp"
145
+ xScale={scaleUtc()}
146
+ yScale={scaleLinear()}
147
+ series={[{ key: 'value', color: 'var(--chart-1)' }]}
148
+ tooltip={false}
149
+ props={{
150
+ spline: { curve: curveNatural, strokeWidth: 1.5 },
151
+ grid: { y: true, x: false },
152
+ highlight: { lines: false, points: false }
153
+ }}
154
+ />
155
+ </Chart.Container>
156
+ ```
@@ -0,0 +1,154 @@
1
+ import { existsSync, mkdirSync, copyFileSync, readdirSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import * as p from '@clack/prompts';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const FILES_DIR = join(__dirname, '..', 'files');
8
+
9
+ function copyDir(src, dest) {
10
+ if (!existsSync(src)) return;
11
+
12
+ mkdirSync(dest, { recursive: true });
13
+
14
+ const entries = readdirSync(src, { withFileTypes: true });
15
+ for (const entry of entries) {
16
+ const srcPath = join(src, entry.name);
17
+ const destPath = join(dest, entry.name);
18
+
19
+ if (entry.isDirectory()) {
20
+ copyDir(srcPath, destPath);
21
+ } else {
22
+ if (existsSync(destPath)) {
23
+ p.log.warn(`Skipped (exists): ${destPath}`);
24
+ } else {
25
+ copyFileSync(srcPath, destPath);
26
+ p.log.success(`Copied: ${destPath}`);
27
+ }
28
+ }
29
+ }
30
+ }
31
+
32
+ function copyFile(src, dest) {
33
+ if (!existsSync(src)) {
34
+ p.log.warn(`Source not found: ${src}`);
35
+ return false;
36
+ }
37
+
38
+ if (existsSync(dest)) {
39
+ p.log.warn(`Skipped (exists): ${dest}`);
40
+ return false;
41
+ }
42
+
43
+ copyFileSync(src, dest);
44
+ p.log.success(`Copied: ${dest}`);
45
+ return true;
46
+ }
47
+
48
+ export function setupCursor(projectDir) {
49
+ p.log.step('Setting up for Cursor IDE');
50
+
51
+ // copy AGENTS.md to root
52
+ copyFile(join(FILES_DIR, 'AGENTS.md'), join(projectDir, 'AGENTS.md'));
53
+
54
+ // copy skills to .cursor/skills/
55
+ const skillsSrc = join(FILES_DIR, 'skills');
56
+ if (existsSync(skillsSrc)) {
57
+ copyDir(skillsSrc, join(projectDir, '.cursor', 'skills'));
58
+ }
59
+
60
+ // copy rules to .cursor/rules/
61
+ const rulesSrc = join(FILES_DIR, 'rules');
62
+ if (existsSync(rulesSrc)) {
63
+ copyDir(rulesSrc, join(projectDir, '.cursor', 'rules'));
64
+ }
65
+
66
+ // copy LICENSE to .cursor/
67
+ copyFile(join(FILES_DIR, 'LICENSE'), join(projectDir, '.cursor', 'LICENSE'));
68
+
69
+ p.log.success('Cursor setup complete');
70
+ p.log.info('Tip: Reload Cursor window (Cmd+Shift+P > "Reload Window")');
71
+ }
72
+
73
+ export function setupClaude(projectDir) {
74
+ p.log.step('Setting up for Claude Code');
75
+
76
+ // copy CLAUDE.md to root
77
+ copyFile(join(FILES_DIR, 'CLAUDE.md'), join(projectDir, 'CLAUDE.md'));
78
+
79
+ // copy skills to .claude/skills/
80
+ const skillsSrc = join(FILES_DIR, 'skills');
81
+ if (existsSync(skillsSrc)) {
82
+ copyDir(skillsSrc, join(projectDir, '.claude', 'skills'));
83
+ }
84
+
85
+ // copy rules to .claude/rules/
86
+ const rulesSrc = join(FILES_DIR, 'rules');
87
+ if (existsSync(rulesSrc)) {
88
+ copyDir(rulesSrc, join(projectDir, '.claude', 'rules'));
89
+ }
90
+
91
+ // copy LICENSE to .claude/
92
+ copyFile(join(FILES_DIR, 'LICENSE'), join(projectDir, '.claude', 'LICENSE'));
93
+
94
+ p.log.success('Claude Code setup complete');
95
+ }
96
+
97
+ export async function addDsConfigCodeagent(flags) {
98
+ const isCursor = flags.includes('--cursor');
99
+ const isClaude = flags.includes('--claude');
100
+ const isHelp = flags.includes('--help') || flags.includes('-h');
101
+
102
+ if (isHelp) {
103
+ console.log(`
104
+ ds add ds-config-codeagent - Install agent configurations
105
+
106
+ Usage:
107
+ npx @archetypeai/ds-cli add ds-config-codeagent --cursor Setup for Cursor IDE
108
+ npx @archetypeai/ds-cli add ds-config-codeagent --claude Setup for Claude Code
109
+ `);
110
+ return;
111
+ }
112
+
113
+ let agent;
114
+
115
+ if (isCursor && isClaude) {
116
+ p.log.error('Please specify only one: --cursor or --claude');
117
+ process.exit(1);
118
+ } else if (isCursor) {
119
+ agent = 'cursor';
120
+ } else if (isClaude) {
121
+ agent = 'claude';
122
+ } else {
123
+ // interactive prompt when no flag is passed
124
+ agent = await p.select({
125
+ message: 'Which agent configuration?',
126
+ options: [
127
+ { value: 'cursor', label: 'Cursor', hint: 'AGENTS.md + skills + rules' },
128
+ { value: 'claude', label: 'Claude Code', hint: 'CLAUDE.md + skills + rules' }
129
+ ]
130
+ });
131
+ if (p.isCancel(agent)) {
132
+ p.cancel('Setup cancelled.');
133
+ process.exit(0);
134
+ }
135
+ }
136
+
137
+ // verify files directory exists
138
+ if (!existsSync(FILES_DIR)) {
139
+ p.log.error('Package files not found. This may be a corrupted installation.');
140
+ process.exit(1);
141
+ }
142
+
143
+ p.intro('Installing Archetype AI agent configurations.');
144
+
145
+ const projectDir = process.cwd();
146
+
147
+ if (agent === 'cursor') {
148
+ setupCursor(projectDir);
149
+ } else {
150
+ setupClaude(projectDir);
151
+ }
152
+
153
+ p.outro('Agent configuration installed.');
154
+ }
@@ -0,0 +1,93 @@
1
+ import { execFileSync } from 'child_process';
2
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
3
+ import { join, dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import * as p from '@clack/prompts';
6
+ import { validateRegistryUrl } from './validate-url.js';
7
+ import { detectPm, getPm } from './use-package-manager.js';
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const REGISTRY_URL = process.env.REGISTRY_URL || 'https://design-system.archetypeai.workers.dev';
11
+ const ALL_COMPONENTS_URL = `${REGISTRY_URL}/r/all.json`;
12
+
13
+ export async function addDsUiSvelte() {
14
+ p.intro('Install all components from Archetype AI Design System.');
15
+
16
+ const projectDir = process.cwd();
17
+
18
+ // check if shadcn-svelte is initialized
19
+ if (!existsSync(join(projectDir, 'components.json'))) {
20
+ p.log.error('shadcn-svelte is not initialized in this project.');
21
+ p.log.info('Please run: npx shadcn-svelte@latest init');
22
+ process.exit(1);
23
+ }
24
+
25
+ // detect package manager from lockfile, fall back to npm
26
+ const pmName = detectPm(projectDir) || 'npm';
27
+ const pm = getPm(pmName);
28
+
29
+ const s = p.spinner();
30
+ s.start('Fetching component list from registry');
31
+
32
+ try {
33
+ const response = await fetch(ALL_COMPONENTS_URL, {
34
+ signal: AbortSignal.timeout(15_000)
35
+ });
36
+ if (!response.ok) {
37
+ throw new Error(`Failed to fetch components: ${response.status} ${response.statusText}`);
38
+ }
39
+
40
+ const componentUrls = await response.json();
41
+ if (!Array.isArray(componentUrls) || componentUrls.length === 0) {
42
+ throw new Error('No components found in registry');
43
+ }
44
+
45
+ // validate all URLs before passing to shell
46
+ for (const url of componentUrls) {
47
+ validateRegistryUrl(url);
48
+ }
49
+
50
+ s.stop(`Found ${componentUrls.length} components`);
51
+
52
+ const installSpinner = p.spinner();
53
+ installSpinner.start('Installing components');
54
+
55
+ const [dlxCmd, ...dlxArgs] = pm.dlx.split(/\s+/);
56
+ execFileSync(
57
+ dlxCmd,
58
+ [...dlxArgs, 'shadcn-svelte@latest', 'add', ...componentUrls, '--yes', '--overwrite'],
59
+ { stdio: 'pipe', cwd: projectDir }
60
+ );
61
+
62
+ installSpinner.stop('All components installed');
63
+
64
+ // copy LICENSE alongside installed components
65
+ try {
66
+ const licenseSrc = join(__dirname, '..', 'LICENSE');
67
+ if (existsSync(licenseSrc)) {
68
+ let componentsDir = 'src/lib/components/ui';
69
+ try {
70
+ const config = JSON.parse(readFileSync(join(projectDir, 'components.json'), 'utf8'));
71
+ if (config.aliases?.components) {
72
+ componentsDir = config.aliases.components.replace('$lib', 'src/lib');
73
+ }
74
+ } catch {
75
+ p.log.warn('Could not read components.json alias config, using default path');
76
+ }
77
+ const licenseDest = join(projectDir, componentsDir, 'LICENSE');
78
+ if (!existsSync(licenseDest)) {
79
+ writeFileSync(licenseDest, readFileSync(licenseSrc, 'utf8'));
80
+ p.log.success(`License copied to ${componentsDir}/LICENSE`);
81
+ }
82
+ }
83
+ } catch {
84
+ p.log.warn('Could not copy LICENSE file');
85
+ }
86
+
87
+ p.outro('Components installed successfully.');
88
+ } catch (error) {
89
+ s.stop('Failed');
90
+ p.log.error(error.message);
91
+ process.exit(1);
92
+ }
93
+ }
@@ -0,0 +1,272 @@
1
+ import { execFileSync } from 'child_process';
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
3
+ import { join } from 'path';
4
+ import * as p from '@clack/prompts';
5
+ import { setupCursor, setupClaude } from './add-ds-config-codeagent.js';
6
+
7
+ // run a command without a shell to prevent injection
8
+ function run(command, args, opts) {
9
+ execFileSync(command, args, { stdio: 'pipe', ...opts });
10
+ }
11
+
12
+ // write a file
13
+ function writeFile(filePath, content) {
14
+ const dir = filePath.substring(0, filePath.lastIndexOf('/'));
15
+ if (!existsSync(dir)) {
16
+ mkdirSync(dir, { recursive: true });
17
+ }
18
+ writeFileSync(filePath, content);
19
+ }
20
+
21
+ // split a package manager command string into [command, ...args]
22
+ function splitCmd(cmdString) {
23
+ return cmdString.split(/\s+/);
24
+ }
25
+
26
+ // run sveltekit create
27
+ export function runSvCreate(pm, name, targetDir) {
28
+ const [cmd, ...baseArgs] = splitCmd(pm.dlx);
29
+ const args = [
30
+ ...baseArgs,
31
+ 'sv@latest',
32
+ 'create',
33
+ name,
34
+ '--template',
35
+ 'minimal',
36
+ '--no-types',
37
+ '--add',
38
+ 'tailwindcss=plugins:none',
39
+ ...splitCmd(pm.svInstallFlag)
40
+ ];
41
+ const s = p.spinner();
42
+ s.start('Creating SvelteKit project');
43
+ try {
44
+ run(cmd, args, { cwd: targetDir });
45
+ s.stop('SvelteKit project created');
46
+ return true;
47
+ } catch (error) {
48
+ s.stop('Failed to create SvelteKit project');
49
+ p.log.error(error.stderr?.toString() || error.message);
50
+ return false;
51
+ }
52
+ }
53
+
54
+ // install design tokens
55
+ export function installTokens(pm, projectPath) {
56
+ const [cmd, ...baseArgs] = splitCmd(pm.install);
57
+ const args = [...baseArgs, '@archetypeai/ds-lib-tokens'];
58
+ const s = p.spinner();
59
+ s.start('Installing design tokens');
60
+ try {
61
+ run(cmd, args, { cwd: projectPath });
62
+ s.stop('Design tokens installed');
63
+ return true;
64
+ } catch (error) {
65
+ s.stop('Failed to install design tokens');
66
+ p.log.error(error.stderr?.toString() || error.message);
67
+ return false;
68
+ }
69
+ }
70
+
71
+ // install internal fonts package
72
+ export function installFonts(pm, projectPath) {
73
+ const [cmd, ...baseArgs] = splitCmd(pm.install);
74
+ const args = [...baseArgs, '@archetypeai/ds-lib-fonts-internal'];
75
+ const s = p.spinner();
76
+ s.start('Installing internal fonts');
77
+ try {
78
+ run(cmd, args, { cwd: projectPath });
79
+ s.stop('Internal fonts installed');
80
+ return true;
81
+ } catch {
82
+ s.stop('Failed to install internal fonts');
83
+ p.log.warn('Make sure you are authenticated with npm (run: npm login)');
84
+ return false;
85
+ }
86
+ }
87
+
88
+ // init shadcn-svelte
89
+ export function initShadcn(pm, projectPath) {
90
+ const s = p.spinner();
91
+ s.start('Setting up shadcn-svelte');
92
+ try {
93
+ const componentsJson = {
94
+ $schema: 'https://shadcn-svelte.com/schema.json',
95
+ style: 'default',
96
+ tailwind: {
97
+ config: '',
98
+ css: 'src/routes/layout.css',
99
+ baseColor: 'slate',
100
+ cssVariables: true,
101
+ prefix: ''
102
+ },
103
+ aliases: {
104
+ utils: '$lib/utils',
105
+ primitives: '$lib/components/primitives',
106
+ lib: '$lib',
107
+ components: '$lib/components'
108
+ },
109
+ typescript: false
110
+ };
111
+
112
+ const componentsJsonPath = join(projectPath, 'components.json');
113
+ if (existsSync(componentsJsonPath)) {
114
+ p.log.info('components.json already exists, skipping');
115
+ } else {
116
+ writeFile(componentsJsonPath, JSON.stringify(componentsJson, null, 2));
117
+ }
118
+
119
+ const utilsPath = join(projectPath, 'src/lib/utils.js');
120
+ const utilsContent = `import { clsx } from "clsx";
121
+ import { twMerge } from "tailwind-merge";
122
+
123
+ export function cn(...inputs) {
124
+ \treturn twMerge(clsx(inputs));
125
+ }
126
+ `;
127
+ if (existsSync(utilsPath)) {
128
+ p.log.info('src/lib/utils.js already exists, skipping');
129
+ } else {
130
+ writeFile(utilsPath, utilsContent);
131
+ }
132
+
133
+ const [cmd, ...baseArgs] = splitCmd(pm.install);
134
+ run(cmd, [...baseArgs, 'clsx', 'tailwind-merge', 'tw-animate-css'], { cwd: projectPath });
135
+
136
+ s.stop('shadcn-svelte ready');
137
+ return true;
138
+ } catch (error) {
139
+ s.stop('Failed to set up shadcn-svelte');
140
+ p.log.error(error.stderr?.toString() || error.message);
141
+ return false;
142
+ }
143
+ }
144
+
145
+ // configure CSS
146
+ export function configureCss(projectPath, includeFonts) {
147
+ let css = '';
148
+ if (includeFonts) {
149
+ css += '@import "@archetypeai/ds-lib-fonts-internal";\n';
150
+ }
151
+ css += '@import "@archetypeai/ds-lib-tokens/theme.css";\n';
152
+ css += '@import "tailwindcss";\n';
153
+ css += '@import "tw-animate-css";\n';
154
+
155
+ const cssPath = join(projectPath, 'src/routes/layout.css');
156
+ writeFile(cssPath, css);
157
+ p.log.success('CSS configured');
158
+ }
159
+
160
+ // install Tailwind CSS v4
161
+ export function installTailwind(pm, projectPath) {
162
+ const [cmd, ...baseArgs] = splitCmd(pm.install);
163
+ const args = [...baseArgs, 'tailwindcss@latest'];
164
+ const s = p.spinner();
165
+ s.start('Installing Tailwind CSS v4');
166
+ try {
167
+ run(cmd, args, { cwd: projectPath });
168
+ s.stop('Tailwind CSS installed');
169
+ return true;
170
+ } catch (error) {
171
+ s.stop('Failed to install Tailwind CSS');
172
+ p.log.error(error.stderr?.toString() || error.message);
173
+ return false;
174
+ }
175
+ }
176
+
177
+ // prepend CSS imports to existing layout.css
178
+ export function prependCss(projectPath, includeFonts) {
179
+ const cssPath = join(projectPath, 'src/routes/layout.css');
180
+
181
+ let existing = '';
182
+ if (existsSync(cssPath)) {
183
+ existing = readFileSync(cssPath, 'utf-8');
184
+ }
185
+
186
+ // skip if DS imports are already present
187
+ if (existing.includes('@archetypeai/ds-lib-tokens/theme.css')) {
188
+ p.log.info('DS imports already present in layout.css, skipping');
189
+ return;
190
+ }
191
+
192
+ let imports = '';
193
+ if (includeFonts) {
194
+ imports += '@import "@archetypeai/ds-lib-fonts-internal";\n';
195
+ }
196
+ imports += '@import "@archetypeai/ds-lib-tokens/theme.css";\n';
197
+ imports += '@import "tailwindcss";\n';
198
+ imports += '@import "tw-animate-css";\n';
199
+
200
+ const dir = cssPath.substring(0, cssPath.lastIndexOf('/'));
201
+ if (!existsSync(dir)) {
202
+ mkdirSync(dir, { recursive: true });
203
+ }
204
+ writeFileSync(cssPath, existing ? imports + '\n' + existing : imports);
205
+ p.log.success('CSS configured');
206
+ }
207
+
208
+ // install components from shadcn-svelte registry
209
+ export function installComponents(pm, projectPath, componentUrls) {
210
+ const s = p.spinner();
211
+ s.start(`Installing ${componentUrls.length} components`);
212
+ try {
213
+ const urls = componentUrls.map((c) => c.url);
214
+ const [dlxCmd, ...dlxArgs] = splitCmd(pm.dlx);
215
+ run(dlxCmd, [...dlxArgs, 'shadcn-svelte@latest', 'add', ...urls, '--yes', '--overwrite'], {
216
+ cwd: projectPath
217
+ });
218
+
219
+ const [installCmd, ...installArgs] = splitCmd(pm.install);
220
+ run(
221
+ installCmd,
222
+ [...installArgs, 'tailwind-variants', 'bits-ui', '@lucide/svelte', 'layerchart', 'paneforge'],
223
+ { cwd: projectPath }
224
+ );
225
+
226
+ s.stop(`${componentUrls.length} components installed`);
227
+ return true;
228
+ } catch (error) {
229
+ s.stop('Failed to install components');
230
+ p.log.error(error.stderr?.toString() || error.message);
231
+ return false;
232
+ }
233
+ }
234
+
235
+ // add a demo page with Button examples to app
236
+ export function createDemoPage(projectPath) {
237
+ const pagePath = join(projectPath, 'src/routes/+page.svelte');
238
+ const demoContent = `<script>
239
+ \timport Button from "$lib/components/ui/button/button.svelte";
240
+ </script>
241
+
242
+ <div class="flex flex-col items-center justify-center min-h-screen space-y-xl">
243
+ <Button variant="default">Default</Button>
244
+ <Button variant="ghost">Ghost</Button>
245
+ <Button variant="outline">Outline</Button>
246
+ <Button variant="secondary">Secondary</Button>
247
+ <Button variant="destructive">Destructive</Button>
248
+ <Button variant="link">Link</Button>
249
+ </div>
250
+ `;
251
+ writeFile(pagePath, demoContent);
252
+ p.log.success('Demo page created');
253
+ }
254
+
255
+ // install agent configuration
256
+ export function installAgentConfig(projectPath, agentType) {
257
+ const s = p.spinner();
258
+ s.start(`Installing ${agentType === 'cursor' ? 'Cursor' : 'Claude Code'} agent configuration`);
259
+ try {
260
+ if (agentType === 'cursor') {
261
+ setupCursor(projectPath);
262
+ } else {
263
+ setupClaude(projectPath);
264
+ }
265
+ s.stop('Agent configuration installed');
266
+ return true;
267
+ } catch (error) {
268
+ s.stop('Failed to install agent configuration');
269
+ p.log.error(error.message);
270
+ return false;
271
+ }
272
+ }