@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
|
@@ -402,9 +402,22 @@ export function defaultMarketplaceDeps(): MarketplaceDeps {
|
|
|
402
402
|
return {
|
|
403
403
|
resolveListing: async (slug, version) => {
|
|
404
404
|
// Search by slug (public). The API returns approved listings only.
|
|
405
|
+
//
|
|
406
|
+
// BUGFIX: the browse handler historically IGNORED `?slug=` and only
|
|
407
|
+
// honored `?q=` (fuzzy), so `?slug=tdd` returned ALL approved listings
|
|
408
|
+
// newest-first — and blindly taking `listings[0]` then installed the
|
|
409
|
+
// newest pack, not the requested slug (e.g. `marketplace:tdd` installed
|
|
410
|
+
// hq-pack-review). We now (a) query with BOTH `slug` (exact, server-side
|
|
411
|
+
// filter being added in parallel) AND `q` (fuzzy fallback) so the right
|
|
412
|
+
// results come back whichever param the server honors, and (b) NEVER take
|
|
413
|
+
// `listings[0]` — we exact-match `l.slug === slug` among the results and
|
|
414
|
+
// throw a clear error if none matches, rather than installing the wrong
|
|
415
|
+
// pack.
|
|
416
|
+
const query: Record<string, string> = { slug, q: slug };
|
|
417
|
+
if (version) query.version = version;
|
|
405
418
|
const res = await vaultApiFetchPublic({
|
|
406
419
|
path: '/v1/listings',
|
|
407
|
-
query
|
|
420
|
+
query,
|
|
408
421
|
});
|
|
409
422
|
if (!res.ok) {
|
|
410
423
|
throw new Error(
|
|
@@ -415,12 +428,22 @@ export function defaultMarketplaceDeps(): MarketplaceDeps {
|
|
|
415
428
|
listings?: RawListingSummary[];
|
|
416
429
|
};
|
|
417
430
|
const listings = body.listings ?? [];
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
431
|
+
// Only consider listings whose slug EXACTLY matches the request — the
|
|
432
|
+
// fuzzy `q` search (and a slug-ignoring browse handler) can return
|
|
433
|
+
// unrelated packs, so an exact-slug filter is the safety floor.
|
|
434
|
+
const exact = listings.filter((l) => l.slug === slug);
|
|
435
|
+
let match: RawListingSummary | undefined;
|
|
436
|
+
if (version) {
|
|
437
|
+
// Pinned `marketplace:<slug>@<version>` → require an exact version too.
|
|
438
|
+
match = exact.find((l) => (l.version ?? l.latestVersion) === version);
|
|
439
|
+
} else {
|
|
440
|
+
// No pin → newest exact-slug match. The feed is newest-first, so the
|
|
441
|
+
// first exact-slug entry is the newest version of that slug.
|
|
442
|
+
match = exact[0];
|
|
443
|
+
}
|
|
421
444
|
if (!match) {
|
|
422
445
|
throw new Error(
|
|
423
|
-
`No
|
|
446
|
+
`No marketplace listing found for slug "${slug}"${version ? `@${version}` : ''}.`,
|
|
424
447
|
);
|
|
425
448
|
}
|
|
426
449
|
const id = match.listingId ?? match.id;
|
|
@@ -998,7 +1021,12 @@ export function validateManifest(
|
|
|
998
1021
|
if (!range || !semverValidRange(range)) {
|
|
999
1022
|
throw new Error(`requires.hqCore must be a valid semver range (got "${range}")`);
|
|
1000
1023
|
}
|
|
1001
|
-
|
|
1024
|
+
// `includePrerelease` so a prerelease host version (e.g. `15.0.9-beta.1`)
|
|
1025
|
+
// satisfies a plain range like `>=14.2.0`. By default node-semver excludes
|
|
1026
|
+
// prereleases from range matching, which would wrongly reject every dogfooding
|
|
1027
|
+
// beta/rc HQ build from installing packs. A genuinely-too-old host (e.g.
|
|
1028
|
+
// `13.0.0`) still fails the range.
|
|
1029
|
+
if (hqVersion && !semverSatisfies(hqVersion, range, { includePrerelease: true })) {
|
|
1002
1030
|
throw new Error(
|
|
1003
1031
|
`Host hqCore ${hqVersion} does not satisfy pack requirement ${range}`
|
|
1004
1032
|
);
|
|
@@ -166,6 +166,11 @@ describe('buildListingNotice', () => {
|
|
|
166
166
|
it('maps 401/403 to an auth error', () => {
|
|
167
167
|
expect(() => buildListingNotice(401, {}, 'hq-pack-demo', '1.0.0')).toThrow(/Not authorized|login/i);
|
|
168
168
|
});
|
|
169
|
+
it('maps a 403 NOT_VERIFIED_CREATOR to the apply guidance', () => {
|
|
170
|
+
expect(() =>
|
|
171
|
+
buildListingNotice(403, { code: 'NOT_VERIFIED_CREATOR' }, 'hq-pack-demo', '1.0.0'),
|
|
172
|
+
).toThrow(/hq creators apply/i);
|
|
173
|
+
});
|
|
169
174
|
it('surfaces a generic server error body', () => {
|
|
170
175
|
expect(() => buildListingNotice(500, { error: 'boom' }, 'hq-pack-demo', '1.0.0')).toThrow(/boom/);
|
|
171
176
|
});
|
package/src/commands/publish.ts
CHANGED
|
@@ -315,6 +315,14 @@ export function buildListingNotice(
|
|
|
315
315
|
);
|
|
316
316
|
}
|
|
317
317
|
if (status === 401 || status === 403) {
|
|
318
|
+
const code = typeof body.code === 'string' ? body.code : '';
|
|
319
|
+
if (code === 'NOT_VERIFIED_CREATOR') {
|
|
320
|
+
throw new Error(
|
|
321
|
+
'Only verified creators can publish to the marketplace. ' +
|
|
322
|
+
'Run `hq creators apply --reason "..."` to request verified-creator access; ' +
|
|
323
|
+
'an Indigo admin will review it.',
|
|
324
|
+
);
|
|
325
|
+
}
|
|
318
326
|
throw new Error('Not authorized to publish — run `hq login` and ensure your creator account is verified.');
|
|
319
327
|
}
|
|
320
328
|
if (status < 200 || status >= 300) {
|
package/src/index.ts
CHANGED
|
@@ -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";
|
|
@@ -108,6 +109,8 @@ registerPackageRemoveCommand(program);
|
|
|
108
109
|
// "hq publish <skill-or-worker-path>" packages and submits a pack to the
|
|
109
110
|
// marketplace via POST /v1/listings.
|
|
110
111
|
registerPublishCommand(program);
|
|
112
|
+
// `hq creators apply` — request verified-creator access (required to publish).
|
|
113
|
+
registerCreatorsCommand(program);
|
|
111
114
|
|
|
112
115
|
// Cloud sync subcommand group
|
|
113
116
|
const syncCmd = program
|
|
@@ -50,10 +50,10 @@ describe("resolveDefaultHqRoot", () => {
|
|
|
50
50
|
// companies/ already exists (we mkdir'd nested inside it)
|
|
51
51
|
|
|
52
52
|
process.chdir(nested);
|
|
53
|
-
expect(resolveDefaultHqRoot()).toBe(hqDir);
|
|
53
|
+
expect(resolveDefaultHqRoot()).toBe(realpathSync(hqDir));
|
|
54
54
|
|
|
55
55
|
process.chdir(hqDir);
|
|
56
|
-
expect(resolveDefaultHqRoot()).toBe(hqDir);
|
|
56
|
+
expect(resolveDefaultHqRoot()).toBe(realpathSync(hqDir));
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
it("priority 2: SKIPS a nested core/core.yaml without a sibling companies/ dir (Codex P2 on hq#146)", () => {
|
|
@@ -72,7 +72,7 @@ describe("resolveDefaultHqRoot", () => {
|
|
|
72
72
|
process.chdir(cwdInsideCore);
|
|
73
73
|
// Should walk PAST the inner core/core.yaml (no companies/ sibling) and
|
|
74
74
|
// resolve to the real hqDir.
|
|
75
|
-
expect(resolveDefaultHqRoot()).toBe(hqDir);
|
|
75
|
+
expect(resolveDefaultHqRoot()).toBe(realpathSync(hqDir));
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
it("priority 3: falls back when neither $HQ_ROOT nor an HQ-root marker pair are found", () => {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("./cognito-session.js", () => ({
|
|
4
|
+
DEFAULT_COGNITO: {
|
|
5
|
+
region: "us-east-1",
|
|
6
|
+
userPoolDomain: "vault-indigo-hq-prod",
|
|
7
|
+
clientId: "client-123",
|
|
8
|
+
port: 8765,
|
|
9
|
+
identityProvider: "Google",
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
cognitoConfigForLoginProvider,
|
|
15
|
+
parseLoginProvider,
|
|
16
|
+
} from "./login-provider.js";
|
|
17
|
+
|
|
18
|
+
describe("login provider helpers", () => {
|
|
19
|
+
it("preserves the default Cognito config when no provider is supplied", () => {
|
|
20
|
+
expect(cognitoConfigForLoginProvider(undefined).identityProvider).toBe("Google");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("maps Microsoft to the Cognito MicrosoftPersonal IdP", () => {
|
|
24
|
+
expect(cognitoConfigForLoginProvider("microsoft").identityProvider).toBe(
|
|
25
|
+
"MicrosoftPersonal",
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("maps picker to no forced IdP", () => {
|
|
30
|
+
expect(cognitoConfigForLoginProvider("picker").identityProvider).toBeUndefined();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("accepts provider values case-insensitively", () => {
|
|
34
|
+
expect(parseLoginProvider("Microsoft")).toBe("microsoft");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("rejects unknown providers", () => {
|
|
38
|
+
expect(() => parseLoginProvider("github")).toThrow(
|
|
39
|
+
"Provider must be one of: google, microsoft, picker",
|
|
40
|
+
);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CognitoAuthConfig } from "@indigoai-us/hq-cloud";
|
|
2
|
+
import { DEFAULT_COGNITO } from "./cognito-session.js";
|
|
3
|
+
|
|
4
|
+
const PROVIDER_TO_COGNITO_IDP = {
|
|
5
|
+
google: "Google",
|
|
6
|
+
microsoft: "MicrosoftPersonal",
|
|
7
|
+
picker: undefined,
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
export type LoginProvider = keyof typeof PROVIDER_TO_COGNITO_IDP;
|
|
11
|
+
|
|
12
|
+
export function parseLoginProvider(value: string | undefined): LoginProvider | undefined {
|
|
13
|
+
if (value === undefined) return undefined;
|
|
14
|
+
const normalized = value.trim().toLowerCase();
|
|
15
|
+
if (normalized in PROVIDER_TO_COGNITO_IDP) {
|
|
16
|
+
return normalized as LoginProvider;
|
|
17
|
+
}
|
|
18
|
+
throw new Error("Provider must be one of: google, microsoft, picker");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function cognitoConfigForLoginProvider(
|
|
22
|
+
value: string | undefined,
|
|
23
|
+
): CognitoAuthConfig {
|
|
24
|
+
const provider = parseLoginProvider(value);
|
|
25
|
+
if (!provider) return DEFAULT_COGNITO;
|
|
26
|
+
return {
|
|
27
|
+
...DEFAULT_COGNITO,
|
|
28
|
+
identityProvider: PROVIDER_TO_COGNITO_IDP[provider],
|
|
29
|
+
};
|
|
30
|
+
}
|