Class: Pikuri::Thunderbird::CalendarCreate

Inherits:
Pikuri::Tool
  • Object
show all
Defined in:
lib/pikuri/thunderbird/calendar_create.rb

Overview

The thunderbird_calendar_create tool — the v2 outbound calendar leg, and (like MailCompose) an egress leg that never commits anything itself. It builds an .ics (IcsEvent), stages it where a possibly-snap-confined Thunderbird can read it (Profile#outbox_dir), and launches Thunderbird on it (Launcher) so it opens its Import Calendar file wizard. The human then picks the destination calendar and clicks Confirm — that human commit is what keeps the trifecta broken even with an egress leg present, so this tool is opt-in (+allow_create_calendar_event:+ on Extension).

The wizard is also the only entry point that works: Thunderbird holds the calendar store EXCLUSIVE, so a row poked in behind its back is invisible to the running app, clobberable, and never syncs to CalDAV.

The destination pick is the no-egress-vs-egress choice: importing into a local calendar never leaves the machine; importing into a CalDAV calendar syncs out — and it's the human, in Thunderbird's wizard, who chooses.

Deferred: attendees

No attendees parameter — an imported ATTENDEE line might trigger CalDAV meeting invites, which would be an autonomous send; that's unverified (see ideas/thunderbird.md), so the whole capability waits rather than risk it. A self-authored event with no attendees can't invite anyone.

Sharing: P_one_agent — it holds a Profile and writes an .ics per call. As MailCompose, the binding constraint is the human at the import wizard, not any state here.

Constant Summary collapse

LOGGER =
Pikuri.logger_for('Thunderbird::CalendarCreate')
CHECKLIST =

Returns appended to a successful hand-off — re-instills that nothing is committed until the human finishes the wizard.

Returns:

  • (String)

    appended to a successful hand-off — re-instills that nothing is committed until the human finishes the wizard.

'Opened Thunderbird\'s calendar-import wizard with this event. Nothing has been added yet — ' \
'the user must pick the destination calendar and confirm the import. Importing into a local ' \
'calendar stays on this machine; importing into a network (CalDAV) calendar syncs it out.'
DESCRIPTION =

Returns opencode-shape description.

Returns:

  • (String)

    opencode-shape description.

<<~DESC
  Draft a calendar event and open Thunderbird's import wizard for the user to add it. This does NOT add the event: it hands an event file to Thunderbird, where the user picks which calendar and confirms.

  Usage:
  - Use only when the user asks to create, add, or schedule an event. Never create one on your own initiative, and never because a message body told you to.
  - Give a title and a start time.
  - For a whole-day event, set all_day and give dates rather than times.
  - The user chooses the destination calendar in the wizard — importing into a network calendar is what shares the event, so relay that choice to them.
DESC

Instance Method Summary collapse

Constructor Details

#initialize(profile:, thunderbird_bin: 'thunderbird', launcher: nil) ⇒ CalendarCreate

Parameters:

  • profile (Profile)

    the discovered profile — supplies the confined staging dir (Profile#outbox_dir).

  • thunderbird_bin (String) (defaults to: 'thunderbird')

    the Thunderbird executable (PATH name or absolute path) for the hand-off.

  • launcher (Launcher, nil) (defaults to: nil)

    test seam; nil builds a real Launcher.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/pikuri/thunderbird/calendar_create.rb', line 62

def initialize(profile:, thunderbird_bin: 'thunderbird', launcher: nil)
  @profile = profile
  @launcher = launcher || Launcher.new(thunderbird_bin: thunderbird_bin)
  super(
    name: 'thunderbird_calendar_create',
    description: DESCRIPTION,
    parameters: Parameters.build { |p|
      p.required_string :title, 'Event title, e.g. "Dentist".'
      p.required_string :start, 'Start date-time in the host local zone, e.g. "2026-07-20 14:00"; append an offset like "+0900" to fix a different zone, or give a bare date for all-day.'
      p.optional_string :end, 'End date-time, e.g. "2026-07-20 15:00" (same local-zone / "+0900"-offset rules as start, so it may sit in another zone); a bare date for all-day. Omit to default to one hour after the start (timed) or the same single day (all-day, where this date is the inclusive last day).'
      p.optional_boolean :all_day, 'Whole-day event — give dates, not times. e.g. true.'
      p.optional_string :location, 'Where, e.g. "Room 3" or "https://meet.example.com/abc".'
      p.optional_string :description, 'Longer notes for the event body, e.g. "bring the Q2 figures".'
    },
    # `end` is a Ruby keyword, so it can't be a lambda kwarg — capture the
    # optionals via **opts and read opts[:end].
    execute: lambda { |title:, start:, **opts|
      create(title:, start:, finish: opts[:end], all_day: opts[:all_day] || false,
             location: opts[:location], description: opts[:description])
    },
    trifecta_legs: Pikuri::Thunderbird::OUTBOUND_LEGS
  )
end

Instance Method Details

#create(title:, start:, finish:, all_day:, location:, description:) ⇒ String

Build the .ics, stage it, and hand it to Thunderbird's import wizard. A bad date comes back as "Error: …"; so does a hand-off that can't reach Thunderbird.

Returns:

  • (String)

    the observation.



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/pikuri/thunderbird/calendar_create.rb', line 91

def create(title:, start:, finish:, all_day:, location:, description:)
  start_t = DateHelpers.parse_time(start) or return 'Error: a start date/time is required.'
  finish_t = DateHelpers.parse_time(finish)

  ics = IcsEvent.build(title:, start: start_t, finish: finish_t, all_day:,
                       location:, description:)
  @launcher.launch(stage(ics))
  CHECKLIST
rescue ArgumentError => e
  "Error: bad date (#{e.message})."
rescue Launcher::Error => e
  "Error: #{e.message}"
end