circal-mcp 0.2.0 → 0.2.1
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/dist/circal-mcp.mjs +64 -19
- package/package.json +1 -1
package/dist/circal-mcp.mjs
CHANGED
|
@@ -1460,8 +1460,9 @@ import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
|
1460
1460
|
|
|
1461
1461
|
// mcp/file.ts
|
|
1462
1462
|
import { randomBytes } from "node:crypto";
|
|
1463
|
-
import { open, readFile, rename, rm } from "node:fs/promises";
|
|
1463
|
+
import { open, readFile, rename, rm, stat } from "node:fs/promises";
|
|
1464
1464
|
import { dirname, join } from "node:path";
|
|
1465
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
1465
1466
|
|
|
1466
1467
|
// src/lib/time.ts
|
|
1467
1468
|
var MINUTES_PER_DAY = 1440;
|
|
@@ -3664,25 +3665,68 @@ async function loadForRead(ctx2) {
|
|
|
3664
3665
|
const contents = await readMirror(ctx2.path);
|
|
3665
3666
|
return { contents, warning: zoneWarning(contents) };
|
|
3666
3667
|
}
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3668
|
+
var LOCK_STALE_MS = 1e4;
|
|
3669
|
+
var LOCK_WAIT_MS = 2e3;
|
|
3670
|
+
var LOCK_POLL_MS = 100;
|
|
3671
|
+
async function acquireLock(path2) {
|
|
3672
|
+
const lock = `${path2}.lock`;
|
|
3673
|
+
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
3674
|
+
for (; ; ) {
|
|
3675
|
+
try {
|
|
3676
|
+
const file = await open(lock, "wx");
|
|
3677
|
+
try {
|
|
3678
|
+
await file.writeFile(`${process.pid} ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
3679
|
+
`, "utf8");
|
|
3680
|
+
} finally {
|
|
3681
|
+
await file.close();
|
|
3679
3682
|
}
|
|
3683
|
+
return lock;
|
|
3684
|
+
} catch (error) {
|
|
3685
|
+
if (error.code !== "EEXIST") throw error;
|
|
3686
|
+
}
|
|
3687
|
+
try {
|
|
3688
|
+
if (Date.now() - (await stat(lock)).mtimeMs > LOCK_STALE_MS) {
|
|
3689
|
+
await rm(lock, { force: true });
|
|
3690
|
+
continue;
|
|
3691
|
+
}
|
|
3692
|
+
} catch {
|
|
3680
3693
|
continue;
|
|
3681
3694
|
}
|
|
3682
|
-
|
|
3683
|
-
|
|
3695
|
+
if (Date.now() >= deadline) {
|
|
3696
|
+
throw new Error(
|
|
3697
|
+
`Another writer holds ${lock}. If nothing else is writing this calendar, delete that file and retry.`
|
|
3698
|
+
);
|
|
3699
|
+
}
|
|
3700
|
+
await sleep(LOCK_POLL_MS);
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
async function casWriteOnce(ctx2, mutate) {
|
|
3704
|
+
const lock = await acquireLock(ctx2.path);
|
|
3705
|
+
try {
|
|
3706
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
3707
|
+
const before = await readMirror(ctx2.path);
|
|
3708
|
+
assertVersionAllowsMutation(before);
|
|
3709
|
+
assertZoneAllowsMutation(before, ctx2.env);
|
|
3710
|
+
const { result, summary } = mutate(before);
|
|
3711
|
+
const after = await readMirror(ctx2.path);
|
|
3712
|
+
if (after.rev !== before.rev) {
|
|
3713
|
+
if (attempt === 1) {
|
|
3714
|
+
throw new Error(
|
|
3715
|
+
`The file moved under this write: read at rev ${before.rev}, still found rev ${after.rev} after retrying once. Something else is writing it \u2014 try again.`
|
|
3716
|
+
);
|
|
3717
|
+
}
|
|
3718
|
+
continue;
|
|
3719
|
+
}
|
|
3720
|
+
await atomicWrite(
|
|
3721
|
+
ctx2.path,
|
|
3722
|
+
serialiseMirror(result, after.rev + 1, after.zone || localZone())
|
|
3723
|
+
);
|
|
3724
|
+
return summary;
|
|
3725
|
+
}
|
|
3726
|
+
throw new Error("unreachable");
|
|
3727
|
+
} finally {
|
|
3728
|
+
await rm(lock, { force: true });
|
|
3684
3729
|
}
|
|
3685
|
-
throw new Error("unreachable");
|
|
3686
3730
|
}
|
|
3687
3731
|
var writeQueue = Promise.resolve();
|
|
3688
3732
|
function casWrite(ctx2, mutate) {
|
|
@@ -3695,7 +3739,7 @@ function casWrite(ctx2, mutate) {
|
|
|
3695
3739
|
}
|
|
3696
3740
|
|
|
3697
3741
|
// mcp/find.ts
|
|
3698
|
-
import { readFile as readFile2, readdir, stat } from "node:fs/promises";
|
|
3742
|
+
import { readFile as readFile2, readdir, stat as stat2 } from "node:fs/promises";
|
|
3699
3743
|
import { homedir } from "node:os";
|
|
3700
3744
|
import { join as join2 } from "node:path";
|
|
3701
3745
|
var SKIP_DIR_NAMES = { node_modules: true, Library: true, ".git": true };
|
|
@@ -3742,7 +3786,7 @@ async function listDir(path2) {
|
|
|
3742
3786
|
}
|
|
3743
3787
|
async function asMirror(path2) {
|
|
3744
3788
|
try {
|
|
3745
|
-
const info = await
|
|
3789
|
+
const info = await stat2(path2);
|
|
3746
3790
|
if (!info.isFile() || info.size > MAX_FILE_BYTES) return null;
|
|
3747
3791
|
const text = await readFile2(path2, "utf8");
|
|
3748
3792
|
const contents = readMirrorFile(text);
|
|
@@ -3930,7 +3974,8 @@ function resolveSpanForCreate(input, day, defaultDuration) {
|
|
|
3930
3974
|
const at = atMinutes(day, parseTime(input.start, "start")).getTime();
|
|
3931
3975
|
return { start: at, end: at, allDay: false, task: true };
|
|
3932
3976
|
}
|
|
3933
|
-
const
|
|
3977
|
+
const endIsDay = input.end !== void 0 && DATE_KEY_RE.test(input.end);
|
|
3978
|
+
const noTimeGiven = input.start === void 0 && (input.end === void 0 || endIsDay) && input.duration === void 0;
|
|
3934
3979
|
const allDay = input.allDay === true || input.allDay === void 0 && noTimeGiven;
|
|
3935
3980
|
if (allDay) {
|
|
3936
3981
|
const days = input.end ? Math.max(1, daysBetween(day, fromDayKey(input.end)) + 1) : 1;
|