Skip to content

Commit 00b6fc3

Browse files
sarahxsandersclaude
andcommitted
refactor(cli): drop cli-manifest.json emit; cliEntries lives in skill-menu.json
The wizard now reads cliEntries from skill-menu.json at runtime — no published consumer reads cli-manifest.json. The standalone file was dead-on-arrival, so removing it now keeps the contract honest (one file, one consumer). generateCliManifest was producing a wrapped object solely for the file write. Renamed to generateCliEntries, returns the entries array directly. Caller embeds it under skill-menu.json's cliEntries. CLAUDE.md and CONTRIBUTING.md updated to point contributors at the new location. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 282c9a4 commit 00b6fc3

21 files changed

Lines changed: 887 additions & 67 deletions

CLAUDE.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ User-facing intro: [README.md](README.md). Contributor handbook:
1818
| Build pipeline | `scripts/lib/` (skill generator, build phases, change router) |
1919
| Build entrypoints | `scripts/build.js` (full) and `scripts/dev-server.js` (partial / watch) |
2020
| Tests | `scripts/lib/tests/` and `scripts/plugins/tests/` (vitest) |
21-
| Manifest output | `dist/skills/manifest.json`, `dist/skills/cli-manifest.json`, `dist/skills/skill-menu.json` |
21+
| Manifest output | `dist/skills/manifest.json`, `dist/skills/skill-menu.json` (CLI entries live under `cliEntries`) |
2222
| Per-skill ZIPs | `dist/skills/<id>.zip` |
2323

2424
## The `cli:` block (read [CONTRIBUTING.md](CONTRIBUTING.md) before editing)
@@ -62,10 +62,11 @@ abstraction. Restructure to a family when a second vendor lands. See
6262
1. Read [CONTRIBUTING.md § Promotion criterion for `role: command`](CONTRIBUTING.md#promotion-criterion-for-role-command).
6363
2. Run `npm test` — the parser's test suite (`scripts/lib/tests/cli-block.test.js`)
6464
covers every naming-convention case.
65-
3. Run `npm run build` — confirm the entry appears (or disappears) in
66-
`dist/skills/cli-manifest.json` with the values you expect.
67-
4. The wizard's next release picks up the change automatically. No wizard
68-
PR needed unless the change requires wizard-side hooks (custom outro,
65+
3. Run `npm run build` — confirm the entry appears (or disappears) under
66+
`cliEntries` inside `dist/skills/skill-menu.json` with the values you
67+
expect.
68+
4. The wizard resolves new entries at runtime, so no wizard release is
69+
required unless the change needs wizard-side hooks (custom outro,
6970
content blocks, abort cases).
7071
5. **Flag the wizard maintainer:** the wizard ships a committed
7172
`docs/cli.md` auto-generated from the manifest. When the wizard

CONTRIBUTING.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ manifest, see the [README](README.md).
99
Every skill ships with a `config.yaml`. An optional `cli:` block on that
1010
config tells the PostHog wizard whether and how this skill appears as a
1111
command. The block is parsed in `scripts/lib/skill-generator.js` and
12-
emitted into `dist/skills/cli-manifest.json` alongside the regular
13-
manifest. The wizard snapshots that file at build time and turns each
14-
entry into a registered command.
12+
emitted as `cliEntries` inside `dist/skills/skill-menu.json`. The wizard
13+
fetches `skill-menu.json` at runtime and registers each entry as a
14+
command, so adding a new skill-backed command is a context-mill release
15+
— no wizard release needed.
1516

1617
### The `cli:` block schema
1718

@@ -199,9 +200,10 @@ When you've decided your skill meets the `role: command` criterion:
199200
1. Add the `cli:` block to the skill's `config.yaml` with `role:
200201
command`, the right `parentCommand` (if it nests under an existing
201202
family), and `command`.
202-
2. Confirm `npm run build` emits the entry in
203-
`dist/skills/cli-manifest.json` with the right `parentCommand` /
204-
`command` values. The wizard's next release picks it up automatically.
203+
2. Confirm `npm run build` emits the entry under `cliEntries` inside
204+
`dist/skills/skill-menu.json` with the right `parentCommand` /
205+
`command` values. The wizard picks it up on its next invocation
206+
(no wizard release needed).
205207
3. No wizard PR is needed for skill-backed public commands. If you also
206208
need wizard-side hooks (custom outro, content blocks, abort cases),
207209
that's a wizard PR — but the CLI registration is handled by the
@@ -239,7 +241,7 @@ manifest is published before the wizard tries to consume it.
239241

240242
- Skill schema details: `scripts/lib/skill-generator.js`
241243
(`parseCliBlock`, `expandSkillGroups`, JSDoc typedef for the `cli:` block)
242-
- CLI manifest emit: `scripts/lib/build-phases.js` (`generateCliManifest`)
244+
- CLI entries emit: `scripts/lib/build-phases.js` (`generateCliEntries`)
243245
- Tests for the cli block parser: `scripts/lib/tests/cli-block.test.js`
244246
- The wizard's side of the contract: [PostHog/wizard CONTRIBUTING.md](https://github.com/PostHog/wizard/blob/main/CONTRIBUTING.md)
245247

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default new Map();
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default new Map();
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
declare module "astro:content" {
2+
export interface RenderResult {
3+
Content: import("astro/runtime/server/index.js").AstroComponentFactory;
4+
headings: import("astro").MarkdownHeading[];
5+
remarkPluginFrontmatter: Record<string, any>;
6+
}
7+
interface Render {
8+
".md": Promise<RenderResult>;
9+
}
10+
11+
export interface RenderedContent {
12+
html: string;
13+
metadata?: {
14+
imagePaths: Array<string>;
15+
[key: string]: unknown;
16+
};
17+
}
18+
}
19+
20+
declare module "astro:content" {
21+
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
22+
23+
export type CollectionKey = keyof AnyEntryMap;
24+
export type CollectionEntry<C extends CollectionKey> = Flatten<
25+
AnyEntryMap[C]
26+
>;
27+
28+
export type ContentCollectionKey = keyof ContentEntryMap;
29+
export type DataCollectionKey = keyof DataEntryMap;
30+
31+
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
32+
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
33+
ContentEntryMap[C]
34+
>["slug"];
35+
36+
export type ReferenceDataEntry<
37+
C extends CollectionKey,
38+
E extends keyof DataEntryMap[C] = string,
39+
> = {
40+
collection: C;
41+
id: E;
42+
};
43+
export type ReferenceContentEntry<
44+
C extends keyof ContentEntryMap,
45+
E extends ValidContentEntrySlug<C> | (string & {}) = string,
46+
> = {
47+
collection: C;
48+
slug: E;
49+
};
50+
export type ReferenceLiveEntry<
51+
C extends keyof LiveContentConfig["collections"],
52+
> = {
53+
collection: C;
54+
id: string;
55+
};
56+
57+
/** @deprecated Use `getEntry` instead. */
58+
export function getEntryBySlug<
59+
C extends keyof ContentEntryMap,
60+
E extends ValidContentEntrySlug<C> | (string & {}),
61+
>(
62+
collection: C,
63+
// Note that this has to accept a regular string too, for SSR
64+
entrySlug: E,
65+
): E extends ValidContentEntrySlug<C>
66+
? Promise<CollectionEntry<C>>
67+
: Promise<CollectionEntry<C> | undefined>;
68+
69+
/** @deprecated Use `getEntry` instead. */
70+
export function getDataEntryById<
71+
C extends keyof DataEntryMap,
72+
E extends keyof DataEntryMap[C],
73+
>(collection: C, entryId: E): Promise<CollectionEntry<C>>;
74+
75+
export function getCollection<
76+
C extends keyof AnyEntryMap,
77+
E extends CollectionEntry<C>,
78+
>(
79+
collection: C,
80+
filter?: (entry: CollectionEntry<C>) => entry is E,
81+
): Promise<E[]>;
82+
export function getCollection<C extends keyof AnyEntryMap>(
83+
collection: C,
84+
filter?: (entry: CollectionEntry<C>) => unknown,
85+
): Promise<CollectionEntry<C>[]>;
86+
87+
export function getLiveCollection<
88+
C extends keyof LiveContentConfig["collections"],
89+
>(
90+
collection: C,
91+
filter?: LiveLoaderCollectionFilterType<C>,
92+
): Promise<
93+
import("astro").LiveDataCollectionResult<
94+
LiveLoaderDataType<C>,
95+
LiveLoaderErrorType<C>
96+
>
97+
>;
98+
99+
export function getEntry<
100+
C extends keyof ContentEntryMap,
101+
E extends ValidContentEntrySlug<C> | (string & {}),
102+
>(
103+
entry: ReferenceContentEntry<C, E>,
104+
): E extends ValidContentEntrySlug<C>
105+
? Promise<CollectionEntry<C>>
106+
: Promise<CollectionEntry<C> | undefined>;
107+
export function getEntry<
108+
C extends keyof DataEntryMap,
109+
E extends keyof DataEntryMap[C] | (string & {}),
110+
>(
111+
entry: ReferenceDataEntry<C, E>,
112+
): E extends keyof DataEntryMap[C]
113+
? Promise<DataEntryMap[C][E]>
114+
: Promise<CollectionEntry<C> | undefined>;
115+
export function getEntry<
116+
C extends keyof ContentEntryMap,
117+
E extends ValidContentEntrySlug<C> | (string & {}),
118+
>(
119+
collection: C,
120+
slug: E,
121+
): E extends ValidContentEntrySlug<C>
122+
? Promise<CollectionEntry<C>>
123+
: Promise<CollectionEntry<C> | undefined>;
124+
export function getEntry<
125+
C extends keyof DataEntryMap,
126+
E extends keyof DataEntryMap[C] | (string & {}),
127+
>(
128+
collection: C,
129+
id: E,
130+
): E extends keyof DataEntryMap[C]
131+
? string extends keyof DataEntryMap[C]
132+
? Promise<DataEntryMap[C][E]> | undefined
133+
: Promise<DataEntryMap[C][E]>
134+
: Promise<CollectionEntry<C> | undefined>;
135+
export function getLiveEntry<
136+
C extends keyof LiveContentConfig["collections"],
137+
>(
138+
collection: C,
139+
filter: string | LiveLoaderEntryFilterType<C>,
140+
): Promise<
141+
import("astro").LiveDataEntryResult<
142+
LiveLoaderDataType<C>,
143+
LiveLoaderErrorType<C>
144+
>
145+
>;
146+
147+
/** Resolve an array of entry references from the same collection */
148+
export function getEntries<C extends keyof ContentEntryMap>(
149+
entries: ReferenceContentEntry<C, ValidContentEntrySlug<C>>[],
150+
): Promise<CollectionEntry<C>[]>;
151+
export function getEntries<C extends keyof DataEntryMap>(
152+
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
153+
): Promise<CollectionEntry<C>[]>;
154+
155+
export function render<C extends keyof AnyEntryMap>(
156+
entry: AnyEntryMap[C][string],
157+
): Promise<RenderResult>;
158+
159+
export function reference<C extends keyof AnyEntryMap>(
160+
collection: C,
161+
): import("astro/zod").ZodEffects<
162+
import("astro/zod").ZodString,
163+
C extends keyof ContentEntryMap
164+
? ReferenceContentEntry<C, ValidContentEntrySlug<C>>
165+
: ReferenceDataEntry<C, keyof DataEntryMap[C]>
166+
>;
167+
// Allow generic `string` to avoid excessive type errors in the config
168+
// if `dev` is not running to update as you edit.
169+
// Invalid collection names will be caught at build time.
170+
export function reference<C extends string>(
171+
collection: C,
172+
): import("astro/zod").ZodEffects<import("astro/zod").ZodString, never>;
173+
174+
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
175+
type InferEntrySchema<C extends keyof AnyEntryMap> =
176+
import("astro/zod").infer<
177+
ReturnTypeOrOriginal<Required<ContentConfig["collections"][C]>["schema"]>
178+
>;
179+
180+
type ContentEntryMap = {};
181+
182+
type DataEntryMap = {};
183+
184+
type AnyEntryMap = ContentEntryMap & DataEntryMap;
185+
186+
type ExtractLoaderTypes<T> = T extends import("astro/loaders").LiveLoader<
187+
infer TData,
188+
infer TEntryFilter,
189+
infer TCollectionFilter,
190+
infer TError
191+
>
192+
? {
193+
data: TData;
194+
entryFilter: TEntryFilter;
195+
collectionFilter: TCollectionFilter;
196+
error: TError;
197+
}
198+
: {
199+
data: never;
200+
entryFilter: never;
201+
collectionFilter: never;
202+
error: never;
203+
};
204+
type ExtractDataType<T> = ExtractLoaderTypes<T>["data"];
205+
type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>["entryFilter"];
206+
type ExtractCollectionFilterType<T> =
207+
ExtractLoaderTypes<T>["collectionFilter"];
208+
type ExtractErrorType<T> = ExtractLoaderTypes<T>["error"];
209+
210+
type LiveLoaderDataType<C extends keyof LiveContentConfig["collections"]> =
211+
LiveContentConfig["collections"][C]["schema"] extends undefined
212+
? ExtractDataType<LiveContentConfig["collections"][C]["loader"]>
213+
: import("astro/zod").infer<
214+
Exclude<LiveContentConfig["collections"][C]["schema"], undefined>
215+
>;
216+
type LiveLoaderEntryFilterType<
217+
C extends keyof LiveContentConfig["collections"],
218+
> = ExtractEntryFilterType<LiveContentConfig["collections"][C]["loader"]>;
219+
type LiveLoaderCollectionFilterType<
220+
C extends keyof LiveContentConfig["collections"],
221+
> = ExtractCollectionFilterType<
222+
LiveContentConfig["collections"][C]["loader"]
223+
>;
224+
type LiveLoaderErrorType<C extends keyof LiveContentConfig["collections"]> =
225+
ExtractErrorType<LiveContentConfig["collections"][C]["loader"]>;
226+
227+
export type ContentConfig = typeof import("../src/content.config.mjs");
228+
export type LiveContentConfig = never;
229+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/// <reference types="astro/client" />
2+
/// <reference path="content.d.ts" />
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default new Map();
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export default new Map();

0 commit comments

Comments
 (0)