calendit 1.0.3 → 2026.4.26

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.
Files changed (52) hide show
  1. package/README.md +81 -62
  2. package/dist/commands/accounts.d.ts +4 -0
  3. package/dist/commands/accounts.js +26 -0
  4. package/dist/commands/add.js +8 -0
  5. package/dist/commands/apply.js +5 -1
  6. package/dist/commands/auth.js +11 -2
  7. package/dist/commands/config.js +50 -16
  8. package/dist/commands/macos.d.ts +3 -0
  9. package/dist/commands/macos.js +401 -0
  10. package/dist/commands/onboard.d.ts +2 -0
  11. package/dist/commands/onboard.js +79 -0
  12. package/dist/commands/query.js +2 -2
  13. package/dist/commands/shared.d.ts +3 -2
  14. package/dist/commands/shared.js +21 -5
  15. package/dist/core/accountStatus.d.ts +18 -0
  16. package/dist/core/accountStatus.js +74 -0
  17. package/dist/core/auth.js +7 -11
  18. package/dist/core/authStatus.d.ts +20 -0
  19. package/dist/core/authStatus.js +82 -0
  20. package/dist/core/config.d.ts +11 -1
  21. package/dist/core/config.js +73 -6
  22. package/dist/core/datetime.js +3 -2
  23. package/dist/core/errors.d.ts +3 -0
  24. package/dist/core/errors.js +5 -0
  25. package/dist/core/eventkitBridgeFetch.d.ts +26 -0
  26. package/dist/core/eventkitBridgeFetch.js +159 -0
  27. package/dist/core/eventkitEnvFromConfig.d.ts +7 -0
  28. package/dist/core/eventkitEnvFromConfig.js +24 -0
  29. package/dist/core/eventkitHelper.d.ts +50 -0
  30. package/dist/core/eventkitHelper.js +336 -0
  31. package/dist/core/formatter.d.ts +41 -0
  32. package/dist/core/formatter.js +79 -0
  33. package/dist/core/i18n.d.ts +7 -0
  34. package/dist/core/i18n.js +52 -0
  35. package/dist/core/localeBootstrap.d.ts +12 -0
  36. package/dist/core/localeBootstrap.js +74 -0
  37. package/dist/core/logger.d.ts +2 -0
  38. package/dist/core/logger.js +5 -0
  39. package/dist/core/macosBridgeApp.d.ts +12 -0
  40. package/dist/core/macosBridgeApp.js +83 -0
  41. package/dist/core/macosTerminalRelay.d.ts +12 -0
  42. package/dist/core/macosTerminalRelay.js +62 -0
  43. package/dist/generated/locale-keys.d.ts +3 -0
  44. package/dist/generated/locale-keys.js +90 -0
  45. package/dist/index.js +99 -17
  46. package/dist/locales/en.json +128 -0
  47. package/dist/locales/ja.json +128 -0
  48. package/dist/services/macos.d.ts +14 -0
  49. package/dist/services/macos.js +115 -0
  50. package/dist/test_runner.js +11 -2
  51. package/dist/types/index.d.ts +12 -1
  52. package/package.json +16 -5
@@ -0,0 +1,401 @@
1
+ import { execFile, spawn } from "child_process";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import { promisify } from "util";
5
+ import Enquirer from "enquirer";
6
+ import { writeStdoutLine } from "../core/logger.js";
7
+ import { logger } from "../core/logger.js";
8
+ import { ValidationError } from "../core/errors.js";
9
+ import { t } from "../core/i18n.js";
10
+ import { eventkitDoctorJson, eventkitListCalendarsJson, hasEventkitTransport, resolveEventkitHelperPath, } from "../core/eventkitHelper.js";
11
+ import { getDefaultEventkitFetchUrlFromEnv, getFetchedEventkitBridgePath, materializeEventkitBridgeFromNetwork, formatMebibytes, probeHttpArchiveSizeBytes, } from "../core/eventkitBridgeFetch.js";
12
+ import { resolveEventkitBridgeAppBundlePath, resolveEventkitBridgeRepoPath, } from "../core/macosBridgeApp.js";
13
+ import { loadConfigIfExists } from "./shared.js";
14
+ import { buildMacosExternalShellLine, buildMacosTerminalSessionLine, openTerminalAndRunShellLine, } from "../core/macosTerminalRelay.js";
15
+ const execFileAsync = promisify(execFile);
16
+ function isLikelyIdeIntegratedTerminal() {
17
+ return process.env.TERM_PROGRAM === "vscode";
18
+ }
19
+ function formatCalendarTable(rows) {
20
+ const headers = ["SOURCE", "TITLE", "CALENDAR_ID", "EDIT"];
21
+ const data = rows.map((r) => [
22
+ r.sourceTitle,
23
+ r.title,
24
+ r.calendarIdentifier,
25
+ r.allowsContentModification ? "yes" : "no",
26
+ ]);
27
+ const all = [headers, ...data];
28
+ const widths = headers.map((_, i) => Math.max(...all.map((row) => String(row[i]).length)));
29
+ const sep = widths.map((w) => "-".repeat(w)).join(" ");
30
+ const fmt = (cols) => cols.map((c, i) => c.padEnd(widths[i])).join(" ");
31
+ return [fmt(headers), sep, ...data.map((cols) => fmt(cols.map(String)))].join("\n");
32
+ }
33
+ async function sleepMs(ms) {
34
+ return new Promise((r) => setTimeout(r, ms));
35
+ }
36
+ function isConnectFailure(msg) {
37
+ return /could not connect|ECONNREFUSED|not connect|EventKit bridge token not found|bridge token not found/i.test(msg);
38
+ }
39
+ function isInteractiveTty() {
40
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
41
+ }
42
+ /** Run `scripts/build-app-bundle.sh` in `bridgeRoot` and log success. */
43
+ function runBridgeBuildFromRoot(bridgeRoot) {
44
+ const script = path.join(bridgeRoot, "scripts/build-app-bundle.sh");
45
+ return new Promise((resolve, reject) => {
46
+ const c = spawn("/bin/bash", [script], { cwd: bridgeRoot, stdio: "inherit", env: process.env });
47
+ c.on("error", reject);
48
+ c.on("close", (code) => {
49
+ if (code === 0) {
50
+ resolve();
51
+ }
52
+ else {
53
+ reject(new Error(String(code ?? 1)));
54
+ }
55
+ });
56
+ });
57
+ }
58
+ export function registerMacosCommands(program, deps) {
59
+ const macosCmd = program
60
+ .command("macos")
61
+ .description("macOS Calendar (EventKit) helper — not OAuth; uses local Calendar.app data store");
62
+ const bridgeCmd = macosCmd
63
+ .command("bridge")
64
+ .description("EventKit bridge (CalenditEventKitBridge.app) — TCC and socket live on this process");
65
+ bridgeCmd
66
+ .command("fetch")
67
+ .description("Download the repo snapshot from GitHub, extract native/eventkit-bridge (with confirmation; then optional Swift build)")
68
+ .option("-f, --force", "Re-download and replace existing fetched sources", false)
69
+ .option("-y, --yes", "Skip the download confirmation prompt (non-interactive)", false)
70
+ .option("-b, --build", "After a successful download, run the Swift .app build without a second prompt", false)
71
+ .action(async (opts) => {
72
+ if (process.platform !== "darwin") {
73
+ writeStdoutLine(t("macos.setup.notDarwin"));
74
+ process.exitCode = 1;
75
+ return;
76
+ }
77
+ const dest = getFetchedEventkitBridgePath();
78
+ let plan;
79
+ try {
80
+ plan = getDefaultEventkitFetchUrlFromEnv();
81
+ }
82
+ catch (e) {
83
+ const message = e instanceof Error ? e.message : String(e);
84
+ logger.error(message);
85
+ process.exitCode = 1;
86
+ return;
87
+ }
88
+ const { url } = plan;
89
+ if (!opts.force && fs.existsSync(path.join(dest, "Package.swift"))) {
90
+ logger.info(t("macos.bridge.fetchSkipsExisting", { path: dest }));
91
+ return;
92
+ }
93
+ if (!opts.yes && !isInteractiveTty()) {
94
+ logger.error(t("macos.bridge.fetchNoTty"));
95
+ process.exitCode = 1;
96
+ return;
97
+ }
98
+ const sizeBytes = await probeHttpArchiveSizeBytes(url);
99
+ const sizeLine = sizeBytes != null
100
+ ? t("macos.bridge.fetchSizeKnown", { mb: formatMebibytes(sizeBytes) })
101
+ : t("macos.bridge.fetchSizeUnknown");
102
+ if (!opts.yes) {
103
+ writeStdoutLine(t("macos.bridge.fetchIntro"));
104
+ writeStdoutLine("");
105
+ writeStdoutLine(t("macos.bridge.fetchPlan", { url, sizeLine, dest }));
106
+ writeStdoutLine("");
107
+ const { go } = await Enquirer.prompt({
108
+ type: "confirm",
109
+ name: "go",
110
+ message: t("macos.bridge.fetchConfirm"),
111
+ initial: true,
112
+ });
113
+ if (!go) {
114
+ logger.info(t("macos.bridge.fetchCancel"));
115
+ return;
116
+ }
117
+ }
118
+ try {
119
+ const r = await materializeEventkitBridgeFromNetwork({ url, force: Boolean(opts.force) });
120
+ logger.info(t("macos.bridge.fetchOk", { path: r.dest }));
121
+ }
122
+ catch (e) {
123
+ const message = e instanceof Error ? e.message : String(e);
124
+ logger.error(t("macos.bridge.fetchFailed", { message }));
125
+ process.exitCode = 1;
126
+ return;
127
+ }
128
+ const bridgeRoot = resolveEventkitBridgeRepoPath();
129
+ if (!bridgeRoot) {
130
+ return;
131
+ }
132
+ let doBuild;
133
+ if (opts.build) {
134
+ doBuild = true;
135
+ }
136
+ else if (opts.yes) {
137
+ doBuild = false;
138
+ }
139
+ else if (isInteractiveTty()) {
140
+ const { build } = await Enquirer.prompt({
141
+ type: "confirm",
142
+ name: "build",
143
+ message: t("macos.bridge.fetchBuildPrompt"),
144
+ initial: true,
145
+ });
146
+ doBuild = build;
147
+ }
148
+ else {
149
+ doBuild = false;
150
+ }
151
+ if (!doBuild) {
152
+ return;
153
+ }
154
+ try {
155
+ await runBridgeBuildFromRoot(bridgeRoot);
156
+ const outApp = path.join(bridgeRoot, ".build/CalenditEventKitBridge.app");
157
+ logger.info(t("macos.bridge.buildOk", { path: outApp }));
158
+ }
159
+ catch (e) {
160
+ const message = e instanceof Error ? e.message : String(e);
161
+ logger.error(t("macos.bridge.buildFailed", { message }));
162
+ process.exitCode = 1;
163
+ }
164
+ });
165
+ bridgeCmd
166
+ .command("build")
167
+ .description("Build CalenditEventKitBridge.app from the Swift package (full repo; requires swift/codesign)")
168
+ .action(async () => {
169
+ if (process.platform !== "darwin") {
170
+ writeStdoutLine(t("macos.setup.notDarwin"));
171
+ process.exitCode = 1;
172
+ return;
173
+ }
174
+ const bridgeRoot = resolveEventkitBridgeRepoPath();
175
+ if (!bridgeRoot) {
176
+ logger.error(t("macos.bridge.buildNoSource"));
177
+ process.exitCode = 1;
178
+ return;
179
+ }
180
+ try {
181
+ await runBridgeBuildFromRoot(bridgeRoot);
182
+ const outApp = path.join(bridgeRoot, ".build/CalenditEventKitBridge.app");
183
+ logger.info(t("macos.bridge.buildOk", { path: outApp }));
184
+ }
185
+ catch (e) {
186
+ const message = e instanceof Error ? e.message : String(e);
187
+ logger.error(t("macos.bridge.buildFailed", { message }));
188
+ process.exitCode = 1;
189
+ }
190
+ });
191
+ bridgeCmd
192
+ .command("start")
193
+ .description("Open CalenditEventKitBridge.app (searches common paths; set CALENDIT_EVENTKIT_BRIDGE_APP to override)")
194
+ .action(async () => {
195
+ if (process.platform !== "darwin") {
196
+ writeStdoutLine(t("macos.setup.notDarwin"));
197
+ process.exitCode = 1;
198
+ return;
199
+ }
200
+ const p = resolveEventkitBridgeAppBundlePath();
201
+ if (!p) {
202
+ logger.error(t("macos.bridge.notFound"));
203
+ process.exitCode = 1;
204
+ return;
205
+ }
206
+ try {
207
+ await execFileAsync("/usr/bin/open", [p], { shell: false });
208
+ logger.info(t("macos.bridge.opened", { path: p }));
209
+ }
210
+ catch (e) {
211
+ const message = e instanceof Error ? e.message : String(e);
212
+ logger.error(t("macos.bridge.openFailed", { message }));
213
+ process.exitCode = 1;
214
+ }
215
+ });
216
+ macosCmd
217
+ .command("setup")
218
+ .description("Interactive: ensure bridge, grant Calendar access, pick a calendar, save a macos context")
219
+ .action(async () => {
220
+ if (process.platform !== "darwin") {
221
+ writeStdoutLine(t("macos.setup.notDarwin"));
222
+ process.exitCode = 1;
223
+ return;
224
+ }
225
+ if (!hasEventkitTransport()) {
226
+ logger.error(t("macos.setup.noTransport"));
227
+ process.exitCode = 1;
228
+ return;
229
+ }
230
+ for (let attempt = 0; attempt < 8; attempt++) {
231
+ try {
232
+ const j = await eventkitDoctorJson();
233
+ if (j.calendarAccess === "denied" || j.calendarAccess !== "authorized") {
234
+ logger.info(t("macos.setup.tcc"));
235
+ const { c } = await Enquirer.prompt({
236
+ type: "input",
237
+ name: "c",
238
+ message: "[Enter] when ready to re-check",
239
+ });
240
+ void c;
241
+ continue;
242
+ }
243
+ const data = await eventkitListCalendarsJson();
244
+ const cals = data.calendars || [];
245
+ if (cals.length === 0) {
246
+ logger.error(t("macos.setup.noCalendars"));
247
+ process.exitCode = 1;
248
+ return;
249
+ }
250
+ const { calendarId } = await Enquirer.prompt({
251
+ type: "select",
252
+ name: "calendarId",
253
+ message: t("macos.setup.pickCalendar"),
254
+ choices: cals.map((r) => ({
255
+ name: `${r.title} (${r.sourceTitle}) — ${r.calendarIdentifier}`,
256
+ value: r.calendarIdentifier,
257
+ })),
258
+ });
259
+ const { ctxName } = await Enquirer.prompt({
260
+ type: "input",
261
+ name: "ctxName",
262
+ message: t("macos.setup.contextName"),
263
+ validate: (s) => (s.trim() ? true : t("macos.setup.contextNameRequired")),
264
+ });
265
+ await loadConfigIfExists(deps.config);
266
+ const name = ctxName.trim();
267
+ deps.config.setContext(name, { service: "macos", calendarId });
268
+ await deps.config.save();
269
+ logger.info(t("macos.setup.saved", { name, calendarId }));
270
+ return;
271
+ }
272
+ catch (e) {
273
+ const message = e instanceof Error ? e.message : String(e);
274
+ if (isConnectFailure(message) && attempt < 4) {
275
+ const { go } = await Enquirer.prompt({
276
+ type: "confirm",
277
+ name: "go",
278
+ message: t("macos.setup.startBridgePrompt"),
279
+ initial: true,
280
+ });
281
+ if (!go) {
282
+ logger.info(t("macos.setup.canceled"));
283
+ process.exitCode = 1;
284
+ return;
285
+ }
286
+ const appPath = resolveEventkitBridgeAppBundlePath();
287
+ if (!appPath) {
288
+ logger.error(t("macos.bridge.notFound"));
289
+ process.exitCode = 1;
290
+ return;
291
+ }
292
+ try {
293
+ await execFileAsync("/usr/bin/open", [appPath], { shell: false });
294
+ await sleepMs(2000);
295
+ }
296
+ catch (err) {
297
+ const m = err instanceof Error ? err.message : String(err);
298
+ logger.error(t("macos.bridge.openFailed", { message: m }));
299
+ process.exitCode = 1;
300
+ return;
301
+ }
302
+ continue;
303
+ }
304
+ logger.error(message);
305
+ process.exitCode = 1;
306
+ return;
307
+ }
308
+ }
309
+ logger.error("Setup: too many attempts.");
310
+ process.exitCode = 1;
311
+ });
312
+ macosCmd
313
+ .command("doctor")
314
+ .description("Check OS, helper binary, and Calendar access (TCC); uses the same transport as `query`")
315
+ .action(async () => {
316
+ if (process.platform !== "darwin") {
317
+ writeStdoutLine("macOS のみ対応です。現在の OS では EventKit ヘルパーを使用できません。");
318
+ return;
319
+ }
320
+ const p = resolveEventkitHelperPath();
321
+ writeStdoutLine(`Helper path: ${p ?? "(not found, OK if using bridge only)"}`);
322
+ if (!hasEventkitTransport()) {
323
+ writeStdoutLine("EventKit: no helper and no local bridge. Build helper or start CalenditEventKitBridge.app.");
324
+ return;
325
+ }
326
+ try {
327
+ const j = await eventkitDoctorJson();
328
+ writeStdoutLine(JSON.stringify(j, null, 2));
329
+ if (j.calendarAccess === "denied" && isLikelyIdeIntegratedTerminal()) {
330
+ writeStdoutLine("");
331
+ writeStdoutLine(t("macos.external.suggestionWhenDenied"));
332
+ }
333
+ }
334
+ catch (e) {
335
+ logger.error(`doctor 失敗: ${e instanceof Error ? e.message : String(e)}`);
336
+ process.exitCode = 1;
337
+ }
338
+ });
339
+ macosCmd
340
+ .command("external")
341
+ .description("Open Terminal.app and run `calendit macos …` or an interactive shell there (Calendar TCC applies to Terminal; use from Cursor/VS Code)")
342
+ .argument("<sub>", "doctor | list-calendars | shell")
343
+ .option("--json", "pass --json to list-calendars", false)
344
+ .action(async (sub, opts) => {
345
+ if (process.platform !== "darwin") {
346
+ writeStdoutLine(t("macos.external.onlyDarwin"));
347
+ process.exitCode = 1;
348
+ return;
349
+ }
350
+ const subNorm = sub.trim();
351
+ if (subNorm !== "doctor" && subNorm !== "list-calendars" && subNorm !== "shell") {
352
+ throw new ValidationError(t("macos.external.badSub"), t("macos.external.badSubHint"));
353
+ }
354
+ if (subNorm === "doctor" && opts.json) {
355
+ throw new ValidationError(t("macos.external.jsonOnlyForList"), t("macos.external.badSubHint"));
356
+ }
357
+ if (subNorm === "shell" && opts.json) {
358
+ throw new ValidationError(t("macos.external.jsonOnlyForList"), t("macos.external.badSubHint"));
359
+ }
360
+ const line = subNorm === "shell"
361
+ ? buildMacosTerminalSessionLine(process.cwd())
362
+ : buildMacosExternalShellLine(process.cwd(), subNorm === "doctor" ? ["doctor"] : opts.json ? ["list-calendars", "--json"] : ["list-calendars"]);
363
+ try {
364
+ openTerminalAndRunShellLine(line);
365
+ logger.info(t("macos.external.opened", { cmd: line }));
366
+ }
367
+ catch (e) {
368
+ const message = e instanceof Error ? e.message : String(e);
369
+ logger.error(t("macos.external.osascriptFailed", { message }));
370
+ process.exitCode = 1;
371
+ }
372
+ });
373
+ macosCmd
374
+ .command("list-calendars")
375
+ .description("List calendars from the local EventKit store (use CALENDAR_ID for config set-context --service macos)")
376
+ .option("--json", "Print raw JSON instead of a table", false)
377
+ .action(async (opts) => {
378
+ if (process.platform !== "darwin") {
379
+ writeStdoutLine("macOS のみ対応です。");
380
+ return;
381
+ }
382
+ if (!hasEventkitTransport()) {
383
+ writeStdoutLine("EventKit 経路が利用できません。`calendit macos doctor` を参照してください。");
384
+ process.exitCode = 1;
385
+ return;
386
+ }
387
+ try {
388
+ const data = await eventkitListCalendarsJson();
389
+ if (opts.json) {
390
+ writeStdoutLine(JSON.stringify(data, null, 2));
391
+ }
392
+ else {
393
+ writeStdoutLine(formatCalendarTable(data.calendars || []));
394
+ }
395
+ }
396
+ catch (e) {
397
+ logger.error(`list-calendars 失敗: ${e instanceof Error ? e.message : String(e)}`);
398
+ process.exitCode = 1;
399
+ }
400
+ });
401
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerOnboardCommand(program: Command): void;
@@ -0,0 +1,79 @@
1
+ import Enquirer from "enquirer";
2
+ import { writeStdoutLine } from "../core/logger.js";
3
+ const RALLY_BLOB = "https://github.com/chromatribe/calendit/blob/main/docs/ai-onboarding-rally.md";
4
+ const RALLY_RAW = "https://raw.githubusercontent.com/chromatribe/calendit/main/docs/ai-onboarding-rally.md";
5
+ const DOCS_BASE = "https://github.com/chromatribe/calendit/blob/main/docs";
6
+ function printRallyHeader() {
7
+ writeStdoutLine("calendit — first-time setup (onboard)");
8
+ writeStdoutLine("");
9
+ writeStdoutLine(`Rally doc (read in browser): ${RALLY_BLOB}`);
10
+ writeStdoutLine(`Rally doc (raw, for agents): ${RALLY_RAW}`);
11
+ writeStdoutLine("");
12
+ }
13
+ function printGoogle() {
14
+ writeStdoutLine("— Google");
15
+ writeStdoutLine(` Console walkthrough: ${DOCS_BASE}/setup_google.md`);
16
+ writeStdoutLine(" calendit config set-google --file <credentials.json>");
17
+ writeStdoutLine(' calendit config set-context my-google --service google --calendar primary --account "you@gmail.com"');
18
+ writeStdoutLine(" calendit auth login google --set my-google");
19
+ writeStdoutLine(" calendit accounts status");
20
+ writeStdoutLine("");
21
+ }
22
+ function printOutlook() {
23
+ writeStdoutLine("— Outlook (Microsoft Graph)");
24
+ writeStdoutLine(` Entra / Azure walkthrough: ${DOCS_BASE}/setup_outlook.md`);
25
+ writeStdoutLine(' calendit config set-outlook --id "<client-id>"');
26
+ writeStdoutLine(' calendit config set-context my-outlook --service outlook --calendar primary --account "you@outlook.com"');
27
+ writeStdoutLine(" calendit auth login outlook --set my-outlook");
28
+ writeStdoutLine(" calendit accounts status");
29
+ writeStdoutLine(" (Do not run Google and Outlook auth logins at the same time; both use localhost:3000.)");
30
+ writeStdoutLine("");
31
+ }
32
+ function printMacos() {
33
+ writeStdoutLine("— macOS (EventKit / Calendar.app)");
34
+ writeStdoutLine(` Bridge / TCC: ${DOCS_BASE}/eventkit-bridge.md`);
35
+ writeStdoutLine(" calendit macos setup # recommended wizard when possible");
36
+ writeStdoutLine(" calendit macos list-calendars # use CALENDAR_ID (not only display title) for set-context");
37
+ writeStdoutLine(' calendit config set-context my-macos --service macos --calendar "<CALENDAR_ID>"');
38
+ writeStdoutLine(" calendit accounts status");
39
+ writeStdoutLine("");
40
+ }
41
+ function printAllBranches() {
42
+ printRallyHeader();
43
+ printGoogle();
44
+ printOutlook();
45
+ printMacos();
46
+ }
47
+ function printOne(p) {
48
+ printRallyHeader();
49
+ if (p === "google")
50
+ printGoogle();
51
+ if (p === "outlook")
52
+ printOutlook();
53
+ if (p === "macos")
54
+ printMacos();
55
+ writeStdoutLine("Full step-by-step (human, no omissions):");
56
+ writeStdoutLine(` ${DOCS_BASE}/getting-started.md`);
57
+ }
58
+ export function registerOnboardCommand(program) {
59
+ program
60
+ .command("onboard")
61
+ .description("First-time setup hints: pick Google, Outlook, or this Mac; prints doc URLs and next commands")
62
+ .action(async () => {
63
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
64
+ printAllBranches();
65
+ return;
66
+ }
67
+ const { provider } = await Enquirer.prompt({
68
+ type: "select",
69
+ name: "provider",
70
+ message: "Which calendar provider are you setting up first?",
71
+ choices: [
72
+ { name: "Google", value: "google" },
73
+ { name: "Outlook (Microsoft 365 / Outlook.com)", value: "outlook" },
74
+ { name: "This Mac only (EventKit / Calendar.app)", value: "macos" },
75
+ ],
76
+ });
77
+ printOne(provider);
78
+ });
79
+ }
@@ -17,7 +17,7 @@ export function registerQueryCommand(program, deps) {
17
17
  .option("--out <file>", "Output file path")
18
18
  .option("--dry-run", "Preview (no effect for query)", false)
19
19
  .action(async (options) => {
20
- const { service, calendarId: ctxCalId } = await getServiceForContext(deps, options.set);
20
+ const { service, calendarId: ctxCalId, serviceType } = await getServiceForContext(deps, options.set);
21
21
  const calendarId = options.calendar || ctxCalId;
22
22
  const now = new Date();
23
23
  let start;
@@ -52,7 +52,7 @@ export function registerQueryCommand(program, deps) {
52
52
  else if (options.format === "csv")
53
53
  output = Formatter.toCsv(events);
54
54
  else
55
- output = JSON.stringify(events, null, 2);
55
+ output = Formatter.toJson(events, { context: options.set, service: serviceType, calendarId });
56
56
  if (options.out) {
57
57
  await fs.writeFile(options.out, output, "utf-8");
58
58
  logger.info(`Events exported to ${options.out}`);
@@ -3,14 +3,15 @@ import { AuthManager } from "../core/auth.js";
3
3
  import { GoogleCalendarService } from "../services/google.js";
4
4
  import { OutlookCalendarService } from "../services/outlook.js";
5
5
  import { MockCalendarService } from "../services/mock.js";
6
+ import { MacosCalendarService } from "../services/macos.js";
6
7
  export interface CommandDeps {
7
8
  config: ConfigManager;
8
9
  auth: AuthManager;
9
10
  }
10
11
  export interface ResolvedService {
11
- service: GoogleCalendarService | OutlookCalendarService | MockCalendarService;
12
+ service: GoogleCalendarService | OutlookCalendarService | MockCalendarService | MacosCalendarService;
12
13
  calendarId: string;
13
- serviceType: "google" | "outlook" | "mock";
14
+ serviceType: "google" | "outlook" | "mock" | "macos";
14
15
  }
15
16
  export declare function loadConfigIfExists(config: ConfigManager): Promise<void>;
16
17
  export declare function getServiceForContext(deps: CommandDeps, contextName?: string): Promise<ResolvedService>;
@@ -1,13 +1,15 @@
1
1
  import { GoogleCalendarService } from "../services/google.js";
2
2
  import { OutlookCalendarService } from "../services/outlook.js";
3
3
  import { MockCalendarService } from "../services/mock.js";
4
+ import { MacosCalendarService } from "../services/macos.js";
4
5
  import { ConfigError } from "../core/errors.js";
6
+ import { t } from "../core/i18n.js";
5
7
  export async function loadConfigIfExists(config) {
6
8
  try {
7
9
  await config.load();
8
10
  }
9
11
  catch (err) {
10
- if (err instanceof ConfigError && err.message.includes("見つかりません")) {
12
+ if (err instanceof ConfigError && err.causeCode === "missing_file") {
11
13
  return;
12
14
  }
13
15
  throw err;
@@ -20,7 +22,7 @@ export async function getServiceForContext(deps, contextName) {
20
22
  }
21
23
  catch (err) {
22
24
  if (err instanceof ConfigError) {
23
- throw new ConfigError("Failed to load configuration. Run 'calendit --help' for setup instructions.", "初回は `calendit config set-google` などで設定を作成してください。");
25
+ throw new ConfigError(t("errors.context.loadFailed"), t("errors.context.loadFailedHint"));
24
26
  }
25
27
  throw err;
26
28
  }
@@ -28,7 +30,7 @@ export async function getServiceForContext(deps, contextName) {
28
30
  ? config.getContext(contextName)
29
31
  : { service: "google", calendarId: "primary", accountId: undefined };
30
32
  if (!ctx) {
31
- throw new ConfigError(`Context '${contextName}' が見つかりません。`, `\`calendit config set-context ${contextName} --service google --calendar primary\` を実行してください。`);
33
+ throw new ConfigError(t("errors.context.missing", { name: contextName ?? "" }), t("errors.context.missingHint", { name: contextName ?? "" }));
32
34
  }
33
35
  if (process.env.CALENDIT_MOCK === "true") {
34
36
  return {
@@ -37,10 +39,24 @@ export async function getServiceForContext(deps, contextName) {
37
39
  serviceType: "mock",
38
40
  };
39
41
  }
42
+ if (ctx.service === "macos") {
43
+ if (process.env.CALENDIT_MOCK === "true") {
44
+ return {
45
+ service: new MockCalendarService("macos"),
46
+ calendarId: ctx.calendarId,
47
+ serviceType: "mock",
48
+ };
49
+ }
50
+ return {
51
+ service: new MacosCalendarService(),
52
+ calendarId: ctx.calendarId,
53
+ serviceType: "macos",
54
+ };
55
+ }
40
56
  if (ctx.service === "google") {
41
57
  const creds = config.getGoogleCreds();
42
58
  if (!creds) {
43
- throw new ConfigError("Google 認証情報が未設定です。", "`calendit config set-google --id <id> --secret <secret>` を実行してください。");
59
+ throw new ConfigError(t("errors.service.googleCredsNotSet"), t("errors.service.googleCredsNotSetHint"));
44
60
  }
45
61
  // Token file is keyed by contextName (used during `auth login --set`), falling back to accountId
46
62
  const tokenKey = contextName || ctx.accountId;
@@ -53,7 +69,7 @@ export async function getServiceForContext(deps, contextName) {
53
69
  }
54
70
  const creds = config.getOutlookCreds();
55
71
  if (!creds) {
56
- throw new ConfigError("Outlook 認証情報が未設定です。", "`calendit config set-outlook --id <id>` を実行してください。");
72
+ throw new ConfigError(t("errors.service.outlookCredsNotSet"), t("errors.service.outlookCredsNotSetHint"));
57
73
  }
58
74
  const pca = await auth.getOutlookClient(creds.id, creds.tenantId);
59
75
  const accounts = await pca.getTokenCache().getAllAccounts();
@@ -0,0 +1,18 @@
1
+ import type { ContextConfig } from "../types/index.js";
2
+ import type { OutlookCredentials } from "../types/index.js";
3
+ import type { AuthManager } from "./auth.js";
4
+ import { type AuthTokenColumn } from "./authStatus.js";
5
+ /** 全サービス共通の接続・利用可否の要約(OAuth トークンに限定しない) */
6
+ export type AccountConnectionState = AuthTokenColumn | "N/A (non-macOS)" | "NO HELPER" | "HELPER ERROR" | "NO CALENDAR ACCESS" | "CALENDAR NOT FOUND" | "OK (bridge)";
7
+ export interface AccountStatusRow {
8
+ context: string;
9
+ service: string;
10
+ calendar: string;
11
+ account: string;
12
+ connection: AccountConnectionState;
13
+ }
14
+ export declare function buildAccountStatusRows(contexts: Record<string, ContextConfig>, deps: {
15
+ auth: AuthManager;
16
+ outlookCreds?: OutlookCredentials;
17
+ }): Promise<AccountStatusRow[]>;
18
+ export declare function formatAccountStatusTable(rows: AccountStatusRow[]): string;