circal-mcp 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -10
- package/dist/circal-mcp.mjs +38 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,16 +46,25 @@ zone, revision) plus a ready-to-paste config block. Paste that block into
|
|
|
46
46
|
your client below, swap the path if it picked up the wrong file, then
|
|
47
47
|
restart the client.
|
|
48
48
|
|
|
49
|
-
> **Use `circal-mcp@0.
|
|
50
|
-
> The cautionary tale behind that floor: `0.1.0` predated
|
|
51
|
-
> `src/lib` (`free`, `remindLead`, `organizer` and the guest pair
|
|
52
|
-
> event; `backupNudge`, `backupInterval`, `deviceZone`, `stampedZones`
|
|
53
|
-
> settings), and every write round-trips the whole document through its
|
|
54
|
-
> reader, so it silently dropped all
|
|
55
|
-
> bundles.
|
|
56
|
-
>
|
|
57
|
-
>
|
|
58
|
-
>
|
|
49
|
+
> **Use `circal-mcp@0.5.0` or newer** (`npx -y circal-mcp` resolves it).
|
|
50
|
+
> The cautionary tale behind that floor: `0.1.0` predated ten fields in
|
|
51
|
+
> `src/lib` (`free`, `travel`, `remindLead`, `organizer` and the guest pair
|
|
52
|
+
> on an event; `backupNudge`, `backupInterval`, `deviceZone`, `stampedZones`
|
|
53
|
+
> in settings), and every write round-trips the whole document through its
|
|
54
|
+
> reader, so it silently dropped all ten — measured, same fixture, two
|
|
55
|
+
> bundles.
|
|
56
|
+
>
|
|
57
|
+
> **The version gate does not save you from this, and the floor is why.** It
|
|
58
|
+
> refuses a document whose _envelope_ is newer than the server's, and an
|
|
59
|
+
> envelope version is bumped only when a whole key changes meaning — the
|
|
60
|
+
> `tasks` list going missing was one. A new field on an event is not: `travel`
|
|
61
|
+
> shipped at `MIRROR_VERSION` 3 like `free` before it, so a `0.4.1` bundle
|
|
62
|
+
> reads `3 <= 3`, writes happily, and erases the field from every event in the
|
|
63
|
+
> file. The gates stop only what they can see coming (`src/lib/mirror.ts`).
|
|
64
|
+
> Reads are always safe; it is writes that lose data. So: **republish and
|
|
65
|
+
> reinstall the server whenever a `CalEvent` or `Settings` field lands**, and
|
|
66
|
+
> raise this floor in the same commit. Contributors run against their own
|
|
67
|
+
> build with `pnpm build:mcp` → `node mcp/dist/circal-mcp.mjs`.
|
|
59
68
|
|
|
60
69
|
### Claude Desktop
|
|
61
70
|
|
package/dist/circal-mcp.mjs
CHANGED
|
@@ -2217,6 +2217,12 @@ function readRemindLead(value) {
|
|
|
2217
2217
|
if (value < 0 || value > REMIND_LEAD_MAX) return REMIND_INHERIT;
|
|
2218
2218
|
return value;
|
|
2219
2219
|
}
|
|
2220
|
+
var TRAVEL_MAX = MINUTES_PER_DAY;
|
|
2221
|
+
function readTravel(value) {
|
|
2222
|
+
if (typeof value !== "number" || !Number.isInteger(value)) return 0;
|
|
2223
|
+
if (value < 0 || value > TRAVEL_MAX) return 0;
|
|
2224
|
+
return value;
|
|
2225
|
+
}
|
|
2220
2226
|
var PARTSTATS = ["needs-action", "accepted", "declined", "tentative"];
|
|
2221
2227
|
var ATTENDEE_MAX = 32;
|
|
2222
2228
|
var GUEST_NAME_MAX = 64;
|
|
@@ -2279,6 +2285,10 @@ function readGraves(value) {
|
|
|
2279
2285
|
}
|
|
2280
2286
|
return [...latest].map(([id, at]) => ({ id, at })).sort((a, b) => b.at - a.at).slice(0, GRAVE_MAX);
|
|
2281
2287
|
}
|
|
2288
|
+
function travelMinutes(occ) {
|
|
2289
|
+
if (occ.task) return 0;
|
|
2290
|
+
return Math.min(occ.event.travel, occ.startMin);
|
|
2291
|
+
}
|
|
2282
2292
|
function overridesIn(events) {
|
|
2283
2293
|
const out = /* @__PURE__ */ new Map();
|
|
2284
2294
|
for (const event of events) {
|
|
@@ -2347,7 +2357,9 @@ function packLanes(occurrences, pad2 = 6) {
|
|
|
2347
2357
|
return occurrences.map((occ) => {
|
|
2348
2358
|
if (occ.event.allDay) return occ;
|
|
2349
2359
|
if (occ.task && occ.startMin === occ.endMin) return occ;
|
|
2350
|
-
|
|
2360
|
+
const travel = travelMinutes(occ);
|
|
2361
|
+
const from = occ.startMin - travel;
|
|
2362
|
+
let lane = laneEnds.findIndex((end) => end <= from + (travel > 0 ? 0 : pad2));
|
|
2351
2363
|
if (lane === -1) lane = laneEnds.length;
|
|
2352
2364
|
laneEnds[lane] = occ.endMin;
|
|
2353
2365
|
return { ...occ, lane };
|
|
@@ -2378,6 +2390,8 @@ function taskEvent(task, start, end) {
|
|
|
2378
2390
|
color: task.color,
|
|
2379
2391
|
icon: task.icon,
|
|
2380
2392
|
free: false,
|
|
2393
|
+
// You do not commute to a deadline, and a plan is the work itself.
|
|
2394
|
+
travel: 0,
|
|
2381
2395
|
remindLead: REMIND_INHERIT,
|
|
2382
2396
|
organizer: null,
|
|
2383
2397
|
attendees: [],
|
|
@@ -2441,8 +2455,13 @@ function taskOccurrences(tasks, calendars, day) {
|
|
|
2441
2455
|
return out;
|
|
2442
2456
|
}
|
|
2443
2457
|
var DEFAULT_WAKING = { from: 8 * 60, to: 22 * 60 };
|
|
2458
|
+
function bookedBlock(occ) {
|
|
2459
|
+
if (occ.event.allDay || occ.event.free) return null;
|
|
2460
|
+
const start = occ.startMin - travelMinutes(occ);
|
|
2461
|
+
return occ.endMin > start ? { start, end: occ.endMin } : null;
|
|
2462
|
+
}
|
|
2444
2463
|
function freeGaps(occurrences, minMinutes, from, to) {
|
|
2445
|
-
const busy = occurrences.filter((
|
|
2464
|
+
const busy = occurrences.map(bookedBlock).filter((block) => block !== null).map((block) => ({ start: Math.max(block.start, from), end: Math.min(block.end, to) })).filter((b) => b.end > b.start).sort((a, b) => a.start - b.start);
|
|
2446
2465
|
const gaps = [];
|
|
2447
2466
|
let cursor = from;
|
|
2448
2467
|
for (const block of busy) {
|
|
@@ -3561,6 +3580,9 @@ function draftEvent(day, startMin, endMin, calendarId, repeat = "never") {
|
|
|
3561
3580
|
icon: "",
|
|
3562
3581
|
// Busy: a gesture that named a time meant that time to be spoken for.
|
|
3563
3582
|
free: false,
|
|
3583
|
+
// None: a drag named when to be somewhere, never how long getting there
|
|
3584
|
+
// takes. Travel is typed, so a gesture cannot have meant one.
|
|
3585
|
+
travel: 0,
|
|
3564
3586
|
// Inheriting, which is the only answer a gesture gives: the drag named a
|
|
3565
3587
|
// time, not how much warning it wants about it.
|
|
3566
3588
|
remindLead: REMIND_INHERIT,
|
|
@@ -3745,6 +3767,10 @@ function readEvent(value) {
|
|
|
3745
3767
|
// exported before this field existed was. A truthy string in a
|
|
3746
3768
|
// hand-edited blob must not quietly take a meeting off the day's total.
|
|
3747
3769
|
free: value.free === true,
|
|
3770
|
+
// What a legal travel is lives in `events.ts`, beside the clipping and the
|
|
3771
|
+
// ceiling it shares bounds with: a hand-edited blob's `-5` or `"20"` comes
|
|
3772
|
+
// back `0`, which is what an event exported before this field existed was.
|
|
3773
|
+
travel: readTravel(value.travel),
|
|
3748
3774
|
// What a legal lead is lives in `remind.ts`, beside the three states it
|
|
3749
3775
|
// has to distinguish: an unreadable one comes back inheriting, which is
|
|
3750
3776
|
// what an event exported before this field existed already did.
|
|
@@ -4890,6 +4916,9 @@ function buildEventRecord(input, calendars, settings, pin) {
|
|
|
4890
4916
|
// Busy. There is no tool argument for this yet, and an agent asked to put
|
|
4891
4917
|
// an hour in the diary means the hour to be spoken for.
|
|
4892
4918
|
free: false,
|
|
4919
|
+
// No run-up unless the caller named one — an invented commute would be an
|
|
4920
|
+
// agent deciding when its user has to leave the house.
|
|
4921
|
+
travel: input.travel !== void 0 ? readTravel(input.travel) : 0,
|
|
4893
4922
|
// Inheriting unless the caller named a lead of its own — an event created
|
|
4894
4923
|
// with one it was not asked for would be an agent quietly deciding how
|
|
4895
4924
|
// much warning its user wants.
|
|
@@ -4932,6 +4961,10 @@ function applyPatchToEvent(base, input, calendars, settings, anchorMs = base.sta
|
|
|
4932
4961
|
// to busy — so leaving it out would quietly rebook a free event the
|
|
4933
4962
|
// moment its title was edited.
|
|
4934
4963
|
free: base.free,
|
|
4964
|
+
// Carried for `free`'s reason, one field over: `readEvent` defaults an
|
|
4965
|
+
// absent travel to none, so leaving it out would erase a commute the
|
|
4966
|
+
// moment an assistant fixed a typo in the title.
|
|
4967
|
+
travel: input.travel !== void 0 ? readTravel(input.travel) : base.travel,
|
|
4935
4968
|
// Carried unless the patch names one — `readEvent` reads an absent lead
|
|
4936
4969
|
// as inheriting, so leaving it out would un-silence an event the user
|
|
4937
4970
|
// silenced the moment an assistant fixed a typo in its title.
|
|
@@ -5569,6 +5602,9 @@ function eventFieldsShape() {
|
|
|
5569
5602
|
zone: z.string().describe(
|
|
5570
5603
|
`IANA zone the date and times are written in, e.g. "Asia/Seoul" \u2014 for a deadline abroad. Stored on the event so every surface prints both clocks; timed events only. On update, "" clears it back to floating (local) time, and patched times are read on the event's own zone.`
|
|
5571
5604
|
).optional(),
|
|
5605
|
+
travel: z.number().int().min(0).max(TRAVEL_MAX).describe(
|
|
5606
|
+
`Minutes of travel before the start, drawn as a run-up ahead of the event and booked as time spent, so nothing else is offered that window. 0 = none (the default), up to ${TRAVEL_MAX}.`
|
|
5607
|
+
).optional(),
|
|
5572
5608
|
remindLead: z.union([z.number().int().min(0).max(REMIND_LEAD_MAX), z.enum(["inherit", "silent"])]).describe(
|
|
5573
5609
|
'Reminder lead: minutes before the start (0 = as it starts), "silent" (never fires), or "inherit" (follow the calendar, then the setting \u2014 the default).'
|
|
5574
5610
|
).optional(),
|
package/package.json
CHANGED