|
| 1 | +"""Config, init, and migrate commands.""" |
| 2 | + |
| 3 | +import json |
| 4 | +import os |
| 5 | +from pathlib import Path |
| 6 | + |
| 7 | +import claude_diary.cli as _cli |
| 8 | + |
| 9 | + |
| 10 | +def cmd_config(args): |
| 11 | + config = _cli.load_config() |
| 12 | + |
| 13 | + if args.add_exporter: |
| 14 | + _add_exporter_interactive(config, args.add_exporter) |
| 15 | + return |
| 16 | + |
| 17 | + if args.set_value: |
| 18 | + key, _, value = args.set_value.partition("=") |
| 19 | + key = key.strip() |
| 20 | + value = value.strip() |
| 21 | + if key == "lang": |
| 22 | + if value not in ("ko", "en"): |
| 23 | + print("Invalid lang: %s (use 'ko' or 'en')" % value) |
| 24 | + return |
| 25 | + config[key] = value |
| 26 | + elif key == "diary_dir": |
| 27 | + config[key] = value |
| 28 | + elif key == "timezone_offset": |
| 29 | + try: |
| 30 | + tz = int(value) |
| 31 | + if not (-12 <= tz <= 14): |
| 32 | + print("Invalid timezone_offset: %s (range: -12 to 14)" % value) |
| 33 | + return |
| 34 | + config[key] = tz |
| 35 | + except ValueError: |
| 36 | + print("Invalid timezone_offset: %s (must be integer)" % value) |
| 37 | + return |
| 38 | + else: |
| 39 | + print("Unknown config key: %s (available: lang, diary_dir, timezone_offset)" % key) |
| 40 | + return |
| 41 | + _cli.save_config(config) |
| 42 | + print("Set %s = %s" % (key, value)) |
| 43 | + return |
| 44 | + |
| 45 | + # Display current config |
| 46 | + print("Config path: %s" % _cli.get_config_path()) |
| 47 | + print() |
| 48 | + for key, value in sorted(config.items()): |
| 49 | + if key == "exporters": |
| 50 | + print("exporters:") |
| 51 | + for name, exp in value.items(): |
| 52 | + enabled = exp.get("enabled", False) |
| 53 | + status = "enabled" if enabled else "disabled" |
| 54 | + details = [] |
| 55 | + for k, v in exp.items(): |
| 56 | + if k == "enabled": |
| 57 | + continue |
| 58 | + if k in ("api_token", "token", "webhook_url") and isinstance(v, str) and len(v) > 8: |
| 59 | + v = v[:4] + "..." + v[-4:] |
| 60 | + details.append("%s=%s" % (k, v)) |
| 61 | + detail_str = " (%s)" % ", ".join(details) if details else "" |
| 62 | + print(" %s: %s%s" % (name, status, detail_str)) |
| 63 | + elif isinstance(value, dict): |
| 64 | + print("%s: %s" % (key, json.dumps(value, ensure_ascii=False))) |
| 65 | + else: |
| 66 | + print("%s: %s" % (key, value)) |
| 67 | + |
| 68 | + |
| 69 | +def _add_exporter_interactive(config, name): |
| 70 | + if "exporters" not in config: |
| 71 | + config["exporters"] = {} |
| 72 | + |
| 73 | + if name == "notion": |
| 74 | + token = input("Notion API token: ").strip() |
| 75 | + db_id = input("Notion Database ID: ").strip() |
| 76 | + config["exporters"]["notion"] = { |
| 77 | + "enabled": True, |
| 78 | + "api_token": token, |
| 79 | + "database_id": db_id, |
| 80 | + } |
| 81 | + elif name in ("slack", "discord"): |
| 82 | + url = input("%s Webhook URL: " % name.capitalize()).strip() |
| 83 | + config["exporters"][name] = {"enabled": True, "webhook_url": url} |
| 84 | + elif name == "obsidian": |
| 85 | + path = input("Obsidian vault path: ").strip() |
| 86 | + config["exporters"]["obsidian"] = {"enabled": True, "vault_path": path} |
| 87 | + elif name == "github": |
| 88 | + repo = input("GitHub repo (owner/repo): ").strip() |
| 89 | + mode = input("Mode (repo/wiki/issue) [repo]: ").strip() or "repo" |
| 90 | + config["exporters"]["github"] = {"enabled": True, "repo": repo, "mode": mode} |
| 91 | + else: |
| 92 | + print("Unknown exporter: %s" % name) |
| 93 | + return |
| 94 | + |
| 95 | + _cli.save_config(config) |
| 96 | + print("Exporter '%s' added and enabled." % name) |
| 97 | + |
| 98 | + |
| 99 | +def cmd_init(args): |
| 100 | + config = _cli.load_config() |
| 101 | + diary_dir = os.path.expanduser(config["diary_dir"]) |
| 102 | + |
| 103 | + # Team mode init |
| 104 | + if hasattr(args, 'team_repo') and args.team_repo: |
| 105 | + from claude_diary.team import init_team |
| 106 | + print("Initializing claude-diary (team mode)...") |
| 107 | + print() |
| 108 | + init_team(args.team_repo) |
| 109 | + return |
| 110 | + |
| 111 | + print("Initializing claude-diary...") |
| 112 | + print() |
| 113 | + |
| 114 | + # Create diary directory |
| 115 | + _cli.ensure_diary_dir(diary_dir) |
| 116 | + print(" [ok] Diary directory: %s" % diary_dir) |
| 117 | + |
| 118 | + # Save config |
| 119 | + _cli.save_config(config) |
| 120 | + print(" [ok] Config: %s" % _cli.get_config_path()) |
| 121 | + |
| 122 | + # Register Stop Hook |
| 123 | + claude_settings = os.path.join(os.path.expanduser("~"), ".claude", "settings.json") |
| 124 | + if os.path.exists(claude_settings): |
| 125 | + try: |
| 126 | + with open(claude_settings, "r", encoding="utf-8") as f: |
| 127 | + settings = json.load(f) |
| 128 | + except (json.JSONDecodeError, IOError, ValueError): |
| 129 | + settings = {} |
| 130 | + |
| 131 | + if "hooks" not in settings: |
| 132 | + settings["hooks"] = {} |
| 133 | + if "Stop" not in settings["hooks"]: |
| 134 | + settings["hooks"]["Stop"] = [] |
| 135 | + |
| 136 | + # Check if already registered |
| 137 | + already = False |
| 138 | + for group in settings["hooks"]["Stop"]: |
| 139 | + for h in group.get("hooks", []): |
| 140 | + if "hook.py" in h.get("command", "") or "claude_diary" in h.get("command", ""): |
| 141 | + already = True |
| 142 | + break |
| 143 | + |
| 144 | + if not already: |
| 145 | + hook_cmd = "python -m claude_diary.hook" |
| 146 | + settings["hooks"]["Stop"].append({ |
| 147 | + "hooks": [{"type": "command", "command": hook_cmd}] |
| 148 | + }) |
| 149 | + with open(claude_settings, "w", encoding="utf-8") as f: |
| 150 | + json.dump(settings, f, indent=2, ensure_ascii=False) |
| 151 | + print(" [ok] Stop Hook registered: %s" % hook_cmd) |
| 152 | + else: |
| 153 | + print(" [ok] Stop Hook already registered") |
| 154 | + else: |
| 155 | + # Create settings.json with hook registration |
| 156 | + claude_dir = os.path.join(os.path.expanduser("~"), ".claude") |
| 157 | + Path(claude_dir).mkdir(parents=True, exist_ok=True) |
| 158 | + hook_cmd = "python -m claude_diary.hook" |
| 159 | + settings = { |
| 160 | + "hooks": { |
| 161 | + "Stop": [{"hooks": [{"type": "command", "command": hook_cmd}]}] |
| 162 | + } |
| 163 | + } |
| 164 | + with open(claude_settings, "w", encoding="utf-8") as f: |
| 165 | + json.dump(settings, f, indent=2, ensure_ascii=False) |
| 166 | + print(" [ok] Created %s with Stop Hook" % claude_settings) |
| 167 | + |
| 168 | + print() |
| 169 | + print("Done! Claude Code sessions will be auto-logged.") |
| 170 | + print(" View diary: cat %s/$(date +%%Y-%%m-%%d).md" % diary_dir) |
| 171 | + |
| 172 | + |
| 173 | +def cmd_migrate(args): |
| 174 | + print("Migrating v1.0 environment variables to config.json...") |
| 175 | + config = _cli.migrate_from_env() |
| 176 | + print(" lang: %s" % config["lang"]) |
| 177 | + print(" diary_dir: %s" % config["diary_dir"]) |
| 178 | + print(" timezone_offset: %s" % config["timezone_offset"]) |
| 179 | + print() |
| 180 | + print("Config saved: %s" % _cli.get_config_path()) |
| 181 | + print("Note: Environment variables still work as fallback.") |
0 commit comments