create-hq 10.10.0 → 10.12.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/commands/{team-sync.md → sync-team.md} +167 -6
- package/dist/__tests__/auth.test.js +176 -186
- package/dist/__tests__/auth.test.js.map +1 -1
- package/dist/__tests__/scaffold.test.js +5 -3
- package/dist/__tests__/scaffold.test.js.map +1 -1
- package/dist/auth.d.ts +57 -129
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +81 -370
- package/dist/auth.js.map +1 -1
- package/dist/fetch-template.d.ts +7 -2
- package/dist/fetch-template.d.ts.map +1 -1
- package/dist/fetch-template.js +27 -15
- package/dist/fetch-template.js.map +1 -1
- package/dist/index.js +7 -17
- package/dist/index.js.map +1 -1
- package/dist/packages.d.ts.map +1 -1
- package/dist/packages.js +1 -1
- package/dist/packages.js.map +1 -1
- package/dist/recommended-packages.d.ts +93 -0
- package/dist/recommended-packages.d.ts.map +1 -0
- package/dist/recommended-packages.js +221 -0
- package/dist/recommended-packages.js.map +1 -0
- package/dist/scaffold.d.ts +4 -2
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +127 -232
- package/dist/scaffold.js.map +1 -1
- package/dist/ui.d.ts +0 -17
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +0 -45
- package/dist/ui.js.map +1 -1
- package/package.json +2 -1
- package/dist/admin-onboarding.d.ts +0 -44
- package/dist/admin-onboarding.d.ts.map +0 -1
- package/dist/admin-onboarding.js +0 -530
- package/dist/admin-onboarding.js.map +0 -1
- package/dist/company-template.d.ts +0 -34
- package/dist/company-template.d.ts.map +0 -1
- package/dist/company-template.js +0 -142
- package/dist/company-template.js.map +0 -1
- package/dist/invite-command.d.ts +0 -10
- package/dist/invite-command.d.ts.map +0 -1
- package/dist/invite-command.js +0 -110
- package/dist/invite-command.js.map +0 -1
- package/dist/invite.d.ts +0 -91
- package/dist/invite.d.ts.map +0 -1
- package/dist/invite.js +0 -230
- package/dist/invite.js.map +0 -1
- package/dist/join-flow.d.ts +0 -32
- package/dist/join-flow.d.ts.map +0 -1
- package/dist/join-flow.js +0 -315
- package/dist/join-flow.js.map +0 -1
- package/dist/team-setup.d.ts +0 -83
- package/dist/team-setup.d.ts.map +0 -1
- package/dist/team-setup.js +0 -353
- package/dist/team-setup.js.map +0 -1
- package/dist/teams-flow.d.ts +0 -41
- package/dist/teams-flow.d.ts.map +0 -1
- package/dist/teams-flow.js +0 -173
- package/dist/teams-flow.js.map +0 -1
package/dist/auth.js
CHANGED
|
@@ -1,397 +1,108 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* Token values are NEVER written to stdout or logs.
|
|
19
|
-
*/
|
|
20
|
-
import * as fs from "fs";
|
|
21
|
-
import * as path from "path";
|
|
22
|
-
import * as os from "os";
|
|
23
|
-
import { exec, execSync } from "child_process";
|
|
24
|
-
import chalk from "chalk";
|
|
25
|
-
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
26
|
-
/** hq-team-sync GitHub App client ID (public — safe to commit). */
|
|
27
|
-
export const HQ_GITHUB_APP_CLIENT_ID = "Iv23liSdkCBQYhrNcRmI";
|
|
28
|
-
export const HQ_GITHUB_APP_SLUG = "hq-team-sync";
|
|
29
|
-
const GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
30
|
-
const GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
31
|
-
const GITHUB_API_USER_URL = "https://api.github.com/user";
|
|
32
|
-
const HQ_DIR = path.join(os.homedir(), ".hq");
|
|
33
|
-
/** Where the HQ App token is persisted. Exported for tests. */
|
|
34
|
-
export const HQ_APP_TOKEN_PATH = path.join(HQ_DIR, "app-token.json");
|
|
35
|
-
// ─── Token persistence (~/.hq/app-token.json) ────────────────────────────
|
|
36
|
-
/**
|
|
37
|
-
* Save the HQ App auth to ~/.hq/app-token.json.
|
|
38
|
-
*
|
|
39
|
-
* The file is written with mode 0600 (owner read+write only). The user's
|
|
40
|
-
* existing `gh` CLI auth is never touched.
|
|
41
|
-
*
|
|
42
|
-
* @param tokenPath — override for testing; defaults to HQ_APP_TOKEN_PATH
|
|
43
|
-
*/
|
|
44
|
-
export function saveGitHubAuth(auth, tokenPath = HQ_APP_TOKEN_PATH) {
|
|
45
|
-
const dir = path.dirname(tokenPath);
|
|
46
|
-
if (!fs.existsSync(dir)) {
|
|
47
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
48
|
-
}
|
|
49
|
-
fs.writeFileSync(tokenPath, JSON.stringify(auth, null, 2), {
|
|
50
|
-
encoding: "utf-8",
|
|
51
|
-
mode: 0o600,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Load HQ App auth from ~/.hq/app-token.json.
|
|
56
|
-
*
|
|
57
|
-
* Returns null if the file doesn't exist, is corrupted, or is missing
|
|
58
|
-
* required fields. Does NOT fall back to `gh` CLI — the HQ App token
|
|
59
|
-
* is separate from the user's personal GitHub auth.
|
|
60
|
-
*
|
|
61
|
-
* @param tokenPath — override for testing; defaults to HQ_APP_TOKEN_PATH
|
|
2
|
+
* Cognito-based authentication for create-hq.
|
|
3
|
+
*
|
|
4
|
+
* Uses the shared OSS `@indigoai-us/hq-cloud` helper to drive Cognito Hosted UI
|
|
5
|
+
* through a PKCE + loopback OAuth flow. Forces Google as the identity provider
|
|
6
|
+
* so the experience matches hq-installer (the Tauri desktop app) and the rest
|
|
7
|
+
* of the HQ by Indigo suite.
|
|
8
|
+
*
|
|
9
|
+
* Token cache: `~/.hq/cognito-tokens.json` — canonical shared session file used
|
|
10
|
+
* by hq-cloud, hq-cli, hq-installer, and the deploy skill. All four agree on
|
|
11
|
+
* this path, so any of them can refresh or invalidate the session.
|
|
12
|
+
*
|
|
13
|
+
* Exports follow the pattern in hq-cli's `utils/cognito-session.ts`:
|
|
14
|
+
* - DEFAULT_COGNITO — config matching hq-installer's .env.local
|
|
15
|
+
* - ensureCognitoToken({ interactive }) — top-level helper used by the
|
|
16
|
+
* teams flow. Returns `CognitoTokens` or null if sign-in failed.
|
|
17
|
+
* - readIdentity(tokens) — decodes the ID token payload (email, name).
|
|
62
18
|
*/
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
if (!fs.existsSync(tokenPath))
|
|
66
|
-
return null;
|
|
67
|
-
const raw = fs.readFileSync(tokenPath, "utf-8");
|
|
68
|
-
const data = JSON.parse(raw);
|
|
69
|
-
// Minimal validation — must have at least a token and login
|
|
70
|
-
if (!data.access_token || !data.login)
|
|
71
|
-
return null;
|
|
72
|
-
return data;
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Remove stored HQ App credentials.
|
|
80
|
-
*
|
|
81
|
-
* Deletes ~/.hq/app-token.json. Does NOT touch `gh` CLI auth.
|
|
82
|
-
*
|
|
83
|
-
* @param tokenPath — override for testing; defaults to HQ_APP_TOKEN_PATH
|
|
84
|
-
*/
|
|
85
|
-
export function clearGitHubAuth(tokenPath = HQ_APP_TOKEN_PATH) {
|
|
86
|
-
try {
|
|
87
|
-
if (fs.existsSync(tokenPath)) {
|
|
88
|
-
fs.unlinkSync(tokenPath);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
// ignore — may already be gone
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Quick liveness probe — does the stored token still work?
|
|
97
|
-
* Validates the token by hitting GET /user on api.github.com.
|
|
98
|
-
*/
|
|
99
|
-
export async function isGitHubAuthValid(auth) {
|
|
100
|
-
if (!auth.access_token)
|
|
101
|
-
return false;
|
|
102
|
-
try {
|
|
103
|
-
const res = await fetch(GITHUB_API_USER_URL, {
|
|
104
|
-
headers: {
|
|
105
|
-
Authorization: `token ${auth.access_token}`,
|
|
106
|
-
Accept: "application/vnd.github+json",
|
|
107
|
-
"User-Agent": "create-hq",
|
|
108
|
-
},
|
|
109
|
-
signal: AbortSignal.timeout(10_000),
|
|
110
|
-
});
|
|
111
|
-
return res.ok;
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
19
|
+
import { browserLogin, refreshTokens, loadCachedTokens, clearCachedTokens, isExpiring, } from "@indigoai-us/hq-cloud";
|
|
117
20
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
21
|
+
* Default Cognito pool for HQ by Indigo. Mirrors hq-installer's `.env.local`
|
|
22
|
+
* and hq-cli's `DEFAULT_COGNITO`. All three tools share the same pool so a
|
|
23
|
+
* single sign-in works across installer, CLI, and create-hq.
|
|
24
|
+
*
|
|
25
|
+
* Defaults point at the shared `vault-indigo-hq-prod` Cognito pool (canonical
|
|
26
|
+
* post-2026-04-25 cutover). The legacy `hq-vault-dev` pool is no longer the
|
|
27
|
+
* fallback — it remains reachable only via explicit `HQ_COGNITO_DOMAIN` env
|
|
28
|
+
* override for staging tests. Mirrors `packages/hq-cli/src/utils/cognito-session.ts`
|
|
29
|
+
* and `packages/hq-cloud/src/bin/sync-runner.ts` so all three callers stay
|
|
30
|
+
* drift-free.
|
|
31
|
+
*
|
|
32
|
+
* Port 8765 matches hq-cli (not hq-installer's 53682) — Cognito App Client
|
|
33
|
+
* callback URLs must list every port we use, and we're standardizing on 8765
|
|
34
|
+
* for all Node-based callers.
|
|
127
35
|
*/
|
|
128
|
-
export
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
signal: AbortSignal.timeout(10_000),
|
|
139
|
-
});
|
|
140
|
-
if (res.ok)
|
|
141
|
-
return "yes";
|
|
142
|
-
// 401/403 = definitive "wrong token type"
|
|
143
|
-
if (res.status === 401 || res.status === 403)
|
|
144
|
-
return "no";
|
|
145
|
-
// Anything else (429, 5xx) = transient
|
|
146
|
-
return "unknown";
|
|
147
|
-
}
|
|
148
|
-
catch {
|
|
149
|
-
// Network error, timeout, DNS failure = transient
|
|
150
|
-
return "unknown";
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
// ─── Browser open (cross-platform) ──────────────────────────────────────────
|
|
154
|
-
/** Open a URL in the user's default browser. Best-effort, never throws. */
|
|
155
|
-
export function openBrowser(url) {
|
|
156
|
-
let command;
|
|
157
|
-
if (process.platform === "darwin") {
|
|
158
|
-
command = `open "${url}"`;
|
|
159
|
-
}
|
|
160
|
-
else if (process.platform === "win32") {
|
|
161
|
-
// Windows: `start` is a cmd.exe builtin. The empty quotes are the
|
|
162
|
-
// window title argument (start treats the first quoted arg as title).
|
|
163
|
-
command = `start "" "${url}"`;
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
command = `xdg-open "${url}"`;
|
|
167
|
-
}
|
|
168
|
-
exec(command, (err) => {
|
|
169
|
-
if (err) {
|
|
170
|
-
console.error(chalk.dim(` (could not open browser automatically — visit ${url} manually)`));
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
// ─── Device flow ────────────────────────────────────────────────────────────
|
|
175
|
-
function sleep(ms) {
|
|
176
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
177
|
-
}
|
|
36
|
+
export const DEFAULT_COGNITO = {
|
|
37
|
+
region: process.env.AWS_REGION ?? "us-east-1",
|
|
38
|
+
userPoolDomain: process.env.HQ_COGNITO_DOMAIN ?? "vault-indigo-hq-prod",
|
|
39
|
+
clientId: process.env.HQ_COGNITO_CLIENT_ID ?? "7acei2c8v870enheptb1j5foln",
|
|
40
|
+
port: process.env.HQ_COGNITO_CALLBACK_PORT
|
|
41
|
+
? Number(process.env.HQ_COGNITO_CALLBACK_PORT)
|
|
42
|
+
: 8765,
|
|
43
|
+
identityProvider: "Google",
|
|
44
|
+
prompt: "select_account",
|
|
45
|
+
};
|
|
178
46
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
* 1. Returned in-memory as part of GitHubAuth (for the current session)
|
|
183
|
-
* 2. Persisted to ~/.hq/app-token.json for future sessions
|
|
184
|
-
*
|
|
185
|
-
* The user's existing `gh` CLI auth is never modified.
|
|
47
|
+
* Return a valid set of Cognito tokens, refreshing or launching the browser
|
|
48
|
+
* flow as needed. Returns null when interactive login fails (user closed the
|
|
49
|
+
* tab, timeout, network error) — caller should treat that as "user declined".
|
|
186
50
|
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
* - Device code expiration
|
|
191
|
-
* - Failure to fetch the user profile
|
|
51
|
+
* When `interactive: false`, this function will never open a browser — it
|
|
52
|
+
* returns null if no valid cached token is available. Useful for CI / non-TTY
|
|
53
|
+
* environments where we want a hard fail instead of a hung browser prompt.
|
|
192
54
|
*/
|
|
193
|
-
export async function
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
"User-Agent": "create-hq",
|
|
201
|
-
},
|
|
202
|
-
body: JSON.stringify({
|
|
203
|
-
client_id: HQ_GITHUB_APP_CLIENT_ID,
|
|
204
|
-
}),
|
|
205
|
-
signal: AbortSignal.timeout(15_000),
|
|
206
|
-
});
|
|
207
|
-
if (!deviceRes.ok) {
|
|
208
|
-
const body = await deviceRes.text().catch(() => "");
|
|
209
|
-
throw new Error(`GitHub device code request failed (${deviceRes.status}): ${body}`);
|
|
210
|
-
}
|
|
211
|
-
const device = (await deviceRes.json());
|
|
212
|
-
// 2. Display the code and open the browser
|
|
213
|
-
console.log();
|
|
214
|
-
console.log(chalk.bold(" Sign in with GitHub"));
|
|
215
|
-
console.log();
|
|
216
|
-
console.log(` Open this URL in your browser:`);
|
|
217
|
-
console.log(` ${chalk.cyan(device.verification_uri)}`);
|
|
218
|
-
console.log();
|
|
219
|
-
console.log(` Enter this code: ${chalk.bold.white(device.user_code)}`);
|
|
220
|
-
console.log();
|
|
221
|
-
console.log(chalk.dim(" Waiting for authorization..."));
|
|
222
|
-
openBrowser(device.verification_uri);
|
|
223
|
-
// 3. Poll for token
|
|
224
|
-
let pollInterval = Math.max((device.interval ?? 5) * 1000, 5000);
|
|
225
|
-
const expiresAt = Date.now() + (device.expires_in ?? 900) * 1000;
|
|
226
|
-
while (Date.now() < expiresAt) {
|
|
227
|
-
await sleep(pollInterval);
|
|
228
|
-
let tokenRes;
|
|
55
|
+
export async function ensureCognitoToken(opts = {}) {
|
|
56
|
+
const interactive = opts.interactive ?? true;
|
|
57
|
+
const config = opts.config ?? DEFAULT_COGNITO;
|
|
58
|
+
const cached = loadCachedTokens();
|
|
59
|
+
if (cached && !isExpiring(cached))
|
|
60
|
+
return cached;
|
|
61
|
+
if (cached) {
|
|
229
62
|
try {
|
|
230
|
-
|
|
231
|
-
method: "POST",
|
|
232
|
-
headers: {
|
|
233
|
-
Accept: "application/json",
|
|
234
|
-
"Content-Type": "application/json",
|
|
235
|
-
"User-Agent": "create-hq",
|
|
236
|
-
},
|
|
237
|
-
body: JSON.stringify({
|
|
238
|
-
client_id: HQ_GITHUB_APP_CLIENT_ID,
|
|
239
|
-
device_code: device.device_code,
|
|
240
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
241
|
-
}),
|
|
242
|
-
signal: AbortSignal.timeout(15_000),
|
|
243
|
-
});
|
|
63
|
+
return await refreshTokens(config, cached.refreshToken);
|
|
244
64
|
}
|
|
245
65
|
catch {
|
|
246
|
-
//
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
const data = (await tokenRes.json().catch(() => ({})));
|
|
250
|
-
if (data.error === "authorization_pending")
|
|
251
|
-
continue;
|
|
252
|
-
if (data.error === "slow_down") {
|
|
253
|
-
// Per spec: increase polling interval by at least 5 seconds
|
|
254
|
-
const bump = (data.interval ?? 5) * 1000;
|
|
255
|
-
pollInterval = Math.max(pollInterval + bump, pollInterval + 5000);
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
if (data.error === "expired_token") {
|
|
259
|
-
throw new Error("GitHub device code expired — please run create-hq again");
|
|
260
|
-
}
|
|
261
|
-
if (data.error === "access_denied") {
|
|
262
|
-
throw new Error("Authorization was denied");
|
|
263
|
-
}
|
|
264
|
-
if (data.error) {
|
|
265
|
-
throw new Error(`GitHub auth error: ${data.error}${data.error_description ? ` — ${data.error_description}` : ""}`);
|
|
66
|
+
// Refresh failed — fall through to interactive login (or return null
|
|
67
|
+
// if interactive is disabled).
|
|
266
68
|
}
|
|
267
|
-
if (!data.access_token) {
|
|
268
|
-
throw new Error("GitHub returned no access_token");
|
|
269
|
-
}
|
|
270
|
-
// 4. Fetch the authenticated user profile
|
|
271
|
-
const userRes = await fetch(GITHUB_API_USER_URL, {
|
|
272
|
-
headers: {
|
|
273
|
-
Authorization: `token ${data.access_token}`,
|
|
274
|
-
Accept: "application/vnd.github+json",
|
|
275
|
-
"User-Agent": "create-hq",
|
|
276
|
-
},
|
|
277
|
-
signal: AbortSignal.timeout(15_000),
|
|
278
|
-
});
|
|
279
|
-
if (!userRes.ok) {
|
|
280
|
-
const body = await userRes.text().catch(() => "");
|
|
281
|
-
throw new Error(`Failed to fetch GitHub user (${userRes.status}): ${body}`);
|
|
282
|
-
}
|
|
283
|
-
const user = (await userRes.json());
|
|
284
|
-
const auth = {
|
|
285
|
-
access_token: data.access_token,
|
|
286
|
-
login: user.login,
|
|
287
|
-
id: user.id,
|
|
288
|
-
name: user.name,
|
|
289
|
-
email: user.email,
|
|
290
|
-
issued_at: new Date().toISOString(),
|
|
291
|
-
};
|
|
292
|
-
// Persist for future sessions (does not touch gh CLI)
|
|
293
|
-
saveGitHubAuth(auth);
|
|
294
|
-
return auth;
|
|
295
69
|
}
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
// ─── gh CLI token (opportunistic) ──────────────────────────────────────────
|
|
299
|
-
/**
|
|
300
|
-
* Try to get the user's `gh` CLI OAuth token.
|
|
301
|
-
*
|
|
302
|
-
* Returns the token string if `gh` is installed, the user is logged in, and
|
|
303
|
-
* `gh auth token` succeeds. Returns null otherwise — this is purely
|
|
304
|
-
* opportunistic and never throws.
|
|
305
|
-
*
|
|
306
|
-
* The gh CLI token typically has `read:org` scope, which lets us enumerate
|
|
307
|
-
* ALL the user's org memberships — not just orgs where our GitHub App is
|
|
308
|
-
* installed. This gives us a better org picker during admin onboarding.
|
|
309
|
-
*/
|
|
310
|
-
export function getGhCliToken() {
|
|
311
|
-
try {
|
|
312
|
-
const token = execSync("gh auth token", {
|
|
313
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
314
|
-
timeout: 5_000,
|
|
315
|
-
})
|
|
316
|
-
.toString()
|
|
317
|
-
.trim();
|
|
318
|
-
// Sanity check — gh tokens start with gho_ or ghp_ (OAuth / PAT)
|
|
319
|
-
if (token && token.length > 10)
|
|
320
|
-
return token;
|
|
70
|
+
if (!interactive)
|
|
321
71
|
return null;
|
|
72
|
+
try {
|
|
73
|
+
return await browserLogin(config);
|
|
322
74
|
}
|
|
323
75
|
catch {
|
|
324
76
|
return null;
|
|
325
77
|
}
|
|
326
78
|
}
|
|
327
79
|
/**
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
* The difference: a gh CLI token with `read:org` scope sees ALL orgs,
|
|
332
|
-
* while an App token only sees orgs where the App is installed.
|
|
333
|
-
*
|
|
334
|
-
* Returns an empty array on any error (permissions, network, etc.).
|
|
80
|
+
* Decode the ID token payload. No signature verification — we only read
|
|
81
|
+
* display fields (email, name) for the UI. Never trust these claims for
|
|
82
|
+
* authorization; the server re-verifies the JWT on every API call.
|
|
335
83
|
*/
|
|
336
|
-
export
|
|
84
|
+
export function readIdentity(tokens) {
|
|
337
85
|
try {
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
.map((m) => ({
|
|
352
|
-
login: m.organization.login,
|
|
353
|
-
id: m.organization.id,
|
|
354
|
-
}));
|
|
86
|
+
const [, payloadB64] = tokens.idToken.split(".");
|
|
87
|
+
if (!payloadB64)
|
|
88
|
+
return null;
|
|
89
|
+
const b64 = payloadB64.replace(/-/g, "+").replace(/_/g, "/");
|
|
90
|
+
const pad = b64.length % 4 === 0 ? "" : "=".repeat(4 - (b64.length % 4));
|
|
91
|
+
const json = Buffer.from(b64 + pad, "base64").toString("utf-8");
|
|
92
|
+
const claims = JSON.parse(json);
|
|
93
|
+
return {
|
|
94
|
+
sub: String(claims.sub ?? ""),
|
|
95
|
+
email: typeof claims.email === "string" ? claims.email : undefined,
|
|
96
|
+
name: typeof claims.name === "string" ? claims.name : undefined,
|
|
97
|
+
claims,
|
|
98
|
+
};
|
|
355
99
|
}
|
|
356
100
|
catch {
|
|
357
|
-
return
|
|
101
|
+
return null;
|
|
358
102
|
}
|
|
359
103
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
* the response body included for diagnostics.
|
|
364
|
-
*/
|
|
365
|
-
export async function githubApi(pathname, auth, init = {}) {
|
|
366
|
-
const url = pathname.startsWith("https://")
|
|
367
|
-
? pathname
|
|
368
|
-
: `https://api.github.com${pathname.startsWith("/") ? "" : "/"}${pathname}`;
|
|
369
|
-
const res = await fetch(url, {
|
|
370
|
-
...init,
|
|
371
|
-
headers: {
|
|
372
|
-
Authorization: `token ${auth.access_token}`,
|
|
373
|
-
Accept: "application/vnd.github+json",
|
|
374
|
-
"User-Agent": "create-hq",
|
|
375
|
-
...(init.headers || {}),
|
|
376
|
-
},
|
|
377
|
-
signal: init.signal ?? AbortSignal.timeout(20_000),
|
|
378
|
-
});
|
|
379
|
-
if (!res.ok) {
|
|
380
|
-
const body = await res.text().catch(() => "");
|
|
381
|
-
// Friendly error when a non-App token hits the App-only installations endpoint.
|
|
382
|
-
if (res.status === 403 &&
|
|
383
|
-
pathname.startsWith("/user/installations") &&
|
|
384
|
-
body.includes("authorized to a GitHub App")) {
|
|
385
|
-
throw new Error("You're signed in with a regular GitHub token that can't list App installations.\n" +
|
|
386
|
-
" HQ Teams requires authentication through the HQ GitHub App.\n\n" +
|
|
387
|
-
" To fix this, re-run the installer — it will prompt you to authorize the HQ App:\n" +
|
|
388
|
-
" npx create-hq");
|
|
389
|
-
}
|
|
390
|
-
throw new Error(`GitHub API ${res.status} ${pathname}: ${body}`);
|
|
391
|
-
}
|
|
392
|
-
// Some endpoints return 204
|
|
393
|
-
if (res.status === 204)
|
|
394
|
-
return undefined;
|
|
395
|
-
return (await res.json());
|
|
104
|
+
/** Delete the cached Cognito session. */
|
|
105
|
+
export function signOut() {
|
|
106
|
+
clearCachedTokens();
|
|
396
107
|
}
|
|
397
108
|
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/fetch-template.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Fetch the
|
|
2
|
+
* Fetch the hq-core scaffold from GitHub and extract it into targetDir.
|
|
3
|
+
*
|
|
4
|
+
* hq-core is the standalone scaffold seed (formerly the `template/` subdir of
|
|
5
|
+
* `indigoai-us/hq`). Its entire repository tree is the fresh-install content.
|
|
6
|
+
* Rich add-ons ship separately as `@indigoai-us/hq-pack-*` content packs
|
|
7
|
+
* installed by the next phase of create-hq via `hq install`.
|
|
3
8
|
*
|
|
4
9
|
* Strategy:
|
|
5
10
|
* 1. GitHub REST API → download tarball_url
|
|
6
|
-
* 2. Fallback: gh CLI (`gh api repos/indigoai-us/hq/tarball/{ref}`)
|
|
11
|
+
* 2. Fallback: gh CLI (`gh api repos/indigoai-us/hq-core/tarball/{ref}`)
|
|
7
12
|
* 3. If both fail: throw with manual clone instructions
|
|
8
13
|
*
|
|
9
14
|
* Returns the version tag that was fetched.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-template.d.ts","sourceRoot":"","sources":["../src/fetch-template.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fetch-template.d.ts","sourceRoot":"","sources":["../src/fetch-template.ts"],"names":[],"mappings":"AA+JA;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GACnC,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAyC9B"}
|
package/dist/fetch-template.js
CHANGED
|
@@ -4,7 +4,12 @@ import * as os from "os";
|
|
|
4
4
|
import { exec } from "child_process";
|
|
5
5
|
import { promisify } from "util";
|
|
6
6
|
const execAsync = promisify(exec);
|
|
7
|
-
|
|
7
|
+
// Scaffold source: the hq-core repo IS the scaffold (its entire tree is the
|
|
8
|
+
// starter content). This replaced the older `indigoai-us/hq` + `template/`
|
|
9
|
+
// subdirectory pattern when the monorepo was split (hq-core v12+). Old tags
|
|
10
|
+
// still on the `indigoai-us/hq` side will not resolve via this client — users
|
|
11
|
+
// on those must use the matching create-hq pre-v12 release.
|
|
12
|
+
const REPO = "indigoai-us/hq-core";
|
|
8
13
|
function formatBytes(bytes) {
|
|
9
14
|
if (bytes < 1024)
|
|
10
15
|
return `${bytes} B`;
|
|
@@ -80,8 +85,9 @@ async function downloadAndExtractViaApi(tarballUrl, targetDir, onProgress) {
|
|
|
80
85
|
}
|
|
81
86
|
}
|
|
82
87
|
async function extractTemplateDirFromTar(tarPath, targetDir, onProgress) {
|
|
83
|
-
// The tarball contains a top-level directory like `indigoai-us-hq-<sha
|
|
84
|
-
//
|
|
88
|
+
// The tarball contains a top-level directory like `indigoai-us-hq-core-<sha>/`.
|
|
89
|
+
// The hq-core repo IS the scaffold — every file in it becomes part of the
|
|
90
|
+
// new HQ instance. Copy the entire root directory's contents (not a subdir).
|
|
85
91
|
const tmpExtract = await fs.mkdtemp(path.join(os.tmpdir(), "create-hq-extract-"));
|
|
86
92
|
try {
|
|
87
93
|
// Extract the full tarball to a temp location
|
|
@@ -92,14 +98,15 @@ async function extractTemplateDirFromTar(tarPath, targetDir, onProgress) {
|
|
|
92
98
|
throw new Error("Tarball was empty");
|
|
93
99
|
}
|
|
94
100
|
const rootDir = path.join(tmpExtract, entries[0]);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
// Sanity check — hq-core must carry core.yaml at its root.
|
|
102
|
+
const coreYaml = path.join(rootDir, "core.yaml");
|
|
103
|
+
if (!await fs.pathExists(coreYaml)) {
|
|
104
|
+
throw new Error("core.yaml not found at tarball root — this does not look like an hq-core scaffold");
|
|
98
105
|
}
|
|
99
|
-
// Copy
|
|
100
|
-
onProgress?.("Copying
|
|
106
|
+
// Copy the entire hq-core tree to targetDir
|
|
107
|
+
onProgress?.("Copying scaffold files...");
|
|
101
108
|
await fs.ensureDir(targetDir);
|
|
102
|
-
await fs.copy(
|
|
109
|
+
await fs.copy(rootDir, targetDir, { overwrite: true });
|
|
103
110
|
}
|
|
104
111
|
finally {
|
|
105
112
|
await fs.remove(tmpExtract);
|
|
@@ -122,11 +129,16 @@ async function extractTemplateDirViaGhCli(tag, targetDir, onProgress) {
|
|
|
122
129
|
}
|
|
123
130
|
}
|
|
124
131
|
/**
|
|
125
|
-
* Fetch the
|
|
132
|
+
* Fetch the hq-core scaffold from GitHub and extract it into targetDir.
|
|
133
|
+
*
|
|
134
|
+
* hq-core is the standalone scaffold seed (formerly the `template/` subdir of
|
|
135
|
+
* `indigoai-us/hq`). Its entire repository tree is the fresh-install content.
|
|
136
|
+
* Rich add-ons ship separately as `@indigoai-us/hq-pack-*` content packs
|
|
137
|
+
* installed by the next phase of create-hq via `hq install`.
|
|
126
138
|
*
|
|
127
139
|
* Strategy:
|
|
128
140
|
* 1. GitHub REST API → download tarball_url
|
|
129
|
-
* 2. Fallback: gh CLI (`gh api repos/indigoai-us/hq/tarball/{ref}`)
|
|
141
|
+
* 2. Fallback: gh CLI (`gh api repos/indigoai-us/hq-core/tarball/{ref}`)
|
|
130
142
|
* 3. If both fail: throw with manual clone instructions
|
|
131
143
|
*
|
|
132
144
|
* Returns the version tag that was fetched.
|
|
@@ -162,13 +174,13 @@ export async function fetchTemplate(targetDir, tag, onProgress) {
|
|
|
162
174
|
// Both failed — provide a clear error message.
|
|
163
175
|
const apiMsg = apiError instanceof Error ? apiError.message : String(apiError);
|
|
164
176
|
const ghMsg = ghErr instanceof Error ? ghErr.message : String(ghErr);
|
|
165
|
-
throw new Error(`Failed to fetch
|
|
177
|
+
throw new Error(`Failed to fetch hq-core scaffold from GitHub.\n\n` +
|
|
166
178
|
` GitHub API error: ${apiMsg}\n` +
|
|
167
179
|
` gh CLI error: ${ghMsg}\n\n` +
|
|
168
180
|
`You appear to be offline or rate-limited.\n` +
|
|
169
|
-
`To set up HQ manually, clone the repo
|
|
170
|
-
` git clone https://github.com/indigoai-us/hq.git\n` +
|
|
171
|
-
`
|
|
181
|
+
`To set up HQ manually, clone the scaffold repo:\n\n` +
|
|
182
|
+
` git clone https://github.com/indigoai-us/hq-core.git ${targetDir}\n` +
|
|
183
|
+
` rm -rf ${targetDir}/.git\n`);
|
|
172
184
|
}
|
|
173
185
|
}
|
|
174
186
|
//# sourceMappingURL=fetch-template.js.map
|