@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.
- package/dist/commands/auth.js +7 -5
- package/dist/commands/cloud-provision.d.ts +13 -4
- package/dist/commands/cloud-provision.js +29 -10
- package/dist/commands/cloud.d.ts +1 -0
- package/dist/commands/cloud.js +35 -2
- package/dist/commands/creators.d.ts +34 -0
- package/dist/commands/creators.js +68 -0
- package/dist/commands/login.d.ts +1 -1
- package/dist/commands/login.js +8 -7
- package/dist/commands/pack-install.js +38 -8
- package/dist/commands/publish.js +8 -2
- package/dist/index.js +5 -2
- package/dist/utils/login-provider.d.ts +11 -0
- package/dist/utils/login-provider.js +28 -0
- package/package.json +3 -3
- package/src/commands/auth.ts +7 -2
- package/src/commands/cloud-provision.test.ts +78 -0
- package/src/commands/cloud-provision.ts +36 -8
- package/src/commands/cloud.ts +35 -0
- package/src/commands/creators.test.ts +60 -0
- package/src/commands/creators.ts +117 -0
- package/src/commands/login.ts +9 -5
- package/src/commands/marketplace-install.test.ts +129 -0
- package/src/commands/pack-install.test.ts +49 -0
- package/src/commands/pack-install.ts +34 -6
- package/src/commands/publish.test.ts +5 -0
- package/src/commands/publish.ts +8 -0
- package/src/index.ts +3 -0
- package/src/utils/cognito-session.test.ts +3 -3
- package/src/utils/login-provider.test.ts +42 -0
- package/src/utils/login-provider.ts +30 -0
package/dist/commands/auth.js
CHANGED
|
@@ -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]="
|
|
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 {
|
|
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
|
-
.
|
|
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(
|
|
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=
|
|
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
|
-
*
|
|
226
|
-
*
|
|
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
|
|
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]="
|
|
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
|
-
*
|
|
213
|
-
*
|
|
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
|
|
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
|
-
|
|
500
|
-
|
|
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:
|
|
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=
|
|
637
|
+
//# debugId=3ae2829b-1343-59f7-92b0-63d93f37086f
|
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -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;
|
package/dist/commands/cloud.js
CHANGED
|
@@ -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]="
|
|
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=
|
|
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
|
package/dist/commands/login.d.ts
CHANGED
package/dist/commands/login.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* hq login — opens browser for Cognito auth
|
|
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]="
|
|
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 {
|
|
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
|
|
27
|
-
.
|
|
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(
|
|
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=
|
|
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]="
|
|
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
|
|
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
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
|
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
|
-
|
|
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=
|
|
1084
|
+
//# debugId=d5029b6f-fc42-52ea-9ddd-aaaae682a8f4
|
package/dist/commands/publish.js
CHANGED
|
@@ -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]="
|
|
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=
|
|
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]="
|
|
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=
|
|
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.
|
|
4
|
-
"description": "HQ by Indigo management CLI
|
|
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.
|
|
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",
|
package/src/commands/auth.ts
CHANGED
|
@@ -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
|
-
.
|
|
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(
|
|
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(
|