@neopress/cli 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # @neopress/cli
2
+
3
+ Manage your [Neopress](https://app.neopress.ai) site from the terminal — pages,
4
+ collections, entries, forms, assets, redirects, analytics, and publishing.
5
+
6
+ Designed to be driven by humans **and** by AI agents (Claude Code, Codex, etc.):
7
+ every command speaks JSON, and `neopress guide` prints the entire command
8
+ surface in one call.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm i -g @neopress/cli
14
+ neopress --version
15
+ ```
16
+
17
+ Or run without installing:
18
+
19
+ ```bash
20
+ npx @neopress/cli guide
21
+ ```
22
+
23
+ ## Authenticate
24
+
25
+ Log in once; the session is stored in `~/.config/neopress/tokens.json` and
26
+ refreshed automatically.
27
+
28
+ ```bash
29
+ neopress login # OAuth in the browser
30
+ neopress whoami # confirm who you are
31
+ ```
32
+
33
+ For CI or headless agents, skip the browser and pass a token instead:
34
+
35
+ ```bash
36
+ export NEOPRESS_ACCESS_TOKEN="<token>"
37
+ ```
38
+
39
+ ## Quickstart
40
+
41
+ ```bash
42
+ neopress sites list # sites you can access
43
+ neopress sites use <id|handle> # pick the active site (persisted)
44
+ neopress pages list # list pages on the active site
45
+ neopress guide # full command reference
46
+ ```
47
+
48
+ Target a one-off site without changing the active one:
49
+
50
+ ```bash
51
+ neopress --site my-blog pages list
52
+ ```
53
+
54
+ ## Common tasks
55
+
56
+ ```bash
57
+ # Create a page from a TSX file (created as a draft)
58
+ neopress pages create --path /about --title About --tsx ./about.tsx
59
+
60
+ # Localize a page — JSON options take inline JSON or @file
61
+ neopress pages create --path /about --i18n @i18n.json --public-locales en,ko
62
+
63
+ # Create and publish a collection entry
64
+ neopress entries create --collection 12 --data @entry.json --title Hello
65
+ neopress entries publish <entryId>
66
+
67
+ # Preview, then publish the whole site
68
+ neopress publish preview
69
+ neopress publish
70
+ ```
71
+
72
+ Run `neopress <command> --help` for the flags and examples of any command.
73
+
74
+ ## JSON / AI mode
75
+
76
+ Output is human-readable in a terminal and **automatically switches to JSON when
77
+ piped or redirected**, so it composes with `jq` and tools out of the box:
78
+
79
+ ```bash
80
+ neopress pages list | jq '.data[].path'
81
+ ```
82
+
83
+ Force JSON in a terminal with `--json`. For agents that want the whole CLI in a
84
+ single introspectable payload:
85
+
86
+ ```bash
87
+ neopress guide --json
88
+ ```
89
+
90
+ `neopress guide` is generated from the live command tree, so it never drifts from
91
+ the installed version — treat it as the source of truth for what the CLI can do.
92
+
93
+ ## Configuration
94
+
95
+ Resolution order for the active site: `--site` flag → `NEOPRESS_SITE_ID` →
96
+ project config file → the site saved by `neopress sites use`.
97
+
98
+ | Variable | Default | Purpose |
99
+ | --- | --- | --- |
100
+ | `NEOPRESS_ACCESS_TOKEN` | — | Auth token (skips `neopress login`) |
101
+ | `NEOPRESS_SITE_ID` | — | Default site id (overrides the active site) |
102
+ | `NEOPRESS_BASE_URL` | `https://app.neopress.ai` | API base URL (staging/local targets) |
103
+
104
+ Per-project defaults can also live in `.neopressrc.json` (or
105
+ `.neopress/config.json`):
106
+
107
+ ```json
108
+ { "siteId": 42, "baseUrl": "https://app.neopress.ai" }
109
+ ```
package/dist/bin.cjs CHANGED
@@ -3652,7 +3652,12 @@ function parseImplicitCallbackTokens(callbackUrl) {
3652
3652
  };
3653
3653
  }
3654
3654
  function registerAuthCommands(program3) {
3655
- program3.command("login").description("Authenticate with Neopress").option("--provider <provider>", "OAuth provider (google)", "google").option("--manual", "Paste the OAuth callback URL manually instead of running a local callback server").action(async (options) => {
3655
+ program3.command("login").description("Authenticate with Neopress").addHelpText("after", `
3656
+ Examples:
3657
+ $ neopress login
3658
+ $ neopress login --manual
3659
+ $ neopress login --provider google
3660
+ Default flow opens a browser and listens on a local callback port; use --manual on a headless host to paste the callback URL by hand. After login, run \`neopress sites use <siteId>\` to select a site.`).option("--provider <provider>", "OAuth provider (google)", "google").option("--manual", "Paste the OAuth callback URL manually instead of running a local callback server").action(async (options) => {
3656
3661
  console.log(options.manual ? "Starting manual browser login..." : "Opening browser for login...");
3657
3662
  const codeVerifier = generateCodeVerifier();
3658
3663
  const codeChallenge = generateCodeChallenge(codeVerifier);
@@ -24882,6 +24887,27 @@ function serializeTemplate(t) {
24882
24887
  createdAt: t.created_at
24883
24888
  };
24884
24889
  }
24890
+ function serializeHeadshot(h) {
24891
+ return {
24892
+ id: h.id,
24893
+ imageUrl: h.image_url,
24894
+ width: h.width,
24895
+ height: h.height,
24896
+ aspectRatio: h.aspect_ratio,
24897
+ mimeType: h.mime_type,
24898
+ byteSize: h.byte_size,
24899
+ ageBand: h.age_band,
24900
+ gender: h.gender,
24901
+ ethnicity: h.ethnicity,
24902
+ attire: h.attire,
24903
+ variationIndex: h.variation_index,
24904
+ gptPrompt: h.gpt_prompt,
24905
+ tags: h.tags ?? [],
24906
+ isActive: h.is_active,
24907
+ createdAt: h.created_at,
24908
+ updatedAt: h.updated_at
24909
+ };
24910
+ }
24885
24911
  function serializeSite(site) {
24886
24912
  return {
24887
24913
  id: site.id,
@@ -40564,6 +40590,113 @@ var TemplatesEndpoint = class {
40564
40590
  }
40565
40591
  };
40566
40592
 
40593
+ // ../sdk/dist/endpoints/headshots.js
40594
+ var HeadshotsEndpoint = class {
40595
+ client;
40596
+ constructor(client) {
40597
+ this.client = client;
40598
+ }
40599
+ async list(params) {
40600
+ const { page, limit, offset } = this.client.resolvePagination(params);
40601
+ let query = this.client.db.from("headshot_pool").select("*", { count: "exact" });
40602
+ if (params?.ageBand)
40603
+ query = query.eq("age_band", params.ageBand);
40604
+ if (params?.gender)
40605
+ query = query.eq("gender", params.gender);
40606
+ if (params?.ethnicity)
40607
+ query = query.eq("ethnicity", params.ethnicity);
40608
+ if (params?.attire)
40609
+ query = query.eq("attire", params.attire);
40610
+ if (params?.isActive !== void 0)
40611
+ query = query.eq("is_active", params.isActive);
40612
+ const { data, error: error48, count } = await query.order("age_band", { ascending: true }).order("gender", { ascending: true }).order("ethnicity", { ascending: true }).order("variation_index", { ascending: true }).range(offset, offset + limit - 1);
40613
+ if (error48)
40614
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40615
+ return {
40616
+ data: (data ?? []).map((r) => serializeHeadshot(r)),
40617
+ pagination: this.client.paginationMeta(page, limit, offset, count ?? 0)
40618
+ };
40619
+ }
40620
+ async get(id) {
40621
+ const { data, error: error48 } = await this.client.db.from("headshot_pool").select("*").eq("id", id).single();
40622
+ if (error48)
40623
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40624
+ return serializeHeadshot(data);
40625
+ }
40626
+ async create(params) {
40627
+ const values = {
40628
+ image_url: params.imageUrl,
40629
+ age_band: params.ageBand,
40630
+ gender: params.gender,
40631
+ ethnicity: params.ethnicity,
40632
+ variation_index: params.variationIndex
40633
+ };
40634
+ if (params.attire !== void 0)
40635
+ values.attire = params.attire;
40636
+ if (params.width !== void 0)
40637
+ values.width = params.width;
40638
+ if (params.height !== void 0)
40639
+ values.height = params.height;
40640
+ if (params.aspectRatio !== void 0)
40641
+ values.aspect_ratio = params.aspectRatio;
40642
+ if (params.mimeType !== void 0)
40643
+ values.mime_type = params.mimeType;
40644
+ if (params.byteSize !== void 0)
40645
+ values.byte_size = params.byteSize;
40646
+ if (params.gptPrompt !== void 0)
40647
+ values.gpt_prompt = params.gptPrompt;
40648
+ if (params.tags !== void 0)
40649
+ values.tags = params.tags;
40650
+ if (params.isActive !== void 0)
40651
+ values.is_active = params.isActive;
40652
+ const { data, error: error48 } = await this.client.db.from("headshot_pool").insert(values).select().single();
40653
+ if (error48)
40654
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40655
+ return serializeHeadshot(data);
40656
+ }
40657
+ async update(id, params) {
40658
+ const values = {};
40659
+ if (params.imageUrl !== void 0)
40660
+ values.image_url = params.imageUrl;
40661
+ if (params.ageBand !== void 0)
40662
+ values.age_band = params.ageBand;
40663
+ if (params.gender !== void 0)
40664
+ values.gender = params.gender;
40665
+ if (params.ethnicity !== void 0)
40666
+ values.ethnicity = params.ethnicity;
40667
+ if (params.attire !== void 0)
40668
+ values.attire = params.attire;
40669
+ if (params.variationIndex !== void 0)
40670
+ values.variation_index = params.variationIndex;
40671
+ if (params.width !== void 0)
40672
+ values.width = params.width;
40673
+ if (params.height !== void 0)
40674
+ values.height = params.height;
40675
+ if (params.aspectRatio !== void 0)
40676
+ values.aspect_ratio = params.aspectRatio;
40677
+ if (params.mimeType !== void 0)
40678
+ values.mime_type = params.mimeType;
40679
+ if (params.byteSize !== void 0)
40680
+ values.byte_size = params.byteSize;
40681
+ if (params.gptPrompt !== void 0)
40682
+ values.gpt_prompt = params.gptPrompt;
40683
+ if (params.tags !== void 0)
40684
+ values.tags = params.tags;
40685
+ if (params.isActive !== void 0)
40686
+ values.is_active = params.isActive;
40687
+ const { data, error: error48 } = await this.client.db.from("headshot_pool").update(values).eq("id", id).select().single();
40688
+ if (error48)
40689
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40690
+ return serializeHeadshot(data);
40691
+ }
40692
+ async delete(id) {
40693
+ const { error: error48 } = await this.client.db.from("headshot_pool").delete().eq("id", id);
40694
+ if (error48)
40695
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40696
+ return { id, deleted: true };
40697
+ }
40698
+ };
40699
+
40567
40700
  // ../sdk/dist/client.js
40568
40701
  var NeopressClient = class {
40569
40702
  baseUrl;
@@ -40584,6 +40717,7 @@ var NeopressClient = class {
40584
40717
  entryReferences;
40585
40718
  analytics;
40586
40719
  templates;
40720
+ headshots;
40587
40721
  constructor(options) {
40588
40722
  this.baseUrl = (options.baseUrl || "https://app.neopress.ai").replace(/\/$/, "");
40589
40723
  this.accessToken = options.accessToken || "";
@@ -40602,6 +40736,7 @@ var NeopressClient = class {
40602
40736
  this.entryReferences = new EntryReferencesEndpoint(this);
40603
40737
  this.analytics = new AnalyticsEndpoint(this);
40604
40738
  this.templates = new TemplatesEndpoint(this);
40739
+ this.headshots = new HeadshotsEndpoint(this);
40605
40740
  }
40606
40741
  get siteBasePath() {
40607
40742
  return `/api/v1/sites/${this.siteId}`;
@@ -40731,39 +40866,40 @@ async function getClientAsync(siteIdOverride) {
40731
40866
  const sb = getSupabaseConfig();
40732
40867
  return new NeopressClient({ accessToken, siteId, baseUrl: getBaseUrl(), supabaseUrl: sb.url, supabaseAnonKey: sb.anonKey });
40733
40868
  }
40869
+ async function getGlobalClientAsync() {
40870
+ const token = process.env.NEOPRESS_ACCESS_TOKEN || await getValidAccessToken();
40871
+ if (!token) {
40872
+ fail("Not authenticated. Run `neopress login` or set NEOPRESS_ACCESS_TOKEN.");
40873
+ }
40874
+ const sb = getSupabaseConfig();
40875
+ return new NeopressClient({ accessToken: token, siteId: 0, baseUrl: getBaseUrl(), supabaseUrl: sb.url, supabaseAnonKey: sb.anonKey });
40876
+ }
40734
40877
 
40735
40878
  // src/commands/sites.ts
40736
40879
  function registerSiteCommands(program3) {
40737
- const sites = program3.command("sites").description("Manage sites");
40880
+ const sites = program3.command("sites").description("Manage sites").addHelpText("after", `
40881
+ Examples:
40882
+ $ neopress sites list
40883
+ $ neopress sites create --name "My Site" --handle my-site --use
40884
+ $ neopress sites use my-site
40885
+ Run \`sites use <id|handle>\` to set the active site for all other commands.`);
40738
40886
  sites.command("list").description("List accessible sites").action(async () => {
40739
- const token = process.env.NEOPRESS_ACCESS_TOKEN || await getValidAccessToken() || void 0;
40740
- if (!token) {
40741
- fail("Not authenticated. Run `neopress login`.");
40742
- }
40743
- const client = new NeopressClient({
40744
- accessToken: token,
40745
- siteId: 0,
40746
- baseUrl: getBaseUrl()
40747
- });
40887
+ const client = await getGlobalClientAsync();
40748
40888
  const sitesList = await client.site.list();
40749
40889
  output(sitesList);
40750
40890
  });
40751
- sites.command("use <siteIdOrHandle>").description("Set active site (ID or handle)").action(async (siteIdOrHandle) => {
40891
+ sites.command("use <siteIdOrHandle>").description("Set active site (ID or handle)").addHelpText("after", `
40892
+ Examples:
40893
+ $ neopress sites use 12
40894
+ $ neopress sites use my-site
40895
+ Accepts a numeric site ID or a handle (resolved via \`sites list\`). Sets the active site used by all other commands.`).action(async (siteIdOrHandle) => {
40752
40896
  const id = parseInt(siteIdOrHandle, 10);
40753
40897
  if (!isNaN(id)) {
40754
40898
  setActiveSiteId(id);
40755
40899
  ok(`Active site set to ${id}`);
40756
40900
  return;
40757
40901
  }
40758
- const token = process.env.NEOPRESS_ACCESS_TOKEN || await getValidAccessToken() || void 0;
40759
- if (!token) {
40760
- fail("Not authenticated. Run `neopress login`.");
40761
- }
40762
- const client = new NeopressClient({
40763
- accessToken: token,
40764
- siteId: 0,
40765
- baseUrl: getBaseUrl()
40766
- });
40902
+ const client = await getGlobalClientAsync();
40767
40903
  const sitesList = await client.site.list();
40768
40904
  const match = sitesList.find((s) => s.handle === siteIdOrHandle);
40769
40905
  if (!match) {
@@ -40772,19 +40908,16 @@ function registerSiteCommands(program3) {
40772
40908
  setActiveSiteId(match.id);
40773
40909
  ok(`Active site set to ${match.id} (${match.handle})`);
40774
40910
  });
40775
- sites.command("create").description("Create a new site").option("--name <name>", "Site name").option("--handle <handle>", "Site handle").option("--prompt <prompt>", "Prompt to generate site name/handle").option("--language <locale>", "Default content locale", "en").option("--use", "Set the created site as active").action(async (options) => {
40911
+ sites.command("create").description("Create a new site").addHelpText("after", `
40912
+ Examples:
40913
+ $ neopress sites create --name "My Site" --handle my-site
40914
+ $ neopress sites create --prompt "a portfolio for a freelance photographer" --use
40915
+ $ neopress sites create --name Acme --handle acme --language en
40916
+ Pass --prompt to auto-generate name/handle, or supply both --name and --handle. --language sets the default content locale (default en). --use sets the new site as active.`).option("--name <name>", "Site name").option("--handle <handle>", "Site handle").option("--prompt <prompt>", "Prompt to generate site name/handle").option("--language <locale>", "Default content locale", "en").option("--use", "Set the created site as active").action(async (options) => {
40776
40917
  if (!options.prompt && !(options.name && options.handle)) {
40777
40918
  fail("Provide --prompt or both --name and --handle.");
40778
40919
  }
40779
- const token = process.env.NEOPRESS_ACCESS_TOKEN || await getValidAccessToken() || void 0;
40780
- if (!token) {
40781
- fail("Not authenticated. Run `neopress login`.");
40782
- }
40783
- const client = new NeopressClient({
40784
- accessToken: token,
40785
- siteId: 0,
40786
- baseUrl: getBaseUrl()
40787
- });
40920
+ const client = await getGlobalClientAsync();
40788
40921
  const site = await client.site.create({
40789
40922
  siteName: options.name,
40790
40923
  siteHandle: options.handle,
@@ -40802,7 +40935,11 @@ function registerSiteCommands(program3) {
40802
40935
  const site = await client.site.get();
40803
40936
  output(site);
40804
40937
  });
40805
- sites.command("update").description("Update site settings").option("--name <name>", "Site name").option("--locale-config <json>", "Locale config JSON").action(async (options) => {
40938
+ sites.command("update").description("Update site settings").addHelpText("after", `
40939
+ Examples:
40940
+ $ neopress sites update --name "New Name"
40941
+ $ neopress sites update --locale-config '{"locales":["en","ko"],"defaultLocale":"en"}'
40942
+ Updates the active site. --locale-config takes inline JSON. Pass at least one of --name or --locale-config.`).option("--name <name>", "Site name").option("--locale-config <json>", "Locale config JSON").action(async (options) => {
40806
40943
  const client = await getClientAsync();
40807
40944
  const data = {};
40808
40945
  if (options.name) data.name = options.name;
@@ -40819,7 +40956,11 @@ function registerSiteCommands(program3) {
40819
40956
  const site = await client.site.update(data);
40820
40957
  output(site);
40821
40958
  });
40822
- sites.command("delete <siteIdOrHandle>").description("Delete a site and all its content (workspace admin only)").option("--yes", "Skip confirmation (required in non-interactive mode)").action(async (siteIdOrHandle, options) => {
40959
+ sites.command("delete <siteIdOrHandle>").description("Delete a site and all its content (workspace admin only)").addHelpText("after", `
40960
+ Examples:
40961
+ $ neopress sites delete my-site
40962
+ $ neopress sites delete 12 --yes
40963
+ Permanently deletes the site and all its content (workspace admin only) \u2014 this is a hard delete, not recoverable. Interactive runs prompt for the id/handle to confirm; --yes is required in non-interactive mode.`).option("--yes", "Skip confirmation (required in non-interactive mode)").action(async (siteIdOrHandle, options) => {
40823
40964
  const interactive = process.stdout.isTTY && process.stdin.isTTY;
40824
40965
  if (!options.yes && !interactive) {
40825
40966
  fail("Refusing to delete without --yes in non-interactive mode.");
@@ -40878,7 +41019,13 @@ function mergeQueryConfigIntoDraftMeta(draftMetaInput, queryConfigInput) {
40878
41019
  };
40879
41020
  }
40880
41021
  function registerPageCommands(program3) {
40881
- const pages = program3.command("pages").description("Manage pages");
41022
+ const pages = program3.command("pages").description("Manage pages").addHelpText("after", `
41023
+ Examples:
41024
+ $ neopress pages create --path /about --title About --tsx ./about.tsx
41025
+ $ neopress pages update <pageId> --tsx ./about.tsx
41026
+ $ neopress pages compile <pageId>
41027
+
41028
+ Draft content fields (--tsx/--i18n/--draft-meta) need \`neopress publish\` to go live. \`pages delete\` is a soft delete.`);
40882
41029
  pages.command("list").description("List pages").option("--status <status>", "Filter by status (draft/published)").option("--page <n>", "Page number", "1").option("--limit <n>", "Results per page", "20").action(async (options) => {
40883
41030
  const client = await getClientAsync();
40884
41031
  const res = await client.pages.list({
@@ -40893,7 +41040,17 @@ function registerPageCommands(program3) {
40893
41040
  const page = await client.pages.get(parseInt(pageId, 10));
40894
41041
  output(page);
40895
41042
  });
40896
- pages.command("create").description("Create a new page").requiredOption("--path <path>", "Page path (e.g., /about)").option("--title <title>", "Page title").option("--tsx <file>", "TSX file path").option("--i18n <json>", "Page i18n JSON string or @file path").option("--public-locales <locales>", "Comma-separated locale list or JSON string array").option("--metadata <json>", "Page metadata JSON string or @file path").option("--draft-meta <json>", "Draft runtime metadata JSON string or @file path").option("--query-config <json>", "Query config JSON string or @file path; stored as draftMeta.queryConfig").option("--collection <id>", "Collection ID for dynamic pages").option("--type <type>", "Page type (static/dynamic)", "static").action(async (options) => {
41043
+ pages.command("create").description("Create a new page").requiredOption("--path <path>", "Page path (e.g., /about)").option("--title <title>", "Page title").option("--tsx <file>", "TSX file path").option("--i18n <json>", "Page i18n JSON string or @file path").option("--public-locales <locales>", "Comma-separated locale list or JSON string array").option("--metadata <json>", "Page metadata JSON string or @file path").option("--draft-meta <json>", "Draft runtime metadata JSON string or @file path").option("--query-config <json>", "Query config JSON string or @file path; stored as draftMeta.queryConfig").option("--collection <id>", "Collection ID for dynamic pages").option("--type <type>", "Page type (static/dynamic)", "static").addHelpText(
41044
+ "after",
41045
+ `
41046
+ Examples:
41047
+ $ neopress pages create --path /about --title About --tsx ./about.tsx
41048
+ $ neopress pages create --path /blog --type dynamic --collection 12 --tsx ./blog.tsx
41049
+ $ neopress pages create --path /about --i18n @i18n.json --public-locales en,ko
41050
+
41051
+ --tsx reads the component from a file. JSON options take inline JSON or @file (e.g. --i18n @i18n.json).
41052
+ Pages are created as drafts \u2014 run \`neopress publish\` to make them live.`
41053
+ ).action(async (options) => {
40897
41054
  const client = await getClientAsync();
40898
41055
  const draftTsx = options.tsx ? (0, import_node_fs3.readFileSync)(options.tsx, "utf-8") : void 0;
40899
41056
  const draftI18n = parseJsonInput(options.i18n);
@@ -40914,7 +41071,13 @@ function registerPageCommands(program3) {
40914
41071
  });
40915
41072
  output(page);
40916
41073
  });
40917
- pages.command("update <pageId>").description("Update a page").option("--path <path>", "New page path").option("--title <title>", "New title").option("--tsx <file>", "TSX file path").option("--i18n <json>", "Page i18n JSON string or @file path").option("--public-locales <locales>", "Comma-separated locale list or JSON string array").option("--metadata <json>", "Page metadata JSON string or @file path").option("--draft-meta <json>", "Draft runtime metadata JSON string or @file path").option("--query-config <json>", "Query config JSON string or @file path; stored as draftMeta.queryConfig").option("--collection <id>", "Collection ID for dynamic pages").action(async (pageId, options) => {
41074
+ pages.command("update <pageId>").description("Update a page").addHelpText("after", `
41075
+ Examples:
41076
+ $ neopress pages update 12 --tsx ./about.tsx
41077
+ $ neopress pages update 12 --title About --path /about-us
41078
+ $ neopress pages update 12 --collection 12 --query-config @query.json
41079
+
41080
+ Pass at least one field flag. --tsx reads the component from a file; JSON options take inline JSON or @file. Draft content (--tsx/--i18n/--draft-meta/--query-config) needs \`neopress publish\` to go live; --title/--path/--collection apply immediately.`).option("--path <path>", "New page path").option("--title <title>", "New title").option("--tsx <file>", "TSX file path").option("--i18n <json>", "Page i18n JSON string or @file path").option("--public-locales <locales>", "Comma-separated locale list or JSON string array").option("--metadata <json>", "Page metadata JSON string or @file path").option("--draft-meta <json>", "Draft runtime metadata JSON string or @file path").option("--query-config <json>", "Query config JSON string or @file path; stored as draftMeta.queryConfig").option("--collection <id>", "Collection ID for dynamic pages").action(async (pageId, options) => {
40918
41081
  const client = await getClientAsync();
40919
41082
  const data = {};
40920
41083
  if (options.path) data.path = options.path;
@@ -40938,7 +41101,11 @@ function registerPageCommands(program3) {
40938
41101
  await client.pages.delete(parseInt(pageId, 10));
40939
41102
  ok(`Page ${pageId} deleted`);
40940
41103
  });
40941
- pages.command("compile <pageId>").description("Validate TSX compilation").action(async (pageId) => {
41104
+ pages.command("compile <pageId>").description("Validate TSX compilation").addHelpText("after", `
41105
+ Examples:
41106
+ $ neopress pages compile 12
41107
+
41108
+ Validates the page's draft TSX and reports compile status \u2014 it does not save or publish.`).action(async (pageId) => {
40942
41109
  const client = await getClientAsync();
40943
41110
  const result = await client.pages.compile(parseInt(pageId, 10));
40944
41111
  output(result);
@@ -40952,7 +41119,13 @@ function parseJsonInput2(input) {
40952
41119
  return input.startsWith("@") ? JSON.parse((0, import_node_fs4.readFileSync)(input.slice(1), "utf-8")) : JSON.parse(input);
40953
41120
  }
40954
41121
  function registerCollectionCommands(program3) {
40955
- const collections = program3.command("collections").description("Manage collections");
41122
+ const collections = program3.command("collections").description("Manage collections").addHelpText("after", `
41123
+ Examples:
41124
+ $ neopress collections create --name Posts --schema @schema.json
41125
+ $ neopress collections list
41126
+ $ neopress collections update 12 --schema @schema.json
41127
+
41128
+ Collections are identified by numeric id, never name. delete is a soft delete \u2014 the collection is hidden from list/get but its row is retained.`);
40956
41129
  collections.command("list").description("List collections").action(async () => {
40957
41130
  const client = await getClientAsync();
40958
41131
  const data = await client.collections.list();
@@ -40963,7 +41136,13 @@ function registerCollectionCommands(program3) {
40963
41136
  const data = await client.collections.get(parseInt(collectionId, 10));
40964
41137
  output(data);
40965
41138
  });
40966
- collections.command("create").description("Create a collection").requiredOption("--name <name>", "Collection name").option("--schema <json>", "Draft schema as JSON string or @file path").action(async (options) => {
41139
+ collections.command("create").description("Create a collection").addHelpText("after", `
41140
+ Examples:
41141
+ $ neopress collections create --name Posts
41142
+ $ neopress collections create --name Posts --schema @schema.json
41143
+ $ neopress collections create --name Posts --schema '{"title":{"type":"text"}}'
41144
+
41145
+ --schema takes inline JSON or @file and is keyed by field id. It is written to the draft schema \u2014 publish the site to make it live.`).requiredOption("--name <name>", "Collection name").option("--schema <json>", "Draft schema as JSON string or @file path").action(async (options) => {
40967
41146
  const client = await getClientAsync();
40968
41147
  const data = await client.collections.create({
40969
41148
  name: options.name,
@@ -40971,7 +41150,12 @@ function registerCollectionCommands(program3) {
40971
41150
  });
40972
41151
  output(data);
40973
41152
  });
40974
- collections.command("update <collectionId>").description("Update a collection").option("--name <name>", "Collection name").option("--schema <json>", "Draft schema as JSON string or @file path").action(async (collectionId, options) => {
41153
+ collections.command("update <collectionId>").description("Update a collection").addHelpText("after", `
41154
+ Examples:
41155
+ $ neopress collections update 12 --name Articles
41156
+ $ neopress collections update 12 --schema @schema.json
41157
+
41158
+ Identify the collection by numeric id. Pass at least one of --name or --schema. --schema (inline JSON or @file, field-id keyed) replaces the draft schema; publish the site to make it live.`).option("--name <name>", "Collection name").option("--schema <json>", "Draft schema as JSON string or @file path").action(async (collectionId, options) => {
40975
41159
  const client = await getClientAsync();
40976
41160
  const data = {};
40977
41161
  if (options.name) data.name = options.name;
@@ -40995,7 +41179,13 @@ function registerCollectionCommands(program3) {
40995
41179
  // src/commands/entries.ts
40996
41180
  var import_node_fs5 = require("fs");
40997
41181
  function registerEntryCommands(program3) {
40998
- const entries = program3.command("entries").description("Manage entries");
41182
+ const entries = program3.command("entries").description("Manage entries").addHelpText("after", `
41183
+ Examples:
41184
+ $ neopress entries list --collection 12 --status published --locale en
41185
+ $ neopress entries create --collection 12 --data @entry.json --title Hello
41186
+ $ neopress entries publish <entryId>
41187
+
41188
+ Entries are created as drafts \u2014 run \`neopress entries publish <entryId>\` to make them live. create/update/delete revalidate the reader cache; delete is a soft delete.`);
40999
41189
  entries.command("list").description("List entries in a collection").requiredOption("--collection <id>", "Collection ID").option("--status <status>", "Filter by status").option("--locale <locale>", "Filter by locale").option("--page <n>", "Page number", "1").option("--limit <n>", "Results per page", "20").action(async (options) => {
41000
41190
  const client = await getClientAsync();
41001
41191
  const res = await client.entries.list(parseInt(options.collection, 10), {
@@ -41011,7 +41201,15 @@ function registerEntryCommands(program3) {
41011
41201
  const data = await client.entries.get(parseInt(entryId, 10));
41012
41202
  output(data);
41013
41203
  });
41014
- entries.command("create").description("Create an entry").requiredOption("--collection <id>", "Collection ID").option("--data <json>", "Entry data as JSON string or @file path").option("--title <title>", "Entry title").option("--slug <slug>", "Entry slug").option("--locale <locale>", "Entry locale").action(async (options) => {
41204
+ entries.command("create").description("Create an entry").requiredOption("--collection <id>", "Collection ID").option("--data <json>", "Entry data as JSON string or @file path").option("--title <title>", "Entry title").option("--slug <slug>", "Entry slug").option("--locale <locale>", "Entry locale").addHelpText(
41205
+ "after",
41206
+ `
41207
+ Examples:
41208
+ $ neopress entries create --collection 12 --data '{"body":"Hello"}' --title Hello
41209
+ $ neopress entries create --collection 12 --data @entry.json --locale ko
41210
+
41211
+ --data takes inline JSON or @file. After creating, run \`neopress entries publish <entryId>\` to publish it.`
41212
+ ).action(async (options) => {
41015
41213
  const client = await getClientAsync();
41016
41214
  let data;
41017
41215
  if (options.data?.startsWith("@")) {
@@ -41030,7 +41228,12 @@ function registerEntryCommands(program3) {
41030
41228
  await client.entries.revalidateReaderCache(entry.id);
41031
41229
  output(entry);
41032
41230
  });
41033
- entries.command("update <entryId>").description("Update an entry").option("--data <json>", "Entry data as JSON string or @file path").option("--title <title>", "Entry title").option("--slug <slug>", "Entry slug").option("--locale <locale>", "Entry locale").action(async (entryId, options) => {
41231
+ entries.command("update <entryId>").description("Update an entry").addHelpText("after", `
41232
+ Examples:
41233
+ $ neopress entries update <entryId> --title "New title"
41234
+ $ neopress entries update <entryId> --data @entry.json --locale ko
41235
+
41236
+ --data takes inline JSON or @file and replaces the entry data. At least one field is required. Updating revalidates the reader cache; publish state is unchanged.`).option("--data <json>", "Entry data as JSON string or @file path").option("--title <title>", "Entry title").option("--slug <slug>", "Entry slug").option("--locale <locale>", "Entry locale").action(async (entryId, options) => {
41034
41237
  const client = await getClientAsync();
41035
41238
  const updateData = {};
41036
41239
  if (options.data) {
@@ -41071,7 +41274,12 @@ function parseJsonInput3(input) {
41071
41274
  return input.startsWith("@") ? JSON.parse((0, import_node_fs6.readFileSync)(input.slice(1), "utf-8")) : JSON.parse(input);
41072
41275
  }
41073
41276
  function registerFormCommands(program3) {
41074
- const forms = program3.command("forms").description("Manage forms");
41277
+ const forms = program3.command("forms").description("Manage forms").addHelpText("after", `
41278
+ Examples:
41279
+ $ neopress forms create --name Contact --schema @schema.json --layout @layout.json
41280
+ $ neopress forms publish <formId>
41281
+ $ neopress forms responses <formId> --limit 50
41282
+ Forms are created as drafts \u2014 run \`neopress forms publish <formId>\` to make them live. JSON options take inline JSON or @file.`);
41075
41283
  forms.command("list").description("List forms").action(async () => {
41076
41284
  const client = await getClientAsync();
41077
41285
  const data = await client.forms.list();
@@ -41082,7 +41290,12 @@ function registerFormCommands(program3) {
41082
41290
  const data = await client.forms.get(parseInt(formId, 10));
41083
41291
  output(data);
41084
41292
  });
41085
- forms.command("create").description("Create a form").requiredOption("--name <name>", "Form name").option("--schema <json>", "Draft form schema JSON string or @file path").option("--layout <json>", "Draft form layout JSON string or @file path").option("--style <json>", "Draft form style JSON string or @file path").option("--i18n <json>", "Draft form i18n JSON string or @file path").option("--settings <json>", "Form settings JSON string or @file path").action(async (options) => {
41293
+ forms.command("create").description("Create a form").addHelpText("after", `
41294
+ Examples:
41295
+ $ neopress forms create --name Contact
41296
+ $ neopress forms create --name Contact --schema @schema.json --layout @layout.json --i18n @i18n.json
41297
+ $ neopress forms create --name Signup --settings '{"redirectUrl":"/thanks"}'
41298
+ Only --name is required. --schema/--layout/--style/--i18n/--settings each take inline JSON or @file and write the draft state. The form is created as a draft \u2014 run \`neopress forms publish\` to make it live.`).requiredOption("--name <name>", "Form name").option("--schema <json>", "Draft form schema JSON string or @file path").option("--layout <json>", "Draft form layout JSON string or @file path").option("--style <json>", "Draft form style JSON string or @file path").option("--i18n <json>", "Draft form i18n JSON string or @file path").option("--settings <json>", "Form settings JSON string or @file path").action(async (options) => {
41086
41299
  const client = await getClientAsync();
41087
41300
  const data = await client.forms.create({
41088
41301
  name: options.name,
@@ -41094,7 +41307,11 @@ function registerFormCommands(program3) {
41094
41307
  });
41095
41308
  output(data);
41096
41309
  });
41097
- forms.command("update <formId>").description("Update a form").option("--name <name>", "Form name").option("--schema <json>", "Draft form schema JSON string or @file path").option("--layout <json>", "Draft form layout JSON string or @file path").option("--style <json>", "Draft form style JSON string or @file path").option("--i18n <json>", "Draft form i18n JSON string or @file path").option("--settings <json>", "Form settings JSON string or @file path").action(async (formId, options) => {
41310
+ forms.command("update <formId>").description("Update a form").addHelpText("after", `
41311
+ Examples:
41312
+ $ neopress forms update <formId> --name Contact
41313
+ $ neopress forms update <formId> --schema @schema.json --layout @layout.json
41314
+ Only the supplied fields change; pass at least one of --name/--schema/--layout/--style/--i18n/--settings. JSON options take inline JSON or @file and update the draft state \u2014 run \`neopress forms publish\` to push changes live.`).option("--name <name>", "Form name").option("--schema <json>", "Draft form schema JSON string or @file path").option("--layout <json>", "Draft form layout JSON string or @file path").option("--style <json>", "Draft form style JSON string or @file path").option("--i18n <json>", "Draft form i18n JSON string or @file path").option("--settings <json>", "Form settings JSON string or @file path").action(async (formId, options) => {
41098
41315
  const client = await getClientAsync();
41099
41316
  const data = {};
41100
41317
  if (options.name) data.name = options.name;
@@ -41109,12 +41326,19 @@ function registerFormCommands(program3) {
41109
41326
  const updated = await client.forms.update(parseInt(formId, 10), data);
41110
41327
  output(updated);
41111
41328
  });
41112
- forms.command("publish <formId>").description("Publish a form").action(async (formId) => {
41329
+ forms.command("publish <formId>").description("Publish a form").addHelpText("after", `
41330
+ Examples:
41331
+ $ neopress forms publish <formId>
41332
+ Copies the form's draft state (schema/layout/style/i18n) to its live state. Required after create/update before the form renders publicly.`).action(async (formId) => {
41113
41333
  const client = await getClientAsync();
41114
41334
  const result = await client.forms.publish(parseInt(formId, 10));
41115
41335
  output(result);
41116
41336
  });
41117
- forms.command("responses <formId>").description("List form responses").option("--page <n>", "Page number", "1").option("--limit <n>", "Results per page", "20").action(async (formId, options) => {
41337
+ forms.command("responses <formId>").description("List form responses").addHelpText("after", `
41338
+ Examples:
41339
+ $ neopress forms responses <formId>
41340
+ $ neopress forms responses <formId> --page 2 --limit 50
41341
+ Returns submitted responses newest-first, paginated (--page/--limit, default page 1, limit 20).`).option("--page <n>", "Page number", "1").option("--limit <n>", "Results per page", "20").action(async (formId, options) => {
41118
41342
  const client = await getClientAsync();
41119
41343
  const res = await client.forms.listResponses(parseInt(formId, 10), {
41120
41344
  page: parseInt(options.page, 10),
@@ -41133,7 +41357,11 @@ function registerFormCommands(program3) {
41133
41357
  var import_node_fs7 = require("fs");
41134
41358
  var import_node_path3 = require("path");
41135
41359
  function registerAssetCommands(program3) {
41136
- const assets = program3.command("assets").description("Manage assets");
41360
+ const assets = program3.command("assets").description("Manage assets").addHelpText("after", `
41361
+ Examples:
41362
+ $ neopress assets upload ./hero.png --folder images
41363
+ $ neopress assets list --limit 50
41364
+ Upload runs presign \u2192 PUT to storage \u2192 register in one step; content type is inferred from the file extension.`);
41137
41365
  assets.command("list").description("List assets").option("--page <n>", "Page number", "1").option("--limit <n>", "Results per page", "20").action(async (options) => {
41138
41366
  const client = await getClientAsync();
41139
41367
  const res = await client.assets.list({
@@ -41142,7 +41370,11 @@ function registerAssetCommands(program3) {
41142
41370
  });
41143
41371
  output(res);
41144
41372
  });
41145
- assets.command("upload <filePath>").description("Upload a file (presign \u2192 upload \u2192 register)").option("--folder <folder>", "Target folder path").action(async (filePath, options) => {
41373
+ assets.command("upload <filePath>").description("Upload a file (presign \u2192 upload \u2192 register)").addHelpText("after", `
41374
+ Examples:
41375
+ $ neopress assets upload ./hero.png
41376
+ $ neopress assets upload ./docs/guide.pdf --folder downloads
41377
+ <filePath> is a local file path; its extension sets the content type. --folder is the storage subfolder (defaults to uploads/); omit it and the asset record's folder is left unset. Prints the registered asset and its public URL.`).option("--folder <folder>", "Target folder path").action(async (filePath, options) => {
41146
41378
  const client = await getClientAsync();
41147
41379
  const fileName = (0, import_node_path3.basename)(filePath);
41148
41380
  const stats = (0, import_node_fs7.statSync)(filePath);
@@ -41188,7 +41420,13 @@ function registerAssetCommands(program3) {
41188
41420
 
41189
41421
  // src/commands/publish.ts
41190
41422
  function registerPublishCommands(program3) {
41191
- const publish = program3.command("publish").description("Publish site or preview changes").action(async () => {
41423
+ const publish = program3.command("publish").description("Publish site or preview changes").addHelpText(
41424
+ "after",
41425
+ `
41426
+ Examples:
41427
+ $ neopress publish preview Show what would change
41428
+ $ neopress publish Publish all pending changes live`
41429
+ ).action(async () => {
41192
41430
  const client = await getClientAsync();
41193
41431
  const result = await client.publish.site();
41194
41432
  output(result);
@@ -41203,13 +41441,22 @@ function registerPublishCommands(program3) {
41203
41441
 
41204
41442
  // src/commands/redirects.ts
41205
41443
  function registerRedirectCommands(program3) {
41206
- const redirects = program3.command("redirects").description("Manage redirect rules");
41444
+ const redirects = program3.command("redirects").description("Manage redirect rules").addHelpText("after", `
41445
+ Examples:
41446
+ $ neopress redirects create --source /old --dest /about
41447
+ $ neopress redirects create --source /docs --dest /guide --type 308
41448
+ $ neopress redirects list
41449
+ Rules are created enabled. delete <ruleId> removes a rule permanently.`);
41207
41450
  redirects.command("list").description("List redirect rules").action(async () => {
41208
41451
  const client = await getClientAsync();
41209
41452
  const data = await client.redirects.list();
41210
41453
  output(data);
41211
41454
  });
41212
- redirects.command("create").description("Create a redirect rule").requiredOption("--source <path>", "Source path").requiredOption("--dest <path>", "Destination path").option("--type <type>", "Redirect type (307/308)", "307").action(async (options) => {
41455
+ redirects.command("create").description("Create a redirect rule").addHelpText("after", `
41456
+ Examples:
41457
+ $ neopress redirects create --source /old --dest /about
41458
+ $ neopress redirects create --source /docs --dest /guide --type 308
41459
+ --source and --dest are both required. --type is 307 (temporary, default) or 308 (permanent). The rule is created enabled.`).requiredOption("--source <path>", "Source path").requiredOption("--dest <path>", "Destination path").option("--type <type>", "Redirect type (307/308)", "307").action(async (options) => {
41213
41460
  const client = await getClientAsync();
41214
41461
  const rule = await client.redirects.create({
41215
41462
  source: options.source,
@@ -41228,13 +41475,22 @@ function registerRedirectCommands(program3) {
41228
41475
  // src/commands/layout.ts
41229
41476
  var import_node_fs8 = require("fs");
41230
41477
  function registerLayoutCommands(program3) {
41231
- const layout = program3.command("layout").description("Manage site layout");
41478
+ const layout = program3.command("layout").description("Manage site layout").addHelpText("after", `
41479
+ Examples:
41480
+ $ neopress layout get
41481
+ $ neopress layout update --tsx ./layout.tsx --global-css ./globals.css
41482
+ $ neopress layout update --not-found-tsx ./not-found.tsx
41483
+ Layout edits land in draft state \u2014 run \`neopress publish\` to make them live.`);
41232
41484
  layout.command("get").description("Get current site layout").action(async () => {
41233
41485
  const client = await getClientAsync();
41234
41486
  const data = await client.site.getLayout();
41235
41487
  output(data);
41236
41488
  });
41237
- layout.command("update").description("Update site layout").option("--tsx <file>", "Layout TSX file path").option("--not-found-tsx <file>", "Not-found page TSX file path").option("--global-css <file>", "Global CSS file path").action(async (options) => {
41489
+ layout.command("update").description("Update site layout").addHelpText("after", `
41490
+ Examples:
41491
+ $ neopress layout update --tsx ./layout.tsx
41492
+ $ neopress layout update --tsx ./layout.tsx --not-found-tsx ./not-found.tsx --global-css ./globals.css
41493
+ Each flag reads its content from a file path. Pass at least one of --tsx, --not-found-tsx, or --global-css. Updates write to the draft layout only \u2014 run \`neopress publish\` to make them live.`).option("--tsx <file>", "Layout TSX file path").option("--not-found-tsx <file>", "Not-found page TSX file path").option("--global-css <file>", "Global CSS file path").action(async (options) => {
41238
41494
  const client = await getClientAsync();
41239
41495
  const data = {};
41240
41496
  if (options.tsx) data.draftTsx = (0, import_node_fs8.readFileSync)(options.tsx, "utf-8");
@@ -41250,7 +41506,12 @@ function registerLayoutCommands(program3) {
41250
41506
 
41251
41507
  // src/commands/entry-references.ts
41252
41508
  function registerEntryReferenceCommands(program3) {
41253
- const refs = program3.command("entry-references").description("Manage entry reference relations");
41509
+ const refs = program3.command("entry-references").description("Manage entry reference relations").addHelpText("after", `
41510
+ Examples:
41511
+ $ neopress entry-references create --from-entry 12 --to-entry 34 --field author
41512
+ $ neopress entry-references list --from-entry 12
41513
+ $ neopress entry-references delete <referenceId>
41514
+ --field is a reference field key from the source entry's collection schema. delete is a hard delete and removes the relation immediately.`);
41254
41515
  refs.command("list").description("List entry references").option("--from-entry <id>", "Filter by source entry ID").option("--to-entry <id>", "Filter by target entry ID").action(async (options) => {
41255
41516
  const client = await getClientAsync();
41256
41517
  const data = await client.entryReferences.list({
@@ -41259,7 +41520,11 @@ function registerEntryReferenceCommands(program3) {
41259
41520
  });
41260
41521
  output(data);
41261
41522
  });
41262
- refs.command("create").description("Create an entry reference").requiredOption("--from-entry <id>", "Source entry ID").requiredOption("--to-entry <id>", "Target entry ID").requiredOption("--field <key>", "Reference field key from the source collection schema").action(async (options) => {
41523
+ refs.command("create").description("Create an entry reference").addHelpText("after", `
41524
+ Examples:
41525
+ $ neopress entry-references create --from-entry 12 --to-entry 34 --field author
41526
+ $ neopress entry-references create --from-entry 100 --to-entry 7 --field relatedPosts
41527
+ All three flags are required. --field is a reference field key defined in the source entry's collection schema; --from-entry and --to-entry must be numeric entry IDs.`).requiredOption("--from-entry <id>", "Source entry ID").requiredOption("--to-entry <id>", "Target entry ID").requiredOption("--field <key>", "Reference field key from the source collection schema").action(async (options) => {
41263
41528
  const client = await getClientAsync();
41264
41529
  const fromEntryId = parseInt(options.fromEntry, 10);
41265
41530
  const toEntryId = parseInt(options.toEntry, 10);
@@ -41326,9 +41591,19 @@ function parseSearchConsoleDimensions(raw) {
41326
41591
  return dimensions;
41327
41592
  }
41328
41593
  function registerAnalyticsCommands(program3) {
41329
- const analytics = program3.command("analytics").description("Read site analytics and Search Console data");
41594
+ const analytics = program3.command("analytics").description("Read site analytics and Search Console data").addHelpText("after", `
41595
+ Examples:
41596
+ $ neopress analytics summary --date-from 2026-01-01 --date-to 2026-01-31
41597
+ $ neopress analytics pages --date-from 2026-01-01 --date-to 2026-01-31 --limit 20
41598
+ $ neopress analytics search-console --date-from 2026-01-01 --date-to 2026-01-31 --dimensions query,page
41599
+ All analytics commands are read-only. Convenience commands (summary, pages, sources, forms, crawlers, pageviews) share --date-from/--date-to/--timezone/--limit.`);
41330
41600
  addDateOptions(
41331
- analytics.command("query").description("Run an allowed analytics pipe").requiredOption("--pipe <pipe>", "Analytics pipe name")
41601
+ analytics.command("query").description("Run an allowed analytics pipe").addHelpText("after", `
41602
+ Examples:
41603
+ $ neopress analytics query --pipe get_top_pages --date-from 2026-01-01 --date-to 2026-01-31
41604
+ $ neopress analytics query --pipe get_pageviews --params '{"device":"mobile"}'
41605
+ $ neopress analytics query --pipe get_top_pages --params @params.json --limit 50
41606
+ --pipe is required. --params takes inline JSON or @file and adds extra pipe params; reserved keys (pipe, site, site_id, timezone, token, q) are ignored.`).requiredOption("--pipe <pipe>", "Analytics pipe name")
41332
41607
  ).action(async (options) => {
41333
41608
  const client = await getClientAsync();
41334
41609
  const data = await client.analytics.query({
@@ -41373,7 +41648,12 @@ function registerAnalyticsCommands(program3) {
41373
41648
  const client = await getClientAsync();
41374
41649
  output(await client.analytics.crawlerSummary(readDateOptions(options)));
41375
41650
  });
41376
- analytics.command("search-console").description("Query Google Search Console performance data").requiredOption("--date-from <date>", "Start date in YYYY-MM-DD").requiredOption("--date-to <date>", "End date in YYYY-MM-DD").option("--dimensions <csv>", "CSV dimensions: query,page,country,device", "query").option("--query-filter <query>", "Exact query filter").option("--page-filter <url>", "Exact page URL filter").option("--row-limit <n>", "Maximum rows to return", "100").action(async (options) => {
41651
+ analytics.command("search-console").description("Query Google Search Console performance data").addHelpText("after", `
41652
+ Examples:
41653
+ $ neopress analytics search-console --date-from 2026-01-01 --date-to 2026-01-31
41654
+ $ neopress analytics search-console --date-from 2026-01-01 --date-to 2026-01-31 --dimensions query,page --row-limit 50
41655
+ $ neopress analytics search-console --date-from 2026-01-01 --date-to 2026-01-31 --page-filter https://example.com/about
41656
+ --date-from and --date-to are required. --dimensions is a CSV of query,page,country,device (defaults to query).`).requiredOption("--date-from <date>", "Start date in YYYY-MM-DD").requiredOption("--date-to <date>", "End date in YYYY-MM-DD").option("--dimensions <csv>", "CSV dimensions: query,page,country,device", "query").option("--query-filter <query>", "Exact query filter").option("--page-filter <url>", "Exact page URL filter").option("--row-limit <n>", "Maximum rows to return", "100").action(async (options) => {
41377
41657
  const client = await getClientAsync();
41378
41658
  const data = await client.analytics.searchConsole({
41379
41659
  dateFrom: options.dateFrom,
@@ -41392,7 +41672,12 @@ function collectPreviewImgUrl(value, previous = []) {
41392
41672
  return previous.concat(value);
41393
41673
  }
41394
41674
  function registerTemplateCommands(program3) {
41395
- const templates = program3.command("templates").description("Manage template metadata for the active site");
41675
+ const templates = program3.command("templates").description("Manage template metadata for the active site").addHelpText("after", `
41676
+ Examples:
41677
+ $ neopress templates mark --name "SaaS Landing" --industry saas --reference-url https://example.com --preview-img-url https://cdn.example.com/1.png
41678
+ $ neopress templates list --industry saas --limit 50
41679
+ $ neopress templates delete
41680
+ \`templates mark\` operates on the active site. \`templates delete\` permanently removes the template metadata AND the connected site \u2014 it is destructive and not recoverable.`);
41396
41681
  templates.command("list").description("List templates across sites you can access").option("--name-prefix <prefix>", "Filter by template name prefix").option("--industry <industry>", "Filter by industry key").option("--limit <n>", "Max results (default 100, max 500)").action(async (options) => {
41397
41682
  const token = process.env.NEOPRESS_ACCESS_TOKEN || await getValidAccessToken() || void 0;
41398
41683
  if (!token) {
@@ -41411,7 +41696,11 @@ function registerTemplateCommands(program3) {
41411
41696
  const template = await client.templates.get();
41412
41697
  output(template);
41413
41698
  });
41414
- templates.command("mark").description("Mark or update the active site as a template").requiredOption("--name <name>", "Template display name").option("--description <description>", "Template description, up to 150 characters").requiredOption("--industry <industry>", "Template industry key").requiredOption(
41699
+ templates.command("mark").description("Mark or update the active site as a template").addHelpText("after", `
41700
+ Examples:
41701
+ $ neopress templates mark --name "SaaS Landing" --industry saas --reference-url https://example.com --preview-img-url https://cdn.example.com/1.png
41702
+ $ neopress templates mark --name Portfolio --industry creative --reference-url https://example.com --preview-img-url https://cdn.example.com/a.png --preview-img-url https://cdn.example.com/b.png --description "Minimal portfolio"
41703
+ --name, --industry, and --reference-url are all required, and at least one --preview-img-url must be given (repeat the flag for multiple images). Upserts the template metadata for the active site and sets the site status to active; running it again updates the existing template.`).requiredOption("--name <name>", "Template display name").option("--description <description>", "Template description, up to 150 characters").requiredOption("--industry <industry>", "Template industry key").requiredOption(
41415
41704
  "--reference-url <url>",
41416
41705
  "Reference URL for template provenance"
41417
41706
  ).option(
@@ -41437,9 +41726,226 @@ function registerTemplateCommands(program3) {
41437
41726
  });
41438
41727
  }
41439
41728
 
41729
+ // src/commands/headshots.ts
41730
+ var AGE_BANDS = ["young", "middle"];
41731
+ var GENDERS = ["male", "female"];
41732
+ var ETHNICITIES = ["asian", "white", "black", "latino"];
41733
+ var ATTIRES = ["formal"];
41734
+ function validateEnum(value, allowed, flag) {
41735
+ if (value !== void 0 && !allowed.includes(value)) {
41736
+ fail(`${flag} must be one of: ${allowed.join(", ")}`);
41737
+ }
41738
+ }
41739
+ function parseNonNegativeInt(raw, flag) {
41740
+ if (raw === void 0) return void 0;
41741
+ const value = Number(raw);
41742
+ if (!Number.isInteger(value) || value < 0) {
41743
+ fail(`${flag} must be a non-negative integer`);
41744
+ }
41745
+ return value;
41746
+ }
41747
+ function registerHeadshotCommands(program3) {
41748
+ const headshots = program3.command("headshots").description("Manage the shared headshot pool (super-admin only)").addHelpText("after", `
41749
+ Examples:
41750
+ $ neopress headshots list --gender female --ethnicity asian
41751
+ $ neopress headshots create --image-url https://cdn/x.webp --age-band young --gender male --ethnicity white --variation-index 0
41752
+ $ neopress headshots update 12 --inactive
41753
+ The pool is global and RLS-gated: only the super-admin account can read or write it. No active site is needed.`);
41754
+ headshots.command("list").description("List headshot pool entries").option("--age-band <band>", `Filter by age band (${AGE_BANDS.join("/")})`).option("--gender <gender>", `Filter by gender (${GENDERS.join("/")})`).option("--ethnicity <ethnicity>", `Filter by ethnicity (${ETHNICITIES.join("/")})`).option("--attire <attire>", `Filter by attire (${ATTIRES.join("/")})`).option("--active", "Only active entries").option("--inactive", "Only inactive entries").option("--page <n>", "Page number", "1").option("--limit <n>", "Results per page", "20").addHelpText("after", `
41755
+ Examples:
41756
+ $ neopress headshots list
41757
+ $ neopress headshots list --gender female --age-band middle --active
41758
+ Filters are equality matches on the pool's demographic columns; results are paginated.`).action(async (options) => {
41759
+ validateEnum(options.ageBand, AGE_BANDS, "--age-band");
41760
+ validateEnum(options.gender, GENDERS, "--gender");
41761
+ validateEnum(options.ethnicity, ETHNICITIES, "--ethnicity");
41762
+ validateEnum(options.attire, ATTIRES, "--attire");
41763
+ if (options.active && options.inactive) {
41764
+ fail("Use only one of --active or --inactive.");
41765
+ }
41766
+ const params = {
41767
+ page: parseInt(options.page, 10),
41768
+ limit: parseInt(options.limit, 10),
41769
+ ageBand: options.ageBand,
41770
+ gender: options.gender,
41771
+ ethnicity: options.ethnicity,
41772
+ attire: options.attire
41773
+ };
41774
+ if (options.active) params.isActive = true;
41775
+ else if (options.inactive) params.isActive = false;
41776
+ const client = await getGlobalClientAsync();
41777
+ const res = await client.headshots.list(params);
41778
+ output(res);
41779
+ });
41780
+ headshots.command("get <id>").description("Get a headshot pool entry").action(async (id) => {
41781
+ const client = await getGlobalClientAsync();
41782
+ const data = await client.headshots.get(parseInt(id, 10));
41783
+ output(data);
41784
+ });
41785
+ headshots.command("create").description("Add a headshot to the pool").requiredOption("--image-url <url>", "Public image URL").requiredOption("--age-band <band>", `Age band (${AGE_BANDS.join("/")})`).requiredOption("--gender <gender>", `Gender (${GENDERS.join("/")})`).requiredOption("--ethnicity <ethnicity>", `Ethnicity (${ETHNICITIES.join("/")})`).requiredOption("--variation-index <n>", "Variation index within the demographic cell (>= 0)").option("--attire <attire>", `Attire (${ATTIRES.join("/")}; default formal)`).option("--width <n>", "Image width in px").option("--height <n>", "Image height in px").option("--aspect-ratio <ratio>", "Aspect ratio, e.g. 2:3").option("--mime-type <type>", "MIME type, e.g. image/webp").option("--byte-size <n>", "File size in bytes").option("--gpt-prompt <prompt>", "Generation prompt (for provenance)").option("--tags <csv>", "Comma-separated tags").option("--inactive", "Create as inactive (default active)").addHelpText("after", `
41786
+ Examples:
41787
+ $ neopress headshots create --image-url https://cdn/a.webp --age-band young --gender female --ethnicity asian --variation-index 0
41788
+ $ neopress headshots create --image-url https://cdn/b.webp --age-band middle --gender male --ethnicity black --variation-index 1 --tags suit,studio
41789
+ (age-band, gender, ethnicity, attire, variation-index) must be unique. The DB rejects out-of-range enum values.`).action(async (options) => {
41790
+ validateEnum(options.ageBand, AGE_BANDS, "--age-band");
41791
+ validateEnum(options.gender, GENDERS, "--gender");
41792
+ validateEnum(options.ethnicity, ETHNICITIES, "--ethnicity");
41793
+ validateEnum(options.attire, ATTIRES, "--attire");
41794
+ const variationIndex = parseNonNegativeInt(options.variationIndex, "--variation-index");
41795
+ const params = {
41796
+ imageUrl: options.imageUrl,
41797
+ ageBand: options.ageBand,
41798
+ gender: options.gender,
41799
+ ethnicity: options.ethnicity,
41800
+ variationIndex
41801
+ };
41802
+ if (options.attire) params.attire = options.attire;
41803
+ const width = parseNonNegativeInt(options.width, "--width");
41804
+ if (width !== void 0) params.width = width;
41805
+ const height = parseNonNegativeInt(options.height, "--height");
41806
+ if (height !== void 0) params.height = height;
41807
+ if (options.aspectRatio) params.aspectRatio = options.aspectRatio;
41808
+ if (options.mimeType) params.mimeType = options.mimeType;
41809
+ const byteSize = parseNonNegativeInt(options.byteSize, "--byte-size");
41810
+ if (byteSize !== void 0) params.byteSize = byteSize;
41811
+ if (options.gptPrompt) params.gptPrompt = options.gptPrompt;
41812
+ if (options.tags) params.tags = String(options.tags).split(",").map((t) => t.trim()).filter(Boolean);
41813
+ if (options.inactive) params.isActive = false;
41814
+ const client = await getGlobalClientAsync();
41815
+ const data = await client.headshots.create(params);
41816
+ output(data);
41817
+ });
41818
+ headshots.command("update <id>").description("Update a headshot pool entry").option("--image-url <url>", "Public image URL").option("--age-band <band>", `Age band (${AGE_BANDS.join("/")})`).option("--gender <gender>", `Gender (${GENDERS.join("/")})`).option("--ethnicity <ethnicity>", `Ethnicity (${ETHNICITIES.join("/")})`).option("--attire <attire>", `Attire (${ATTIRES.join("/")})`).option("--variation-index <n>", "Variation index (>= 0)").option("--width <n>", "Image width in px").option("--height <n>", "Image height in px").option("--aspect-ratio <ratio>", "Aspect ratio, e.g. 2:3").option("--mime-type <type>", "MIME type, e.g. image/webp").option("--byte-size <n>", "File size in bytes").option("--gpt-prompt <prompt>", "Generation prompt").option("--tags <csv>", "Comma-separated tags (replaces existing)").option("--active", "Mark active").option("--inactive", "Mark inactive").addHelpText("after", `
41819
+ Examples:
41820
+ $ neopress headshots update 12 --inactive
41821
+ $ neopress headshots update 12 --image-url https://cdn/new.webp --tags suit,studio
41822
+ Pass at least one field. --active/--inactive toggle is_active; --tags replaces the tag list.`).action(async (id, options) => {
41823
+ validateEnum(options.ageBand, AGE_BANDS, "--age-band");
41824
+ validateEnum(options.gender, GENDERS, "--gender");
41825
+ validateEnum(options.ethnicity, ETHNICITIES, "--ethnicity");
41826
+ validateEnum(options.attire, ATTIRES, "--attire");
41827
+ if (options.active && options.inactive) {
41828
+ fail("Use only one of --active or --inactive.");
41829
+ }
41830
+ const params = {};
41831
+ if (options.imageUrl) params.imageUrl = options.imageUrl;
41832
+ if (options.ageBand) params.ageBand = options.ageBand;
41833
+ if (options.gender) params.gender = options.gender;
41834
+ if (options.ethnicity) params.ethnicity = options.ethnicity;
41835
+ if (options.attire) params.attire = options.attire;
41836
+ const variationIndex = parseNonNegativeInt(options.variationIndex, "--variation-index");
41837
+ if (variationIndex !== void 0) params.variationIndex = variationIndex;
41838
+ const width = parseNonNegativeInt(options.width, "--width");
41839
+ if (width !== void 0) params.width = width;
41840
+ const height = parseNonNegativeInt(options.height, "--height");
41841
+ if (height !== void 0) params.height = height;
41842
+ if (options.aspectRatio) params.aspectRatio = options.aspectRatio;
41843
+ if (options.mimeType) params.mimeType = options.mimeType;
41844
+ const byteSize = parseNonNegativeInt(options.byteSize, "--byte-size");
41845
+ if (byteSize !== void 0) params.byteSize = byteSize;
41846
+ if (options.gptPrompt) params.gptPrompt = options.gptPrompt;
41847
+ if (options.tags) params.tags = String(options.tags).split(",").map((t) => t.trim()).filter(Boolean);
41848
+ if (options.active) params.isActive = true;
41849
+ else if (options.inactive) params.isActive = false;
41850
+ if (Object.keys(params).length === 0) {
41851
+ fail("No fields to update.");
41852
+ }
41853
+ const client = await getGlobalClientAsync();
41854
+ const data = await client.headshots.update(parseInt(id, 10), params);
41855
+ output(data);
41856
+ });
41857
+ headshots.command("delete <id>").description("Delete a headshot pool entry (hard delete)").action(async (id) => {
41858
+ const client = await getGlobalClientAsync();
41859
+ await client.headshots.delete(parseInt(id, 10));
41860
+ ok(`Headshot ${id} deleted`);
41861
+ });
41862
+ }
41863
+
41864
+ // src/commands/guide.ts
41865
+ function describeOption(opt) {
41866
+ const info = { flags: opt.flags, description: opt.description || "" };
41867
+ if (opt.defaultValue !== void 0) info.default = opt.defaultValue;
41868
+ return info;
41869
+ }
41870
+ function describeArgument(arg) {
41871
+ return {
41872
+ name: arg.name(),
41873
+ required: arg.required,
41874
+ variadic: arg.variadic,
41875
+ description: arg.description || ""
41876
+ };
41877
+ }
41878
+ function describeCommand(cmd, prefix) {
41879
+ const path = `${prefix} ${cmd.name()}`;
41880
+ return {
41881
+ path,
41882
+ aliases: cmd.aliases(),
41883
+ description: cmd.description(),
41884
+ arguments: cmd.registeredArguments.map(describeArgument),
41885
+ options: cmd.options.map(describeOption),
41886
+ subcommands: cmd.commands.filter((c) => c.name() !== "help").map((c) => describeCommand(c, path))
41887
+ };
41888
+ }
41889
+ function buildGuide(program3) {
41890
+ return {
41891
+ name: program3.name(),
41892
+ version: program3.version() ?? "",
41893
+ description: program3.description(),
41894
+ globalOptions: program3.options.map(describeOption),
41895
+ commands: program3.commands.filter((c) => c.name() !== "help").map((c) => describeCommand(c, program3.name()))
41896
+ };
41897
+ }
41898
+ function pad(s, width) {
41899
+ return s.length >= width ? `${s} ` : s + " ".repeat(width - s.length);
41900
+ }
41901
+ function argSignature(args) {
41902
+ return args.map((a) => {
41903
+ const inner = a.variadic ? `${a.name}...` : a.name;
41904
+ return a.required ? `<${inner}>` : `[${inner}]`;
41905
+ }).join(" ");
41906
+ }
41907
+ function printCommandNode(node, lines, depth) {
41908
+ const indent = " ".repeat(depth * 2);
41909
+ const sig = argSignature(node.arguments);
41910
+ const head2 = `${indent}${node.path}${sig ? ` ${sig}` : ""}`;
41911
+ lines.push(`${pad(head2, 46)}${node.description}`);
41912
+ if (node.subcommands.length === 0) {
41913
+ for (const o of node.options) {
41914
+ const def = o.default !== void 0 ? ` (default: ${JSON.stringify(o.default)})` : "";
41915
+ lines.push(`${pad(`${indent} ${o.flags}`, 46)}${o.description}${def}`);
41916
+ }
41917
+ }
41918
+ for (const sub of node.subcommands) printCommandNode(sub, lines, depth + 1);
41919
+ }
41920
+ function printHuman(guide) {
41921
+ const lines = [];
41922
+ lines.push(`${guide.name} ${guide.version} \u2014 ${guide.description}`);
41923
+ lines.push("");
41924
+ if (guide.globalOptions.length) {
41925
+ lines.push("GLOBAL OPTIONS");
41926
+ for (const o of guide.globalOptions) lines.push(`${pad(` ${o.flags}`, 46)}${o.description}`);
41927
+ lines.push("");
41928
+ }
41929
+ lines.push("COMMANDS");
41930
+ for (const c of guide.commands) printCommandNode(c, lines, 1);
41931
+ lines.push("");
41932
+ lines.push("Run `neopress <command> --help` for details, or `neopress guide --json` for the full surface.");
41933
+ console.log(lines.join("\n"));
41934
+ }
41935
+ function registerGuideCommands(program3) {
41936
+ program3.command("guide").description("Print the full command reference (human tree, or --json for AI tools)").option("--json", "Output the command surface as JSON").action(() => {
41937
+ const guide = buildGuide(program3);
41938
+ if (isJsonMode()) {
41939
+ console.log(JSON.stringify(guide, null, 2));
41940
+ } else {
41941
+ printHuman(guide);
41942
+ }
41943
+ });
41944
+ }
41945
+
41440
41946
  // src/bin.ts
41441
41947
  var program2 = new Command();
41442
- program2.name("neopress").description("Neopress CLI \u2014 manage your site from the terminal").version("4.0.0").option("--json", "Output in JSON format (for AI tools and pipes)").option("--site <idOrHandle>", "Target a specific site (overrides active site; not persisted)").option("--no-input", "Non-interactive mode").hook("preAction", (thisCommand) => {
41948
+ program2.name("neopress").description("Neopress CLI \u2014 manage your site from the terminal").version("4.1.0").option("--json", "Output in JSON format (for AI tools and pipes)").option("--site <idOrHandle>", "Target a specific site (overrides active site; not persisted)").option("--no-input", "Non-interactive mode").hook("preAction", (thisCommand) => {
41443
41949
  const opts = thisCommand.optsWithGlobals();
41444
41950
  if (opts.site) setSiteOverride(String(opts.site));
41445
41951
  if (opts.json || !process.stdout.isTTY) setJsonMode(true);
@@ -41457,6 +41963,28 @@ registerLayoutCommands(program2);
41457
41963
  registerEntryReferenceCommands(program2);
41458
41964
  registerAnalyticsCommands(program2);
41459
41965
  registerTemplateCommands(program2);
41966
+ registerHeadshotCommands(program2);
41967
+ registerGuideCommands(program2);
41968
+ program2.addHelpText(
41969
+ "after",
41970
+ `
41971
+ Quickstart:
41972
+ $ neopress login Authenticate (or set NEOPRESS_ACCESS_TOKEN)
41973
+ $ neopress sites list See sites you can access
41974
+ $ neopress sites use <id|handle> Pick the active site
41975
+ $ neopress pages list List pages on the active site
41976
+ $ neopress guide Full command reference (add --json for AI tools)
41977
+
41978
+ AI / scripting:
41979
+ Output is JSON automatically when piped or redirected; force it with --json.
41980
+ Run \`neopress guide --json\` to get the entire command surface in one call.
41981
+
41982
+ Env vars:
41983
+ NEOPRESS_ACCESS_TOKEN Auth token (skips \`neopress login\`)
41984
+ NEOPRESS_SITE_ID Default site id (overrides the active site)
41985
+ NEOPRESS_BASE_URL API base URL (defaults to the hosted Neopress app)
41986
+ `
41987
+ );
41460
41988
  program2.parseAsync(process.argv).catch((err) => {
41461
41989
  if (err?.code) {
41462
41990
  console.error(JSON.stringify({ ok: false, error: err.message, code: err.code }));
package/dist/index.js CHANGED
@@ -21146,6 +21146,27 @@ function serializeTemplate(t) {
21146
21146
  createdAt: t.created_at
21147
21147
  };
21148
21148
  }
21149
+ function serializeHeadshot(h) {
21150
+ return {
21151
+ id: h.id,
21152
+ imageUrl: h.image_url,
21153
+ width: h.width,
21154
+ height: h.height,
21155
+ aspectRatio: h.aspect_ratio,
21156
+ mimeType: h.mime_type,
21157
+ byteSize: h.byte_size,
21158
+ ageBand: h.age_band,
21159
+ gender: h.gender,
21160
+ ethnicity: h.ethnicity,
21161
+ attire: h.attire,
21162
+ variationIndex: h.variation_index,
21163
+ gptPrompt: h.gpt_prompt,
21164
+ tags: h.tags ?? [],
21165
+ isActive: h.is_active,
21166
+ createdAt: h.created_at,
21167
+ updatedAt: h.updated_at
21168
+ };
21169
+ }
21149
21170
  function serializeSite(site) {
21150
21171
  return {
21151
21172
  id: site.id,
@@ -36828,6 +36849,113 @@ var TemplatesEndpoint = class {
36828
36849
  }
36829
36850
  };
36830
36851
 
36852
+ // ../sdk/dist/endpoints/headshots.js
36853
+ var HeadshotsEndpoint = class {
36854
+ client;
36855
+ constructor(client) {
36856
+ this.client = client;
36857
+ }
36858
+ async list(params) {
36859
+ const { page, limit, offset } = this.client.resolvePagination(params);
36860
+ let query = this.client.db.from("headshot_pool").select("*", { count: "exact" });
36861
+ if (params?.ageBand)
36862
+ query = query.eq("age_band", params.ageBand);
36863
+ if (params?.gender)
36864
+ query = query.eq("gender", params.gender);
36865
+ if (params?.ethnicity)
36866
+ query = query.eq("ethnicity", params.ethnicity);
36867
+ if (params?.attire)
36868
+ query = query.eq("attire", params.attire);
36869
+ if (params?.isActive !== void 0)
36870
+ query = query.eq("is_active", params.isActive);
36871
+ const { data, error: error48, count } = await query.order("age_band", { ascending: true }).order("gender", { ascending: true }).order("ethnicity", { ascending: true }).order("variation_index", { ascending: true }).range(offset, offset + limit - 1);
36872
+ if (error48)
36873
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
36874
+ return {
36875
+ data: (data ?? []).map((r) => serializeHeadshot(r)),
36876
+ pagination: this.client.paginationMeta(page, limit, offset, count ?? 0)
36877
+ };
36878
+ }
36879
+ async get(id) {
36880
+ const { data, error: error48 } = await this.client.db.from("headshot_pool").select("*").eq("id", id).single();
36881
+ if (error48)
36882
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
36883
+ return serializeHeadshot(data);
36884
+ }
36885
+ async create(params) {
36886
+ const values = {
36887
+ image_url: params.imageUrl,
36888
+ age_band: params.ageBand,
36889
+ gender: params.gender,
36890
+ ethnicity: params.ethnicity,
36891
+ variation_index: params.variationIndex
36892
+ };
36893
+ if (params.attire !== void 0)
36894
+ values.attire = params.attire;
36895
+ if (params.width !== void 0)
36896
+ values.width = params.width;
36897
+ if (params.height !== void 0)
36898
+ values.height = params.height;
36899
+ if (params.aspectRatio !== void 0)
36900
+ values.aspect_ratio = params.aspectRatio;
36901
+ if (params.mimeType !== void 0)
36902
+ values.mime_type = params.mimeType;
36903
+ if (params.byteSize !== void 0)
36904
+ values.byte_size = params.byteSize;
36905
+ if (params.gptPrompt !== void 0)
36906
+ values.gpt_prompt = params.gptPrompt;
36907
+ if (params.tags !== void 0)
36908
+ values.tags = params.tags;
36909
+ if (params.isActive !== void 0)
36910
+ values.is_active = params.isActive;
36911
+ const { data, error: error48 } = await this.client.db.from("headshot_pool").insert(values).select().single();
36912
+ if (error48)
36913
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
36914
+ return serializeHeadshot(data);
36915
+ }
36916
+ async update(id, params) {
36917
+ const values = {};
36918
+ if (params.imageUrl !== void 0)
36919
+ values.image_url = params.imageUrl;
36920
+ if (params.ageBand !== void 0)
36921
+ values.age_band = params.ageBand;
36922
+ if (params.gender !== void 0)
36923
+ values.gender = params.gender;
36924
+ if (params.ethnicity !== void 0)
36925
+ values.ethnicity = params.ethnicity;
36926
+ if (params.attire !== void 0)
36927
+ values.attire = params.attire;
36928
+ if (params.variationIndex !== void 0)
36929
+ values.variation_index = params.variationIndex;
36930
+ if (params.width !== void 0)
36931
+ values.width = params.width;
36932
+ if (params.height !== void 0)
36933
+ values.height = params.height;
36934
+ if (params.aspectRatio !== void 0)
36935
+ values.aspect_ratio = params.aspectRatio;
36936
+ if (params.mimeType !== void 0)
36937
+ values.mime_type = params.mimeType;
36938
+ if (params.byteSize !== void 0)
36939
+ values.byte_size = params.byteSize;
36940
+ if (params.gptPrompt !== void 0)
36941
+ values.gpt_prompt = params.gptPrompt;
36942
+ if (params.tags !== void 0)
36943
+ values.tags = params.tags;
36944
+ if (params.isActive !== void 0)
36945
+ values.is_active = params.isActive;
36946
+ const { data, error: error48 } = await this.client.db.from("headshot_pool").update(values).eq("id", id).select().single();
36947
+ if (error48)
36948
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
36949
+ return serializeHeadshot(data);
36950
+ }
36951
+ async delete(id) {
36952
+ const { error: error48 } = await this.client.db.from("headshot_pool").delete().eq("id", id);
36953
+ if (error48)
36954
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
36955
+ return { id, deleted: true };
36956
+ }
36957
+ };
36958
+
36831
36959
  // ../sdk/dist/client.js
36832
36960
  var NeopressClient = class {
36833
36961
  baseUrl;
@@ -36848,6 +36976,7 @@ var NeopressClient = class {
36848
36976
  entryReferences;
36849
36977
  analytics;
36850
36978
  templates;
36979
+ headshots;
36851
36980
  constructor(options) {
36852
36981
  this.baseUrl = (options.baseUrl || "https://app.neopress.ai").replace(/\/$/, "");
36853
36982
  this.accessToken = options.accessToken || "";
@@ -36866,6 +36995,7 @@ var NeopressClient = class {
36866
36995
  this.entryReferences = new EntryReferencesEndpoint(this);
36867
36996
  this.analytics = new AnalyticsEndpoint(this);
36868
36997
  this.templates = new TemplatesEndpoint(this);
36998
+ this.headshots = new HeadshotsEndpoint(this);
36869
36999
  }
36870
37000
  get siteBasePath() {
36871
37001
  return `/api/v1/sites/${this.siteId}`;
package/package.json CHANGED
@@ -1,15 +1,25 @@
1
1
  {
2
2
  "name": "@neopress/cli",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "Neopress CLI — manage your Neopress site from the terminal",
5
5
  "type": "module",
6
+ "keywords": [
7
+ "neopress",
8
+ "cli",
9
+ "cms",
10
+ "headless-cms",
11
+ "website-builder",
12
+ "content-management",
13
+ "ai-agent"
14
+ ],
6
15
  "bin": {
7
16
  "neopress": "./dist/bin.cjs"
8
17
  },
9
18
  "main": "./dist/index.js",
10
19
  "types": "./dist/index.d.ts",
11
20
  "files": [
12
- "dist"
21
+ "dist",
22
+ "README.md"
13
23
  ],
14
24
  "publishConfig": {
15
25
  "access": "public"