@@ -305,6 +305,40 @@ def main():
305305 parser = argparse .ArgumentParser (prog = "braindump" , description = "Personal expression material library" )
306306 sub = parser .add_subparsers (dest = "command" )
307307
308+ # init
309+ init_p = sub .add_parser ("init" , help = "Initialize data directory, config, and database" )
310+ init_p .add_argument ("--data-dir" , default = None , help = "Data directory (default: ~/braindump-data)" )
311+ init_p .add_argument ("--force" , action = "store_true" , help = "Overwrite config.toml if it already exists" )
312+ init_p .add_argument ("--json" , action = "store_true" , help = "Print machine-readable JSON" )
313+
314+ # add
315+ add_p = sub .add_parser ("add" , help = "Create a text note from CLI" )
316+ add_p .add_argument ("text" , nargs = "?" , help = "Text content. Use --stdin to read from stdin" )
317+ add_p .add_argument ("--stdin" , action = "store_true" , help = "Read note content from stdin" )
318+ add_p .add_argument ("--tag" , action = "append" , default = [], help = "Add a tag (repeatable)" )
319+ add_p .add_argument ("--tags" , default = "" , help = "Comma-separated tags" )
320+ add_p .add_argument ("--json" , action = "store_true" , help = "Print machine-readable JSON" )
321+
322+ # list
323+ list_p = sub .add_parser ("list" , help = "List recent notes" )
324+ list_p .add_argument ("--limit" , type = int , default = 20 )
325+ list_p .add_argument ("--offset" , type = int , default = 0 )
326+ list_p .add_argument ("--type" , dest = "media_type" , default = None , help = "Filter by media type" )
327+ list_p .add_argument ("--tag" , default = None , help = "Filter by tag" )
328+ list_p .add_argument ("--q" , default = None , help = "Full-text search query" )
329+ list_p .add_argument ("--json" , action = "store_true" , help = "Print machine-readable JSON" )
330+
331+ # search
332+ search_p = sub .add_parser ("search" , help = "Search notes" )
333+ search_p .add_argument ("query" , help = "Search query" )
334+ search_p .add_argument ("--limit" , type = int , default = 20 )
335+ search_p .add_argument ("--json" , action = "store_true" , help = "Print machine-readable JSON" )
336+
337+ # show
338+ show_p = sub .add_parser ("show" , help = "Show a note" )
339+ show_p .add_argument ("note_id" , type = int )
340+ show_p .add_argument ("--json" , action = "store_true" , help = "Print machine-readable JSON" )
341+
308342 # serve
309343 sub .add_parser ("serve" , help = "Start all services (Bot + Web + Transcribe + Summary)" )
310344
@@ -323,7 +357,8 @@ def main():
323357 sub .add_parser ("bot" , help = "Start Telegram Bot only" )
324358
325359 # stats
326- sub .add_parser ("stats" , help = "Show statistics" )
360+ stats_p = sub .add_parser ("stats" , help = "Show statistics" )
361+ stats_p .add_argument ("--json" , action = "store_true" , help = "Print machine-readable JSON" )
327362
328363 # upgrade
329364 sub .add_parser ("upgrade" , help = "Run database migrations" )
@@ -355,7 +390,94 @@ def main():
355390 parser .print_help ()
356391 sys .exit (1 )
357392
358- if args .command == "web" :
393+ if args .command == "init" :
394+ from pathlib import Path
395+ from braindump .cli import emit , init_project
396+
397+ result = asyncio .run (init_project (Path (args .data_dir ) if args .data_dir else None , force = args .force ))
398+ emit (
399+ result ,
400+ args .json ,
401+ [
402+ f"Initialized braindump data dir: { result ['data_dir' ]} " ,
403+ f"Config: { result ['config_path' ]} " ,
404+ f"Database: { result ['db_path' ]} " ,
405+ "Next: braindump add \" your thought\" --json" ,
406+ ],
407+ )
408+
409+ elif args .command == "add" :
410+ from braindump .cli import add_text_note , emit , read_stdin_or_arg
411+
412+ try :
413+ content = read_stdin_or_arg (args .text , args .stdin )
414+ result = asyncio .run (add_text_note (content , args .tag , args .tags ))
415+ except ValueError as exc :
416+ print (f"error: { exc } " , file = sys .stderr )
417+ sys .exit (2 )
418+ emit (
419+ result ,
420+ args .json ,
421+ [
422+ f"Saved note #{ result ['id' ]} " ,
423+ f"File: { result ['file_path' ]} " ,
424+ ],
425+ )
426+
427+ elif args .command == "list" :
428+ from braindump .cli import emit , list_notes
429+
430+ result = asyncio .run (
431+ list_notes (
432+ limit = args .limit ,
433+ offset = args .offset ,
434+ media_type = args .media_type ,
435+ tag = args .tag ,
436+ query = args .q ,
437+ )
438+ )
439+ emit (
440+ result ,
441+ args .json ,
442+ [
443+ f"{ note ['id' ]} \t { note ['created_at' ]} \t { note ['type' ]} \t { note ['title' ]} "
444+ for note in result ["notes" ]
445+ ],
446+ )
447+
448+ elif args .command == "search" :
449+ from braindump .cli import emit , search_notes
450+
451+ result = asyncio .run (search_notes (args .query , limit = args .limit ))
452+ emit (
453+ result ,
454+ args .json ,
455+ [
456+ f"{ note ['id' ]} \t { note ['created_at' ]} \t { note ['type' ]} \t { note ['title' ]} "
457+ for note in result ["notes" ]
458+ ],
459+ )
460+
461+ elif args .command == "show" :
462+ from braindump .cli import emit , show_note
463+
464+ try :
465+ result = asyncio .run (show_note (args .note_id ))
466+ except LookupError as exc :
467+ print (f"error: { exc } " , file = sys .stderr )
468+ sys .exit (3 )
469+ note = result ["note" ]
470+ content = note .get ("content" ) or note .get ("transcript" ) or ""
471+ emit (
472+ result ,
473+ args .json ,
474+ [
475+ f"#{ note ['id' ]} { note ['created_at' ]} [{ note ['media_type' ]} ]" ,
476+ content ,
477+ ],
478+ )
479+
480+ elif args .command == "web" :
359481 from braindump .web .app import run_web
360482 run_web (host = args .host , port = args .port )
361483
@@ -391,8 +513,14 @@ def main():
391513 asyncio .run (run_migrations ())
392514
393515 elif args .command == "stats" :
394- from braindump .database import show_stats
395- asyncio .run (show_stats ())
516+ if args .json :
517+ from braindump .cli import emit
518+ from braindump .database import get_stats
519+
520+ emit (asyncio .run (get_stats ()), True )
521+ else :
522+ from braindump .database import show_stats
523+ asyncio .run (show_stats ())
396524
397525 elif args .command == "rebuild-index" :
398526 from braindump .database import rebuild_index
0 commit comments