whatsappImportUnknown = regexp.MustCompile(`^\[WhatsApp\] (.*), [A-Za-z]+ (\d{4}-\d{2}-\d{2})$`)
)
+// WhatsAppImportPlan holds the new chat-day batches and the size of the merged
+// log. Its estimate uses the measured cost of 1.3 calls per message, not a
+// promise about a particular export or compaction retry.
+type WhatsAppImportPlan struct {
+ agent string
+ cutoffs map[string]time.Time
+ added []DatedMessage
+ chats []string
+ cutoff time.Time
+ first time.Time
+ last time.Time
+ bytes int
+ messages int
+ skipped int
+ ownerKnown bool
+}
+
+// WhatsAppImportOptions selects the target, exports, and plan-only mode.
+type WhatsAppImportOptions struct {
+ planOnly bool
+ agent string
+ paths []string
+}
+
+// Write prints the plan without chat text or the owner's profile name.
+func (p WhatsAppImportPlan) Write(out io.Writer) error {
+ cutoff := "none (no live WhatsApp batch)"
+ if !p.cutoff.IsZero() {
+ cutoff = p.cutoff.Format(time.RFC3339)
+ }
+
+ dateRange := "none"
+ if !p.first.IsZero() {
+ dateRange = p.first.Format(time.RFC3339) + " to " + p.last.Format(time.RFC3339)
+ }
+
+ owner := "known (mapped to You)"
+ if !p.ownerKnown {
+ owner = "unknown (sender names kept as written)"
+ }
+
+ _, err := fmt.Fprintf(out, "agent: %s\nchats: %d\n", p.agent, len(p.chats))
+ if err != nil {
+ return fmt.Errorf("write import plan: %w", err)
+ }
+
+ for _, chat := range p.chats {
+ line := " " + chat
+ cutoff, found := p.cutoffs[chat]
+ if found {
+ line += " (cutoff: " + cutoff.Format(time.RFC3339) + ")"
+ }
+
+ _, err = fmt.Fprintln(out, line)
+ if err != nil {
+ return fmt.Errorf("write import plan: %w", err)
+ }
+ }
+
+ estimate := 1.3 * float64(p.messages)
+ if len(p.added) == 0 {
+ estimate = 0
+ }
+
+ lines := []string{
+ fmt.Sprintf("batches: %d (%d chat-days skipped)", len(p.added), p.skipped),
+ "dates: " + dateRange,
+ "cutoff: " + cutoff,
+ "owner: " + owner,
+ fmt.Sprintf("bytes: %d (new batch text)", p.bytes),
+ fmt.Sprintf("messages: %d (merged log)", p.messages),
+ fmt.Sprintf("estimated calls: %.0f (about 1.3 per message, limit %d)", estimate, rebuildCallLimit),
+ }
+
+ _, err = io.WriteString(out, strings.Join(lines, "\n")+"\n")
+ if err != nil {
+ return fmt.Errorf("write import plan: %w", err)
+ }
+
+ return nil
+}
+
// whatsappImport runs the offline import command. It holds the same token lock
// as the daemon and probe, even for a plan. The plan reads no credentials for a
// model and makes no network call. Only a completed rebuild replaces the agent
return nil
}
-// WhatsAppImportPlan holds the new chat-day batches and the size of the merged
-// log. Its estimate uses the measured cost of 1.3 calls per message, not a
-// promise about a particular export or compaction retry.
-type WhatsAppImportPlan struct {
- agent string
- cutoffs map[string]time.Time
- added []DatedMessage
- chats []string
- cutoff time.Time
- first time.Time
- last time.Time
- bytes int
- messages int
- skipped int
- ownerKnown bool
-}
-
-// Write prints the plan without chat text or the owner's profile name.
-func (p WhatsAppImportPlan) Write(out io.Writer) error {
- cutoff := "none (no live WhatsApp batch)"
- if !p.cutoff.IsZero() {
- cutoff = p.cutoff.Format(time.RFC3339)
- }
-
- dateRange := "none"
- if !p.first.IsZero() {
- dateRange = p.first.Format(time.RFC3339) + " to " + p.last.Format(time.RFC3339)
- }
-
- owner := "known (mapped to You)"
- if !p.ownerKnown {
- owner = "unknown (sender names kept as written)"
- }
-
- _, err := fmt.Fprintf(out, "agent: %s\nchats: %d\n", p.agent, len(p.chats))
- if err != nil {
- return fmt.Errorf("write import plan: %w", err)
- }
-
- for _, chat := range p.chats {
- line := " " + chat
- cutoff, found := p.cutoffs[chat]
- if found {
- line += " (cutoff: " + cutoff.Format(time.RFC3339) + ")"
- }
-
- _, err = fmt.Fprintln(out, line)
- if err != nil {
- return fmt.Errorf("write import plan: %w", err)
- }
- }
-
- estimate := 1.3 * float64(p.messages)
- if len(p.added) == 0 {
- estimate = 0
- }
-
- lines := []string{
- fmt.Sprintf("batches: %d (%d chat-days skipped)", len(p.added), p.skipped),
- "dates: " + dateRange,
- "cutoff: " + cutoff,
- "owner: " + owner,
- fmt.Sprintf("bytes: %d (new batch text)", p.bytes),
- fmt.Sprintf("messages: %d (merged log)", p.messages),
- fmt.Sprintf("estimated calls: %.0f (about 1.3 per message, limit %d)", estimate, rebuildCallLimit),
- }
-
- _, err = io.WriteString(out, strings.Join(lines, "\n")+"\n")
- if err != nil {
- return fmt.Errorf("write import plan: %w", err)
- }
-
- return nil
-}
-
-// WhatsAppImportOptions selects the target, exports, and plan-only mode.
-type WhatsAppImportOptions struct {
- planOnly bool
- agent string
- paths []string
-}
-
// whatsappImportParse accepts GNU-style flags, including --agent=NAME and --
// before paths that start with a dash.
func whatsappImportParse(args []string) (WhatsAppImportOptions, error) {