//! `koh save` and `koh checkpoint`. module cli import json2 import src.fmt import src.history import src.snapshot pub fn cmd_save(raw []string, is_checkpoint bool) int { verb := if is_checkpoint { 'checkpoint' } else { 'save' } args := parse(verb, raw, Options{ values: ['agent', 'session'] flags: ['json'] }) or { return usage_error(err.msg()) } message := args.positional_at(0) if message == '' { example := if is_checkpoint { 'before combine replay engine' } else { 'fix off-by-one in chunk boundary' } return usage_error('${verb} needs a message, e.g. koh ${verb} "${example}"') } store := open_store() or { return refuse(args.json(), err.msg()) } defer { store.close() } walked := snapshot.walk_report(store, store.root, snapshot.load(store.root)) or { return refuse(args.json(), err.msg()) } save := history.save_worktree(store, store.root, snapshot.load(store.root), history.SaveInput{ message: message agent: args.value('agent') session: args.value('session') is_checkpoint: is_checkpoint }) or { return refuse(args.json(), err.msg()) } files := history.files_of(store, save.id) or { []snapshot.FileEntry{} } if args.json() { emit_json(json2.encode(SaveOut{ id: save.id parent: save.parent message: save.message created_at: save.created_at machine: save.machine is_checkpoint: save.is_checkpoint agent: save.agent session: save.session files: files.len size_bytes: save.size_bytes unsafe_paths: walked.unsafe_paths })) auto_offer(store, true) return 0 } for path in walked.unsafe_paths { warn('not saved: unsafe name ${path}') } ok('saved [${save.id}] ${save.message}') mut detail := []string{} if save.is_checkpoint { detail << 'checkpoint' } detail << fmt.stamp(save.created_at) detail << fmt.plural(files.len, 'file') detail << fmt.human_bytes(save.size_bytes) note(detail.join(' · ')) mut attribution := []string{} if save.agent != '' { attribution << 'agent: ${save.agent}' } if save.session != '' { attribution << 'session: ${save.session}' } if attribution.len > 0 { note(attribution.join(' · ')) } auto_offer(store, false) return 0 } // Every field is always present, so a consumer never has to tell "absent" from // "missing key". An empty string means the save recorded none. struct SaveOut { id string parent string message string created_at i64 machine string is_checkpoint bool agent string session string files int size_bytes i64 unsafe_paths []string }