climemo 0.1.0

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 (76) hide show
  1. package/dist/commands/ask.d.ts +2 -0
  2. package/dist/commands/ask.js +37 -0
  3. package/dist/commands/ask.js.map +1 -0
  4. package/dist/commands/compare.d.ts +2 -0
  5. package/dist/commands/compare.js +57 -0
  6. package/dist/commands/compare.js.map +1 -0
  7. package/dist/commands/config.d.ts +2 -0
  8. package/dist/commands/config.js +37 -0
  9. package/dist/commands/config.js.map +1 -0
  10. package/dist/commands/daemon.d.ts +2 -0
  11. package/dist/commands/daemon.js +69 -0
  12. package/dist/commands/daemon.js.map +1 -0
  13. package/dist/commands/invite.d.ts +2 -0
  14. package/dist/commands/invite.js +49 -0
  15. package/dist/commands/invite.js.map +1 -0
  16. package/dist/commands/login.d.ts +2 -0
  17. package/dist/commands/login.js +94 -0
  18. package/dist/commands/login.js.map +1 -0
  19. package/dist/commands/logout.d.ts +2 -0
  20. package/dist/commands/logout.js +9 -0
  21. package/dist/commands/logout.js.map +1 -0
  22. package/dist/commands/members.d.ts +2 -0
  23. package/dist/commands/members.js +98 -0
  24. package/dist/commands/members.js.map +1 -0
  25. package/dist/commands/merge.d.ts +2 -0
  26. package/dist/commands/merge.js +120 -0
  27. package/dist/commands/merge.js.map +1 -0
  28. package/dist/commands/search.d.ts +2 -0
  29. package/dist/commands/search.js +32 -0
  30. package/dist/commands/search.js.map +1 -0
  31. package/dist/commands/sync.d.ts +2 -0
  32. package/dist/commands/sync.js +200 -0
  33. package/dist/commands/sync.js.map +1 -0
  34. package/dist/commands/token.d.ts +2 -0
  35. package/dist/commands/token.js +60 -0
  36. package/dist/commands/token.js.map +1 -0
  37. package/dist/commands/whoami.d.ts +2 -0
  38. package/dist/commands/whoami.js +20 -0
  39. package/dist/commands/whoami.js.map +1 -0
  40. package/dist/daemon/index.d.ts +2 -0
  41. package/dist/daemon/index.js +27 -0
  42. package/dist/daemon/index.js.map +1 -0
  43. package/dist/daemon/install.d.ts +13 -0
  44. package/dist/daemon/install.js +143 -0
  45. package/dist/daemon/install.js.map +1 -0
  46. package/dist/daemon/logger.d.ts +8 -0
  47. package/dist/daemon/logger.js +41 -0
  48. package/dist/daemon/logger.js.map +1 -0
  49. package/dist/daemon/token-refresher.d.ts +14 -0
  50. package/dist/daemon/token-refresher.js +88 -0
  51. package/dist/daemon/token-refresher.js.map +1 -0
  52. package/dist/index.d.ts +2 -0
  53. package/dist/index.js +40 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/lib/api.d.ts +13 -0
  56. package/dist/lib/api.js +18 -0
  57. package/dist/lib/api.js.map +1 -0
  58. package/dist/lib/auth.d.ts +24 -0
  59. package/dist/lib/auth.js +72 -0
  60. package/dist/lib/auth.js.map +1 -0
  61. package/dist/lib/config.d.ts +9 -0
  62. package/dist/lib/config.js +36 -0
  63. package/dist/lib/config.js.map +1 -0
  64. package/dist/lib/git.d.ts +24 -0
  65. package/dist/lib/git.js +63 -0
  66. package/dist/lib/git.js.map +1 -0
  67. package/dist/lib/keychain.d.ts +4 -0
  68. package/dist/lib/keychain.js +39 -0
  69. package/dist/lib/keychain.js.map +1 -0
  70. package/dist/lib/project.d.ts +13 -0
  71. package/dist/lib/project.js +133 -0
  72. package/dist/lib/project.js.map +1 -0
  73. package/dist/lib/server.d.ts +9 -0
  74. package/dist/lib/server.js +80 -0
  75. package/dist/lib/server.js.map +1 -0
  76. package/package.json +48 -0
@@ -0,0 +1,143 @@
1
+ import { writeFileSync, existsSync, mkdirSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ import { execSync } from "child_process";
5
+ const PLIST_NAME = "com.climemo.daemon";
6
+ const PLIST_PATH = join(homedir(), "Library", "LaunchAgents", `${PLIST_NAME}.plist`);
7
+ /**
8
+ * Install the daemon as a launchd service (macOS)
9
+ */
10
+ export function installDaemon(logger) {
11
+ const platform = process.platform;
12
+ if (platform === "darwin") {
13
+ installMacOS(logger);
14
+ }
15
+ else if (platform === "linux") {
16
+ installLinux(logger);
17
+ }
18
+ else {
19
+ throw new Error(`Daemon installation not supported on ${platform} yet.`);
20
+ }
21
+ }
22
+ /**
23
+ * Uninstall the daemon
24
+ */
25
+ export function uninstallDaemon(logger) {
26
+ const platform = process.platform;
27
+ if (platform === "darwin") {
28
+ uninstallMacOS(logger);
29
+ }
30
+ else if (platform === "linux") {
31
+ uninstallLinux(logger);
32
+ }
33
+ }
34
+ /**
35
+ * Check if daemon is running
36
+ */
37
+ export function isDaemonRunning() {
38
+ try {
39
+ if (process.platform === "darwin") {
40
+ const result = execSync(`launchctl list | grep ${PLIST_NAME}`, { encoding: "utf-8" });
41
+ return result.includes(PLIST_NAME);
42
+ }
43
+ else if (process.platform === "linux") {
44
+ const result = execSync("systemctl --user is-active climemo-daemon", { encoding: "utf-8" });
45
+ return result.trim() === "active";
46
+ }
47
+ return false;
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ function installMacOS(logger) {
54
+ const cliPath = process.argv[1] || "climemo";
55
+ const logPath = join(homedir(), ".climemo", "logs");
56
+ if (!existsSync(join(homedir(), "Library", "LaunchAgents"))) {
57
+ mkdirSync(join(homedir(), "Library", "LaunchAgents"), { recursive: true });
58
+ }
59
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
60
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
61
+ <plist version="1.0">
62
+ <dict>
63
+ <key>Label</key>
64
+ <string>${PLIST_NAME}</string>
65
+ <key>ProgramArguments</key>
66
+ <array>
67
+ <string>node</string>
68
+ <string>${cliPath}</string>
69
+ <string>daemon</string>
70
+ <string>run</string>
71
+ </array>
72
+ <key>RunAtLoad</key>
73
+ <true/>
74
+ <key>KeepAlive</key>
75
+ <true/>
76
+ <key>StandardOutPath</key>
77
+ <string>${logPath}/daemon-stdout.log</string>
78
+ <key>StandardErrorPath</key>
79
+ <string>${logPath}/daemon-stderr.log</string>
80
+ </dict>
81
+ </plist>`;
82
+ writeFileSync(PLIST_PATH, plist);
83
+ try {
84
+ execSync(`launchctl unload ${PLIST_PATH} 2>/dev/null`);
85
+ }
86
+ catch {
87
+ // Ignore if not loaded
88
+ }
89
+ execSync(`launchctl load ${PLIST_PATH}`);
90
+ logger?.info(`Daemon installed at ${PLIST_PATH}`);
91
+ logger?.info("Daemon will start automatically on login.");
92
+ }
93
+ function uninstallMacOS(logger) {
94
+ try {
95
+ execSync(`launchctl unload ${PLIST_PATH}`);
96
+ }
97
+ catch {
98
+ // Ignore
99
+ }
100
+ try {
101
+ const { unlinkSync } = require("fs");
102
+ unlinkSync(PLIST_PATH);
103
+ }
104
+ catch {
105
+ // Ignore
106
+ }
107
+ logger?.info("Daemon uninstalled.");
108
+ }
109
+ function installLinux(logger) {
110
+ const serviceDir = join(homedir(), ".config", "systemd", "user");
111
+ const servicePath = join(serviceDir, "climemo-daemon.service");
112
+ const cliPath = process.argv[1] || "climemo";
113
+ if (!existsSync(serviceDir)) {
114
+ mkdirSync(serviceDir, { recursive: true });
115
+ }
116
+ const service = `[Unit]
117
+ Description=Climemo Daemon
118
+ After=network.target
119
+
120
+ [Service]
121
+ ExecStart=node ${cliPath} daemon run
122
+ Restart=on-failure
123
+ RestartSec=10
124
+
125
+ [Install]
126
+ WantedBy=default.target
127
+ `;
128
+ writeFileSync(servicePath, service);
129
+ execSync("systemctl --user daemon-reload");
130
+ execSync("systemctl --user enable climemo-daemon");
131
+ execSync("systemctl --user start climemo-daemon");
132
+ logger?.info(`Daemon installed at ${servicePath}`);
133
+ }
134
+ function uninstallLinux(logger) {
135
+ try {
136
+ execSync("systemctl --user stop climemo-daemon");
137
+ execSync("systemctl --user disable climemo-daemon");
138
+ }
139
+ catch {
140
+ // Ignore
141
+ }
142
+ }
143
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../src/daemon/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export declare class DaemonLogger {
2
+ constructor();
3
+ info(message: string, ...args: unknown[]): void;
4
+ error(message: string, ...args: unknown[]): void;
5
+ debug(message: string, ...args: unknown[]): void;
6
+ warn(message: string, ...args: unknown[]): void;
7
+ private log;
8
+ }
@@ -0,0 +1,41 @@
1
+ import { existsSync, mkdirSync, appendFileSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ const LOG_DIR = join(homedir(), ".climemo", "logs");
5
+ const LOG_FILE = join(LOG_DIR, "daemon.log");
6
+ export class DaemonLogger {
7
+ constructor() {
8
+ if (!existsSync(LOG_DIR)) {
9
+ mkdirSync(LOG_DIR, { recursive: true });
10
+ }
11
+ }
12
+ info(message, ...args) {
13
+ this.log("INFO", message, ...args);
14
+ }
15
+ error(message, ...args) {
16
+ this.log("ERROR", message, ...args);
17
+ }
18
+ debug(message, ...args) {
19
+ this.log("DEBUG", message, ...args);
20
+ }
21
+ warn(message, ...args) {
22
+ this.log("WARN", message, ...args);
23
+ }
24
+ log(level, message, ...args) {
25
+ const timestamp = new Date().toISOString();
26
+ const extra = args.length > 0 ? " " + args.map(a => a instanceof Error ? a.message : String(a)).join(" ") : "";
27
+ const line = `[${timestamp}] [${level}] ${message}${extra}\n`;
28
+ try {
29
+ appendFileSync(LOG_FILE, line);
30
+ }
31
+ catch {
32
+ // Silently fail if can't write log
33
+ }
34
+ // Also print to stdout if running interactively
35
+ if (process.stdout.isTTY) {
36
+ const prefix = level === "ERROR" ? "\u2717" : level === "WARN" ? "\u26A0" : "\u2022";
37
+ process.stdout.write(`${prefix} ${message}${extra}\n`);
38
+ }
39
+ }
40
+ }
41
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/daemon/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,IAAI,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;AACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;AAE7C,MAAM,OAAO,YAAY;IACvB;QACE,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACzB,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAG,IAAe;QACtC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAG,IAAe;QACvC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,GAAG,IAAe;QACvC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IACtC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,GAAG,IAAe;QACtC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IACrC,CAAC;IAEO,GAAG,CAAC,KAAa,EAAE,OAAe,EAAE,GAAG,IAAe;QAC5D,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CACjD,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAC3C,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,GAAG,IAAI,SAAS,MAAM,KAAK,KAAK,OAAO,GAAG,KAAK,IAAI,CAAC;QAE9D,IAAI,CAAC;YACH,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC;YACP,mCAAmC;QACrC,CAAC;QAED,gDAAgD;QAChD,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;YACrF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,IAAI,OAAO,GAAG,KAAK,IAAI,CAAC,CAAC;QACzD,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,14 @@
1
+ import type { DaemonLogger } from "./logger.js";
2
+ export declare class TokenRefresher {
3
+ private timer;
4
+ private logger;
5
+ constructor(logger: DaemonLogger);
6
+ start(intervalMs: number): void;
7
+ stop(): void;
8
+ private refresh;
9
+ private doRefresh;
10
+ /**
11
+ * Decode JWT and check if it expires within `thresholdSeconds`
12
+ */
13
+ private isTokenExpiringSoon;
14
+ }
@@ -0,0 +1,88 @@
1
+ import { getAccessToken, getRefreshToken, saveTokens } from "../lib/keychain.js";
2
+ import { getConfig } from "../lib/config.js";
3
+ export class TokenRefresher {
4
+ timer = null;
5
+ logger;
6
+ constructor(logger) {
7
+ this.logger = logger;
8
+ }
9
+ start(intervalMs) {
10
+ // Run immediately on start
11
+ this.refresh();
12
+ // Then run on interval
13
+ this.timer = setInterval(() => this.refresh(), intervalMs);
14
+ }
15
+ stop() {
16
+ if (this.timer) {
17
+ clearInterval(this.timer);
18
+ this.timer = null;
19
+ }
20
+ }
21
+ async refresh() {
22
+ try {
23
+ const accessToken = await getAccessToken();
24
+ const refreshToken = await getRefreshToken();
25
+ if (!refreshToken) {
26
+ this.logger.debug("No refresh token stored. Skipping refresh.");
27
+ return;
28
+ }
29
+ if (!accessToken) {
30
+ this.logger.info("No access token. Attempting refresh...");
31
+ await this.doRefresh(refreshToken);
32
+ return;
33
+ }
34
+ // Check if token is about to expire (within 10 minutes)
35
+ const isExpiringSoon = this.isTokenExpiringSoon(accessToken, 10 * 60);
36
+ if (isExpiringSoon) {
37
+ this.logger.info("Access token expiring soon. Refreshing...");
38
+ await this.doRefresh(refreshToken);
39
+ }
40
+ else {
41
+ this.logger.debug("Access token still valid. No refresh needed.");
42
+ }
43
+ }
44
+ catch (err) {
45
+ this.logger.error("Token refresh check failed:", err);
46
+ }
47
+ }
48
+ async doRefresh(refreshToken) {
49
+ const config = getConfig();
50
+ try {
51
+ const res = await fetch(`${config.apiUrl}/api/cli/token/refresh`, {
52
+ method: "POST",
53
+ headers: { "Content-Type": "application/json" },
54
+ body: JSON.stringify({ refresh_token: refreshToken }),
55
+ });
56
+ if (!res.ok) {
57
+ this.logger.error(`Token refresh failed with status ${res.status}`);
58
+ return;
59
+ }
60
+ const { access_token, refresh_token: new_refresh } = await res.json();
61
+ await saveTokens(access_token, new_refresh);
62
+ this.logger.info("Token refreshed successfully. Stored in Keychain.");
63
+ }
64
+ catch (err) {
65
+ this.logger.error("Token refresh request failed:", err);
66
+ }
67
+ }
68
+ /**
69
+ * Decode JWT and check if it expires within `thresholdSeconds`
70
+ */
71
+ isTokenExpiringSoon(token, thresholdSeconds) {
72
+ try {
73
+ const parts = token.split(".");
74
+ if (parts.length !== 3)
75
+ return true; // Not a valid JWT, refresh anyway
76
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
77
+ const exp = payload.exp;
78
+ if (!exp)
79
+ return true; // No expiry, refresh anyway
80
+ const now = Math.floor(Date.now() / 1000);
81
+ return (exp - now) < thresholdSeconds;
82
+ }
83
+ catch {
84
+ return true; // Can't decode, refresh anyway
85
+ }
86
+ }
87
+ }
88
+ //# sourceMappingURL=token-refresher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-refresher.js","sourceRoot":"","sources":["../../src/daemon/token-refresher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { loginCommand } from "./commands/login.js";
4
+ import { logoutCommand } from "./commands/logout.js";
5
+ import { whoamiCommand } from "./commands/whoami.js";
6
+ import { tokenCommand } from "./commands/token.js";
7
+ import { configCommand } from "./commands/config.js";
8
+ import { daemonCommand } from "./commands/daemon.js";
9
+ import { askCommand } from "./commands/ask.js";
10
+ import { searchCommand } from "./commands/search.js";
11
+ import { compareCommand } from "./commands/compare.js";
12
+ import { syncCommand } from "./commands/sync.js";
13
+ import { inviteCommand } from "./commands/invite.js";
14
+ import { mergeCommand } from "./commands/merge.js";
15
+ import { membersCommand } from "./commands/members.js";
16
+ const program = new Command();
17
+ program
18
+ .name("climemo")
19
+ .description("AI 시대의 프로젝트 지식 관리 도구")
20
+ .version("0.1.0");
21
+ // Auth commands
22
+ program.addCommand(loginCommand);
23
+ program.addCommand(logoutCommand);
24
+ program.addCommand(whoamiCommand);
25
+ program.addCommand(tokenCommand);
26
+ program.addCommand(configCommand);
27
+ program.addCommand(daemonCommand);
28
+ // Project commands
29
+ program.addCommand(syncCommand);
30
+ program.addCommand(inviteCommand);
31
+ program.addCommand(mergeCommand);
32
+ program.addCommand(membersCommand);
33
+ // AI commands
34
+ program.addCommand(askCommand);
35
+ program.addCommand(searchCommand);
36
+ program.addCommand(compareCommand);
37
+ // Global --token option for CI/CD
38
+ program.option("--token <token>", "Auth token (for CI/CD)");
39
+ program.parse();
40
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,sBAAsB,CAAC;KACnC,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,gBAAgB;AAChB,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAElC,mBAAmB;AACnB,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AAEnC,cAAc;AACd,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;AAC/B,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AAEnC,kCAAkC;AAClC,OAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,wBAAwB,CAAC,CAAC;AAE5D,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Make an authenticated API request.
3
+ * Token resolution priority: --token flag > env var > keychain
4
+ */
5
+ export declare function apiRequest(path: string, options?: {
6
+ method?: string;
7
+ body?: unknown;
8
+ token?: string;
9
+ }): Promise<{
10
+ ok: boolean;
11
+ status: number;
12
+ data: unknown;
13
+ }>;
@@ -0,0 +1,18 @@
1
+ import { getAuthHeaders } from "./auth.js";
2
+ import { getConfig } from "./config.js";
3
+ /**
4
+ * Make an authenticated API request.
5
+ * Token resolution priority: --token flag > env var > keychain
6
+ */
7
+ export async function apiRequest(path, options = {}) {
8
+ const config = getConfig();
9
+ const headers = await getAuthHeaders(options.token);
10
+ const res = await fetch(`${config.apiUrl}${path}`, {
11
+ method: options.method || "GET",
12
+ headers,
13
+ ...(options.body ? { body: JSON.stringify(options.body) } : {}),
14
+ });
15
+ const data = await res.json().catch(() => null);
16
+ return { ok: res.ok, status: res.status, data };
17
+ }
18
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAY,EACZ,UAII,EAAE;IAEN,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAEpD,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,IAAI,EAAE,EAAE;QACjD,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;QAC/B,OAAO;QACP,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAChE,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;AAClD,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Resolve auth token with priority:
3
+ * 1. --token flag (CI/CD)
4
+ * 2. CLIMEMO_TOKEN env var
5
+ * 3. OS Keychain (stored by `climemo login`)
6
+ */
7
+ export declare function resolveToken(flagToken?: string): Promise<string | null>;
8
+ /**
9
+ * Get authenticated headers for API calls
10
+ */
11
+ export declare function getAuthHeaders(flagToken?: string): Promise<Record<string, string>>;
12
+ /**
13
+ * Refresh the access token using the refresh token
14
+ * Called by daemon automatically
15
+ */
16
+ export declare function refreshAccessToken(): Promise<boolean>;
17
+ /**
18
+ * Validate current access token
19
+ */
20
+ export declare function validateToken(token: string): Promise<{
21
+ valid: boolean;
22
+ email?: string;
23
+ expires_at?: string;
24
+ }>;
@@ -0,0 +1,72 @@
1
+ import { getAccessToken, getRefreshToken, saveTokens } from "./keychain.js";
2
+ const API_BASE = process.env.CLIMEMO_API_URL || "https://climemo.com";
3
+ /**
4
+ * Resolve auth token with priority:
5
+ * 1. --token flag (CI/CD)
6
+ * 2. CLIMEMO_TOKEN env var
7
+ * 3. OS Keychain (stored by `climemo login`)
8
+ */
9
+ export async function resolveToken(flagToken) {
10
+ // 1. CLI flag (CI/CD)
11
+ if (flagToken)
12
+ return flagToken;
13
+ // 2. Environment variable
14
+ if (process.env.CLIMEMO_TOKEN)
15
+ return process.env.CLIMEMO_TOKEN;
16
+ // 3. OS Keychain
17
+ return getAccessToken();
18
+ }
19
+ /**
20
+ * Get authenticated headers for API calls
21
+ */
22
+ export async function getAuthHeaders(flagToken) {
23
+ const token = await resolveToken(flagToken);
24
+ if (!token) {
25
+ throw new Error("Not authenticated. Run `climemo login` first.");
26
+ }
27
+ return {
28
+ "Authorization": `Bearer ${token}`,
29
+ "Content-Type": "application/json",
30
+ };
31
+ }
32
+ /**
33
+ * Refresh the access token using the refresh token
34
+ * Called by daemon automatically
35
+ */
36
+ export async function refreshAccessToken() {
37
+ const refreshToken = await getRefreshToken();
38
+ if (!refreshToken)
39
+ return false;
40
+ try {
41
+ const res = await fetch(`${API_BASE}/api/cli/token/refresh`, {
42
+ method: "POST",
43
+ headers: { "Content-Type": "application/json" },
44
+ body: JSON.stringify({ refresh_token: refreshToken }),
45
+ });
46
+ if (!res.ok)
47
+ return false;
48
+ const { access_token, refresh_token } = await res.json();
49
+ await saveTokens(access_token, refresh_token);
50
+ return true;
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ }
56
+ /**
57
+ * Validate current access token
58
+ */
59
+ export async function validateToken(token) {
60
+ try {
61
+ const res = await fetch(`${API_BASE}/api/cli/whoami`, {
62
+ headers: { "Authorization": `Bearer ${token}` },
63
+ });
64
+ if (!res.ok)
65
+ return { valid: false };
66
+ return { valid: true, ...(await res.json()) };
67
+ }
68
+ catch {
69
+ return { valid: false };
70
+ }
71
+ }
72
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG5E,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,qBAAqB,CAAC;AAEtE;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,SAAkB;IACnD,sBAAsB;IACtB,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAEhC,0BAA0B;IAC1B,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IAEhE,iBAAiB;IACjB,OAAO,cAAc,EAAE,CAAC;AAC1B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,SAAkB;IACrD,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;IACnE,CAAC;IACD,OAAO;QACL,eAAe,EAAE,UAAU,KAAK,EAAE;QAClC,cAAc,EAAE,kBAAkB;KACnC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,YAAY,GAAG,MAAM,eAAe,EAAE,CAAC;IAC7C,IAAI,CAAC,YAAY;QAAE,OAAO,KAAK,CAAC;IAEhC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,wBAAwB,EAAE;YAC3D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,YAAY,EAAE,CAAC;SACtD,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,KAAK,CAAC;QAE1B,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACzD,MAAM,UAAU,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;QAC9C,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAa;IAC/C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,iBAAiB,EAAE;YACpD,OAAO,EAAE,EAAE,eAAe,EAAE,UAAU,KAAK,EAAE,EAAE;SAChD,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QACrC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAC1B,CAAC;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ export interface CliConfig {
2
+ apiUrl: string;
3
+ defaultProject?: string;
4
+ defaultModel?: string;
5
+ slackWebhook?: string;
6
+ }
7
+ export declare function getConfig(): CliConfig;
8
+ export declare function setConfig(updates: Partial<CliConfig>): void;
9
+ export declare function getConfigValue(key: keyof CliConfig): string | undefined;
@@ -0,0 +1,36 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ const CONFIG_DIR = join(homedir(), ".climemo");
5
+ const CONFIG_FILE = join(CONFIG_DIR, "config.json");
6
+ const DEFAULT_CONFIG = {
7
+ apiUrl: "https://climemo.com",
8
+ };
9
+ function ensureConfigDir() {
10
+ if (!existsSync(CONFIG_DIR)) {
11
+ mkdirSync(CONFIG_DIR, { recursive: true });
12
+ }
13
+ }
14
+ export function getConfig() {
15
+ ensureConfigDir();
16
+ if (!existsSync(CONFIG_FILE))
17
+ return { ...DEFAULT_CONFIG };
18
+ try {
19
+ const raw = readFileSync(CONFIG_FILE, "utf-8");
20
+ return { ...DEFAULT_CONFIG, ...JSON.parse(raw) };
21
+ }
22
+ catch {
23
+ return { ...DEFAULT_CONFIG };
24
+ }
25
+ }
26
+ export function setConfig(updates) {
27
+ ensureConfigDir();
28
+ const current = getConfig();
29
+ const merged = { ...current, ...updates };
30
+ writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2));
31
+ }
32
+ export function getConfigValue(key) {
33
+ const config = getConfig();
34
+ return config[key];
35
+ }
36
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,CAAC;AAC/C,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AASpD,MAAM,cAAc,GAAc;IAChC,MAAM,EAAE,qBAAqB;CAC9B,CAAC;AAEF,SAAS,eAAe;IACtB,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,eAAe,EAAE,CAAC;IAClB,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,EAAE,GAAG,cAAc,EAAE,CAAC;IAE3D,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAC/C,OAAO,EAAE,GAAG,cAAc,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,GAAG,cAAc,EAAE,CAAC;IAC/B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAA2B;IACnD,eAAe,EAAE,CAAC;IAClB,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC;IAC1C,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAoB;IACjD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,OAAO,MAAM,CAAC,GAAG,CAAuB,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,24 @@
1
+ export interface GitInfo {
2
+ /** e.g. "climemo" */
3
+ name: string;
4
+ /** e.g. "climemo" (sanitized for URL slug) */
5
+ slug: string;
6
+ /** e.g. "https://github.com/user/climemo" */
7
+ remoteUrl: string | null;
8
+ /** Absolute path to repo root */
9
+ rootPath: string;
10
+ }
11
+ /**
12
+ * Detect git repository info from cwd.
13
+ * Returns null if not inside a git repo.
14
+ */
15
+ export declare function getGitInfo(): GitInfo | null;
16
+ export interface GitHubRepo {
17
+ owner: string;
18
+ repo: string;
19
+ }
20
+ /**
21
+ * Parse GitHub owner/repo from remote URL.
22
+ * Returns null if not a GitHub repo.
23
+ */
24
+ export declare function parseGitHubRemote(remoteUrl: string | null): GitHubRepo | null;
@@ -0,0 +1,63 @@
1
+ import { execSync } from "child_process";
2
+ import { createHash } from "crypto";
3
+ function exec(cmd) {
4
+ try {
5
+ return execSync(cmd, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
6
+ }
7
+ catch {
8
+ return null;
9
+ }
10
+ }
11
+ /**
12
+ * Detect git repository info from cwd.
13
+ * Returns null if not inside a git repo.
14
+ */
15
+ export function getGitInfo() {
16
+ const rootPath = exec("git rev-parse --show-toplevel");
17
+ if (!rootPath)
18
+ return null;
19
+ // Get remote URL (prefer origin)
20
+ const remoteUrl = exec("git remote get-url origin") ||
21
+ exec("git remote get-url $(git remote | head -1)");
22
+ // Extract repo name from remote URL or directory name
23
+ let name;
24
+ if (remoteUrl) {
25
+ // git@github.com:user/repo.git → repo
26
+ // https://github.com/user/repo.git → repo
27
+ const match = remoteUrl.match(/\/([^/]+?)(?:\.git)?$/);
28
+ name = match ? match[1] : rootPath.split("/").pop() || "unknown";
29
+ }
30
+ else {
31
+ name = rootPath.split("/").pop() || "unknown";
32
+ }
33
+ let slug = name
34
+ .toLowerCase()
35
+ .replace(/[^a-z0-9-]/g, "-")
36
+ .replace(/-+/g, "-")
37
+ .replace(/^-|-$/g, "");
38
+ // For local repos without a remote, append a hash of the root path
39
+ // to avoid slug collisions between unrelated repos with the same directory name.
40
+ if (!remoteUrl) {
41
+ const pathHash = createHash("sha256").update(rootPath).digest("hex").slice(0, 6);
42
+ slug = `${slug}-${pathHash}`;
43
+ }
44
+ return { name, slug, remoteUrl, rootPath };
45
+ }
46
+ /**
47
+ * Parse GitHub owner/repo from remote URL.
48
+ * Returns null if not a GitHub repo.
49
+ */
50
+ export function parseGitHubRemote(remoteUrl) {
51
+ if (!remoteUrl)
52
+ return null;
53
+ // git@github.com:owner/repo.git
54
+ const sshMatch = remoteUrl.match(/github\.com[:/]([^/]+)\/([^/.]+?)(?:\.git)?$/);
55
+ if (sshMatch)
56
+ return { owner: sshMatch[1], repo: sshMatch[2] };
57
+ // https://github.com/owner/repo.git
58
+ const httpsMatch = remoteUrl.match(/github\.com\/([^/]+)\/([^/.]+?)(?:\.git)?$/);
59
+ if (httpsMatch)
60
+ return { owner: httpsMatch[1], repo: httpsMatch[2] };
61
+ return null;
62
+ }
63
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/lib/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAapC,SAAS,IAAI,CAAC,GAAW;IACvB,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACtF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,+BAA+B,CAAC,CAAC;IACvD,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,iCAAiC;IACjC,MAAM,SAAS,GACb,IAAI,CAAC,2BAA2B,CAAC;QACjC,IAAI,CAAC,4CAA4C,CAAC,CAAC;IAErD,sDAAsD;IACtD,IAAI,IAAY,CAAC;IACjB,IAAI,SAAS,EAAE,CAAC;QACd,sCAAsC;QACtC,0CAA0C;QAC1C,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACvD,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,SAAS,CAAC;IACnE,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,SAAS,CAAC;IAChD,CAAC;IAED,IAAI,IAAI,GAAG,IAAI;SACZ,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAEzB,mEAAmE;IACnE,iFAAiF;IACjF,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACjF,IAAI,GAAG,GAAG,IAAI,IAAI,QAAQ,EAAE,CAAC;IAC/B,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC;AAC7C,CAAC;AAOD;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAwB;IACxD,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAE5B,gCAAgC;IAChC,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;IACjF,IAAI,QAAQ;QAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IAE/D,oCAAoC;IACpC,MAAM,UAAU,GAAG,SAAS,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;IACjF,IAAI,UAAU;QAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IAErE,OAAO,IAAI,CAAC;AACd,CAAC"}