@indigoai-us/hq-cli 5.5.1 → 5.5.2
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/bin/hq-auth-refresh.js +0 -0
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +2 -1
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/auth.d.ts +0 -3
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +0 -3
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +18 -1
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/login.d.ts +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +28 -9
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.d.ts +1 -1
- package/dist/commands/logout.d.ts.map +1 -1
- package/dist/commands/logout.js +8 -3
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/pack-install.d.ts +44 -0
- package/dist/commands/pack-install.d.ts.map +1 -0
- package/dist/commands/pack-install.js +513 -0
- package/dist/commands/pack-install.js.map +1 -0
- package/dist/commands/pkg-install.d.ts.map +1 -1
- package/dist/commands/pkg-install.js +20 -8
- package/dist/commands/pkg-install.js.map +1 -1
- package/dist/commands/pkg-list.js +4 -4
- package/dist/commands/pkg-list.js.map +1 -1
- package/dist/commands/pkg-update.js +3 -3
- package/dist/commands/pkg-update.js.map +1 -1
- package/dist/commands/secrets.d.ts +3 -0
- package/dist/commands/secrets.d.ts.map +1 -0
- package/dist/commands/secrets.js +521 -0
- package/dist/commands/secrets.js.map +1 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +10 -2
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/team-sync.d.ts.map +1 -1
- package/dist/commands/team-sync.js +6 -6
- package/dist/commands/team-sync.js.map +1 -1
- package/dist/commands/whoami.d.ts +1 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +23 -7
- package/dist/commands/whoami.js.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/strategies/link.d.ts +2 -2
- package/dist/strategies/link.d.ts.map +1 -1
- package/dist/strategies/link.js.map +1 -1
- package/dist/strategies/merge.d.ts +2 -2
- package/dist/strategies/merge.d.ts.map +1 -1
- package/dist/strategies/merge.js.map +1 -1
- package/dist/types.d.ts +37 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/cognito-session.js +3 -3
- package/dist/utils/cognito-session.js.map +1 -1
- package/dist/utils/manifest.d.ts +13 -0
- package/dist/utils/manifest.d.ts.map +1 -1
- package/dist/utils/manifest.js +24 -1
- package/dist/utils/manifest.js.map +1 -1
- package/dist/utils/secrets-cache.d.ts +7 -0
- package/dist/utils/secrets-cache.d.ts.map +1 -0
- package/dist/utils/secrets-cache.js +134 -0
- package/dist/utils/secrets-cache.js.map +1 -0
- package/package.json +7 -4
- package/src/commands/add.ts +7 -6
- package/src/commands/auth.ts +0 -3
- package/src/commands/list.ts +20 -1
- package/src/commands/login.ts +28 -9
- package/src/commands/logout.ts +8 -3
- package/src/commands/pack-install.ts +667 -0
- package/src/commands/pkg-install.ts +34 -16
- package/src/commands/pkg-list.ts +4 -4
- package/src/commands/pkg-update.ts +3 -3
- package/src/commands/secrets.ts +670 -0
- package/src/commands/sync.ts +20 -4
- package/src/commands/team-sync.ts +7 -8
- package/src/commands/whoami.ts +22 -7
- package/src/index.ts +5 -1
- package/src/strategies/link.ts +2 -2
- package/src/strategies/merge.ts +2 -2
- package/src/types.ts +54 -4
- package/src/utils/cognito-session.ts +3 -3
- package/src/utils/manifest.test.ts +148 -0
- package/src/utils/manifest.ts +22 -1
- package/src/utils/secrets-cache.ts +129 -0
- package/tsconfig.json +2 -1
- package/src/utils/auth.ts +0 -193
- package/src/utils/token-store.ts +0 -83
package/src/utils/auth.ts
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth utilities — OAuth PKCE flow for Clerk (US-004)
|
|
3
|
-
*
|
|
4
|
-
* Token values are NEVER written to stdout or logs.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import * as crypto from 'crypto';
|
|
8
|
-
import * as http from 'http';
|
|
9
|
-
import { exec } from 'child_process';
|
|
10
|
-
import { saveToken, loadToken, isTokenExpired } from './token-store.js';
|
|
11
|
-
import { getRegistryUrl } from './registry-client.js';
|
|
12
|
-
import type { AuthToken } from './token-store.js';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Generate a cryptographically random PKCE code verifier (43-128 chars, URL-safe).
|
|
16
|
-
*/
|
|
17
|
-
function generateCodeVerifier(): string {
|
|
18
|
-
return crypto.randomBytes(32).toString('base64url');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Derive the PKCE code challenge from the verifier (S256).
|
|
23
|
-
*/
|
|
24
|
-
function generateCodeChallenge(verifier: string): string {
|
|
25
|
-
return crypto.createHash('sha256').update(verifier).digest('base64url');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Open a URL in the default browser (macOS / Linux).
|
|
30
|
-
*/
|
|
31
|
-
function openBrowser(url: string): void {
|
|
32
|
-
const command =
|
|
33
|
-
process.platform === 'darwin' ? `open "${url}"` : `xdg-open "${url}"`;
|
|
34
|
-
exec(command, (err) => {
|
|
35
|
-
if (err) {
|
|
36
|
-
// Non-fatal — user can manually open the URL
|
|
37
|
-
console.error(
|
|
38
|
-
`Could not open browser automatically. Please visit:\n ${url}`
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Start the OAuth PKCE auth flow:
|
|
46
|
-
* 1. Generate PKCE code_verifier + code_challenge
|
|
47
|
-
* 2. Start local HTTP server on a random port for the callback
|
|
48
|
-
* 3. Open browser to Clerk auth URL
|
|
49
|
-
* 4. Wait for callback with auth code
|
|
50
|
-
* 5. Exchange code for token via registry /auth/token endpoint
|
|
51
|
-
* 6. Return AuthToken
|
|
52
|
-
*/
|
|
53
|
-
export async function startAuthFlow(
|
|
54
|
-
registryUrl: string
|
|
55
|
-
): Promise<AuthToken> {
|
|
56
|
-
const codeVerifier = generateCodeVerifier();
|
|
57
|
-
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
58
|
-
|
|
59
|
-
return new Promise<AuthToken>((resolve, reject) => {
|
|
60
|
-
const server = http.createServer(async (req, res) => {
|
|
61
|
-
try {
|
|
62
|
-
const url = new URL(req.url ?? '/', `http://localhost`);
|
|
63
|
-
|
|
64
|
-
if (url.pathname !== '/callback') {
|
|
65
|
-
res.writeHead(404);
|
|
66
|
-
res.end('Not found');
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const code = url.searchParams.get('code');
|
|
71
|
-
const error = url.searchParams.get('error');
|
|
72
|
-
|
|
73
|
-
if (error) {
|
|
74
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
75
|
-
res.end(
|
|
76
|
-
'<html><body><h2>Authentication failed</h2><p>You can close this window.</p></body></html>'
|
|
77
|
-
);
|
|
78
|
-
server.close();
|
|
79
|
-
reject(new Error(`Auth failed: ${error}`));
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (!code) {
|
|
84
|
-
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
85
|
-
res.end(
|
|
86
|
-
'<html><body><h2>Missing auth code</h2><p>You can close this window.</p></body></html>'
|
|
87
|
-
);
|
|
88
|
-
server.close();
|
|
89
|
-
reject(new Error('No authorization code received'));
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Exchange code for token
|
|
94
|
-
const tokenResponse = await fetch(`${registryUrl}/auth/token`, {
|
|
95
|
-
method: 'POST',
|
|
96
|
-
headers: { 'Content-Type': 'application/json' },
|
|
97
|
-
body: JSON.stringify({
|
|
98
|
-
code,
|
|
99
|
-
code_verifier: codeVerifier,
|
|
100
|
-
redirect_uri: `http://localhost:${(server.address() as { port: number }).port}/callback`,
|
|
101
|
-
}),
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
if (!tokenResponse.ok) {
|
|
105
|
-
const body = await tokenResponse.text();
|
|
106
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
107
|
-
res.end(
|
|
108
|
-
'<html><body><h2>Token exchange failed</h2><p>You can close this window.</p></body></html>'
|
|
109
|
-
);
|
|
110
|
-
server.close();
|
|
111
|
-
reject(
|
|
112
|
-
new Error(`Token exchange failed (${tokenResponse.status}): ${body}`)
|
|
113
|
-
);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const tokenData = (await tokenResponse.json()) as AuthToken;
|
|
118
|
-
|
|
119
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
120
|
-
res.end(
|
|
121
|
-
'<html><body><h2>Authenticated!</h2><p>You can close this window and return to the terminal.</p></body></html>'
|
|
122
|
-
);
|
|
123
|
-
server.close();
|
|
124
|
-
resolve(tokenData);
|
|
125
|
-
} catch (err) {
|
|
126
|
-
server.close();
|
|
127
|
-
reject(err);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
// Listen on random port
|
|
132
|
-
server.listen(0, '127.0.0.1', () => {
|
|
133
|
-
const addr = server.address() as { port: number };
|
|
134
|
-
const redirectUri = `http://localhost:${addr.port}/callback`;
|
|
135
|
-
const authUrl = `${registryUrl}/auth/login?redirect_uri=${encodeURIComponent(redirectUri)}&code_challenge=${codeChallenge}&code_challenge_method=S256`;
|
|
136
|
-
|
|
137
|
-
console.log('Opening browser for authentication...');
|
|
138
|
-
openBrowser(authUrl);
|
|
139
|
-
console.log(`\nIf the browser doesn't open, visit:\n ${authUrl}\n`);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// Timeout after 2 minutes
|
|
143
|
-
setTimeout(() => {
|
|
144
|
-
server.close();
|
|
145
|
-
reject(new Error('Authentication timed out after 2 minutes'));
|
|
146
|
-
}, 120_000);
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Get a valid auth token:
|
|
152
|
-
* 1. Load from store
|
|
153
|
-
* 2. If expired or within 5 min buffer: try refresh
|
|
154
|
-
* 3. If refresh fails: throw with message to re-login
|
|
155
|
-
* 4. Return valid token
|
|
156
|
-
*
|
|
157
|
-
* Token values are NEVER written to stdout.
|
|
158
|
-
*/
|
|
159
|
-
export async function getAuthToken(): Promise<AuthToken> {
|
|
160
|
-
const token = await loadToken();
|
|
161
|
-
|
|
162
|
-
if (!token) {
|
|
163
|
-
throw new Error("Not logged in. Run 'hq login' to authenticate.");
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (!isTokenExpired(token)) {
|
|
167
|
-
return token;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Attempt refresh
|
|
171
|
-
try {
|
|
172
|
-
const registryUrl = getRegistryUrl();
|
|
173
|
-
const refreshResponse = await fetch(`${registryUrl}/auth/refresh`, {
|
|
174
|
-
method: 'POST',
|
|
175
|
-
headers: { 'Content-Type': 'application/json' },
|
|
176
|
-
body: JSON.stringify({
|
|
177
|
-
clerk_session_token: token.clerk_session_token,
|
|
178
|
-
}),
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
if (refreshResponse.ok) {
|
|
182
|
-
const refreshedToken = (await refreshResponse.json()) as AuthToken;
|
|
183
|
-
await saveToken(refreshedToken);
|
|
184
|
-
return refreshedToken;
|
|
185
|
-
}
|
|
186
|
-
} catch {
|
|
187
|
-
// Refresh failed — fall through to error
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
throw new Error(
|
|
191
|
-
"Session expired. Run 'hq login' to re-authenticate."
|
|
192
|
-
);
|
|
193
|
-
}
|
package/src/utils/token-store.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth token store — manages ~/.hq/auth.json with secure file permissions (US-004)
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import * as fs from 'fs';
|
|
6
|
-
import * as path from 'path';
|
|
7
|
-
import * as os from 'os';
|
|
8
|
-
|
|
9
|
-
export interface AuthToken {
|
|
10
|
-
clerk_session_token: string;
|
|
11
|
-
user_id: string;
|
|
12
|
-
email: string;
|
|
13
|
-
expires_at: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const HQ_DIR = path.join(os.homedir(), '.hq');
|
|
17
|
-
const AUTH_FILE = path.join(HQ_DIR, 'auth.json');
|
|
18
|
-
|
|
19
|
-
function ensureHqDir(): void {
|
|
20
|
-
if (!fs.existsSync(HQ_DIR)) {
|
|
21
|
-
fs.mkdirSync(HQ_DIR, { recursive: true, mode: 0o700 });
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Save auth token to ~/.hq/auth.json with 0600 permissions.
|
|
27
|
-
* Token content is never logged or written to stdout.
|
|
28
|
-
*/
|
|
29
|
-
export async function saveToken(token: AuthToken): Promise<void> {
|
|
30
|
-
ensureHqDir();
|
|
31
|
-
const content = JSON.stringify(token, null, 2);
|
|
32
|
-
fs.writeFileSync(AUTH_FILE, content, { mode: 0o600 });
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Load auth token from ~/.hq/auth.json.
|
|
37
|
-
* Returns null if file is missing, unreadable, or contains invalid JSON.
|
|
38
|
-
*/
|
|
39
|
-
export async function loadToken(): Promise<AuthToken | null> {
|
|
40
|
-
try {
|
|
41
|
-
if (!fs.existsSync(AUTH_FILE)) {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
const content = fs.readFileSync(AUTH_FILE, 'utf-8');
|
|
45
|
-
const parsed = JSON.parse(content);
|
|
46
|
-
|
|
47
|
-
// Validate required fields
|
|
48
|
-
if (
|
|
49
|
-
typeof parsed.clerk_session_token !== 'string' ||
|
|
50
|
-
typeof parsed.user_id !== 'string' ||
|
|
51
|
-
typeof parsed.email !== 'string' ||
|
|
52
|
-
typeof parsed.expires_at !== 'string'
|
|
53
|
-
) {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return parsed as AuthToken;
|
|
58
|
-
} catch {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Delete the auth token file.
|
|
65
|
-
*/
|
|
66
|
-
export async function clearToken(): Promise<void> {
|
|
67
|
-
try {
|
|
68
|
-
if (fs.existsSync(AUTH_FILE)) {
|
|
69
|
-
fs.unlinkSync(AUTH_FILE);
|
|
70
|
-
}
|
|
71
|
-
} catch {
|
|
72
|
-
// Ignore errors — file may already be gone
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Check if a token is expired or within 5 minutes of expiry.
|
|
78
|
-
*/
|
|
79
|
-
export function isTokenExpired(token: AuthToken): boolean {
|
|
80
|
-
const expiresAt = new Date(token.expires_at).getTime();
|
|
81
|
-
const bufferMs = 5 * 60 * 1000; // 5 minutes
|
|
82
|
-
return Date.now() >= expiresAt - bufferMs;
|
|
83
|
-
}
|