@indigoai-us/hq-cli 5.35.0 → 5.35.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.
@@ -14,10 +14,11 @@
14
14
  * by the deploy + sync skills.
15
15
  */
16
16
 
17
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="a4a30b14-55b3-5206-9148-48aff1a8cdd4")}catch(e){}}();
17
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e8f8cd77-2f24-5e71-8cb6-518095482b22")}catch(e){}}();
18
18
  import chalk from "chalk";
19
19
  import { browserLogin, clearCachedTokens, loadCachedTokens, isExpiring, CognitoAuthError, } from "@indigoai-us/hq-cloud";
20
- import { DEFAULT_COGNITO, refreshCachedSession, } from "../utils/cognito-session.js";
20
+ import { refreshCachedSession, } from "../utils/cognito-session.js";
21
+ import { cognitoConfigForLoginProvider } from "../utils/login-provider.js";
21
22
  /**
22
23
  * Decode the (unverified) ID token payload for display purposes only.
23
24
  * The token was just returned by Cognito's token endpoint, so its contents
@@ -44,7 +45,8 @@ export function registerAuthCommands(program) {
44
45
  authCmd
45
46
  .command("login")
46
47
  .description("Sign in to HQ — opens the Cognito Hosted UI and caches tokens locally")
47
- .action(async () => {
48
+ .option("--provider <provider>", "OAuth provider to use: google, microsoft, or picker")
49
+ .action(async (options) => {
48
50
  const existing = loadCachedTokens();
49
51
  if (existing && !isExpiring(existing, 120)) {
50
52
  const who = peekIdToken(existing.idToken).email ?? "cached session";
@@ -52,7 +54,7 @@ export function registerAuthCommands(program) {
52
54
  return;
53
55
  }
54
56
  try {
55
- const tokens = await browserLogin(DEFAULT_COGNITO);
57
+ const tokens = await browserLogin(cognitoConfigForLoginProvider(options.provider));
56
58
  const who = peekIdToken(tokens.idToken).email ?? "HQ";
57
59
  console.log(chalk.green(`Signed in as ${who}`));
58
60
  console.log(chalk.dim(` Token cached at ~/.hq/cognito-tokens.json (expires ${tokens.expiresAt})`));
@@ -110,4 +112,4 @@ export function registerAuthCommands(program) {
110
112
  });
111
113
  }
112
114
  //# sourceMappingURL=auth.js.map
113
- //# debugId=a4a30b14-55b3-5206-9148-48aff1a8cdd4
115
+ //# debugId=e8f8cd77-2f24-5e71-8cb6-518095482b22
@@ -222,11 +222,20 @@ export interface ManifestCompanyEntry {
222
222
  * under the target slug. Read → mutate → temp-write → rename so concurrent
223
223
  * readers never see a partially-written file.
224
224
  *
225
- * Idempotent: if the values already match, this is a no-op (still rewrites
226
- * the file to canonical YAML, but the mutation is identical).
225
+ * Skip-if-unchanged: if the target slug already carries the exact
226
+ * `cloud_uid` + `bucket_name`, this is a true no-op — the file is left
227
+ * byte-for-byte intact (comments, ordering, and formatting preserved) and
228
+ * we return `false`. This matters because `yaml.dump` does NOT round-trip
229
+ * comments or the original layout: an unconditional rewrite re-canonicalises
230
+ * the manifest (stripping the `/newcompany` header comment, reflowing keys)
231
+ * on EVERY provision/Connect, even when nothing semantically changed. Each
232
+ * such rewrite is then pushed by the initial-sync step, so the
233
+ * comment-stripped local form perpetually diverges from any peer/cloud copy
234
+ * that still holds the commented form — manifesting as a recurring HQ Sync
235
+ * conflict loop on `companies/manifest.yaml` that re-fires every sync. Only
236
+ * writing when a value actually changes lets the two forms converge.
227
237
  *
228
- * Returns true if the file was written (always true in current impl
229
- * reserved for future "skip if unchanged" optimization).
238
+ * Returns true if the file was written, false if it was already current.
230
239
  */
231
240
  export declare function patchManifest(hqRoot: string, slug: string, cloudUid: string, bucketName: string): boolean;
232
241
  /**
@@ -28,7 +28,7 @@
28
28
  * `initial_sync.ok=false`). Manifest + config may have been written.
29
29
  */
30
30
 
31
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="925baddc-7b12-5ad4-b64f-d7e20bd0daab")}catch(e){}}();
31
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="3ae2829b-1343-59f7-92b0-63d93f37086f")}catch(e){}}();
32
32
  import chalk from "chalk";
33
33
  import * as fs from "node:fs";
34
34
  import * as path from "node:path";
@@ -209,11 +209,20 @@ export function ensureManifestEntryForProvision(hqRoot, slug) {
209
209
  * under the target slug. Read → mutate → temp-write → rename so concurrent
210
210
  * readers never see a partially-written file.
211
211
  *
212
- * Idempotent: if the values already match, this is a no-op (still rewrites
213
- * the file to canonical YAML, but the mutation is identical).
212
+ * Skip-if-unchanged: if the target slug already carries the exact
213
+ * `cloud_uid` + `bucket_name`, this is a true no-op — the file is left
214
+ * byte-for-byte intact (comments, ordering, and formatting preserved) and
215
+ * we return `false`. This matters because `yaml.dump` does NOT round-trip
216
+ * comments or the original layout: an unconditional rewrite re-canonicalises
217
+ * the manifest (stripping the `/newcompany` header comment, reflowing keys)
218
+ * on EVERY provision/Connect, even when nothing semantically changed. Each
219
+ * such rewrite is then pushed by the initial-sync step, so the
220
+ * comment-stripped local form perpetually diverges from any peer/cloud copy
221
+ * that still holds the commented form — manifesting as a recurring HQ Sync
222
+ * conflict loop on `companies/manifest.yaml` that re-fires every sync. Only
223
+ * writing when a value actually changes lets the two forms converge.
214
224
  *
215
- * Returns true if the file was written (always true in current impl
216
- * reserved for future "skip if unchanged" optimization).
225
+ * Returns true if the file was written, false if it was already current.
217
226
  */
218
227
  export function patchManifest(hqRoot, slug, cloudUid, bucketName) {
219
228
  const mPath = manifestPath(hqRoot);
@@ -224,6 +233,12 @@ export function patchManifest(hqRoot, slug, cloudUid, bucketName) {
224
233
  const existing = parsed.companies[slug];
225
234
  // Preserve null / object / unknown — promote null → {} so we can write keys.
226
235
  const entry = existing && typeof existing === "object" ? { ...existing } : {};
236
+ // No-op guard: both fields already match → leave the on-disk file (and its
237
+ // comments) untouched so a re-provision can't churn the manifest and seed a
238
+ // sync conflict loop.
239
+ if (entry.cloud_uid === cloudUid && entry.bucket_name === bucketName) {
240
+ return false;
241
+ }
227
242
  entry.cloud_uid = cloudUid;
228
243
  entry.bucket_name = bucketName;
229
244
  parsed.companies[slug] = entry;
@@ -495,9 +510,13 @@ export async function provisionCompany(options) {
495
510
  const cloudUid = entity.uid;
496
511
  const bucketName = entity.bucketName;
497
512
  const kmsKeyId = entity.kmsKeyId ?? null;
498
- // Step 6: patch manifest atomically
499
- patchManifest(options.hqRoot, options.slug, cloudUid, bucketName);
500
- log(`patched companies/manifest.yaml`);
513
+ // Step 6: patch manifest atomically. Skip-if-unchanged returns false when
514
+ // the manifest already carries this slug's cloud_uid + bucket_name, so we
515
+ // report the honest outcome rather than always claiming a patch.
516
+ const manifestPatched = patchManifest(options.hqRoot, options.slug, cloudUid, bucketName);
517
+ log(manifestPatched
518
+ ? `patched companies/manifest.yaml`
519
+ : `companies/manifest.yaml already current — left untouched`);
501
520
  // Step 7: write .hq/config.json atomically
502
521
  writeCompanyConfig(options.hqRoot, options.slug, {
503
522
  companyUid: cloudUid,
@@ -556,7 +575,7 @@ export async function provisionCompany(options) {
556
575
  vault_api_url: options.vaultApiUrl,
557
576
  kms_key_id: kmsKeyId,
558
577
  created_entity: createdEntity,
559
- manifest_patched: true,
578
+ manifest_patched: manifestPatched,
560
579
  config_written: true,
561
580
  initial_sync: initialSync,
562
581
  };
@@ -615,4 +634,4 @@ export function registerCloudProvisionCommands(program) {
615
634
  });
616
635
  }
617
636
  //# sourceMappingURL=cloud-provision.js.map
618
- //# debugId=925baddc-7b12-5ad4-b64f-d7e20bd0daab
637
+ //# debugId=3ae2829b-1343-59f7-92b0-63d93f37086f
@@ -104,6 +104,7 @@ export interface ShareCallOptions {
104
104
  message?: string;
105
105
  skipUnchanged?: boolean;
106
106
  propagateDeletes?: boolean;
107
+ propagateDeletePolicy?: "owned-only" | "currency-gated" | "all";
107
108
  }
108
109
  export interface ShareCallResult {
109
110
  filesUploaded: number;
@@ -13,13 +13,40 @@
13
13
  * hq sync status — show local journal summary
14
14
  */
15
15
 
16
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="bdaf154f-b17d-5482-9e4d-feaebe606a1b")}catch(e){}}();
16
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="94a293c9-eb80-5100-a3e0-d64c93f7165c")}catch(e){}}();
17
17
  import chalk from "chalk";
18
18
  import * as fs from "fs";
19
19
  import * as path from "path";
20
20
  import { share, sync, readJournal, getJournalPath, loadCachedTokens, VaultClient, computePersonalVaultPaths, } from "@indigoai-us/hq-cloud";
21
21
  import { DEFAULT_HQ_ROOT, ensureCognitoToken, buildVaultConfig, } from "../utils/cognito-session.js";
22
22
  import { emitNarrowHint, isStrictRefusal, resolveBannerLevel, } from "../lib/narrow-hint-banner.js";
23
+ /**
24
+ * Resolve the `propagateDeletePolicy` for share() calls.
25
+ *
26
+ * Mirrors `@indigoai-us/hq-cloud`'s `bin/sync-runner.js` `resolveDeletePolicy`
27
+ * — the same function the AppBar HQ Sync menubar uses — so the CLI and the
28
+ * menubar agree on the policy by default. Inlined here (rather than
29
+ * deep-imported from `dist/bin/sync-runner.js`) to avoid coupling to a
30
+ * non-public subpath.
31
+ *
32
+ * Default is `"currency-gated"` (etag-verified safe delete). Override via
33
+ * the `HQ_SYNC_DELETE_POLICY` env var: `owned-only` (legacy direction-of-
34
+ * origin filter, multi-user safe but stranded down-stream litter on
35
+ * personal vaults until hq-cloud 6.0.1's prs_ override), `currency-gated`
36
+ * (the menubar default), or `all` (emergency-reconcile, no safety gates).
37
+ *
38
+ * Prior to this helper, the CLI passed nothing → share() fell through to
39
+ * its own `"owned-only"` default, leaving CLI users on a stricter (and
40
+ * sometimes wrong, on personal vault) policy than menubar users for
41
+ * months. See `workspace/reports/owned-only-delete-policy-purpose-debug.md`.
42
+ */
43
+ function resolveDeletePolicy() {
44
+ const env = process.env.HQ_SYNC_DELETE_POLICY;
45
+ if (env === "owned-only" || env === "all" || env === "currency-gated") {
46
+ return env;
47
+ }
48
+ return "currency-gated";
49
+ }
23
50
  // Oldest-first by createdAt, ties broken by uid lexicographic — matches
24
51
  // `pickCanonicalPersonEntity` in @indigoai-us/hq-cloud so the CLI lands on
25
52
  // the same person bucket that `hq-sync-runner` picks.
@@ -171,6 +198,7 @@ export async function pushAll(options, deps) {
171
198
  paths: [path.join(options.hqRoot, "companies", slug)],
172
199
  skipUnchanged: true,
173
200
  propagateDeletes: true,
201
+ propagateDeletePolicy: resolveDeletePolicy(),
174
202
  ...(options.onConflict ? { onConflict: options.onConflict } : {}),
175
203
  ...(options.message ? { message: options.message } : {}),
176
204
  },
@@ -188,6 +216,7 @@ export async function pushAll(options, deps) {
188
216
  journalSlug: "personal",
189
217
  skipUnchanged: true,
190
218
  propagateDeletes: true,
219
+ propagateDeletePolicy: resolveDeletePolicy(),
191
220
  ...(options.onConflict ? { onConflict: options.onConflict } : {}),
192
221
  ...(options.message ? { message: options.message } : {}),
193
222
  },
@@ -869,6 +898,9 @@ async function runPushAll(hqRoot, message, onConflict, skipPersonal) {
869
898
  ...(opts.propagateDeletes !== undefined
870
899
  ? { propagateDeletes: opts.propagateDeletes }
871
900
  : {}),
901
+ ...(opts.propagateDeletePolicy !== undefined
902
+ ? { propagateDeletePolicy: opts.propagateDeletePolicy }
903
+ : {}),
872
904
  ...(author ? { author } : {}),
873
905
  }),
874
906
  });
@@ -949,6 +981,7 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
949
981
  hqRoot,
950
982
  skipUnchanged: true,
951
983
  propagateDeletes: true,
984
+ propagateDeletePolicy: resolveDeletePolicy(),
952
985
  ...(onConflict ? { onConflict } : {}),
953
986
  ...(message ? { message } : {}),
954
987
  ...(personalMode ? { personalMode: true } : {}),
@@ -1130,4 +1163,4 @@ function resolveUploadAuthorFromCache() {
1130
1163
  }
1131
1164
  }
1132
1165
  //# sourceMappingURL=cloud.js.map
1133
- //# debugId=bdaf154f-b17d-5482-9e4d-feaebe606a1b
1166
+ //# debugId=94a293c9-eb80-5100-a3e0-d64c93f7165c
@@ -0,0 +1,34 @@
1
+ /**
2
+ * `hq creators apply` — apply for verified-creator access.
3
+ *
4
+ * Publishing to the marketplace is gated on verified-creator status. An
5
+ * unverified caller submits an application (a short pitch + optional desired
6
+ * handle); it is persisted server-side and an Indigo admin reviews it. On
7
+ * approval the caller becomes a verified creator and can `hq publish`.
8
+ *
9
+ * Wire contract (POST /v1/creators/request-access, authed):
10
+ * body { reason: string, handle?: string }
11
+ * 202 { status, code, applicationId, requestAccessPath }
12
+ * 409 { code: "APPLICATION_PENDING", error, applicationId } (already pending)
13
+ */
14
+ import { Command } from "commander";
15
+ export interface CreatorApplyOptions {
16
+ reason: string;
17
+ handle?: string;
18
+ }
19
+ export interface CreatorApplyDeps {
20
+ /** Resolve a non-expired access token (prompts login when interactive). */
21
+ getAccessToken: () => Promise<string>;
22
+ /** POST the application body to the vault API. */
23
+ post: (token: string, body: Record<string, unknown>) => Promise<Response>;
24
+ }
25
+ export interface CreatorApplyResult {
26
+ message: string;
27
+ }
28
+ /**
29
+ * Testable core. Submits the application and maps the server response to a
30
+ * single human-readable message. Throws on hard failures.
31
+ */
32
+ export declare function runCreatorApply(opts: CreatorApplyOptions, deps: CreatorApplyDeps): Promise<CreatorApplyResult>;
33
+ export declare function registerCreatorsCommand(program: Command): void;
34
+ //# sourceMappingURL=creators.d.ts.map
@@ -0,0 +1,68 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="9cd4617e-f5b0-54ad-86a0-042157165a91")}catch(e){}}();
3
+ import chalk from "chalk";
4
+ import { ensureCognitoToken } from "../utils/cognito-session.js";
5
+ import { vaultApiFetch } from "../utils/vault-api.js";
6
+ /**
7
+ * Testable core. Submits the application and maps the server response to a
8
+ * single human-readable message. Throws on hard failures.
9
+ */
10
+ export async function runCreatorApply(opts, deps) {
11
+ const reason = opts.reason?.trim();
12
+ if (!reason) {
13
+ throw new Error('A reason is required — pass --reason "why you want to publish to the marketplace".');
14
+ }
15
+ const token = await deps.getAccessToken();
16
+ const body = { reason };
17
+ const handle = opts.handle?.trim();
18
+ if (handle)
19
+ body.handle = handle;
20
+ const res = await deps.post(token, body);
21
+ const parsed = (await res
22
+ .json()
23
+ .catch(() => ({})));
24
+ if (res.status === 409 || parsed.code === "APPLICATION_PENDING") {
25
+ return {
26
+ message: "You already have a pending creator application — an Indigo admin will review it.",
27
+ };
28
+ }
29
+ if (res.status < 200 || res.status >= 300) {
30
+ const msg = parsed.error ?? parsed.message ?? `HTTP ${res.status}`;
31
+ throw new Error(`Creator application failed: ${msg}`);
32
+ }
33
+ const id = typeof parsed.applicationId === "string" ? ` (${parsed.applicationId})` : "";
34
+ return {
35
+ message: `Creator application submitted${id} — an Indigo admin will review it. ` +
36
+ "You'll be able to `hq publish` once approved.",
37
+ };
38
+ }
39
+ export function registerCreatorsCommand(program) {
40
+ const creators = program
41
+ .command("creators")
42
+ .description("Creator marketplace account");
43
+ creators
44
+ .command("apply")
45
+ .description("Apply for verified-creator access (required to publish packs)")
46
+ .requiredOption("--reason <reason>", "Why you want to publish to the marketplace")
47
+ .option("--handle <handle>", "Desired creator handle (optional)")
48
+ .action(async (options) => {
49
+ try {
50
+ const result = await runCreatorApply({ reason: options.reason, handle: options.handle }, {
51
+ getAccessToken: () => ensureCognitoToken({ interactive: true }),
52
+ post: (token, body) => vaultApiFetch({
53
+ token,
54
+ path: "/v1/creators/request-access",
55
+ method: "POST",
56
+ body,
57
+ }),
58
+ });
59
+ console.log(chalk.green(result.message));
60
+ }
61
+ catch (err) {
62
+ console.error(chalk.red(err.message));
63
+ process.exitCode = 1;
64
+ }
65
+ });
66
+ }
67
+ //# sourceMappingURL=creators.js.map
68
+ //# debugId=9cd4617e-f5b0-54ad-86a0-042157165a91
@@ -1,5 +1,5 @@
1
1
  /**
2
- * hq login — opens browser for Cognito auth via Google OAuth
2
+ * hq login — opens browser for Cognito auth.
3
3
  */
4
4
  import { Command } from 'commander';
5
5
  export declare function registerLoginCommand(program: Command): void;
@@ -1,11 +1,11 @@
1
1
  /**
2
- * hq login — opens browser for Cognito auth via Google OAuth
2
+ * hq login — opens browser for Cognito auth.
3
3
  */
4
4
 
5
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="31735154-208b-5544-85a4-22f8f0fbe415")}catch(e){}}();
5
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="cb1c9acd-d4b0-52a2-b5ce-f567be9ff048")}catch(e){}}();
6
6
  import chalk from 'chalk';
7
7
  import { browserLogin, loadCachedTokens, isExpiring } from '@indigoai-us/hq-cloud';
8
- import { DEFAULT_COGNITO } from '../utils/cognito-session.js';
8
+ import { cognitoConfigForLoginProvider } from '../utils/login-provider.js';
9
9
  function peekIdToken(idToken) {
10
10
  try {
11
11
  const payload = idToken.split('.')[1];
@@ -23,8 +23,9 @@ function peekIdToken(idToken) {
23
23
  export function registerLoginCommand(program) {
24
24
  program
25
25
  .command('login')
26
- .description('Authenticate with HQ via Cognito (Google OAuth)')
27
- .action(async () => {
26
+ .description('Authenticate with HQ via Cognito')
27
+ .option('--provider <provider>', 'OAuth provider to use: google, microsoft, or picker')
28
+ .action(async (options) => {
28
29
  try {
29
30
  const existing = loadCachedTokens();
30
31
  if (existing && !isExpiring(existing, 120)) {
@@ -33,7 +34,7 @@ export function registerLoginCommand(program) {
33
34
  return;
34
35
  }
35
36
  console.log('Opening browser for authentication...');
36
- const tokens = await browserLogin(DEFAULT_COGNITO);
37
+ const tokens = await browserLogin(cognitoConfigForLoginProvider(options.provider));
37
38
  const who = peekIdToken(tokens.idToken).email ?? 'HQ';
38
39
  console.log(chalk.green(`Logged in as ${who}`));
39
40
  }
@@ -44,4 +45,4 @@ export function registerLoginCommand(program) {
44
45
  });
45
46
  }
46
47
  //# sourceMappingURL=login.js.map
47
- //# debugId=31735154-208b-5544-85a4-22f8f0fbe415
48
+ //# debugId=cb1c9acd-d4b0-52a2-b5ce-f567be9ff048
@@ -34,7 +34,7 @@
34
34
  * from each pack's package.yaml; rationale lives in the layout-fix PR.)
35
35
  */
36
36
 
37
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="dafe45c8-d9bb-5082-b5b7-aaf1a2ee5c7e")}catch(e){}}();
37
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="d5029b6f-fc42-52ea-9ddd-aaaae682a8f4")}catch(e){}}();
38
38
  import * as fs from 'fs';
39
39
  import * as os from 'os';
40
40
  import * as path from 'path';
@@ -263,20 +263,45 @@ export function defaultMarketplaceDeps() {
263
263
  return {
264
264
  resolveListing: async (slug, version) => {
265
265
  // Search by slug (public). The API returns approved listings only.
266
+ //
267
+ // BUGFIX: the browse handler historically IGNORED `?slug=` and only
268
+ // honored `?q=` (fuzzy), so `?slug=tdd` returned ALL approved listings
269
+ // newest-first — and blindly taking `listings[0]` then installed the
270
+ // newest pack, not the requested slug (e.g. `marketplace:tdd` installed
271
+ // hq-pack-review). We now (a) query with BOTH `slug` (exact, server-side
272
+ // filter being added in parallel) AND `q` (fuzzy fallback) so the right
273
+ // results come back whichever param the server honors, and (b) NEVER take
274
+ // `listings[0]` — we exact-match `l.slug === slug` among the results and
275
+ // throw a clear error if none matches, rather than installing the wrong
276
+ // pack.
277
+ const query = { slug, q: slug };
278
+ if (version)
279
+ query.version = version;
266
280
  const res = await vaultApiFetchPublic({
267
281
  path: '/v1/listings',
268
- query: version ? { slug, version } : { slug },
282
+ query,
269
283
  });
270
284
  if (!res.ok) {
271
285
  throw new Error(`Failed to search marketplace for "${slug}" (HTTP ${res.status}).`);
272
286
  }
273
287
  const body = (await res.json().catch(() => ({})));
274
288
  const listings = body.listings ?? [];
275
- const match = version
276
- ? listings.find((l) => (l.version ?? l.latestVersion) === version)
277
- : listings[0];
289
+ // Only consider listings whose slug EXACTLY matches the request — the
290
+ // fuzzy `q` search (and a slug-ignoring browse handler) can return
291
+ // unrelated packs, so an exact-slug filter is the safety floor.
292
+ const exact = listings.filter((l) => l.slug === slug);
293
+ let match;
294
+ if (version) {
295
+ // Pinned `marketplace:<slug>@<version>` → require an exact version too.
296
+ match = exact.find((l) => (l.version ?? l.latestVersion) === version);
297
+ }
298
+ else {
299
+ // No pin → newest exact-slug match. The feed is newest-first, so the
300
+ // first exact-slug entry is the newest version of that slug.
301
+ match = exact[0];
302
+ }
278
303
  if (!match) {
279
- throw new Error(`No approved marketplace listing found for "${slug}"${version ? `@${version}` : ''}.`);
304
+ throw new Error(`No marketplace listing found for slug "${slug}"${version ? `@${version}` : ''}.`);
280
305
  }
281
306
  const id = match.listingId ?? match.id;
282
307
  if (!id)
@@ -726,7 +751,12 @@ export function validateManifest(payloadDir, hqVersion) {
726
751
  if (!range || !semverValidRange(range)) {
727
752
  throw new Error(`requires.hqCore must be a valid semver range (got "${range}")`);
728
753
  }
729
- if (hqVersion && !semverSatisfies(hqVersion, range)) {
754
+ // `includePrerelease` so a prerelease host version (e.g. `15.0.9-beta.1`)
755
+ // satisfies a plain range like `>=14.2.0`. By default node-semver excludes
756
+ // prereleases from range matching, which would wrongly reject every dogfooding
757
+ // beta/rc HQ build from installing packs. A genuinely-too-old host (e.g.
758
+ // `13.0.0`) still fails the range.
759
+ if (hqVersion && !semverSatisfies(hqVersion, range, { includePrerelease: true })) {
730
760
  throw new Error(`Host hqCore ${hqVersion} does not satisfy pack requirement ${range}`);
731
761
  }
732
762
  // 7. contributes has at least one non-empty subfield
@@ -1051,4 +1081,4 @@ export async function installPack(source, opts = {}) {
1051
1081
  }
1052
1082
  }
1053
1083
  //# sourceMappingURL=pack-install.js.map
1054
- //# debugId=dafe45c8-d9bb-5082-b5b7-aaf1a2ee5c7e
1084
+ //# debugId=d5029b6f-fc42-52ea-9ddd-aaaae682a8f4
@@ -28,7 +28,7 @@
28
28
  * - manifest.ts (modules.yaml read/write for provenance)
29
29
  */
30
30
 
31
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="73704bc7-d7b1-5c4c-a34e-54edadcc2753")}catch(e){}}();
31
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="f5046847-a2b9-50e4-9a68-6a81a9e0de2b")}catch(e){}}();
32
32
  import * as fs from 'fs';
33
33
  import * as os from 'os';
34
34
  import * as path from 'path';
@@ -237,6 +237,12 @@ export function buildListingNotice(status, body, packName, packVersion) {
237
237
  `Bump the version in package.yaml and re-run \`hq publish\`.`);
238
238
  }
239
239
  if (status === 401 || status === 403) {
240
+ const code = typeof body.code === 'string' ? body.code : '';
241
+ if (code === 'NOT_VERIFIED_CREATOR') {
242
+ throw new Error('Only verified creators can publish to the marketplace. ' +
243
+ 'Run `hq creators apply --reason "..."` to request verified-creator access; ' +
244
+ 'an Indigo admin will review it.');
245
+ }
240
246
  throw new Error('Not authorized to publish — run `hq login` and ensure your creator account is verified.');
241
247
  }
242
248
  if (status < 200 || status >= 300) {
@@ -372,4 +378,4 @@ export function registerPublishCommand(program) {
372
378
  });
373
379
  }
374
380
  //# sourceMappingURL=publish.js.map
375
- //# debugId=73704bc7-d7b1-5c4c-a34e-54edadcc2753
381
+ //# debugId=f5046847-a2b9-50e4-9a68-6a81a9e0de2b
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * HQ CLI - Module management, package management, and cloud sync for HQ
4
4
  */
5
5
 
6
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="202089d2-b89b-5779-aa66-f88531a9b893")}catch(e){}}();
6
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="6fa5c878-2930-5b94-bc7c-5deea8795218")}catch(e){}}();
7
7
  import { Command } from "commander";
8
8
  import { initSentry, Sentry } from "./sentry.js";
9
9
  import { registerAddCommand } from "./commands/add.js";
@@ -25,6 +25,7 @@ import { registerPackageUpdateCommand } from "./commands/pkg-update.js";
25
25
  import { registerPackageListCommand } from "./commands/pkg-list.js";
26
26
  import { registerPacksCommand } from "./commands/packs.js";
27
27
  import { registerPublishCommand } from "./commands/publish.js";
28
+ import { registerCreatorsCommand } from "./commands/creators.js";
28
29
  import { registerTeamSyncCommand } from "./commands/team-sync.js";
29
30
  import { registerAuthCommands } from "./commands/auth.js";
30
31
  import { registerSecretsCommand } from "./commands/secrets.js";
@@ -91,6 +92,8 @@ registerPackageRemoveCommand(program);
91
92
  // "hq publish <skill-or-worker-path>" packages and submits a pack to the
92
93
  // marketplace via POST /v1/listings.
93
94
  registerPublishCommand(program);
95
+ // `hq creators apply` — request verified-creator access (required to publish).
96
+ registerCreatorsCommand(program);
94
97
  // Cloud sync subcommand group
95
98
  const syncCmd = program
96
99
  .command("sync")
@@ -174,4 +177,4 @@ registerRescueCommand(program);
174
177
  }
175
178
  })();
176
179
  //# sourceMappingURL=index.js.map
177
- //# debugId=202089d2-b89b-5779-aa66-f88531a9b893
180
+ //# debugId=6fa5c878-2930-5b94-bc7c-5deea8795218
@@ -0,0 +1,11 @@
1
+ import type { CognitoAuthConfig } from "@indigoai-us/hq-cloud";
2
+ declare const PROVIDER_TO_COGNITO_IDP: {
3
+ readonly google: "Google";
4
+ readonly microsoft: "MicrosoftPersonal";
5
+ readonly picker: undefined;
6
+ };
7
+ export type LoginProvider = keyof typeof PROVIDER_TO_COGNITO_IDP;
8
+ export declare function parseLoginProvider(value: string | undefined): LoginProvider | undefined;
9
+ export declare function cognitoConfigForLoginProvider(value: string | undefined): CognitoAuthConfig;
10
+ export {};
11
+ //# sourceMappingURL=login-provider.d.ts.map
@@ -0,0 +1,28 @@
1
+
2
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="10f78162-e7f8-5295-a572-936ae454ad62")}catch(e){}}();
3
+ import { DEFAULT_COGNITO } from "./cognito-session.js";
4
+ const PROVIDER_TO_COGNITO_IDP = {
5
+ google: "Google",
6
+ microsoft: "MicrosoftPersonal",
7
+ picker: undefined,
8
+ };
9
+ export function parseLoginProvider(value) {
10
+ if (value === undefined)
11
+ return undefined;
12
+ const normalized = value.trim().toLowerCase();
13
+ if (normalized in PROVIDER_TO_COGNITO_IDP) {
14
+ return normalized;
15
+ }
16
+ throw new Error("Provider must be one of: google, microsoft, picker");
17
+ }
18
+ export function cognitoConfigForLoginProvider(value) {
19
+ const provider = parseLoginProvider(value);
20
+ if (!provider)
21
+ return DEFAULT_COGNITO;
22
+ return {
23
+ ...DEFAULT_COGNITO,
24
+ identityProvider: PROVIDER_TO_COGNITO_IDP[provider],
25
+ };
26
+ }
27
+ //# sourceMappingURL=login-provider.js.map
28
+ //# debugId=10f78162-e7f8-5295-a572-936ae454ad62
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.35.0",
4
- "description": "HQ by Indigo management CLI modules and cloud sync",
3
+ "version": "5.35.2",
4
+ "description": "HQ by Indigo management CLI \u2014 modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "hq": "dist/index.js",
@@ -15,7 +15,7 @@
15
15
  "clean": "rm -rf dist"
16
16
  },
17
17
  "dependencies": {
18
- "@indigoai-us/hq-cloud": "^6.0.0",
18
+ "@indigoai-us/hq-cloud": "^6.0.1",
19
19
  "@indigoai-us/hq-onboarding": "^0.1.0",
20
20
  "@sentry/node": "^10.49.0",
21
21
  "chalk": "^5.3.0",
@@ -27,6 +27,7 @@ import {
27
27
  DEFAULT_COGNITO,
28
28
  refreshCachedSession,
29
29
  } from "../utils/cognito-session.js";
30
+ import { cognitoConfigForLoginProvider } from "../utils/login-provider.js";
30
31
 
31
32
  /**
32
33
  * Decode the (unverified) ID token payload for display purposes only.
@@ -62,7 +63,11 @@ export function registerAuthCommands(program: Command): void {
62
63
  .description(
63
64
  "Sign in to HQ — opens the Cognito Hosted UI and caches tokens locally",
64
65
  )
65
- .action(async () => {
66
+ .option(
67
+ "--provider <provider>",
68
+ "OAuth provider to use: google, microsoft, or picker",
69
+ )
70
+ .action(async (options: { provider?: string }) => {
66
71
  const existing = loadCachedTokens();
67
72
  if (existing && !isExpiring(existing, 120)) {
68
73
  const who = peekIdToken(existing.idToken).email ?? "cached session";
@@ -72,7 +77,7 @@ export function registerAuthCommands(program: Command): void {
72
77
  return;
73
78
  }
74
79
  try {
75
- const tokens = await browserLogin(DEFAULT_COGNITO);
80
+ const tokens = await browserLogin(cognitoConfigForLoginProvider(options.provider));
76
81
  const who = peekIdToken(tokens.idToken).email ?? "HQ";
77
82
  console.log(chalk.green(`Signed in as ${who}`));
78
83
  console.log(