@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.
@@ -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.0";
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.0",
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
- return filePaths.map((filePath)=>{
805
- const absolutePath = (0, external_path_namespaceObject.resolve)(cwd, filePath);
806
- try {
807
- const content = (0, external_fs_namespaceObject.readFileSync)(absolutePath, 'utf-8');
808
- const filename = (0, external_path_namespaceObject.relative)(cwd, absolutePath);
809
- return {
810
- filename,
811
- content
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 "${filePath}": ${msg}`);
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.0",
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.0");
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.0",
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
- return filePaths.map((filePath)=>{
715
- const absolutePath = external_path_resolve(cwd, filePath);
716
- try {
717
- const content = readFileSync(absolutePath, 'utf-8');
718
- const filename = relative(cwd, absolutePath);
719
- return {
720
- filename,
721
- content
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 "${filePath}": ${msg}`);
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.0",
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.0");
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.0",
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
+ }