calendit 1.0.2 → 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 +103 -18
  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
package/README.md CHANGED
@@ -1,94 +1,113 @@
1
- # calendit 🗓️
1
+ # calendit
2
2
 
3
- ターミナルから Google / Outlook カレンダーを自在に操るための CLI ツール。
4
- 人間のための Markdown 管理と、AI エージェントのための JSON 管理を両立します。
3
+ **ターミナルから** Google カレンダー、Microsoft Outlook(Graph)、および **この Mac のカレンダー(EventKit)** の予定を、**調べる・追加する・Markdown などのファイルと同期する** ためのコマンドラインツール(CLI)です。
5
4
 
6
- [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
7
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
5
+ ライセンス: **ISC**([LICENSE](LICENSE))。
6
+ 作者: **chromatribe - s.ohara**(ivis.klain@chromatri.be)
8
7
 
9
- ## 🌟 特徴
8
+ **npm でグローバル導入した直後(AI / Cursor の初回ラリー用)** — 同梱されない `docs/` を **GitHub 上の次の 1 本**から辿ってください(**リポジトリを clone していない場合も有効**):
10
9
 
11
- - **Markdown 同期**: カレンダーの予定を Markdown で書き出し、メモ帳感覚で編集して一括反映。
12
- - **コンテキスト管理**: 「仕事」「プライベート」などの用途(カレンダーID、認証アカウント)を `--set` 一つで切り替え。
13
- - **マルチアカウント対応**: 複数の Google / Outlook アカウントを個別に認証し、シームレスに使い分け可能。
14
- - **堅牢なエラーハンドリング**: 詳細なエラーメッセージと改善のためのヒントを提供。
15
- - **自律的テスティング**: 期待動作をドキュメント化し、AI が自ら検証・修正を行う高度な開発フロー。
16
- - **macOS 最適化**: 永続的なトークン管理とローカル時刻の完全サポート。
10
+ - **初回ラリー(対話用プレイブック):** <https://github.com/chromatribe/calendit/blob/main/docs/ai-onboarding-rally.md>
11
+ - **生テキスト URL:** <https://raw.githubusercontent.com/chromatribe/calendit/main/docs/ai-onboarding-rally.md>
17
12
 
18
- ## 🚀 クイックスタート
13
+ `npm install` 直後のターミナルにも、同じ URL を 1 行表示する `postinstall` があります(`package.json`)。
19
14
 
20
- ### 1. インストール
15
+ ---
21
16
 
22
- ```bash
23
- git clone https://github.com/YOUR_USERNAME/calendit.git
24
- cd calendit
25
- npm install
26
- npm run build
27
- # パスを通す(任意)
28
- npm link
29
- ```
17
+ ## 目的(なぜこのツールがあるか)
30
18
 
31
- ### 2. 認証設定
19
+ 1. **人間**が、エディタとターミナルだけで予定をファイル(Markdown 等)として扱えるようにする。
20
+ 2. **AI エージェント**(Cursor、Antigravity、GitHub Copilot、その他「ターミナルでコマンドを実行できる」開発支援ツール)が、**同じコマンド**を繰り返し安全に実行できるようにする。
21
+ 3. **複数のカレンダー環境**(仕事用 Google、個人用 Outlook、Mac ローカルなど)を **名前付きコンテキスト** で切り替える。
32
22
 
33
- 各サービスのセットアップガイドに従って、API 認証情報を設定します。
23
+ ---
34
24
 
35
- - **Google**: [docs/setup_google.md](docs/setup_google.md)
36
- - **Outlook**: [docs/setup_outlook.md](docs/setup_outlook.md)
25
+ ## 主な機能
37
26
 
38
- ```bash
39
- # Google の設定例
40
- calendit config set-google --id YOUR_CLIENT_ID --secret YOUR_CLIENT_SECRET
41
- calendit auth login google
27
+ | 機能 | 説明 |
28
+ |------|------|
29
+ | **query** | 指定期間の予定を **Markdown / CSV / JSON** で表示またはファイル出力 |
30
+ | **apply** | ファイルの内容をカレンダーに反映(ID があれば更新、なければ新規。**同期モード**でファイルに無い予定の削除も可) |
31
+ | **add** | 1 件の予定を対話なしで追加(`--dry-run` で確認のみ) |
32
+ | **cal** | カレンダー一覧・作成・削除(macOS コンテキストでは一部未対応) |
33
+ | **config** | API クレデンシャル、コンテキスト、UI 言語の保存 |
34
+ | **auth / accounts** | OAuth ログイン、全コンテキストの **接続状態一覧** |
35
+ | **macos** | EventKit 診断(`doctor`)、カレンダー一覧、IDE 向け **Terminal.app 委譲**(`external`) |
42
36
 
43
- # Outlook の設定例
44
- calendit config set-outlook --id YOUR_CLIENT_ID
45
- calendit auth login outlook
46
- ```
37
+ ---
47
38
 
48
- ### 3. コンテキストの設定
39
+ ## ドキュメント(必読の順)
49
40
 
50
- 用途に応じたカレンダーを「コンテキスト」として登録します。
41
+ **非エンジニアでも動かせる手順**から、**コマンド一覧**、**AI 向けの構造化情報**まで、すべて **`docs/`** にあります。
51
42
 
52
- ```bash
53
- # 仕事用カレンダーの登録
54
- calendit config set-context work --service google --calendar primary
55
- ```
43
+ | 順 | 文書 | 内容 |
44
+ |----|------|------|
45
+ | 0 | **初回ラリー**(npm `-g` では `docs` 未同梱): **[ai-onboarding-rally.md on GitHub](https://github.com/chromatribe/calendit/blob/main/docs/ai-onboarding-rally.md)** | **AI / Cursor 向け:** Google / Outlook / macOS の**分岐**と、次のコマンドの幹。リポ clone なしで参照可 |
46
+ | 1 | **[docs/README.md](docs/README.md)** | `docs/` 全体の目次(**リポジトリを手元に clone した人向け**相対パス) |
47
+ | 1b | **[docs/beginner-guide-ja.md](docs/beginner-guide-ja.md)** | **非エンジニア向け(日本語):** 導入 → 起動 → 登録に集中した全体の地図(詳細は `getting-started` へ) |
48
+ | 2 | **[docs/getting-started.md](docs/getting-started.md)** | **省略なし:** Node の確認 → 取得 → ビルド → Google / Outlook / macOS のどれかでログイン → カレンダー登録 → 動作確認 → `npm test` |
49
+ | 3 | **[docs/commands.md](docs/commands.md)** | **コマンド早見表** + 各サブコマンドのオプション |
50
+ | 4 | **[docs/for-ai-agents.md](docs/for-ai-agents.md)** | リポジトリ構成、環境変数、テスト契約、OAuth の落とし穴 |
56
51
 
57
- ### 4. 基本操作
52
+ プロバイダ別のクラウドコンソール操作:
58
53
 
59
- ```bash
60
- # 今日の予定を Markdown に書き出す
61
- calendit query --set work --format md --out today.md
54
+ - [docs/setup_google.md](docs/setup_google.md)
55
+ - [docs/setup_outlook.md](docs/setup_outlook.md)
62
56
 
63
- # 予定をカレンダーに反映(新規作成・更新)
64
- # --dry-run で変更内容を事前に確認できます
65
- calendit apply --in today.md --dry-run
57
+ macOS 常駐ブリッジ(上級):
66
58
 
67
- # 単発の予定を追加
68
- calendit add --summary "ランチミーティング" --start "today 12:00" --set work
69
- ```
59
+ - [docs/eventkit-bridge.md](docs/eventkit-bridge.md)
60
+ - [native/eventkit-helper/README.md](native/eventkit-helper/README.md)
61
+ - [native/eventkit-bridge/README.md](native/eventkit-bridge/README.md)
62
+
63
+ 開発・テストの内部仕様:
70
64
 
71
- ## 📖 詳細ドキュメント
65
+ - [docs/ux-evaluation.md](docs/ux-evaluation.md)(**UX 検証**の始め方: `npm test` → `npm run ux:link` 等)
66
+ - [docs/development.md](docs/development.md)
67
+ - [docs/tests.md](docs/tests.md)
72
68
 
73
- - [コマンドリファレンス](docs/commands.md)
74
- - [Google カレンダー セットアップガイド](docs/setup_google.md)
75
- - [Outlook カレンダー セットアップガイド](docs/setup_outlook.md)
76
- - [開発者向けガイド (テスト・設計)](docs/development.md)
69
+ ---
77
70
 
78
- ## 🛠️ 開発者向け
71
+ ## 開発環境向けクイックコマンド(最短)
79
72
 
80
- ### テストの実行
73
+ **前提:** Node.js **18 以上**、Git。
74
+
75
+ ```bash
76
+ git clone https://github.com/chromatribe/calendit.git
77
+ cd calendit
78
+ npm ci # 失敗したら npm install
79
+ npm run build
80
+ npm run ux:link # ビルド + npm link(`npm link` だけでも可)
81
+ calendit --version
82
+ ```
83
+
84
+ 以降の **人間向けの詳細な手順** は **[docs/getting-started.md](docs/getting-started.md)** に集約しています(**ここでは省略しません**。README からリンク先へ誘導します)。
85
+
86
+ ---
87
+
88
+ ## 自動テスト
81
89
 
82
90
  ```bash
83
91
  npm test
84
92
  ```
85
93
 
86
- `docs/tests.md` に定義されたテストケースに基づき、自律的に検証が走ります。
94
+ `docs/tests.md` に定義されたケースを実行します(実カレンダーは触らない **モック** が既定)。
95
+
96
+ ---
87
97
 
88
- ## 📄 ライセンス
98
+ ## その他おすすめ記載(本 README で触れる項目)
89
99
 
90
- ISC License. 詳細は [LICENSE](LICENSE) を参照してください。
100
+ | 項目 | 参照先 |
101
+ |------|--------|
102
+ | 仕様の要約 | [spec/spec.md](spec/spec.md) |
103
+ | 版ごとの変更履歴 | [spec/history/](spec/history/) |
104
+ | ロードマップ | [docs/roadmap.md](docs/roadmap.md) |
105
+ | 手動スモーク(実 API) | [docs/manual-local-smoke.md](docs/manual-local-smoke.md) |
106
+ | 変更ログ(ドキュメント) | [docs/changelog.md](docs/changelog.md) |
91
107
 
92
- ## 👤 著者
108
+ ---
93
109
 
94
- **chromatribe - s.ohara** (<ivis.klain@chromatri.be>)
110
+ ## バッジ
111
+
112
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
113
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
@@ -0,0 +1,4 @@
1
+ import { Command } from "commander";
2
+ import { CommandDeps } from "./shared.js";
3
+ export declare function printAccountStatusTable(deps: CommandDeps): Promise<void>;
4
+ export declare function registerAccountsCommands(program: Command, deps: CommandDeps): void;
@@ -0,0 +1,26 @@
1
+ import { loadConfigIfExists } from "./shared.js";
2
+ import { writeStdoutLine } from "../core/logger.js";
3
+ import { buildAccountStatusRows, formatAccountStatusTable } from "../core/accountStatus.js";
4
+ export async function printAccountStatusTable(deps) {
5
+ await loadConfigIfExists(deps.config);
6
+ const contexts = deps.config.getAllContexts();
7
+ const names = Object.keys(contexts);
8
+ if (names.length === 0) {
9
+ writeStdoutLine("コンテキストが定義されていません。`calendit config set-context` で追加してください。");
10
+ return;
11
+ }
12
+ const rows = await buildAccountStatusRows(contexts, {
13
+ auth: deps.auth,
14
+ outlookCreds: deps.config.getOutlookCreds(),
15
+ });
16
+ writeStdoutLine(formatAccountStatusTable(rows));
17
+ }
18
+ export function registerAccountsCommands(program, deps) {
19
+ const accountsCmd = program.command("accounts").description("Calendar accounts and connection status (all services)");
20
+ accountsCmd
21
+ .command("status")
22
+ .description("Show each context's service, calendar, account, and connection state (google / outlook / macos)")
23
+ .action(async () => {
24
+ await printAccountStatusTable(deps);
25
+ });
26
+ }
@@ -12,6 +12,7 @@ export function registerAddCommand(program, deps) {
12
12
  .option("--end <dateTime>", "End date/time")
13
13
  .option("--location <text>", "Event location")
14
14
  .option("--description <text>", "Event description")
15
+ .option("--attendees <emails>", "Comma-separated attendee emails (macOS EventKit: may be ignored depending on OS)")
15
16
  .option("--set <name>", "Use a named context")
16
17
  .option("--calendar <id>", "Explicit Calendar ID")
17
18
  .option("--dry-run", "Preview addition without applying", false)
@@ -32,6 +33,12 @@ export function registerAddCommand(program, deps) {
32
33
  }
33
34
  const formattedStart = formatInTimeZone(startDate, timeZone, "yyyy-MM-dd'T'HH:mm:ssXXX");
34
35
  const formattedEnd = formatInTimeZone(endDate, timeZone, "yyyy-MM-dd'T'HH:mm:ssXXX");
36
+ const attendeeList = options.attendees
37
+ ? options.attendees
38
+ .split(",")
39
+ .map((s) => s.trim())
40
+ .filter(Boolean)
41
+ : undefined;
35
42
  logger.info(`Adding event: ${options.summary}`);
36
43
  logger.info(`Start: ${formattedStart}`);
37
44
  logger.info(`End: ${formattedEnd}`);
@@ -45,6 +52,7 @@ export function registerAddCommand(program, deps) {
45
52
  end: formattedEnd,
46
53
  location: options.location,
47
54
  description: options.description,
55
+ ...(attendeeList?.length ? { attendees: attendeeList } : {}),
48
56
  });
49
57
  logger.info(`✅ Event added successfully: "${options.summary}"`);
50
58
  });
@@ -33,7 +33,11 @@ export function registerApplyCommand(program, deps) {
33
33
  inputEvents = Formatter.fromCsv(inputData);
34
34
  }
35
35
  else {
36
- inputEvents = JSON.parse(inputData);
36
+ const parsed = Formatter.fromJson(inputData);
37
+ if (parsed.warnings.length > 0) {
38
+ parsed.warnings.forEach((w) => logger.warn(w));
39
+ }
40
+ inputEvents = parsed.events;
37
41
  }
38
42
  const applier = new Applier(service);
39
43
  if (options.dryRun) {
@@ -1,8 +1,17 @@
1
1
  import { loadConfigIfExists } from "./shared.js";
2
2
  import { ConfigError, ValidationError } from "../core/errors.js";
3
3
  import { logger } from "../core/logger.js";
4
+ import { t } from "../core/i18n.js";
5
+ import { printAccountStatusTable } from "./accounts.js";
4
6
  export function registerAuthCommands(program, deps) {
5
7
  const authCmd = program.command("auth").description("Authentication management");
8
+ authCmd
9
+ .command("status")
10
+ .description("Same as `calendit accounts status` (deprecated name; use accounts status for clarity)")
11
+ .action(async () => {
12
+ logger.info("推奨: 全サービス横断の状態は `calendit accounts status` を使用してください。");
13
+ await printAccountStatusTable(deps);
14
+ });
6
15
  authCmd
7
16
  .command("login <service>")
8
17
  .description("Login to Google or Outlook")
@@ -14,7 +23,7 @@ export function registerAuthCommands(program, deps) {
14
23
  if (service === "google") {
15
24
  const creds = deps.config.getGoogleCreds();
16
25
  if (!creds) {
17
- throw new ConfigError("Google Client ID / Secret が未設定です。", "docs/setup_google.md を参照、または `calendit config set-google --file <path>` を実行してください。");
26
+ throw new ConfigError(t("errors.service.googleCredsNotSet"), t("errors.auth.googleLoginHint"));
18
27
  }
19
28
  logger.info("Google 認証フローを開始します...");
20
29
  await deps.auth.loginGoogle(creds.id, creds.secret, accountId);
@@ -24,7 +33,7 @@ export function registerAuthCommands(program, deps) {
24
33
  if (service === "outlook") {
25
34
  const creds = deps.config.getOutlookCreds();
26
35
  if (!creds) {
27
- throw new ConfigError("Outlook Client ID が未設定です。", "`calendit config set-outlook --id <id>` を先に実行してください。");
36
+ throw new ConfigError(t("errors.service.outlookCredsNotSet"), t("errors.service.outlookCredsNotSetHint"));
28
37
  }
29
38
  logger.info("Outlook 認証フローを開始します...");
30
39
  await deps.auth.loginOutlook(creds.id, creds.tenantId, accountId);
@@ -2,8 +2,22 @@ import * as fs from "fs/promises";
2
2
  import { loadConfigIfExists } from "./shared.js";
3
3
  import { ValidationError } from "../core/errors.js";
4
4
  import { logger } from "../core/logger.js";
5
+ import { isUiLocale, t } from "../core/i18n.js";
5
6
  export function registerConfigCommands(program, deps) {
6
7
  const configCmd = program.command("config").description("Configuration management");
8
+ configCmd
9
+ .command("set-locale <code>")
10
+ .description("Set UI language (en or ja) and persist to config")
11
+ .action(async (code) => {
12
+ await loadConfigIfExists(deps.config);
13
+ const c = code.trim().toLowerCase();
14
+ if (!isUiLocale(c)) {
15
+ throw new ValidationError(`Invalid locale: ${code}`, "Use en or ja.");
16
+ }
17
+ deps.config.setUi({ locale: c, localePromptCompleted: true });
18
+ await deps.config.save();
19
+ logger.info(t("config.cmd.localeSet", { locale: c }));
20
+ });
7
21
  configCmd
8
22
  .command("set-google")
9
23
  .description("Set Google API credentials (manually or via JSON file)")
@@ -35,7 +49,7 @@ export function registerConfigCommands(program, deps) {
35
49
  }
36
50
  deps.config.setGoogleCreds(id, secret);
37
51
  await deps.config.save();
38
- logger.info("Google credentials saved to config.");
52
+ logger.info(t("config.cmd.googleSaved"));
39
53
  });
40
54
  configCmd
41
55
  .command("set-outlook")
@@ -46,7 +60,7 @@ export function registerConfigCommands(program, deps) {
46
60
  await loadConfigIfExists(deps.config);
47
61
  deps.config.setOutlookCreds(options.id, options.tenant);
48
62
  await deps.config.save();
49
- logger.info("Outlook credentials saved to config.");
63
+ logger.info(t("config.cmd.outlookSaved"));
50
64
  });
51
65
  configCmd
52
66
  .command("check")
@@ -58,13 +72,33 @@ export function registerConfigCommands(program, deps) {
58
72
  const contexts = deps.config.getAllContexts();
59
73
  const contextEntries = Object.entries(contexts);
60
74
  const mask = (value) => (value.length <= 8 ? value : `${value.slice(0, 3)}...${value.slice(-3)}`);
61
- logger.info("[CONFIG CHECK]");
62
- logger.info(` Google credentials : ${googleCreds ? `OK (id: ${mask(googleCreds.id)})` : "NOT SET (run: calendit config set-google --id <id> --secret <secret>)"}`);
63
- logger.info(` Outlook credentials: ${outlookCreds ? `OK (id: ${mask(outlookCreds.id)})` : "NOT SET (run: calendit config set-outlook --id <id>)"}`);
64
- logger.info(` Contexts : ${contextEntries.length > 0
65
- ? contextEntries.map(([name, ctx]) => `${name} (${ctx.service}/${ctx.calendarId})`).join(", ")
66
- : "none"}`);
67
- logger.info(" Config file : ~/.config/calendit/config.json (or CALENDIT_CONFIG_DIR override)");
75
+ logger.info(t("config.check.header"));
76
+ logger.info(googleCreds
77
+ ? t("config.check.googleOk", { mask: mask(googleCreds.id) })
78
+ : t("config.check.googleNotSet"));
79
+ logger.info(outlookCreds
80
+ ? t("config.check.outlookOk", { mask: mask(outlookCreds.id) })
81
+ : t("config.check.outlookNotSet"));
82
+ logger.info(contextEntries.length > 0
83
+ ? t("config.check.contexts", {
84
+ list: contextEntries.map(([name, ctx]) => `${name} (${ctx.service}/${ctx.calendarId})`).join(", "),
85
+ })
86
+ : t("config.check.contextsNone"));
87
+ logger.info(t("config.check.fileLine"));
88
+ logger.info(t("config.check.uiLocale", { locale: deps.config.getUi()?.locale ?? "en" }));
89
+ });
90
+ configCmd
91
+ .command("set-macos-transport <value>")
92
+ .description("Persist default EventKit transport: auto, bridge, or helper (used when CALENDIT_EVENTKIT_BRIDGE is unset in the shell)")
93
+ .action(async (value) => {
94
+ await loadConfigIfExists(deps.config);
95
+ const v = value.trim().toLowerCase();
96
+ if (v !== "auto" && v !== "bridge" && v !== "helper") {
97
+ throw new ValidationError("Value must be auto, bridge, or helper.");
98
+ }
99
+ deps.config.setEventkitDefaultTransport(v);
100
+ await deps.config.save();
101
+ logger.info(t("config.cmd.macosTransportSet", { value: v }));
68
102
  });
69
103
  configCmd
70
104
  .command("delete-context <name>")
@@ -73,21 +107,21 @@ export function registerConfigCommands(program, deps) {
73
107
  await loadConfigIfExists(deps.config);
74
108
  const deleted = deps.config.deleteContext(name);
75
109
  if (!deleted) {
76
- throw new ValidationError(`Context '${name}' が見つかりません。`, `登録済みのコンテキストを確認するには \`calendit config check\` を実行してください。`);
110
+ throw new ValidationError(t("errors.context.missing", { name }), t("errors.context.missingHint", { name }));
77
111
  }
78
112
  await deps.config.save();
79
- logger.info(`Context '${name}' deleted.`);
113
+ logger.info(t("config.cmd.contextDeleted", { name }));
80
114
  });
81
115
  configCmd
82
116
  .command("set-context <name>")
83
117
  .description("Set a named context (e.g. work, hobby)")
84
- .requiredOption("--service <service>", "google or outlook")
85
- .requiredOption("--calendar <id>", "Calendar ID")
118
+ .requiredOption("--service <service>", "google, outlook, or macos")
119
+ .requiredOption("--calendar <id>", "Calendar ID (for macos: EventKit calendarIdentifier from `calendit macos list-calendars`)")
86
120
  .option("--account <id>", "Custom account identifier for tokens")
87
121
  .action(async (name, options) => {
88
122
  await loadConfigIfExists(deps.config);
89
- if (options.service !== "google" && options.service !== "outlook") {
90
- throw new ValidationError("Service must be 'google' or 'outlook'.");
123
+ if (options.service !== "google" && options.service !== "outlook" && options.service !== "macos") {
124
+ throw new ValidationError("Service must be 'google', 'outlook', or 'macos'.");
91
125
  }
92
126
  deps.config.setContext(name, {
93
127
  service: options.service,
@@ -95,6 +129,6 @@ export function registerConfigCommands(program, deps) {
95
129
  accountId: options.account,
96
130
  });
97
131
  await deps.config.save();
98
- logger.info(`Context '${name}' saved.`);
132
+ logger.info(t("config.cmd.contextSaved", { name }));
99
133
  });
100
134
  }
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ import { type CommandDeps } from "./shared.js";
3
+ export declare function registerMacosCommands(program: Command, deps: CommandDeps): void;