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.
- package/dist/commands/ask.d.ts +2 -0
- package/dist/commands/ask.js +37 -0
- package/dist/commands/ask.js.map +1 -0
- package/dist/commands/compare.d.ts +2 -0
- package/dist/commands/compare.js +57 -0
- package/dist/commands/compare.js.map +1 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +37 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/daemon.d.ts +2 -0
- package/dist/commands/daemon.js +69 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/invite.d.ts +2 -0
- package/dist/commands/invite.js +49 -0
- package/dist/commands/invite.js.map +1 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +94 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.js +9 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/members.d.ts +2 -0
- package/dist/commands/members.js +98 -0
- package/dist/commands/members.js.map +1 -0
- package/dist/commands/merge.d.ts +2 -0
- package/dist/commands/merge.js +120 -0
- package/dist/commands/merge.js.map +1 -0
- package/dist/commands/search.d.ts +2 -0
- package/dist/commands/search.js +32 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/sync.d.ts +2 -0
- package/dist/commands/sync.js +200 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/token.d.ts +2 -0
- package/dist/commands/token.js +60 -0
- package/dist/commands/token.js.map +1 -0
- package/dist/commands/whoami.d.ts +2 -0
- package/dist/commands/whoami.js +20 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/daemon/index.d.ts +2 -0
- package/dist/daemon/index.js +27 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/install.d.ts +13 -0
- package/dist/daemon/install.js +143 -0
- package/dist/daemon/install.js.map +1 -0
- package/dist/daemon/logger.d.ts +8 -0
- package/dist/daemon/logger.js +41 -0
- package/dist/daemon/logger.js.map +1 -0
- package/dist/daemon/token-refresher.d.ts +14 -0
- package/dist/daemon/token-refresher.js +88 -0
- package/dist/daemon/token-refresher.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api.d.ts +13 -0
- package/dist/lib/api.js +18 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/auth.d.ts +24 -0
- package/dist/lib/auth.js +72 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/config.d.ts +9 -0
- package/dist/lib/config.js +36 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/git.d.ts +24 -0
- package/dist/lib/git.js +63 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/keychain.d.ts +4 -0
- package/dist/lib/keychain.js +39 -0
- package/dist/lib/keychain.js.map +1 -0
- package/dist/lib/project.d.ts +13 -0
- package/dist/lib/project.js +133 -0
- package/dist/lib/project.js.map +1 -0
- package/dist/lib/server.d.ts +9 -0
- package/dist/lib/server.js +80 -0
- package/dist/lib/server.js.map +1 -0
- 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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"}
|
package/dist/index.d.ts
ADDED
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
|
+
}>;
|
package/dist/lib/api.js
ADDED
|
@@ -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
|
+
}>;
|
package/dist/lib/auth.js
ADDED
|
@@ -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;
|
package/dist/lib/git.js
ADDED
|
@@ -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"}
|