@superdesign/cli 0.1.13 → 0.2.1
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 +7 -0
- package/dist/config/constants.d.ts +1 -1
- package/dist/index.cjs +145 -10
- package/dist/index.js +146 -11
- package/dist/utils/context-files.d.ts +70 -0
- package/package.json +1 -1
package/dist/api/drafts.d.ts
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Drafts API endpoints
|
|
3
3
|
*/
|
|
4
|
+
export interface ContextFile {
|
|
5
|
+
filename: string;
|
|
6
|
+
content: string;
|
|
7
|
+
}
|
|
4
8
|
export interface CreateDraftRequest {
|
|
5
9
|
title: string;
|
|
6
10
|
prompt: string;
|
|
7
11
|
deviceMode?: 'mobile' | 'tablet' | 'desktop';
|
|
12
|
+
contextFiles?: ContextFile[];
|
|
8
13
|
}
|
|
9
14
|
export interface IterateDraftRequest {
|
|
10
15
|
prompts?: string[];
|
|
11
16
|
prompt?: string;
|
|
12
17
|
count?: 1 | 2 | 3 | 4;
|
|
13
18
|
mode: 'replace' | 'branch';
|
|
19
|
+
contextFiles?: ContextFile[];
|
|
14
20
|
}
|
|
15
21
|
export interface PlanFlowRequest {
|
|
16
22
|
flowContext?: string;
|
|
@@ -22,6 +28,7 @@ export interface FlowPage {
|
|
|
22
28
|
export interface ExecuteFlowRequest {
|
|
23
29
|
flowContext?: string;
|
|
24
30
|
pages: FlowPage[];
|
|
31
|
+
contextFiles?: ContextFile[];
|
|
25
32
|
}
|
|
26
33
|
export interface JobCreatedResponse {
|
|
27
34
|
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.1
|
|
15
|
+
export declare const CLI_VERSION = "0.2.1";
|
|
16
16
|
/** PostHog analytics configuration */
|
|
17
17
|
export declare const POSTHOG_KEY: string;
|
|
18
18
|
export declare const POSTHOG_HOST: string;
|
package/dist/index.cjs
CHANGED
|
@@ -316,7 +316,7 @@ var __webpack_exports__ = {};
|
|
|
316
316
|
try {
|
|
317
317
|
startSpinner('Creating auth session...');
|
|
318
318
|
const session = await createSession({
|
|
319
|
-
cliVersion: "0.1
|
|
319
|
+
cliVersion: "0.2.1",
|
|
320
320
|
os: `${external_os_namespaceObject.platform()} ${external_os_namespaceObject.release()}`,
|
|
321
321
|
hostname: external_os_namespaceObject.hostname()
|
|
322
322
|
});
|
|
@@ -799,11 +799,140 @@ superdesign --help
|
|
|
799
799
|
const response = await client.post(`/external/drafts/${draftId}/flow/execute`, data);
|
|
800
800
|
return response.data;
|
|
801
801
|
}
|
|
802
|
+
function parseContextFileArg(arg) {
|
|
803
|
+
const match = arg.match(/^(.+?):(\d+)(?::(\d+))?$/);
|
|
804
|
+
if (!match) return {
|
|
805
|
+
rawPath: arg
|
|
806
|
+
};
|
|
807
|
+
const [, pathPart, startStr, endStr] = match;
|
|
808
|
+
const startLine = parseInt(startStr, 10);
|
|
809
|
+
const endLine = void 0 !== endStr ? parseInt(endStr, 10) : void 0;
|
|
810
|
+
if (startLine < 1) {
|
|
811
|
+
output_error(`Invalid line range in "${arg}": start line must be >= 1`);
|
|
812
|
+
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
813
|
+
}
|
|
814
|
+
if (void 0 !== endLine && endLine < startLine) {
|
|
815
|
+
output_error(`Invalid line range in "${arg}": end line (${endLine}) must be >= start line (${startLine})`);
|
|
816
|
+
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
817
|
+
}
|
|
818
|
+
const cwd = process.cwd();
|
|
819
|
+
const extractedAbsolute = (0, external_path_namespaceObject.resolve)(cwd, pathPart);
|
|
820
|
+
const originalAbsolute = (0, external_path_namespaceObject.resolve)(cwd, arg);
|
|
821
|
+
if (!(0, external_fs_namespaceObject.existsSync)(extractedAbsolute) && (0, external_fs_namespaceObject.existsSync)(originalAbsolute)) return {
|
|
822
|
+
rawPath: arg
|
|
823
|
+
};
|
|
824
|
+
return {
|
|
825
|
+
rawPath: pathPart,
|
|
826
|
+
startLine,
|
|
827
|
+
endLine
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function extractLines(content, start, end) {
|
|
831
|
+
const lines = content.split('\n');
|
|
832
|
+
const from = Math.max(0, start - 1);
|
|
833
|
+
const to = Math.min(lines.length, end);
|
|
834
|
+
return lines.slice(from, to).join('\n');
|
|
835
|
+
}
|
|
836
|
+
function mergeRanges(ranges) {
|
|
837
|
+
if (0 === ranges.length) return [];
|
|
838
|
+
const sorted = [
|
|
839
|
+
...ranges
|
|
840
|
+
].sort((a, b)=>a.start - b.start);
|
|
841
|
+
const merged = [
|
|
842
|
+
sorted[0]
|
|
843
|
+
];
|
|
844
|
+
for(let i = 1; i < sorted.length; i++){
|
|
845
|
+
const prev = merged[merged.length - 1];
|
|
846
|
+
const curr = sorted[i];
|
|
847
|
+
if (prev.end >= curr.start - 1 || prev.end === 1 / 0) prev.end = Math.max(prev.end, curr.end);
|
|
848
|
+
else merged.push({
|
|
849
|
+
...curr
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
return merged;
|
|
853
|
+
}
|
|
854
|
+
function formatRangeContent(content, ranges) {
|
|
855
|
+
const totalLines = content.split('\n').length;
|
|
856
|
+
const parts = [];
|
|
857
|
+
for(let i = 0; i < ranges.length; i++){
|
|
858
|
+
const range = ranges[i];
|
|
859
|
+
const effectiveEnd = range.end === 1 / 0 ? totalLines : Math.min(range.end, totalLines);
|
|
860
|
+
if (i > 0) {
|
|
861
|
+
const prevEnd = ranges[i - 1].end === 1 / 0 ? totalLines : Math.min(ranges[i - 1].end, totalLines);
|
|
862
|
+
const gapStart = prevEnd + 1;
|
|
863
|
+
const gapEnd = range.start - 1;
|
|
864
|
+
if (gapEnd >= gapStart) {
|
|
865
|
+
const gapSize = gapEnd - gapStart + 1;
|
|
866
|
+
parts.push(`// ... (lines ${gapStart}-${gapEnd} omitted, ${gapSize} lines) ...`);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
parts.push(`// === Lines ${range.start}-${effectiveEnd} ===`);
|
|
870
|
+
parts.push(extractLines(content, range.start, effectiveEnd));
|
|
871
|
+
}
|
|
872
|
+
return parts.join('\n');
|
|
873
|
+
}
|
|
874
|
+
function readContextFiles(filePaths) {
|
|
875
|
+
const cwd = process.cwd();
|
|
876
|
+
const refs = filePaths.map(parseContextFileArg);
|
|
877
|
+
const groups = new Map();
|
|
878
|
+
for (const ref of refs){
|
|
879
|
+
const absolutePath = (0, external_path_namespaceObject.resolve)(cwd, ref.rawPath);
|
|
880
|
+
const relPath = (0, external_path_namespaceObject.relative)(cwd, absolutePath);
|
|
881
|
+
let group = groups.get(absolutePath);
|
|
882
|
+
if (!group) {
|
|
883
|
+
group = {
|
|
884
|
+
relPath,
|
|
885
|
+
fullFile: false,
|
|
886
|
+
ranges: []
|
|
887
|
+
};
|
|
888
|
+
groups.set(absolutePath, group);
|
|
889
|
+
}
|
|
890
|
+
if (void 0 === ref.startLine) group.fullFile = true;
|
|
891
|
+
else group.ranges.push({
|
|
892
|
+
start: ref.startLine,
|
|
893
|
+
end: ref.endLine ?? 1 / 0
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
const results = [];
|
|
897
|
+
for (const [absolutePath, group] of groups){
|
|
898
|
+
let content;
|
|
899
|
+
try {
|
|
900
|
+
content = (0, external_fs_namespaceObject.readFileSync)(absolutePath, 'utf-8');
|
|
901
|
+
} catch (err) {
|
|
902
|
+
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
903
|
+
output_error(`Failed to read context file "${group.relPath}": ${msg}`);
|
|
904
|
+
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
905
|
+
}
|
|
906
|
+
if (group.fullFile || 0 === group.ranges.length) {
|
|
907
|
+
results.push({
|
|
908
|
+
filename: group.relPath,
|
|
909
|
+
content
|
|
910
|
+
});
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
const merged = mergeRanges(group.ranges);
|
|
914
|
+
const totalLines = content.split('\n').length;
|
|
915
|
+
if (1 === merged.length && merged[0].start <= 1 && (merged[0].end === 1 / 0 || merged[0].end >= totalLines)) {
|
|
916
|
+
results.push({
|
|
917
|
+
filename: group.relPath,
|
|
918
|
+
content
|
|
919
|
+
});
|
|
920
|
+
continue;
|
|
921
|
+
}
|
|
922
|
+
const formatted = formatRangeContent(content, merged);
|
|
923
|
+
results.push({
|
|
924
|
+
filename: group.relPath,
|
|
925
|
+
content: formatted
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
return results;
|
|
929
|
+
}
|
|
802
930
|
function createCreateDesignDraftCommand() {
|
|
803
|
-
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('--json', 'Output in JSON format').action(async (options)=>{
|
|
931
|
+
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)=>{
|
|
804
932
|
if (options.json) setJsonMode(true);
|
|
805
933
|
job_runner_requireAuth(manager_isAuthenticated);
|
|
806
934
|
validateDeviceMode(options.device);
|
|
935
|
+
const contextFiles = options.contextFile ? readContextFiles(options.contextFile) : void 0;
|
|
807
936
|
await runJob({
|
|
808
937
|
startLabel: 'Creating design draft...',
|
|
809
938
|
pollingLabel: 'Generating design with AI...',
|
|
@@ -813,7 +942,8 @@ superdesign --help
|
|
|
813
942
|
startJob: ()=>createDraft(options.projectId, {
|
|
814
943
|
title: options.title,
|
|
815
944
|
prompt: options.prompt,
|
|
816
|
-
deviceMode: options.device
|
|
945
|
+
deviceMode: options.device,
|
|
946
|
+
contextFiles
|
|
817
947
|
}),
|
|
818
948
|
transformResult: (job)=>({
|
|
819
949
|
draftId: job.result.draftId,
|
|
@@ -885,17 +1015,20 @@ Usage Examples:
|
|
|
885
1015
|
superdesign iterate-design-draft --draft-id <id> -p "dark theme" -p "minimal" -p "bold" --mode branch --json
|
|
886
1016
|
|
|
887
1017
|
# 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)
|
|
888
|
-
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('--json', 'Output in JSON format').addOption(new external_commander_namespaceObject.Option('--project-id <id>').hideHelp()).action(async (options)=>{
|
|
1018
|
+
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)=>{
|
|
889
1019
|
if (options.json) setJsonMode(true);
|
|
890
1020
|
job_runner_requireAuth(manager_isAuthenticated);
|
|
891
1021
|
const { prompts, count, mode } = validateOptions(options);
|
|
1022
|
+
const contextFiles = options.contextFile ? readContextFiles(options.contextFile) : void 0;
|
|
892
1023
|
const requestData = prompts.length > 1 ? {
|
|
893
1024
|
prompts,
|
|
894
|
-
mode
|
|
1025
|
+
mode,
|
|
1026
|
+
contextFiles
|
|
895
1027
|
} : {
|
|
896
1028
|
prompt: prompts[0],
|
|
897
1029
|
count: count,
|
|
898
|
-
mode
|
|
1030
|
+
mode,
|
|
1031
|
+
contextFiles
|
|
899
1032
|
};
|
|
900
1033
|
await runJob({
|
|
901
1034
|
startLabel: 'Starting iteration...',
|
|
@@ -966,7 +1099,7 @@ Usage Examples:
|
|
|
966
1099
|
return parsed;
|
|
967
1100
|
}
|
|
968
1101
|
function createExecuteFlowPagesCommand() {
|
|
969
|
-
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('--json', 'Output in JSON format').action(async (options)=>{
|
|
1102
|
+
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)=>{
|
|
970
1103
|
if (options.json) setJsonMode(true);
|
|
971
1104
|
job_runner_requireAuth(manager_isAuthenticated);
|
|
972
1105
|
let pages;
|
|
@@ -986,6 +1119,7 @@ Usage Examples:
|
|
|
986
1119
|
output_error('Maximum 10 pages allowed');
|
|
987
1120
|
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
988
1121
|
}
|
|
1122
|
+
const contextFiles = options.contextFile ? readContextFiles(options.contextFile) : void 0;
|
|
989
1123
|
const timeoutMs = Math.max(300000, 2 * pages.length * 60000);
|
|
990
1124
|
await runJob({
|
|
991
1125
|
startLabel: `Generating ${pages.length} flow page(s)...`,
|
|
@@ -996,7 +1130,8 @@ Usage Examples:
|
|
|
996
1130
|
timeoutMs,
|
|
997
1131
|
startJob: ()=>executeFlowPages(options.draftId, {
|
|
998
1132
|
flowContext: options.context,
|
|
999
|
-
pages
|
|
1133
|
+
pages,
|
|
1134
|
+
contextFiles
|
|
1000
1135
|
}),
|
|
1001
1136
|
transformResult: (job)=>({
|
|
1002
1137
|
drafts: job.result.drafts,
|
|
@@ -1311,7 +1446,7 @@ Usage Examples:
|
|
|
1311
1446
|
durationMs: opts.durationMs,
|
|
1312
1447
|
errorCode: opts.errorCode,
|
|
1313
1448
|
options: opts.options,
|
|
1314
|
-
cliVersion: "0.1
|
|
1449
|
+
cliVersion: "0.2.1",
|
|
1315
1450
|
os: `${external_os_default().platform()} ${external_os_default().release()}`
|
|
1316
1451
|
};
|
|
1317
1452
|
const posthog = getPostHog();
|
|
@@ -1379,7 +1514,7 @@ Usage Examples:
|
|
|
1379
1514
|
}
|
|
1380
1515
|
function createProgram() {
|
|
1381
1516
|
const program = new external_commander_namespaceObject.Command();
|
|
1382
|
-
program.name('superdesign').description('SuperDesign CLI - AI product designer for coding agents').version("0.1
|
|
1517
|
+
program.name('superdesign').description('SuperDesign CLI - AI product designer for coding agents').version("0.2.1");
|
|
1383
1518
|
program.configureOutput({
|
|
1384
1519
|
writeErr: (str)=>{
|
|
1385
1520
|
if (!process.argv.includes('--json')) process.stderr.write(str);
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { config as external_dotenv_config } from "dotenv";
|
|
2
2
|
import { fileURLToPath } from "url";
|
|
3
|
-
import { dirname, join, resolve as external_path_resolve } from "path";
|
|
3
|
+
import { dirname, join, relative, resolve as external_path_resolve } from "path";
|
|
4
4
|
import { Command, Option } from "commander";
|
|
5
5
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
6
6
|
import os, { homedir, hostname, platform, release } from "os";
|
|
@@ -226,7 +226,7 @@ async function runAuthFlow(options = {}) {
|
|
|
226
226
|
try {
|
|
227
227
|
startSpinner('Creating auth session...');
|
|
228
228
|
const session = await createSession({
|
|
229
|
-
cliVersion: "0.1
|
|
229
|
+
cliVersion: "0.2.1",
|
|
230
230
|
os: `${platform()} ${release()}`,
|
|
231
231
|
hostname: hostname()
|
|
232
232
|
});
|
|
@@ -709,11 +709,140 @@ async function executeFlowPages(draftId, data) {
|
|
|
709
709
|
const response = await client.post(`/external/drafts/${draftId}/flow/execute`, data);
|
|
710
710
|
return response.data;
|
|
711
711
|
}
|
|
712
|
+
function parseContextFileArg(arg) {
|
|
713
|
+
const match = arg.match(/^(.+?):(\d+)(?::(\d+))?$/);
|
|
714
|
+
if (!match) return {
|
|
715
|
+
rawPath: arg
|
|
716
|
+
};
|
|
717
|
+
const [, pathPart, startStr, endStr] = match;
|
|
718
|
+
const startLine = parseInt(startStr, 10);
|
|
719
|
+
const endLine = void 0 !== endStr ? parseInt(endStr, 10) : void 0;
|
|
720
|
+
if (startLine < 1) {
|
|
721
|
+
output_error(`Invalid line range in "${arg}": start line must be >= 1`);
|
|
722
|
+
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
723
|
+
}
|
|
724
|
+
if (void 0 !== endLine && endLine < startLine) {
|
|
725
|
+
output_error(`Invalid line range in "${arg}": end line (${endLine}) must be >= start line (${startLine})`);
|
|
726
|
+
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
727
|
+
}
|
|
728
|
+
const cwd = process.cwd();
|
|
729
|
+
const extractedAbsolute = external_path_resolve(cwd, pathPart);
|
|
730
|
+
const originalAbsolute = external_path_resolve(cwd, arg);
|
|
731
|
+
if (!existsSync(extractedAbsolute) && existsSync(originalAbsolute)) return {
|
|
732
|
+
rawPath: arg
|
|
733
|
+
};
|
|
734
|
+
return {
|
|
735
|
+
rawPath: pathPart,
|
|
736
|
+
startLine,
|
|
737
|
+
endLine
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
function extractLines(content, start, end) {
|
|
741
|
+
const lines = content.split('\n');
|
|
742
|
+
const from = Math.max(0, start - 1);
|
|
743
|
+
const to = Math.min(lines.length, end);
|
|
744
|
+
return lines.slice(from, to).join('\n');
|
|
745
|
+
}
|
|
746
|
+
function mergeRanges(ranges) {
|
|
747
|
+
if (0 === ranges.length) return [];
|
|
748
|
+
const sorted = [
|
|
749
|
+
...ranges
|
|
750
|
+
].sort((a, b)=>a.start - b.start);
|
|
751
|
+
const merged = [
|
|
752
|
+
sorted[0]
|
|
753
|
+
];
|
|
754
|
+
for(let i = 1; i < sorted.length; i++){
|
|
755
|
+
const prev = merged[merged.length - 1];
|
|
756
|
+
const curr = sorted[i];
|
|
757
|
+
if (prev.end >= curr.start - 1 || prev.end === 1 / 0) prev.end = Math.max(prev.end, curr.end);
|
|
758
|
+
else merged.push({
|
|
759
|
+
...curr
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
return merged;
|
|
763
|
+
}
|
|
764
|
+
function formatRangeContent(content, ranges) {
|
|
765
|
+
const totalLines = content.split('\n').length;
|
|
766
|
+
const parts = [];
|
|
767
|
+
for(let i = 0; i < ranges.length; i++){
|
|
768
|
+
const range = ranges[i];
|
|
769
|
+
const effectiveEnd = range.end === 1 / 0 ? totalLines : Math.min(range.end, totalLines);
|
|
770
|
+
if (i > 0) {
|
|
771
|
+
const prevEnd = ranges[i - 1].end === 1 / 0 ? totalLines : Math.min(ranges[i - 1].end, totalLines);
|
|
772
|
+
const gapStart = prevEnd + 1;
|
|
773
|
+
const gapEnd = range.start - 1;
|
|
774
|
+
if (gapEnd >= gapStart) {
|
|
775
|
+
const gapSize = gapEnd - gapStart + 1;
|
|
776
|
+
parts.push(`// ... (lines ${gapStart}-${gapEnd} omitted, ${gapSize} lines) ...`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
parts.push(`// === Lines ${range.start}-${effectiveEnd} ===`);
|
|
780
|
+
parts.push(extractLines(content, range.start, effectiveEnd));
|
|
781
|
+
}
|
|
782
|
+
return parts.join('\n');
|
|
783
|
+
}
|
|
784
|
+
function readContextFiles(filePaths) {
|
|
785
|
+
const cwd = process.cwd();
|
|
786
|
+
const refs = filePaths.map(parseContextFileArg);
|
|
787
|
+
const groups = new Map();
|
|
788
|
+
for (const ref of refs){
|
|
789
|
+
const absolutePath = external_path_resolve(cwd, ref.rawPath);
|
|
790
|
+
const relPath = relative(cwd, absolutePath);
|
|
791
|
+
let group = groups.get(absolutePath);
|
|
792
|
+
if (!group) {
|
|
793
|
+
group = {
|
|
794
|
+
relPath,
|
|
795
|
+
fullFile: false,
|
|
796
|
+
ranges: []
|
|
797
|
+
};
|
|
798
|
+
groups.set(absolutePath, group);
|
|
799
|
+
}
|
|
800
|
+
if (void 0 === ref.startLine) group.fullFile = true;
|
|
801
|
+
else group.ranges.push({
|
|
802
|
+
start: ref.startLine,
|
|
803
|
+
end: ref.endLine ?? 1 / 0
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
const results = [];
|
|
807
|
+
for (const [absolutePath, group] of groups){
|
|
808
|
+
let content;
|
|
809
|
+
try {
|
|
810
|
+
content = readFileSync(absolutePath, 'utf-8');
|
|
811
|
+
} catch (err) {
|
|
812
|
+
const msg = err instanceof Error ? err.message : 'Unknown error';
|
|
813
|
+
output_error(`Failed to read context file "${group.relPath}": ${msg}`);
|
|
814
|
+
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
815
|
+
}
|
|
816
|
+
if (group.fullFile || 0 === group.ranges.length) {
|
|
817
|
+
results.push({
|
|
818
|
+
filename: group.relPath,
|
|
819
|
+
content
|
|
820
|
+
});
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
const merged = mergeRanges(group.ranges);
|
|
824
|
+
const totalLines = content.split('\n').length;
|
|
825
|
+
if (1 === merged.length && merged[0].start <= 1 && (merged[0].end === 1 / 0 || merged[0].end >= totalLines)) {
|
|
826
|
+
results.push({
|
|
827
|
+
filename: group.relPath,
|
|
828
|
+
content
|
|
829
|
+
});
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
const formatted = formatRangeContent(content, merged);
|
|
833
|
+
results.push({
|
|
834
|
+
filename: group.relPath,
|
|
835
|
+
content: formatted
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
return results;
|
|
839
|
+
}
|
|
712
840
|
function createCreateDesignDraftCommand() {
|
|
713
|
-
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('--json', 'Output in JSON format').action(async (options)=>{
|
|
841
|
+
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)=>{
|
|
714
842
|
if (options.json) setJsonMode(true);
|
|
715
843
|
job_runner_requireAuth(manager_isAuthenticated);
|
|
716
844
|
validateDeviceMode(options.device);
|
|
845
|
+
const contextFiles = options.contextFile ? readContextFiles(options.contextFile) : void 0;
|
|
717
846
|
await runJob({
|
|
718
847
|
startLabel: 'Creating design draft...',
|
|
719
848
|
pollingLabel: 'Generating design with AI...',
|
|
@@ -723,7 +852,8 @@ function createCreateDesignDraftCommand() {
|
|
|
723
852
|
startJob: ()=>createDraft(options.projectId, {
|
|
724
853
|
title: options.title,
|
|
725
854
|
prompt: options.prompt,
|
|
726
|
-
deviceMode: options.device
|
|
855
|
+
deviceMode: options.device,
|
|
856
|
+
contextFiles
|
|
727
857
|
}),
|
|
728
858
|
transformResult: (job)=>({
|
|
729
859
|
draftId: job.result.draftId,
|
|
@@ -795,17 +925,20 @@ Usage Examples:
|
|
|
795
925
|
superdesign iterate-design-draft --draft-id <id> -p "dark theme" -p "minimal" -p "bold" --mode branch --json
|
|
796
926
|
|
|
797
927
|
# 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)
|
|
798
|
-
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('--json', 'Output in JSON format').addOption(new Option('--project-id <id>').hideHelp()).action(async (options)=>{
|
|
928
|
+
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)=>{
|
|
799
929
|
if (options.json) setJsonMode(true);
|
|
800
930
|
job_runner_requireAuth(manager_isAuthenticated);
|
|
801
931
|
const { prompts, count, mode } = validateOptions(options);
|
|
932
|
+
const contextFiles = options.contextFile ? readContextFiles(options.contextFile) : void 0;
|
|
802
933
|
const requestData = prompts.length > 1 ? {
|
|
803
934
|
prompts,
|
|
804
|
-
mode
|
|
935
|
+
mode,
|
|
936
|
+
contextFiles
|
|
805
937
|
} : {
|
|
806
938
|
prompt: prompts[0],
|
|
807
939
|
count: count,
|
|
808
|
-
mode
|
|
940
|
+
mode,
|
|
941
|
+
contextFiles
|
|
809
942
|
};
|
|
810
943
|
await runJob({
|
|
811
944
|
startLabel: 'Starting iteration...',
|
|
@@ -876,7 +1009,7 @@ function parsePages(pagesJson) {
|
|
|
876
1009
|
return parsed;
|
|
877
1010
|
}
|
|
878
1011
|
function createExecuteFlowPagesCommand() {
|
|
879
|
-
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('--json', 'Output in JSON format').action(async (options)=>{
|
|
1012
|
+
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)=>{
|
|
880
1013
|
if (options.json) setJsonMode(true);
|
|
881
1014
|
job_runner_requireAuth(manager_isAuthenticated);
|
|
882
1015
|
let pages;
|
|
@@ -896,6 +1029,7 @@ function createExecuteFlowPagesCommand() {
|
|
|
896
1029
|
output_error('Maximum 10 pages allowed');
|
|
897
1030
|
process.exit(EXIT_CODES.VALIDATION_ERROR);
|
|
898
1031
|
}
|
|
1032
|
+
const contextFiles = options.contextFile ? readContextFiles(options.contextFile) : void 0;
|
|
899
1033
|
const timeoutMs = Math.max(300000, 2 * pages.length * 60000);
|
|
900
1034
|
await runJob({
|
|
901
1035
|
startLabel: `Generating ${pages.length} flow page(s)...`,
|
|
@@ -906,7 +1040,8 @@ function createExecuteFlowPagesCommand() {
|
|
|
906
1040
|
timeoutMs,
|
|
907
1041
|
startJob: ()=>executeFlowPages(options.draftId, {
|
|
908
1042
|
flowContext: options.context,
|
|
909
|
-
pages
|
|
1043
|
+
pages,
|
|
1044
|
+
contextFiles
|
|
910
1045
|
}),
|
|
911
1046
|
transformResult: (job)=>({
|
|
912
1047
|
drafts: job.result.drafts,
|
|
@@ -1219,7 +1354,7 @@ async function trackCommand(opts) {
|
|
|
1219
1354
|
durationMs: opts.durationMs,
|
|
1220
1355
|
errorCode: opts.errorCode,
|
|
1221
1356
|
options: opts.options,
|
|
1222
|
-
cliVersion: "0.1
|
|
1357
|
+
cliVersion: "0.2.1",
|
|
1223
1358
|
os: `${os.platform()} ${os.release()}`
|
|
1224
1359
|
};
|
|
1225
1360
|
const posthog = getPostHog();
|
|
@@ -1287,7 +1422,7 @@ function findFailedCommand(program) {
|
|
|
1287
1422
|
}
|
|
1288
1423
|
function createProgram() {
|
|
1289
1424
|
const program = new Command();
|
|
1290
|
-
program.name('superdesign').description('SuperDesign CLI - AI product designer for coding agents').version("0.1
|
|
1425
|
+
program.name('superdesign').description('SuperDesign CLI - AI product designer for coding agents').version("0.2.1");
|
|
1291
1426
|
program.configureOutput({
|
|
1292
1427
|
writeErr: (str)=>{
|
|
1293
1428
|
if (!process.argv.includes('--json')) process.stderr.write(str);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
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.
|
|
11
|
+
*/
|
|
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;
|
|
58
|
+
/**
|
|
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
|
+
*
|
|
67
|
+
* Exits the process if any file cannot be read.
|
|
68
|
+
*/
|
|
69
|
+
export declare function readContextFiles(filePaths: string[]): ContextFile[];
|
|
70
|
+
export {};
|