@superdesign/cli 0.1.4 → 0.1.6
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 +21 -0
- package/dist/config/constants.d.ts +4 -1
- package/dist/index.cjs +198 -356
- package/dist/index.js +196 -355
- package/dist/utils/analytics.d.ts +25 -0
- package/dist/utils/job-runner.d.ts +6 -0
- package/package.json +9 -8
package/dist/index.js
CHANGED
|
@@ -3,10 +3,11 @@ import { fileURLToPath } from "url";
|
|
|
3
3
|
import { dirname, join, resolve as external_path_resolve } from "path";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
6
|
-
import { homedir, hostname, platform, release } from "os";
|
|
6
|
+
import os, { homedir, hostname, platform, release } from "os";
|
|
7
7
|
import axios from "axios";
|
|
8
8
|
import ora from "ora";
|
|
9
9
|
import { writeFile } from "fs/promises";
|
|
10
|
+
import { PostHog } from "posthog-node";
|
|
10
11
|
const CONFIG_DIR_NAME = '.superdesign';
|
|
11
12
|
const CONFIG_FILE_NAME = 'config.json';
|
|
12
13
|
const EXIT_CODES = {
|
|
@@ -20,6 +21,8 @@ const EXIT_CODES = {
|
|
|
20
21
|
};
|
|
21
22
|
const SKILLS_DIR = '.claude/skills/superdesign';
|
|
22
23
|
const SKILL_FILE_NAME = 'SKILL.md';
|
|
24
|
+
const POSTHOG_KEY = process.env.POSTHOG_KEY || 'phc_oUcDklFBX3wy8eksSyEC0pataKCgSadur1sio5hBHg4';
|
|
25
|
+
const POSTHOG_HOST = process.env.POSTHOG_HOST || 'https://eu.i.posthog.com';
|
|
23
26
|
function getConfigDir() {
|
|
24
27
|
return join(homedir(), CONFIG_DIR_NAME);
|
|
25
28
|
}
|
|
@@ -223,7 +226,7 @@ async function runAuthFlow(options = {}) {
|
|
|
223
226
|
try {
|
|
224
227
|
startSpinner('Creating auth session...');
|
|
225
228
|
const session = await createSession({
|
|
226
|
-
cliVersion: "0.1.
|
|
229
|
+
cliVersion: "0.1.6",
|
|
227
230
|
os: `${platform()} ${release()}`,
|
|
228
231
|
hostname: hostname()
|
|
229
232
|
});
|
|
@@ -422,269 +425,35 @@ function getSkillTemplate() {
|
|
|
422
425
|
return `---
|
|
423
426
|
name: superdesign
|
|
424
427
|
description: Superdesign is a design agent, where it specialised in frontend UI/UX design; Use this skill before you implement any UI that require some design thinking
|
|
428
|
+
metadata:
|
|
429
|
+
author: superdesign
|
|
430
|
+
version: "0.0.1"
|
|
425
431
|
---
|
|
426
432
|
|
|
427
|
-
SuperDesign helps you (1) find design inspirations/styles and (2)
|
|
428
|
-
|
|
429
|
-
Each SuperDesign canvas run creates a new node and returns a \`previewUrl\`.
|
|
433
|
+
SuperDesign helps you (1) find design inspirations/styles and (2) generate/iterate design drafts on an infinite canvas.
|
|
430
434
|
|
|
431
435
|
---
|
|
432
436
|
|
|
433
|
-
|
|
437
|
+
# Core scenarios (what this skill handles)
|
|
434
438
|
|
|
435
439
|
1. **Help me design X** (feature/page/flow)
|
|
436
440
|
2. **Set design system**
|
|
437
441
|
3. **Help me improve design of X**
|
|
438
442
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
## Tooling overview
|
|
442
|
-
|
|
443
|
-
### A) Inspiration & Style Tools (generic, always available)
|
|
444
|
-
Use these to discover style direction, references, and brand context:
|
|
445
|
-
|
|
446
|
-
- **Search prompt library** (style/components/pages)
|
|
447
|
-
\`\`\`bash
|
|
448
|
-
superdesign search-prompts --keyword "<keyword>" --json
|
|
449
|
-
superdesign search-prompts --tags "style" --json
|
|
450
|
-
superdesign search-prompts --tags "style" --keyword "<style keyword>" --json
|
|
451
|
-
\`\`\`
|
|
452
|
-
|
|
453
|
-
- **Get full prompt details**
|
|
454
|
-
\`\`\`bash
|
|
455
|
-
superdesign get-prompts --slugs "<slug1,slug2,...>" --json
|
|
456
|
-
\`\`\`
|
|
457
|
-
|
|
458
|
-
- **Extract brand guide from a URL**
|
|
459
|
-
\`\`\`bash
|
|
460
|
-
superdesign extract-brand-guide --url https://example.com --json
|
|
461
|
-
\`\`\`
|
|
462
|
-
|
|
463
|
-
### B) Canvas Design Tools (specialised, optional)
|
|
464
|
-
|
|
465
|
-
Use these when you want to explore multiple directions and show previews:
|
|
466
|
-
|
|
467
|
-
- Create project (supports prompt / prompt file / HTML)
|
|
468
|
-
- Create design draft
|
|
469
|
-
- Iterate design draft (replace / branch)
|
|
470
|
-
- Plan flow pages → execute flow pages
|
|
471
|
-
- Fetch nodes, export HTML
|
|
472
|
-
|
|
473
|
-
> Pixel-perfect HTML playground is ONLY required for Canvas workflows.
|
|
474
|
-
|
|
475
|
-
---
|
|
476
|
-
|
|
477
|
-
## Always-on rules
|
|
478
|
-
|
|
479
|
-
- Design system should live at: \`.superdesign/design-system.md\`
|
|
480
|
-
- If \`.superdesign/design-system.md\` is missing, run **Design System Setup** first.
|
|
481
|
-
- Use \`askQuestion\` to ask high-signal questions (constraints, taste, tradeoffs).
|
|
482
|
-
- For Canvas workflows: build a simplified/pixel-perfect prototype HTML in:
|
|
483
|
-
- \`.superdesign/playgrounds/<name>.html\`
|
|
484
|
-
- Always use \`--json\` for machine parsing.
|
|
485
|
-
|
|
486
|
-
---
|
|
487
|
-
|
|
488
|
-
## Playground rules (Canvas only)
|
|
489
|
-
|
|
490
|
-
**Playground = BEFORE state (what exists now).** It provides context for SuperDesign agent.
|
|
491
|
-
**Prompt = DESIRED CHANGE (what to add/modify).**
|
|
492
|
-
|
|
493
|
-
The playground HTML must contain **ONLY UI that currently exists in the codebase**. For NEW features/sections, describe them entirely in the \`--prompt\` flag — do NOT add wireframes, placeholders, or sketches to the playground HTML.
|
|
494
|
-
|
|
495
|
-
- **DO NOT** design or improve anything in the playground
|
|
496
|
-
- **DO NOT** add placeholder sections like \`<!-- NEW FEATURE - DESIGN THIS -->\`
|
|
497
|
-
- **DO** create pixel-perfect replica of current UI state
|
|
498
|
-
- Valid formats:
|
|
499
|
-
- **Component-only**: isolated component with enough markup to show core UX/states
|
|
500
|
-
- **Full page**: replica of page where component operates (shows surrounding context)
|
|
501
|
-
- Save to: \`.superdesign/playgrounds/<name>.html\`
|
|
502
|
-
|
|
503
|
-
### Example: Adding a "Book Demo" section to home page
|
|
504
|
-
|
|
505
|
-
**BAD approach:**
|
|
506
|
-
\`\`\`html
|
|
507
|
-
<!-- playground includes a sketched Book Demo section -->
|
|
508
|
-
<section class="book-demo">
|
|
509
|
-
<!-- DESIGN THIS - Add CTA here -->
|
|
510
|
-
<h3>Book a Demo</h3>
|
|
511
|
-
<button>Schedule</button>
|
|
512
|
-
</section>
|
|
513
|
-
\`\`\`
|
|
514
|
-
|
|
515
|
-
**GOOD approach:**
|
|
516
|
-
\`\`\`html
|
|
517
|
-
<!-- playground is pure replica of existing home page (hero + projects) -->
|
|
518
|
-
<!-- NO book demo section in HTML -->
|
|
519
|
-
\`\`\`
|
|
520
|
-
Then in the iterate command:
|
|
521
|
-
\`\`\`bash
|
|
522
|
-
--prompt "Add a Book Demo section between the hero and projects sections. Requirements: minimal card style, horizontal layout..."
|
|
523
|
-
\`\`\`
|
|
524
|
-
|
|
525
|
-
---
|
|
526
|
-
|
|
527
|
-
# 1) Design System Setup
|
|
528
|
-
|
|
529
|
-
### Step 0 — Ask user (one question)
|
|
530
|
-
|
|
531
|
-
"Do you want to **create a new design system** or **extract from the current codebase**?"
|
|
532
|
-
|
|
533
|
-
### A) Extract from codebase
|
|
534
|
-
|
|
535
|
-
1. Investigate codebase:
|
|
536
|
-
- design tokens, typography, colors, spacing, radius, shadows
|
|
537
|
-
- motion/animation patterns
|
|
538
|
-
- example components usage + implementation patterns
|
|
539
|
-
- core product functions + key pages
|
|
540
|
-
2. Write standalone design system to:
|
|
541
|
-
- \`.superdesign/design-system.md\`
|
|
542
|
-
- Must be implementable without the codebase
|
|
543
|
-
|
|
544
|
-
### B) Create a new design system (to improve current UI)
|
|
545
|
-
|
|
546
|
-
1. Investigate codebase to understand product + needed pages/components
|
|
547
|
-
2. Gather inspirations (generic tools):
|
|
548
|
-
- \`superdesign search-prompts --tags "style" --json\`
|
|
549
|
-
- \`superdesign get-prompts --slugs ... --json\`
|
|
550
|
-
- optional: \`superdesign extract-brand-guide --url ... --json\`
|
|
551
|
-
3. Interview user (\`askQuestion\`) to choose direction
|
|
552
|
-
4. Write:
|
|
553
|
-
- \`.superdesign/design-system.md\` (adapted to product + references)
|
|
554
|
-
|
|
555
|
-
---
|
|
556
|
-
|
|
557
|
-
# 2) Designing X (feature/page/flow)
|
|
558
|
-
|
|
559
|
-
## A) If user wants references / direction only (no canvas)
|
|
560
|
-
|
|
561
|
-
1. Ensure \`.superdesign/design-system.md\` exists (setup if missing)
|
|
562
|
-
2. Use inspiration tools to collect references:
|
|
563
|
-
- search prompts (style + component/page)
|
|
564
|
-
- get prompts (full details)
|
|
565
|
-
- extract brand guide if relevant
|
|
566
|
-
3. Provide a concrete design spec:
|
|
567
|
-
- layout + hierarchy
|
|
568
|
-
- component set + states
|
|
569
|
-
- token guidance (typography/color/spacing/radius/motion)
|
|
570
|
-
- interaction notes + edge cases
|
|
571
|
-
|
|
572
|
-
## B) If user wants visual exploration (canvas optional)
|
|
573
|
-
|
|
574
|
-
Use canvas when direction is uncertain or multiple options are needed.
|
|
575
|
-
|
|
576
|
-
### Canvas workflow — Add feature to existing page
|
|
577
|
-
|
|
578
|
-
1. Ensure \`.superdesign/design-system.md\` exists (setup if missing)
|
|
579
|
-
2. Build prototype HTML playground (simplified + pixel-perfect enough):
|
|
580
|
-
- \`.superdesign/playgrounds/<page>-<feature>.html\`
|
|
581
|
-
3. Ask targeted questions (\`askQuestion\`) about requirements + taste
|
|
582
|
-
4. Create project with playground HTML (returns \`draftId\`):
|
|
583
|
-
\`\`\`bash
|
|
584
|
-
superdesign create-project \\
|
|
585
|
-
--title "<feature>" \\
|
|
586
|
-
--html-file .superdesign/playgrounds/<file>.html \\
|
|
587
|
-
--prompt-file .superdesign/design-system.md \\
|
|
588
|
-
--json
|
|
589
|
-
\`\`\`
|
|
590
|
-
→ Note: \`draftId\` in response is the baseline draft
|
|
591
|
-
5. Branch designs from baseline (use \`draftId\` from step 4):
|
|
592
|
-
\`\`\`bash
|
|
593
|
-
superdesign iterate-design-draft \\
|
|
594
|
-
--draft-id <draftId> \\
|
|
595
|
-
--prompt "<design requirements>" \\
|
|
596
|
-
--mode branch \\
|
|
597
|
-
--count 3 \\
|
|
598
|
-
--json
|
|
599
|
-
\`\`\`
|
|
600
|
-
6. Share \`previewUrl\` → collect feedback → iterate
|
|
601
|
-
|
|
602
|
-
### Canvas workflow — Add new page
|
|
603
|
-
|
|
604
|
-
Same as above, but playground should include shared UI shell:
|
|
605
|
-
- nav, layout container, spacing rhythm, shared components
|
|
606
|
-
|
|
607
|
-
### Canvas workflow — Improve current UI
|
|
608
|
-
|
|
609
|
-
1. Investigate product + current UI states
|
|
610
|
-
2. Ensure design system exists
|
|
611
|
-
3. Propose improvement options:
|
|
612
|
-
- big restructure
|
|
613
|
-
- style uplift
|
|
614
|
-
- low-hanging polish
|
|
615
|
-
4. Pick one → build playground → create project (prompt-file design system) → branch drafts
|
|
616
|
-
5. Share \`previewUrl\` → iterate
|
|
617
|
-
|
|
618
|
-
### When to use create-design-draft
|
|
619
|
-
|
|
620
|
-
Only use \`create-design-draft\` when creating from scratch (no HTML baseline).
|
|
621
|
-
|
|
622
|
-
**Required in prompt:**
|
|
623
|
-
- Full page structure (header, sidebar, main content areas)
|
|
624
|
-
- Component environment/surroundings
|
|
625
|
-
- Layout context (what's above/below/beside)
|
|
626
|
-
|
|
627
|
-
\`\`\`bash
|
|
628
|
-
superdesign create-design-draft \\
|
|
629
|
-
--project-id <id> \\
|
|
630
|
-
--title "Feature X" \\
|
|
631
|
-
--prompt "Page has: fixed header 64px, left sidebar 240px, main content area. Design a settings panel in the main content area with..." \\
|
|
632
|
-
--device desktop \\
|
|
633
|
-
--json
|
|
443
|
+
# How it works
|
|
444
|
+
Fetch fresh guidelines below:
|
|
634
445
|
\`\`\`
|
|
635
|
-
|
|
636
|
-
---
|
|
637
|
-
|
|
638
|
-
# Advanced usage
|
|
639
|
-
|
|
640
|
-
### Plan + generate a full user journey
|
|
641
|
-
|
|
642
|
-
Plan:
|
|
643
|
-
\`\`\`bash
|
|
644
|
-
superdesign plan-flow-pages \\
|
|
645
|
-
--draft-id <draftId> \\
|
|
646
|
-
--source-node-id <nodeId> \\
|
|
647
|
-
--context "<flow context>" \\
|
|
648
|
-
--json
|
|
446
|
+
https://raw.githubusercontent.com/superdesigndev/superdesign-skill/main/skills/superdesign/SUPERDESIGN.md
|
|
649
447
|
\`\`\`
|
|
650
448
|
|
|
651
|
-
|
|
652
|
-
\`\`\`bash
|
|
653
|
-
superdesign execute-flow-pages \\
|
|
654
|
-
--draft-id <draftId> \\
|
|
655
|
-
--source-node-id <nodeId> \\
|
|
656
|
-
--pages '[{"title":"Signup","prompt":"..."},{"title":"Payment","prompt":"..."}]' \\
|
|
657
|
-
--json
|
|
658
|
-
\`\`\`
|
|
449
|
+
Action accordingly based on instruction in the guidelines
|
|
659
450
|
|
|
660
|
-
|
|
451
|
+
# Superdesign CLI
|
|
661
452
|
|
|
662
|
-
\`\`\`bash
|
|
663
|
-
superdesign get-design --draft-id <draftId> --output ./design.html
|
|
664
453
|
\`\`\`
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
## Quick reference (key commands)
|
|
669
|
-
|
|
670
|
-
\`\`\`bash
|
|
671
|
-
# Inspirations
|
|
672
|
-
superdesign search-prompts --keyword "<keyword>" --json
|
|
673
|
-
superdesign search-prompts --tags "style" --json
|
|
674
|
-
superdesign get-prompts --slugs "<slug1,slug2>" --json
|
|
675
|
-
superdesign extract-brand-guide --url https://example.com --json
|
|
676
|
-
|
|
677
|
-
# Canvas
|
|
678
|
-
superdesign create-project --title "X" --prompt "..." --json
|
|
679
|
-
superdesign create-project --title "X" --prompt-file .superdesign/design-system.md --json
|
|
680
|
-
superdesign create-project --title "X" --html-file ./index.html --prompt-file .superdesign/design-system.md --json
|
|
681
|
-
|
|
682
|
-
superdesign create-design-draft --project-id <id> --title "X" --prompt "..." --device desktop --json
|
|
683
|
-
superdesign iterate-design-draft --draft-id <id> --prompt "..." --mode replace --json
|
|
684
|
-
superdesign iterate-design-draft --draft-id <id> --prompt "..." --mode branch --count 3 --json
|
|
685
|
-
|
|
686
|
-
superdesign fetch-design-nodes --project-id <id> --json
|
|
687
|
-
superdesign get-design --draft-id <id> --json
|
|
454
|
+
npm install -g @superdesign/cli@latest
|
|
455
|
+
superdesign init
|
|
456
|
+
superdesign --help
|
|
688
457
|
\`\`\`
|
|
689
458
|
`;
|
|
690
459
|
}
|
|
@@ -776,102 +545,6 @@ async function addDraft(projectId, data) {
|
|
|
776
545
|
const response = await client.post(`/external/projects/${projectId}/drafts/add`, data);
|
|
777
546
|
return response.data;
|
|
778
547
|
}
|
|
779
|
-
function createCreateProjectCommand() {
|
|
780
|
-
const command = new Command('create-project').description('Create a new SuperDesign project').requiredOption('--title <title>', 'Project title').option('--html <html>', 'Initial HTML content for first draft').option('--html-file <path>', 'Path to HTML file for first draft').option('--prompt <prompt>', 'System prompt for the AI agent').option('--prompt-file <path>', 'Path to markdown file containing system prompt').option('--device <mode>', 'Device mode for draft (mobile, tablet, desktop)', 'desktop').option('--json', 'Output in JSON format').option('--no-open', 'Do not open project in browser after creation').action(async (options)=>{
|
|
781
|
-
if (options.json) setJsonMode(true);
|
|
782
|
-
try {
|
|
783
|
-
if (!manager_isAuthenticated()) {
|
|
784
|
-
output_error('Not authenticated. Run `superdesign login` first.');
|
|
785
|
-
process.exit(EXIT_CODES.AUTH_REQUIRED);
|
|
786
|
-
}
|
|
787
|
-
if (options.device && ![
|
|
788
|
-
'mobile',
|
|
789
|
-
'tablet',
|
|
790
|
-
'desktop'
|
|
791
|
-
].includes(options.device)) {
|
|
792
|
-
output_error('Invalid device mode. Must be: mobile, tablet, or desktop');
|
|
793
|
-
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
794
|
-
}
|
|
795
|
-
let htmlContent;
|
|
796
|
-
if (options.htmlFile) {
|
|
797
|
-
if (!existsSync(options.htmlFile)) {
|
|
798
|
-
output_error(`HTML file not found: ${options.htmlFile}`);
|
|
799
|
-
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
800
|
-
}
|
|
801
|
-
htmlContent = readFileSync(options.htmlFile, 'utf-8');
|
|
802
|
-
} else if (options.html) htmlContent = options.html;
|
|
803
|
-
let promptContent;
|
|
804
|
-
if (options.promptFile) {
|
|
805
|
-
if (!existsSync(options.promptFile)) {
|
|
806
|
-
output_error(`Prompt file not found: ${options.promptFile}`);
|
|
807
|
-
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
808
|
-
}
|
|
809
|
-
promptContent = readFileSync(options.promptFile, 'utf-8');
|
|
810
|
-
} else if (options.prompt) promptContent = options.prompt;
|
|
811
|
-
startSpinner('Creating project...');
|
|
812
|
-
const project = await createProject({
|
|
813
|
-
title: options.title,
|
|
814
|
-
html: htmlContent,
|
|
815
|
-
prompt: promptContent,
|
|
816
|
-
deviceMode: options.device
|
|
817
|
-
});
|
|
818
|
-
succeedSpinner('Project created successfully!');
|
|
819
|
-
if (options.open) {
|
|
820
|
-
const urlWithLive = `${project.projectUrl}?live=1`;
|
|
821
|
-
await openBrowser(urlWithLive);
|
|
822
|
-
}
|
|
823
|
-
if (isJsonMode()) output({
|
|
824
|
-
projectId: project.projectId,
|
|
825
|
-
title: project.title,
|
|
826
|
-
projectUrl: project.projectUrl,
|
|
827
|
-
shareToken: project.shareToken,
|
|
828
|
-
...project.draftId && {
|
|
829
|
-
draftId: project.draftId,
|
|
830
|
-
previewUrl: project.previewUrl
|
|
831
|
-
}
|
|
832
|
-
});
|
|
833
|
-
else {
|
|
834
|
-
success(`Project "${project.title}" created!`);
|
|
835
|
-
info(`\nProject ID: ${project.projectId}`);
|
|
836
|
-
info(`Project URL: ${project.projectUrl}`);
|
|
837
|
-
if (project.draftId) {
|
|
838
|
-
info(`\nDraft ID: ${project.draftId}`);
|
|
839
|
-
info(`Preview URL: ${project.previewUrl}`);
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
} catch (err) {
|
|
843
|
-
failSpinner('Failed to create project');
|
|
844
|
-
if (err instanceof ApiClientError) {
|
|
845
|
-
output_error(`API Error: ${err.message}`);
|
|
846
|
-
process.exit(EXIT_CODES.API_ERROR);
|
|
847
|
-
}
|
|
848
|
-
const message = err instanceof Error ? err.message : 'Unknown error';
|
|
849
|
-
output_error(message);
|
|
850
|
-
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
851
|
-
}
|
|
852
|
-
});
|
|
853
|
-
return command;
|
|
854
|
-
}
|
|
855
|
-
async function createDraft(projectId, data) {
|
|
856
|
-
const client = getApiClient();
|
|
857
|
-
const response = await client.post(`/external/projects/${projectId}/drafts/create`, data);
|
|
858
|
-
return response.data;
|
|
859
|
-
}
|
|
860
|
-
async function iterateDraft(draftId, data) {
|
|
861
|
-
const client = getApiClient();
|
|
862
|
-
const response = await client.post(`/external/drafts/${draftId}/iterate`, data);
|
|
863
|
-
return response.data;
|
|
864
|
-
}
|
|
865
|
-
async function planFlowPages(draftId, data) {
|
|
866
|
-
const client = getApiClient();
|
|
867
|
-
const response = await client.post(`/external/drafts/${draftId}/flow/plan`, data);
|
|
868
|
-
return response.data;
|
|
869
|
-
}
|
|
870
|
-
async function executeFlowPages(draftId, data) {
|
|
871
|
-
const client = getApiClient();
|
|
872
|
-
const response = await client.post(`/external/drafts/${draftId}/flow/execute`, data);
|
|
873
|
-
return response.data;
|
|
874
|
-
}
|
|
875
548
|
async function getJobStatus(jobId) {
|
|
876
549
|
const client = getApiClient();
|
|
877
550
|
const response = await client.get(`/external/jobs/${jobId}`);
|
|
@@ -961,18 +634,98 @@ function handleApiError(err, failureMessage) {
|
|
|
961
634
|
else output_error(`${failureMessage}: ${message}`);
|
|
962
635
|
process.exit(EXIT_CODES.GENERAL_ERROR);
|
|
963
636
|
}
|
|
964
|
-
|
|
965
|
-
|
|
637
|
+
const VALID_DEVICES = [
|
|
638
|
+
'mobile',
|
|
639
|
+
'tablet',
|
|
640
|
+
'desktop'
|
|
641
|
+
];
|
|
642
|
+
function validateDeviceMode(device) {
|
|
643
|
+
if (device && !VALID_DEVICES.includes(device)) {
|
|
644
|
+
output_error('Invalid device mode. Must be: mobile, tablet, or desktop');
|
|
645
|
+
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
function readFileContent(filePath, inlineContent, fileType) {
|
|
649
|
+
if (filePath) {
|
|
650
|
+
if (!existsSync(filePath)) {
|
|
651
|
+
output_error(`${fileType} file not found: ${filePath}`);
|
|
652
|
+
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
653
|
+
}
|
|
654
|
+
return readFileSync(filePath, 'utf-8');
|
|
655
|
+
}
|
|
656
|
+
return inlineContent;
|
|
657
|
+
}
|
|
658
|
+
function createCreateProjectCommand() {
|
|
659
|
+
const command = new Command('create-project').description('Create a new SuperDesign project').requiredOption('--title <title>', 'Project title').option('--html <html>', 'Initial HTML content for first draft').option('--html-file <path>', 'Path to HTML file for first draft').option('-s, --set-project-prompt <prompt>', 'System prompt for the AI agent').option('--set-project-prompt-file <path>', 'Path to markdown file containing system prompt').option('--device <mode>', 'Device mode for draft (mobile, tablet, desktop)', 'desktop').option('--json', 'Output in JSON format').option('--no-open', 'Do not open project in browser after creation').action(async (options)=>{
|
|
966
660
|
if (options.json) setJsonMode(true);
|
|
967
661
|
job_runner_requireAuth(manager_isAuthenticated);
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
662
|
+
validateDeviceMode(options.device);
|
|
663
|
+
const htmlContent = readFileContent(options.htmlFile, options.html, 'HTML');
|
|
664
|
+
const promptContent = readFileContent(options.setProjectPromptFile, options.setProjectPrompt, 'Prompt');
|
|
665
|
+
startSpinner('Creating project...');
|
|
666
|
+
try {
|
|
667
|
+
const project = await createProject({
|
|
668
|
+
title: options.title,
|
|
669
|
+
html: htmlContent,
|
|
670
|
+
prompt: promptContent,
|
|
671
|
+
deviceMode: options.device
|
|
672
|
+
});
|
|
673
|
+
succeedSpinner('Project created successfully!');
|
|
674
|
+
if (options.open) {
|
|
675
|
+
const urlWithLive = `${project.projectUrl}?live=1`;
|
|
676
|
+
await openBrowser(urlWithLive);
|
|
677
|
+
}
|
|
678
|
+
if (isJsonMode()) output({
|
|
679
|
+
projectId: project.projectId,
|
|
680
|
+
title: project.title,
|
|
681
|
+
projectUrl: project.projectUrl,
|
|
682
|
+
shareToken: project.shareToken,
|
|
683
|
+
...project.draftId && {
|
|
684
|
+
draftId: project.draftId,
|
|
685
|
+
previewUrl: project.previewUrl
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
else {
|
|
689
|
+
success(`Project "${project.title}" created!`);
|
|
690
|
+
info(`\nProject ID: ${project.projectId}`);
|
|
691
|
+
info(`Project URL: ${project.projectUrl}`);
|
|
692
|
+
if (project.draftId) {
|
|
693
|
+
info(`\nDraft ID: ${project.draftId}`);
|
|
694
|
+
info(`Preview URL: ${project.previewUrl}`);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
} catch (err) {
|
|
698
|
+
failSpinner('Failed to create project');
|
|
699
|
+
handleApiError(err, 'Failed to create project');
|
|
975
700
|
}
|
|
701
|
+
});
|
|
702
|
+
return command;
|
|
703
|
+
}
|
|
704
|
+
async function createDraft(projectId, data) {
|
|
705
|
+
const client = getApiClient();
|
|
706
|
+
const response = await client.post(`/external/projects/${projectId}/drafts/create`, data);
|
|
707
|
+
return response.data;
|
|
708
|
+
}
|
|
709
|
+
async function iterateDraft(draftId, data) {
|
|
710
|
+
const client = getApiClient();
|
|
711
|
+
const response = await client.post(`/external/drafts/${draftId}/iterate`, data);
|
|
712
|
+
return response.data;
|
|
713
|
+
}
|
|
714
|
+
async function planFlowPages(draftId, data) {
|
|
715
|
+
const client = getApiClient();
|
|
716
|
+
const response = await client.post(`/external/drafts/${draftId}/flow/plan`, data);
|
|
717
|
+
return response.data;
|
|
718
|
+
}
|
|
719
|
+
async function executeFlowPages(draftId, data) {
|
|
720
|
+
const client = getApiClient();
|
|
721
|
+
const response = await client.post(`/external/drafts/${draftId}/flow/execute`, data);
|
|
722
|
+
return response.data;
|
|
723
|
+
}
|
|
724
|
+
function createCreateDesignDraftCommand() {
|
|
725
|
+
const command = new Command('create-design-draft').description('Create a design draft using AI generation').requiredOption('--project-id <id>', 'Project ID').requiredOption('--title <title>', 'Draft title').requiredOption('-p, --prompt <prompt>', 'Design prompt for AI generation').option('--device <mode>', 'Device mode (mobile, tablet, desktop)', 'desktop').option('--json', 'Output in JSON format').action(async (options)=>{
|
|
726
|
+
if (options.json) setJsonMode(true);
|
|
727
|
+
job_runner_requireAuth(manager_isAuthenticated);
|
|
728
|
+
validateDeviceMode(options.device);
|
|
976
729
|
await runJob({
|
|
977
730
|
startLabel: 'Creating design draft...',
|
|
978
731
|
pollingLabel: 'Generating design with AI...',
|
|
@@ -1434,15 +1187,103 @@ function createGetDesignCommand() {
|
|
|
1434
1187
|
});
|
|
1435
1188
|
return command;
|
|
1436
1189
|
}
|
|
1190
|
+
let posthogClient = null;
|
|
1191
|
+
function getPostHog() {
|
|
1192
|
+
if (!posthogClient && POSTHOG_KEY) posthogClient = new PostHog(POSTHOG_KEY, {
|
|
1193
|
+
host: POSTHOG_HOST
|
|
1194
|
+
});
|
|
1195
|
+
return posthogClient;
|
|
1196
|
+
}
|
|
1197
|
+
function sanitizeOptions(options) {
|
|
1198
|
+
const sensitiveKeys = [
|
|
1199
|
+
'token',
|
|
1200
|
+
'key',
|
|
1201
|
+
'secret',
|
|
1202
|
+
'password',
|
|
1203
|
+
'apiKey',
|
|
1204
|
+
'api_key',
|
|
1205
|
+
'apikey',
|
|
1206
|
+
'auth',
|
|
1207
|
+
'authorization',
|
|
1208
|
+
'credential'
|
|
1209
|
+
];
|
|
1210
|
+
const sanitized = {};
|
|
1211
|
+
for (const [key, value] of Object.entries(options)){
|
|
1212
|
+
const lowerKey = key.toLowerCase();
|
|
1213
|
+
if (!sensitiveKeys.some((sensitive)=>lowerKey.includes(sensitive))) {
|
|
1214
|
+
if ('string' != typeof value || !(value.length > 100)) sanitized[key] = value;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
return sanitized;
|
|
1218
|
+
}
|
|
1219
|
+
async function trackCommand(opts) {
|
|
1220
|
+
const config = loadConfig();
|
|
1221
|
+
const teamId = config.teamId;
|
|
1222
|
+
const metadata = {
|
|
1223
|
+
command: opts.command,
|
|
1224
|
+
success: opts.success,
|
|
1225
|
+
durationMs: opts.durationMs,
|
|
1226
|
+
errorCode: opts.errorCode,
|
|
1227
|
+
options: opts.options ? sanitizeOptions(opts.options) : void 0,
|
|
1228
|
+
cliVersion: "0.1.6",
|
|
1229
|
+
os: `${os.platform()} ${os.release()}`
|
|
1230
|
+
};
|
|
1231
|
+
const posthog = getPostHog();
|
|
1232
|
+
if (posthog) {
|
|
1233
|
+
const distinctId = teamId || `anon_${os.hostname()}`;
|
|
1234
|
+
posthog.capture({
|
|
1235
|
+
distinctId,
|
|
1236
|
+
event: 'cli_command',
|
|
1237
|
+
properties: {
|
|
1238
|
+
team_id: teamId,
|
|
1239
|
+
...metadata
|
|
1240
|
+
}
|
|
1241
|
+
});
|
|
1242
|
+
}
|
|
1243
|
+
if (manager_isAuthenticated() && teamId) try {
|
|
1244
|
+
const client = getApiClient();
|
|
1245
|
+
await client.post('/external/track', {
|
|
1246
|
+
activityType: 'cli_command',
|
|
1247
|
+
metadata
|
|
1248
|
+
});
|
|
1249
|
+
} catch {}
|
|
1250
|
+
}
|
|
1251
|
+
async function shutdownAnalytics() {
|
|
1252
|
+
if (posthogClient) {
|
|
1253
|
+
await posthogClient.shutdown();
|
|
1254
|
+
posthogClient = null;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1437
1257
|
const src_filename = fileURLToPath(import.meta.url);
|
|
1438
1258
|
const src_dirname = dirname(src_filename);
|
|
1439
1259
|
external_dotenv_config({
|
|
1440
1260
|
path: external_path_resolve(src_dirname, '../.env')
|
|
1441
1261
|
});
|
|
1442
1262
|
external_dotenv_config();
|
|
1263
|
+
let commandStartTime = 0;
|
|
1264
|
+
let currentCommand = '';
|
|
1265
|
+
function sanitizeCommandOptions(opts) {
|
|
1266
|
+
const sanitized = {};
|
|
1267
|
+
for (const [key, value] of Object.entries(opts))if (!('function' == typeof value || key.startsWith('_'))) sanitized[key] = value;
|
|
1268
|
+
return sanitized;
|
|
1269
|
+
}
|
|
1443
1270
|
function createProgram() {
|
|
1444
1271
|
const program = new Command();
|
|
1445
|
-
program.name('superdesign').description('SuperDesign CLI - AI product designer for coding agents').version("0.1.
|
|
1272
|
+
program.name('superdesign').description('SuperDesign CLI - AI product designer for coding agents').version("0.1.6");
|
|
1273
|
+
program.hook('preAction', ()=>{
|
|
1274
|
+
commandStartTime = Date.now();
|
|
1275
|
+
currentCommand = process.argv[2] || 'unknown';
|
|
1276
|
+
});
|
|
1277
|
+
program.hook('postAction', async (_thisCommand, actionCommand)=>{
|
|
1278
|
+
const durationMs = Date.now() - commandStartTime;
|
|
1279
|
+
await trackCommand({
|
|
1280
|
+
command: currentCommand,
|
|
1281
|
+
success: true,
|
|
1282
|
+
durationMs,
|
|
1283
|
+
options: sanitizeCommandOptions(actionCommand.opts())
|
|
1284
|
+
});
|
|
1285
|
+
await shutdownAnalytics();
|
|
1286
|
+
});
|
|
1446
1287
|
program.addCommand(createLoginCommand());
|
|
1447
1288
|
program.addCommand(createLogoutCommand());
|
|
1448
1289
|
program.addCommand(createInitCommand());
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Analytics - Dual tracking with PostHog (real-time) and Backend (long-term storage)
|
|
3
|
+
*/
|
|
4
|
+
export interface TrackCommandOptions {
|
|
5
|
+
command: string;
|
|
6
|
+
success: boolean;
|
|
7
|
+
durationMs: number;
|
|
8
|
+
errorCode?: string;
|
|
9
|
+
options?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Sanitize command options to remove sensitive values
|
|
13
|
+
*/
|
|
14
|
+
declare function sanitizeOptions(options: Record<string, unknown>): Record<string, unknown>;
|
|
15
|
+
/**
|
|
16
|
+
* Track a CLI command execution
|
|
17
|
+
* Sends to both PostHog (real-time dashboards) and backend (long-term storage)
|
|
18
|
+
*/
|
|
19
|
+
export declare function trackCommand(opts: TrackCommandOptions): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Shutdown analytics and flush any pending events
|
|
22
|
+
* Should be called before CLI exits
|
|
23
|
+
*/
|
|
24
|
+
export declare function shutdownAnalytics(): Promise<void>;
|
|
25
|
+
export { sanitizeOptions };
|
|
@@ -44,3 +44,9 @@ export declare function requireAuth(isAuthenticated: () => boolean): void;
|
|
|
44
44
|
* Exits the process with appropriate exit code
|
|
45
45
|
*/
|
|
46
46
|
export declare function handleApiError(err: unknown, failureMessage: string): never;
|
|
47
|
+
export declare const VALID_DEVICES: readonly ["mobile", "tablet", "desktop"];
|
|
48
|
+
export type DeviceMode = (typeof VALID_DEVICES)[number];
|
|
49
|
+
/**
|
|
50
|
+
* Validate device mode option and exit with error if invalid
|
|
51
|
+
*/
|
|
52
|
+
export declare function validateDeviceMode(device: string | undefined): void;
|