@@ -734,37 +734,166 @@ async function launchTui(_focusId?: string): Promise<void> {
734734
735735async function checkFastPath ( prompt : string ) : Promise < boolean > {
736736 const lowerPrompt = prompt . toLowerCase ( ) . trim ( ) ;
737-
738- const createMatch = lowerPrompt . match ( / ^ (?: (?: r u n \s + ) ? (?: c r e a t e | m a k e ) \s + f i l e ) \s + ( .+ ) $ / ) ;
739- if ( createMatch ) {
737+
738+ // --- helpers ---
739+ const resolvePath = ( raw : string ) : string => {
740+ const p = raw . trim ( ) . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) ;
741+ if ( / ^ [ a - z ] : [ / \\ ] / i. test ( p ) ) return path . resolve ( p ) ;
742+ return path . resolve ( process . cwd ( ) , p ) ;
743+ } ;
744+
745+ const isPathLike = ( s : string ) =>
746+ / ^ (?: [ a - z ] : ) ? [ \\ / ] / . test ( s ) || s . includes ( "/" ) || s . includes ( "\\" ) ;
747+
748+ // 1) Create file — catch "create [a] [python] file [named|called] snake.py"
749+ // "make a python file snake.py"
750+ // "create there python code for neon snake"
751+ const createAlias = lowerPrompt . match (
752+ / ^ (?: (?: r u n \s + ) ? (?: c r e a t e | m a k e | n e w | w r i t e | g e n e r a t e ) \s + (?: a \s + ) ? (?: p y t h o n \s + ) ? (?: f i l e | c o d e | s c r i p t ) ? \. ? \s * (?: n a m e d | c a l l e d ) ? \. ? \s * ) ( [ \w \- . / \\ ] + (?: \. [ a - z 0 - 9 ] + ) ? (?: \s + \S + ) * ) $ / i
753+ ) ;
754+ if ( createAlias ) {
755+ const tail = createAlias [ 1 ] . trim ( ) ;
756+ // If tail itself contains spaces, take the first non-space token as filename
757+ const candidate = tail . split ( / \s + / ) . find ( ( t ) => / ^ \w | [ \w \- . / \\ ] / . test ( t ) ) || tail ;
758+ const fp = resolvePath ( candidate ) ;
740759 try {
741- fs . writeFileSync ( path . resolve ( process . cwd ( ) , createMatch [ 1 ] ! ) , "" ) ;
742- console . log ( `[OK] file created: ${ createMatch [ 1 ] } ` ) ;
760+ fs . mkdirSync ( path . dirname ( fp ) , { recursive : true } ) ;
761+ fs . writeFileSync ( fp , "" ) ;
762+ console . log ( `[OK] created ${ fp } ` ) ;
763+ return true ;
743764 } catch ( e : any ) {
744765 console . log ( `[ERR] failed to create file: ${ e . message } ` ) ;
766+ return true ;
745767 }
746- return true ;
747768 }
748769
749- const runMatch = lowerPrompt . match ( / ^ (?: (?: r u n \s + ) ? c o m m a n d | e x e c u t e ) \s + ( .+ ) $ / ) ;
770+ // 2) Create file with content (e.g. "create python code for neon snake")
771+ // → generate a minimal Python script for the topic
772+ if ( / ^ (?: c r e a t e | m a k e | w r i t e | g e n e r a t e ) \s + (?: a \s + ) ? (?: p y t h o n | j s | t s | h t m l | c s s | j s o n | m d ) \b / i. test ( lowerPrompt ) ) {
773+ const langMatch = lowerPrompt . match ( / ^ (?: c r e a t e | m a k e | w r i t e | g e n e r a t e ) \s + (?: a \s + ) ? ( p y t h o n | j s | t s | h t m l | c s s | j s o n | m d ) / i) ;
774+ const lang = ( langMatch ?. [ 1 ] || "py" ) . trim ( ) ;
775+ let ext = lang ;
776+ if ( lang === "js" ) ext = "js" ;
777+ if ( lang === "ts" ) ext = "ts" ;
778+ const safeName = prompt
779+ . replace ( / ^ (?: c r e a t e | m a k e | w r i t e | g e n e r a t e ) \s + (?: a \s + ) ? (?: p y t h o n | j s | t s | h t m l | c s s | j s o n | m d ) \s * / i, "" )
780+ . split ( / \s + / )
781+ . slice ( 0 , 3 )
782+ . join ( "_" )
783+ . replace ( / [ ^ a - z 0 - 9 _ \- ] / gi, "" ) ;
784+ const fileName = safeName || `script.${ ext } ` ;
785+ const fp = resolvePath ( fileName . endsWith ( `.${ ext } ` ) ? fileName : `${ fileName } .${ ext } ` ) ;
786+ let content = "" ;
787+ if ( lang === "python" ) {
788+ content = `# ${ path . basename ( fp ) } \nprint("Hello from Splash")\n` ;
789+ } else if ( lang === "html" ) {
790+ content = `<!DOCTYPE html>\n<html>\n<body>\n <h1>Hello from Splash</h1>\n</body>\n</html>\n` ;
791+ } else {
792+ content = `// ${ path . basename ( fp ) } \nconsole.log("Hello from Splash");\n` ;
793+ }
794+ try {
795+ fs . mkdirSync ( path . dirname ( fp ) , { recursive : true } ) ;
796+ fs . writeFileSync ( fp , content ) ;
797+ console . log ( `[OK] created ${ fp } ` ) ;
798+ return true ;
799+ } catch ( e : any ) {
800+ console . log ( `[ERR] failed to create file: ${ e . message } ` ) ;
801+ return true ;
802+ }
803+ }
804+
805+ // 3) List folders/files in a directory
806+ const listAlias = lowerPrompt . match (
807+ / ^ (?: l i s t | l s | d i r | s h o w ) \s + (?: a l l \s + ) ? (?: f o l d e r s | d i r e c t o r i e s | f i l e s | s u b f o l d e r s ) ? \. ? \s * (?: i n | o f | f o r ) ? \. ? \s * ( [ \w \- . / \\ ] + ) ? $ /
808+ ) ;
809+ if ( listAlias && / (?: f o l d e r s | f i l e s | l i s t | l s | d i r | s h o w ) / . test ( lowerPrompt ) ) {
810+ const target = listAlias [ 1 ]
811+ ? resolvePath ( listAlias [ 1 ] . trim ( ) )
812+ : process . cwd ( ) ;
813+ try {
814+ const entries = fs . readdirSync ( target , { withFileTypes : true } ) ;
815+ const dirs = entries . filter ( ( e ) => e . isDirectory ( ) ) . map ( ( e ) => e . name ) ;
816+ const files = entries . filter ( ( e ) => e . isFile ( ) ) . map ( ( e ) => e . name ) ;
817+ console . log ( `[OK] ${ target } — ${ dirs . length } folder(s), ${ files . length } file(s)` ) ;
818+ for ( const d of dirs ) console . log ( ` [DIR] ${ d } ` ) ;
819+ for ( const f of files ) console . log ( ` [FILE] ${ f } ` ) ;
820+ return true ;
821+ } catch ( e : any ) {
822+ console . log ( `[ERR] failed to list directory: ${ e . message } ` ) ;
823+ return true ;
824+ }
825+ }
826+
827+ // 4) "go to <path> and [do simple thing]"
828+ const goToMatch = lowerPrompt . match ( / ^ (?: g o \s + t o | o p e n | c d ) \s + ( .+ ?) (?: \s + a n d \s + ( .+ ) ) ? $ / ) ;
829+ if ( goToMatch ) {
830+ const dir = goToMatch [ 1 ] . trim ( ) ;
831+ const rest = ( goToMatch [ 2 ] || "" ) . trim ( ) ;
832+ if ( / ^ ( l i s t | l s | s h o w ) \b / i. test ( rest ) || rest === "" ) {
833+ const target = resolvePath ( dir ) ;
834+ try {
835+ const entries = fs . readdirSync ( target , { withFileTypes : true } ) ;
836+ const dirs = entries . filter ( ( e ) => e . isDirectory ( ) ) . map ( ( e ) => e . name ) ;
837+ console . log ( `[OK] ${ target } — ${ dirs . length } folder(s)` ) ;
838+ for ( const d of dirs ) console . log ( ` ${ d } ` ) ;
839+ return true ;
840+ } catch ( e : any ) {
841+ console . log ( `[ERR] failed to list directory: ${ e . message } ` ) ;
842+ return true ;
843+ }
844+ }
845+ // fall through for non-simple rest
846+ }
847+
848+ // 5) Write "write X to file Y"
849+ const writeToMatch = lowerPrompt . match ( / ^ (?: w r i t e | p u t | s a v e ) \s + ( .+ ?) \s + t o \s + (?: f i l e \s + ) ? ( .+ ) $ / ) ;
850+ if ( writeToMatch ) {
851+ const content = writeToMatch [ 1 ] ;
852+ const filePath = writeToMatch [ 2 ] ;
853+ if ( isPathLike ( filePath ) || / \. \w { 1 , 4 } $ / . test ( filePath . trim ( ) ) ) {
854+ try {
855+ const fp = resolvePath ( filePath ) ;
856+ fs . mkdirSync ( path . dirname ( fp ) , { recursive : true } ) ;
857+ fs . writeFileSync ( fp , content ) ;
858+ console . log ( `[OK] wrote ${ content . length } chars to ${ fp } ` ) ;
859+ return true ;
860+ } catch ( e : any ) {
861+ console . log ( `[ERR] failed to write file: ${ e . message } ` ) ;
862+ return true ;
863+ }
864+ }
865+ }
866+
867+ // 6) Read file "read file X" / "open file X"
868+ const readAlias = lowerPrompt . match ( / ^ (?: r e a d | o p e n | c a t | s h o w | g e t ) \s + (?: f i l e \s + ) ? ( .+ ) $ / ) ;
869+ if ( readAlias ) {
870+ const candidate = readAlias [ 1 ] . trim ( ) ;
871+ if ( isPathLike ( candidate ) || / ^ [ \w \- . / \\ ] + $ / . test ( candidate ) ) {
872+ const fp = resolvePath ( candidate ) ;
873+ if ( fs . existsSync ( fp ) ) {
874+ try {
875+ const txt = fs . readFileSync ( fp , "utf-8" ) ;
876+ console . log ( txt ) ;
877+ return true ;
878+ } catch ( e : any ) {
879+ console . log ( `[ERR] failed to read file: ${ e . message } ` ) ;
880+ return true ;
881+ }
882+ }
883+ }
884+ }
885+
886+ // 7) Shell command
887+ const runMatch = lowerPrompt . match ( / ^ (?: (?: r u n \s + ) ? (?: c o m m a n d | e x e c u t e | r u n | s h e l l ) ) \s + ( .+ ) $ / ) ;
750888 if ( runMatch ) {
751889 try {
752- spawnSync ( runMatch [ 1 ] ! , { shell : true , stdio : "inherit" } ) ;
890+ spawnSync ( runMatch [ 1 ] ! , { shell : true , stdio : "inherit" , timeout : 30000 } ) ;
753891 console . log ( `[OK] command executed: ${ runMatch [ 1 ] } ` ) ;
892+ return true ;
754893 } catch ( e : any ) {
755894 console . log ( `[ERR] command failed: ${ e . message } ` ) ;
895+ return true ;
756896 }
757- return true ;
758- }
759-
760- const searchMatch = lowerPrompt . match ( / ^ (?: (?: r u n \s + ) ? s e a r c h (?: \s + f o r ) ? ) \s + ( .+ ) $ / ) ;
761- if ( searchMatch ) {
762- console . log ( `[INFO] Searching for: ${ searchMatch [ 1 ] } ...` ) ;
763- const { WebSearchSkill } = await import ( "@alpclaw/skills" ) ;
764- const skill = new WebSearchSkill ( ) ;
765- const result = await skill . execute ( { query : searchMatch [ 1 ] } , { } as any ) ;
766- console . log ( ( result as any ) . output || "[INFO] No results found." ) ;
767- return true ;
768897 }
769898
770899 return false ;
0 commit comments