shipfolio 1.0.2 → 1.0.3
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/cli.js +544 -139
- package/dist/cli.js.map +1 -1
- package/dist/lib/orchestrator/prompt-builder.js +158 -30
- package/dist/lib/orchestrator/prompt-builder.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -591,58 +591,268 @@ var init_scanner = __esm({
|
|
|
591
591
|
}
|
|
592
592
|
});
|
|
593
593
|
|
|
594
|
+
// src/utils/i18n.ts
|
|
595
|
+
function detectLocale() {
|
|
596
|
+
const lang = (process.env.LANG || process.env.LC_ALL || process.env.LC_MESSAGES || process.env.LANGUAGE || "").toLowerCase();
|
|
597
|
+
if (lang.startsWith("zh")) return "zh";
|
|
598
|
+
return "en";
|
|
599
|
+
}
|
|
600
|
+
function initLocale() {
|
|
601
|
+
currentLocale = detectLocale();
|
|
602
|
+
}
|
|
603
|
+
function t() {
|
|
604
|
+
return messages[currentLocale];
|
|
605
|
+
}
|
|
606
|
+
var messages, currentLocale;
|
|
607
|
+
var init_i18n = __esm({
|
|
608
|
+
"src/utils/i18n.ts"() {
|
|
609
|
+
"use strict";
|
|
610
|
+
init_esm_shims();
|
|
611
|
+
messages = {
|
|
612
|
+
en: {
|
|
613
|
+
// Project selection
|
|
614
|
+
selectProjects: "Select projects to include (space = select, enter = confirm):",
|
|
615
|
+
mergePrompt: "Merge any related projects into one entry? (e.g. web + mobile)",
|
|
616
|
+
selectToMerge: "Select projects to merge together (space = select, enter = confirm):",
|
|
617
|
+
mergedNamePrompt: "Name for the merged entry:",
|
|
618
|
+
mergeNeedTwo: "Need at least 2 projects to merge. Skipping.",
|
|
619
|
+
mergedResult: (names, target) => `Merged ${names} -> "${target}"`,
|
|
620
|
+
mergeMore: "Merge more projects?",
|
|
621
|
+
// Per-project
|
|
622
|
+
configuring: (name) => `Configuring: ${name}`,
|
|
623
|
+
descriptionFor: (name) => `Description for ${name} (enter to use auto-generated):`,
|
|
624
|
+
descriptionPlaceholder: "Auto-generate from README",
|
|
625
|
+
demoUrlFor: (name) => `Demo URL for ${name}:`,
|
|
626
|
+
showSourceFor: (name) => `Show source code link for ${name}?`,
|
|
627
|
+
roleFor: (name) => `Your role in ${name}:`,
|
|
628
|
+
metricsFor: (name) => `Key metrics for ${name} (e.g. "1k users, $5k MRR"):`,
|
|
629
|
+
optional: "optional",
|
|
630
|
+
// Personal info
|
|
631
|
+
personalInfo: "Personal Information",
|
|
632
|
+
fullName: "Your full name:",
|
|
633
|
+
nameRequired: "Name is required",
|
|
634
|
+
tagline: "Professional tagline (one line):",
|
|
635
|
+
taglinePlaceholder: "Full-stack developer. I ship things.",
|
|
636
|
+
bio: "Bio:",
|
|
637
|
+
bioAuto: "Auto-generate from my projects",
|
|
638
|
+
bioManual: "Write manually",
|
|
639
|
+
bioPrompt: "Your bio (2-3 sentences):",
|
|
640
|
+
photoUrl: "Photo URL (optional):",
|
|
641
|
+
githubUser: "GitHub username or URL:",
|
|
642
|
+
twitterHandle: "Twitter/X handle:",
|
|
643
|
+
linkedinUrl: "LinkedIn URL:",
|
|
644
|
+
blogUrl: "Blog URL:",
|
|
645
|
+
contactEmail: "Contact email:",
|
|
646
|
+
// Design
|
|
647
|
+
designPrefs: "Design Preferences",
|
|
648
|
+
theme: "Theme:",
|
|
649
|
+
accentColor: "Accent color:",
|
|
650
|
+
customHex: "Custom hex",
|
|
651
|
+
customHexPrompt: "Custom accent color (hex):",
|
|
652
|
+
invalidHex: "Enter a valid hex color",
|
|
653
|
+
font: "Font:",
|
|
654
|
+
animationLevel: "Animation level:",
|
|
655
|
+
// Sections
|
|
656
|
+
sections: "Sections",
|
|
657
|
+
additionalSections: "Additional sections (Hero + Projects always included):",
|
|
658
|
+
// Engine
|
|
659
|
+
aiEngine: "AI Engine",
|
|
660
|
+
engineUsing: (name) => `Using ${name}`,
|
|
661
|
+
engineSelect: "AI engine to use:",
|
|
662
|
+
// Deploy
|
|
663
|
+
deployment: "Deployment",
|
|
664
|
+
deployTo: "Deploy to:",
|
|
665
|
+
projectNamePrompt: "Project name (used in URL):",
|
|
666
|
+
projectNameInvalid: "Lowercase letters, numbers, and hyphens only",
|
|
667
|
+
configComplete: "Configuration complete. Generating your site...",
|
|
668
|
+
// Draft
|
|
669
|
+
draftFound: "Found a saved draft from a previous session.",
|
|
670
|
+
draftLoadPrompt: "Load draft? (skip re-entering previous answers)",
|
|
671
|
+
draftSaved: "Draft saved. Your progress will be restored next time.",
|
|
672
|
+
draftCleared: "Draft cleared.",
|
|
673
|
+
// Roles
|
|
674
|
+
roleSolo: "Solo",
|
|
675
|
+
roleLead: "Lead",
|
|
676
|
+
roleContributor: "Contributor",
|
|
677
|
+
// Themes
|
|
678
|
+
themeDarkMinimal: "Dark Minimal",
|
|
679
|
+
themeLightClean: "Light Clean",
|
|
680
|
+
themeMonochrome: "Monochrome",
|
|
681
|
+
themeCustom: "Custom (AI decides)",
|
|
682
|
+
// Fonts
|
|
683
|
+
fontInter: "Inter",
|
|
684
|
+
fontJetBrains: "JetBrains Mono",
|
|
685
|
+
fontSystem: "System Default",
|
|
686
|
+
// Animations
|
|
687
|
+
animSubtle: "Subtle",
|
|
688
|
+
animModerate: "Moderate",
|
|
689
|
+
animNone: "None",
|
|
690
|
+
// Section names
|
|
691
|
+
secSkills: "Skills / Tech Stack",
|
|
692
|
+
secAbout: "About Me",
|
|
693
|
+
secTimeline: "Timeline / Changelog",
|
|
694
|
+
secBlog: "Blog",
|
|
695
|
+
secMetrics: "Metrics Dashboard",
|
|
696
|
+
secContact: "Contact",
|
|
697
|
+
// Deploy platforms
|
|
698
|
+
deployCloudflare: "Cloudflare Pages",
|
|
699
|
+
deployVercel: "Vercel",
|
|
700
|
+
deployLocal: "Local only (no deploy)",
|
|
701
|
+
// Colors
|
|
702
|
+
colorPurple: "Purple",
|
|
703
|
+
colorGreen: "Green",
|
|
704
|
+
colorOrange: "Orange",
|
|
705
|
+
colorBlue: "Blue",
|
|
706
|
+
colorRed: "Red",
|
|
707
|
+
colorPink: "Pink",
|
|
708
|
+
// Engines
|
|
709
|
+
engineClaude: "Claude Code",
|
|
710
|
+
engineCodex: "Codex",
|
|
711
|
+
engineV0: "v0 (Vercel)"
|
|
712
|
+
},
|
|
713
|
+
zh: {
|
|
714
|
+
selectProjects: "\u9009\u62E9\u8981\u5C55\u793A\u7684\u9879\u76EE (\u7A7A\u683C = \u9009\u62E9, \u56DE\u8F66 = \u786E\u8BA4):",
|
|
715
|
+
mergePrompt: "\u662F\u5426\u8981\u5408\u5E76\u76F8\u5173\u9879\u76EE\u4E3A\u4E00\u4E2A\u6761\u76EE? (\u5982 web + mobile)",
|
|
716
|
+
selectToMerge: "\u9009\u62E9\u8981\u5408\u5E76\u7684\u9879\u76EE (\u7A7A\u683C = \u9009\u62E9, \u56DE\u8F66 = \u786E\u8BA4):",
|
|
717
|
+
mergedNamePrompt: "\u5408\u5E76\u540E\u7684\u540D\u79F0:",
|
|
718
|
+
mergeNeedTwo: "\u81F3\u5C11\u9700\u8981\u9009\u62E9 2 \u4E2A\u9879\u76EE\u624D\u80FD\u5408\u5E76, \u8DF3\u8FC7.",
|
|
719
|
+
mergedResult: (names, target) => `\u5DF2\u5408\u5E76 ${names} -> "${target}"`,
|
|
720
|
+
mergeMore: "\u7EE7\u7EED\u5408\u5E76\u5176\u4ED6\u9879\u76EE?",
|
|
721
|
+
configuring: (name) => `\u914D\u7F6E: ${name}`,
|
|
722
|
+
descriptionFor: (name) => `${name} \u7684\u63CF\u8FF0 (\u56DE\u8F66\u4F7F\u7528\u81EA\u52A8\u751F\u6210):`,
|
|
723
|
+
descriptionPlaceholder: "\u4ECE README \u81EA\u52A8\u751F\u6210",
|
|
724
|
+
demoUrlFor: (name) => `${name} \u7684 Demo \u5730\u5740:`,
|
|
725
|
+
showSourceFor: (name) => `\u662F\u5426\u5C55\u793A ${name} \u7684\u6E90\u7801\u94FE\u63A5?`,
|
|
726
|
+
roleFor: (name) => `\u4F60\u5728 ${name} \u4E2D\u7684\u89D2\u8272:`,
|
|
727
|
+
metricsFor: (name) => `${name} \u7684\u5173\u952E\u6307\u6807 (\u5982 "1k \u7528\u6237, $5k MRR"):`,
|
|
728
|
+
optional: "\u53EF\u9009",
|
|
729
|
+
personalInfo: "\u4E2A\u4EBA\u4FE1\u606F",
|
|
730
|
+
fullName: "\u4F60\u7684\u5168\u540D:",
|
|
731
|
+
nameRequired: "\u59D3\u540D\u4E0D\u80FD\u4E3A\u7A7A",
|
|
732
|
+
tagline: "\u804C\u4E1A\u6807\u8BED (\u4E00\u53E5\u8BDD):",
|
|
733
|
+
taglinePlaceholder: "\u5168\u6808\u5DE5\u7A0B\u5E08, \u6301\u7EED\u4EA4\u4ED8.",
|
|
734
|
+
bio: "\u4E2A\u4EBA\u7B80\u4ECB:",
|
|
735
|
+
bioAuto: "\u6839\u636E\u9879\u76EE\u81EA\u52A8\u751F\u6210",
|
|
736
|
+
bioManual: "\u624B\u52A8\u586B\u5199",
|
|
737
|
+
bioPrompt: "\u4F60\u7684\u7B80\u4ECB (2-3\u53E5\u8BDD):",
|
|
738
|
+
photoUrl: "\u5934\u50CF\u94FE\u63A5 (\u53EF\u9009):",
|
|
739
|
+
githubUser: "GitHub \u7528\u6237\u540D\u6216\u94FE\u63A5:",
|
|
740
|
+
twitterHandle: "Twitter/X \u8D26\u53F7:",
|
|
741
|
+
linkedinUrl: "LinkedIn \u94FE\u63A5:",
|
|
742
|
+
blogUrl: "\u535A\u5BA2\u94FE\u63A5:",
|
|
743
|
+
contactEmail: "\u8054\u7CFB\u90AE\u7BB1:",
|
|
744
|
+
designPrefs: "\u8BBE\u8BA1\u504F\u597D",
|
|
745
|
+
theme: "\u4E3B\u9898:",
|
|
746
|
+
accentColor: "\u5F3A\u8C03\u8272:",
|
|
747
|
+
customHex: "\u81EA\u5B9A\u4E49\u8272\u503C",
|
|
748
|
+
customHexPrompt: "\u81EA\u5B9A\u4E49\u5F3A\u8C03\u8272 (hex):",
|
|
749
|
+
invalidHex: "\u8BF7\u8F93\u5165\u6709\u6548\u7684 hex \u989C\u8272\u503C",
|
|
750
|
+
font: "\u5B57\u4F53:",
|
|
751
|
+
animationLevel: "\u52A8\u753B\u7EA7\u522B:",
|
|
752
|
+
sections: "\u9875\u9762\u533A\u5757",
|
|
753
|
+
additionalSections: "\u9644\u52A0\u533A\u5757 (Hero + \u9879\u76EE\u5C55\u793A \u59CB\u7EC8\u5305\u542B):",
|
|
754
|
+
aiEngine: "AI \u5F15\u64CE",
|
|
755
|
+
engineUsing: (name) => `\u4F7F\u7528 ${name}`,
|
|
756
|
+
engineSelect: "\u9009\u62E9 AI \u5F15\u64CE:",
|
|
757
|
+
deployment: "\u90E8\u7F72",
|
|
758
|
+
deployTo: "\u90E8\u7F72\u5230:",
|
|
759
|
+
projectNamePrompt: "\u9879\u76EE\u540D\u79F0 (\u7528\u4E8E URL):",
|
|
760
|
+
projectNameInvalid: "\u4EC5\u9650\u5C0F\u5199\u5B57\u6BCD, \u6570\u5B57\u548C\u8FDE\u5B57\u7B26",
|
|
761
|
+
configComplete: "\u914D\u7F6E\u5B8C\u6210, \u6B63\u5728\u751F\u6210\u7F51\u7AD9...",
|
|
762
|
+
draftFound: "\u53D1\u73B0\u4E0A\u6B21\u672A\u5B8C\u6210\u7684\u914D\u7F6E\u8349\u7A3F.",
|
|
763
|
+
draftLoadPrompt: "\u662F\u5426\u52A0\u8F7D\u8349\u7A3F? (\u8DF3\u8FC7\u91CD\u590D\u586B\u5199)",
|
|
764
|
+
draftSaved: "\u8349\u7A3F\u5DF2\u4FDD\u5B58, \u4E0B\u6B21\u8FD0\u884C\u65F6\u53EF\u6062\u590D.",
|
|
765
|
+
draftCleared: "\u8349\u7A3F\u5DF2\u6E05\u9664.",
|
|
766
|
+
roleSolo: "\u72EC\u7ACB\u5B8C\u6210",
|
|
767
|
+
roleLead: "\u4E3B\u5BFC\u5F00\u53D1",
|
|
768
|
+
roleContributor: "\u53C2\u4E0E\u8D21\u732E",
|
|
769
|
+
themeDarkMinimal: "\u6697\u8272\u6781\u7B80",
|
|
770
|
+
themeLightClean: "\u660E\u4EAE\u7B80\u6D01",
|
|
771
|
+
themeMonochrome: "\u9ED1\u767D",
|
|
772
|
+
themeCustom: "\u81EA\u5B9A\u4E49 (AI \u51B3\u5B9A)",
|
|
773
|
+
fontInter: "Inter",
|
|
774
|
+
fontJetBrains: "JetBrains Mono",
|
|
775
|
+
fontSystem: "\u7CFB\u7EDF\u9ED8\u8BA4",
|
|
776
|
+
animSubtle: "\u8F7B\u5FAE",
|
|
777
|
+
animModerate: "\u9002\u4E2D",
|
|
778
|
+
animNone: "\u65E0",
|
|
779
|
+
secSkills: "\u6280\u672F\u6808",
|
|
780
|
+
secAbout: "\u5173\u4E8E\u6211",
|
|
781
|
+
secTimeline: "\u65F6\u95F4\u7EBF",
|
|
782
|
+
secBlog: "\u535A\u5BA2",
|
|
783
|
+
secMetrics: "\u6570\u636E\u9762\u677F",
|
|
784
|
+
secContact: "\u8054\u7CFB\u65B9\u5F0F",
|
|
785
|
+
deployCloudflare: "Cloudflare Pages",
|
|
786
|
+
deployVercel: "Vercel",
|
|
787
|
+
deployLocal: "\u4EC5\u672C\u5730 (\u4E0D\u90E8\u7F72)",
|
|
788
|
+
colorPurple: "\u7D2B\u8272",
|
|
789
|
+
colorGreen: "\u7EFF\u8272",
|
|
790
|
+
colorOrange: "\u6A59\u8272",
|
|
791
|
+
colorBlue: "\u84DD\u8272",
|
|
792
|
+
colorRed: "\u7EA2\u8272",
|
|
793
|
+
colorPink: "\u7C89\u8272",
|
|
794
|
+
engineClaude: "Claude Code",
|
|
795
|
+
engineCodex: "Codex",
|
|
796
|
+
engineV0: "v0 (Vercel)"
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
currentLocale = "en";
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
|
|
594
803
|
// src/interviewer/questions.ts
|
|
595
804
|
var THEME_OPTIONS, FONT_OPTIONS, ANIMATION_OPTIONS, SECTION_OPTIONS, ENGINE_OPTIONS, DEPLOY_OPTIONS, ROLE_OPTIONS, DEFAULT_ACCENT_COLORS;
|
|
596
805
|
var init_questions = __esm({
|
|
597
806
|
"src/interviewer/questions.ts"() {
|
|
598
807
|
"use strict";
|
|
599
808
|
init_esm_shims();
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
{ value: "
|
|
603
|
-
{ value: "
|
|
604
|
-
{ value: "
|
|
809
|
+
init_i18n();
|
|
810
|
+
THEME_OPTIONS = () => [
|
|
811
|
+
{ value: "dark-minimal", label: t().themeDarkMinimal },
|
|
812
|
+
{ value: "light-clean", label: t().themeLightClean },
|
|
813
|
+
{ value: "monochrome", label: t().themeMonochrome },
|
|
814
|
+
{ value: "custom", label: t().themeCustom }
|
|
605
815
|
];
|
|
606
|
-
FONT_OPTIONS = [
|
|
607
|
-
{ value: "Inter", label:
|
|
608
|
-
{ value: "JetBrains Mono", label:
|
|
609
|
-
{ value: "system", label:
|
|
816
|
+
FONT_OPTIONS = () => [
|
|
817
|
+
{ value: "Inter", label: t().fontInter },
|
|
818
|
+
{ value: "JetBrains Mono", label: t().fontJetBrains },
|
|
819
|
+
{ value: "system", label: t().fontSystem }
|
|
610
820
|
];
|
|
611
|
-
ANIMATION_OPTIONS = [
|
|
612
|
-
{ value: "subtle", label:
|
|
613
|
-
{ value: "moderate", label:
|
|
614
|
-
{ value: "none", label:
|
|
821
|
+
ANIMATION_OPTIONS = () => [
|
|
822
|
+
{ value: "subtle", label: t().animSubtle },
|
|
823
|
+
{ value: "moderate", label: t().animModerate },
|
|
824
|
+
{ value: "none", label: t().animNone }
|
|
615
825
|
];
|
|
616
|
-
SECTION_OPTIONS = [
|
|
617
|
-
{ value: "skills", label:
|
|
618
|
-
{ value: "about", label:
|
|
619
|
-
{ value: "timeline", label:
|
|
620
|
-
{ value: "blog", label:
|
|
621
|
-
{ value: "metrics", label:
|
|
622
|
-
{ value: "contact", label:
|
|
826
|
+
SECTION_OPTIONS = () => [
|
|
827
|
+
{ value: "skills", label: t().secSkills },
|
|
828
|
+
{ value: "about", label: t().secAbout },
|
|
829
|
+
{ value: "timeline", label: t().secTimeline },
|
|
830
|
+
{ value: "blog", label: t().secBlog },
|
|
831
|
+
{ value: "metrics", label: t().secMetrics },
|
|
832
|
+
{ value: "contact", label: t().secContact }
|
|
623
833
|
];
|
|
624
|
-
ENGINE_OPTIONS = [
|
|
625
|
-
{ value: "claude", label:
|
|
626
|
-
{ value: "codex", label:
|
|
627
|
-
{ value: "v0", label:
|
|
834
|
+
ENGINE_OPTIONS = () => [
|
|
835
|
+
{ value: "claude", label: t().engineClaude },
|
|
836
|
+
{ value: "codex", label: t().engineCodex },
|
|
837
|
+
{ value: "v0", label: t().engineV0 }
|
|
628
838
|
];
|
|
629
|
-
DEPLOY_OPTIONS = [
|
|
630
|
-
{ value: "cloudflare", label:
|
|
631
|
-
{ value: "vercel", label:
|
|
632
|
-
{ value: "local", label:
|
|
839
|
+
DEPLOY_OPTIONS = () => [
|
|
840
|
+
{ value: "cloudflare", label: t().deployCloudflare },
|
|
841
|
+
{ value: "vercel", label: t().deployVercel },
|
|
842
|
+
{ value: "local", label: t().deployLocal }
|
|
633
843
|
];
|
|
634
|
-
ROLE_OPTIONS = [
|
|
635
|
-
{ value: "solo", label:
|
|
636
|
-
{ value: "lead", label:
|
|
637
|
-
{ value: "contributor", label:
|
|
844
|
+
ROLE_OPTIONS = () => [
|
|
845
|
+
{ value: "solo", label: t().roleSolo },
|
|
846
|
+
{ value: "lead", label: t().roleLead },
|
|
847
|
+
{ value: "contributor", label: t().roleContributor }
|
|
638
848
|
];
|
|
639
|
-
DEFAULT_ACCENT_COLORS = [
|
|
640
|
-
{ value: "#7c3aed", label:
|
|
641
|
-
{ value: "#10b981", label:
|
|
642
|
-
{ value: "#f97316", label:
|
|
643
|
-
{ value: "#3b82f6", label:
|
|
644
|
-
{ value: "#ef4444", label:
|
|
645
|
-
{ value: "#ec4899", label:
|
|
849
|
+
DEFAULT_ACCENT_COLORS = () => [
|
|
850
|
+
{ value: "#7c3aed", label: t().colorPurple },
|
|
851
|
+
{ value: "#10b981", label: t().colorGreen },
|
|
852
|
+
{ value: "#f97316", label: t().colorOrange },
|
|
853
|
+
{ value: "#3b82f6", label: t().colorBlue },
|
|
854
|
+
{ value: "#ef4444", label: t().colorRed },
|
|
855
|
+
{ value: "#ec4899", label: t().colorPink }
|
|
646
856
|
];
|
|
647
857
|
}
|
|
648
858
|
});
|
|
@@ -657,7 +867,7 @@ function handleCancel(value) {
|
|
|
657
867
|
}
|
|
658
868
|
async function runMergeStep(projects) {
|
|
659
869
|
const shouldMerge = await p.confirm({
|
|
660
|
-
message:
|
|
870
|
+
message: t().mergePrompt,
|
|
661
871
|
initialValue: false
|
|
662
872
|
});
|
|
663
873
|
handleCancel(shouldMerge);
|
|
@@ -665,7 +875,7 @@ async function runMergeStep(projects) {
|
|
|
665
875
|
let remaining = [...projects];
|
|
666
876
|
while (remaining.length >= 2) {
|
|
667
877
|
const mergeIds = await p.multiselect({
|
|
668
|
-
message:
|
|
878
|
+
message: t().selectToMerge,
|
|
669
879
|
options: remaining.map((proj) => ({
|
|
670
880
|
value: proj.id,
|
|
671
881
|
label: `${proj.name} (${proj.localPath})`,
|
|
@@ -675,7 +885,7 @@ async function runMergeStep(projects) {
|
|
|
675
885
|
});
|
|
676
886
|
handleCancel(mergeIds);
|
|
677
887
|
if (mergeIds.length < 2) {
|
|
678
|
-
logger.warn(
|
|
888
|
+
logger.warn(t().mergeNeedTwo);
|
|
679
889
|
break;
|
|
680
890
|
}
|
|
681
891
|
const toMerge = mergeIds.map(
|
|
@@ -683,7 +893,7 @@ async function runMergeStep(projects) {
|
|
|
683
893
|
);
|
|
684
894
|
const defaultName = toMerge.map((p6) => p6.name).join(" + ");
|
|
685
895
|
const mergedName = await p.text({
|
|
686
|
-
message:
|
|
896
|
+
message: t().mergedNamePrompt,
|
|
687
897
|
placeholder: defaultName,
|
|
688
898
|
defaultValue: defaultName
|
|
689
899
|
});
|
|
@@ -692,11 +902,11 @@ async function runMergeStep(projects) {
|
|
|
692
902
|
remaining = remaining.filter((p6) => !mergeIds.includes(p6.id));
|
|
693
903
|
remaining.push(merged);
|
|
694
904
|
logger.info(
|
|
695
|
-
|
|
905
|
+
t().mergedResult(toMerge.map((p6) => p6.name).join(", "), mergedName)
|
|
696
906
|
);
|
|
697
907
|
if (remaining.length < 2) break;
|
|
698
908
|
const more = await p.confirm({
|
|
699
|
-
message:
|
|
909
|
+
message: t().mergeMore,
|
|
700
910
|
initialValue: false
|
|
701
911
|
});
|
|
702
912
|
handleCancel(more);
|
|
@@ -770,7 +980,7 @@ async function runInterview(scannedProjects, availableEngines) {
|
|
|
770
980
|
};
|
|
771
981
|
});
|
|
772
982
|
const selectedIds = await p.multiselect({
|
|
773
|
-
message:
|
|
983
|
+
message: t().selectProjects,
|
|
774
984
|
options: projectOptions,
|
|
775
985
|
required: true
|
|
776
986
|
});
|
|
@@ -785,32 +995,32 @@ async function runInterview(scannedProjects, availableEngines) {
|
|
|
785
995
|
for (const meta of selectedMetas) {
|
|
786
996
|
const displayName = meta.children ? `${meta.name} (${meta.children.length} sub-projects)` : meta.name;
|
|
787
997
|
logger.plain(`
|
|
788
|
-
|
|
998
|
+
${t().configuring(displayName)}`);
|
|
789
999
|
const overrideDesc = await p.text({
|
|
790
|
-
message:
|
|
791
|
-
placeholder: meta.description?.slice(0, 80) ||
|
|
1000
|
+
message: t().descriptionFor(displayName),
|
|
1001
|
+
placeholder: meta.description?.slice(0, 80) || t().descriptionPlaceholder,
|
|
792
1002
|
defaultValue: ""
|
|
793
1003
|
});
|
|
794
1004
|
handleCancel(overrideDesc);
|
|
795
1005
|
const demoUrl = await p.text({
|
|
796
|
-
message:
|
|
1006
|
+
message: t().demoUrlFor(displayName),
|
|
797
1007
|
placeholder: meta.demoUrl || "none",
|
|
798
1008
|
defaultValue: meta.demoUrl || ""
|
|
799
1009
|
});
|
|
800
1010
|
handleCancel(demoUrl);
|
|
801
1011
|
const showSource = await p.confirm({
|
|
802
|
-
message:
|
|
1012
|
+
message: t().showSourceFor(displayName),
|
|
803
1013
|
initialValue: !!meta.remoteUrl
|
|
804
1014
|
});
|
|
805
1015
|
handleCancel(showSource);
|
|
806
1016
|
const role = await p.select({
|
|
807
|
-
message:
|
|
808
|
-
options: ROLE_OPTIONS
|
|
1017
|
+
message: t().roleFor(displayName),
|
|
1018
|
+
options: ROLE_OPTIONS()
|
|
809
1019
|
});
|
|
810
1020
|
handleCancel(role);
|
|
811
1021
|
const metricsInput = await p.text({
|
|
812
|
-
message:
|
|
813
|
-
placeholder:
|
|
1022
|
+
message: t().metricsFor(displayName),
|
|
1023
|
+
placeholder: t().optional,
|
|
814
1024
|
defaultValue: ""
|
|
815
1025
|
});
|
|
816
1026
|
handleCancel(metricsInput);
|
|
@@ -828,60 +1038,60 @@ async function runInterview(scannedProjects, availableEngines) {
|
|
|
828
1038
|
metrics
|
|
829
1039
|
});
|
|
830
1040
|
}
|
|
831
|
-
logger.header(
|
|
1041
|
+
logger.header(t().personalInfo);
|
|
832
1042
|
const name = await p.text({
|
|
833
|
-
message:
|
|
834
|
-
validate: (v) => v.length === 0 ?
|
|
1043
|
+
message: t().fullName,
|
|
1044
|
+
validate: (v) => v.length === 0 ? t().nameRequired : void 0
|
|
835
1045
|
});
|
|
836
1046
|
handleCancel(name);
|
|
837
1047
|
const tagline = await p.text({
|
|
838
|
-
message:
|
|
839
|
-
placeholder:
|
|
1048
|
+
message: t().tagline,
|
|
1049
|
+
placeholder: t().taglinePlaceholder
|
|
840
1050
|
});
|
|
841
1051
|
handleCancel(tagline);
|
|
842
1052
|
const bioChoice = await p.select({
|
|
843
|
-
message:
|
|
1053
|
+
message: t().bio,
|
|
844
1054
|
options: [
|
|
845
|
-
{ value: "auto", label:
|
|
846
|
-
{ value: "manual", label:
|
|
1055
|
+
{ value: "auto", label: t().bioAuto },
|
|
1056
|
+
{ value: "manual", label: t().bioManual }
|
|
847
1057
|
]
|
|
848
1058
|
});
|
|
849
1059
|
handleCancel(bioChoice);
|
|
850
1060
|
let bio = "auto";
|
|
851
1061
|
if (bioChoice === "manual") {
|
|
852
1062
|
bio = await p.text({
|
|
853
|
-
message:
|
|
1063
|
+
message: t().bioPrompt
|
|
854
1064
|
});
|
|
855
1065
|
handleCancel(bio);
|
|
856
1066
|
}
|
|
857
1067
|
const photoUrl = await p.text({
|
|
858
|
-
message:
|
|
1068
|
+
message: t().photoUrl,
|
|
859
1069
|
placeholder: "https://...",
|
|
860
1070
|
defaultValue: ""
|
|
861
1071
|
});
|
|
862
1072
|
handleCancel(photoUrl);
|
|
863
1073
|
const github = await p.text({
|
|
864
|
-
message:
|
|
1074
|
+
message: t().githubUser,
|
|
865
1075
|
defaultValue: ""
|
|
866
1076
|
});
|
|
867
1077
|
handleCancel(github);
|
|
868
1078
|
const twitter = await p.text({
|
|
869
|
-
message:
|
|
1079
|
+
message: t().twitterHandle,
|
|
870
1080
|
defaultValue: ""
|
|
871
1081
|
});
|
|
872
1082
|
handleCancel(twitter);
|
|
873
1083
|
const linkedin = await p.text({
|
|
874
|
-
message:
|
|
1084
|
+
message: t().linkedinUrl,
|
|
875
1085
|
defaultValue: ""
|
|
876
1086
|
});
|
|
877
1087
|
handleCancel(linkedin);
|
|
878
1088
|
const blogUrl = await p.text({
|
|
879
|
-
message:
|
|
1089
|
+
message: t().blogUrl,
|
|
880
1090
|
defaultValue: ""
|
|
881
1091
|
});
|
|
882
1092
|
handleCancel(blogUrl);
|
|
883
1093
|
const email = await p.text({
|
|
884
|
-
message:
|
|
1094
|
+
message: t().contactEmail,
|
|
885
1095
|
defaultValue: ""
|
|
886
1096
|
});
|
|
887
1097
|
handleCancel(email);
|
|
@@ -898,37 +1108,37 @@ async function runInterview(scannedProjects, availableEngines) {
|
|
|
898
1108
|
email: email || void 0
|
|
899
1109
|
}
|
|
900
1110
|
};
|
|
901
|
-
logger.header(
|
|
1111
|
+
logger.header(t().designPrefs);
|
|
902
1112
|
const theme = await p.select({
|
|
903
|
-
message:
|
|
904
|
-
options: THEME_OPTIONS
|
|
1113
|
+
message: t().theme,
|
|
1114
|
+
options: THEME_OPTIONS()
|
|
905
1115
|
});
|
|
906
1116
|
handleCancel(theme);
|
|
907
1117
|
const accentColor = await p.select({
|
|
908
|
-
message:
|
|
1118
|
+
message: t().accentColor,
|
|
909
1119
|
options: [
|
|
910
|
-
...DEFAULT_ACCENT_COLORS,
|
|
911
|
-
{ value: "custom", label:
|
|
1120
|
+
...DEFAULT_ACCENT_COLORS(),
|
|
1121
|
+
{ value: "custom", label: t().customHex }
|
|
912
1122
|
]
|
|
913
1123
|
});
|
|
914
1124
|
handleCancel(accentColor);
|
|
915
1125
|
let finalAccent = accentColor;
|
|
916
1126
|
if (accentColor === "custom") {
|
|
917
1127
|
finalAccent = await p.text({
|
|
918
|
-
message:
|
|
1128
|
+
message: t().customHexPrompt,
|
|
919
1129
|
placeholder: "#7c3aed",
|
|
920
|
-
validate: (v) => /^#[0-9a-fA-F]{6}$/.test(v) ? void 0 :
|
|
1130
|
+
validate: (v) => /^#[0-9a-fA-F]{6}$/.test(v) ? void 0 : t().invalidHex
|
|
921
1131
|
});
|
|
922
1132
|
handleCancel(finalAccent);
|
|
923
1133
|
}
|
|
924
1134
|
const font = await p.select({
|
|
925
|
-
message:
|
|
926
|
-
options: FONT_OPTIONS
|
|
1135
|
+
message: t().font,
|
|
1136
|
+
options: FONT_OPTIONS()
|
|
927
1137
|
});
|
|
928
1138
|
handleCancel(font);
|
|
929
1139
|
const animationLevel = await p.select({
|
|
930
|
-
message:
|
|
931
|
-
options: ANIMATION_OPTIONS
|
|
1140
|
+
message: t().animationLevel,
|
|
1141
|
+
options: ANIMATION_OPTIONS()
|
|
932
1142
|
});
|
|
933
1143
|
handleCancel(animationLevel);
|
|
934
1144
|
const style = {
|
|
@@ -937,10 +1147,10 @@ async function runInterview(scannedProjects, availableEngines) {
|
|
|
937
1147
|
font,
|
|
938
1148
|
animationLevel
|
|
939
1149
|
};
|
|
940
|
-
logger.header(
|
|
1150
|
+
logger.header(t().sections);
|
|
941
1151
|
const additionalSections = await p.multiselect({
|
|
942
|
-
message:
|
|
943
|
-
options: SECTION_OPTIONS,
|
|
1152
|
+
message: t().additionalSections,
|
|
1153
|
+
options: SECTION_OPTIONS(),
|
|
944
1154
|
required: false
|
|
945
1155
|
});
|
|
946
1156
|
handleCancel(additionalSections);
|
|
@@ -949,37 +1159,37 @@ async function runInterview(scannedProjects, availableEngines) {
|
|
|
949
1159
|
"projects",
|
|
950
1160
|
...additionalSections
|
|
951
1161
|
];
|
|
952
|
-
logger.header(
|
|
1162
|
+
logger.header(t().aiEngine);
|
|
953
1163
|
let engine;
|
|
954
1164
|
if (availableEngines.length === 1) {
|
|
955
1165
|
engine = availableEngines[0];
|
|
956
|
-
logger.info(
|
|
1166
|
+
logger.info(t().engineUsing(engine));
|
|
957
1167
|
} else {
|
|
958
|
-
const filteredEngineOptions = ENGINE_OPTIONS.filter(
|
|
1168
|
+
const filteredEngineOptions = ENGINE_OPTIONS().filter(
|
|
959
1169
|
(o) => availableEngines.includes(o.value)
|
|
960
1170
|
);
|
|
961
1171
|
engine = await p.select({
|
|
962
|
-
message:
|
|
1172
|
+
message: t().engineSelect,
|
|
963
1173
|
options: filteredEngineOptions
|
|
964
1174
|
});
|
|
965
1175
|
handleCancel(engine);
|
|
966
1176
|
}
|
|
967
|
-
logger.header(
|
|
1177
|
+
logger.header(t().deployment);
|
|
968
1178
|
const deployPlatform = await p.select({
|
|
969
|
-
message:
|
|
970
|
-
options: DEPLOY_OPTIONS
|
|
1179
|
+
message: t().deployTo,
|
|
1180
|
+
options: DEPLOY_OPTIONS()
|
|
971
1181
|
});
|
|
972
1182
|
handleCancel(deployPlatform);
|
|
973
1183
|
let projectName = "";
|
|
974
1184
|
if (deployPlatform !== "local") {
|
|
975
1185
|
projectName = await p.text({
|
|
976
|
-
message:
|
|
1186
|
+
message: t().projectNamePrompt,
|
|
977
1187
|
placeholder: "my-shipfolio",
|
|
978
|
-
validate: (v) => /^[a-z0-9-]+$/.test(v) ? void 0 :
|
|
1188
|
+
validate: (v) => /^[a-z0-9-]+$/.test(v) ? void 0 : t().projectNameInvalid
|
|
979
1189
|
});
|
|
980
1190
|
handleCancel(projectName);
|
|
981
1191
|
}
|
|
982
|
-
p.outro(
|
|
1192
|
+
p.outro(t().configComplete);
|
|
983
1193
|
return {
|
|
984
1194
|
projects: projectEntries,
|
|
985
1195
|
owner,
|
|
@@ -998,6 +1208,7 @@ var init_interviewer = __esm({
|
|
|
998
1208
|
init_esm_shims();
|
|
999
1209
|
init_questions();
|
|
1000
1210
|
init_logger();
|
|
1211
|
+
init_i18n();
|
|
1001
1212
|
}
|
|
1002
1213
|
});
|
|
1003
1214
|
|
|
@@ -1026,42 +1237,14 @@ var init_builder = __esm({
|
|
|
1026
1237
|
});
|
|
1027
1238
|
|
|
1028
1239
|
// src/orchestrator/prompt-builder.ts
|
|
1029
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
1030
|
-
import { join as join2, dirname } from "path";
|
|
1031
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1032
|
-
function getPromptsDir() {
|
|
1033
|
-
const currentDir = dirname(fileURLToPath2(import.meta.url));
|
|
1034
|
-
const candidates = [
|
|
1035
|
-
join2(currentDir, "../../prompts"),
|
|
1036
|
-
join2(currentDir, "../prompts"),
|
|
1037
|
-
join2(currentDir, "../../../prompts")
|
|
1038
|
-
];
|
|
1039
|
-
return candidates[0];
|
|
1040
|
-
}
|
|
1041
|
-
async function loadTemplate(filename) {
|
|
1042
|
-
const promptsDir = getPromptsDir();
|
|
1043
|
-
const candidates = [
|
|
1044
|
-
join2(promptsDir, filename),
|
|
1045
|
-
join2(dirname(fileURLToPath2(import.meta.url)), "../../prompts", filename),
|
|
1046
|
-
join2(dirname(fileURLToPath2(import.meta.url)), "../../../prompts", filename)
|
|
1047
|
-
];
|
|
1048
|
-
for (const candidate of candidates) {
|
|
1049
|
-
try {
|
|
1050
|
-
return await readFile2(candidate, "utf-8");
|
|
1051
|
-
} catch {
|
|
1052
|
-
continue;
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
throw new Error(`Prompt template not found: ${filename}`);
|
|
1056
|
-
}
|
|
1057
1240
|
async function buildFreshPrompt(spec) {
|
|
1058
|
-
let template =
|
|
1241
|
+
let template = FRESH_BUILD_TEMPLATE;
|
|
1059
1242
|
const sectionsText = spec.sections.map((s) => `- ${s}`).join("\n");
|
|
1060
1243
|
template = template.replace("{{SPEC_JSON}}", JSON.stringify(spec, null, 2)).replace("{{THEME}}", spec.style.theme).replace("{{ACCENT_COLOR}}", spec.style.accentColor).replace("{{ANIMATION_LEVEL}}", spec.style.animationLevel).replace("{{FONT}}", spec.style.font).replace("{{SECTIONS_LIST}}", sectionsText);
|
|
1061
1244
|
return template;
|
|
1062
1245
|
}
|
|
1063
1246
|
async function buildUpdatePrompt(existingConfig, diff) {
|
|
1064
|
-
let template =
|
|
1247
|
+
let template = UPDATE_TEMPLATE;
|
|
1065
1248
|
const newProjectsText = diff.newProjects.length > 0 ? diff.newProjects.map(
|
|
1066
1249
|
(p6) => `- ${p6.name}: ${p6.description}
|
|
1067
1250
|
Tech: ${p6.techStack.join(", ")}
|
|
@@ -1079,10 +1262,167 @@ async function buildUpdatePrompt(existingConfig, diff) {
|
|
|
1079
1262
|
).replace("{{NEW_PROJECTS}}", newProjectsText).replace("{{UPDATED_PROJECTS}}", updatedProjectsText).replace("{{REMOVED_PROJECTS}}", removedProjectsText).replace("{{PERSONAL_INFO_DIFF}}", "No changes");
|
|
1080
1263
|
return template;
|
|
1081
1264
|
}
|
|
1265
|
+
var FRESH_BUILD_TEMPLATE, UPDATE_TEMPLATE;
|
|
1082
1266
|
var init_prompt_builder = __esm({
|
|
1083
1267
|
"src/orchestrator/prompt-builder.ts"() {
|
|
1084
1268
|
"use strict";
|
|
1085
1269
|
init_esm_shims();
|
|
1270
|
+
FRESH_BUILD_TEMPLATE = `# Task
|
|
1271
|
+
|
|
1272
|
+
Generate a complete, production-ready personal portfolio website.
|
|
1273
|
+
All data and design preferences are provided in the spec below.
|
|
1274
|
+
Output a fully working Next.js project that builds and deploys as a static site.
|
|
1275
|
+
|
|
1276
|
+
# Spec
|
|
1277
|
+
|
|
1278
|
+
{{SPEC_JSON}}
|
|
1279
|
+
|
|
1280
|
+
# Technical Requirements
|
|
1281
|
+
|
|
1282
|
+
- Next.js 15 with App Router and TypeScript
|
|
1283
|
+
- Tailwind CSS v4 for styling
|
|
1284
|
+
- shadcn/ui components (initialize with \`npx shadcn@latest init --yes\`)
|
|
1285
|
+
- Static export: set \`output: 'export'\` in next.config.ts
|
|
1286
|
+
- Build command: \`npm run build\` producing \`out/\` directory
|
|
1287
|
+
- Zero external API calls at runtime -- all data is embedded in source
|
|
1288
|
+
- All project data in \`src/data/projects.ts\` as typed constants
|
|
1289
|
+
- Include \`@media print\` stylesheet in \`src/app/globals.css\` for PDF export
|
|
1290
|
+
- Responsive: mobile-first with Tailwind breakpoints (sm, md, lg, xl)
|
|
1291
|
+
- Lighthouse performance score 95+
|
|
1292
|
+
- Semantic HTML with ARIA labels and keyboard navigation
|
|
1293
|
+
- No emoji anywhere in text, UI, code, or comments
|
|
1294
|
+
- Use lucide-react for icons
|
|
1295
|
+
|
|
1296
|
+
# Design Direction
|
|
1297
|
+
|
|
1298
|
+
- Theme: {{THEME}}
|
|
1299
|
+
- Accent color: {{ACCENT_COLOR}}
|
|
1300
|
+
- Animation level: {{ANIMATION_LEVEL}}
|
|
1301
|
+
- Font: {{FONT}}
|
|
1302
|
+
|
|
1303
|
+
Design guidelines:
|
|
1304
|
+
- Typography-driven layout with large, bold headings
|
|
1305
|
+
- Generous whitespace between sections
|
|
1306
|
+
- Single accent color for interactive elements and highlights only
|
|
1307
|
+
- Project cards should feel substantial but clean
|
|
1308
|
+
- The site must look hand-crafted, not template-generated
|
|
1309
|
+
- Dark themes: use zinc/slate backgrounds, not pure black
|
|
1310
|
+
- Light themes: use warm whites and subtle grays
|
|
1311
|
+
|
|
1312
|
+
# Content Generation
|
|
1313
|
+
|
|
1314
|
+
For each project in the spec:
|
|
1315
|
+
- Write a 2-3 sentence narrative description based on the README content and tech stack
|
|
1316
|
+
- Focus on what it does and why it matters
|
|
1317
|
+
- If user provided an override description, use that instead
|
|
1318
|
+
- Maintain consistent voice across all descriptions
|
|
1319
|
+
|
|
1320
|
+
For the bio (if set to "auto"):
|
|
1321
|
+
- Generate a professional, authentic bio based on the project portfolio
|
|
1322
|
+
- Emphasize shipping velocity and breadth
|
|
1323
|
+
- Tone: confident, direct, no buzzwords or fluff
|
|
1324
|
+
|
|
1325
|
+
# Sections to Include
|
|
1326
|
+
|
|
1327
|
+
{{SECTIONS_LIST}}
|
|
1328
|
+
|
|
1329
|
+
Hero and Projects are always included. Additional sections as specified.
|
|
1330
|
+
|
|
1331
|
+
# Print / PDF Styles
|
|
1332
|
+
|
|
1333
|
+
In globals.css, add @media print rules:
|
|
1334
|
+
- Single-column layout
|
|
1335
|
+
- Hide navigation, footer, animations
|
|
1336
|
+
- Preserve background colors (user will print with backgrounds enabled)
|
|
1337
|
+
- Show link URLs inline: \`a[href]::after { content: " (" attr(href) ")"; }\`
|
|
1338
|
+
- Avoid page breaks inside project cards
|
|
1339
|
+
- A4-friendly margins and font sizes
|
|
1340
|
+
|
|
1341
|
+
# File Structure to Generate
|
|
1342
|
+
|
|
1343
|
+
\`\`\`
|
|
1344
|
+
next.config.ts
|
|
1345
|
+
package.json
|
|
1346
|
+
tailwind.config.ts
|
|
1347
|
+
tsconfig.json
|
|
1348
|
+
postcss.config.mjs
|
|
1349
|
+
components.json
|
|
1350
|
+
src/app/layout.tsx
|
|
1351
|
+
src/app/page.tsx
|
|
1352
|
+
src/app/globals.css
|
|
1353
|
+
src/components/ui/ (shadcn components as needed)
|
|
1354
|
+
src/components/hero.tsx
|
|
1355
|
+
src/components/project-card.tsx
|
|
1356
|
+
src/components/project-grid.tsx
|
|
1357
|
+
src/components/skills.tsx
|
|
1358
|
+
src/components/about.tsx
|
|
1359
|
+
src/components/timeline.tsx
|
|
1360
|
+
src/components/contact.tsx
|
|
1361
|
+
src/components/navigation.tsx
|
|
1362
|
+
src/components/footer.tsx
|
|
1363
|
+
src/data/projects.ts
|
|
1364
|
+
src/data/owner.ts
|
|
1365
|
+
src/lib/utils.ts
|
|
1366
|
+
public/favicon.svg
|
|
1367
|
+
shipfolio.config.json
|
|
1368
|
+
\`\`\`
|
|
1369
|
+
|
|
1370
|
+
# Important
|
|
1371
|
+
|
|
1372
|
+
- Generate ALL files needed for the project to build successfully
|
|
1373
|
+
- Include all shadcn/ui component files that are referenced
|
|
1374
|
+
- The \`package.json\` must include all dependencies
|
|
1375
|
+
- \`npm install && npm run build\` must succeed without errors
|
|
1376
|
+
- Do not use next/image (incompatible with static export) -- use standard <img> tags
|
|
1377
|
+
- Do not use features that require a server (API routes, middleware, ISR)
|
|
1378
|
+
`;
|
|
1379
|
+
UPDATE_TEMPLATE = `# Task
|
|
1380
|
+
|
|
1381
|
+
Update an existing portfolio website previously generated by shipfolio.
|
|
1382
|
+
You must preserve the existing design system, layout structure, component
|
|
1383
|
+
architecture, and any custom modifications the user has made.
|
|
1384
|
+
|
|
1385
|
+
# Existing Site Configuration
|
|
1386
|
+
|
|
1387
|
+
{{EXISTING_CONFIG_JSON}}
|
|
1388
|
+
|
|
1389
|
+
# Changes to Apply
|
|
1390
|
+
|
|
1391
|
+
## New Projects to Add
|
|
1392
|
+
{{NEW_PROJECTS}}
|
|
1393
|
+
|
|
1394
|
+
## Projects to Update (new commits since last scan)
|
|
1395
|
+
{{UPDATED_PROJECTS}}
|
|
1396
|
+
|
|
1397
|
+
## Projects to Remove
|
|
1398
|
+
{{REMOVED_PROJECTS}}
|
|
1399
|
+
|
|
1400
|
+
## Updated Personal Info
|
|
1401
|
+
{{PERSONAL_INFO_DIFF}}
|
|
1402
|
+
|
|
1403
|
+
# Rules
|
|
1404
|
+
|
|
1405
|
+
1. Do NOT change the overall layout, color scheme, or design system
|
|
1406
|
+
2. Do NOT reorganize existing components or rename files
|
|
1407
|
+
3. Only modify files that need changes for the specified updates
|
|
1408
|
+
4. For new projects: follow the exact same card format and component pattern
|
|
1409
|
+
as existing project cards
|
|
1410
|
+
5. Preserve all custom CSS, custom components, and manual edits
|
|
1411
|
+
6. Update src/data/projects.ts with new/changed/removed project data
|
|
1412
|
+
7. Update src/data/owner.ts if personal info changed
|
|
1413
|
+
8. Update shipfolio.config.json with new timestamps and project list
|
|
1414
|
+
9. If a new section type is needed, create it following the existing
|
|
1415
|
+
component patterns and design tokens in the codebase
|
|
1416
|
+
10. No emoji anywhere
|
|
1417
|
+
|
|
1418
|
+
# Technical Notes
|
|
1419
|
+
|
|
1420
|
+
- The site uses Next.js 15 + Tailwind CSS + shadcn/ui
|
|
1421
|
+
- Static export via \`output: 'export'\` in next.config.ts
|
|
1422
|
+
- Do not break the build -- \`npm run build\` must succeed
|
|
1423
|
+
- Do not add new dependencies unless absolutely necessary
|
|
1424
|
+
- Keep the existing @media print styles working
|
|
1425
|
+
`;
|
|
1086
1426
|
}
|
|
1087
1427
|
});
|
|
1088
1428
|
|
|
@@ -1199,7 +1539,7 @@ var init_codex = __esm({
|
|
|
1199
1539
|
// src/orchestrator/engines/v0.ts
|
|
1200
1540
|
import OpenAI from "openai";
|
|
1201
1541
|
import { writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
1202
|
-
import { join as
|
|
1542
|
+
import { join as join2, dirname } from "path";
|
|
1203
1543
|
import ora4 from "ora";
|
|
1204
1544
|
async function generateWithV0(prompt, outputDir, apiKey) {
|
|
1205
1545
|
const spinner = ora4("Generating site with v0...").start();
|
|
@@ -1232,14 +1572,14 @@ async function generateWithV0(prompt, outputDir, apiKey) {
|
|
|
1232
1572
|
}
|
|
1233
1573
|
spinner.text = `Writing ${files.length} files...`;
|
|
1234
1574
|
for (const file of files) {
|
|
1235
|
-
const filePath =
|
|
1236
|
-
await mkdir2(
|
|
1575
|
+
const filePath = join2(outputDir, file.filename);
|
|
1576
|
+
await mkdir2(dirname(filePath), { recursive: true });
|
|
1237
1577
|
await writeFile2(filePath, file.content, "utf-8");
|
|
1238
1578
|
}
|
|
1239
1579
|
const hasPkgJson = files.some((f) => f.filename === "package.json");
|
|
1240
1580
|
if (!hasPkgJson) {
|
|
1241
1581
|
await writeFile2(
|
|
1242
|
-
|
|
1582
|
+
join2(outputDir, "package.json"),
|
|
1243
1583
|
JSON.stringify(
|
|
1244
1584
|
{
|
|
1245
1585
|
name: "shipfolio-site",
|
|
@@ -1610,15 +1950,15 @@ var init_github = __esm({
|
|
|
1610
1950
|
});
|
|
1611
1951
|
|
|
1612
1952
|
// src/deployer/index.ts
|
|
1613
|
-
import { join as
|
|
1953
|
+
import { join as join3 } from "path";
|
|
1614
1954
|
async function deploy(siteDir, platform, projectName) {
|
|
1615
1955
|
if (platform === "local") {
|
|
1616
|
-
logger.info(`Site built at ${
|
|
1956
|
+
logger.info(`Site built at ${join3(siteDir, "out/")}`);
|
|
1617
1957
|
logger.info("Run `npx serve ./out` in the site directory to preview.");
|
|
1618
1958
|
return null;
|
|
1619
1959
|
}
|
|
1620
1960
|
await ensureAuth(platform);
|
|
1621
|
-
const distDir =
|
|
1961
|
+
const distDir = join3(siteDir, "out");
|
|
1622
1962
|
let url;
|
|
1623
1963
|
if (platform === "cloudflare") {
|
|
1624
1964
|
url = await deployToCloudflare(distDir, projectName);
|
|
@@ -1642,11 +1982,11 @@ var init_deployer = __esm({
|
|
|
1642
1982
|
|
|
1643
1983
|
// src/pdf/index.ts
|
|
1644
1984
|
import { createServer } from "http";
|
|
1645
|
-
import { join as
|
|
1985
|
+
import { join as join4 } from "path";
|
|
1646
1986
|
import ora8 from "ora";
|
|
1647
1987
|
async function exportPdf(siteDir, outputPath) {
|
|
1648
|
-
const distDir =
|
|
1649
|
-
const pdfPath = outputPath ||
|
|
1988
|
+
const distDir = join4(siteDir, "out");
|
|
1989
|
+
const pdfPath = outputPath || join4(distDir, "shipfolio.pdf");
|
|
1650
1990
|
const spinner = ora8("Exporting PDF...").start();
|
|
1651
1991
|
const server = await startStaticServer(distDir);
|
|
1652
1992
|
const port = server.address()?.port || 3456;
|
|
@@ -1702,7 +2042,7 @@ function startStaticServer(dir) {
|
|
|
1702
2042
|
});
|
|
1703
2043
|
}
|
|
1704
2044
|
async function startPreviewServer(siteDir, port = 3e3) {
|
|
1705
|
-
const distDir =
|
|
2045
|
+
const distDir = join4(siteDir, "out");
|
|
1706
2046
|
const handler = await import("serve-handler");
|
|
1707
2047
|
const server = createServer((req, res) => {
|
|
1708
2048
|
return handler.default(req, res, {
|
|
@@ -1726,6 +2066,49 @@ var init_pdf = __esm({
|
|
|
1726
2066
|
}
|
|
1727
2067
|
});
|
|
1728
2068
|
|
|
2069
|
+
// src/utils/draft.ts
|
|
2070
|
+
import { readFile as readFile2, writeFile as writeFile3, mkdir as mkdir3, unlink } from "fs/promises";
|
|
2071
|
+
import { join as join5 } from "path";
|
|
2072
|
+
import { homedir } from "os";
|
|
2073
|
+
async function saveDraft(interviewResult) {
|
|
2074
|
+
try {
|
|
2075
|
+
await mkdir3(DRAFT_DIR, { recursive: true });
|
|
2076
|
+
const draft = {
|
|
2077
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2078
|
+
interviewResult
|
|
2079
|
+
};
|
|
2080
|
+
await writeFile3(DRAFT_FILE, JSON.stringify(draft, null, 2), "utf-8");
|
|
2081
|
+
logger.info(t().draftSaved);
|
|
2082
|
+
} catch {
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
async function loadDraft() {
|
|
2086
|
+
try {
|
|
2087
|
+
const content = await readFile2(DRAFT_FILE, "utf-8");
|
|
2088
|
+
return JSON.parse(content);
|
|
2089
|
+
} catch {
|
|
2090
|
+
return null;
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
async function clearDraft() {
|
|
2094
|
+
try {
|
|
2095
|
+
await unlink(DRAFT_FILE);
|
|
2096
|
+
logger.info(t().draftCleared);
|
|
2097
|
+
} catch {
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
var DRAFT_DIR, DRAFT_FILE;
|
|
2101
|
+
var init_draft = __esm({
|
|
2102
|
+
"src/utils/draft.ts"() {
|
|
2103
|
+
"use strict";
|
|
2104
|
+
init_esm_shims();
|
|
2105
|
+
init_logger();
|
|
2106
|
+
init_i18n();
|
|
2107
|
+
DRAFT_DIR = join5(homedir(), ".shipfolio");
|
|
2108
|
+
DRAFT_FILE = join5(DRAFT_DIR, "draft.json");
|
|
2109
|
+
}
|
|
2110
|
+
});
|
|
2111
|
+
|
|
1729
2112
|
// src/commands/init.ts
|
|
1730
2113
|
var init_exports = {};
|
|
1731
2114
|
__export(init_exports, {
|
|
@@ -1735,7 +2118,7 @@ import { resolve } from "path";
|
|
|
1735
2118
|
import { join as join6 } from "path";
|
|
1736
2119
|
import * as p3 from "@clack/prompts";
|
|
1737
2120
|
async function initCommand(options) {
|
|
1738
|
-
logger.header("shipfolio v1.0.
|
|
2121
|
+
logger.header("shipfolio v1.0.3");
|
|
1739
2122
|
logger.info("Detecting AI engines...");
|
|
1740
2123
|
const engines = await detectEngines();
|
|
1741
2124
|
const availableTypes = getAvailableEngineTypes(engines);
|
|
@@ -1769,7 +2152,24 @@ async function initCommand(options) {
|
|
|
1769
2152
|
]);
|
|
1770
2153
|
logger.table(tableRows);
|
|
1771
2154
|
logger.blank();
|
|
1772
|
-
|
|
2155
|
+
let interviewResult;
|
|
2156
|
+
const draft = await loadDraft();
|
|
2157
|
+
if (draft) {
|
|
2158
|
+
logger.info(t().draftFound);
|
|
2159
|
+
const useDraft = await p3.confirm({
|
|
2160
|
+
message: t().draftLoadPrompt,
|
|
2161
|
+
initialValue: true
|
|
2162
|
+
});
|
|
2163
|
+
if (!p3.isCancel(useDraft) && useDraft) {
|
|
2164
|
+
interviewResult = draft.interviewResult;
|
|
2165
|
+
await clearDraft();
|
|
2166
|
+
} else {
|
|
2167
|
+
interviewResult = await runInterview(scannedProjects, availableTypes);
|
|
2168
|
+
}
|
|
2169
|
+
} else {
|
|
2170
|
+
interviewResult = await runInterview(scannedProjects, availableTypes);
|
|
2171
|
+
}
|
|
2172
|
+
await saveDraft(interviewResult);
|
|
1773
2173
|
const spec = buildSpec(interviewResult);
|
|
1774
2174
|
const prompt = await buildFreshPrompt(spec);
|
|
1775
2175
|
const outputDir = resolve(options.output || "./shipfolio-site");
|
|
@@ -1820,6 +2220,7 @@ async function initCommand(options) {
|
|
|
1820
2220
|
...spec,
|
|
1821
2221
|
sitePath: outputDir
|
|
1822
2222
|
});
|
|
2223
|
+
await clearDraft();
|
|
1823
2224
|
logger.header("Done.");
|
|
1824
2225
|
if (spec.deploy.url) {
|
|
1825
2226
|
logger.info(`Site: ${spec.deploy.url}`);
|
|
@@ -1844,6 +2245,8 @@ var init_init = __esm({
|
|
|
1844
2245
|
init_pdf();
|
|
1845
2246
|
init_fs();
|
|
1846
2247
|
init_logger();
|
|
2248
|
+
init_draft();
|
|
2249
|
+
init_i18n();
|
|
1847
2250
|
}
|
|
1848
2251
|
});
|
|
1849
2252
|
|
|
@@ -2160,10 +2563,12 @@ async function previewCommand(options) {
|
|
|
2160
2563
|
}
|
|
2161
2564
|
|
|
2162
2565
|
// src/index.ts
|
|
2566
|
+
init_i18n();
|
|
2567
|
+
initLocale();
|
|
2163
2568
|
var program = new Command();
|
|
2164
2569
|
program.name("shipfolio").description(
|
|
2165
2570
|
"Generate and deploy your personal portfolio site from local projects using AI"
|
|
2166
|
-
).version("1.0.
|
|
2571
|
+
).version("1.0.3");
|
|
2167
2572
|
program.command("init", { isDefault: true }).description("Generate a new portfolio site").option("-s, --scan <dirs...>", "Directories to scan for projects").option("-e, --engine <engine>", "AI engine: claude | codex | v0").option("-d, --deploy <platform>", "Deploy target: cloudflare | vercel | local").option("--style <theme>", "Theme: dark-minimal | light-clean | monochrome").option("--accent <hex>", "Accent color (hex)").option("--auto", "Skip prompts, use defaults").option("-o, --output <dir>", "Output directory", "./shipfolio-site").option("--no-pdf", "Skip PDF export").option("--no-preview", "Skip local preview").option("-v, --verbose", "Verbose output").action(initCommand);
|
|
2168
2573
|
program.command("update").description("Update an existing portfolio site").requiredOption("--site <path>", "Path to existing site directory").option("-s, --scan <dirs...>", "Directories to scan for projects").option("--no-pdf", "Skip PDF export").option("--no-preview", "Skip preview").option("--no-deploy", "Skip deployment").action(updateCommand);
|
|
2169
2574
|
program.command("spec").description("Generate spec and prompt files only").option("-s, --scan <dirs...>", "Directories to scan for projects").option("-o, --output <dir>", "Output directory for spec files", ".").action(specCommand);
|