@@ -110,7 +110,7 @@ async function main() {
110110 "version" , "help" , "init" , "setup" , "config" , "chat" , "telegram" , "slack" ,
111111 "whatsapp" , "messenger" , "discord" , "runs" , "tui" , "dashboard" , "self-improve" ,
112112 "providers" , "skills" , "memory" , "maintenance" , "voice" , "swarm" , "antigravity" ,
113- "run" , "doctor" , "browser"
113+ "run" , "doctor" , "browser" , "self-modify" , "self-rollback"
114114 ] ;
115115
116116 if ( ! KNOWN_COMMANDS . includes ( cmd ) ) {
@@ -173,6 +173,12 @@ async function main() {
173173 case "self-improve" :
174174 await runSelfImprove ( ) ;
175175 return ;
176+ case "self-modify" :
177+ await runSelfModify ( args . slice ( 1 ) ) ;
178+ return ;
179+ case "self-rollback" :
180+ await runSelfRollback ( args . slice ( 1 ) ) ;
181+ return ;
176182 case "providers" :
177183 await runProviders ( args . slice ( 1 ) ) ;
178184 return ;
@@ -259,107 +265,142 @@ function printHelp(): void {
259265
260266async function runInit ( ) {
261267 console . log ( renderBanner ( { subtitle : "Setup" } ) ) ;
262- p . intro ( pc . bgCyan ( pc . black ( " SPLASH INIT " ) ) ) ;
263- p . log . message ( "Pick a provider and drop in a key. You can change this any time with `splash config` ." ) ;
268+ p . intro ( pc . bgCyan ( pc . black ( " SPLASH INIT V2 " ) ) ) ;
269+ p . log . message ( "Let's set up your Splash environment in 8 steps ." ) ;
264270
265271 const existing = readGlobalConfig ( ) ;
266272 const next : GlobalConfigShape = { ...existing } ;
267273 next . apiKeys = { ...( existing . apiKeys || { } ) } ;
274+ next . providers = { ...( existing . providers || { } ) } ;
275+ next . providers . apiKeys = next . apiKeys ;
268276
277+ // Step 2: Primary Provider Choice
269278 const provider = await p . select ( {
270- message : "1. Default provider :" ,
279+ message : "1. Primary Provider (Top 10) :" ,
271280 options : [
272- { value : "openrouter" , label : "OpenRouter — recommended, unlocks 300+ models" } ,
273- { value : "claude" , label : "Anthropic Claude — direct API" } ,
274- { value : "openai" , label : "OpenAI — GPT-4o, o3, o4-mini" } ,
275- { value : "gemini" , label : "Google Gemini — 2.5 Pro/Flash" } ,
276- { value : "deepseek" , label : "DeepSeek — R1, V3 (affordable)" } ,
277- { value : "ollama" , label : "Ollama — local models, no key needed" } ,
281+ { value : "openrouter" , label : "OpenRouter (Recommended — 300+ models)" } ,
282+ { value : "openai" , label : "OpenAI (GPT-4o, o3)" } ,
283+ { value : "anthropic" , label : "Anthropic (Claude 3.5)" } ,
284+ { value : "google" , label : "Google Gemini" } ,
285+ { value : "deepseek" , label : "DeepSeek (R1, V3)" } ,
286+ { value : "mistral" , label : "Mistral" } ,
287+ { value : "groq" , label : "Groq (Ultra-fast)" } ,
288+ { value : "cohere" , label : "Cohere" } ,
289+ { value : "nvidia" , label : "Nvidia NIM" } ,
290+ { value : "together" , label : "Together AI" } ,
291+ { value : "ollama" , label : "Ollama (Local)" } ,
278292 ] ,
279293 } ) ;
280294 if ( p . isCancel ( provider ) ) return abort ( ) ;
281295
282296 next . defaultProvider = provider as string ;
297+ next . providers . default = provider as string ;
283298
299+ // Step 3: API Key Input
284300 if ( provider !== "ollama" ) {
285301 const key = await p . password ( { message : `2. Paste your ${ provider } API key (input hidden):` } ) ;
286302 if ( p . isCancel ( key ) ) return abort ( ) ;
287- if ( key ) next . apiKeys [ provider as string ] = key as string ;
303+ if ( key ) {
304+ next . apiKeys [ provider as string ] = key as string ;
305+ next . providers . apiKeys [ provider as string ] = key as string ;
306+ }
288307 }
289308
290- if ( provider === "openrouter" ) {
291- const s = p . spinner ( ) ;
292- s . start ( "Fetching live OpenRouter models..." ) ;
293- let liveModels : any [ ] = [ ] ;
294- try {
295- const res = await fetch ( "https://openrouter.ai/api/v1/models" ) ;
296- const data = await res . json ( ) as any ;
297- liveModels = data . data . sort ( ( a : any , b : any ) => a . id . localeCompare ( b . id ) ) ;
298- } catch ( e ) {
299- // Fallback
300- }
301- s . stop ( "Models loaded." ) ;
302-
303- const category = await p . select ( {
304- message : "3. Model Category:" ,
305- options : [
306- { value : "free" , label : "Free — Top performing free models (DeepSeek, Llama)" } ,
307- { value : "paid" , label : "Premium (Paid) — SOTA models (Claude 3.5, GPT-4o, Gemini Pro)" } ,
308- { value : "all" , label : "All Live Models" } ,
309- ]
310- } ) ;
311- if ( p . isCancel ( category ) ) return abort ( ) ;
312-
313- let options : { value : string ; label : string } [ ] = [ ] ;
314- if ( liveModels . length > 0 ) {
315- const isFree = ( m : any ) => m . pricing ?. prompt === "0" && m . pricing ?. completion === "0" ;
316- const filtered = category === "free" ? liveModels . filter ( isFree )
317- : category === "paid" ? liveModels . filter ( ( m : any ) => ! isFree ( m ) )
318- : liveModels ;
319-
320- options = filtered . slice ( 0 , 50 ) . map ( ( m : any ) => ( {
321- value : m . id ,
322- label : `${ m . name } ${ isFree ( m ) ? pc . green ( "(Free)" ) : pc . yellow ( "(Paid)" ) } - ${ m . context_length } k ctx`
323- } ) ) ;
324- } else {
325- // Fallback hardcoded if offline
326- options = [
327- { value : "anthropic/claude-3.5-sonnet" , label : "Claude 3.5 Sonnet (Premium)" } ,
328- { value : "deepseek/deepseek-r1:free" , label : "DeepSeek R1 (Free)" } ,
329- ] ;
330- }
309+ // Step 4: Default Model Selection
310+ let defaultModels : Record < string , string [ ] > = {
311+ openrouter : [ "anthropic/claude-3.5-sonnet" , "deepseek/deepseek-r1" , "openai/gpt-4o" , "google/gemini-2.5-pro" , "mistralai/mistral-large" ] ,
312+ openai : [ "gpt-4o" , "gpt-4o-mini" , "o3-mini" ] ,
313+ anthropic : [ "claude-3-5-sonnet-20241022" , "claude-3-5-haiku-20241022" ] ,
314+ google : [ "gemini-2.5-pro" , "gemini-2.5-flash" , "gemini-2.0-flash" ] ,
315+ deepseek : [ "deepseek-chat" , "deepseek-reasoner" ] ,
316+ mistral : [ "mistral-large-latest" , "pixtral-large-latest" , "ministral-8b-latest" ] ,
317+ groq : [ "llama3-70b-8192" , "mixtral-8x7b-32768" ] ,
318+ cohere : [ "command-r-plus" , "command-r" ] ,
319+ nvidia : [ "meta/llama-3.1-70b-instruct" , "meta/llama-3.1-405b-instruct" ] ,
320+ together : [ "meta-llama/Llama-3-70b-chat-hf" , "deepseek-ai/DeepSeek-V3" ] ,
321+ ollama : [ "llama3" , "mistral" , "qwen2.5" ] ,
322+ } ;
331323
332- const model = await p . select ( {
333- message : "4. Default model:" ,
334- options : options ,
335- } ) ;
336- if ( ! p . isCancel ( model ) ) next . defaultModel = model as string ;
337- }
324+ const modelOptions = defaultModels [ provider as string ] . map ( m => ( { value : m , label : m } ) ) ;
325+ const model = await p . select ( {
326+ message : "3. Default model:" ,
327+ options : modelOptions ,
328+ } ) ;
329+ if ( p . isCancel ( model ) ) return abort ( ) ;
330+ next . defaultModel = model as string ;
338331
339- const safety = await p . select ( {
340- message : "4. Safety level:" ,
332+ // Step 5: Fallback Provider Selection
333+ const fallback = await p . select ( {
334+ message : "4. Fallback Provider (Used on 429 Rate Limit):" ,
341335 options : [
342- { value : "standard" , label : "Standard — confirms risky actions (recommended)" } ,
343- { value : "strict" , label : "Strict — confirms every action" } ,
344- { value : "permissive" , label : "Permissive — fully autonomous" } ,
345- ] ,
336+ { value : "none" , label : "None" } ,
337+ { value : "openrouter" , label : "OpenRouter" } ,
338+ { value : "openai" , label : "OpenAI" } ,
339+ { value : "anthropic" , label : "Anthropic" } ,
340+ { value : "google" , label : "Google Gemini" } ,
341+ { value : "deepseek" , label : "DeepSeek" } ,
342+ { value : "groq" , label : "Groq" } ,
343+ { value : "ollama" , label : "Ollama (Local)" } ,
344+ ] . filter ( o => o . value !== provider ) ,
346345 } ) ;
347- if ( ! p . isCancel ( safety ) ) next . safetyMode = safety as GlobalConfigShape [ "safetyMode" ] ;
346+ if ( p . isCancel ( fallback ) ) return abort ( ) ;
347+
348+ if ( fallback !== "none" ) {
349+ next . providers . fallbackOrder = [ fallback as string ] ;
350+ if ( fallback !== "ollama" && ! next . apiKeys [ fallback as string ] ) {
351+ const fallbackKey = await p . password ( { message : `Paste your ${ fallback } API key:` } ) ;
352+ if ( ! p . isCancel ( fallbackKey ) && fallbackKey ) {
353+ next . apiKeys [ fallback as string ] = fallbackKey as string ;
354+ next . providers . apiKeys [ fallback as string ] = fallbackKey as string ;
355+ }
356+ }
357+ } else {
358+ next . providers . fallbackOrder = [ ] ;
359+ }
360+
361+ // Step 6: Workspace Setup
362+ const workspace = await p . confirm ( {
363+ message : `5. Set up global workspace at ~/.splash?` ,
364+ initialValue : true ,
365+ } ) ;
366+ if ( p . isCancel ( workspace ) ) return abort ( ) ;
348367
368+ // Step 7: Theme Selection
349369 const theme = await p . select ( {
350- message : "5 . CLI Theme:" ,
370+ message : "6 . CLI Theme & UI Mode :" ,
351371 options : [
352- { value : "splash" , label : "Splash (Default) — Compact colored banner + minimal output " } ,
353- { value : "hydro " , label : "Hydro — Blue ANSI feel, compact " } ,
354- { value : "edge " , label : "Edge — No color, no banner, just prompt " } ,
355- { value : "silent" , label : "Silent — Pure prompt, silent execution" } ,
372+ { value : "splash" , label : "Splash — Compact banner, standard UI " } ,
373+ { value : "tui " , label : "TUI Dashboard — Interactive full-screen view " } ,
374+ { value : "hydro " , label : "Hydro — Blue ANSI feel " } ,
375+ { value : "silent" , label : "Silent — Pure execution" } ,
356376 ] ,
357377 } ) ;
358378 if ( ! p . isCancel ( theme ) ) {
359379 next . cli = next . cli || { } ;
360- next . cli . style = theme as "splash" | "hydro" | "edge" | "silent" ;
380+ if ( theme === "tui" ) {
381+ next . tui = true ;
382+ next . cli . style = "splash" ;
383+ } else {
384+ next . tui = false ;
385+ next . cli . style = theme as "splash" | "hydro" | "silent" ;
386+ }
361387 }
362388
389+ // Step 8: Generate Config
390+ const safety = await p . select ( {
391+ message : "7. Safety level:" ,
392+ options : [
393+ { value : "standard" , label : "Standard — confirms risky actions" } ,
394+ { value : "strict" , label : "Strict — confirms every action" } ,
395+ { value : "permissive" , label : "Permissive — fully autonomous" } ,
396+ ] ,
397+ } ) ;
398+ if ( ! p . isCancel ( safety ) ) {
399+ next . safetyMode = safety as GlobalConfigShape [ "safetyMode" ] ;
400+ next . safety = { mode : safety as GlobalConfigShape [ "safetyMode" ] } ;
401+ }
402+
403+ p . log . message ( "8. Generating configuration..." ) ;
363404 writeGlobalConfig ( next ) ;
364405 p . outro ( pc . green ( `[OK] Saved to ${ globalConfigPath ( ) } ` ) ) ;
365406 console . log ( pc . dim ( `\nTry it: ${ pc . cyan ( "splash \"summarize this folder\"" ) } ` ) ) ;
@@ -950,6 +991,69 @@ Output ONLY a list of crisp, actionable rules you should adopt. Do not explain t
950991 console . log ( getOutput ( result ) ) ;
951992}
952993
994+ async function runSelfModify ( args : string [ ] ) {
995+ const isAutoApprove = args . includes ( "--apply" ) ;
996+ const instruction = args . filter ( a => a !== "--apply" ) . join ( " " ) ;
997+ if ( ! instruction ) {
998+ console . error ( pc . red ( "Usage: splash self-modify [--apply] <instruction>" ) ) ;
999+ return ;
1000+ }
1001+
1002+ const { SelfModifier } = await import ( "@alpclaw/core" ) ;
1003+ const modifier = new SelfModifier ( isAutoApprove ) ;
1004+
1005+ console . log ( pc . magenta ( "\nSPLASH SELF-MODIFIER" ) ) ;
1006+ console . log ( pc . dim ( `Instruction: ${ instruction } ` ) ) ;
1007+ console . log ( pc . dim ( `Mode: ${ isAutoApprove ? "APPLY" : "DRY-RUN" } \n` ) ) ;
1008+
1009+ const alpclaw = await buildAgent ( ) ;
1010+ const prompt = `You are a self-modifying engine. The user has asked you to: ${ instruction }
1011+ Analyze the codebase in the current working directory, figure out which file needs changing, and output a JSON array of objects with 'file' and 'content'.
1012+ Example: [{"file": "packages/core/src/index.ts", "content": "export const a = 1;"}]
1013+ Do NOT use markdown blocks around the JSON. Output pure JSON.` ;
1014+
1015+ const res = await alpclaw . createAgent ( { onPhaseChange : ( ) => { } } ) . run ( prompt ) ;
1016+ const out = getOutput ( res ) ;
1017+
1018+ try {
1019+ let jsonStr = out ;
1020+ if ( jsonStr . includes ( "\`\`\`json" ) ) {
1021+ jsonStr = jsonStr . split ( "\`\`\`json" ) [ 1 ] . split ( "\`\`\`" ) [ 0 ] . trim ( ) ;
1022+ }
1023+ const changes = JSON . parse ( jsonStr ) ;
1024+ for ( const change of changes ) {
1025+ if ( change . file && change . content ) {
1026+ const result = await modifier . applyChange ( change . file , change . content , instruction ) ;
1027+ if ( ! result . ok ) {
1028+ console . error ( pc . red ( `[ERR] Failed to apply to ${ change . file } : ${ result . error . message } ` ) ) ;
1029+ } else {
1030+ console . log ( pc . green ( `[OK] Applied to ${ change . file } ` ) ) ;
1031+ }
1032+ }
1033+ }
1034+ } catch ( e : any ) {
1035+ console . error ( pc . red ( "[ERR] Failed to parse agent response as JSON" ) , e . message ) ;
1036+ console . log ( out ) ;
1037+ }
1038+ }
1039+
1040+ async function runSelfRollback ( args : string [ ] ) {
1041+ const timestamp = args [ 0 ] ;
1042+ if ( ! timestamp ) {
1043+ console . error ( pc . red ( "Usage: splash self-rollback <timestamp>" ) ) ;
1044+ return ;
1045+ }
1046+ const { SelfModifier } = await import ( "@alpclaw/core" ) ;
1047+ const modifier = new SelfModifier ( true ) ;
1048+ const result = await modifier . rollback ( timestamp ) ;
1049+ if ( ! result . ok ) {
1050+ console . error ( pc . red ( `[ERR] Rollback failed: ${ result . error . message } ` ) ) ;
1051+ } else {
1052+ console . log ( pc . green ( `[OK] Rolled back to ${ timestamp } ` ) ) ;
1053+ }
1054+ }
1055+
1056+
9531057// ──────────────────────────────────────────────────────────────────────────
9541058// Voice
9551059// ──────────────────────────────────────────────────────────────────────────
@@ -1330,8 +1434,44 @@ async function runMemory(args: string[]): Promise<void> {
13301434 return ;
13311435 }
13321436
1437+ if ( sub === "import" ) {
1438+ const inFile = args [ 1 ] ;
1439+ if ( ! inFile || ! fs . existsSync ( inFile ) ) {
1440+ console . error ( pc . red ( "Usage: splash memory import <file.json>" ) ) ;
1441+ return ;
1442+ }
1443+ try {
1444+ const content = fs . readFileSync ( inFile , "utf-8" ) ;
1445+ const entries = JSON . parse ( content ) ;
1446+ if ( ! Array . isArray ( entries ) ) throw new Error ( "Expected JSON array" ) ;
1447+
1448+ const newSessionId = "imported_" + Date . now ( ) ;
1449+ const newFile = path . join ( sessionsDir , newSessionId + ".jsonl" ) ;
1450+ if ( ! fs . existsSync ( sessionsDir ) ) fs . mkdirSync ( sessionsDir , { recursive : true } ) ;
1451+
1452+ const lines = entries . map ( e => JSON . stringify ( e ) ) . join ( "\n" ) + "\n" ;
1453+ fs . writeFileSync ( newFile , lines , "utf-8" ) ;
1454+ console . log ( pc . green ( `Imported ${ entries . length } entries into session ${ newSessionId } ` ) ) ;
1455+ } catch ( e : any ) {
1456+ console . error ( pc . red ( `Failed to import: ${ e . message } ` ) ) ;
1457+ }
1458+ return ;
1459+ }
1460+
1461+ if ( sub === "clear" ) {
1462+ if ( fs . existsSync ( sessionsDir ) ) {
1463+ fs . rmSync ( sessionsDir , { recursive : true , force : true } ) ;
1464+ }
1465+ const knowledgePath = path . join ( memDir , "knowledge.jsonl" ) ;
1466+ if ( fs . existsSync ( knowledgePath ) ) {
1467+ fs . rmSync ( knowledgePath , { force : true } ) ;
1468+ }
1469+ console . log ( pc . green ( "Cleared all memory." ) ) ;
1470+ return ;
1471+ }
1472+
13331473 console . error ( pc . red ( `Unknown subcommand: memory ${ sub } ` ) ) ;
1334- console . log ( pc . dim ( " Available: list, search <query>, export [file]" ) ) ;
1474+ console . log ( pc . dim ( " Available: list, search <query>, export [file], import <file>, clear " ) ) ;
13351475}
13361476
13371477// ──────────────────────────────────────────────────────────────────────────
0 commit comments