@superdesign/cli 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/drafts.d.ts +3 -0
- package/dist/config/constants.d.ts +3 -1
- package/dist/index.cjs +156 -20
- package/dist/index.js +156 -20
- package/dist/utils/context-files.d.ts +62 -1
- package/dist/utils/job-runner.d.ts +4 -0
- package/package.json +2 -2
package/dist/api/drafts.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface CreateDraftRequest {
|
|
|
10
10
|
prompt: string;
|
|
11
11
|
deviceMode?: 'mobile' | 'tablet' | 'desktop';
|
|
12
12
|
contextFiles?: ContextFile[];
|
|
13
|
+
model?: string;
|
|
13
14
|
}
|
|
14
15
|
export interface IterateDraftRequest {
|
|
15
16
|
prompts?: string[];
|
|
@@ -17,6 +18,7 @@ export interface IterateDraftRequest {
|
|
|
17
18
|
count?: 1 | 2 | 3 | 4;
|
|
18
19
|
mode: 'replace' | 'branch';
|
|
19
20
|
contextFiles?: ContextFile[];
|
|
21
|
+
model?: string;
|
|
20
22
|
}
|
|
21
23
|
export interface PlanFlowRequest {
|
|
22
24
|
flowContext?: string;
|
|
@@ -29,6 +31,7 @@ export interface ExecuteFlowRequest {
|
|
|
29
31
|
flowContext?: string;
|
|
30
32
|
pages: FlowPage[];
|
|
31
33
|
contextFiles?: ContextFile[];
|
|
34
|
+
model?: string;
|
|
32
35
|
}
|
|
33
36
|
export interface JobCreatedResponse {
|
|
34
37
|
jobId: string;
|
|
@@ -12,7 +12,7 @@ export declare const POLL_TIMEOUT_MS: number;
|
|
|
12
12
|
export declare const AUTH_POLL_INTERVAL_MS = 2000;
|
|
13
13
|
export declare const AUTH_POLL_TIMEOUT_MS: number;
|
|
14
14
|
/** CLI version - should match package.json */
|
|
15
|
-
export declare const CLI_VERSION = "0.2.
|
|
15
|
+
export declare const CLI_VERSION = "0.2.2";
|
|
16
16
|
/** PostHog analytics configuration */
|
|
17
17
|
export declare const POSTHOG_KEY: string;
|
|
18
18
|
export declare const POSTHOG_HOST: string;
|
|
@@ -30,6 +30,8 @@ export declare const EXIT_CODES: {
|
|
|
30
30
|
readonly VALIDATION_ERROR: 5;
|
|
31
31
|
readonly TIMEOUT: 6;
|
|
32
32
|
};
|
|
33
|
+
/** Supported model IDs for --model option */
|
|
34
|
+
export declare const SUPPORTED_MODELS: readonly ["gemini-3-flash", "gemini-3-pro", "gemini-3.1-pro", "claude-haiku-4-5", "claude-sonnet-4-5", "claude-opus-4-5", "claude-opus-4-6", "gpt-5.2", "gpt-5.2-thinking", "gpt-5-mini", "kimi-k2.5"];
|
|
33
35
|
/** Skill files directory relative to project root */
|
|
34
36
|
export declare const SKILLS_DIR = ".claude/skills/superdesign";
|
|
35
37
|
/** SKILL.md file name */
|
package/dist/index.cjs
CHANGED
|
@@ -104,6 +104,19 @@ var __webpack_exports__ = {};
|
|
|
104
104
|
VALIDATION_ERROR: 5,
|
|
105
105
|
TIMEOUT: 6
|
|
106
106
|
};
|
|
107
|
+
const SUPPORTED_MODELS = [
|
|
108
|
+
'gemini-3-flash',
|
|
109
|
+
'gemini-3-pro',
|
|
110
|
+
'gemini-3.1-pro',
|
|
111
|
+
'claude-haiku-4-5',
|
|
112
|
+
'claude-sonnet-4-5',
|
|
113
|
+
'claude-opus-4-5',
|
|
114
|
+
'claude-opus-4-6',
|
|
115
|
+
'gpt-5.2',
|
|
116
|
+
'gpt-5.2-thinking',
|
|
117
|
+
'gpt-5-mini',
|
|
118
|
+
'kimi-k2.5'
|
|
119
|
+
];
|
|
107
120
|
const SKILLS_DIR = '.claude/skills/superdesign';
|
|
108
121
|
const SKILL_FILE_NAME = 'SKILL.md';
|
|
109
122
|
const external_fs_namespaceObject = require("fs");
|
|
@@ -316,7 +329,7 @@ var __webpack_exports__ = {};
|
|
|
316
329
|
try {
|
|
317
330
|
startSpinner('Creating auth session...');
|
|
318
331
|
const session = await createSession({
|
|
319
|
-
cliVersion: "0.2.
|
|
332
|
+
cliVersion: "0.2.2",
|
|
320
333
|
os: `${external_os_namespaceObject.platform()} ${external_os_namespaceObject.release()}`,
|
|
321
334
|
hostname: external_os_namespaceObject.hostname()
|
|
322
335
|
});
|
|
@@ -724,6 +737,12 @@ superdesign --help
|
|
|
724
737
|
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
725
738
|
}
|
|
726
739
|
}
|
|
740
|
+
function validateModel(model) {
|
|
741
|
+
if (model && !SUPPORTED_MODELS.includes(model)) {
|
|
742
|
+
output_error(`Invalid model "${model}". Supported models: ${SUPPORTED_MODELS.join(', ')}`);
|
|
743
|
+
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
727
746
|
function readFileIfExists(filePath) {
|
|
728
747
|
if ((0, external_fs_namespaceObject.existsSync)(filePath)) return (0, external_fs_namespaceObject.readFileSync)(filePath, 'utf-8');
|
|
729
748
|
}
|
|
@@ -799,29 +818,140 @@ superdesign --help
|
|
|
799
818
|
const response = await client.post(`/external/drafts/${draftId}/flow/execute`, data);
|
|
800
819
|
return response.data;
|
|
801
820
|
}
|
|
821
|
+
function parseContextFileArg(arg) {
|
|
822
|
+
const match = arg.match(/^(.+?):(\d+)(?::(\d+))?$/);
|
|
823
|
+
if (!match) return {
|
|
824
|
+
rawPath: arg
|
|
825
|
+
};
|
|
826
|
+
const [, pathPart, startStr, endStr] = match;
|
|
827
|
+
const startLine = parseInt(startStr, 10);
|
|
828
|
+
const endLine = void 0 !== endStr ? parseInt(endStr, 10) : void 0;
|
|
829
|
+
if (startLine < 1) {
|
|
830
|
+
output_error(`Invalid line range in "${arg}": start line must be >= 1`);
|
|
831
|
+
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
832
|
+
}
|
|
833
|
+
if (void 0 !== endLine && endLine < startLine) {
|
|
834
|
+
output_error(`Invalid line range in "${arg}": end line (${endLine}) must be >= start line (${startLine})`);
|
|
835
|
+
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
836
|
+
}
|
|
837
|
+
const cwd = process.cwd();
|
|
838
|
+
const extractedAbsolute = (0, external_path_namespaceObject.resolve)(cwd, pathPart);
|
|
839
|
+
const originalAbsolute = (0, external_path_namespaceObject.resolve)(cwd, arg);
|
|
840
|
+
if (!(0, external_fs_namespaceObject.existsSync)(extractedAbsolute) && (0, external_fs_namespaceObject.existsSync)(originalAbsolute)) return {
|
|
841
|
+
rawPath: arg
|
|
842
|
+
};
|
|
843
|
+
return {
|
|
844
|
+
rawPath: pathPart,
|
|
845
|
+
startLine,
|
|
846
|
+
endLine
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
function extractLines(content, start, end) {
|
|
850
|
+
const lines = content.split('\n');
|
|
851
|
+
const from = Math.max(0, start - 1);
|
|
852
|
+
const to = Math.min(lines.length, end);
|
|
853
|
+
return lines.slice(from, to).join('\n');
|
|
854
|
+
}
|
|
855
|
+
function mergeRanges(ranges) {
|
|
856
|
+
if (0 === ranges.length) return [];
|
|
857
|
+
const sorted = [
|
|
858
|
+
...ranges
|
|
859
|
+
].sort((a, b)=>a.start - b.start);
|
|
860
|
+
const merged = [
|
|
861
|
+
sorted[0]
|
|
862
|
+
];
|
|
863
|
+
for(let i = 1; i < sorted.length; i++){
|
|
864
|
+
const prev = merged[merged.length - 1];
|
|
865
|
+
const curr = sorted[i];
|
|
866
|
+
if (prev.end >= curr.start - 1 || prev.end === 1 / 0) prev.end = Math.max(prev.end, curr.end);
|
|
867
|
+
else merged.push({
|
|
868
|
+
...curr
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
return merged;
|
|
872
|
+
}
|
|
873
|
+
function formatRangeContent(content, ranges) {
|
|
874
|
+
const totalLines = content.split('\n').length;
|
|
875
|
+
const parts = [];
|
|
876
|
+
for(let i = 0; i < ranges.length; i++){
|
|
877
|
+
const range = ranges[i];
|
|
878
|
+
const effectiveEnd = range.end === 1 / 0 ? totalLines : Math.min(range.end, totalLines);
|
|
879
|
+
if (i > 0) {
|
|
880
|
+
const prevEnd = ranges[i - 1].end === 1 / 0 ? totalLines : Math.min(ranges[i - 1].end, totalLines);
|
|
881
|
+
const gapStart = prevEnd + 1;
|
|
882
|
+
const gapEnd = range.start - 1;
|
|
883
|
+
if (gapEnd >= gapStart) {
|
|
884
|
+
const gapSize = gapEnd - gapStart + 1;
|
|
885
|
+
parts.push(`// ... (lines ${gapStart}-${gapEnd} omitted, ${gapSize} lines) ...`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
parts.push(`// === Lines ${range.start}-${effectiveEnd} ===`);
|
|
889
|
+
parts.push(extractLines(content, range.start, effectiveEnd));
|
|
890
|
+
}
|
|
891
|
+
return parts.join('\n');
|
|
892
|
+
}
|
|
802
893
|
function readContextFiles(filePaths) {
|
|
803
894
|
const cwd = process.cwd();
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
895
|
+
const refs = filePaths.map(parseContextFileArg);
|
|
896
|
+
const groups = new Map();
|
|
897
|
+
for (const ref of refs){
|
|
898
|
+
const absolutePath = (0, external_path_namespaceObject.resolve)(cwd, ref.rawPath);
|
|
899
|
+
const relPath = (0, external_path_namespaceObject.relative)(cwd, absolutePath);
|
|
900
|
+
let group = groups.get(absolutePath);
|
|
901
|
+
if (!group) {
|
|
902
|
+
group = {
|
|
903
|
+
relPath,
|
|
904
|
+
fullFile: false,
|
|
905
|
+
ranges: []
|
|
812
906
|
};
|
|
907
|
+
groups.set(absolutePath, group);
|
|
908
|
+
}
|
|
909
|
+
if (void 0 === ref.startLine) group.fullFile = true;
|
|
910
|
+
else group.ranges.push({
|
|
911
|
+
start: ref.startLine,
|
|
912
|
+
end: ref.endLine ?? 1 / 0
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
const results = [];
|
|
916
|
+
for (const [absolutePath, group] of groups){
|
|
917
|
+
let content;
|
|
918
|
+
try {
|
|
919
|
+
content = (0, external_fs_namespaceObject.readFileSync)(absolutePath, 'utf-8');
|
|
813
920
|
} catch (err) {
|
|
814
921
|
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
815
|
-
output_error(`Failed to read context file "${
|
|
922
|
+
output_error(`Failed to read context file "${group.relPath}": ${msg}`);
|
|
816
923
|
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
817
924
|
}
|
|
818
|
-
|
|
925
|
+
if (group.fullFile || 0 === group.ranges.length) {
|
|
926
|
+
results.push({
|
|
927
|
+
filename: group.relPath,
|
|
928
|
+
content
|
|
929
|
+
});
|
|
930
|
+
continue;
|
|
931
|
+
}
|
|
932
|
+
const merged = mergeRanges(group.ranges);
|
|
933
|
+
const totalLines = content.split('\n').length;
|
|
934
|
+
if (1 === merged.length && merged[0].start <= 1 && (merged[0].end === 1 / 0 || merged[0].end >= totalLines)) {
|
|
935
|
+
results.push({
|
|
936
|
+
filename: group.relPath,
|
|
937
|
+
content
|
|
938
|
+
});
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
const formatted = formatRangeContent(content, merged);
|
|
942
|
+
results.push({
|
|
943
|
+
filename: group.relPath,
|
|
944
|
+
content: formatted
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
return results;
|
|
819
948
|
}
|
|
820
949
|
function createCreateDesignDraftCommand() {
|
|
821
|
-
const command = new external_commander_namespaceObject.Command('create-design-draft').description('Create a design draft from scratch without any reference, Default dont use this, use iterate-design-draft instead').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('--context-file <paths...>', 'UI source files to include as context for AI generation').option('--json', 'Output in JSON format').action(async (options)=>{
|
|
950
|
+
const command = new external_commander_namespaceObject.Command('create-design-draft').description('Create a design draft from scratch without any reference, Default dont use this, use iterate-design-draft instead').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('--context-file <paths...>', 'UI source files to include as context for AI generation').option('--model <modelId>', 'Model ID to use for generation (e.g. claude-sonnet-4-5)').option('--json', 'Output in JSON format').action(async (options)=>{
|
|
822
951
|
if (options.json) setJsonMode(true);
|
|
823
952
|
job_runner_requireAuth(manager_isAuthenticated);
|
|
824
953
|
validateDeviceMode(options.device);
|
|
954
|
+
validateModel(options.model);
|
|
825
955
|
const contextFiles = options.contextFile ? readContextFiles(options.contextFile) : void 0;
|
|
826
956
|
await runJob({
|
|
827
957
|
startLabel: 'Creating design draft...',
|
|
@@ -833,7 +963,8 @@ superdesign --help
|
|
|
833
963
|
title: options.title,
|
|
834
964
|
prompt: options.prompt,
|
|
835
965
|
deviceMode: options.device,
|
|
836
|
-
contextFiles
|
|
966
|
+
contextFiles,
|
|
967
|
+
model: options.model
|
|
837
968
|
}),
|
|
838
969
|
transformResult: (job)=>({
|
|
839
970
|
draftId: job.result.draftId,
|
|
@@ -905,20 +1036,23 @@ Usage Examples:
|
|
|
905
1036
|
superdesign iterate-design-draft --draft-id <id> -p "dark theme" -p "minimal" -p "bold" --mode branch --json
|
|
906
1037
|
|
|
907
1038
|
# Iterate: Auto explore (only give exploration direction, and let Superdesign fill in details, e.g. explore different styles; Default do NOT use auto explore mode)
|
|
908
|
-
superdesign iterate-design-draft --draft-id <id> -p "..." --mode branch --count 3 --json`).requiredOption('--draft-id <id>', 'Draft ID to iterate on').requiredOption('-p, --prompt <prompt...>', 'Iteration prompt(s). Use multiple -p for specific prompts per variation.').requiredOption('--mode <mode>', 'Iteration mode (replace or branch)').option('--count <count>', 'Number of variations (1-4). Only used when single prompt provided.').option('--context-file <paths...>', 'UI source files to include as context for AI generation').option('--json', 'Output in JSON format').addOption(new external_commander_namespaceObject.Option('--project-id <id>').hideHelp()).action(async (options)=>{
|
|
1039
|
+
superdesign iterate-design-draft --draft-id <id> -p "..." --mode branch --count 3 --json`).requiredOption('--draft-id <id>', 'Draft ID to iterate on').requiredOption('-p, --prompt <prompt...>', 'Iteration prompt(s). Use multiple -p for specific prompts per variation.').requiredOption('--mode <mode>', 'Iteration mode (replace or branch)').option('--count <count>', 'Number of variations (1-4). Only used when single prompt provided.').option('--context-file <paths...>', 'UI source files to include as context for AI generation').option('--model <modelId>', 'Model ID to use for generation (e.g. claude-sonnet-4-5)').option('--json', 'Output in JSON format').addOption(new external_commander_namespaceObject.Option('--project-id <id>').hideHelp()).action(async (options)=>{
|
|
909
1040
|
if (options.json) setJsonMode(true);
|
|
910
1041
|
job_runner_requireAuth(manager_isAuthenticated);
|
|
1042
|
+
validateModel(options.model);
|
|
911
1043
|
const { prompts, count, mode } = validateOptions(options);
|
|
912
1044
|
const contextFiles = options.contextFile ? readContextFiles(options.contextFile) : void 0;
|
|
913
1045
|
const requestData = prompts.length > 1 ? {
|
|
914
1046
|
prompts,
|
|
915
1047
|
mode,
|
|
916
|
-
contextFiles
|
|
1048
|
+
contextFiles,
|
|
1049
|
+
model: options.model
|
|
917
1050
|
} : {
|
|
918
1051
|
prompt: prompts[0],
|
|
919
1052
|
count: count,
|
|
920
1053
|
mode,
|
|
921
|
-
contextFiles
|
|
1054
|
+
contextFiles,
|
|
1055
|
+
model: options.model
|
|
922
1056
|
};
|
|
923
1057
|
await runJob({
|
|
924
1058
|
startLabel: 'Starting iteration...',
|
|
@@ -989,9 +1123,10 @@ Usage Examples:
|
|
|
989
1123
|
return parsed;
|
|
990
1124
|
}
|
|
991
1125
|
function createExecuteFlowPagesCommand() {
|
|
992
|
-
const command = new external_commander_namespaceObject.Command('execute-flow-pages').description('Generate flow pages using AI').requiredOption('--draft-id <id>', 'Source draft ID').requiredOption('--pages <json>', 'JSON array of pages to generate [{title, prompt}]').option('--context <context>', 'Additional context for flow generation').option('--context-file <paths...>', 'UI source files to include as context for AI generation').option('--json', 'Output in JSON format').action(async (options)=>{
|
|
1126
|
+
const command = new external_commander_namespaceObject.Command('execute-flow-pages').description('Generate flow pages using AI').requiredOption('--draft-id <id>', 'Source draft ID').requiredOption('--pages <json>', 'JSON array of pages to generate [{title, prompt}]').option('--context <context>', 'Additional context for flow generation').option('--context-file <paths...>', 'UI source files to include as context for AI generation').option('--model <modelId>', 'Model ID to use for generation (e.g. claude-sonnet-4-5)').option('--json', 'Output in JSON format').action(async (options)=>{
|
|
993
1127
|
if (options.json) setJsonMode(true);
|
|
994
1128
|
job_runner_requireAuth(manager_isAuthenticated);
|
|
1129
|
+
validateModel(options.model);
|
|
995
1130
|
let pages;
|
|
996
1131
|
try {
|
|
997
1132
|
pages = parsePages(options.pages);
|
|
@@ -1021,7 +1156,8 @@ Usage Examples:
|
|
|
1021
1156
|
startJob: ()=>executeFlowPages(options.draftId, {
|
|
1022
1157
|
flowContext: options.context,
|
|
1023
1158
|
pages,
|
|
1024
|
-
contextFiles
|
|
1159
|
+
contextFiles,
|
|
1160
|
+
model: options.model
|
|
1025
1161
|
}),
|
|
1026
1162
|
transformResult: (job)=>({
|
|
1027
1163
|
drafts: job.result.drafts,
|
|
@@ -1336,7 +1472,7 @@ Usage Examples:
|
|
|
1336
1472
|
durationMs: opts.durationMs,
|
|
1337
1473
|
errorCode: opts.errorCode,
|
|
1338
1474
|
options: opts.options,
|
|
1339
|
-
cliVersion: "0.2.
|
|
1475
|
+
cliVersion: "0.2.2",
|
|
1340
1476
|
os: `${external_os_default().platform()} ${external_os_default().release()}`
|
|
1341
1477
|
};
|
|
1342
1478
|
const posthog = getPostHog();
|
|
@@ -1404,7 +1540,7 @@ Usage Examples:
|
|
|
1404
1540
|
}
|
|
1405
1541
|
function createProgram() {
|
|
1406
1542
|
const program = new external_commander_namespaceObject.Command();
|
|
1407
|
-
program.name('superdesign').description('SuperDesign CLI - AI product designer for coding agents').version("0.2.
|
|
1543
|
+
program.name('superdesign').description('SuperDesign CLI - AI product designer for coding agents').version("0.2.2");
|
|
1408
1544
|
program.configureOutput({
|
|
1409
1545
|
writeErr: (str)=>{
|
|
1410
1546
|
if (!process.argv.includes('--json')) process.stderr.write(str);
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,19 @@ const EXIT_CODES = {
|
|
|
21
21
|
VALIDATION_ERROR: 5,
|
|
22
22
|
TIMEOUT: 6
|
|
23
23
|
};
|
|
24
|
+
const SUPPORTED_MODELS = [
|
|
25
|
+
'gemini-3-flash',
|
|
26
|
+
'gemini-3-pro',
|
|
27
|
+
'gemini-3.1-pro',
|
|
28
|
+
'claude-haiku-4-5',
|
|
29
|
+
'claude-sonnet-4-5',
|
|
30
|
+
'claude-opus-4-5',
|
|
31
|
+
'claude-opus-4-6',
|
|
32
|
+
'gpt-5.2',
|
|
33
|
+
'gpt-5.2-thinking',
|
|
34
|
+
'gpt-5-mini',
|
|
35
|
+
'kimi-k2.5'
|
|
36
|
+
];
|
|
24
37
|
const SKILLS_DIR = '.claude/skills/superdesign';
|
|
25
38
|
const SKILL_FILE_NAME = 'SKILL.md';
|
|
26
39
|
function getConfigDir() {
|
|
@@ -226,7 +239,7 @@ async function runAuthFlow(options = {}) {
|
|
|
226
239
|
try {
|
|
227
240
|
startSpinner('Creating auth session...');
|
|
228
241
|
const session = await createSession({
|
|
229
|
-
cliVersion: "0.2.
|
|
242
|
+
cliVersion: "0.2.2",
|
|
230
243
|
os: `${platform()} ${release()}`,
|
|
231
244
|
hostname: hostname()
|
|
232
245
|
});
|
|
@@ -634,6 +647,12 @@ function validateDeviceMode(device) {
|
|
|
634
647
|
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
635
648
|
}
|
|
636
649
|
}
|
|
650
|
+
function validateModel(model) {
|
|
651
|
+
if (model && !SUPPORTED_MODELS.includes(model)) {
|
|
652
|
+
output_error(`Invalid model "${model}". Supported models: ${SUPPORTED_MODELS.join(', ')}`);
|
|
653
|
+
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
637
656
|
function readFileIfExists(filePath) {
|
|
638
657
|
if (existsSync(filePath)) return readFileSync(filePath, 'utf-8');
|
|
639
658
|
}
|
|
@@ -709,29 +728,140 @@ async function executeFlowPages(draftId, data) {
|
|
|
709
728
|
const response = await client.post(`/external/drafts/${draftId}/flow/execute`, data);
|
|
710
729
|
return response.data;
|
|
711
730
|
}
|
|
731
|
+
function parseContextFileArg(arg) {
|
|
732
|
+
const match = arg.match(/^(.+?):(\d+)(?::(\d+))?$/);
|
|
733
|
+
if (!match) return {
|
|
734
|
+
rawPath: arg
|
|
735
|
+
};
|
|
736
|
+
const [, pathPart, startStr, endStr] = match;
|
|
737
|
+
const startLine = parseInt(startStr, 10);
|
|
738
|
+
const endLine = void 0 !== endStr ? parseInt(endStr, 10) : void 0;
|
|
739
|
+
if (startLine < 1) {
|
|
740
|
+
output_error(`Invalid line range in "${arg}": start line must be >= 1`);
|
|
741
|
+
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
742
|
+
}
|
|
743
|
+
if (void 0 !== endLine && endLine < startLine) {
|
|
744
|
+
output_error(`Invalid line range in "${arg}": end line (${endLine}) must be >= start line (${startLine})`);
|
|
745
|
+
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
746
|
+
}
|
|
747
|
+
const cwd = process.cwd();
|
|
748
|
+
const extractedAbsolute = external_path_resolve(cwd, pathPart);
|
|
749
|
+
const originalAbsolute = external_path_resolve(cwd, arg);
|
|
750
|
+
if (!existsSync(extractedAbsolute) && existsSync(originalAbsolute)) return {
|
|
751
|
+
rawPath: arg
|
|
752
|
+
};
|
|
753
|
+
return {
|
|
754
|
+
rawPath: pathPart,
|
|
755
|
+
startLine,
|
|
756
|
+
endLine
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
function extractLines(content, start, end) {
|
|
760
|
+
const lines = content.split('\n');
|
|
761
|
+
const from = Math.max(0, start - 1);
|
|
762
|
+
const to = Math.min(lines.length, end);
|
|
763
|
+
return lines.slice(from, to).join('\n');
|
|
764
|
+
}
|
|
765
|
+
function mergeRanges(ranges) {
|
|
766
|
+
if (0 === ranges.length) return [];
|
|
767
|
+
const sorted = [
|
|
768
|
+
...ranges
|
|
769
|
+
].sort((a, b)=>a.start - b.start);
|
|
770
|
+
const merged = [
|
|
771
|
+
sorted[0]
|
|
772
|
+
];
|
|
773
|
+
for(let i = 1; i < sorted.length; i++){
|
|
774
|
+
const prev = merged[merged.length - 1];
|
|
775
|
+
const curr = sorted[i];
|
|
776
|
+
if (prev.end >= curr.start - 1 || prev.end === 1 / 0) prev.end = Math.max(prev.end, curr.end);
|
|
777
|
+
else merged.push({
|
|
778
|
+
...curr
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
return merged;
|
|
782
|
+
}
|
|
783
|
+
function formatRangeContent(content, ranges) {
|
|
784
|
+
const totalLines = content.split('\n').length;
|
|
785
|
+
const parts = [];
|
|
786
|
+
for(let i = 0; i < ranges.length; i++){
|
|
787
|
+
const range = ranges[i];
|
|
788
|
+
const effectiveEnd = range.end === 1 / 0 ? totalLines : Math.min(range.end, totalLines);
|
|
789
|
+
if (i > 0) {
|
|
790
|
+
const prevEnd = ranges[i - 1].end === 1 / 0 ? totalLines : Math.min(ranges[i - 1].end, totalLines);
|
|
791
|
+
const gapStart = prevEnd + 1;
|
|
792
|
+
const gapEnd = range.start - 1;
|
|
793
|
+
if (gapEnd >= gapStart) {
|
|
794
|
+
const gapSize = gapEnd - gapStart + 1;
|
|
795
|
+
parts.push(`// ... (lines ${gapStart}-${gapEnd} omitted, ${gapSize} lines) ...`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
parts.push(`// === Lines ${range.start}-${effectiveEnd} ===`);
|
|
799
|
+
parts.push(extractLines(content, range.start, effectiveEnd));
|
|
800
|
+
}
|
|
801
|
+
return parts.join('\n');
|
|
802
|
+
}
|
|
712
803
|
function readContextFiles(filePaths) {
|
|
713
804
|
const cwd = process.cwd();
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
805
|
+
const refs = filePaths.map(parseContextFileArg);
|
|
806
|
+
const groups = new Map();
|
|
807
|
+
for (const ref of refs){
|
|
808
|
+
const absolutePath = external_path_resolve(cwd, ref.rawPath);
|
|
809
|
+
const relPath = relative(cwd, absolutePath);
|
|
810
|
+
let group = groups.get(absolutePath);
|
|
811
|
+
if (!group) {
|
|
812
|
+
group = {
|
|
813
|
+
relPath,
|
|
814
|
+
fullFile: false,
|
|
815
|
+
ranges: []
|
|
722
816
|
};
|
|
817
|
+
groups.set(absolutePath, group);
|
|
818
|
+
}
|
|
819
|
+
if (void 0 === ref.startLine) group.fullFile = true;
|
|
820
|
+
else group.ranges.push({
|
|
821
|
+
start: ref.startLine,
|
|
822
|
+
end: ref.endLine ?? 1 / 0
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
const results = [];
|
|
826
|
+
for (const [absolutePath, group] of groups){
|
|
827
|
+
let content;
|
|
828
|
+
try {
|
|
829
|
+
content = readFileSync(absolutePath, 'utf-8');
|
|
723
830
|
} catch (err) {
|
|
724
831
|
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
725
|
-
output_error(`Failed to read context file "${
|
|
832
|
+
output_error(`Failed to read context file "${group.relPath}": ${msg}`);
|
|
726
833
|
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
727
834
|
}
|
|
728
|
-
|
|
835
|
+
if (group.fullFile || 0 === group.ranges.length) {
|
|
836
|
+
results.push({
|
|
837
|
+
filename: group.relPath,
|
|
838
|
+
content
|
|
839
|
+
});
|
|
840
|
+
continue;
|
|
841
|
+
}
|
|
842
|
+
const merged = mergeRanges(group.ranges);
|
|
843
|
+
const totalLines = content.split('\n').length;
|
|
844
|
+
if (1 === merged.length && merged[0].start <= 1 && (merged[0].end === 1 / 0 || merged[0].end >= totalLines)) {
|
|
845
|
+
results.push({
|
|
846
|
+
filename: group.relPath,
|
|
847
|
+
content
|
|
848
|
+
});
|
|
849
|
+
continue;
|
|
850
|
+
}
|
|
851
|
+
const formatted = formatRangeContent(content, merged);
|
|
852
|
+
results.push({
|
|
853
|
+
filename: group.relPath,
|
|
854
|
+
content: formatted
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
return results;
|
|
729
858
|
}
|
|
730
859
|
function createCreateDesignDraftCommand() {
|
|
731
|
-
const command = new Command('create-design-draft').description('Create a design draft from scratch without any reference, Default dont use this, use iterate-design-draft instead').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('--context-file <paths...>', 'UI source files to include as context for AI generation').option('--json', 'Output in JSON format').action(async (options)=>{
|
|
860
|
+
const command = new Command('create-design-draft').description('Create a design draft from scratch without any reference, Default dont use this, use iterate-design-draft instead').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('--context-file <paths...>', 'UI source files to include as context for AI generation').option('--model <modelId>', 'Model ID to use for generation (e.g. claude-sonnet-4-5)').option('--json', 'Output in JSON format').action(async (options)=>{
|
|
732
861
|
if (options.json) setJsonMode(true);
|
|
733
862
|
job_runner_requireAuth(manager_isAuthenticated);
|
|
734
863
|
validateDeviceMode(options.device);
|
|
864
|
+
validateModel(options.model);
|
|
735
865
|
const contextFiles = options.contextFile ? readContextFiles(options.contextFile) : void 0;
|
|
736
866
|
await runJob({
|
|
737
867
|
startLabel: 'Creating design draft...',
|
|
@@ -743,7 +873,8 @@ function createCreateDesignDraftCommand() {
|
|
|
743
873
|
title: options.title,
|
|
744
874
|
prompt: options.prompt,
|
|
745
875
|
deviceMode: options.device,
|
|
746
|
-
contextFiles
|
|
876
|
+
contextFiles,
|
|
877
|
+
model: options.model
|
|
747
878
|
}),
|
|
748
879
|
transformResult: (job)=>({
|
|
749
880
|
draftId: job.result.draftId,
|
|
@@ -815,20 +946,23 @@ Usage Examples:
|
|
|
815
946
|
superdesign iterate-design-draft --draft-id <id> -p "dark theme" -p "minimal" -p "bold" --mode branch --json
|
|
816
947
|
|
|
817
948
|
# Iterate: Auto explore (only give exploration direction, and let Superdesign fill in details, e.g. explore different styles; Default do NOT use auto explore mode)
|
|
818
|
-
superdesign iterate-design-draft --draft-id <id> -p "..." --mode branch --count 3 --json`).requiredOption('--draft-id <id>', 'Draft ID to iterate on').requiredOption('-p, --prompt <prompt...>', 'Iteration prompt(s). Use multiple -p for specific prompts per variation.').requiredOption('--mode <mode>', 'Iteration mode (replace or branch)').option('--count <count>', 'Number of variations (1-4). Only used when single prompt provided.').option('--context-file <paths...>', 'UI source files to include as context for AI generation').option('--json', 'Output in JSON format').addOption(new Option('--project-id <id>').hideHelp()).action(async (options)=>{
|
|
949
|
+
superdesign iterate-design-draft --draft-id <id> -p "..." --mode branch --count 3 --json`).requiredOption('--draft-id <id>', 'Draft ID to iterate on').requiredOption('-p, --prompt <prompt...>', 'Iteration prompt(s). Use multiple -p for specific prompts per variation.').requiredOption('--mode <mode>', 'Iteration mode (replace or branch)').option('--count <count>', 'Number of variations (1-4). Only used when single prompt provided.').option('--context-file <paths...>', 'UI source files to include as context for AI generation').option('--model <modelId>', 'Model ID to use for generation (e.g. claude-sonnet-4-5)').option('--json', 'Output in JSON format').addOption(new Option('--project-id <id>').hideHelp()).action(async (options)=>{
|
|
819
950
|
if (options.json) setJsonMode(true);
|
|
820
951
|
job_runner_requireAuth(manager_isAuthenticated);
|
|
952
|
+
validateModel(options.model);
|
|
821
953
|
const { prompts, count, mode } = validateOptions(options);
|
|
822
954
|
const contextFiles = options.contextFile ? readContextFiles(options.contextFile) : void 0;
|
|
823
955
|
const requestData = prompts.length > 1 ? {
|
|
824
956
|
prompts,
|
|
825
957
|
mode,
|
|
826
|
-
contextFiles
|
|
958
|
+
contextFiles,
|
|
959
|
+
model: options.model
|
|
827
960
|
} : {
|
|
828
961
|
prompt: prompts[0],
|
|
829
962
|
count: count,
|
|
830
963
|
mode,
|
|
831
|
-
contextFiles
|
|
964
|
+
contextFiles,
|
|
965
|
+
model: options.model
|
|
832
966
|
};
|
|
833
967
|
await runJob({
|
|
834
968
|
startLabel: 'Starting iteration...',
|
|
@@ -899,9 +1033,10 @@ function parsePages(pagesJson) {
|
|
|
899
1033
|
return parsed;
|
|
900
1034
|
}
|
|
901
1035
|
function createExecuteFlowPagesCommand() {
|
|
902
|
-
const command = new Command('execute-flow-pages').description('Generate flow pages using AI').requiredOption('--draft-id <id>', 'Source draft ID').requiredOption('--pages <json>', 'JSON array of pages to generate [{title, prompt}]').option('--context <context>', 'Additional context for flow generation').option('--context-file <paths...>', 'UI source files to include as context for AI generation').option('--json', 'Output in JSON format').action(async (options)=>{
|
|
1036
|
+
const command = new Command('execute-flow-pages').description('Generate flow pages using AI').requiredOption('--draft-id <id>', 'Source draft ID').requiredOption('--pages <json>', 'JSON array of pages to generate [{title, prompt}]').option('--context <context>', 'Additional context for flow generation').option('--context-file <paths...>', 'UI source files to include as context for AI generation').option('--model <modelId>', 'Model ID to use for generation (e.g. claude-sonnet-4-5)').option('--json', 'Output in JSON format').action(async (options)=>{
|
|
903
1037
|
if (options.json) setJsonMode(true);
|
|
904
1038
|
job_runner_requireAuth(manager_isAuthenticated);
|
|
1039
|
+
validateModel(options.model);
|
|
905
1040
|
let pages;
|
|
906
1041
|
try {
|
|
907
1042
|
pages = parsePages(options.pages);
|
|
@@ -931,7 +1066,8 @@ function createExecuteFlowPagesCommand() {
|
|
|
931
1066
|
startJob: ()=>executeFlowPages(options.draftId, {
|
|
932
1067
|
flowContext: options.context,
|
|
933
1068
|
pages,
|
|
934
|
-
contextFiles
|
|
1069
|
+
contextFiles,
|
|
1070
|
+
model: options.model
|
|
935
1071
|
}),
|
|
936
1072
|
transformResult: (job)=>({
|
|
937
1073
|
drafts: job.result.drafts,
|
|
@@ -1244,7 +1380,7 @@ async function trackCommand(opts) {
|
|
|
1244
1380
|
durationMs: opts.durationMs,
|
|
1245
1381
|
errorCode: opts.errorCode,
|
|
1246
1382
|
options: opts.options,
|
|
1247
|
-
cliVersion: "0.2.
|
|
1383
|
+
cliVersion: "0.2.2",
|
|
1248
1384
|
os: `${os.platform()} ${os.release()}`
|
|
1249
1385
|
};
|
|
1250
1386
|
const posthog = getPostHog();
|
|
@@ -1312,7 +1448,7 @@ function findFailedCommand(program) {
|
|
|
1312
1448
|
}
|
|
1313
1449
|
function createProgram() {
|
|
1314
1450
|
const program = new Command();
|
|
1315
|
-
program.name('superdesign').description('SuperDesign CLI - AI product designer for coding agents').version("0.2.
|
|
1451
|
+
program.name('superdesign').description('SuperDesign CLI - AI product designer for coding agents').version("0.2.2");
|
|
1316
1452
|
program.configureOutput({
|
|
1317
1453
|
writeErr: (str)=>{
|
|
1318
1454
|
if (!process.argv.includes('--json')) process.stderr.write(str);
|
|
@@ -1,9 +1,70 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Utilities for reading context files from disk
|
|
2
|
+
* Utilities for reading context files from disk.
|
|
3
|
+
*
|
|
4
|
+
* Supports optional line-range syntax:
|
|
5
|
+
* path → full file
|
|
6
|
+
* path:startLine → from startLine to end-of-file (1-based)
|
|
7
|
+
* path:start:end → lines start–end inclusive (1-based)
|
|
8
|
+
*
|
|
9
|
+
* Multiple ranges from the same file are merged into a single ContextFile
|
|
10
|
+
* with omission markers between non-contiguous ranges.
|
|
3
11
|
*/
|
|
4
12
|
import type { ContextFile } from '../api/drafts';
|
|
13
|
+
/** A parsed reference to a file with an optional line range. */
|
|
14
|
+
interface ParsedFileRef {
|
|
15
|
+
/** Raw path as the user typed it (before resolution). */
|
|
16
|
+
rawPath: string;
|
|
17
|
+
/** Start line (1-based, inclusive). `undefined` means "from the beginning". */
|
|
18
|
+
startLine?: number;
|
|
19
|
+
/** End line (1-based, inclusive). `undefined` means "to the end of the file". */
|
|
20
|
+
endLine?: number;
|
|
21
|
+
}
|
|
22
|
+
/** Normalised, validated range ready for slicing. */
|
|
23
|
+
interface LineRange {
|
|
24
|
+
start: number;
|
|
25
|
+
end: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse a `--context-file` argument into a path and optional line range.
|
|
29
|
+
*
|
|
30
|
+
* Accepted forms:
|
|
31
|
+
* "src/App.tsx" → { rawPath: "src/App.tsx" }
|
|
32
|
+
* "src/App.tsx:10:50" → { rawPath: "src/App.tsx", startLine: 10, endLine: 50 }
|
|
33
|
+
* "src/App.tsx:10" → { rawPath: "src/App.tsx", startLine: 10 }
|
|
34
|
+
*
|
|
35
|
+
* Windows paths (C:\foo\bar.ts) are handled: we only split on the
|
|
36
|
+
* *last* colon-separated numeric segments.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parseContextFileArg(arg: string): ParsedFileRef;
|
|
39
|
+
/**
|
|
40
|
+
* Extract a range of lines from content.
|
|
41
|
+
* Both `start` and `end` are 1-based inclusive.
|
|
42
|
+
* If `end` exceeds total lines the result is clamped.
|
|
43
|
+
*/
|
|
44
|
+
export declare function extractLines(content: string, start: number, end: number): string;
|
|
45
|
+
/**
|
|
46
|
+
* Merge overlapping or adjacent line ranges.
|
|
47
|
+
* Input ranges do not need to be sorted.
|
|
48
|
+
* Returns sorted, non-overlapping ranges.
|
|
49
|
+
*/
|
|
50
|
+
export declare function mergeRanges(ranges: LineRange[]): LineRange[];
|
|
51
|
+
/**
|
|
52
|
+
* Build annotated content for multiple line ranges from a single file.
|
|
53
|
+
*
|
|
54
|
+
* Each range gets a `// === Lines X-Y ===` header.
|
|
55
|
+
* Gaps between ranges get `// ... (lines X-Y omitted, N lines) ...` markers.
|
|
56
|
+
*/
|
|
57
|
+
export declare function formatRangeContent(content: string, ranges: LineRange[]): string;
|
|
5
58
|
/**
|
|
6
59
|
* Read context files from disk and return as ContextFile array.
|
|
60
|
+
*
|
|
61
|
+
* Supports `path:startLine:endLine` syntax. Multiple references to the
|
|
62
|
+
* same file are aggregated: the file is read once and ranges are merged.
|
|
63
|
+
*
|
|
64
|
+
* If any file has *both* a full-file reference and partial ranges, the
|
|
65
|
+
* full file content wins (no annotations).
|
|
66
|
+
*
|
|
7
67
|
* Exits the process if any file cannot be read.
|
|
8
68
|
*/
|
|
9
69
|
export declare function readContextFiles(filePaths: string[]): ContextFile[];
|
|
70
|
+
export {};
|
|
@@ -51,3 +51,7 @@ export type DeviceMode = (typeof VALID_DEVICES)[number];
|
|
|
51
51
|
* Validate device mode option and exit with error if invalid
|
|
52
52
|
*/
|
|
53
53
|
export declare function validateDeviceMode(device: string | undefined): void;
|
|
54
|
+
/**
|
|
55
|
+
* Validate model option and exit with error if invalid
|
|
56
|
+
*/
|
|
57
|
+
export declare function validateModel(model: string | undefined): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superdesign/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "CLI for SuperDesign Platform - agent skills for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -53,4 +53,4 @@
|
|
|
53
53
|
"directory": "packages/cli"
|
|
54
54
|
},
|
|
55
55
|
"license": "MIT"
|
|
56
|
-
}
|
|
56
|
+
}
|