@@ -4,8 +4,9 @@ import { Command } from "commander";
44import chalk from "chalk" ;
55import readline from "readline" ;
66import { execSync } from "node:child_process" ;
7- import { readFileSync } from "node:fs" ;
7+ import { readFileSync , writeFileSync , mkdirSync , existsSync } from "node:fs" ;
88import { join } from "node:path" ;
9+ import { homedir } from "node:os" ;
910
1011let PKG_VERSION = "0.1.8" ;
1112try {
@@ -266,15 +267,140 @@ program
266267 await emailGate ( ) ;
267268 } ) ;
268269
269- program
270+ const storeCmd = program
270271 . command ( "store" )
271272 . description ( "Browse the module marketplace" )
272- . argument ( "[action]" , "search | info | publish" )
273- . argument ( "[query]" , "search query or module name" )
274273 . action ( async ( ) => {
275274 await emailGate ( ) ;
276275 } ) ;
277276
277+ storeCmd
278+ . command ( "search <query>" )
279+ . description ( "Search for modules in the store" )
280+ . action ( async ( ) => {
281+ await emailGate ( ) ;
282+ } ) ;
283+
284+ storeCmd
285+ . command ( "publish <url>" )
286+ . description ( "Publish a module from a git URL or web URL" )
287+ . action ( async ( url : string ) => {
288+ console . log ( LOGO ) ;
289+
290+ // 1. Get email
291+ const configPath = join ( homedir ( ) , ".threatcrush" , "config.json" ) ;
292+ let email = "" ;
293+ try {
294+ const config = JSON . parse ( readFileSync ( configPath , "utf-8" ) ) ;
295+ email = config . email || "" ;
296+ } catch { }
297+
298+ if ( ! email ) {
299+ const rl = readline . createInterface ( { input : process . stdin , output : process . stdout } ) ;
300+ email = await new Promise < string > ( ( resolve ) => {
301+ rl . question ( chalk . green ( " Enter your email: " ) , ( answer ) => {
302+ rl . close ( ) ;
303+ resolve ( answer . trim ( ) ) ;
304+ } ) ;
305+ } ) ;
306+
307+ if ( ! email || ! email . includes ( "@" ) ) {
308+ console . log ( chalk . red ( "\n Invalid email.\n" ) ) ;
309+ return ;
310+ }
311+
312+ // Save email
313+ try {
314+ const dir = join ( homedir ( ) , ".threatcrush" ) ;
315+ if ( ! existsSync ( dir ) ) mkdirSync ( dir , { recursive : true } ) ;
316+ writeFileSync ( configPath , JSON . stringify ( { email } , null , 2 ) ) ;
317+ console . log ( chalk . dim ( ` Saved email to ${ configPath } ` ) ) ;
318+ } catch { }
319+ }
320+
321+ console . log ( chalk . dim ( `\n Fetching metadata from ${ url } ...\n` ) ) ;
322+
323+ // 2. Fetch metadata
324+ let meta : Record < string , unknown > ;
325+ try {
326+ const res = await fetch ( `${ API_URL } /api/modules/fetch-meta` , {
327+ method : "POST" ,
328+ headers : { "Content-Type" : "application/json" } ,
329+ body : JSON . stringify ( { url, git_url : url } ) ,
330+ } ) ;
331+ if ( ! res . ok ) {
332+ const err = await res . json ( ) . catch ( ( ) => ( { error : res . statusText } ) ) ;
333+ console . log ( chalk . red ( ` ✗ Failed to fetch metadata: ${ ( err as Record < string , string > ) . error } \n` ) ) ;
334+ return ;
335+ }
336+ meta = await res . json ( ) as Record < string , unknown > ;
337+ } catch ( err ) {
338+ console . log ( chalk . red ( ` ✗ Failed to fetch metadata: ${ err instanceof Error ? err . message : err } \n` ) ) ;
339+ return ;
340+ }
341+
342+ // 3. Preview
343+ console . log ( chalk . green ( " ── Module Preview ──\n" ) ) ;
344+ console . log ( ` ${ chalk . bold ( "Name:" ) } ${ meta . name || chalk . dim ( "(none)" ) } ` ) ;
345+ console . log ( ` ${ chalk . bold ( "Display:" ) } ${ meta . display_name || chalk . dim ( "(none)" ) } ` ) ;
346+ console . log ( ` ${ chalk . bold ( "Description:" ) } ${ meta . description || chalk . dim ( "(none)" ) } ` ) ;
347+ console . log ( ` ${ chalk . bold ( "Version:" ) } ${ meta . version || "0.1.0" } ` ) ;
348+ console . log ( ` ${ chalk . bold ( "License:" ) } ${ meta . license || "MIT" } ` ) ;
349+ console . log ( ` ${ chalk . bold ( "Author:" ) } ${ meta . author_name || chalk . dim ( "(none)" ) } ` ) ;
350+ console . log ( ` ${ chalk . bold ( "Homepage:" ) } ${ meta . homepage_url || chalk . dim ( "(none)" ) } ` ) ;
351+ console . log ( ` ${ chalk . bold ( "Git:" ) } ${ meta . git_url || url } ` ) ;
352+ if ( Array . isArray ( meta . tags ) && meta . tags . length > 0 ) {
353+ console . log ( ` ${ chalk . bold ( "Tags:" ) } ${ ( meta . tags as string [ ] ) . join ( ", " ) } ` ) ;
354+ }
355+ if ( meta . stars ) {
356+ console . log ( ` ${ chalk . bold ( "Stars:" ) } ⭐ ${ meta . stars } ` ) ;
357+ }
358+ console . log ( ) ;
359+
360+ // 4. Confirm
361+ const rl = readline . createInterface ( { input : process . stdin , output : process . stdout } ) ;
362+ const confirm = await new Promise < string > ( ( resolve ) => {
363+ rl . question ( chalk . yellow ( " Publish this module? (y/N): " ) , ( answer ) => {
364+ rl . close ( ) ;
365+ resolve ( answer . trim ( ) . toLowerCase ( ) ) ;
366+ } ) ;
367+ } ) ;
368+
369+ if ( confirm !== "y" && confirm !== "yes" ) {
370+ console . log ( chalk . dim ( "\n Cancelled.\n" ) ) ;
371+ return ;
372+ }
373+
374+ // 5. Publish
375+ console . log ( chalk . dim ( "\n Publishing..." ) ) ;
376+ try {
377+ const res = await fetch ( `${ API_URL } /api/modules` , {
378+ method : "POST" ,
379+ headers : { "Content-Type" : "application/json" } ,
380+ body : JSON . stringify ( {
381+ ...meta ,
382+ author_email : email ,
383+ git_url : ( meta . git_url as string ) || url ,
384+ homepage_url : ( meta . homepage_url as string ) || url ,
385+ } ) ,
386+ } ) ;
387+
388+ const result = await res . json ( ) as Record < string , unknown > ;
389+
390+ if ( ! res . ok ) {
391+ console . log ( chalk . red ( `\n ✗ Publish failed: ${ ( result as Record < string , string > ) . error } \n` ) ) ;
392+ return ;
393+ }
394+
395+ const mod = result . module as Record < string , string > ;
396+ const slug = mod ?. slug || meta . name ;
397+ console . log ( chalk . green ( `\n ✓ Module published!` ) ) ;
398+ console . log ( chalk . dim ( ` ${ API_URL } /store/${ slug } \n` ) ) ;
399+ } catch ( err ) {
400+ console . log ( chalk . red ( `\n ✗ Publish failed: ${ err instanceof Error ? err . message : err } \n` ) ) ;
401+ }
402+ } ) ;
403+
278404// Default action (no command — show help)
279405program . action ( ( ) => {
280406 console . log ( LOGO ) ;
0 commit comments