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.
- package/README.md +81 -62
- package/dist/commands/accounts.d.ts +4 -0
- package/dist/commands/accounts.js +26 -0
- package/dist/commands/add.js +8 -0
- package/dist/commands/apply.js +5 -1
- package/dist/commands/auth.js +11 -2
- package/dist/commands/config.js +50 -16
- package/dist/commands/macos.d.ts +3 -0
- package/dist/commands/macos.js +401 -0
- package/dist/commands/onboard.d.ts +2 -0
- package/dist/commands/onboard.js +79 -0
- package/dist/commands/query.js +2 -2
- package/dist/commands/shared.d.ts +3 -2
- package/dist/commands/shared.js +21 -5
- package/dist/core/accountStatus.d.ts +18 -0
- package/dist/core/accountStatus.js +74 -0
- package/dist/core/auth.js +7 -11
- package/dist/core/authStatus.d.ts +20 -0
- package/dist/core/authStatus.js +82 -0
- package/dist/core/config.d.ts +11 -1
- package/dist/core/config.js +73 -6
- package/dist/core/datetime.js +3 -2
- package/dist/core/errors.d.ts +3 -0
- package/dist/core/errors.js +5 -0
- package/dist/core/eventkitBridgeFetch.d.ts +26 -0
- package/dist/core/eventkitBridgeFetch.js +159 -0
- package/dist/core/eventkitEnvFromConfig.d.ts +7 -0
- package/dist/core/eventkitEnvFromConfig.js +24 -0
- package/dist/core/eventkitHelper.d.ts +50 -0
- package/dist/core/eventkitHelper.js +336 -0
- package/dist/core/formatter.d.ts +41 -0
- package/dist/core/formatter.js +79 -0
- package/dist/core/i18n.d.ts +7 -0
- package/dist/core/i18n.js +52 -0
- package/dist/core/localeBootstrap.d.ts +12 -0
- package/dist/core/localeBootstrap.js +74 -0
- package/dist/core/logger.d.ts +2 -0
- package/dist/core/logger.js +5 -0
- package/dist/core/macosBridgeApp.d.ts +12 -0
- package/dist/core/macosBridgeApp.js +83 -0
- package/dist/core/macosTerminalRelay.d.ts +12 -0
- package/dist/core/macosTerminalRelay.js +62 -0
- package/dist/generated/locale-keys.d.ts +3 -0
- package/dist/generated/locale-keys.js +90 -0
- package/dist/index.js +99 -17
- package/dist/locales/en.json +128 -0
- package/dist/locales/ja.json +128 -0
- package/dist/services/macos.d.ts +14 -0
- package/dist/services/macos.js +115 -0
- package/dist/test_runner.js +11 -2
- package/dist/types/index.d.ts +12 -1
- 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,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
|
+
}
|
package/dist/commands/query.js
CHANGED
|
@@ -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 =
|
|
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>;
|
package/dist/commands/shared.js
CHANGED
|
@@ -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.
|
|
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("
|
|
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(
|
|
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("
|
|
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("
|
|
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;
|