skilluse 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 (68) hide show
  1. package/README.md +100 -0
  2. package/dist/app.d.ts +6 -0
  3. package/dist/app.js +6 -0
  4. package/dist/cli.d.ts +3 -0
  5. package/dist/cli.js +167 -0
  6. package/dist/commands/demo.d.ts +14 -0
  7. package/dist/commands/demo.js +46 -0
  8. package/dist/commands/index.d.ts +8 -0
  9. package/dist/commands/index.js +77 -0
  10. package/dist/commands/list.d.ts +14 -0
  11. package/dist/commands/list.js +54 -0
  12. package/dist/commands/login.d.ts +14 -0
  13. package/dist/commands/login.js +153 -0
  14. package/dist/commands/logout.d.ts +8 -0
  15. package/dist/commands/logout.js +47 -0
  16. package/dist/commands/repo/add.d.ts +22 -0
  17. package/dist/commands/repo/add.js +139 -0
  18. package/dist/commands/repo/edit.d.ts +19 -0
  19. package/dist/commands/repo/edit.js +117 -0
  20. package/dist/commands/repo/index.d.ts +8 -0
  21. package/dist/commands/repo/index.js +47 -0
  22. package/dist/commands/repo/list.d.ts +8 -0
  23. package/dist/commands/repo/list.js +47 -0
  24. package/dist/commands/repo/remove.d.ts +16 -0
  25. package/dist/commands/repo/remove.js +83 -0
  26. package/dist/commands/repo/sync.d.ts +10 -0
  27. package/dist/commands/repo/sync.js +78 -0
  28. package/dist/commands/repo/use.d.ts +10 -0
  29. package/dist/commands/repo/use.js +56 -0
  30. package/dist/commands/repos.d.ts +11 -0
  31. package/dist/commands/repos.js +50 -0
  32. package/dist/commands/search.d.ts +16 -0
  33. package/dist/commands/search.js +199 -0
  34. package/dist/commands/skills.d.ts +11 -0
  35. package/dist/commands/skills.js +43 -0
  36. package/dist/commands/whoami.d.ts +8 -0
  37. package/dist/commands/whoami.js +69 -0
  38. package/dist/components/CLIError.d.ts +27 -0
  39. package/dist/components/CLIError.js +24 -0
  40. package/dist/components/ProgressBar.d.ts +7 -0
  41. package/dist/components/ProgressBar.js +9 -0
  42. package/dist/components/Select.d.ts +11 -0
  43. package/dist/components/Select.js +6 -0
  44. package/dist/components/Spinner.d.ts +6 -0
  45. package/dist/components/Spinner.js +7 -0
  46. package/dist/components/StatusMessage.d.ts +9 -0
  47. package/dist/components/StatusMessage.js +13 -0
  48. package/dist/components/Table.d.ts +9 -0
  49. package/dist/components/Table.js +27 -0
  50. package/dist/components/TextInput.d.ts +9 -0
  51. package/dist/components/TextInput.js +6 -0
  52. package/dist/components/index.d.ts +8 -0
  53. package/dist/components/index.js +8 -0
  54. package/dist/index.d.ts +3 -0
  55. package/dist/index.js +43815 -0
  56. package/dist/services/credentials.d.ts +69 -0
  57. package/dist/services/credentials.js +216 -0
  58. package/dist/services/index.d.ts +6 -0
  59. package/dist/services/index.js +10 -0
  60. package/dist/services/oauth.d.ts +106 -0
  61. package/dist/services/oauth.js +208 -0
  62. package/dist/services/paths.d.ts +19 -0
  63. package/dist/services/paths.js +21 -0
  64. package/dist/services/store.d.ts +64 -0
  65. package/dist/services/store.js +107 -0
  66. package/dist/services/update.d.ts +20 -0
  67. package/dist/services/update.js +93 -0
  68. package/package.json +70 -0
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Secure credential storage service.
3
+ *
4
+ * Stores OAuth tokens securely using:
5
+ * 1. System keychain (preferred) - macOS Keychain, Windows Credential Manager, Linux libsecret
6
+ * 2. Encrypted file fallback - when keychain is unavailable
7
+ *
8
+ * Storage strategy:
9
+ * - User token → Keychain/encrypted file (sensitive)
10
+ * - Installation list → Config file JSON via store.ts (metadata)
11
+ * - Installation token → Memory cache (short-lived, 1 hour)
12
+ */
13
+ /** @deprecated Use UserCredentials instead */
14
+ export interface Credentials {
15
+ token: string;
16
+ user: string;
17
+ }
18
+ export interface UserCredentials {
19
+ token: string;
20
+ userName: string;
21
+ }
22
+ export interface InstallationTokenCache {
23
+ installationId: number;
24
+ token: string;
25
+ expiresAt: Date;
26
+ }
27
+ /**
28
+ * Check if the system keychain is available and working.
29
+ */
30
+ export declare function isKeychainAvailable(): Promise<boolean>;
31
+ /**
32
+ * Get stored credentials from keychain or encrypted file.
33
+ */
34
+ export declare function getCredentials(): Promise<Credentials | null>;
35
+ /**
36
+ * Store credentials in keychain or encrypted file.
37
+ */
38
+ export declare function setCredentials(token: string, user: string): Promise<void>;
39
+ /**
40
+ * Clear stored credentials from both keychain and encrypted file.
41
+ */
42
+ export declare function clearCredentials(): Promise<void>;
43
+ /**
44
+ * Store user credentials (token and username) securely.
45
+ */
46
+ export declare function setUserCredentials(token: string, userName: string): Promise<void>;
47
+ /**
48
+ * Get user credentials from secure storage.
49
+ */
50
+ export declare function getUserCredentials(): Promise<UserCredentials | null>;
51
+ /**
52
+ * Get cached installation token if it exists and is not expired.
53
+ * Returns null if no valid token is cached.
54
+ */
55
+ export declare function getCachedInstallationToken(installationId: number): InstallationTokenCache | null;
56
+ /**
57
+ * Cache an installation token.
58
+ */
59
+ export declare function setCachedInstallationToken(cache: InstallationTokenCache): void;
60
+ /**
61
+ * Clear all cached installation tokens.
62
+ */
63
+ export declare function clearInstallationTokenCache(): void;
64
+ /**
65
+ * Clear all credentials: user token, and installation token cache.
66
+ * Note: Installation list and default installation are cleared via store.ts
67
+ */
68
+ export declare function clearAllCredentials(): Promise<void>;
69
+ //# sourceMappingURL=credentials.d.ts.map
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Secure credential storage service.
3
+ *
4
+ * Stores OAuth tokens securely using:
5
+ * 1. System keychain (preferred) - macOS Keychain, Windows Credential Manager, Linux libsecret
6
+ * 2. Encrypted file fallback - when keychain is unavailable
7
+ *
8
+ * Storage strategy:
9
+ * - User token → Keychain/encrypted file (sensitive)
10
+ * - Installation list → Config file JSON via store.ts (metadata)
11
+ * - Installation token → Memory cache (short-lived, 1 hour)
12
+ */
13
+ import crypto from "crypto";
14
+ import fs from "fs/promises";
15
+ import os from "os";
16
+ import path from "path";
17
+ import keytar from "keytar";
18
+ import { dataPath } from "./paths.js";
19
+ const SERVICE_NAME = "skilluse";
20
+ const CREDENTIALS_FILE = "credentials.enc";
21
+ // In-memory cache for short-lived installation tokens
22
+ const installationTokenCache = new Map();
23
+ // Cache keychain availability to avoid repeated checks
24
+ let keychainAvailable = null;
25
+ /**
26
+ * Check if the system keychain is available and working.
27
+ */
28
+ export async function isKeychainAvailable() {
29
+ if (keychainAvailable !== null) {
30
+ return keychainAvailable;
31
+ }
32
+ try {
33
+ // Try a test operation to see if keychain is working
34
+ const testKey = "__keychain_test__";
35
+ await keytar.setPassword(SERVICE_NAME, testKey, "test");
36
+ await keytar.deletePassword(SERVICE_NAME, testKey);
37
+ keychainAvailable = true;
38
+ }
39
+ catch {
40
+ keychainAvailable = false;
41
+ }
42
+ return keychainAvailable;
43
+ }
44
+ /**
45
+ * Get stored credentials from keychain or encrypted file.
46
+ */
47
+ export async function getCredentials() {
48
+ if (await isKeychainAvailable()) {
49
+ return getCredentialsFromKeychain();
50
+ }
51
+ return getCredentialsFromFile();
52
+ }
53
+ /**
54
+ * Store credentials in keychain or encrypted file.
55
+ */
56
+ export async function setCredentials(token, user) {
57
+ if (await isKeychainAvailable()) {
58
+ await setCredentialsToKeychain(token, user);
59
+ }
60
+ else {
61
+ await setCredentialsToFile(token, user);
62
+ }
63
+ }
64
+ /**
65
+ * Clear stored credentials from both keychain and encrypted file.
66
+ */
67
+ export async function clearCredentials() {
68
+ // Clear from keychain
69
+ try {
70
+ await keytar.deletePassword(SERVICE_NAME, "github-token");
71
+ await keytar.deletePassword(SERVICE_NAME, "github-user");
72
+ }
73
+ catch {
74
+ // Ignore keychain errors
75
+ }
76
+ // Clear encrypted file
77
+ try {
78
+ const filePath = path.join(dataPath, CREDENTIALS_FILE);
79
+ await fs.unlink(filePath);
80
+ }
81
+ catch {
82
+ // Ignore file errors (file may not exist)
83
+ }
84
+ }
85
+ // --- Keychain operations ---
86
+ async function getCredentialsFromKeychain() {
87
+ try {
88
+ const token = await keytar.getPassword(SERVICE_NAME, "github-token");
89
+ const user = await keytar.getPassword(SERVICE_NAME, "github-user");
90
+ if (token && user) {
91
+ return { token, user };
92
+ }
93
+ return null;
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ }
99
+ async function setCredentialsToKeychain(token, user) {
100
+ await keytar.setPassword(SERVICE_NAME, "github-token", token);
101
+ await keytar.setPassword(SERVICE_NAME, "github-user", user);
102
+ }
103
+ // --- Encrypted file operations ---
104
+ /**
105
+ * Derive an encryption key from machine-specific information.
106
+ * This provides basic protection against copying the file to another machine.
107
+ */
108
+ function deriveKey() {
109
+ const machineInfo = `${os.hostname()}:${os.userInfo().username}:${SERVICE_NAME}`;
110
+ return crypto.scryptSync(machineInfo, "skilluse-salt", 32);
111
+ }
112
+ function encrypt(data) {
113
+ const key = deriveKey();
114
+ const iv = crypto.randomBytes(16);
115
+ const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
116
+ let encrypted = cipher.update(data, "utf8", "hex");
117
+ encrypted += cipher.final("hex");
118
+ const authTag = cipher.getAuthTag();
119
+ // Format: iv:authTag:encryptedData
120
+ return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
121
+ }
122
+ function decrypt(encryptedData) {
123
+ const [ivHex, authTagHex, encrypted] = encryptedData.split(":");
124
+ const key = deriveKey();
125
+ const iv = Buffer.from(ivHex, "hex");
126
+ const authTag = Buffer.from(authTagHex, "hex");
127
+ const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
128
+ decipher.setAuthTag(authTag);
129
+ let decrypted = decipher.update(encrypted, "hex", "utf8");
130
+ decrypted += decipher.final("utf8");
131
+ return decrypted;
132
+ }
133
+ async function getCredentialsFromFile() {
134
+ try {
135
+ const filePath = path.join(dataPath, CREDENTIALS_FILE);
136
+ const encryptedData = await fs.readFile(filePath, "utf8");
137
+ const decrypted = decrypt(encryptedData);
138
+ return JSON.parse(decrypted);
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ async function setCredentialsToFile(token, user) {
145
+ const credentials = { token, user };
146
+ const encrypted = encrypt(JSON.stringify(credentials));
147
+ // Ensure data directory exists
148
+ await fs.mkdir(dataPath, { recursive: true });
149
+ const filePath = path.join(dataPath, CREDENTIALS_FILE);
150
+ await fs.writeFile(filePath, encrypted, { mode: 0o600 }); // Owner read/write only
151
+ }
152
+ // ============================================================================
153
+ // New GitHub App Credential Functions
154
+ // ============================================================================
155
+ /**
156
+ * Store user credentials (token and username) securely.
157
+ */
158
+ export async function setUserCredentials(token, userName) {
159
+ // Reuse existing implementation with new interface
160
+ await setCredentials(token, userName);
161
+ }
162
+ /**
163
+ * Get user credentials from secure storage.
164
+ */
165
+ export async function getUserCredentials() {
166
+ const creds = await getCredentials();
167
+ if (!creds)
168
+ return null;
169
+ return {
170
+ token: creds.token,
171
+ userName: creds.user,
172
+ };
173
+ }
174
+ // ============================================================================
175
+ // Installation Token Cache (in-memory, short-lived)
176
+ // ============================================================================
177
+ /**
178
+ * Get cached installation token if it exists and is not expired.
179
+ * Returns null if no valid token is cached.
180
+ */
181
+ export function getCachedInstallationToken(installationId) {
182
+ const cached = installationTokenCache.get(installationId);
183
+ if (!cached)
184
+ return null;
185
+ // Check if token is expired (with 5 minute buffer for safety)
186
+ const bufferMs = 5 * 60 * 1000;
187
+ if (new Date(cached.expiresAt).getTime() - bufferMs < Date.now()) {
188
+ // Token is expired or about to expire, remove it
189
+ installationTokenCache.delete(installationId);
190
+ return null;
191
+ }
192
+ return cached;
193
+ }
194
+ /**
195
+ * Cache an installation token.
196
+ */
197
+ export function setCachedInstallationToken(cache) {
198
+ installationTokenCache.set(cache.installationId, cache);
199
+ }
200
+ /**
201
+ * Clear all cached installation tokens.
202
+ */
203
+ export function clearInstallationTokenCache() {
204
+ installationTokenCache.clear();
205
+ }
206
+ /**
207
+ * Clear all credentials: user token, and installation token cache.
208
+ * Note: Installation list and default installation are cleared via store.ts
209
+ */
210
+ export async function clearAllCredentials() {
211
+ // Clear user credentials
212
+ await clearCredentials();
213
+ // Clear installation token cache
214
+ clearInstallationTokenCache();
215
+ }
216
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1,6 @@
1
+ export { requestDeviceCode, pollForAccessToken, pollUntilComplete, openBrowser, sleep, getUserInstallations, getInstallationRepositories, getInstallationToken, type DeviceCodeResponse, type AccessTokenResponse, type OAuthError, type PollResult, type Installation, type Repository, type InstallationToken, } from "./oauth.js";
2
+ export { getConfig, addRepo, removeRepo, setDefaultRepo, addInstalledSkill, removeInstalledSkill, setInstallations, getInstallations, setDefaultInstallation, getDefaultInstallation, clearInstallations, isFirstRun, type Config, type RepoConfig, type InstalledSkill, type StoredInstallation, } from "./store.js";
3
+ export { configPath, dataPath, cachePath, logPath, tempPath, } from "./paths.js";
4
+ export { getCredentials, setCredentials, clearCredentials, isKeychainAvailable, type Credentials, setUserCredentials, getUserCredentials, getCachedInstallationToken, setCachedInstallationToken, clearInstallationTokenCache, clearAllCredentials, type UserCredentials, type InstallationTokenCache, } from "./credentials.js";
5
+ export { checkForUpdate, getCurrentVersion, type UpdateInfo, } from "./update.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,10 @@
1
+ export { requestDeviceCode, pollForAccessToken, pollUntilComplete, openBrowser, sleep, getUserInstallations, getInstallationRepositories, getInstallationToken, } from "./oauth.js";
2
+ export { getConfig, addRepo, removeRepo, setDefaultRepo, addInstalledSkill, removeInstalledSkill, setInstallations, getInstallations, setDefaultInstallation, getDefaultInstallation, clearInstallations, isFirstRun, } from "./store.js";
3
+ export { configPath, dataPath, cachePath, logPath, tempPath, } from "./paths.js";
4
+ export {
5
+ // Legacy (deprecated)
6
+ getCredentials, setCredentials, clearCredentials, isKeychainAvailable,
7
+ // New GitHub App credential functions
8
+ setUserCredentials, getUserCredentials, getCachedInstallationToken, setCachedInstallationToken, clearInstallationTokenCache, clearAllCredentials, } from "./credentials.js";
9
+ export { checkForUpdate, getCurrentVersion, } from "./update.js";
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,106 @@
1
+ /**
2
+ * GitHub OAuth Device Flow implementation
3
+ * Reference: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow
4
+ */
5
+ export interface Installation {
6
+ id: number;
7
+ account: {
8
+ login: string;
9
+ type: "User" | "Organization";
10
+ };
11
+ repository_selection: "all" | "selected";
12
+ permissions: Record<string, string>;
13
+ }
14
+ export interface Repository {
15
+ id: number;
16
+ name: string;
17
+ full_name: string;
18
+ private: boolean;
19
+ }
20
+ export interface InstallationToken {
21
+ token: string;
22
+ expires_at: string;
23
+ permissions: Record<string, string>;
24
+ repositories?: Repository[];
25
+ }
26
+ export interface DeviceCodeResponse {
27
+ device_code: string;
28
+ user_code: string;
29
+ verification_uri: string;
30
+ expires_in: number;
31
+ interval: number;
32
+ }
33
+ export interface AccessTokenResponse {
34
+ access_token: string;
35
+ token_type: string;
36
+ scope: string;
37
+ }
38
+ export interface OAuthError {
39
+ error: string;
40
+ error_description?: string;
41
+ }
42
+ export type PollResult = {
43
+ status: "success";
44
+ token: AccessTokenResponse;
45
+ } | {
46
+ status: "pending";
47
+ } | {
48
+ status: "slow_down";
49
+ newInterval: number;
50
+ } | {
51
+ status: "expired";
52
+ } | {
53
+ status: "access_denied";
54
+ } | {
55
+ status: "error";
56
+ message: string;
57
+ };
58
+ /**
59
+ * Request a device code from GitHub
60
+ * This is the first step in the device flow
61
+ */
62
+ export declare function requestDeviceCode(clientId: string, scope?: string): Promise<DeviceCodeResponse>;
63
+ /**
64
+ * Poll GitHub for the access token
65
+ * Returns the poll result which can be:
66
+ * - success: user authorized, token received
67
+ * - pending: user hasn't authorized yet
68
+ * - slow_down: polling too fast, increase interval
69
+ * - expired: device code expired
70
+ * - access_denied: user denied authorization
71
+ * - error: other error occurred
72
+ */
73
+ export declare function pollForAccessToken(clientId: string, deviceCode: string): Promise<PollResult>;
74
+ /**
75
+ * Helper to sleep for a given number of milliseconds
76
+ */
77
+ export declare function sleep(ms: number): Promise<void>;
78
+ /**
79
+ * Poll for access token with automatic retry
80
+ * This handles the full polling loop until success, timeout, or error
81
+ */
82
+ export declare function pollUntilComplete(clientId: string, deviceCode: string, expiresIn: number, initialInterval: number, onPoll?: (attempt: number) => void): Promise<{
83
+ success: true;
84
+ token: AccessTokenResponse;
85
+ } | {
86
+ success: false;
87
+ reason: "expired" | "denied" | "error";
88
+ message?: string;
89
+ }>;
90
+ /**
91
+ * Open URL in the default browser
92
+ */
93
+ export declare function openBrowser(url: string): Promise<void>;
94
+ /**
95
+ * Get all GitHub App installations for the authenticated user
96
+ */
97
+ export declare function getUserInstallations(userToken: string): Promise<Installation[]>;
98
+ /**
99
+ * Get repositories accessible by a specific installation
100
+ */
101
+ export declare function getInstallationRepositories(userToken: string, installationId: number): Promise<Repository[]>;
102
+ /**
103
+ * Get an installation access token for API operations
104
+ */
105
+ export declare function getInstallationToken(userToken: string, installationId: number): Promise<InstallationToken>;
106
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1,208 @@
1
+ /**
2
+ * GitHub OAuth Device Flow implementation
3
+ * Reference: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow
4
+ */
5
+ const DEVICE_CODE_URL = "https://github.com/login/device/code";
6
+ const ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
7
+ /**
8
+ * Request a device code from GitHub
9
+ * This is the first step in the device flow
10
+ */
11
+ export async function requestDeviceCode(clientId, scope = "repo") {
12
+ const response = await fetch(DEVICE_CODE_URL, {
13
+ method: "POST",
14
+ headers: {
15
+ Accept: "application/json",
16
+ "Content-Type": "application/x-www-form-urlencoded",
17
+ },
18
+ body: new URLSearchParams({
19
+ client_id: clientId,
20
+ scope,
21
+ }),
22
+ });
23
+ if (!response.ok) {
24
+ throw new Error(`Failed to request device code: ${response.status}`);
25
+ }
26
+ const data = (await response.json());
27
+ if (data.error) {
28
+ throw new Error(data.error_description || data.error);
29
+ }
30
+ return data;
31
+ }
32
+ /**
33
+ * Poll GitHub for the access token
34
+ * Returns the poll result which can be:
35
+ * - success: user authorized, token received
36
+ * - pending: user hasn't authorized yet
37
+ * - slow_down: polling too fast, increase interval
38
+ * - expired: device code expired
39
+ * - access_denied: user denied authorization
40
+ * - error: other error occurred
41
+ */
42
+ export async function pollForAccessToken(clientId, deviceCode) {
43
+ const response = await fetch(ACCESS_TOKEN_URL, {
44
+ method: "POST",
45
+ headers: {
46
+ Accept: "application/json",
47
+ "Content-Type": "application/x-www-form-urlencoded",
48
+ },
49
+ body: new URLSearchParams({
50
+ client_id: clientId,
51
+ device_code: deviceCode,
52
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
53
+ }),
54
+ });
55
+ if (!response.ok) {
56
+ return {
57
+ status: "error",
58
+ message: `HTTP error: ${response.status}`,
59
+ };
60
+ }
61
+ const data = (await response.json());
62
+ // Check for error responses
63
+ if (data.error) {
64
+ switch (data.error) {
65
+ case "authorization_pending":
66
+ return { status: "pending" };
67
+ case "slow_down":
68
+ // GitHub requires adding 5 seconds to the interval
69
+ return { status: "slow_down", newInterval: 5 };
70
+ case "expired_token":
71
+ return { status: "expired" };
72
+ case "access_denied":
73
+ return { status: "access_denied" };
74
+ default:
75
+ return {
76
+ status: "error",
77
+ message: data.error_description || data.error,
78
+ };
79
+ }
80
+ }
81
+ // Success - we got the token
82
+ return {
83
+ status: "success",
84
+ token: data,
85
+ };
86
+ }
87
+ /**
88
+ * Helper to sleep for a given number of milliseconds
89
+ */
90
+ export function sleep(ms) {
91
+ return new Promise((resolve) => setTimeout(resolve, ms));
92
+ }
93
+ /**
94
+ * Poll for access token with automatic retry
95
+ * This handles the full polling loop until success, timeout, or error
96
+ */
97
+ export async function pollUntilComplete(clientId, deviceCode, expiresIn, initialInterval, onPoll) {
98
+ const startTime = Date.now();
99
+ const expiresAt = startTime + expiresIn * 1000;
100
+ let interval = initialInterval;
101
+ let attempt = 0;
102
+ while (Date.now() < expiresAt) {
103
+ await sleep(interval * 1000);
104
+ attempt++;
105
+ if (onPoll) {
106
+ onPoll(attempt);
107
+ }
108
+ const result = await pollForAccessToken(clientId, deviceCode);
109
+ switch (result.status) {
110
+ case "success":
111
+ return { success: true, token: result.token };
112
+ case "pending":
113
+ // Continue polling
114
+ break;
115
+ case "slow_down":
116
+ interval += result.newInterval;
117
+ break;
118
+ case "expired":
119
+ return { success: false, reason: "expired" };
120
+ case "access_denied":
121
+ return { success: false, reason: "denied" };
122
+ case "error":
123
+ return { success: false, reason: "error", message: result.message };
124
+ }
125
+ }
126
+ return { success: false, reason: "expired" };
127
+ }
128
+ /**
129
+ * Open URL in the default browser
130
+ */
131
+ export async function openBrowser(url) {
132
+ const { exec } = await import("child_process");
133
+ const { promisify } = await import("util");
134
+ const execAsync = promisify(exec);
135
+ // Determine the command based on the platform
136
+ const platform = process.platform;
137
+ let command;
138
+ if (platform === "darwin") {
139
+ command = `open "${url}"`;
140
+ }
141
+ else if (platform === "win32") {
142
+ command = `start "" "${url}"`;
143
+ }
144
+ else {
145
+ // Linux and others
146
+ command = `xdg-open "${url}"`;
147
+ }
148
+ await execAsync(command);
149
+ }
150
+ // ============================================================================
151
+ // GitHub App Installation Management
152
+ // ============================================================================
153
+ const GITHUB_API_URL = "https://api.github.com";
154
+ /**
155
+ * Get all GitHub App installations for the authenticated user
156
+ */
157
+ export async function getUserInstallations(userToken) {
158
+ const response = await fetch(`${GITHUB_API_URL}/user/installations`, {
159
+ headers: {
160
+ Accept: "application/vnd.github+json",
161
+ Authorization: `Bearer ${userToken}`,
162
+ "X-GitHub-Api-Version": "2022-11-28",
163
+ },
164
+ });
165
+ if (!response.ok) {
166
+ const error = await response.text();
167
+ throw new Error(`Failed to get installations: ${response.status} - ${error}`);
168
+ }
169
+ const data = (await response.json());
170
+ return data.installations;
171
+ }
172
+ /**
173
+ * Get repositories accessible by a specific installation
174
+ */
175
+ export async function getInstallationRepositories(userToken, installationId) {
176
+ const response = await fetch(`${GITHUB_API_URL}/user/installations/${installationId}/repositories`, {
177
+ headers: {
178
+ Accept: "application/vnd.github+json",
179
+ Authorization: `Bearer ${userToken}`,
180
+ "X-GitHub-Api-Version": "2022-11-28",
181
+ },
182
+ });
183
+ if (!response.ok) {
184
+ const error = await response.text();
185
+ throw new Error(`Failed to get installation repositories: ${response.status} - ${error}`);
186
+ }
187
+ const data = (await response.json());
188
+ return data.repositories;
189
+ }
190
+ /**
191
+ * Get an installation access token for API operations
192
+ */
193
+ export async function getInstallationToken(userToken, installationId) {
194
+ const response = await fetch(`${GITHUB_API_URL}/user/installations/${installationId}/access_tokens`, {
195
+ method: "POST",
196
+ headers: {
197
+ Accept: "application/vnd.github+json",
198
+ Authorization: `Bearer ${userToken}`,
199
+ "X-GitHub-Api-Version": "2022-11-28",
200
+ },
201
+ });
202
+ if (!response.ok) {
203
+ const error = await response.text();
204
+ throw new Error(`Failed to get installation token: ${response.status} - ${error}`);
205
+ }
206
+ return (await response.json());
207
+ }
208
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Platform-native paths for configuration, data, and cache storage.
3
+ *
4
+ * Uses env-paths to provide appropriate directories per platform:
5
+ * - macOS: ~/Library/Application Support/skilluse/, ~/Library/Caches/skilluse/
6
+ * - Linux: ~/.config/skilluse/, ~/.local/share/skilluse/, ~/.cache/skilluse/
7
+ * - Windows: %APPDATA%/skilluse/, %LOCALAPPDATA%/skilluse/
8
+ */
9
+ /** Directory for user configuration (settings, preferences) */
10
+ export declare const configPath: string;
11
+ /** Directory for application data (installed skills, repos) */
12
+ export declare const dataPath: string;
13
+ /** Directory for cached data (temporary files) */
14
+ export declare const cachePath: string;
15
+ /** Directory for log files */
16
+ export declare const logPath: string;
17
+ /** Directory for temporary files */
18
+ export declare const tempPath: string;
19
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Platform-native paths for configuration, data, and cache storage.
3
+ *
4
+ * Uses env-paths to provide appropriate directories per platform:
5
+ * - macOS: ~/Library/Application Support/skilluse/, ~/Library/Caches/skilluse/
6
+ * - Linux: ~/.config/skilluse/, ~/.local/share/skilluse/, ~/.cache/skilluse/
7
+ * - Windows: %APPDATA%/skilluse/, %LOCALAPPDATA%/skilluse/
8
+ */
9
+ import envPaths from "env-paths";
10
+ const paths = envPaths("skilluse", { suffix: "" });
11
+ /** Directory for user configuration (settings, preferences) */
12
+ export const configPath = paths.config;
13
+ /** Directory for application data (installed skills, repos) */
14
+ export const dataPath = paths.data;
15
+ /** Directory for cached data (temporary files) */
16
+ export const cachePath = paths.cache;
17
+ /** Directory for log files */
18
+ export const logPath = paths.log;
19
+ /** Directory for temporary files */
20
+ export const tempPath = paths.temp;
21
+ //# sourceMappingURL=paths.js.map