codebakers 3.3.0 → 4.1.0
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/index.js +1165 -1421
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,9 +6,10 @@ import {
|
|
|
6
6
|
} from "./chunk-HOWR3YTF.js";
|
|
7
7
|
|
|
8
8
|
// src/index.ts
|
|
9
|
-
import * as
|
|
9
|
+
import * as p7 from "@clack/prompts";
|
|
10
10
|
import ora3 from "ora";
|
|
11
|
-
import
|
|
11
|
+
import fs17 from "fs-extra";
|
|
12
|
+
import path18 from "path";
|
|
12
13
|
|
|
13
14
|
// src/core/config.ts
|
|
14
15
|
import Conf from "conf";
|
|
@@ -749,7 +750,7 @@ import Anthropic from "@anthropic-ai/sdk";
|
|
|
749
750
|
var CORE_PATTERNS = `
|
|
750
751
|
## RULES (You MUST follow these)
|
|
751
752
|
|
|
752
|
-
### BANNED (never do these)
|
|
753
|
+
### BANNED (never do these) - 15 Rules
|
|
753
754
|
- \u274C \`any\` type \u2192 use proper TypeScript types
|
|
754
755
|
- \u274C \`@ts-ignore\` or \`@ts-nocheck\` \u2192 fix the actual type error
|
|
755
756
|
- \u274C \`console.log\` \u2192 remove or use proper logger
|
|
@@ -760,226 +761,468 @@ var CORE_PATTERNS = `
|
|
|
760
761
|
- \u274C \`throw new Error('Not implemented')\` \u2192 implement it
|
|
761
762
|
- \u274C \`debugger\` statements \u2192 remove before commit
|
|
762
763
|
- \u274C hardcoded secrets/keys \u2192 use environment variables
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
- \
|
|
766
|
-
- \
|
|
767
|
-
- \
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
- \u2705
|
|
771
|
-
- \u2705
|
|
772
|
-
- \u2705
|
|
773
|
-
- \u2705
|
|
774
|
-
- \u2705
|
|
775
|
-
- \u2705
|
|
776
|
-
- \u2705
|
|
777
|
-
- \u2705
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
useEffect(() => {
|
|
846
|
-
async function fetchData() {
|
|
847
|
-
try {
|
|
848
|
-
const response = await fetch('/api/data');
|
|
849
|
-
if (!response.ok) throw new Error('Failed to fetch');
|
|
850
|
-
const result = await response.json();
|
|
851
|
-
setData(result);
|
|
852
|
-
} catch (err) {
|
|
853
|
-
setError(err instanceof Error ? err.message : 'Failed to load');
|
|
854
|
-
} finally {
|
|
855
|
-
setIsLoading(false);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
fetchData();
|
|
859
|
-
}, []);
|
|
860
|
-
|
|
861
|
-
if (isLoading) return <Skeleton />;
|
|
862
|
-
if (error) return <ErrorState message={error} onRetry={() => window.location.reload()} />;
|
|
863
|
-
if (!data) return <EmptyState message="No data found" />;
|
|
864
|
-
|
|
865
|
-
return <DataDisplay data={data} />;
|
|
866
|
-
}
|
|
867
|
-
\`\`\`
|
|
868
|
-
|
|
869
|
-
#### API Route
|
|
870
|
-
\`\`\`tsx
|
|
871
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
872
|
-
import { z } from 'zod';
|
|
873
|
-
import { createClient } from '@/lib/supabase/server';
|
|
874
|
-
|
|
875
|
-
const requestSchema = z.object({
|
|
876
|
-
name: z.string().min(1),
|
|
877
|
-
email: z.string().email(),
|
|
878
|
-
});
|
|
879
|
-
|
|
880
|
-
export async function POST(request: NextRequest) {
|
|
881
|
-
try {
|
|
882
|
-
// 1. Auth check
|
|
883
|
-
const supabase = createClient();
|
|
884
|
-
const { data: { user }, error: authError } = await supabase.auth.getUser();
|
|
885
|
-
|
|
886
|
-
if (authError || !user) {
|
|
887
|
-
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
// 2. Validate input
|
|
891
|
-
const body = await request.json();
|
|
892
|
-
const result = requestSchema.safeParse(body);
|
|
893
|
-
|
|
894
|
-
if (!result.success) {
|
|
895
|
-
return NextResponse.json(
|
|
896
|
-
{ error: 'Validation failed', details: result.error.flatten() },
|
|
897
|
-
{ status: 400 }
|
|
898
|
-
);
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
// 3. Business logic
|
|
902
|
-
const data = result.data;
|
|
903
|
-
// ... do something
|
|
904
|
-
|
|
905
|
-
// 4. Return success
|
|
906
|
-
return NextResponse.json({ data }, { status: 201 });
|
|
907
|
-
|
|
908
|
-
} catch (error) {
|
|
909
|
-
console.error('[API] Error:', error);
|
|
910
|
-
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
\`\`\`
|
|
764
|
+
- \u274C \`innerHTML\` or \`dangerouslySetInnerHTML\` without sanitization
|
|
765
|
+
- \u274C \`document.write\` \u2192 use React/DOM methods
|
|
766
|
+
- \u274C synchronous \`localStorage\` in render \u2192 use useEffect
|
|
767
|
+
- \u274C \`var\` keyword \u2192 use const/let
|
|
768
|
+
- \u274C nested ternaries \u2192 use if/else or early returns
|
|
769
|
+
|
|
770
|
+
### REACT PATTERNS - 12 Rules
|
|
771
|
+
- \u2705 Components must have TypeScript props interface
|
|
772
|
+
- \u2705 useState must have explicit type: \`useState<Type>()\`
|
|
773
|
+
- \u2705 useEffect must have dependency array
|
|
774
|
+
- \u2705 useEffect cleanup for subscriptions/timers
|
|
775
|
+
- \u2705 useMemo/useCallback for expensive computations
|
|
776
|
+
- \u2705 Keys in lists must be stable IDs (not index)
|
|
777
|
+
- \u2705 Error boundaries for component trees
|
|
778
|
+
- \u2705 Suspense boundaries for lazy components
|
|
779
|
+
- \u2705 forwardRef for components accepting refs
|
|
780
|
+
- \u2705 displayName for HOCs and forwardRef
|
|
781
|
+
- \u2705 Prop drilling > 2 levels \u2192 use Context
|
|
782
|
+
- \u2705 Custom hooks for reusable stateful logic
|
|
783
|
+
|
|
784
|
+
### FORM PATTERNS - 8 Rules
|
|
785
|
+
- \u2705 Forms must have Zod validation schema
|
|
786
|
+
- \u2705 Forms must show field-level error messages
|
|
787
|
+
- \u2705 Forms must disable submit while submitting
|
|
788
|
+
- \u2705 Forms must show loading state on submit button
|
|
789
|
+
- \u2705 Forms must handle server errors gracefully
|
|
790
|
+
- \u2705 Forms must prevent double-submission
|
|
791
|
+
- \u2705 File inputs must validate file type/size
|
|
792
|
+
- \u2705 Forms must have accessible labels
|
|
793
|
+
|
|
794
|
+
### ASYNC/DATA PATTERNS - 10 Rules
|
|
795
|
+
- \u2705 Async operations must have try/catch
|
|
796
|
+
- \u2705 Async operations must have loading state
|
|
797
|
+
- \u2705 Lists must have loading state (skeleton/spinner)
|
|
798
|
+
- \u2705 Lists must have empty state
|
|
799
|
+
- \u2705 Lists must have error state with retry
|
|
800
|
+
- \u2705 Fetch must check response.ok
|
|
801
|
+
- \u2705 Fetch must have timeout handling
|
|
802
|
+
- \u2705 Pagination for lists > 20 items
|
|
803
|
+
- \u2705 Optimistic updates for better UX
|
|
804
|
+
- \u2705 Debounce for search/filter inputs
|
|
805
|
+
|
|
806
|
+
### API ROUTE PATTERNS - 8 Rules
|
|
807
|
+
- \u2705 API routes must check authentication
|
|
808
|
+
- \u2705 API routes must validate input with Zod
|
|
809
|
+
- \u2705 API routes must return proper HTTP status codes
|
|
810
|
+
- \u2705 API routes must log errors
|
|
811
|
+
- \u2705 API routes must sanitize user input
|
|
812
|
+
- \u2705 API routes must rate limit sensitive endpoints
|
|
813
|
+
- \u2705 API routes must validate content-type
|
|
814
|
+
- \u2705 API routes must handle CORS properly
|
|
815
|
+
|
|
816
|
+
### SECURITY PATTERNS - 8 Rules
|
|
817
|
+
- \u2705 Sanitize all user input before display
|
|
818
|
+
- \u2705 Use parameterized queries (no SQL injection)
|
|
819
|
+
- \u2705 CSRF tokens for state-changing operations
|
|
820
|
+
- \u2705 Secure cookies (httpOnly, secure, sameSite)
|
|
821
|
+
- \u2705 Validate redirect URLs
|
|
822
|
+
- \u2705 Use Content-Security-Policy headers
|
|
823
|
+
- \u2705 Hash passwords with bcrypt/argon2
|
|
824
|
+
- \u2705 Rate limit authentication endpoints
|
|
825
|
+
|
|
826
|
+
### ACCESSIBILITY PATTERNS - 6 Rules
|
|
827
|
+
- \u2705 Images must have alt text
|
|
828
|
+
- \u2705 Interactive elements must be keyboard accessible
|
|
829
|
+
- \u2705 Form inputs must have labels
|
|
830
|
+
- \u2705 Color contrast must meet WCAG AA
|
|
831
|
+
- \u2705 Focus indicators must be visible
|
|
832
|
+
- \u2705 ARIA labels for icon-only buttons
|
|
833
|
+
|
|
834
|
+
### TYPESCRIPT PATTERNS - 5 Rules
|
|
835
|
+
- \u2705 All exports must have explicit types
|
|
836
|
+
- \u2705 Prefer interfaces over type aliases for objects
|
|
837
|
+
- \u2705 Use discriminated unions for state
|
|
838
|
+
- \u2705 Avoid type assertions (as) where possible
|
|
839
|
+
- \u2705 Use strict mode
|
|
840
|
+
|
|
841
|
+
### TESTING PATTERNS - 4 Rules
|
|
842
|
+
- \u2705 Test files must be co-located with source
|
|
843
|
+
- \u2705 Test user behavior, not implementation
|
|
844
|
+
- \u2705 Mock external dependencies
|
|
845
|
+
- \u2705 Use data-testid for test selectors
|
|
914
846
|
`;
|
|
915
847
|
var BANNED_PATTERNS = [
|
|
848
|
+
// === BANNED PATTERNS (15) ===
|
|
916
849
|
{
|
|
917
850
|
id: "no-any",
|
|
918
851
|
regex: /:\s*any\b|<any>|as\s+any/g,
|
|
919
852
|
rule: "No `any` type",
|
|
920
853
|
fix: "Use proper TypeScript types",
|
|
921
|
-
severity: "error"
|
|
854
|
+
severity: "error",
|
|
855
|
+
category: "banned"
|
|
922
856
|
},
|
|
923
857
|
{
|
|
924
858
|
id: "no-ts-ignore",
|
|
925
859
|
regex: /@ts-ignore|@ts-nocheck/g,
|
|
926
860
|
rule: "No @ts-ignore",
|
|
927
861
|
fix: "Fix the actual type error",
|
|
928
|
-
severity: "error"
|
|
862
|
+
severity: "error",
|
|
863
|
+
category: "banned"
|
|
929
864
|
},
|
|
930
865
|
{
|
|
931
866
|
id: "no-console",
|
|
932
867
|
regex: /console\.(log|debug|info)\(/g,
|
|
933
868
|
rule: "No console.log",
|
|
934
869
|
fix: "Remove or use proper logger",
|
|
935
|
-
severity: "
|
|
870
|
+
severity: "warning",
|
|
871
|
+
category: "banned"
|
|
936
872
|
},
|
|
937
873
|
{
|
|
938
874
|
id: "no-todo",
|
|
939
875
|
regex: /\/\/\s*(TODO|FIXME|HACK|XXX):/gi,
|
|
940
876
|
rule: "No TODO/FIXME",
|
|
941
877
|
fix: "Complete the implementation now",
|
|
942
|
-
severity: "error"
|
|
878
|
+
severity: "error",
|
|
879
|
+
category: "banned"
|
|
943
880
|
},
|
|
944
881
|
{
|
|
945
882
|
id: "no-eval",
|
|
946
883
|
regex: /\beval\s*\(/g,
|
|
947
884
|
rule: "No eval()",
|
|
948
885
|
fix: "Use safer alternatives",
|
|
949
|
-
severity: "error"
|
|
886
|
+
severity: "error",
|
|
887
|
+
category: "security"
|
|
950
888
|
},
|
|
951
889
|
{
|
|
952
890
|
id: "no-empty-handler",
|
|
953
891
|
regex: /on\w+\s*=\s*\{\s*\(\)\s*=>\s*\{\s*\}\s*\}/g,
|
|
954
892
|
rule: "No empty event handlers",
|
|
955
893
|
fix: "Implement the handler",
|
|
956
|
-
severity: "error"
|
|
894
|
+
severity: "error",
|
|
895
|
+
category: "banned"
|
|
957
896
|
},
|
|
958
897
|
{
|
|
959
898
|
id: "no-debugger",
|
|
960
899
|
regex: /\bdebugger\b/g,
|
|
961
900
|
rule: "No debugger statements",
|
|
962
901
|
fix: "Remove debugger",
|
|
963
|
-
severity: "error"
|
|
902
|
+
severity: "error",
|
|
903
|
+
category: "banned"
|
|
964
904
|
},
|
|
965
905
|
{
|
|
966
906
|
id: "no-placeholder",
|
|
967
907
|
regex: /\/\/\s*\.\.\.|\/\*\s*\.\.\.\s*\*\//g,
|
|
968
908
|
rule: "No placeholder comments",
|
|
969
909
|
fix: "Write actual code",
|
|
970
|
-
severity: "error"
|
|
910
|
+
severity: "error",
|
|
911
|
+
category: "banned"
|
|
971
912
|
},
|
|
972
913
|
{
|
|
973
914
|
id: "no-not-implemented",
|
|
974
915
|
regex: /throw\s+new\s+Error\s*\(\s*['"`]not\s+implemented/gi,
|
|
975
916
|
rule: 'No "not implemented" errors',
|
|
976
917
|
fix: "Implement the function",
|
|
977
|
-
severity: "error"
|
|
918
|
+
severity: "error",
|
|
919
|
+
category: "banned"
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
id: "no-var",
|
|
923
|
+
regex: /\bvar\s+\w+/g,
|
|
924
|
+
rule: "No var keyword",
|
|
925
|
+
fix: "Use const or let",
|
|
926
|
+
severity: "error",
|
|
927
|
+
category: "banned"
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
id: "no-innerhtml",
|
|
931
|
+
regex: /\.innerHTML\s*=|dangerouslySetInnerHTML/g,
|
|
932
|
+
rule: "No innerHTML without sanitization",
|
|
933
|
+
fix: "Sanitize content or use React methods",
|
|
934
|
+
severity: "error",
|
|
935
|
+
category: "security"
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
id: "no-document-write",
|
|
939
|
+
regex: /document\.write\s*\(/g,
|
|
940
|
+
rule: "No document.write",
|
|
941
|
+
fix: "Use DOM methods or React",
|
|
942
|
+
severity: "error",
|
|
943
|
+
category: "banned"
|
|
944
|
+
},
|
|
945
|
+
{
|
|
946
|
+
id: "no-hardcoded-secret",
|
|
947
|
+
regex: /(api[_-]?key|secret|password|token)\s*[:=]\s*['"`][^'"`]{8,}/gi,
|
|
948
|
+
rule: "No hardcoded secrets",
|
|
949
|
+
fix: "Use environment variables",
|
|
950
|
+
severity: "error",
|
|
951
|
+
category: "security"
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
id: "no-nested-ternary",
|
|
955
|
+
regex: /\?\s*[^:]+\?\s*[^:]+:/g,
|
|
956
|
+
rule: "No nested ternaries",
|
|
957
|
+
fix: "Use if/else or early returns",
|
|
958
|
+
severity: "warning",
|
|
959
|
+
category: "banned"
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
id: "no-alert",
|
|
963
|
+
regex: /\balert\s*\(|\bconfirm\s*\(|\bprompt\s*\(/g,
|
|
964
|
+
rule: "No browser alerts",
|
|
965
|
+
fix: "Use proper UI components",
|
|
966
|
+
severity: "warning",
|
|
967
|
+
category: "banned"
|
|
968
|
+
},
|
|
969
|
+
// === REACT PATTERNS (12) ===
|
|
970
|
+
{
|
|
971
|
+
id: "react-no-index-key",
|
|
972
|
+
regex: /key\s*=\s*\{?\s*(index|i|idx)\s*\}?/g,
|
|
973
|
+
rule: "No array index as key",
|
|
974
|
+
fix: "Use stable unique ID",
|
|
975
|
+
severity: "warning",
|
|
976
|
+
category: "react"
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
id: "react-missing-deps",
|
|
980
|
+
regex: /useEffect\s*\(\s*\(\)\s*=>\s*\{[^}]*\}\s*\)/g,
|
|
981
|
+
rule: "useEffect missing dependency array",
|
|
982
|
+
fix: "Add dependency array []",
|
|
983
|
+
severity: "error",
|
|
984
|
+
category: "react"
|
|
985
|
+
},
|
|
986
|
+
{
|
|
987
|
+
id: "react-setstate-in-render",
|
|
988
|
+
regex: /function\s+\w+\s*\([^)]*\)\s*\{[^}]*\bset\w+\s*\([^)]+\)[^}]*return\s*\(/gs,
|
|
989
|
+
rule: "setState called during render",
|
|
990
|
+
fix: "Move to useEffect or event handler",
|
|
991
|
+
severity: "error",
|
|
992
|
+
category: "react"
|
|
993
|
+
},
|
|
994
|
+
// === SECURITY PATTERNS (8) ===
|
|
995
|
+
{
|
|
996
|
+
id: "security-sql-injection",
|
|
997
|
+
regex: /\$\{[^}]+\}.*(?:SELECT|INSERT|UPDATE|DELETE|FROM|WHERE)/gi,
|
|
998
|
+
rule: "Potential SQL injection",
|
|
999
|
+
fix: "Use parameterized queries",
|
|
1000
|
+
severity: "error",
|
|
1001
|
+
category: "security"
|
|
1002
|
+
},
|
|
1003
|
+
{
|
|
1004
|
+
id: "security-open-redirect",
|
|
1005
|
+
regex: /redirect\s*\(\s*(?:req\.|request\.)/gi,
|
|
1006
|
+
rule: "Potential open redirect",
|
|
1007
|
+
fix: "Validate redirect URL",
|
|
1008
|
+
severity: "error",
|
|
1009
|
+
category: "security"
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
id: "security-exec",
|
|
1013
|
+
regex: /\bexec\s*\(|\bexecSync\s*\(|\bspawn\s*\(/g,
|
|
1014
|
+
rule: "Command execution detected",
|
|
1015
|
+
fix: "Sanitize input or avoid shell commands",
|
|
1016
|
+
severity: "warning",
|
|
1017
|
+
category: "security"
|
|
1018
|
+
},
|
|
1019
|
+
// === ACCESSIBILITY PATTERNS (6) ===
|
|
1020
|
+
{
|
|
1021
|
+
id: "a11y-img-alt",
|
|
1022
|
+
regex: /<img[^>]+(?!alt\s*=)[^>]*>/gi,
|
|
1023
|
+
rule: "Image missing alt attribute",
|
|
1024
|
+
fix: 'Add alt="description"',
|
|
1025
|
+
severity: "warning",
|
|
1026
|
+
category: "a11y"
|
|
1027
|
+
},
|
|
1028
|
+
{
|
|
1029
|
+
id: "a11y-button-type",
|
|
1030
|
+
regex: /<button(?![^>]*type\s*=)[^>]*>/gi,
|
|
1031
|
+
rule: "Button missing type attribute",
|
|
1032
|
+
fix: 'Add type="button" or type="submit"',
|
|
1033
|
+
severity: "warning",
|
|
1034
|
+
category: "a11y"
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
id: "a11y-anchor-blank",
|
|
1038
|
+
regex: /target\s*=\s*['"]_blank['"](?![^>]*rel\s*=)/gi,
|
|
1039
|
+
rule: "External link missing rel attribute",
|
|
1040
|
+
fix: 'Add rel="noopener noreferrer"',
|
|
1041
|
+
severity: "warning",
|
|
1042
|
+
category: "a11y"
|
|
1043
|
+
},
|
|
1044
|
+
// === ASYNC PATTERNS (10) ===
|
|
1045
|
+
{
|
|
1046
|
+
id: "async-no-await",
|
|
1047
|
+
regex: /async\s+(?:function|\([^)]*\)\s*=>)[^{]*\{(?:(?!await)[^}])*\}/g,
|
|
1048
|
+
rule: "Async function without await",
|
|
1049
|
+
fix: "Add await or remove async",
|
|
1050
|
+
severity: "warning",
|
|
1051
|
+
category: "async"
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
id: "async-promise-no-catch",
|
|
1055
|
+
regex: /\.then\s*\([^)]+\)(?!\s*\.catch)/g,
|
|
1056
|
+
rule: "Promise without catch",
|
|
1057
|
+
fix: "Add .catch() handler",
|
|
1058
|
+
severity: "warning",
|
|
1059
|
+
category: "async"
|
|
1060
|
+
},
|
|
1061
|
+
// === TYPESCRIPT PATTERNS (5) ===
|
|
1062
|
+
{
|
|
1063
|
+
id: "ts-non-null-assertion",
|
|
1064
|
+
regex: /\w+!/g,
|
|
1065
|
+
rule: "Non-null assertion used",
|
|
1066
|
+
fix: "Use optional chaining or null check",
|
|
1067
|
+
severity: "warning",
|
|
1068
|
+
category: "typescript"
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
id: "ts-explicit-any-return",
|
|
1072
|
+
regex: /\):\s*any\s*\{/g,
|
|
1073
|
+
rule: "Function returns any",
|
|
1074
|
+
fix: "Add explicit return type",
|
|
1075
|
+
severity: "error",
|
|
1076
|
+
category: "typescript"
|
|
1077
|
+
},
|
|
1078
|
+
{
|
|
1079
|
+
id: "ts-object-type",
|
|
1080
|
+
regex: /:\s*object\b/g,
|
|
1081
|
+
rule: "Generic object type used",
|
|
1082
|
+
fix: "Use specific interface or Record<K,V>",
|
|
1083
|
+
severity: "warning",
|
|
1084
|
+
category: "typescript"
|
|
1085
|
+
},
|
|
1086
|
+
// === MORE REACT PATTERNS ===
|
|
1087
|
+
{
|
|
1088
|
+
id: "react-no-bind-in-render",
|
|
1089
|
+
regex: /onClick\s*=\s*\{[^}]*\.bind\s*\(/g,
|
|
1090
|
+
rule: "No .bind() in render",
|
|
1091
|
+
fix: "Use arrow function or useCallback",
|
|
1092
|
+
severity: "warning",
|
|
1093
|
+
category: "react"
|
|
1094
|
+
},
|
|
1095
|
+
{
|
|
1096
|
+
id: "react-no-arrow-in-render",
|
|
1097
|
+
regex: /onClick\s*=\s*\{\s*\(\)\s*=>\s*\w+\([^)]*\)\s*\}/g,
|
|
1098
|
+
rule: "Arrow function in render creates new function",
|
|
1099
|
+
fix: "Extract handler or use useCallback",
|
|
1100
|
+
severity: "warning",
|
|
1101
|
+
category: "react"
|
|
1102
|
+
},
|
|
1103
|
+
{
|
|
1104
|
+
id: "react-missing-error-boundary",
|
|
1105
|
+
regex: /throw\s+new\s+Error\s*\(/g,
|
|
1106
|
+
rule: "Throwing error without boundary",
|
|
1107
|
+
fix: "Wrap in Error Boundary",
|
|
1108
|
+
severity: "warning",
|
|
1109
|
+
category: "react"
|
|
1110
|
+
},
|
|
1111
|
+
{
|
|
1112
|
+
id: "react-direct-dom",
|
|
1113
|
+
regex: /document\.getElementById|document\.querySelector|document\.getElement/g,
|
|
1114
|
+
rule: "Direct DOM manipulation in React",
|
|
1115
|
+
fix: "Use refs or React state",
|
|
1116
|
+
severity: "warning",
|
|
1117
|
+
category: "react"
|
|
1118
|
+
},
|
|
1119
|
+
// === MORE SECURITY PATTERNS ===
|
|
1120
|
+
{
|
|
1121
|
+
id: "security-jwt-secret",
|
|
1122
|
+
regex: /jwt\.sign\s*\([^)]*['"`][^'"`]{8,}['"`]/gi,
|
|
1123
|
+
rule: "Hardcoded JWT secret",
|
|
1124
|
+
fix: "Use environment variable",
|
|
1125
|
+
severity: "error",
|
|
1126
|
+
category: "security"
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
id: "security-md5",
|
|
1130
|
+
regex: /\bmd5\s*\(|createHash\s*\(\s*['"]md5['"]/gi,
|
|
1131
|
+
rule: "MD5 is insecure for passwords",
|
|
1132
|
+
fix: "Use bcrypt or argon2",
|
|
1133
|
+
severity: "error",
|
|
1134
|
+
category: "security"
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
id: "security-sha1",
|
|
1138
|
+
regex: /createHash\s*\(\s*['"]sha1['"]/gi,
|
|
1139
|
+
rule: "SHA1 is deprecated",
|
|
1140
|
+
fix: "Use SHA256 or bcrypt",
|
|
1141
|
+
severity: "warning",
|
|
1142
|
+
category: "security"
|
|
1143
|
+
},
|
|
1144
|
+
{
|
|
1145
|
+
id: "security-cors-star",
|
|
1146
|
+
regex: /Access-Control-Allow-Origin['":\s]+\*/gi,
|
|
1147
|
+
rule: "CORS allows all origins",
|
|
1148
|
+
fix: "Specify allowed origins",
|
|
1149
|
+
severity: "warning",
|
|
1150
|
+
category: "security"
|
|
1151
|
+
},
|
|
1152
|
+
// === MORE ASYNC PATTERNS ===
|
|
1153
|
+
{
|
|
1154
|
+
id: "async-floating-promise",
|
|
1155
|
+
regex: /(?<!await\s)(?<!return\s)\w+\s*\.\s*then\s*\(/g,
|
|
1156
|
+
rule: "Floating promise (not awaited)",
|
|
1157
|
+
fix: "Add await or handle promise",
|
|
1158
|
+
severity: "warning",
|
|
1159
|
+
category: "async"
|
|
1160
|
+
},
|
|
1161
|
+
{
|
|
1162
|
+
id: "async-foreach-async",
|
|
1163
|
+
regex: /\.forEach\s*\(\s*async/g,
|
|
1164
|
+
rule: "async in forEach does not wait",
|
|
1165
|
+
fix: "Use for...of or Promise.all with map",
|
|
1166
|
+
severity: "error",
|
|
1167
|
+
category: "async"
|
|
1168
|
+
},
|
|
1169
|
+
// === MORE ACCESSIBILITY PATTERNS ===
|
|
1170
|
+
{
|
|
1171
|
+
id: "a11y-no-autofocus",
|
|
1172
|
+
regex: /autoFocus|autofocus/g,
|
|
1173
|
+
rule: "Autofocus can confuse screen readers",
|
|
1174
|
+
fix: "Remove or use carefully",
|
|
1175
|
+
severity: "warning",
|
|
1176
|
+
category: "a11y"
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
id: "a11y-positive-tabindex",
|
|
1180
|
+
regex: /tabIndex\s*=\s*\{?\s*[1-9]/g,
|
|
1181
|
+
rule: "Positive tabindex disrupts focus order",
|
|
1182
|
+
fix: "Use tabIndex={0} or tabIndex={-1}",
|
|
1183
|
+
severity: "warning",
|
|
1184
|
+
category: "a11y"
|
|
1185
|
+
},
|
|
1186
|
+
{
|
|
1187
|
+
id: "a11y-onclick-no-keyboard",
|
|
1188
|
+
regex: /onClick\s*=\s*\{[^}]+\}(?![^>]*onKeyDown|onKeyPress|onKeyUp)/g,
|
|
1189
|
+
rule: "Click handler without keyboard support",
|
|
1190
|
+
fix: "Add onKeyDown for Enter/Space",
|
|
1191
|
+
severity: "warning",
|
|
1192
|
+
category: "a11y"
|
|
1193
|
+
},
|
|
1194
|
+
// === FORM PATTERNS ===
|
|
1195
|
+
{
|
|
1196
|
+
id: "form-no-autocomplete",
|
|
1197
|
+
regex: /<input[^>]+type\s*=\s*['"]password['"][^>]*(?!autocomplete)/gi,
|
|
1198
|
+
rule: "Password field missing autocomplete",
|
|
1199
|
+
fix: 'Add autocomplete="current-password" or "new-password"',
|
|
1200
|
+
severity: "warning",
|
|
1201
|
+
category: "form"
|
|
1202
|
+
},
|
|
1203
|
+
// === PERFORMANCE PATTERNS ===
|
|
1204
|
+
{
|
|
1205
|
+
id: "perf-console-in-loop",
|
|
1206
|
+
regex: /(?:for|while|\.forEach|\.map)\s*\([^)]*\)\s*\{[^}]*console\./g,
|
|
1207
|
+
rule: "Console in loop impacts performance",
|
|
1208
|
+
fix: "Remove or move outside loop",
|
|
1209
|
+
severity: "warning",
|
|
1210
|
+
category: "banned"
|
|
1211
|
+
},
|
|
1212
|
+
{
|
|
1213
|
+
id: "perf-await-in-loop",
|
|
1214
|
+
regex: /(?:for|while)\s*\([^)]*\)\s*\{[^}]*await\s/g,
|
|
1215
|
+
rule: "await in loop is sequential",
|
|
1216
|
+
fix: "Use Promise.all for parallel execution",
|
|
1217
|
+
severity: "warning",
|
|
1218
|
+
category: "async"
|
|
978
1219
|
}
|
|
979
1220
|
];
|
|
980
1221
|
var REQUIRED_CHECKS = [
|
|
1222
|
+
// === BUTTON CHECKS ===
|
|
981
1223
|
{
|
|
982
1224
|
id: "button-loading",
|
|
1225
|
+
category: "form",
|
|
983
1226
|
check: (code) => {
|
|
984
1227
|
const hasButton = /<Button|<button/i.test(code);
|
|
985
1228
|
if (!hasButton) return { pass: true };
|
|
@@ -987,17 +1230,31 @@ var REQUIRED_CHECKS = [
|
|
|
987
1230
|
return { pass: hasDisabled, rule: "Buttons must have disabled={isLoading}", fix: "Add loading state" };
|
|
988
1231
|
}
|
|
989
1232
|
},
|
|
1233
|
+
// === FORM CHECKS ===
|
|
990
1234
|
{
|
|
991
1235
|
id: "form-validation",
|
|
1236
|
+
category: "form",
|
|
992
1237
|
check: (code) => {
|
|
993
1238
|
const hasForm = /<form|useForm|handleSubmit/i.test(code);
|
|
994
1239
|
if (!hasForm) return { pass: true };
|
|
995
|
-
const hasValidation = /zod|z\.|zodResolver|schema/i.test(code);
|
|
996
|
-
return { pass: hasValidation, rule: "Forms must have
|
|
1240
|
+
const hasValidation = /zod|z\.|zodResolver|schema|yup|validate/i.test(code);
|
|
1241
|
+
return { pass: hasValidation, rule: "Forms must have validation", fix: "Add Zod/Yup schema" };
|
|
1242
|
+
}
|
|
1243
|
+
},
|
|
1244
|
+
{
|
|
1245
|
+
id: "form-error-display",
|
|
1246
|
+
category: "form",
|
|
1247
|
+
check: (code) => {
|
|
1248
|
+
const hasForm = /<form|useForm/i.test(code);
|
|
1249
|
+
if (!hasForm) return { pass: true };
|
|
1250
|
+
const hasErrors = /errors\.|error\s*&&|formState.*errors|\.message/i.test(code);
|
|
1251
|
+
return { pass: hasErrors, rule: "Forms must display errors", fix: "Add error messages" };
|
|
997
1252
|
}
|
|
998
1253
|
},
|
|
1254
|
+
// === ASYNC CHECKS ===
|
|
999
1255
|
{
|
|
1000
1256
|
id: "async-error-handling",
|
|
1257
|
+
category: "async",
|
|
1001
1258
|
check: (code) => {
|
|
1002
1259
|
const hasAsync = /async\s+|await\s+/i.test(code);
|
|
1003
1260
|
if (!hasAsync) return { pass: true };
|
|
@@ -1005,14 +1262,102 @@ var REQUIRED_CHECKS = [
|
|
|
1005
1262
|
return { pass: hasTryCatch, rule: "Async operations must have try/catch", fix: "Add error handling" };
|
|
1006
1263
|
}
|
|
1007
1264
|
},
|
|
1265
|
+
{
|
|
1266
|
+
id: "async-loading-state",
|
|
1267
|
+
category: "async",
|
|
1268
|
+
check: (code) => {
|
|
1269
|
+
const hasAsync = /async\s+|await\s+|\.then\s*\(/i.test(code);
|
|
1270
|
+
if (!hasAsync) return { pass: true };
|
|
1271
|
+
const hasLoading = /loading|isLoading|pending|fetching|submitting/i.test(code);
|
|
1272
|
+
return { pass: hasLoading, rule: "Async operations must have loading state", fix: "Add isLoading state" };
|
|
1273
|
+
}
|
|
1274
|
+
},
|
|
1275
|
+
// === API ROUTE CHECKS ===
|
|
1008
1276
|
{
|
|
1009
1277
|
id: "api-auth-check",
|
|
1278
|
+
category: "api",
|
|
1010
1279
|
check: (code) => {
|
|
1011
1280
|
const isApiRoute = /export\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)/i.test(code);
|
|
1012
1281
|
if (!isApiRoute) return { pass: true };
|
|
1013
|
-
const hasAuth = /getUser|getSession|auth\(\)|authenticate|unauthorized|401/i.test(code);
|
|
1282
|
+
const hasAuth = /getUser|getSession|auth\(\)|authenticate|unauthorized|401|getServerSession/i.test(code);
|
|
1014
1283
|
return { pass: hasAuth, rule: "API routes must check authentication", fix: "Add auth check" };
|
|
1015
1284
|
}
|
|
1285
|
+
},
|
|
1286
|
+
{
|
|
1287
|
+
id: "api-input-validation",
|
|
1288
|
+
category: "api",
|
|
1289
|
+
check: (code) => {
|
|
1290
|
+
const isApiRoute = /export\s+(async\s+)?function\s+(POST|PUT|PATCH)/i.test(code);
|
|
1291
|
+
if (!isApiRoute) return { pass: true };
|
|
1292
|
+
const hasValidation = /safeParse|parse\(|validate|schema|zod|z\./i.test(code);
|
|
1293
|
+
return { pass: hasValidation, rule: "API routes must validate input", fix: "Add Zod validation" };
|
|
1294
|
+
}
|
|
1295
|
+
},
|
|
1296
|
+
{
|
|
1297
|
+
id: "api-status-codes",
|
|
1298
|
+
category: "api",
|
|
1299
|
+
check: (code) => {
|
|
1300
|
+
const isApiRoute = /export\s+(async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH)/i.test(code);
|
|
1301
|
+
if (!isApiRoute) return { pass: true };
|
|
1302
|
+
const hasStatusCodes = /status:\s*\d{3}|\{\s*status:\s*\d/i.test(code);
|
|
1303
|
+
return { pass: hasStatusCodes, rule: "API routes must return proper status codes", fix: "Add status codes" };
|
|
1304
|
+
}
|
|
1305
|
+
},
|
|
1306
|
+
// === LIST/DATA CHECKS ===
|
|
1307
|
+
{
|
|
1308
|
+
id: "list-loading-state",
|
|
1309
|
+
category: "async",
|
|
1310
|
+
check: (code) => {
|
|
1311
|
+
const hasList = /\.map\s*\(|Array\.from|forEach/i.test(code);
|
|
1312
|
+
const hasFetch = /fetch|useQuery|useSWR|axios/i.test(code);
|
|
1313
|
+
if (!hasList || !hasFetch) return { pass: true };
|
|
1314
|
+
const hasLoading = /Skeleton|Spinner|Loading|isLoading|loading/i.test(code);
|
|
1315
|
+
return { pass: hasLoading, rule: "Lists must have loading state", fix: "Add skeleton/spinner" };
|
|
1316
|
+
}
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
id: "list-empty-state",
|
|
1320
|
+
category: "async",
|
|
1321
|
+
check: (code) => {
|
|
1322
|
+
const hasList = /\.map\s*\(/i.test(code);
|
|
1323
|
+
const hasFetch = /fetch|useQuery|useSWR|axios/i.test(code);
|
|
1324
|
+
if (!hasList || !hasFetch) return { pass: true };
|
|
1325
|
+
const hasEmpty = /length\s*===?\s*0|!.*\.length|empty|no\s+\w+\s+found/i.test(code);
|
|
1326
|
+
return { pass: hasEmpty, rule: "Lists must have empty state", fix: "Add empty state UI" };
|
|
1327
|
+
}
|
|
1328
|
+
},
|
|
1329
|
+
// === SECURITY CHECKS ===
|
|
1330
|
+
{
|
|
1331
|
+
id: "security-env-secrets",
|
|
1332
|
+
category: "security",
|
|
1333
|
+
check: (code) => {
|
|
1334
|
+
const hasSecret = /api[_-]?key|secret|password|token/i.test(code);
|
|
1335
|
+
if (!hasSecret) return { pass: true };
|
|
1336
|
+
const usesEnv = /process\.env|import\.meta\.env/i.test(code);
|
|
1337
|
+
return { pass: usesEnv, rule: "Secrets must use environment variables", fix: "Move to .env" };
|
|
1338
|
+
}
|
|
1339
|
+
},
|
|
1340
|
+
// === TYPESCRIPT CHECKS ===
|
|
1341
|
+
{
|
|
1342
|
+
id: "ts-export-types",
|
|
1343
|
+
category: "typescript",
|
|
1344
|
+
check: (code) => {
|
|
1345
|
+
const hasExport = /export\s+(const|function|class)\s+\w+/i.test(code);
|
|
1346
|
+
if (!hasExport) return { pass: true };
|
|
1347
|
+
const hasTypes = /:\s*\w+|<\w+>|interface\s+|type\s+/i.test(code);
|
|
1348
|
+
return { pass: hasTypes, rule: "Exports must have TypeScript types", fix: "Add type annotations" };
|
|
1349
|
+
}
|
|
1350
|
+
},
|
|
1351
|
+
// === ACCESSIBILITY CHECKS ===
|
|
1352
|
+
{
|
|
1353
|
+
id: "a11y-form-labels",
|
|
1354
|
+
category: "a11y",
|
|
1355
|
+
check: (code) => {
|
|
1356
|
+
const hasInput = /<input|<select|<textarea/i.test(code);
|
|
1357
|
+
if (!hasInput) return { pass: true };
|
|
1358
|
+
const hasLabel = /<label|aria-label|aria-labelledby|id=.*htmlFor/i.test(code);
|
|
1359
|
+
return { pass: hasLabel, rule: "Form inputs must have labels", fix: "Add <label> or aria-label" };
|
|
1360
|
+
}
|
|
1016
1361
|
}
|
|
1017
1362
|
];
|
|
1018
1363
|
function validateCode(code) {
|
|
@@ -1029,7 +1374,8 @@ function validateCode(code) {
|
|
|
1029
1374
|
line: lineNumber,
|
|
1030
1375
|
code: match[0],
|
|
1031
1376
|
fix: pattern.fix,
|
|
1032
|
-
severity: pattern.severity
|
|
1377
|
+
severity: pattern.severity,
|
|
1378
|
+
category: pattern.category
|
|
1033
1379
|
});
|
|
1034
1380
|
}
|
|
1035
1381
|
}
|
|
@@ -1039,7 +1385,8 @@ function validateCode(code) {
|
|
|
1039
1385
|
violations.push({
|
|
1040
1386
|
rule: result.rule,
|
|
1041
1387
|
fix: result.fix || "See pattern documentation",
|
|
1042
|
-
severity: "error"
|
|
1388
|
+
severity: "error",
|
|
1389
|
+
category: check.category
|
|
1043
1390
|
});
|
|
1044
1391
|
}
|
|
1045
1392
|
}
|
|
@@ -1285,7 +1632,7 @@ ${originalRequest}`;
|
|
|
1285
1632
|
}
|
|
1286
1633
|
const pathMatch = request.match(/['"`]([^'"`]+)['"`]/g);
|
|
1287
1634
|
if (pathMatch) {
|
|
1288
|
-
keywords.push(...pathMatch.map((
|
|
1635
|
+
keywords.push(...pathMatch.map((p8) => p8.replace(/['"`]/g, "")));
|
|
1289
1636
|
}
|
|
1290
1637
|
return keywords;
|
|
1291
1638
|
}
|
|
@@ -1499,11 +1846,11 @@ Format each as a single line. Be brief.`;
|
|
|
1499
1846
|
} else if (line.toLowerCase().includes("suggestion") || line.toLowerCase().includes("improvement")) {
|
|
1500
1847
|
currentSection = "suggestions";
|
|
1501
1848
|
} else if (line.startsWith("-") || line.startsWith("\u2022") || /^\d+\./.test(line)) {
|
|
1502
|
-
const
|
|
1503
|
-
if (
|
|
1504
|
-
issues.push(
|
|
1505
|
-
} else if (
|
|
1506
|
-
suggestions.push(
|
|
1849
|
+
const text5 = line.replace(/^[-•\d.]\s*/, "").trim();
|
|
1850
|
+
if (text5 && currentSection === "issues") {
|
|
1851
|
+
issues.push(text5);
|
|
1852
|
+
} else if (text5 && currentSection === "suggestions") {
|
|
1853
|
+
suggestions.push(text5);
|
|
1507
1854
|
}
|
|
1508
1855
|
}
|
|
1509
1856
|
}
|
|
@@ -1572,8 +1919,8 @@ Format each as a single line. Be brief.`;
|
|
|
1572
1919
|
if (this.ai) {
|
|
1573
1920
|
onProgress?.("Running AI deep analysis...", 60);
|
|
1574
1921
|
const filesForAI = await Promise.all(
|
|
1575
|
-
filePaths.slice(0, 50).map(async (
|
|
1576
|
-
const f = await this.loadFile(
|
|
1922
|
+
filePaths.slice(0, 50).map(async (p8) => {
|
|
1923
|
+
const f = await this.loadFile(p8);
|
|
1577
1924
|
return f ? { path: f.path, content: f.content } : null;
|
|
1578
1925
|
})
|
|
1579
1926
|
);
|
|
@@ -1730,57 +2077,67 @@ import * as p from "@clack/prompts";
|
|
|
1730
2077
|
import chalk from "chalk";
|
|
1731
2078
|
var colors = {
|
|
1732
2079
|
primary: chalk.hex("#7fff00"),
|
|
1733
|
-
// Lime green (
|
|
2080
|
+
// Lime green (brand)
|
|
1734
2081
|
secondary: chalk.hex("#888888"),
|
|
1735
2082
|
// Gray text
|
|
1736
2083
|
muted: chalk.hex("#555555"),
|
|
1737
2084
|
// Dimmed text
|
|
1738
|
-
success: chalk.hex("#
|
|
1739
|
-
//
|
|
1740
|
-
warning: chalk.hex("#
|
|
1741
|
-
//
|
|
2085
|
+
success: chalk.hex("#2ecc71"),
|
|
2086
|
+
// Bright green
|
|
2087
|
+
warning: chalk.hex("#f1c40f"),
|
|
2088
|
+
// Yellow
|
|
1742
2089
|
error: chalk.hex("#e74c3c"),
|
|
1743
2090
|
// Red
|
|
1744
2091
|
info: chalk.hex("#3498db"),
|
|
1745
2092
|
// Blue
|
|
1746
|
-
white: chalk.hex("#
|
|
1747
|
-
dim: chalk.hex("#666666")
|
|
2093
|
+
white: chalk.hex("#ffffff"),
|
|
2094
|
+
dim: chalk.hex("#666666"),
|
|
2095
|
+
accent: chalk.hex("#9b59b6")
|
|
2096
|
+
// Purple for highlights
|
|
2097
|
+
};
|
|
2098
|
+
var box = {
|
|
2099
|
+
topLeft: "\u256D",
|
|
2100
|
+
topRight: "\u256E",
|
|
2101
|
+
bottomLeft: "\u2570",
|
|
2102
|
+
bottomRight: "\u256F",
|
|
2103
|
+
horizontal: "\u2500",
|
|
2104
|
+
vertical: "\u2502",
|
|
2105
|
+
teeRight: "\u251C",
|
|
2106
|
+
teeLeft: "\u2524"
|
|
1748
2107
|
};
|
|
1749
2108
|
var sym = {
|
|
1750
|
-
check:
|
|
1751
|
-
cross:
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
2109
|
+
check: colors.success("\u2713"),
|
|
2110
|
+
cross: colors.error("\u2717"),
|
|
2111
|
+
bullet: colors.dim("\u2022"),
|
|
2112
|
+
arrow: colors.primary("\u2192"),
|
|
2113
|
+
arrowRight: "\u203A",
|
|
2114
|
+
spinner: "\u27F3",
|
|
2115
|
+
star: "\u2605",
|
|
2116
|
+
lightning: "\u26A1",
|
|
2117
|
+
folder: "\u{1F4C1}",
|
|
2118
|
+
file: "\u{1F4C4}",
|
|
2119
|
+
gear: "\u2699",
|
|
2120
|
+
rocket: "\u{1F680}",
|
|
2121
|
+
sparkles: "\u2728",
|
|
2122
|
+
warning: "\u26A0",
|
|
2123
|
+
info: "\u2139"
|
|
1757
2124
|
};
|
|
1758
|
-
function showHeader() {
|
|
1759
|
-
console.clear();
|
|
1760
|
-
console.log("");
|
|
1761
|
-
console.log(colors.muted(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
1762
|
-
console.log(colors.muted(" \u2502") + colors.white(" \u{1F4E6} C O D E B A K E R S ") + colors.muted("\u2502"));
|
|
1763
|
-
console.log(colors.muted(" \u2502") + colors.dim(" ") + colors.muted("\u2502"));
|
|
1764
|
-
console.log(colors.muted(" \u2502") + colors.secondary(" AI Dev Team That Follows The Rules ") + colors.muted("\u2502"));
|
|
1765
|
-
console.log(colors.muted(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
1766
|
-
console.log("");
|
|
1767
|
-
}
|
|
1768
2125
|
function showSuccess(message) {
|
|
1769
|
-
console.log(` ${sym.check} ${colors.
|
|
2126
|
+
console.log(` ${sym.check} ${colors.success(message)}`);
|
|
1770
2127
|
}
|
|
1771
2128
|
function showError(message) {
|
|
1772
2129
|
console.log(` ${sym.cross} ${colors.error(message)}`);
|
|
1773
2130
|
}
|
|
1774
2131
|
function showWarning(message) {
|
|
1775
|
-
console.log(` ${colors.warning(
|
|
2132
|
+
console.log(` ${colors.warning(sym.warning)} ${colors.warning(message)}`);
|
|
1776
2133
|
}
|
|
1777
2134
|
function showInfo(message) {
|
|
1778
|
-
console.log(colors.
|
|
2135
|
+
console.log(` ${colors.info(sym.info)} ${colors.info(message)}`);
|
|
1779
2136
|
}
|
|
1780
2137
|
function divider() {
|
|
1781
|
-
console.log(colors.muted(
|
|
2138
|
+
console.log(colors.muted(` ${box.horizontal.repeat(50)}`));
|
|
1782
2139
|
}
|
|
1783
|
-
var promptSymbol = colors.primary("\
|
|
2140
|
+
var promptSymbol = colors.primary("\u203A");
|
|
1784
2141
|
|
|
1785
2142
|
// src/core/nlp.ts
|
|
1786
2143
|
var INTENT_PATTERNS = [
|
|
@@ -2503,12 +2860,12 @@ var NLPInterpreter = class {
|
|
|
2503
2860
|
deploy: "This will deploy your code. Continue?"
|
|
2504
2861
|
};
|
|
2505
2862
|
console.log("");
|
|
2506
|
-
const
|
|
2863
|
+
const confirm7 = await p.confirm({
|
|
2507
2864
|
message: colors.warning(messages[intent.type] || "Are you sure?"),
|
|
2508
2865
|
initialValue: intent.type !== "deploy"
|
|
2509
2866
|
// Default no for deploy
|
|
2510
2867
|
});
|
|
2511
|
-
return !p.isCancel(
|
|
2868
|
+
return !p.isCancel(confirm7) && confirm7 === true;
|
|
2512
2869
|
}
|
|
2513
2870
|
};
|
|
2514
2871
|
var nlp = new NLPInterpreter();
|
|
@@ -2736,22 +3093,22 @@ Please provide ONLY the corrected file content with the path. No explanation nee
|
|
|
2736
3093
|
// HELPERS
|
|
2737
3094
|
// ============================================================================
|
|
2738
3095
|
async findMissingPackages(packages) {
|
|
2739
|
-
const
|
|
3096
|
+
const fs18 = await import("fs-extra");
|
|
2740
3097
|
const path19 = await import("path");
|
|
2741
3098
|
const pkgPath = path19.join(this.scanner["projectPath"], "package.json");
|
|
2742
|
-
if (!await
|
|
2743
|
-
const pkg = await
|
|
3099
|
+
if (!await fs18.pathExists(pkgPath)) return packages;
|
|
3100
|
+
const pkg = await fs18.readJson(pkgPath);
|
|
2744
3101
|
const installed = {
|
|
2745
3102
|
...pkg.dependencies,
|
|
2746
3103
|
...pkg.devDependencies
|
|
2747
3104
|
};
|
|
2748
|
-
return packages.filter((
|
|
3105
|
+
return packages.filter((p8) => !installed[p8]);
|
|
2749
3106
|
}
|
|
2750
|
-
spin(
|
|
3107
|
+
spin(text5) {
|
|
2751
3108
|
if (this.spinner) {
|
|
2752
|
-
this.spinner.text =
|
|
3109
|
+
this.spinner.text = text5 + " (ESC to cancel)";
|
|
2753
3110
|
} else {
|
|
2754
|
-
this.spinner = ora(
|
|
3111
|
+
this.spinner = ora(text5 + " (ESC to cancel)").start();
|
|
2755
3112
|
}
|
|
2756
3113
|
}
|
|
2757
3114
|
stop() {
|
|
@@ -4410,574 +4767,46 @@ function getModeManager() {
|
|
|
4410
4767
|
|
|
4411
4768
|
// src/core/plan-executor.ts
|
|
4412
4769
|
import Anthropic3 from "@anthropic-ai/sdk";
|
|
4413
|
-
|
|
4770
|
+
|
|
4771
|
+
// src/core/question-executor.ts
|
|
4772
|
+
import * as p2 from "@clack/prompts";
|
|
4773
|
+
import Anthropic4 from "@anthropic-ai/sdk";
|
|
4774
|
+
|
|
4775
|
+
// src/core/agent-executor.ts
|
|
4776
|
+
import ora2 from "ora";
|
|
4777
|
+
|
|
4778
|
+
// src/core/input-handler.ts
|
|
4779
|
+
import fs9 from "fs-extra";
|
|
4780
|
+
import path9 from "path";
|
|
4781
|
+
|
|
4782
|
+
// src/core/prd-parser.ts
|
|
4783
|
+
import Anthropic5 from "@anthropic-ai/sdk";
|
|
4784
|
+
var PRDParser = class {
|
|
4414
4785
|
anthropic;
|
|
4415
4786
|
memory;
|
|
4416
|
-
|
|
4417
|
-
constructor(config, memory, scanner) {
|
|
4787
|
+
constructor(config, memory) {
|
|
4418
4788
|
const apiKey = config.getAnthropicKey();
|
|
4419
4789
|
if (!apiKey) throw new Error("API key not configured");
|
|
4420
|
-
this.anthropic = new
|
|
4790
|
+
this.anthropic = new Anthropic5({ apiKey });
|
|
4421
4791
|
this.memory = memory;
|
|
4422
|
-
this.scanner = scanner;
|
|
4423
4792
|
}
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
Create a detailed plan. Respond with ONLY valid JSON:
|
|
4435
|
-
{
|
|
4436
|
-
"description": "Brief summary of what will be built",
|
|
4437
|
-
"filesToCreate": ["src/path/file.ts", ...],
|
|
4438
|
-
"filesToModify": ["src/existing/file.ts", ...],
|
|
4439
|
-
"packagesToInstall": ["package-name", ...],
|
|
4440
|
-
"commandsToRun": ["npm install", "npm run build", ...],
|
|
4441
|
-
"estimatedTime": "2-3 minutes"
|
|
4442
|
-
}
|
|
4443
|
-
|
|
4444
|
-
Be specific about file paths. List ALL files that will be created or modified.`;
|
|
4445
|
-
const response = await this.anthropic.messages.create({
|
|
4446
|
-
model: "claude-sonnet-4-20250514",
|
|
4447
|
-
max_tokens: 2048,
|
|
4448
|
-
messages: [{ role: "user", content: prompt }]
|
|
4449
|
-
});
|
|
4450
|
-
const content = response.content[0]?.type === "text" ? response.content[0].text : "";
|
|
4451
|
-
try {
|
|
4452
|
-
const jsonStr = content.replace(/```json\n?|\n?```/g, "").trim();
|
|
4453
|
-
return JSON.parse(jsonStr);
|
|
4454
|
-
} catch {
|
|
4455
|
-
return {
|
|
4456
|
-
description: "Unable to parse plan",
|
|
4457
|
-
filesToCreate: [],
|
|
4458
|
-
filesToModify: [],
|
|
4459
|
-
packagesToInstall: [],
|
|
4460
|
-
commandsToRun: [],
|
|
4461
|
-
estimatedTime: "Unknown"
|
|
4462
|
-
};
|
|
4793
|
+
// ============================================================================
|
|
4794
|
+
// PARSE PRD
|
|
4795
|
+
// ============================================================================
|
|
4796
|
+
async parse(prdContent) {
|
|
4797
|
+
const chunks = this.chunkContent(prdContent, 5e4);
|
|
4798
|
+
let analysis;
|
|
4799
|
+
if (chunks.length === 1) {
|
|
4800
|
+
analysis = await this.analyzeChunk(chunks[0], true);
|
|
4801
|
+
} else {
|
|
4802
|
+
analysis = await this.analyzeMultipleChunks(chunks);
|
|
4463
4803
|
}
|
|
4804
|
+
analysis.phases = this.createPhases(analysis.features);
|
|
4805
|
+
return analysis;
|
|
4464
4806
|
}
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
output += colors.muted(" " + plan.description) + "\n\n";
|
|
4469
|
-
if (plan.filesToCreate.length > 0) {
|
|
4470
|
-
output += colors.white(" Files to create:\n");
|
|
4471
|
-
for (const file of plan.filesToCreate) {
|
|
4472
|
-
output += colors.success(` + ${file}`) + "\n";
|
|
4473
|
-
}
|
|
4474
|
-
output += "\n";
|
|
4475
|
-
}
|
|
4476
|
-
if (plan.filesToModify.length > 0) {
|
|
4477
|
-
output += colors.white(" Files to modify:\n");
|
|
4478
|
-
for (const file of plan.filesToModify) {
|
|
4479
|
-
output += colors.warning(` ~ ${file}`) + "\n";
|
|
4480
|
-
}
|
|
4481
|
-
output += "\n";
|
|
4482
|
-
}
|
|
4483
|
-
if (plan.packagesToInstall.length > 0) {
|
|
4484
|
-
output += colors.white(" Packages to install:\n");
|
|
4485
|
-
for (const pkg of plan.packagesToInstall) {
|
|
4486
|
-
output += colors.muted(` + ${pkg}`) + "\n";
|
|
4487
|
-
}
|
|
4488
|
-
output += "\n";
|
|
4489
|
-
}
|
|
4490
|
-
if (plan.commandsToRun.length > 0) {
|
|
4491
|
-
output += colors.white(" Commands to run:\n");
|
|
4492
|
-
for (const cmd of plan.commandsToRun) {
|
|
4493
|
-
output += colors.muted(` $ ${cmd}`) + "\n";
|
|
4494
|
-
}
|
|
4495
|
-
output += "\n";
|
|
4496
|
-
}
|
|
4497
|
-
output += colors.muted(` Estimated time: ${plan.estimatedTime}`) + "\n";
|
|
4498
|
-
return output;
|
|
4499
|
-
}
|
|
4500
|
-
};
|
|
4501
|
-
|
|
4502
|
-
// src/core/question-executor.ts
|
|
4503
|
-
import * as p2 from "@clack/prompts";
|
|
4504
|
-
import Anthropic4 from "@anthropic-ai/sdk";
|
|
4505
|
-
var QuestionExecutor = class {
|
|
4506
|
-
anthropic;
|
|
4507
|
-
memory;
|
|
4508
|
-
scanner;
|
|
4509
|
-
constructor(config, memory, scanner) {
|
|
4510
|
-
const apiKey = config.getAnthropicKey();
|
|
4511
|
-
if (!apiKey) throw new Error("API key not configured");
|
|
4512
|
-
this.anthropic = new Anthropic4({ apiKey });
|
|
4513
|
-
this.memory = memory;
|
|
4514
|
-
this.scanner = scanner;
|
|
4515
|
-
}
|
|
4516
|
-
async generateQuestions(request) {
|
|
4517
|
-
const projectContext = await this.scanner.getContextString([]);
|
|
4518
|
-
const memoryContext = this.memory.getFullContext();
|
|
4519
|
-
const prompt = `You are about to build something. Before starting, identify what clarifying questions would help you build exactly what the user wants.
|
|
4520
|
-
|
|
4521
|
-
PROJECT CONTEXT:
|
|
4522
|
-
${projectContext}
|
|
4523
|
-
|
|
4524
|
-
MEMORY (what you already know):
|
|
4525
|
-
${memoryContext}
|
|
4526
|
-
|
|
4527
|
-
USER REQUEST:
|
|
4528
|
-
${request}
|
|
4529
|
-
|
|
4530
|
-
Generate 2-4 clarifying questions. Only ask questions where the answer would significantly change what you build. Skip questions if the answer is obvious from context or memory.
|
|
4531
|
-
|
|
4532
|
-
Respond with ONLY valid JSON:
|
|
4533
|
-
{
|
|
4534
|
-
"questions": [
|
|
4535
|
-
{
|
|
4536
|
-
"id": "auth_type",
|
|
4537
|
-
"text": "What type of authentication do you need?",
|
|
4538
|
-
"type": "select",
|
|
4539
|
-
"options": [
|
|
4540
|
-
{ "value": "email", "label": "Email/password only" },
|
|
4541
|
-
{ "value": "oauth", "label": "OAuth (Google, GitHub)" },
|
|
4542
|
-
{ "value": "both", "label": "Both email and OAuth" }
|
|
4543
|
-
]
|
|
4544
|
-
},
|
|
4545
|
-
{
|
|
4546
|
-
"id": "need_reset",
|
|
4547
|
-
"text": "Include password reset flow?",
|
|
4548
|
-
"type": "confirm",
|
|
4549
|
-
"default": "yes"
|
|
4550
|
-
}
|
|
4551
|
-
]
|
|
4552
|
-
}
|
|
4553
|
-
|
|
4554
|
-
Question types:
|
|
4555
|
-
- "select": Multiple choice (provide options)
|
|
4556
|
-
- "confirm": Yes/no question
|
|
4557
|
-
- "text": Free text input
|
|
4558
|
-
|
|
4559
|
-
Only ask ESSENTIAL questions. If you can make a reasonable assumption, do so.`;
|
|
4560
|
-
const response = await this.anthropic.messages.create({
|
|
4561
|
-
model: "claude-sonnet-4-20250514",
|
|
4562
|
-
max_tokens: 2048,
|
|
4563
|
-
messages: [{ role: "user", content: prompt }]
|
|
4564
|
-
});
|
|
4565
|
-
const content = response.content[0]?.type === "text" ? response.content[0].text : "";
|
|
4566
|
-
try {
|
|
4567
|
-
const jsonStr = content.replace(/```json\n?|\n?```/g, "").trim();
|
|
4568
|
-
const parsed = JSON.parse(jsonStr);
|
|
4569
|
-
return parsed.questions || [];
|
|
4570
|
-
} catch {
|
|
4571
|
-
return [];
|
|
4572
|
-
}
|
|
4573
|
-
}
|
|
4574
|
-
async askQuestions(questions) {
|
|
4575
|
-
const answers = {};
|
|
4576
|
-
if (questions.length === 0) {
|
|
4577
|
-
console.log(colors.muted("\n No clarification needed - proceeding with build.\n"));
|
|
4578
|
-
return { questions: [], answers: {} };
|
|
4579
|
-
}
|
|
4580
|
-
console.log("\n" + colors.secondary(" \u{1F914} A few questions first:") + "\n");
|
|
4581
|
-
for (const question of questions) {
|
|
4582
|
-
let answer;
|
|
4583
|
-
switch (question.type) {
|
|
4584
|
-
case "select":
|
|
4585
|
-
answer = await p2.select({
|
|
4586
|
-
message: question.text,
|
|
4587
|
-
options: question.options?.map((opt) => ({
|
|
4588
|
-
value: opt.value,
|
|
4589
|
-
label: opt.label
|
|
4590
|
-
})) || []
|
|
4591
|
-
});
|
|
4592
|
-
break;
|
|
4593
|
-
case "confirm":
|
|
4594
|
-
answer = await p2.confirm({
|
|
4595
|
-
message: question.text,
|
|
4596
|
-
initialValue: question.default === "yes"
|
|
4597
|
-
});
|
|
4598
|
-
break;
|
|
4599
|
-
case "text":
|
|
4600
|
-
default:
|
|
4601
|
-
answer = await p2.text({
|
|
4602
|
-
message: question.text,
|
|
4603
|
-
placeholder: question.default
|
|
4604
|
-
});
|
|
4605
|
-
break;
|
|
4606
|
-
}
|
|
4607
|
-
if (p2.isCancel(answer)) {
|
|
4608
|
-
return { questions, answers };
|
|
4609
|
-
}
|
|
4610
|
-
answers[question.id] = String(answer);
|
|
4611
|
-
}
|
|
4612
|
-
return { questions, answers };
|
|
4613
|
-
}
|
|
4614
|
-
formatAnswersForPrompt(result) {
|
|
4615
|
-
if (Object.keys(result.answers).length === 0) {
|
|
4616
|
-
return "";
|
|
4617
|
-
}
|
|
4618
|
-
let context = "\n\nUSER PREFERENCES (from clarifying questions):\n";
|
|
4619
|
-
for (const question of result.questions) {
|
|
4620
|
-
const answer = result.answers[question.id];
|
|
4621
|
-
if (answer) {
|
|
4622
|
-
context += `- ${question.text}: ${answer}
|
|
4623
|
-
`;
|
|
4624
|
-
}
|
|
4625
|
-
}
|
|
4626
|
-
return context;
|
|
4627
|
-
}
|
|
4628
|
-
showSummary(result) {
|
|
4629
|
-
if (Object.keys(result.answers).length === 0) return;
|
|
4630
|
-
console.log("\n" + colors.muted(" Got it! Building with:"));
|
|
4631
|
-
for (const question of result.questions) {
|
|
4632
|
-
const answer = result.answers[question.id];
|
|
4633
|
-
if (answer) {
|
|
4634
|
-
let displayAnswer = answer;
|
|
4635
|
-
if (question.type === "select" && question.options) {
|
|
4636
|
-
const option = question.options.find((o) => o.value === answer);
|
|
4637
|
-
if (option) displayAnswer = option.label;
|
|
4638
|
-
} else if (question.type === "confirm") {
|
|
4639
|
-
displayAnswer = answer === "true" ? "Yes" : "No";
|
|
4640
|
-
}
|
|
4641
|
-
console.log(colors.muted(` \u2022 ${displayAnswer}`));
|
|
4642
|
-
}
|
|
4643
|
-
}
|
|
4644
|
-
console.log("");
|
|
4645
|
-
}
|
|
4646
|
-
};
|
|
4647
|
-
|
|
4648
|
-
// src/core/agent-executor.ts
|
|
4649
|
-
import ora2 from "ora";
|
|
4650
|
-
var AgentExecutor = class {
|
|
4651
|
-
ai;
|
|
4652
|
-
config;
|
|
4653
|
-
memory;
|
|
4654
|
-
scanner;
|
|
4655
|
-
editor;
|
|
4656
|
-
spinner = null;
|
|
4657
|
-
paused = false;
|
|
4658
|
-
cancelled = false;
|
|
4659
|
-
constructor(config, ai, memory, scanner, editor) {
|
|
4660
|
-
this.config = config;
|
|
4661
|
-
this.ai = ai;
|
|
4662
|
-
this.memory = memory;
|
|
4663
|
-
this.scanner = scanner;
|
|
4664
|
-
this.editor = editor;
|
|
4665
|
-
}
|
|
4666
|
-
// ============================================================================
|
|
4667
|
-
// MAIN EXECUTION
|
|
4668
|
-
// ============================================================================
|
|
4669
|
-
async execute(request, options = {}) {
|
|
4670
|
-
const startTime = Date.now();
|
|
4671
|
-
this.paused = false;
|
|
4672
|
-
this.cancelled = false;
|
|
4673
|
-
const result = {
|
|
4674
|
-
success: false,
|
|
4675
|
-
filesCreated: [],
|
|
4676
|
-
filesModified: [],
|
|
4677
|
-
packagesInstalled: [],
|
|
4678
|
-
commandsRun: [],
|
|
4679
|
-
errors: [],
|
|
4680
|
-
duration: 0
|
|
4681
|
-
};
|
|
4682
|
-
const checkCancelled = () => {
|
|
4683
|
-
if (options.checkCancelled?.() || this.cancelled) {
|
|
4684
|
-
return true;
|
|
4685
|
-
}
|
|
4686
|
-
return false;
|
|
4687
|
-
};
|
|
4688
|
-
console.log("");
|
|
4689
|
-
console.log(colors.primary(" \u{1F916} AGENT MODE") + colors.muted(" - Running autonomously"));
|
|
4690
|
-
console.log(colors.muted(" Press ESC to pause at any checkpoint"));
|
|
4691
|
-
console.log("");
|
|
4692
|
-
try {
|
|
4693
|
-
const isComplex = this.isComplexRequest(request);
|
|
4694
|
-
if (isComplex) {
|
|
4695
|
-
return await this.executeParallel(request, options, checkCancelled);
|
|
4696
|
-
}
|
|
4697
|
-
const orchestrator = new Orchestrator(this.ai, this.editor, this.scanner, this.memory);
|
|
4698
|
-
this.spin("Generating code...");
|
|
4699
|
-
const genResult = await this.ai.generateStream(
|
|
4700
|
-
request,
|
|
4701
|
-
{
|
|
4702
|
-
onToken: (token) => {
|
|
4703
|
-
}
|
|
4704
|
-
},
|
|
4705
|
-
checkCancelled
|
|
4706
|
-
);
|
|
4707
|
-
if (checkCancelled()) {
|
|
4708
|
-
this.stop();
|
|
4709
|
-
return { ...result, cancelled: true, duration: Date.now() - startTime };
|
|
4710
|
-
}
|
|
4711
|
-
if (this.paused) {
|
|
4712
|
-
this.stop();
|
|
4713
|
-
return { ...result, paused: true, duration: Date.now() - startTime };
|
|
4714
|
-
}
|
|
4715
|
-
this.spin("Applying changes...");
|
|
4716
|
-
this.editor.parseResponse(genResult.content);
|
|
4717
|
-
const changes = this.editor.getPendingChanges();
|
|
4718
|
-
for (const change of changes) {
|
|
4719
|
-
if (checkCancelled()) {
|
|
4720
|
-
this.stop();
|
|
4721
|
-
return { ...result, cancelled: true, duration: Date.now() - startTime };
|
|
4722
|
-
}
|
|
4723
|
-
options.onFile?.(change.path, change.type === "create" ? "create" : "modify");
|
|
4724
|
-
if (change.type === "create") {
|
|
4725
|
-
result.filesCreated.push(change.path);
|
|
4726
|
-
} else {
|
|
4727
|
-
result.filesModified.push(change.path);
|
|
4728
|
-
}
|
|
4729
|
-
this.logStep(`${change.type === "create" ? "Created" : "Modified"} ${change.path}`);
|
|
4730
|
-
}
|
|
4731
|
-
await this.editor.apply();
|
|
4732
|
-
const allContent = changes.map((c) => c.content || "").join("\n");
|
|
4733
|
-
const packages = this.detectPackages(allContent);
|
|
4734
|
-
if (packages.length > 0 && !checkCancelled()) {
|
|
4735
|
-
this.spin("Installing packages...");
|
|
4736
|
-
const { Terminal: Terminal2 } = await import("./terminal-6ZQVP6R7.js");
|
|
4737
|
-
const terminal = new Terminal2();
|
|
4738
|
-
for (const pkg of packages) {
|
|
4739
|
-
this.logStep(`Installing ${pkg}...`);
|
|
4740
|
-
}
|
|
4741
|
-
const installResult = await terminal.npmInstall(packages);
|
|
4742
|
-
if (installResult.success) {
|
|
4743
|
-
result.packagesInstalled = packages;
|
|
4744
|
-
result.commandsRun.push(`npm install ${packages.join(" ")}`);
|
|
4745
|
-
}
|
|
4746
|
-
}
|
|
4747
|
-
if (!checkCancelled()) {
|
|
4748
|
-
this.spin("Type checking...");
|
|
4749
|
-
const { Terminal: Terminal2 } = await import("./terminal-6ZQVP6R7.js");
|
|
4750
|
-
const terminal = new Terminal2();
|
|
4751
|
-
const typeResult = await terminal.typecheck();
|
|
4752
|
-
result.commandsRun.push("tsc --noEmit");
|
|
4753
|
-
if (!typeResult.success) {
|
|
4754
|
-
this.logStep("Fixing type errors...");
|
|
4755
|
-
}
|
|
4756
|
-
}
|
|
4757
|
-
this.stop();
|
|
4758
|
-
result.success = result.errors.length === 0;
|
|
4759
|
-
result.duration = Date.now() - startTime;
|
|
4760
|
-
this.showSummary(result);
|
|
4761
|
-
for (const file of [...result.filesCreated, ...result.filesModified]) {
|
|
4762
|
-
this.memory.addModifiedFile(file);
|
|
4763
|
-
}
|
|
4764
|
-
await this.memory.save();
|
|
4765
|
-
return result;
|
|
4766
|
-
} catch (error) {
|
|
4767
|
-
this.stop();
|
|
4768
|
-
result.errors.push(error instanceof Error ? error.message : "Unknown error");
|
|
4769
|
-
result.duration = Date.now() - startTime;
|
|
4770
|
-
return result;
|
|
4771
|
-
}
|
|
4772
|
-
}
|
|
4773
|
-
// ============================================================================
|
|
4774
|
-
// PARALLEL EXECUTION
|
|
4775
|
-
// ============================================================================
|
|
4776
|
-
async executeParallel(request, options, checkCancelled) {
|
|
4777
|
-
const startTime = Date.now();
|
|
4778
|
-
const result = {
|
|
4779
|
-
success: false,
|
|
4780
|
-
filesCreated: [],
|
|
4781
|
-
filesModified: [],
|
|
4782
|
-
packagesInstalled: [],
|
|
4783
|
-
commandsRun: [],
|
|
4784
|
-
errors: [],
|
|
4785
|
-
duration: 0
|
|
4786
|
-
};
|
|
4787
|
-
console.log(colors.muted(" Using parallel agents for faster build..."));
|
|
4788
|
-
console.log("");
|
|
4789
|
-
const parallelSystem = new ParallelAgentSystem(
|
|
4790
|
-
this.config,
|
|
4791
|
-
this.memory,
|
|
4792
|
-
this.scanner,
|
|
4793
|
-
this.editor
|
|
4794
|
-
);
|
|
4795
|
-
const parallelResult = await parallelSystem.build(
|
|
4796
|
-
request,
|
|
4797
|
-
{
|
|
4798
|
-
onTaskPlan: (tasks) => {
|
|
4799
|
-
console.log(colors.muted(` \u{1F4CB} ${tasks.length} tasks planned`));
|
|
4800
|
-
},
|
|
4801
|
-
onAgentStart: (taskId, name) => {
|
|
4802
|
-
console.log(colors.muted(` \u25CF [${name}] Starting...`));
|
|
4803
|
-
},
|
|
4804
|
-
onAgentComplete: (taskId, agentResult) => {
|
|
4805
|
-
if (agentResult.success) {
|
|
4806
|
-
console.log(colors.success(` \u2713 [${taskId}] Complete (${agentResult.files.length} files)`));
|
|
4807
|
-
} else {
|
|
4808
|
-
console.log(colors.error(` \u2717 [${taskId}] Failed`));
|
|
4809
|
-
}
|
|
4810
|
-
},
|
|
4811
|
-
onMergeStart: () => {
|
|
4812
|
-
console.log(colors.muted(" \u{1F504} Merging results..."));
|
|
4813
|
-
},
|
|
4814
|
-
onMergeComplete: (conflicts) => {
|
|
4815
|
-
if (conflicts.length === 0) {
|
|
4816
|
-
console.log(colors.success(" \u2713 No conflicts"));
|
|
4817
|
-
}
|
|
4818
|
-
}
|
|
4819
|
-
},
|
|
4820
|
-
checkCancelled
|
|
4821
|
-
);
|
|
4822
|
-
if (parallelResult.cancelled) {
|
|
4823
|
-
return { ...result, cancelled: true, duration: Date.now() - startTime };
|
|
4824
|
-
}
|
|
4825
|
-
const changes = this.editor.getPendingChanges();
|
|
4826
|
-
await this.editor.apply();
|
|
4827
|
-
for (const change of changes) {
|
|
4828
|
-
if (change.type === "create") {
|
|
4829
|
-
result.filesCreated.push(change.path);
|
|
4830
|
-
} else {
|
|
4831
|
-
result.filesModified.push(change.path);
|
|
4832
|
-
}
|
|
4833
|
-
}
|
|
4834
|
-
result.success = parallelResult.success;
|
|
4835
|
-
result.duration = Date.now() - startTime;
|
|
4836
|
-
this.showSummary(result);
|
|
4837
|
-
return result;
|
|
4838
|
-
}
|
|
4839
|
-
// ============================================================================
|
|
4840
|
-
// HELPERS
|
|
4841
|
-
// ============================================================================
|
|
4842
|
-
isComplexRequest(request) {
|
|
4843
|
-
const lower = request.toLowerCase();
|
|
4844
|
-
const complexIndicators = [
|
|
4845
|
-
" and ",
|
|
4846
|
-
" with ",
|
|
4847
|
-
", ",
|
|
4848
|
-
" + ",
|
|
4849
|
-
"full",
|
|
4850
|
-
"complete",
|
|
4851
|
-
"entire",
|
|
4852
|
-
"saas",
|
|
4853
|
-
"dashboard",
|
|
4854
|
-
"auth",
|
|
4855
|
-
"billing",
|
|
4856
|
-
"payment"
|
|
4857
|
-
];
|
|
4858
|
-
let score = 0;
|
|
4859
|
-
for (const indicator of complexIndicators) {
|
|
4860
|
-
if (lower.includes(indicator)) score++;
|
|
4861
|
-
}
|
|
4862
|
-
return score >= 3;
|
|
4863
|
-
}
|
|
4864
|
-
detectPackages(content) {
|
|
4865
|
-
const packages = /* @__PURE__ */ new Set();
|
|
4866
|
-
const importRegex = /import\s+.*?from\s+['"]([^'"./][^'"]*)['"]/g;
|
|
4867
|
-
let match;
|
|
4868
|
-
while ((match = importRegex.exec(content)) !== null) {
|
|
4869
|
-
let pkg = match[1];
|
|
4870
|
-
if (pkg.startsWith("@")) {
|
|
4871
|
-
pkg = pkg.split("/").slice(0, 2).join("/");
|
|
4872
|
-
} else {
|
|
4873
|
-
pkg = pkg.split("/")[0];
|
|
4874
|
-
}
|
|
4875
|
-
const builtins = ["fs", "path", "http", "https", "crypto", "stream", "events", "os", "util", "url"];
|
|
4876
|
-
if (!builtins.includes(pkg)) {
|
|
4877
|
-
packages.add(pkg);
|
|
4878
|
-
}
|
|
4879
|
-
}
|
|
4880
|
-
return Array.from(packages);
|
|
4881
|
-
}
|
|
4882
|
-
spin(text6) {
|
|
4883
|
-
if (this.spinner) {
|
|
4884
|
-
this.spinner.text = text6;
|
|
4885
|
-
} else {
|
|
4886
|
-
this.spinner = ora2(text6).start();
|
|
4887
|
-
}
|
|
4888
|
-
}
|
|
4889
|
-
stop() {
|
|
4890
|
-
if (this.spinner) {
|
|
4891
|
-
this.spinner.stop();
|
|
4892
|
-
this.spinner = null;
|
|
4893
|
-
}
|
|
4894
|
-
}
|
|
4895
|
-
logStep(message) {
|
|
4896
|
-
this.stop();
|
|
4897
|
-
console.log(colors.muted(` \u25CF ${message}`));
|
|
4898
|
-
}
|
|
4899
|
-
showSummary(result) {
|
|
4900
|
-
console.log("");
|
|
4901
|
-
console.log(" " + "\u2550".repeat(50));
|
|
4902
|
-
if (result.success) {
|
|
4903
|
-
console.log(colors.success(" \u{1F389} AUTONOMOUS BUILD COMPLETE"));
|
|
4904
|
-
} else if (result.cancelled) {
|
|
4905
|
-
console.log(colors.warning(" \u26A0\uFE0F BUILD CANCELLED"));
|
|
4906
|
-
} else if (result.paused) {
|
|
4907
|
-
console.log(colors.warning(" \u23F8\uFE0F BUILD PAUSED"));
|
|
4908
|
-
} else {
|
|
4909
|
-
console.log(colors.error(" \u274C BUILD FAILED"));
|
|
4910
|
-
}
|
|
4911
|
-
console.log("");
|
|
4912
|
-
if (result.filesCreated.length > 0) {
|
|
4913
|
-
console.log(colors.muted(` Created: ${result.filesCreated.length} files`));
|
|
4914
|
-
}
|
|
4915
|
-
if (result.filesModified.length > 0) {
|
|
4916
|
-
console.log(colors.muted(` Modified: ${result.filesModified.length} files`));
|
|
4917
|
-
}
|
|
4918
|
-
if (result.packagesInstalled.length > 0) {
|
|
4919
|
-
console.log(colors.muted(` Installed: ${result.packagesInstalled.join(", ")}`));
|
|
4920
|
-
}
|
|
4921
|
-
const seconds = (result.duration / 1e3).toFixed(1);
|
|
4922
|
-
console.log(colors.muted(` Time: ${seconds}s`));
|
|
4923
|
-
if (result.errors.length > 0) {
|
|
4924
|
-
console.log("");
|
|
4925
|
-
console.log(colors.error(" Errors:"));
|
|
4926
|
-
for (const error of result.errors) {
|
|
4927
|
-
console.log(colors.error(` \u2022 ${error}`));
|
|
4928
|
-
}
|
|
4929
|
-
}
|
|
4930
|
-
console.log("");
|
|
4931
|
-
console.log(colors.muted(" All changes saved. Use /undo to rollback."));
|
|
4932
|
-
console.log(" " + "\u2550".repeat(50));
|
|
4933
|
-
console.log("");
|
|
4934
|
-
}
|
|
4935
|
-
// ============================================================================
|
|
4936
|
-
// CONTROL
|
|
4937
|
-
// ============================================================================
|
|
4938
|
-
pause() {
|
|
4939
|
-
this.paused = true;
|
|
4940
|
-
}
|
|
4941
|
-
cancel() {
|
|
4942
|
-
this.cancelled = true;
|
|
4943
|
-
}
|
|
4944
|
-
resume() {
|
|
4945
|
-
this.paused = false;
|
|
4946
|
-
}
|
|
4947
|
-
};
|
|
4948
|
-
|
|
4949
|
-
// src/core/input-handler.ts
|
|
4950
|
-
import fs9 from "fs-extra";
|
|
4951
|
-
import path9 from "path";
|
|
4952
|
-
|
|
4953
|
-
// src/core/prd-parser.ts
|
|
4954
|
-
import Anthropic5 from "@anthropic-ai/sdk";
|
|
4955
|
-
var PRDParser = class {
|
|
4956
|
-
anthropic;
|
|
4957
|
-
memory;
|
|
4958
|
-
constructor(config, memory) {
|
|
4959
|
-
const apiKey = config.getAnthropicKey();
|
|
4960
|
-
if (!apiKey) throw new Error("API key not configured");
|
|
4961
|
-
this.anthropic = new Anthropic5({ apiKey });
|
|
4962
|
-
this.memory = memory;
|
|
4963
|
-
}
|
|
4964
|
-
// ============================================================================
|
|
4965
|
-
// PARSE PRD
|
|
4966
|
-
// ============================================================================
|
|
4967
|
-
async parse(prdContent) {
|
|
4968
|
-
const chunks = this.chunkContent(prdContent, 5e4);
|
|
4969
|
-
let analysis;
|
|
4970
|
-
if (chunks.length === 1) {
|
|
4971
|
-
analysis = await this.analyzeChunk(chunks[0], true);
|
|
4972
|
-
} else {
|
|
4973
|
-
analysis = await this.analyzeMultipleChunks(chunks);
|
|
4974
|
-
}
|
|
4975
|
-
analysis.phases = this.createPhases(analysis.features);
|
|
4976
|
-
return analysis;
|
|
4977
|
-
}
|
|
4978
|
-
chunkContent(content, maxSize) {
|
|
4979
|
-
if (content.length <= maxSize) {
|
|
4980
|
-
return [content];
|
|
4807
|
+
chunkContent(content, maxSize) {
|
|
4808
|
+
if (content.length <= maxSize) {
|
|
4809
|
+
return [content];
|
|
4981
4810
|
}
|
|
4982
4811
|
const chunks = [];
|
|
4983
4812
|
let remaining = content;
|
|
@@ -5188,7 +5017,7 @@ ${chunks[i]}`,
|
|
|
5188
5017
|
projectName: analysis.projectName,
|
|
5189
5018
|
totalFeatures: analysis.features.length,
|
|
5190
5019
|
completedFeatures: analysis.features.filter((f) => f.status === "complete").length,
|
|
5191
|
-
currentPhase: analysis.phases.find((
|
|
5020
|
+
currentPhase: analysis.phases.find((p8) => p8.status === "in_progress")?.id || analysis.phases[0]?.id || "",
|
|
5192
5021
|
currentFeature: analysis.features.find((f) => f.status === "in_progress")?.id || "",
|
|
5193
5022
|
features: analysis.features.map((f) => ({ id: f.id, status: f.status })),
|
|
5194
5023
|
startedAt: Date.now(),
|
|
@@ -6684,8 +6513,8 @@ var SmartGitManager = class {
|
|
|
6684
6513
|
const pr = await this.createPR(phaseBranch, baseBranch);
|
|
6685
6514
|
return pr;
|
|
6686
6515
|
}
|
|
6687
|
-
slugify(
|
|
6688
|
-
return
|
|
6516
|
+
slugify(text5) {
|
|
6517
|
+
return text5.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
|
|
6689
6518
|
}
|
|
6690
6519
|
// ============================================================================
|
|
6691
6520
|
// SMART COMMITS
|
|
@@ -6964,7 +6793,7 @@ var ProjectBuilder = class {
|
|
|
6964
6793
|
totalDuration: 0
|
|
6965
6794
|
};
|
|
6966
6795
|
try {
|
|
6967
|
-
const phaseNames = spec.buildPhases.map((
|
|
6796
|
+
const phaseNames = spec.buildPhases.map((p8) => p8.name.replace(/Phase \d+:\s*/i, ""));
|
|
6968
6797
|
const strategy = await this.git.createProjectStrategy(spec.name, phaseNames);
|
|
6969
6798
|
console.log(this.git.formatStrategy(strategy));
|
|
6970
6799
|
const proceed = await p4.confirm({
|
|
@@ -7671,7 +7500,7 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
|
|
|
7671
7500
|
return this.patterns;
|
|
7672
7501
|
}
|
|
7673
7502
|
enablePattern(id) {
|
|
7674
|
-
const pattern = this.patterns.find((
|
|
7503
|
+
const pattern = this.patterns.find((p8) => p8.id === id);
|
|
7675
7504
|
if (pattern) {
|
|
7676
7505
|
pattern.enabled = true;
|
|
7677
7506
|
return true;
|
|
@@ -7679,7 +7508,7 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
|
|
|
7679
7508
|
return false;
|
|
7680
7509
|
}
|
|
7681
7510
|
disablePattern(id) {
|
|
7682
|
-
const pattern = this.patterns.find((
|
|
7511
|
+
const pattern = this.patterns.find((p8) => p8.id === id);
|
|
7683
7512
|
if (pattern) {
|
|
7684
7513
|
pattern.enabled = false;
|
|
7685
7514
|
return true;
|
|
@@ -7738,8 +7567,8 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
|
|
|
7738
7567
|
formatPatterns() {
|
|
7739
7568
|
let output = "\n";
|
|
7740
7569
|
output += colors.white(" \u{1F4CB} Pattern Rules") + "\n\n";
|
|
7741
|
-
const enabled = this.patterns.filter((
|
|
7742
|
-
const disabled = this.patterns.filter((
|
|
7570
|
+
const enabled = this.patterns.filter((p8) => p8.enabled);
|
|
7571
|
+
const disabled = this.patterns.filter((p8) => !p8.enabled);
|
|
7743
7572
|
output += colors.success(" ENABLED") + "\n";
|
|
7744
7573
|
for (const pattern of enabled) {
|
|
7745
7574
|
const severityIcon = pattern.severity === "critical" ? "\u{1F534}" : pattern.severity === "warning" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
@@ -7764,7 +7593,7 @@ Return ONLY the fixed code, no explanations. Keep all other code exactly the sam
|
|
|
7764
7593
|
return grouped;
|
|
7765
7594
|
}
|
|
7766
7595
|
getPatternName(id) {
|
|
7767
|
-
const pattern = this.patterns.find((
|
|
7596
|
+
const pattern = this.patterns.find((p8) => p8.id === id);
|
|
7768
7597
|
return pattern?.name || id;
|
|
7769
7598
|
}
|
|
7770
7599
|
// ============================================================================
|
|
@@ -7970,7 +7799,7 @@ var SmartSuggestions = class {
|
|
|
7970
7799
|
/platform\s*(that|which|for)/,
|
|
7971
7800
|
/saas\s*(for|that|which)/
|
|
7972
7801
|
];
|
|
7973
|
-
return ideaPatterns.some((
|
|
7802
|
+
return ideaPatterns.some((p8) => p8.test(lower));
|
|
7974
7803
|
}
|
|
7975
7804
|
looksLikeComplexRequest(input) {
|
|
7976
7805
|
const lower = input.toLowerCase();
|
|
@@ -8000,7 +7829,7 @@ var SmartSuggestions = class {
|
|
|
8000
7829
|
/clean\s*up/,
|
|
8001
7830
|
/improve\s*(the\s*)?(code|quality)/
|
|
8002
7831
|
];
|
|
8003
|
-
return patterns.some((
|
|
7832
|
+
return patterns.some((p8) => p8.test(lower));
|
|
8004
7833
|
}
|
|
8005
7834
|
// ============================================================================
|
|
8006
7835
|
// DISPLAY
|
|
@@ -8863,7 +8692,7 @@ ${issues.map((i) => ` \u2022 ${i}`).join("\n")}` : "\n\nEverything looks good!"
|
|
|
8863
8692
|
/^(ugh|argh|damn|shit|fuck)/i
|
|
8864
8693
|
// Frustration words
|
|
8865
8694
|
];
|
|
8866
|
-
return frustrationPatterns.some((
|
|
8695
|
+
return frustrationPatterns.some((p8) => p8.test(input));
|
|
8867
8696
|
}
|
|
8868
8697
|
// ============================================================================
|
|
8869
8698
|
// RATE LIMITING (Don't be annoying)
|
|
@@ -9125,8 +8954,8 @@ ${chunk.content.slice(0, 2e3)}`
|
|
|
9125
8954
|
chunk.embedding = this.simpleEmbed(chunk.content + " " + chunk.summary);
|
|
9126
8955
|
}
|
|
9127
8956
|
}
|
|
9128
|
-
simpleEmbed(
|
|
9129
|
-
const words =
|
|
8957
|
+
simpleEmbed(text5) {
|
|
8958
|
+
const words = text5.toLowerCase().split(/\W+/).filter((w) => w.length > 2);
|
|
9130
8959
|
const embedding = new Array(256).fill(0);
|
|
9131
8960
|
for (const word of words) {
|
|
9132
8961
|
const hash = this.hashString(word);
|
|
@@ -9587,7 +9416,7 @@ ${featureList}`
|
|
|
9587
9416
|
"Redux": ["@reduxjs/toolkit", "redux"]
|
|
9588
9417
|
};
|
|
9589
9418
|
for (const [name, packages] of Object.entries(categories)) {
|
|
9590
|
-
if (packages.some((
|
|
9419
|
+
if (packages.some((p8) => deps.has(p8))) {
|
|
9591
9420
|
stack.push(name);
|
|
9592
9421
|
}
|
|
9593
9422
|
}
|
|
@@ -9719,279 +9548,11 @@ ${featureList}`
|
|
|
9719
9548
|
}
|
|
9720
9549
|
};
|
|
9721
9550
|
|
|
9722
|
-
// src/core/onboarding.ts
|
|
9723
|
-
import * as p6 from "@clack/prompts";
|
|
9724
|
-
import fs16 from "fs-extra";
|
|
9725
|
-
import path16 from "path";
|
|
9726
|
-
function showWelcomeScreen() {
|
|
9727
|
-
console.clear();
|
|
9728
|
-
console.log("");
|
|
9729
|
-
console.log(colors.primary(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
9730
|
-
console.log(colors.primary(" \u2502 \u2502"));
|
|
9731
|
-
console.log(colors.primary(" \u2502") + colors.white(" Welcome to CodeBakers! ") + colors.primary("\u2502"));
|
|
9732
|
-
console.log(colors.primary(" \u2502") + colors.muted(" AI Dev Team That Follows The Rules ") + colors.primary("\u2502"));
|
|
9733
|
-
console.log(colors.primary(" \u2502 \u2502"));
|
|
9734
|
-
console.log(colors.primary(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
9735
|
-
console.log("");
|
|
9736
|
-
console.log(colors.white(" Let's get you set up in 2 quick steps:"));
|
|
9737
|
-
console.log("");
|
|
9738
|
-
console.log(colors.muted(" 1. Connect your account (or use API key)"));
|
|
9739
|
-
console.log(colors.muted(" 2. Initialize your project"));
|
|
9740
|
-
console.log("");
|
|
9741
|
-
console.log(colors.dim(" Press ESC at any time to go back"));
|
|
9742
|
-
console.log("");
|
|
9743
|
-
}
|
|
9744
|
-
async function showAuthScreen() {
|
|
9745
|
-
console.clear();
|
|
9746
|
-
console.log("");
|
|
9747
|
-
console.log(colors.white(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
9748
|
-
console.log(colors.white(" \u2502") + colors.primary(" Step 1 of 2: ") + colors.white("Connect Your Account ") + colors.white("\u2502"));
|
|
9749
|
-
console.log(colors.white(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
9750
|
-
console.log("");
|
|
9751
|
-
const choice = await p6.select({
|
|
9752
|
-
message: "How would you like to connect?",
|
|
9753
|
-
options: [
|
|
9754
|
-
{
|
|
9755
|
-
value: "browser",
|
|
9756
|
-
label: "\u{1F310} Login with browser",
|
|
9757
|
-
hint: "Google or GitHub - recommended"
|
|
9758
|
-
},
|
|
9759
|
-
{
|
|
9760
|
-
value: "code",
|
|
9761
|
-
label: "\u{1F511} Enter admin/beta code",
|
|
9762
|
-
hint: "If you have a special code"
|
|
9763
|
-
},
|
|
9764
|
-
{
|
|
9765
|
-
value: "apikey",
|
|
9766
|
-
label: "\u{1F527} Use my own API key",
|
|
9767
|
-
hint: "No account needed, limited features"
|
|
9768
|
-
},
|
|
9769
|
-
{
|
|
9770
|
-
value: "skip",
|
|
9771
|
-
label: "\u23ED\uFE0F Skip for now",
|
|
9772
|
-
hint: "You can login later with /login"
|
|
9773
|
-
}
|
|
9774
|
-
]
|
|
9775
|
-
});
|
|
9776
|
-
if (p6.isCancel(choice)) {
|
|
9777
|
-
return "back";
|
|
9778
|
-
}
|
|
9779
|
-
return choice;
|
|
9780
|
-
}
|
|
9781
|
-
async function showApiKeyScreen(config) {
|
|
9782
|
-
console.clear();
|
|
9783
|
-
console.log("");
|
|
9784
|
-
console.log(colors.white(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
9785
|
-
console.log(colors.white(" \u2502") + colors.primary(" Step 1 of 2: ") + colors.white("Enter Your API Key ") + colors.white("\u2502"));
|
|
9786
|
-
console.log(colors.white(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
9787
|
-
console.log("");
|
|
9788
|
-
console.log(colors.muted(" Get your API key from: https://console.anthropic.com/"));
|
|
9789
|
-
console.log("");
|
|
9790
|
-
const apiKey = await p6.text({
|
|
9791
|
-
message: "Anthropic API Key:",
|
|
9792
|
-
placeholder: "sk-ant-api...",
|
|
9793
|
-
validate: (value) => {
|
|
9794
|
-
if (!value) return "API key is required";
|
|
9795
|
-
if (!value.startsWith("sk-ant-")) return "API key should start with sk-ant-";
|
|
9796
|
-
return void 0;
|
|
9797
|
-
}
|
|
9798
|
-
});
|
|
9799
|
-
if (p6.isCancel(apiKey)) {
|
|
9800
|
-
return "back";
|
|
9801
|
-
}
|
|
9802
|
-
config.setAnthropicKey(apiKey);
|
|
9803
|
-
console.log("");
|
|
9804
|
-
console.log(colors.success(" \u2713 API key saved"));
|
|
9805
|
-
await sleep(500);
|
|
9806
|
-
return "done";
|
|
9807
|
-
}
|
|
9808
|
-
async function showProjectScreen(memory) {
|
|
9809
|
-
console.clear();
|
|
9810
|
-
console.log("");
|
|
9811
|
-
console.log(colors.white(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
9812
|
-
console.log(colors.white(" \u2502") + colors.primary(" Step 2 of 2: ") + colors.white("Initialize Your Project ") + colors.white("\u2502"));
|
|
9813
|
-
console.log(colors.white(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
9814
|
-
console.log("");
|
|
9815
|
-
const cwd = process.cwd();
|
|
9816
|
-
const dirName = path16.basename(cwd);
|
|
9817
|
-
const hasPackageJson = await fs16.pathExists(path16.join(cwd, "package.json"));
|
|
9818
|
-
if (hasPackageJson) {
|
|
9819
|
-
console.log(colors.muted(` \u{1F4C1} Detected project: ${dirName}`));
|
|
9820
|
-
console.log("");
|
|
9821
|
-
} else {
|
|
9822
|
-
console.log(colors.muted(` \u{1F4C1} Current folder: ${dirName}`));
|
|
9823
|
-
console.log("");
|
|
9824
|
-
}
|
|
9825
|
-
const action = await p6.select({
|
|
9826
|
-
message: "What would you like to do?",
|
|
9827
|
-
options: [
|
|
9828
|
-
{
|
|
9829
|
-
value: "init",
|
|
9830
|
-
label: "\u2728 Initialize this folder as a CB project",
|
|
9831
|
-
hint: "CB will remember context about this project"
|
|
9832
|
-
},
|
|
9833
|
-
{
|
|
9834
|
-
value: "skip",
|
|
9835
|
-
label: "\u23ED\uFE0F Skip for now",
|
|
9836
|
-
hint: "You can initialize later with /init"
|
|
9837
|
-
}
|
|
9838
|
-
]
|
|
9839
|
-
});
|
|
9840
|
-
if (p6.isCancel(action)) {
|
|
9841
|
-
return "back";
|
|
9842
|
-
}
|
|
9843
|
-
if (action === "skip") {
|
|
9844
|
-
return "skip";
|
|
9845
|
-
}
|
|
9846
|
-
const projectName = await p6.text({
|
|
9847
|
-
message: "Project name:",
|
|
9848
|
-
initialValue: dirName,
|
|
9849
|
-
placeholder: dirName
|
|
9850
|
-
});
|
|
9851
|
-
if (p6.isCancel(projectName)) {
|
|
9852
|
-
return "back";
|
|
9853
|
-
}
|
|
9854
|
-
const description = await p6.text({
|
|
9855
|
-
message: "Brief description (optional):",
|
|
9856
|
-
placeholder: "A web app that..."
|
|
9857
|
-
});
|
|
9858
|
-
if (p6.isCancel(description)) {
|
|
9859
|
-
return "back";
|
|
9860
|
-
}
|
|
9861
|
-
await memory.init();
|
|
9862
|
-
memory.setIdentity(projectName, description || "");
|
|
9863
|
-
console.log("");
|
|
9864
|
-
console.log(colors.success(` \u2713 Project "${projectName}" initialized`));
|
|
9865
|
-
console.log(colors.muted(" CB will now remember context about this project"));
|
|
9866
|
-
await sleep(500);
|
|
9867
|
-
return "done";
|
|
9868
|
-
}
|
|
9869
|
-
function showCompleteScreen() {
|
|
9870
|
-
console.clear();
|
|
9871
|
-
console.log("");
|
|
9872
|
-
console.log(colors.primary(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
9873
|
-
console.log(colors.primary(" \u2502") + colors.white(" ") + colors.primary("\u2502"));
|
|
9874
|
-
console.log(colors.primary(" \u2502") + colors.success(" \u2713 You're all set! ") + colors.primary("\u2502"));
|
|
9875
|
-
console.log(colors.primary(" \u2502") + colors.white(" ") + colors.primary("\u2502"));
|
|
9876
|
-
console.log(colors.primary(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
9877
|
-
console.log("");
|
|
9878
|
-
console.log(colors.white(" Quick start:"));
|
|
9879
|
-
console.log("");
|
|
9880
|
-
console.log(colors.muted(" Just type what you want:"));
|
|
9881
|
-
console.log(colors.white(' "Add a login page"'));
|
|
9882
|
-
console.log(colors.white(' "Fix the bug in navbar"'));
|
|
9883
|
-
console.log(colors.white(' "Create an API for users"'));
|
|
9884
|
-
console.log("");
|
|
9885
|
-
console.log(colors.muted(" Or use commands:"));
|
|
9886
|
-
console.log(colors.dim(" /help ") + colors.muted("Show all commands"));
|
|
9887
|
-
console.log(colors.dim(" /audit ") + colors.muted("Check code quality"));
|
|
9888
|
-
console.log(colors.dim(" /spec ") + colors.muted("Generate a product spec"));
|
|
9889
|
-
console.log("");
|
|
9890
|
-
}
|
|
9891
|
-
async function runOnboarding(config, memory) {
|
|
9892
|
-
let currentStep = "welcome";
|
|
9893
|
-
const hasApiKey = config.isConfigured();
|
|
9894
|
-
const hasAuth = auth.isLoggedIn();
|
|
9895
|
-
const hasProject = memory.getIdentity().name !== "";
|
|
9896
|
-
if (hasAuth || hasApiKey) {
|
|
9897
|
-
if (hasProject) {
|
|
9898
|
-
return true;
|
|
9899
|
-
}
|
|
9900
|
-
currentStep = "project";
|
|
9901
|
-
}
|
|
9902
|
-
while (true) {
|
|
9903
|
-
switch (currentStep) {
|
|
9904
|
-
case "welcome": {
|
|
9905
|
-
showWelcomeScreen();
|
|
9906
|
-
const proceed = await p6.confirm({
|
|
9907
|
-
message: "Ready to begin?",
|
|
9908
|
-
initialValue: true
|
|
9909
|
-
});
|
|
9910
|
-
if (p6.isCancel(proceed) || !proceed) {
|
|
9911
|
-
return false;
|
|
9912
|
-
}
|
|
9913
|
-
currentStep = "auth";
|
|
9914
|
-
break;
|
|
9915
|
-
}
|
|
9916
|
-
case "auth": {
|
|
9917
|
-
const authChoice = await showAuthScreen();
|
|
9918
|
-
if (authChoice === "back") {
|
|
9919
|
-
currentStep = "welcome";
|
|
9920
|
-
break;
|
|
9921
|
-
}
|
|
9922
|
-
if (authChoice === "skip") {
|
|
9923
|
-
if (!config.isConfigured()) {
|
|
9924
|
-
console.log("");
|
|
9925
|
-
console.log(colors.warning(" \u26A0 You need either an account or API key to use CB"));
|
|
9926
|
-
console.log("");
|
|
9927
|
-
await sleep(1500);
|
|
9928
|
-
break;
|
|
9929
|
-
}
|
|
9930
|
-
currentStep = "project";
|
|
9931
|
-
break;
|
|
9932
|
-
}
|
|
9933
|
-
if (authChoice === "apikey") {
|
|
9934
|
-
currentStep = "api_key";
|
|
9935
|
-
break;
|
|
9936
|
-
}
|
|
9937
|
-
if (authChoice === "browser" || authChoice === "code") {
|
|
9938
|
-
const success = authChoice === "browser" ? await auth.loginWithBrowser() : await auth.loginWithCode();
|
|
9939
|
-
if (success) {
|
|
9940
|
-
currentStep = "project";
|
|
9941
|
-
}
|
|
9942
|
-
break;
|
|
9943
|
-
}
|
|
9944
|
-
break;
|
|
9945
|
-
}
|
|
9946
|
-
case "api_key": {
|
|
9947
|
-
const result = await showApiKeyScreen(config);
|
|
9948
|
-
if (result === "back") {
|
|
9949
|
-
currentStep = "auth";
|
|
9950
|
-
break;
|
|
9951
|
-
}
|
|
9952
|
-
currentStep = "project";
|
|
9953
|
-
break;
|
|
9954
|
-
}
|
|
9955
|
-
case "project": {
|
|
9956
|
-
const result = await showProjectScreen(memory);
|
|
9957
|
-
if (result === "back") {
|
|
9958
|
-
if (!config.isConfigured() && !auth.isLoggedIn()) {
|
|
9959
|
-
currentStep = "auth";
|
|
9960
|
-
}
|
|
9961
|
-
break;
|
|
9962
|
-
}
|
|
9963
|
-
currentStep = "complete";
|
|
9964
|
-
break;
|
|
9965
|
-
}
|
|
9966
|
-
case "complete": {
|
|
9967
|
-
showCompleteScreen();
|
|
9968
|
-
const ready = await p6.confirm({
|
|
9969
|
-
message: "Start using CodeBakers?",
|
|
9970
|
-
initialValue: true
|
|
9971
|
-
});
|
|
9972
|
-
if (p6.isCancel(ready)) {
|
|
9973
|
-
currentStep = "project";
|
|
9974
|
-
break;
|
|
9975
|
-
}
|
|
9976
|
-
return true;
|
|
9977
|
-
}
|
|
9978
|
-
}
|
|
9979
|
-
}
|
|
9980
|
-
}
|
|
9981
|
-
function needsOnboarding(config, memory) {
|
|
9982
|
-
const hasApiKey = config.isConfigured();
|
|
9983
|
-
const hasAuth = auth.isLoggedIn();
|
|
9984
|
-
return !hasApiKey && !hasAuth;
|
|
9985
|
-
}
|
|
9986
|
-
function sleep(ms) {
|
|
9987
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9988
|
-
}
|
|
9989
|
-
|
|
9990
9551
|
// src/utils/folder-picker.ts
|
|
9991
9552
|
import { execSync } from "child_process";
|
|
9992
|
-
import
|
|
9553
|
+
import path16 from "path";
|
|
9993
9554
|
import os2 from "os";
|
|
9994
|
-
import * as
|
|
9555
|
+
import * as p6 from "@clack/prompts";
|
|
9995
9556
|
async function pickFolder(message = "Select a folder") {
|
|
9996
9557
|
const platform = os2.platform();
|
|
9997
9558
|
try {
|
|
@@ -10084,24 +9645,24 @@ async function manualFolderEntry(message) {
|
|
|
10084
9645
|
const commonPaths = [
|
|
10085
9646
|
{ label: "\u{1F4C1} Current folder", value: process.cwd() },
|
|
10086
9647
|
{ label: "\u{1F3E0} Home", value: homeDir },
|
|
10087
|
-
{ label: "\u{1F4C2} Desktop", value:
|
|
10088
|
-
{ label: "\u{1F4C2} Documents", value:
|
|
10089
|
-
{ label: "\u{1F4C2} Downloads", value:
|
|
9648
|
+
{ label: "\u{1F4C2} Desktop", value: path16.join(homeDir, "Desktop") },
|
|
9649
|
+
{ label: "\u{1F4C2} Documents", value: path16.join(homeDir, "Documents") },
|
|
9650
|
+
{ label: "\u{1F4C2} Downloads", value: path16.join(homeDir, "Downloads") },
|
|
10090
9651
|
{ label: "\u270F\uFE0F Type custom path", value: "__custom__" }
|
|
10091
9652
|
];
|
|
10092
|
-
const choice = await
|
|
9653
|
+
const choice = await p6.select({
|
|
10093
9654
|
message,
|
|
10094
|
-
options: commonPaths.map((
|
|
10095
|
-
value:
|
|
10096
|
-
label:
|
|
10097
|
-
hint:
|
|
9655
|
+
options: commonPaths.map((p8) => ({
|
|
9656
|
+
value: p8.value,
|
|
9657
|
+
label: p8.label,
|
|
9658
|
+
hint: p8.value !== "__custom__" ? p8.value : void 0
|
|
10098
9659
|
}))
|
|
10099
9660
|
});
|
|
10100
|
-
if (
|
|
9661
|
+
if (p6.isCancel(choice)) {
|
|
10101
9662
|
return null;
|
|
10102
9663
|
}
|
|
10103
9664
|
if (choice === "__custom__") {
|
|
10104
|
-
const customPath = await
|
|
9665
|
+
const customPath = await p6.text({
|
|
10105
9666
|
message: "Enter folder path:",
|
|
10106
9667
|
placeholder: "/path/to/folder",
|
|
10107
9668
|
validate: (value) => {
|
|
@@ -10109,7 +9670,7 @@ async function manualFolderEntry(message) {
|
|
|
10109
9670
|
return void 0;
|
|
10110
9671
|
}
|
|
10111
9672
|
});
|
|
10112
|
-
if (
|
|
9673
|
+
if (p6.isCancel(customPath)) {
|
|
10113
9674
|
return null;
|
|
10114
9675
|
}
|
|
10115
9676
|
let cleanPath = customPath.trim();
|
|
@@ -10122,8 +9683,8 @@ async function manualFolderEntry(message) {
|
|
|
10122
9683
|
|
|
10123
9684
|
// src/services/git.ts
|
|
10124
9685
|
import simpleGit from "simple-git";
|
|
10125
|
-
import
|
|
10126
|
-
import
|
|
9686
|
+
import fs16 from "fs-extra";
|
|
9687
|
+
import path17 from "path";
|
|
10127
9688
|
var GitService = class {
|
|
10128
9689
|
git;
|
|
10129
9690
|
projectPath;
|
|
@@ -10132,7 +9693,7 @@ var GitService = class {
|
|
|
10132
9693
|
this.git = simpleGit(projectPath);
|
|
10133
9694
|
}
|
|
10134
9695
|
async isRepo() {
|
|
10135
|
-
return
|
|
9696
|
+
return fs16.pathExists(path17.join(this.projectPath, ".git"));
|
|
10136
9697
|
}
|
|
10137
9698
|
async init() {
|
|
10138
9699
|
await this.git.init();
|
|
@@ -10243,7 +9804,7 @@ var VercelService = class {
|
|
|
10243
9804
|
};
|
|
10244
9805
|
|
|
10245
9806
|
// src/index.ts
|
|
10246
|
-
var VERSION = "
|
|
9807
|
+
var VERSION = "4.1.0";
|
|
10247
9808
|
async function main() {
|
|
10248
9809
|
const config = new Config();
|
|
10249
9810
|
const args = process.argv.slice(2);
|
|
@@ -10255,76 +9816,106 @@ async function main() {
|
|
|
10255
9816
|
showHelp();
|
|
10256
9817
|
return;
|
|
10257
9818
|
}
|
|
10258
|
-
if (args[0] === "setup") {
|
|
10259
|
-
await runSetup(config);
|
|
10260
|
-
return;
|
|
10261
|
-
}
|
|
10262
9819
|
if (args[0] === "login") {
|
|
10263
9820
|
await auth.login();
|
|
10264
9821
|
return;
|
|
10265
9822
|
}
|
|
9823
|
+
if (args[0] === "logout") {
|
|
9824
|
+
auth.logout();
|
|
9825
|
+
return;
|
|
9826
|
+
}
|
|
10266
9827
|
const memory = new Memory();
|
|
10267
9828
|
await memory.init();
|
|
10268
|
-
if (needsOnboarding(config, memory)) {
|
|
10269
|
-
const completed = await runOnboarding(config, memory);
|
|
10270
|
-
if (!completed) {
|
|
10271
|
-
console.log("");
|
|
10272
|
-
console.log(colors.muted(" Run `cb` again when you're ready."));
|
|
10273
|
-
console.log("");
|
|
10274
|
-
return;
|
|
10275
|
-
}
|
|
10276
|
-
}
|
|
10277
|
-
showHeader();
|
|
10278
|
-
console.log(colors.muted(` v${VERSION}`));
|
|
10279
|
-
console.log("");
|
|
10280
9829
|
const scanner = new ProjectScanner();
|
|
10281
9830
|
const structure = await scanner.detectProject();
|
|
10282
9831
|
const editor = new FileEditor();
|
|
10283
9832
|
const git = new GitService();
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
console.log(colors.white(
|
|
10287
|
-
|
|
10288
|
-
console.log(colors.
|
|
10289
|
-
console.log(
|
|
9833
|
+
if (!config.isConfigured() && !auth.isLoggedIn()) {
|
|
9834
|
+
console.log("");
|
|
9835
|
+
console.log(colors.white(" Welcome to CodeBakers"));
|
|
9836
|
+
console.log("");
|
|
9837
|
+
console.log(colors.muted(" You need an API key to continue."));
|
|
9838
|
+
console.log("");
|
|
9839
|
+
const apiKey = await p7.text({
|
|
9840
|
+
message: "Anthropic API Key:",
|
|
9841
|
+
placeholder: "sk-ant-api..."
|
|
9842
|
+
});
|
|
9843
|
+
if (p7.isCancel(apiKey) || !apiKey) {
|
|
9844
|
+
console.log("");
|
|
9845
|
+
console.log(colors.muted(" Get your key at: https://console.anthropic.com"));
|
|
9846
|
+
console.log(colors.muted(" Then run: cb"));
|
|
9847
|
+
console.log("");
|
|
9848
|
+
return;
|
|
9849
|
+
}
|
|
9850
|
+
config.setAnthropicKey(apiKey);
|
|
9851
|
+
console.log(colors.success(" \u2713 API key saved"));
|
|
9852
|
+
console.log("");
|
|
10290
9853
|
}
|
|
10291
|
-
console.log("");
|
|
10292
9854
|
let ai;
|
|
10293
9855
|
try {
|
|
10294
9856
|
ai = new AIEngine(config, memory, scanner);
|
|
10295
9857
|
} catch (error) {
|
|
10296
|
-
|
|
9858
|
+
console.log("");
|
|
9859
|
+
console.log(colors.error(` \u2717 ${error instanceof Error ? error.message : "Failed to start"}`));
|
|
9860
|
+
console.log("");
|
|
9861
|
+
console.log(colors.muted(" Check your API key with: cb login"));
|
|
9862
|
+
console.log("");
|
|
10297
9863
|
return;
|
|
10298
9864
|
}
|
|
10299
9865
|
const nlp2 = new NLPInterpreter();
|
|
10300
9866
|
const modeManager = getModeManager();
|
|
10301
9867
|
const suggestions = new SmartSuggestions(memory, scanner);
|
|
10302
9868
|
const proactive = new ProactiveAssistant(memory, scanner, config);
|
|
10303
|
-
console.
|
|
10304
|
-
console.log(
|
|
9869
|
+
console.clear();
|
|
9870
|
+
console.log("");
|
|
9871
|
+
console.log(colors.muted(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
|
|
9872
|
+
console.log(colors.muted(" \u2502") + colors.primary(" \u2B21 C O D E B A K E R S ") + colors.muted("\u2502"));
|
|
9873
|
+
console.log(colors.muted(" \u2502") + colors.dim(` v${VERSION} \u2022 58 patterns \u2022 auto-parallel `) + colors.muted("\u2502"));
|
|
9874
|
+
console.log(colors.muted(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
10305
9875
|
console.log("");
|
|
9876
|
+
console.log(colors.muted(" \u{1F4C1} ") + colors.white(structure.name) + (structure.framework ? colors.dim(` (${structure.framework})`) : ""));
|
|
9877
|
+
console.log("");
|
|
9878
|
+
console.log(colors.dim(" Just tell me what you want to build or do."));
|
|
9879
|
+
console.log(colors.dim(" Type /help to see examples."));
|
|
9880
|
+
console.log("");
|
|
9881
|
+
const terminalHeight = process.stdout.rows || 24;
|
|
9882
|
+
const paddingLines = Math.max(0, terminalHeight - 14);
|
|
9883
|
+
console.log("\n".repeat(paddingLines));
|
|
10306
9884
|
let lastAction;
|
|
10307
9885
|
let running = true;
|
|
10308
9886
|
while (running) {
|
|
10309
|
-
const
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
placeholder: "What would you like to build? (Shift+Tab to change mode)"
|
|
9887
|
+
const input = await p7.text({
|
|
9888
|
+
message: colors.primary(">"),
|
|
9889
|
+
placeholder: 'e.g. "Add a login page" or "Check my code"'
|
|
10313
9890
|
});
|
|
10314
|
-
if (
|
|
9891
|
+
if (p7.isCancel(input)) {
|
|
10315
9892
|
running = false;
|
|
10316
|
-
|
|
9893
|
+
console.log("");
|
|
9894
|
+
console.log(colors.muted(" Goodbye! \u{1F44B}"));
|
|
9895
|
+
console.log("");
|
|
10317
9896
|
break;
|
|
10318
9897
|
}
|
|
10319
9898
|
const cmd = input.trim();
|
|
10320
9899
|
if (!cmd) continue;
|
|
10321
|
-
const
|
|
10322
|
-
if (
|
|
10323
|
-
console.log(suggestions.formatSuggestion(
|
|
9900
|
+
const smartSuggestion = await suggestions.checkForSuggestions(cmd, lastAction);
|
|
9901
|
+
if (smartSuggestion) {
|
|
9902
|
+
console.log(suggestions.formatSuggestion(smartSuggestion));
|
|
10324
9903
|
}
|
|
10325
9904
|
const result = await nlp2.interpret(cmd);
|
|
10326
9905
|
if (result.type === "unclear") {
|
|
10327
|
-
|
|
9906
|
+
console.log("");
|
|
9907
|
+
console.log(colors.muted(" I'm not sure what you want to do."));
|
|
9908
|
+
console.log("");
|
|
9909
|
+
const cmdSuggestions = getSuggestions(cmd);
|
|
9910
|
+
if (cmdSuggestions.length > 0) {
|
|
9911
|
+
console.log(colors.muted(" Did you mean:"));
|
|
9912
|
+
cmdSuggestions.forEach((s) => {
|
|
9913
|
+
console.log(colors.dim(` \u2022 ${s}`));
|
|
9914
|
+
});
|
|
9915
|
+
console.log("");
|
|
9916
|
+
}
|
|
9917
|
+
console.log(colors.dim(" Type /help for a list of commands"));
|
|
9918
|
+
console.log("");
|
|
10328
9919
|
continue;
|
|
10329
9920
|
}
|
|
10330
9921
|
if (["undo", "clear", "deploy"].includes(result.type)) {
|
|
@@ -10335,7 +9926,9 @@ async function main() {
|
|
|
10335
9926
|
switch (result.type) {
|
|
10336
9927
|
case "quit":
|
|
10337
9928
|
running = false;
|
|
10338
|
-
|
|
9929
|
+
console.log("");
|
|
9930
|
+
console.log(colors.muted(" Goodbye! \u{1F44B}"));
|
|
9931
|
+
console.log("");
|
|
10339
9932
|
break;
|
|
10340
9933
|
case "help":
|
|
10341
9934
|
showCommandHelp();
|
|
@@ -10368,9 +9961,13 @@ async function main() {
|
|
|
10368
9961
|
await memory.save();
|
|
10369
9962
|
showSuccess(`Decision recorded: ${result.params.content}`);
|
|
10370
9963
|
} else {
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
9964
|
+
const decision = await p7.text({
|
|
9965
|
+
message: "What decision would you like to record?",
|
|
9966
|
+
placeholder: "e.g., Use Supabase for auth"
|
|
9967
|
+
});
|
|
9968
|
+
if (p7.isCancel(decision)) {
|
|
9969
|
+
console.log(colors.muted(" Cancelled"));
|
|
9970
|
+
} else if (decision) {
|
|
10374
9971
|
memory.addDecision(decision);
|
|
10375
9972
|
await memory.save();
|
|
10376
9973
|
showSuccess(`Decision recorded: ${decision}`);
|
|
@@ -10383,9 +9980,13 @@ async function main() {
|
|
|
10383
9980
|
await memory.save();
|
|
10384
9981
|
showSuccess(`Rule added: ${result.params.content}`);
|
|
10385
9982
|
} else {
|
|
10386
|
-
|
|
10387
|
-
|
|
10388
|
-
|
|
9983
|
+
const rule = await p7.text({
|
|
9984
|
+
message: "What rule would you like to add?",
|
|
9985
|
+
placeholder: "e.g., Always use TypeScript strict mode"
|
|
9986
|
+
});
|
|
9987
|
+
if (p7.isCancel(rule)) {
|
|
9988
|
+
console.log(colors.muted(" Cancelled"));
|
|
9989
|
+
} else if (rule) {
|
|
10389
9990
|
memory.addCustomRule(rule);
|
|
10390
9991
|
await memory.save();
|
|
10391
9992
|
showSuccess(`Rule added: ${rule}`);
|
|
@@ -10401,8 +10002,10 @@ async function main() {
|
|
|
10401
10002
|
if (result.params?.command) {
|
|
10402
10003
|
await handleRunCommand(result.params.command, ctx);
|
|
10403
10004
|
} else {
|
|
10404
|
-
|
|
10405
|
-
|
|
10005
|
+
console.log("");
|
|
10006
|
+
console.log(colors.muted(" Usage: /run <command>"));
|
|
10007
|
+
console.log(colors.dim(" Example: /run npm install lodash"));
|
|
10008
|
+
console.log("");
|
|
10406
10009
|
}
|
|
10407
10010
|
break;
|
|
10408
10011
|
case "create":
|
|
@@ -10459,67 +10062,166 @@ async function main() {
|
|
|
10459
10062
|
const detected = await inputHandler.detectAndProcess(cmd);
|
|
10460
10063
|
if (detected.type !== "text") {
|
|
10461
10064
|
console.log(inputHandler.formatDetection(detected));
|
|
10462
|
-
const proceed = await
|
|
10065
|
+
const proceed = await p7.confirm({
|
|
10463
10066
|
message: `Process this ${detected.type}?`,
|
|
10464
10067
|
initialValue: true
|
|
10465
10068
|
});
|
|
10466
|
-
if (!proceed ||
|
|
10069
|
+
if (!proceed || p7.isCancel(proceed)) {
|
|
10070
|
+
console.log(colors.muted(" Cancelled"));
|
|
10071
|
+
break;
|
|
10072
|
+
}
|
|
10467
10073
|
const enhancedCmd = inputHandler.enhancePrompt(detected, cmd);
|
|
10468
|
-
await
|
|
10074
|
+
await handleAutomaticBuild(enhancedCmd, ctx);
|
|
10469
10075
|
break;
|
|
10470
10076
|
}
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
} else if (currentMode === "question") {
|
|
10475
|
-
await handleQuestionMode(cmd, ctx);
|
|
10476
|
-
} else if (currentMode === "agent") {
|
|
10477
|
-
await handleAgentMode(cmd, ctx);
|
|
10478
|
-
} else {
|
|
10479
|
-
const isComplex = detectComplexRequest(cmd);
|
|
10480
|
-
if (isComplex) {
|
|
10481
|
-
console.log("");
|
|
10482
|
-
console.log(colors.secondary(" \u{1F4A1} This looks like a complex request."));
|
|
10483
|
-
console.log(colors.muted(" Parallel build could be ~3x faster."));
|
|
10484
|
-
console.log("");
|
|
10485
|
-
const useParallel = await p8.confirm({
|
|
10486
|
-
message: "Use parallel build with 3 agents?",
|
|
10487
|
-
initialValue: true
|
|
10488
|
-
});
|
|
10489
|
-
if (useParallel && !p8.isCancel(useParallel)) {
|
|
10490
|
-
await handleParallelBuild(cmd, ctx);
|
|
10491
|
-
break;
|
|
10492
|
-
}
|
|
10493
|
-
}
|
|
10494
|
-
const templateManager = new TemplateManager();
|
|
10495
|
-
const suggestedTemplate = templateManager.suggestTemplate(cmd);
|
|
10496
|
-
if (suggestedTemplate) {
|
|
10497
|
-
console.log("");
|
|
10498
|
-
console.log(colors.secondary(` \u{1F4A1} I recommend the "${suggestedTemplate.name}" template.`));
|
|
10499
|
-
console.log(colors.muted(` ${suggestedTemplate.description}`));
|
|
10500
|
-
console.log("");
|
|
10501
|
-
const useTemplate = await p8.confirm({
|
|
10502
|
-
message: "Install this template first?",
|
|
10503
|
-
initialValue: true
|
|
10504
|
-
});
|
|
10505
|
-
if (useTemplate && !p8.isCancel(useTemplate)) {
|
|
10506
|
-
await handleInstallTemplate(suggestedTemplate.id, ctx);
|
|
10507
|
-
showSuccess("Template installed! Now customizing...");
|
|
10508
|
-
}
|
|
10509
|
-
}
|
|
10510
|
-
await handleGeneration(cmd, ctx);
|
|
10511
|
-
const filesCreated = editor.getPendingChanges().length;
|
|
10512
|
-
const followUp = await proactive.onAfterAction(cmd, filesCreated);
|
|
10513
|
-
if (followUp) {
|
|
10514
|
-
console.log(proactive.formatAction(followUp));
|
|
10515
|
-
proactive.markSuggestionShown();
|
|
10516
|
-
}
|
|
10517
|
-
}
|
|
10077
|
+
await handleAutomaticBuild(cmd, ctx);
|
|
10078
|
+
lastAction = cmd;
|
|
10079
|
+
break;
|
|
10518
10080
|
break;
|
|
10519
10081
|
}
|
|
10520
10082
|
proactive.updateLastActivity();
|
|
10521
10083
|
}
|
|
10522
10084
|
}
|
|
10085
|
+
async function handleAutomaticBuild(request, ctx) {
|
|
10086
|
+
const { config, memory, scanner, editor, ai } = ctx;
|
|
10087
|
+
memory.addMessage("user", request);
|
|
10088
|
+
memory.setActiveTask(request);
|
|
10089
|
+
await memory.createSnapshot(`Before: ${request.slice(0, 50)}`);
|
|
10090
|
+
const tasks = analyzeAndSplitTasks(request);
|
|
10091
|
+
if (tasks.length > 1) {
|
|
10092
|
+
console.log("");
|
|
10093
|
+
console.log(colors.muted(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
|
|
10094
|
+
console.log(colors.muted(" \u2502") + colors.primary(" \u26A1 Parallel Build ") + colors.muted("\u2502"));
|
|
10095
|
+
console.log(colors.muted(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
|
|
10096
|
+
tasks.forEach((task, i) => {
|
|
10097
|
+
const taskText = task.slice(0, 43);
|
|
10098
|
+
console.log(colors.muted(" \u2502") + colors.dim(` ${i + 1}. ${taskText}`.padEnd(47)) + colors.muted("\u2502"));
|
|
10099
|
+
});
|
|
10100
|
+
console.log(colors.muted(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
10101
|
+
console.log("");
|
|
10102
|
+
const hasParallel = await auth.checkFeature(FEATURES.parallel);
|
|
10103
|
+
if (hasParallel) {
|
|
10104
|
+
await runParallelBuild(tasks, ctx);
|
|
10105
|
+
} else {
|
|
10106
|
+
console.log(colors.dim(" Running sequentially (parallel requires Pro)..."));
|
|
10107
|
+
console.log("");
|
|
10108
|
+
for (const task of tasks) {
|
|
10109
|
+
await handleGeneration(task, ctx);
|
|
10110
|
+
}
|
|
10111
|
+
}
|
|
10112
|
+
} else {
|
|
10113
|
+
await handleGeneration(request, ctx);
|
|
10114
|
+
}
|
|
10115
|
+
}
|
|
10116
|
+
function analyzeAndSplitTasks(request) {
|
|
10117
|
+
const lowered = request.toLowerCase();
|
|
10118
|
+
const multiIndicators = [
|
|
10119
|
+
/\band\b/i,
|
|
10120
|
+
// "login and dashboard"
|
|
10121
|
+
/\+/,
|
|
10122
|
+
// "login + dashboard"
|
|
10123
|
+
/,\s*(?:and\s+)?/,
|
|
10124
|
+
// "login, dashboard, settings"
|
|
10125
|
+
/\bthen\b/i,
|
|
10126
|
+
// "login then dashboard"
|
|
10127
|
+
/\balso\b/i,
|
|
10128
|
+
// "also add settings"
|
|
10129
|
+
/\bplus\b/i
|
|
10130
|
+
// "plus a settings page"
|
|
10131
|
+
];
|
|
10132
|
+
const hasMultiIndicator = multiIndicators.some((r) => r.test(request));
|
|
10133
|
+
if (!hasMultiIndicator) {
|
|
10134
|
+
return [request];
|
|
10135
|
+
}
|
|
10136
|
+
let tasks = request.split(/(?:\band\b|\+|,\s*(?:and\s+)?|\bthen\b|\balso\b|\bplus\b)/i).map((t) => t.trim()).filter((t) => t.length > 3);
|
|
10137
|
+
if (tasks.length <= 1) {
|
|
10138
|
+
return [request];
|
|
10139
|
+
}
|
|
10140
|
+
if (tasks.length > 4) {
|
|
10141
|
+
const first3 = tasks.slice(0, 3);
|
|
10142
|
+
const rest = tasks.slice(3).join(" and ");
|
|
10143
|
+
tasks = [...first3, rest];
|
|
10144
|
+
}
|
|
10145
|
+
return tasks;
|
|
10146
|
+
}
|
|
10147
|
+
async function runParallelBuild(tasks, ctx) {
|
|
10148
|
+
const { config, memory, scanner, editor } = ctx;
|
|
10149
|
+
if (!await auth.checkFeature(FEATURES.parallel)) {
|
|
10150
|
+
return;
|
|
10151
|
+
}
|
|
10152
|
+
const parallelSystem = new ParallelAgentSystem(config, memory, scanner, editor);
|
|
10153
|
+
let cancelled = false;
|
|
10154
|
+
const checkCancelled = () => cancelled;
|
|
10155
|
+
const onKeypress = (key) => {
|
|
10156
|
+
const str = key.toString();
|
|
10157
|
+
if (str === "\x1B" || str === "\x1B") {
|
|
10158
|
+
cancelled = true;
|
|
10159
|
+
parallelSystem.cancel();
|
|
10160
|
+
}
|
|
10161
|
+
};
|
|
10162
|
+
if (process.stdin.isTTY) {
|
|
10163
|
+
process.stdin.setRawMode(true);
|
|
10164
|
+
}
|
|
10165
|
+
process.stdin.resume();
|
|
10166
|
+
process.stdin.on("data", onKeypress);
|
|
10167
|
+
console.log(colors.muted(" Press ESC to cancel"));
|
|
10168
|
+
console.log("");
|
|
10169
|
+
try {
|
|
10170
|
+
const startTime = Date.now();
|
|
10171
|
+
const combinedRequest = tasks.join(" AND ");
|
|
10172
|
+
const result = await parallelSystem.build(
|
|
10173
|
+
combinedRequest,
|
|
10174
|
+
{
|
|
10175
|
+
onTaskPlan: (plannedTasks) => {
|
|
10176
|
+
console.log(colors.muted(` Planned ${plannedTasks.length} parallel tasks`));
|
|
10177
|
+
},
|
|
10178
|
+
onAgentStart: (taskId, name) => {
|
|
10179
|
+
console.log(colors.dim(` \u2192 Starting: ${name}`));
|
|
10180
|
+
},
|
|
10181
|
+
onAgentComplete: (taskId, agentResult) => {
|
|
10182
|
+
const status = agentResult.success ? colors.success("\u2713") : colors.error("\u2717");
|
|
10183
|
+
console.log(` ${status} ${taskId}: ${agentResult.files.length} files`);
|
|
10184
|
+
}
|
|
10185
|
+
},
|
|
10186
|
+
checkCancelled
|
|
10187
|
+
);
|
|
10188
|
+
process.stdin.removeListener("data", onKeypress);
|
|
10189
|
+
if (process.stdin.isTTY) {
|
|
10190
|
+
process.stdin.setRawMode(false);
|
|
10191
|
+
}
|
|
10192
|
+
const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
10193
|
+
console.log("");
|
|
10194
|
+
if (result.cancelled) {
|
|
10195
|
+
console.log(colors.muted(" Cancelled"));
|
|
10196
|
+
} else if (result.success) {
|
|
10197
|
+
console.log(colors.success(` \u2713 Completed in ${duration}s`));
|
|
10198
|
+
console.log(colors.muted(` Files: ${result.totalFiles}`));
|
|
10199
|
+
if (result.mergeConflicts.length > 0) {
|
|
10200
|
+
console.log(colors.warning(` Conflicts: ${result.mergeConflicts.length}`));
|
|
10201
|
+
}
|
|
10202
|
+
} else {
|
|
10203
|
+
console.log(colors.error(" Build failed"));
|
|
10204
|
+
result.results.forEach((r) => {
|
|
10205
|
+
if (!r.success && r.errors.length > 0) {
|
|
10206
|
+
r.errors.forEach((e) => console.log(colors.dim(` \u2022 ${e}`)));
|
|
10207
|
+
}
|
|
10208
|
+
});
|
|
10209
|
+
}
|
|
10210
|
+
console.log("");
|
|
10211
|
+
} catch (error) {
|
|
10212
|
+
process.stdin.removeListener("data", onKeypress);
|
|
10213
|
+
if (process.stdin.isTTY) {
|
|
10214
|
+
process.stdin.setRawMode(false);
|
|
10215
|
+
}
|
|
10216
|
+
if (cancelled) {
|
|
10217
|
+
console.log("");
|
|
10218
|
+
console.log(colors.muted(" Cancelled"));
|
|
10219
|
+
console.log("");
|
|
10220
|
+
return;
|
|
10221
|
+
}
|
|
10222
|
+
showError(error instanceof Error ? error.message : "Parallel build failed");
|
|
10223
|
+
}
|
|
10224
|
+
}
|
|
10523
10225
|
async function handleGeneration(request, ctx) {
|
|
10524
10226
|
const { memory, editor, ai, scanner } = ctx;
|
|
10525
10227
|
memory.addMessage("user", request);
|
|
@@ -10559,31 +10261,46 @@ async function handleGeneration(request, ctx) {
|
|
|
10559
10261
|
return;
|
|
10560
10262
|
}
|
|
10561
10263
|
console.log("");
|
|
10264
|
+
console.log(colors.muted(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
|
|
10562
10265
|
if (result.filesCreated.length > 0) {
|
|
10563
|
-
console.log(colors.success(`
|
|
10564
|
-
for (const f of result.filesCreated) {
|
|
10565
|
-
console.log(colors.muted(`
|
|
10266
|
+
console.log(colors.muted(" \u2502") + colors.success(` \u2713 Created ${result.filesCreated.length} file(s)`.padEnd(47)) + colors.muted("\u2502"));
|
|
10267
|
+
for (const f of result.filesCreated.slice(0, 5)) {
|
|
10268
|
+
console.log(colors.muted(" \u2502") + colors.dim(` + ${f}`.padEnd(47)) + colors.muted("\u2502"));
|
|
10269
|
+
}
|
|
10270
|
+
if (result.filesCreated.length > 5) {
|
|
10271
|
+
console.log(colors.muted(" \u2502") + colors.dim(` ... and ${result.filesCreated.length - 5} more`.padEnd(47)) + colors.muted("\u2502"));
|
|
10566
10272
|
}
|
|
10567
10273
|
}
|
|
10568
10274
|
if (result.filesModified.length > 0) {
|
|
10569
|
-
console.log(colors.
|
|
10570
|
-
for (const f of result.filesModified) {
|
|
10571
|
-
console.log(colors.muted(`
|
|
10275
|
+
console.log(colors.muted(" \u2502") + colors.warning(` ~ Modified ${result.filesModified.length} file(s)`.padEnd(47)) + colors.muted("\u2502"));
|
|
10276
|
+
for (const f of result.filesModified.slice(0, 5)) {
|
|
10277
|
+
console.log(colors.muted(" \u2502") + colors.dim(` ~ ${f}`.padEnd(47)) + colors.muted("\u2502"));
|
|
10278
|
+
}
|
|
10279
|
+
if (result.filesModified.length > 5) {
|
|
10280
|
+
console.log(colors.muted(" \u2502") + colors.dim(` ... and ${result.filesModified.length - 5} more`.padEnd(47)) + colors.muted("\u2502"));
|
|
10572
10281
|
}
|
|
10573
10282
|
}
|
|
10574
10283
|
if (result.commandsRun.length > 0) {
|
|
10575
|
-
console.log(colors.muted(
|
|
10284
|
+
console.log(colors.muted(" \u2502") + colors.dim(` \u26A1 Ran: ${result.commandsRun.join(", ")}`.padEnd(47)) + colors.muted("\u2502"));
|
|
10576
10285
|
}
|
|
10577
10286
|
if (result.errors.length > 0) {
|
|
10578
|
-
console.log("");
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
console.log(colors.error(` \u2022 ${e}`));
|
|
10287
|
+
console.log(colors.muted(" \u2502") + colors.error(` \u2717 ${result.errors.length} error(s)`.padEnd(47)) + colors.muted("\u2502"));
|
|
10288
|
+
for (const e of result.errors.slice(0, 3)) {
|
|
10289
|
+
console.log(colors.muted(" \u2502") + colors.dim(` ${e.slice(0, 43)}`.padEnd(47)) + colors.muted("\u2502"));
|
|
10582
10290
|
}
|
|
10583
10291
|
}
|
|
10292
|
+
console.log(colors.muted(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
10584
10293
|
if (result.success) {
|
|
10585
10294
|
console.log("");
|
|
10586
|
-
|
|
10295
|
+
console.log(colors.success(" \u2713 Done!"));
|
|
10296
|
+
const totalFiles = result.filesCreated.length + result.filesModified.length;
|
|
10297
|
+
if (totalFiles > 0) {
|
|
10298
|
+
console.log("");
|
|
10299
|
+
console.log(colors.dim(" What's next?"));
|
|
10300
|
+
console.log(colors.dim(' \u2022 "Check my code for issues"'));
|
|
10301
|
+
console.log(colors.dim(' \u2022 "Generate tests"'));
|
|
10302
|
+
console.log(colors.dim(' \u2022 "Commit my changes"'));
|
|
10303
|
+
}
|
|
10587
10304
|
}
|
|
10588
10305
|
console.log("");
|
|
10589
10306
|
memory.addMessage("assistant", `Created: ${result.filesCreated.join(", ")}. Modified: ${result.filesModified.join(", ")}`);
|
|
@@ -10641,7 +10358,7 @@ async function handleCreate(name, ctx) {
|
|
|
10641
10358
|
const { config } = ctx;
|
|
10642
10359
|
let projectName = name;
|
|
10643
10360
|
if (!projectName) {
|
|
10644
|
-
const nameInput = await
|
|
10361
|
+
const nameInput = await p7.text({
|
|
10645
10362
|
message: "Project name:",
|
|
10646
10363
|
placeholder: "my-awesome-app",
|
|
10647
10364
|
validate: (value) => {
|
|
@@ -10649,23 +10366,23 @@ async function handleCreate(name, ctx) {
|
|
|
10649
10366
|
if (!/^[a-z0-9-]+$/.test(value)) return "Use lowercase letters, numbers, and hyphens only";
|
|
10650
10367
|
}
|
|
10651
10368
|
});
|
|
10652
|
-
if (
|
|
10369
|
+
if (p7.isCancel(nameInput)) return;
|
|
10653
10370
|
projectName = nameInput;
|
|
10654
10371
|
}
|
|
10655
10372
|
console.log("");
|
|
10656
|
-
const github = await
|
|
10373
|
+
const github = await p7.confirm({
|
|
10657
10374
|
message: "Create GitHub repository?",
|
|
10658
10375
|
initialValue: !!config.getGithubToken()
|
|
10659
10376
|
});
|
|
10660
|
-
const supabase = await
|
|
10377
|
+
const supabase = await p7.confirm({
|
|
10661
10378
|
message: "Create Supabase project?",
|
|
10662
10379
|
initialValue: !!config.getSupabaseToken()
|
|
10663
10380
|
});
|
|
10664
|
-
const vercel = await
|
|
10381
|
+
const vercel = await p7.confirm({
|
|
10665
10382
|
message: "Deploy to Vercel?",
|
|
10666
10383
|
initialValue: !!config.getVercelToken()
|
|
10667
10384
|
});
|
|
10668
|
-
if (
|
|
10385
|
+
if (p7.isCancel(github) || p7.isCancel(supabase) || p7.isCancel(vercel)) return;
|
|
10669
10386
|
const creator = new ProjectCreator(config);
|
|
10670
10387
|
const spinner = ora3("Creating project...").start();
|
|
10671
10388
|
const result = await creator.create(
|
|
@@ -10726,14 +10443,17 @@ async function handleInstallTemplate(templateId, ctx) {
|
|
|
10726
10443
|
let id = templateId;
|
|
10727
10444
|
if (!id) {
|
|
10728
10445
|
const templates = templateManager.listTemplates();
|
|
10729
|
-
const choice = await
|
|
10446
|
+
const choice = await p7.select({
|
|
10730
10447
|
message: "Choose a template:",
|
|
10731
10448
|
options: templates.map((t) => ({
|
|
10732
10449
|
value: t.id,
|
|
10733
10450
|
label: `${t.name} - ${t.description}`
|
|
10734
10451
|
}))
|
|
10735
10452
|
});
|
|
10736
|
-
if (
|
|
10453
|
+
if (p7.isCancel(choice)) {
|
|
10454
|
+
console.log(colors.muted(" Cancelled"));
|
|
10455
|
+
return;
|
|
10456
|
+
}
|
|
10737
10457
|
id = choice;
|
|
10738
10458
|
}
|
|
10739
10459
|
const spinner = ora3(`Installing ${id}...`).start();
|
|
@@ -10809,11 +10529,11 @@ async function handleParallelBuild(request, ctx) {
|
|
|
10809
10529
|
console.log(colors.muted(` + ${change.path}`));
|
|
10810
10530
|
}
|
|
10811
10531
|
console.log("");
|
|
10812
|
-
const apply = await
|
|
10532
|
+
const apply = await p7.confirm({
|
|
10813
10533
|
message: "Apply all files?",
|
|
10814
10534
|
initialValue: true
|
|
10815
10535
|
});
|
|
10816
|
-
if (apply && !
|
|
10536
|
+
if (apply && !p7.isCancel(apply)) {
|
|
10817
10537
|
const applied = await editor.apply();
|
|
10818
10538
|
showSuccess(`Applied ${applied.length} files`);
|
|
10819
10539
|
for (const file of applied) {
|
|
@@ -10839,124 +10559,18 @@ async function handleParallelBuild(request, ctx) {
|
|
|
10839
10559
|
}
|
|
10840
10560
|
console.log("");
|
|
10841
10561
|
}
|
|
10842
|
-
function detectComplexRequest(request) {
|
|
10843
|
-
const lower = request.toLowerCase();
|
|
10844
|
-
const features = [
|
|
10845
|
-
"auth",
|
|
10846
|
-
"login",
|
|
10847
|
-
"signup",
|
|
10848
|
-
"authentication",
|
|
10849
|
-
"dashboard",
|
|
10850
|
-
"admin",
|
|
10851
|
-
"settings",
|
|
10852
|
-
"profile",
|
|
10853
|
-
"billing",
|
|
10854
|
-
"payment",
|
|
10855
|
-
"stripe",
|
|
10856
|
-
"subscription",
|
|
10857
|
-
"analytics",
|
|
10858
|
-
"charts",
|
|
10859
|
-
"users",
|
|
10860
|
-
"user management",
|
|
10861
|
-
"api",
|
|
10862
|
-
"backend",
|
|
10863
|
-
"database",
|
|
10864
|
-
"crud"
|
|
10865
|
-
];
|
|
10866
|
-
let featureCount = 0;
|
|
10867
|
-
for (const feature of features) {
|
|
10868
|
-
if (lower.includes(feature)) {
|
|
10869
|
-
featureCount++;
|
|
10870
|
-
}
|
|
10871
|
-
}
|
|
10872
|
-
const hasMultiple = lower.includes(" and ") || lower.includes(" with ") || lower.includes(", ") || lower.includes(" + ");
|
|
10873
|
-
const scopeKeywords = ["full", "complete", "entire", "whole", "saas", "app", "application", "system"];
|
|
10874
|
-
const hasScope = scopeKeywords.some((k) => lower.includes(k));
|
|
10875
|
-
return featureCount >= 3 || featureCount >= 2 && hasMultiple || hasScope && featureCount >= 2;
|
|
10876
|
-
}
|
|
10877
|
-
async function handlePlanMode(request, ctx) {
|
|
10878
|
-
const { config, memory, scanner } = ctx;
|
|
10879
|
-
console.log("");
|
|
10880
|
-
console.log(colors.primary(" \u{1F4CB} PLAN MODE") + colors.muted(" - Showing what will happen"));
|
|
10881
|
-
console.log("");
|
|
10882
|
-
const planExecutor = new PlanExecutor(config, memory, scanner);
|
|
10883
|
-
const spinner = ora3("Creating plan...").start();
|
|
10884
|
-
try {
|
|
10885
|
-
const plan = await planExecutor.createPlan(request);
|
|
10886
|
-
spinner.stop();
|
|
10887
|
-
console.log(planExecutor.formatPlan(plan));
|
|
10888
|
-
const execute = await p8.confirm({
|
|
10889
|
-
message: "Execute this plan?",
|
|
10890
|
-
initialValue: true
|
|
10891
|
-
});
|
|
10892
|
-
if (execute && !p8.isCancel(execute)) {
|
|
10893
|
-
await handleGeneration(request, ctx);
|
|
10894
|
-
}
|
|
10895
|
-
} catch (error) {
|
|
10896
|
-
spinner.stop();
|
|
10897
|
-
showError(error instanceof Error ? error.message : "Plan failed");
|
|
10898
|
-
}
|
|
10899
|
-
}
|
|
10900
|
-
async function handleQuestionMode(request, ctx) {
|
|
10901
|
-
const { config, memory, scanner } = ctx;
|
|
10902
|
-
const questionExecutor = new QuestionExecutor(config, memory, scanner);
|
|
10903
|
-
try {
|
|
10904
|
-
const questions = await questionExecutor.generateQuestions(request);
|
|
10905
|
-
const result = await questionExecutor.askQuestions(questions);
|
|
10906
|
-
if (Object.keys(result.answers).length > 0) {
|
|
10907
|
-
questionExecutor.showSummary(result);
|
|
10908
|
-
const enhancedRequest = request + questionExecutor.formatAnswersForPrompt(result);
|
|
10909
|
-
await handleGeneration(enhancedRequest, ctx);
|
|
10910
|
-
} else {
|
|
10911
|
-
await handleGeneration(request, ctx);
|
|
10912
|
-
}
|
|
10913
|
-
} catch (error) {
|
|
10914
|
-
showError(error instanceof Error ? error.message : "Question mode failed");
|
|
10915
|
-
}
|
|
10916
|
-
}
|
|
10917
|
-
async function handleAgentMode(request, ctx) {
|
|
10918
|
-
const { config, ai, memory, scanner, editor } = ctx;
|
|
10919
|
-
await memory.createSnapshot(`Before agent: ${request.slice(0, 50)}`);
|
|
10920
|
-
let cancelled = false;
|
|
10921
|
-
const checkCancelled = () => cancelled;
|
|
10922
|
-
const onKeypress = (key) => {
|
|
10923
|
-
const str = key.toString();
|
|
10924
|
-
if (str === "\x1B" || str === "\x1B") {
|
|
10925
|
-
cancelled = true;
|
|
10926
|
-
}
|
|
10927
|
-
};
|
|
10928
|
-
if (process.stdin.isTTY) {
|
|
10929
|
-
process.stdin.setRawMode(true);
|
|
10930
|
-
}
|
|
10931
|
-
process.stdin.resume();
|
|
10932
|
-
process.stdin.on("data", onKeypress);
|
|
10933
|
-
const agentExecutor = new AgentExecutor(config, ai, memory, scanner, editor);
|
|
10934
|
-
try {
|
|
10935
|
-
const result = await agentExecutor.execute(request, { checkCancelled });
|
|
10936
|
-
process.stdin.removeListener("data", onKeypress);
|
|
10937
|
-
if (process.stdin.isTTY) {
|
|
10938
|
-
process.stdin.setRawMode(false);
|
|
10939
|
-
}
|
|
10940
|
-
if (result.cancelled) {
|
|
10941
|
-
showWarning("Cancelled.");
|
|
10942
|
-
}
|
|
10943
|
-
} catch (error) {
|
|
10944
|
-
process.stdin.removeListener("data", onKeypress);
|
|
10945
|
-
if (process.stdin.isTTY) {
|
|
10946
|
-
process.stdin.setRawMode(false);
|
|
10947
|
-
}
|
|
10948
|
-
showError(error instanceof Error ? error.message : "Agent mode failed");
|
|
10949
|
-
}
|
|
10950
|
-
}
|
|
10951
10562
|
async function handleTestGeneration(filePath, ctx) {
|
|
10952
10563
|
const { config, scanner, editor } = ctx;
|
|
10953
10564
|
const testGen = new TestGenerator(config, scanner);
|
|
10954
10565
|
if (!filePath) {
|
|
10955
|
-
const file = await
|
|
10566
|
+
const file = await p7.text({
|
|
10956
10567
|
message: "File to generate tests for:",
|
|
10957
10568
|
placeholder: "src/components/Button.tsx"
|
|
10958
10569
|
});
|
|
10959
|
-
if (
|
|
10570
|
+
if (p7.isCancel(file) || !file) {
|
|
10571
|
+
console.log(colors.muted(" Cancelled"));
|
|
10572
|
+
return;
|
|
10573
|
+
}
|
|
10960
10574
|
filePath = file;
|
|
10961
10575
|
}
|
|
10962
10576
|
const spinner = ora3(`Generating tests for ${filePath}...`).start();
|
|
@@ -10973,13 +10587,17 @@ async function handleTestGeneration(filePath, ctx) {
|
|
|
10973
10587
|
console.log(colors.muted(" " + test.content.split("\n").slice(0, 10).join("\n ")));
|
|
10974
10588
|
console.log(colors.muted(" ..."));
|
|
10975
10589
|
console.log("");
|
|
10976
|
-
const save = await
|
|
10590
|
+
const save = await p7.confirm({
|
|
10977
10591
|
message: `Save to ${test.testPath}?`,
|
|
10978
10592
|
initialValue: true
|
|
10979
10593
|
});
|
|
10980
|
-
if (
|
|
10594
|
+
if (p7.isCancel(save)) {
|
|
10595
|
+
console.log(colors.muted(" Test not saved"));
|
|
10596
|
+
} else if (save) {
|
|
10981
10597
|
await testGen.writeTest(test);
|
|
10982
10598
|
showSuccess(`Test saved to ${test.testPath}`);
|
|
10599
|
+
} else {
|
|
10600
|
+
console.log(colors.muted(" Test not saved"));
|
|
10983
10601
|
}
|
|
10984
10602
|
} catch (error) {
|
|
10985
10603
|
spinner.stop();
|
|
@@ -11015,11 +10633,11 @@ async function handleLivePreview(ctx) {
|
|
|
11015
10633
|
const { scanner } = ctx;
|
|
11016
10634
|
const preview = new LivePreview(scanner);
|
|
11017
10635
|
if (preview.isRunning()) {
|
|
11018
|
-
const stop = await
|
|
10636
|
+
const stop = await p7.confirm({
|
|
11019
10637
|
message: "Preview is running. Stop it?",
|
|
11020
10638
|
initialValue: false
|
|
11021
10639
|
});
|
|
11022
|
-
if (stop && !
|
|
10640
|
+
if (stop && !p7.isCancel(stop)) {
|
|
11023
10641
|
await preview.stop();
|
|
11024
10642
|
showSuccess("Preview stopped");
|
|
11025
10643
|
} else {
|
|
@@ -11047,28 +10665,38 @@ async function handleSpecGeneration(input, ctx) {
|
|
|
11047
10665
|
return;
|
|
11048
10666
|
}
|
|
11049
10667
|
const { config, memory, scanner, editor, ai } = ctx;
|
|
10668
|
+
console.log("");
|
|
10669
|
+
console.log(colors.white(" \u{1F4DD} Spec Generator"));
|
|
10670
|
+
console.log(colors.muted(" \u2500".repeat(35)));
|
|
10671
|
+
console.log("");
|
|
11050
10672
|
let description = input.replace(/^\/spec\s*/i, "").replace(/^\/prd\s*/i, "").replace(/^\/plan\s*/i, "").trim();
|
|
11051
10673
|
if (!description) {
|
|
11052
|
-
const desc = await
|
|
10674
|
+
const desc = await p7.text({
|
|
11053
10675
|
message: "What do you want to build?",
|
|
11054
10676
|
placeholder: "An Uber for dog walkers"
|
|
11055
10677
|
});
|
|
11056
|
-
if (
|
|
10678
|
+
if (p7.isCancel(desc) || !desc) {
|
|
10679
|
+
console.log(colors.muted(" Spec generation cancelled"));
|
|
10680
|
+
console.log("");
|
|
10681
|
+
return;
|
|
10682
|
+
}
|
|
11057
10683
|
description = desc;
|
|
11058
10684
|
}
|
|
11059
10685
|
const specGen = new SpecGenerator(config);
|
|
11060
10686
|
try {
|
|
11061
10687
|
const spec = await specGen.generate(description);
|
|
11062
10688
|
if (!spec) {
|
|
11063
|
-
|
|
10689
|
+
console.log("");
|
|
10690
|
+
console.log(colors.muted(" Spec generation cancelled"));
|
|
10691
|
+
console.log("");
|
|
11064
10692
|
return;
|
|
11065
10693
|
}
|
|
11066
10694
|
console.log(specGen.formatSpec(spec));
|
|
11067
|
-
const buildNow = await
|
|
10695
|
+
const buildNow = await p7.confirm({
|
|
11068
10696
|
message: "Build from this spec?",
|
|
11069
10697
|
initialValue: true
|
|
11070
10698
|
});
|
|
11071
|
-
if (buildNow && !
|
|
10699
|
+
if (buildNow && !p7.isCancel(buildNow)) {
|
|
11072
10700
|
const builder = new ProjectBuilder(config, memory, scanner, editor, ai);
|
|
11073
10701
|
let cancelled = false;
|
|
11074
10702
|
const checkCancelled = () => cancelled;
|
|
@@ -11195,30 +10823,47 @@ async function handleAudit(args, ctx) {
|
|
|
11195
10823
|
const auditor = new CodebaseAuditor(config);
|
|
11196
10824
|
const git = new SmartGitManager(config);
|
|
11197
10825
|
const subcommand = args?.split(" ")[0] || "";
|
|
10826
|
+
console.log("");
|
|
10827
|
+
console.log(colors.white(" \u{1F50D} Codebase Audit"));
|
|
10828
|
+
console.log(colors.muted(" \u2500".repeat(35)));
|
|
11198
10829
|
switch (subcommand) {
|
|
11199
|
-
case "fix":
|
|
10830
|
+
case "fix":
|
|
10831
|
+
case "--fix": {
|
|
10832
|
+
console.log("");
|
|
11200
10833
|
const spinner = ora3("Scanning codebase...").start();
|
|
11201
10834
|
const report = await auditor.audit((file, current, total) => {
|
|
11202
10835
|
spinner.text = `Scanning ${current}/${total}: ${file}`;
|
|
11203
10836
|
});
|
|
11204
|
-
spinner.
|
|
10837
|
+
spinner.succeed("Scan complete");
|
|
11205
10838
|
console.log(auditor.formatReport(report));
|
|
11206
10839
|
if (report.totalIssues === 0) {
|
|
11207
|
-
|
|
10840
|
+
console.log("");
|
|
10841
|
+
showSuccess("No issues found! Your code is clean.");
|
|
10842
|
+
console.log("");
|
|
10843
|
+
console.log(colors.muted(" What you can do next:"));
|
|
10844
|
+
console.log(colors.dim(" \u2022 /review Get a full codebase review"));
|
|
10845
|
+
console.log(colors.dim(" \u2022 /spec Generate a product spec"));
|
|
10846
|
+
console.log("");
|
|
11208
10847
|
return;
|
|
11209
10848
|
}
|
|
11210
10849
|
const fixType = args?.includes("critical") ? "critical" : args?.includes("all") ? "all" : null;
|
|
11211
10850
|
let issuesToFix = report.critical;
|
|
11212
10851
|
if (!fixType) {
|
|
11213
|
-
const choice = await
|
|
10852
|
+
const choice = await p7.select({
|
|
11214
10853
|
message: "What would you like to fix?",
|
|
11215
10854
|
options: [
|
|
11216
10855
|
{ value: "critical", label: `Critical issues only (${report.critical.length})` },
|
|
11217
10856
|
{ value: "warnings", label: `Critical + Warnings (${report.critical.length + report.warnings.length})` },
|
|
11218
|
-
{ value: "all", label: `Everything (${report.totalIssues})` }
|
|
10857
|
+
{ value: "all", label: `Everything (${report.totalIssues})` },
|
|
10858
|
+
{ value: "cancel", label: "\u2190 Back", hint: "Fix later" }
|
|
11219
10859
|
]
|
|
11220
10860
|
});
|
|
11221
|
-
if (
|
|
10861
|
+
if (p7.isCancel(choice) || choice === "cancel") {
|
|
10862
|
+
console.log("");
|
|
10863
|
+
console.log(colors.muted(" Auto-fix cancelled. You can run /audit fix anytime."));
|
|
10864
|
+
console.log("");
|
|
10865
|
+
return;
|
|
10866
|
+
}
|
|
11222
10867
|
if (choice === "warnings") {
|
|
11223
10868
|
issuesToFix = [...report.critical, ...report.warnings];
|
|
11224
10869
|
} else if (choice === "all") {
|
|
@@ -11249,11 +10894,11 @@ async function handleAudit(args, ctx) {
|
|
|
11249
10894
|
const files = result.changes.map((c) => c.file);
|
|
11250
10895
|
await git.commitFiles(files, "fix: resolve codebase audit issues");
|
|
11251
10896
|
console.log("");
|
|
11252
|
-
const push = await
|
|
10897
|
+
const push = await p7.confirm({
|
|
11253
10898
|
message: "Push and create PR?",
|
|
11254
10899
|
initialValue: true
|
|
11255
10900
|
});
|
|
11256
|
-
if (push && !
|
|
10901
|
+
if (push && !p7.isCancel(push)) {
|
|
11257
10902
|
await git.push();
|
|
11258
10903
|
const pr = await git.createPR(branchName, await git.getMainBranch(), "Fix: Codebase Audit Issues");
|
|
11259
10904
|
if (pr?.url) {
|
|
@@ -11271,21 +10916,29 @@ async function handleAudit(args, ctx) {
|
|
|
11271
10916
|
spinner.stop();
|
|
11272
10917
|
const markdown = auditor.exportMarkdown(report);
|
|
11273
10918
|
const reportPath = "AUDIT_REPORT.md";
|
|
11274
|
-
await
|
|
10919
|
+
await fs17.writeFile(reportPath, markdown);
|
|
11275
10920
|
console.log(auditor.formatReport(report));
|
|
11276
10921
|
showSuccess(`Report saved to ${reportPath}`);
|
|
11277
10922
|
break;
|
|
11278
10923
|
}
|
|
11279
10924
|
default: {
|
|
10925
|
+
console.log("");
|
|
11280
10926
|
const spinner = ora3("Scanning codebase...").start();
|
|
11281
10927
|
const report = await auditor.audit((file, current, total) => {
|
|
11282
10928
|
spinner.text = `Scanning ${current}/${total}: ${file}`;
|
|
11283
10929
|
});
|
|
11284
|
-
spinner.
|
|
10930
|
+
spinner.succeed("Scan complete");
|
|
11285
10931
|
console.log(auditor.formatReport(report));
|
|
11286
|
-
if (report.totalIssues
|
|
11287
|
-
console.log(
|
|
11288
|
-
|
|
10932
|
+
if (report.totalIssues === 0) {
|
|
10933
|
+
console.log("");
|
|
10934
|
+
showSuccess("No issues found! Your code is clean.");
|
|
10935
|
+
console.log("");
|
|
10936
|
+
} else {
|
|
10937
|
+
console.log("");
|
|
10938
|
+
console.log(colors.muted(" What you can do:"));
|
|
10939
|
+
console.log(colors.dim(" /audit fix Auto-fix issues"));
|
|
10940
|
+
console.log(colors.dim(" /audit report Export markdown report"));
|
|
10941
|
+
console.log("");
|
|
11289
10942
|
}
|
|
11290
10943
|
}
|
|
11291
10944
|
}
|
|
@@ -11375,7 +11028,7 @@ async function handleFolderChange() {
|
|
|
11375
11028
|
console.log(colors.muted(" Cancelled"));
|
|
11376
11029
|
return;
|
|
11377
11030
|
}
|
|
11378
|
-
if (!await
|
|
11031
|
+
if (!await fs17.pathExists(folder)) {
|
|
11379
11032
|
showError(`Folder not found: ${folder}`);
|
|
11380
11033
|
return;
|
|
11381
11034
|
}
|
|
@@ -11393,17 +11046,29 @@ async function initProject(ctx) {
|
|
|
11393
11046
|
const { memory, scanner } = ctx;
|
|
11394
11047
|
const structure = await scanner.detectProject();
|
|
11395
11048
|
console.log("");
|
|
11396
|
-
|
|
11049
|
+
console.log(colors.white(" \u{1F4C1} Initialize Project"));
|
|
11050
|
+
console.log(colors.muted(" \u2500".repeat(35)));
|
|
11051
|
+
console.log("");
|
|
11052
|
+
const name = await p7.text({
|
|
11397
11053
|
message: "Project name:",
|
|
11398
|
-
initialValue: structure.name
|
|
11054
|
+
initialValue: structure.name,
|
|
11055
|
+
placeholder: structure.name
|
|
11399
11056
|
});
|
|
11400
|
-
if (
|
|
11401
|
-
|
|
11402
|
-
|
|
11057
|
+
if (p7.isCancel(name)) {
|
|
11058
|
+
console.log(colors.muted(" Cancelled"));
|
|
11059
|
+
console.log("");
|
|
11060
|
+
return;
|
|
11061
|
+
}
|
|
11062
|
+
const description = await p7.text({
|
|
11063
|
+
message: "Description (optional):",
|
|
11403
11064
|
placeholder: "A brief description of your project"
|
|
11404
11065
|
});
|
|
11405
|
-
if (
|
|
11406
|
-
|
|
11066
|
+
if (p7.isCancel(description)) {
|
|
11067
|
+
console.log(colors.muted(" Cancelled"));
|
|
11068
|
+
console.log("");
|
|
11069
|
+
return;
|
|
11070
|
+
}
|
|
11071
|
+
const stack = await p7.text({
|
|
11407
11072
|
message: "Tech stack (comma-separated):",
|
|
11408
11073
|
initialValue: [
|
|
11409
11074
|
structure.framework,
|
|
@@ -11412,7 +11077,11 @@ async function initProject(ctx) {
|
|
|
11412
11077
|
"Tailwind"
|
|
11413
11078
|
].filter(Boolean).join(", ")
|
|
11414
11079
|
});
|
|
11415
|
-
if (
|
|
11080
|
+
if (p7.isCancel(stack)) {
|
|
11081
|
+
console.log(colors.muted(" Cancelled"));
|
|
11082
|
+
console.log("");
|
|
11083
|
+
return;
|
|
11084
|
+
}
|
|
11416
11085
|
memory.setIdentity(
|
|
11417
11086
|
name,
|
|
11418
11087
|
description,
|
|
@@ -11455,11 +11124,11 @@ async function undoChanges(ctx, steps) {
|
|
|
11455
11124
|
const snapshot = snapshots[targetIndex];
|
|
11456
11125
|
console.log("");
|
|
11457
11126
|
console.log(colors.white(` Rolling back to: ${snapshot.description}`));
|
|
11458
|
-
const
|
|
11127
|
+
const confirm7 = await p7.confirm({
|
|
11459
11128
|
message: "Proceed with rollback?",
|
|
11460
11129
|
initialValue: false
|
|
11461
11130
|
});
|
|
11462
|
-
if (
|
|
11131
|
+
if (confirm7 && !p7.isCancel(confirm7)) {
|
|
11463
11132
|
const restored = await memory.rollback(snapshot.id);
|
|
11464
11133
|
showSuccess(`Restored ${restored.length} file${restored.length > 1 ? "s" : ""}`);
|
|
11465
11134
|
}
|
|
@@ -11467,11 +11136,11 @@ async function undoChanges(ctx, steps) {
|
|
|
11467
11136
|
async function commitChanges(ctx, message) {
|
|
11468
11137
|
const { git, memory } = ctx;
|
|
11469
11138
|
if (!await git.isRepo()) {
|
|
11470
|
-
const init = await
|
|
11139
|
+
const init = await p7.confirm({
|
|
11471
11140
|
message: "No git repo found. Initialize one?",
|
|
11472
11141
|
initialValue: true
|
|
11473
11142
|
});
|
|
11474
|
-
if (init && !
|
|
11143
|
+
if (init && !p7.isCancel(init)) {
|
|
11475
11144
|
await git.init();
|
|
11476
11145
|
showSuccess("Git repository initialized");
|
|
11477
11146
|
} else {
|
|
@@ -11496,13 +11165,13 @@ async function deployProject(ctx) {
|
|
|
11496
11165
|
showError("Vercel CLI not installed. Run: npm i -g vercel");
|
|
11497
11166
|
return;
|
|
11498
11167
|
}
|
|
11499
|
-
const prod = await
|
|
11168
|
+
const prod = await p7.confirm({
|
|
11500
11169
|
message: "Deploy to production?",
|
|
11501
11170
|
initialValue: false
|
|
11502
11171
|
});
|
|
11503
11172
|
const spinner = ora3("Deploying...").start();
|
|
11504
11173
|
try {
|
|
11505
|
-
const result = await vercel.deploy(process.cwd(), prod && !
|
|
11174
|
+
const result = await vercel.deploy(process.cwd(), prod && !p7.isCancel(prod));
|
|
11506
11175
|
spinner.stop();
|
|
11507
11176
|
showSuccess(`Deployed: ${result.url}`);
|
|
11508
11177
|
} catch (error) {
|
|
@@ -11511,78 +11180,108 @@ async function deployProject(ctx) {
|
|
|
11511
11180
|
}
|
|
11512
11181
|
}
|
|
11513
11182
|
async function reviewCodebase(ctx) {
|
|
11514
|
-
const { ai } = ctx;
|
|
11515
|
-
|
|
11183
|
+
const { ai, memory } = ctx;
|
|
11184
|
+
console.log("");
|
|
11185
|
+
console.log(colors.white(" \u{1F4CA} Codebase Review"));
|
|
11186
|
+
console.log(colors.muted(" \u2500".repeat(35)));
|
|
11187
|
+
console.log("");
|
|
11188
|
+
const includeAI = await p7.confirm({
|
|
11516
11189
|
message: "Include AI deep analysis? (slower but more thorough)",
|
|
11517
11190
|
initialValue: true
|
|
11518
11191
|
});
|
|
11519
|
-
if (
|
|
11192
|
+
if (p7.isCancel(includeAI)) {
|
|
11193
|
+
console.log(colors.muted(" Review cancelled"));
|
|
11194
|
+
console.log("");
|
|
11195
|
+
return;
|
|
11196
|
+
}
|
|
11520
11197
|
const analyzer = new CodebaseAnalyzer(
|
|
11521
11198
|
process.cwd(),
|
|
11522
11199
|
includeAI ? ai : void 0
|
|
11523
11200
|
);
|
|
11524
|
-
|
|
11201
|
+
console.log("");
|
|
11202
|
+
const spinner = ora3({
|
|
11203
|
+
text: "Starting codebase review...",
|
|
11204
|
+
spinner: "dots"
|
|
11205
|
+
}).start();
|
|
11525
11206
|
try {
|
|
11526
11207
|
const report = await analyzer.fullReview((step, progress) => {
|
|
11527
11208
|
spinner.text = `${step} (${Math.round(progress)}%)`;
|
|
11528
11209
|
});
|
|
11529
|
-
spinner.
|
|
11210
|
+
spinner.succeed("Review complete!");
|
|
11211
|
+
console.log("");
|
|
11530
11212
|
const formatted = analyzer.formatReport(report);
|
|
11531
11213
|
console.log(formatted);
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11214
|
+
console.log(colors.muted(" \u2500".repeat(35)));
|
|
11215
|
+
console.log("");
|
|
11216
|
+
const hasIssues = report.summary.totalViolations > 0 || report.summary.totalIssues > 0;
|
|
11217
|
+
if (!hasIssues) {
|
|
11218
|
+
console.log(colors.success(" \u2713 Your codebase looks healthy!"));
|
|
11219
|
+
console.log("");
|
|
11220
|
+
console.log(colors.muted(" What you can do next:"));
|
|
11221
|
+
console.log(colors.dim(" \u2022 /audit Run a deeper audit with auto-fix"));
|
|
11222
|
+
console.log(colors.dim(" \u2022 /spec Generate a product spec"));
|
|
11223
|
+
console.log(colors.dim(" \u2022 /git Check your git status"));
|
|
11224
|
+
console.log("");
|
|
11225
|
+
} else {
|
|
11226
|
+
console.log(colors.warning(` Found ${report.summary.totalViolations + report.summary.totalIssues} issues to address`));
|
|
11227
|
+
console.log("");
|
|
11228
|
+
const action = await p7.select({
|
|
11229
|
+
message: "What would you like to do?",
|
|
11230
|
+
options: [
|
|
11231
|
+
{
|
|
11232
|
+
value: "fix",
|
|
11233
|
+
label: "\u{1F527} Auto-fix pattern violations",
|
|
11234
|
+
hint: `Fix ${report.byCategory.patterns.length} violations`
|
|
11235
|
+
},
|
|
11236
|
+
{
|
|
11237
|
+
value: "audit",
|
|
11238
|
+
label: "\u{1F50D} Run detailed audit",
|
|
11239
|
+
hint: "Get line-by-line fixes"
|
|
11240
|
+
},
|
|
11241
|
+
{
|
|
11242
|
+
value: "export",
|
|
11243
|
+
label: "\u{1F4C4} Export report",
|
|
11244
|
+
hint: "Save to file"
|
|
11245
|
+
},
|
|
11246
|
+
{
|
|
11247
|
+
value: "skip",
|
|
11248
|
+
label: "\u2190 Back to main",
|
|
11249
|
+
hint: "Address later"
|
|
11250
|
+
}
|
|
11251
|
+
]
|
|
11536
11252
|
});
|
|
11537
|
-
if (
|
|
11538
|
-
|
|
11253
|
+
if (p7.isCancel(action) || action === "skip") {
|
|
11254
|
+
console.log("");
|
|
11255
|
+
console.log(colors.muted(" You can run /audit anytime to fix issues"));
|
|
11256
|
+
console.log("");
|
|
11257
|
+
return;
|
|
11258
|
+
}
|
|
11259
|
+
if (action === "fix") {
|
|
11260
|
+
console.log("");
|
|
11261
|
+
console.log(colors.muted(" Starting auto-fix..."));
|
|
11262
|
+
await handleAudit("--fix", ctx);
|
|
11263
|
+
} else if (action === "audit") {
|
|
11264
|
+
await handleAudit("", ctx);
|
|
11265
|
+
} else if (action === "export") {
|
|
11266
|
+
const reportPath = path18.join(process.cwd(), "codebase-review.md");
|
|
11267
|
+
await fs17.writeFile(reportPath, formatted.replace(/\x1b\[[0-9;]*m/g, ""));
|
|
11268
|
+
console.log("");
|
|
11269
|
+
showSuccess(`Report saved to ${reportPath}`);
|
|
11539
11270
|
}
|
|
11540
11271
|
}
|
|
11272
|
+
memory.addDecision(`Codebase reviewed: ${report.summary.healthScore}/100 health score, ${report.summary.totalViolations} violations`);
|
|
11273
|
+
await memory.save();
|
|
11541
11274
|
} catch (error) {
|
|
11542
|
-
spinner.
|
|
11543
|
-
|
|
11544
|
-
|
|
11545
|
-
|
|
11546
|
-
|
|
11547
|
-
|
|
11548
|
-
|
|
11549
|
-
|
|
11550
|
-
|
|
11551
|
-
message: "Anthropic API key:",
|
|
11552
|
-
placeholder: "sk-ant-...",
|
|
11553
|
-
validate: (value) => {
|
|
11554
|
-
if (!value) return "Required";
|
|
11555
|
-
if (!value.startsWith("sk-ant-")) return "Invalid format";
|
|
11556
|
-
}
|
|
11557
|
-
});
|
|
11558
|
-
if (p8.isCancel(apiKey)) return;
|
|
11559
|
-
config.setAnthropicKey(apiKey);
|
|
11560
|
-
const setupGithub = await p8.confirm({
|
|
11561
|
-
message: "Set up GitHub token? (optional)",
|
|
11562
|
-
initialValue: false
|
|
11563
|
-
});
|
|
11564
|
-
if (setupGithub && !p8.isCancel(setupGithub)) {
|
|
11565
|
-
const token = await p8.text({
|
|
11566
|
-
message: "GitHub token:",
|
|
11567
|
-
placeholder: "ghp_..."
|
|
11568
|
-
});
|
|
11569
|
-
if (!p8.isCancel(token) && token) {
|
|
11570
|
-
config.setGithubToken(token);
|
|
11571
|
-
}
|
|
11572
|
-
}
|
|
11573
|
-
const setupVercel = await p8.confirm({
|
|
11574
|
-
message: "Set up Vercel token? (optional)",
|
|
11575
|
-
initialValue: false
|
|
11576
|
-
});
|
|
11577
|
-
if (setupVercel && !p8.isCancel(setupVercel)) {
|
|
11578
|
-
const token = await p8.text({
|
|
11579
|
-
message: "Vercel token:"
|
|
11580
|
-
});
|
|
11581
|
-
if (!p8.isCancel(token) && token) {
|
|
11582
|
-
config.setVercelToken(token);
|
|
11583
|
-
}
|
|
11275
|
+
spinner.fail("Review failed");
|
|
11276
|
+
console.log("");
|
|
11277
|
+
showError(error instanceof Error ? error.message : "Unknown error");
|
|
11278
|
+
console.log("");
|
|
11279
|
+
console.log(colors.muted(" Try:"));
|
|
11280
|
+
console.log(colors.dim(" \u2022 Check if you're in a project directory"));
|
|
11281
|
+
console.log(colors.dim(" \u2022 Run /status to verify your setup"));
|
|
11282
|
+
console.log(colors.dim(' \u2022 Run without AI: answer "no" to deep analysis'));
|
|
11283
|
+
console.log("");
|
|
11584
11284
|
}
|
|
11585
|
-
showSuccess("Setup complete!");
|
|
11586
11285
|
}
|
|
11587
11286
|
function showHelp() {
|
|
11588
11287
|
console.log(`
|
|
@@ -11602,33 +11301,78 @@ function showHelp() {
|
|
|
11602
11301
|
`);
|
|
11603
11302
|
}
|
|
11604
11303
|
function showCommandHelp() {
|
|
11605
|
-
console.log(
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
11630
|
-
|
|
11631
|
-
|
|
11632
|
-
|
|
11304
|
+
console.log("");
|
|
11305
|
+
console.log(colors.muted(" \u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E"));
|
|
11306
|
+
console.log(colors.muted(" \u2502") + colors.white(" Just tell me what you want ") + colors.muted("\u2502"));
|
|
11307
|
+
console.log(colors.muted(" \u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"));
|
|
11308
|
+
console.log("");
|
|
11309
|
+
console.log(colors.primary(" Build & Create"));
|
|
11310
|
+
console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
11311
|
+
console.log(colors.white(' "Add a login page with Google auth"'));
|
|
11312
|
+
console.log(colors.white(' "Build login, dashboard, and settings"'));
|
|
11313
|
+
console.log(colors.white(' "I want to build an Uber for dog walking"'));
|
|
11314
|
+
console.log("");
|
|
11315
|
+
console.log(colors.primary(" Review & Fix"));
|
|
11316
|
+
console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
11317
|
+
console.log(colors.white(' "Check my code for issues"'));
|
|
11318
|
+
console.log(colors.white(` "What's wrong with this project?"`));
|
|
11319
|
+
console.log(colors.white(' "Fix the problems you find"'));
|
|
11320
|
+
console.log("");
|
|
11321
|
+
console.log(colors.primary(" Git & Deploy"));
|
|
11322
|
+
console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
11323
|
+
console.log(colors.white(' "Commit my changes"'));
|
|
11324
|
+
console.log(colors.white(' "Push to GitHub"'));
|
|
11325
|
+
console.log(colors.white(' "Deploy to Vercel"'));
|
|
11326
|
+
console.log("");
|
|
11327
|
+
console.log(colors.primary(" Other"));
|
|
11328
|
+
console.log(colors.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
11329
|
+
console.log(colors.white(' "Generate tests"') + colors.dim(' "Start dev server"'));
|
|
11330
|
+
console.log(colors.white(' "Create a PR"') + colors.dim(' "Undo that"'));
|
|
11331
|
+
console.log("");
|
|
11332
|
+
console.log(colors.dim(" Shortcuts: /help /quit /status /login"));
|
|
11333
|
+
console.log("");
|
|
11334
|
+
}
|
|
11335
|
+
function getSuggestions(input) {
|
|
11336
|
+
const lowered = input.toLowerCase();
|
|
11337
|
+
const suggestions = [];
|
|
11338
|
+
const mappings = {
|
|
11339
|
+
"reveiw": ["/review - Review your codebase"],
|
|
11340
|
+
"reviw": ["/review - Review your codebase"],
|
|
11341
|
+
"aduit": ["/audit - Audit code quality"],
|
|
11342
|
+
"auidt": ["/audit - Audit code quality"],
|
|
11343
|
+
"hlep": ["/help - Show all commands"],
|
|
11344
|
+
"helo": ["/help - Show all commands"],
|
|
11345
|
+
"init": ["/init - Initialize project"],
|
|
11346
|
+
"start": ["/preview - Start dev server", "/init - Initialize project"],
|
|
11347
|
+
"run": ["/run <command> - Run a terminal command"],
|
|
11348
|
+
"fix": ["/audit fix - Auto-fix issues"],
|
|
11349
|
+
"check": ["/audit - Check code quality", "/review - Review codebase"],
|
|
11350
|
+
"test": ["/test - Generate tests"],
|
|
11351
|
+
"build": ['"Build a login page" - Describe what you want'],
|
|
11352
|
+
"make": ['"Make a navbar" - Describe what you want'],
|
|
11353
|
+
"create": ["/create - Create new project", '"Create a dashboard" - Describe it'],
|
|
11354
|
+
"git": ["/git status - Check git", "/git commit - Commit changes"],
|
|
11355
|
+
"commit": ["/commit - Commit changes", '/git commit -m "message"'],
|
|
11356
|
+
"push": ["/git push - Push to remote"],
|
|
11357
|
+
"status": ["/status - Project status", "/git status - Git status"],
|
|
11358
|
+
"login": ["/login - Login to CodeBakers"],
|
|
11359
|
+
"logout": ["/logout - Logout"]
|
|
11360
|
+
};
|
|
11361
|
+
for (const [key, vals] of Object.entries(mappings)) {
|
|
11362
|
+
if (lowered.includes(key) || key.includes(lowered)) {
|
|
11363
|
+
suggestions.push(...vals);
|
|
11364
|
+
}
|
|
11365
|
+
}
|
|
11366
|
+
if (suggestions.length === 0) {
|
|
11367
|
+
if (lowered.length < 3) {
|
|
11368
|
+
suggestions.push("/help - Show all commands");
|
|
11369
|
+
} else {
|
|
11370
|
+
suggestions.push(
|
|
11371
|
+
'"' + input + '" - Try describing what you want to build',
|
|
11372
|
+
"/help - Show all commands"
|
|
11373
|
+
);
|
|
11374
|
+
}
|
|
11375
|
+
}
|
|
11376
|
+
return [...new Set(suggestions)].slice(0, 4);
|
|
11633
11377
|
}
|
|
11634
11378
|
main().catch(console.error);
|