@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.
Files changed (91) hide show
  1. package/dist/bin/hq-auth-refresh.js +0 -0
  2. package/dist/commands/add.d.ts.map +1 -1
  3. package/dist/commands/add.js +2 -1
  4. package/dist/commands/add.js.map +1 -1
  5. package/dist/commands/auth.d.ts +0 -3
  6. package/dist/commands/auth.d.ts.map +1 -1
  7. package/dist/commands/auth.js +0 -3
  8. package/dist/commands/auth.js.map +1 -1
  9. package/dist/commands/list.d.ts.map +1 -1
  10. package/dist/commands/list.js +18 -1
  11. package/dist/commands/list.js.map +1 -1
  12. package/dist/commands/login.d.ts +1 -1
  13. package/dist/commands/login.d.ts.map +1 -1
  14. package/dist/commands/login.js +28 -9
  15. package/dist/commands/login.js.map +1 -1
  16. package/dist/commands/logout.d.ts +1 -1
  17. package/dist/commands/logout.d.ts.map +1 -1
  18. package/dist/commands/logout.js +8 -3
  19. package/dist/commands/logout.js.map +1 -1
  20. package/dist/commands/pack-install.d.ts +44 -0
  21. package/dist/commands/pack-install.d.ts.map +1 -0
  22. package/dist/commands/pack-install.js +513 -0
  23. package/dist/commands/pack-install.js.map +1 -0
  24. package/dist/commands/pkg-install.d.ts.map +1 -1
  25. package/dist/commands/pkg-install.js +20 -8
  26. package/dist/commands/pkg-install.js.map +1 -1
  27. package/dist/commands/pkg-list.js +4 -4
  28. package/dist/commands/pkg-list.js.map +1 -1
  29. package/dist/commands/pkg-update.js +3 -3
  30. package/dist/commands/pkg-update.js.map +1 -1
  31. package/dist/commands/secrets.d.ts +3 -0
  32. package/dist/commands/secrets.d.ts.map +1 -0
  33. package/dist/commands/secrets.js +521 -0
  34. package/dist/commands/secrets.js.map +1 -0
  35. package/dist/commands/sync.d.ts.map +1 -1
  36. package/dist/commands/sync.js +10 -2
  37. package/dist/commands/sync.js.map +1 -1
  38. package/dist/commands/team-sync.d.ts.map +1 -1
  39. package/dist/commands/team-sync.js +6 -6
  40. package/dist/commands/team-sync.js.map +1 -1
  41. package/dist/commands/whoami.d.ts +1 -1
  42. package/dist/commands/whoami.d.ts.map +1 -1
  43. package/dist/commands/whoami.js +23 -7
  44. package/dist/commands/whoami.js.map +1 -1
  45. package/dist/index.js +4 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/strategies/link.d.ts +2 -2
  48. package/dist/strategies/link.d.ts.map +1 -1
  49. package/dist/strategies/link.js.map +1 -1
  50. package/dist/strategies/merge.d.ts +2 -2
  51. package/dist/strategies/merge.d.ts.map +1 -1
  52. package/dist/strategies/merge.js.map +1 -1
  53. package/dist/types.d.ts +37 -3
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/types.js +6 -0
  56. package/dist/types.js.map +1 -1
  57. package/dist/utils/cognito-session.js +3 -3
  58. package/dist/utils/cognito-session.js.map +1 -1
  59. package/dist/utils/manifest.d.ts +13 -0
  60. package/dist/utils/manifest.d.ts.map +1 -1
  61. package/dist/utils/manifest.js +24 -1
  62. package/dist/utils/manifest.js.map +1 -1
  63. package/dist/utils/secrets-cache.d.ts +7 -0
  64. package/dist/utils/secrets-cache.d.ts.map +1 -0
  65. package/dist/utils/secrets-cache.js +134 -0
  66. package/dist/utils/secrets-cache.js.map +1 -0
  67. package/package.json +7 -4
  68. package/src/commands/add.ts +7 -6
  69. package/src/commands/auth.ts +0 -3
  70. package/src/commands/list.ts +20 -1
  71. package/src/commands/login.ts +28 -9
  72. package/src/commands/logout.ts +8 -3
  73. package/src/commands/pack-install.ts +667 -0
  74. package/src/commands/pkg-install.ts +34 -16
  75. package/src/commands/pkg-list.ts +4 -4
  76. package/src/commands/pkg-update.ts +3 -3
  77. package/src/commands/secrets.ts +670 -0
  78. package/src/commands/sync.ts +20 -4
  79. package/src/commands/team-sync.ts +7 -8
  80. package/src/commands/whoami.ts +22 -7
  81. package/src/index.ts +5 -1
  82. package/src/strategies/link.ts +2 -2
  83. package/src/strategies/merge.ts +2 -2
  84. package/src/types.ts +54 -4
  85. package/src/utils/cognito-session.ts +3 -3
  86. package/src/utils/manifest.test.ts +148 -0
  87. package/src/utils/manifest.ts +22 -1
  88. package/src/utils/secrets-cache.ts +129 -0
  89. package/tsconfig.json +2 -1
  90. package/src/utils/auth.ts +0 -193
  91. 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
- }
@@ -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
- }