@neopress/cli 4.9.0 → 4.10.1

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/index.d.ts CHANGED
@@ -19,7 +19,7 @@ interface PaginatedResponse<T> {
19
19
  pagination: PaginationMeta;
20
20
  }
21
21
  /** Sort direction for list endpoints that support opt-in created_at ordering. */
22
- type SortOrder = 'asc' | 'desc';
22
+ type SortOrder = "asc" | "desc";
23
23
  interface Site {
24
24
  id: number;
25
25
  name: string | null;
@@ -32,7 +32,7 @@ interface Site {
32
32
  localeConfig: {
33
33
  defaultLocale: string;
34
34
  locales: string[];
35
- localePrefix?: 'as-needed';
35
+ localePrefix?: "as-needed";
36
36
  } | null;
37
37
  createdAt: string | null;
38
38
  updatedAt: string | null;
@@ -169,7 +169,7 @@ interface RedirectRule {
169
169
  siteId: number;
170
170
  source: string;
171
171
  destination: string;
172
- mode: 'redirect' | 'proxy';
172
+ mode: "redirect" | "proxy";
173
173
  redirectType: number;
174
174
  includeChildren: boolean;
175
175
  preservePath: boolean;
@@ -191,11 +191,13 @@ interface SiteLayout {
191
191
  draftTsx: string | null;
192
192
  draftGlobalCss: string | null;
193
193
  draftNotFoundTsx: string | null;
194
+ draftI18n: unknown;
194
195
  prodTsx: string | null;
195
196
  prodJs: string | null;
196
197
  prodGlobalCss: string | null;
197
198
  prodNotFoundTsx: string | null;
198
199
  prodNotFoundJs: string | null;
200
+ prodI18n: unknown;
199
201
  }
200
202
  interface Headshot {
201
203
  id: number;
@@ -255,7 +257,7 @@ interface AnalyticsDateRangeParams {
255
257
  limit?: number;
256
258
  params?: Record<string, AnalyticsQueryValue>;
257
259
  }
258
- type SearchConsoleDimension = 'query' | 'page' | 'country' | 'device';
260
+ type SearchConsoleDimension = "query" | "page" | "country" | "device";
259
261
  interface SearchConsoleQueryParams {
260
262
  dateFrom: string;
261
263
  dateTo: string;
@@ -282,12 +284,111 @@ interface SearchConsoleQueryResult {
282
284
  };
283
285
  error?: string;
284
286
  }
287
+ type AnalyticsEventType = "custom" | "page" | "click" | "form" | "scroll" | "visibility" | "dwell" | "outbound";
288
+ /** `visibility` remains readable for historical definitions but cannot be created. */
289
+ type AnalyticsEventCreateType = Exclude<AnalyticsEventType, "visibility">;
290
+ type FunnelFilterField = "event.path" | "event.url" | "event.referrer" | "event.referrer_source" | "event.property" | "session.referrer" | "session.referrer_source" | "session.traffic_category" | "session.utm_source" | "session.utm_medium" | "session.utm_campaign" | "session.utm_content" | "session.utm_term";
291
+ type FunnelFilterOperator = "eq" | "neq" | "contains" | "not_contains" | "starts_with" | "ends_with" | "exists" | "not_exists" | "gt" | "gte" | "lt" | "lte";
292
+ type AnalyticsFunnelFilterCondition = {
293
+ kind: "condition";
294
+ field: FunnelFilterField;
295
+ operator: FunnelFilterOperator;
296
+ key?: string;
297
+ value?: string | number;
298
+ };
299
+ type AnalyticsFunnelFilterGroup = {
300
+ kind: "group";
301
+ op: "and" | "or";
302
+ children: AnalyticsFunnelFilterNode[];
303
+ };
304
+ type AnalyticsFunnelFilterNode = AnalyticsFunnelFilterGroup | AnalyticsFunnelFilterCondition;
305
+ type AnalyticsFunnelFilter = {
306
+ version: 1;
307
+ root: AnalyticsFunnelFilterGroup;
308
+ };
309
+ interface AnalyticsEventDefinition {
310
+ id: string;
311
+ chart_color: string | null;
312
+ name: string;
313
+ chart_visible: boolean;
314
+ is_pinned: boolean;
315
+ sort_order?: number | null;
316
+ event_type: AnalyticsEventType;
317
+ event_name: string;
318
+ path: string;
319
+ selector: string;
320
+ form_id: string;
321
+ scroll_depth: number;
322
+ dwell_seconds: number;
323
+ tracking_version: number;
324
+ created_at: string;
325
+ updated_at: string;
326
+ }
327
+ interface AnalyticsEventDefinitionInput {
328
+ /** Optional client UUID; reuse it with the same payload after an uncertain create response. */
329
+ id?: string;
330
+ name: string;
331
+ eventType: AnalyticsEventCreateType;
332
+ eventName?: string;
333
+ path?: string;
334
+ selector?: string;
335
+ formId?: string;
336
+ scrollDepth?: number;
337
+ dwellSeconds?: number;
338
+ }
339
+ type AnalyticsEventDefinitionUpdate = AnalyticsEventDefinitionInput | {
340
+ name: string;
341
+ };
342
+ interface AnalyticsEventDisplayUpdate {
343
+ chartVisible?: boolean;
344
+ pinned?: boolean;
345
+ chartColor?: string;
346
+ }
347
+ interface AnalyticsFunnelStepInput {
348
+ eventId: string;
349
+ path?: string;
350
+ filter?: AnalyticsFunnelFilter;
351
+ }
352
+ interface AnalyticsFunnelStep {
353
+ event_id: string;
354
+ path?: string;
355
+ filter?: AnalyticsFunnelFilter;
356
+ }
357
+ interface AnalyticsFunnelDefinition {
358
+ invalid?: true;
359
+ id: string;
360
+ name: string;
361
+ steps: AnalyticsFunnelStep[];
362
+ session_only: boolean;
363
+ window_minutes: number;
364
+ created_at: string;
365
+ updated_at: string;
366
+ }
367
+ interface AnalyticsTargets {
368
+ pages: Array<{
369
+ id: number;
370
+ name: string;
371
+ path: string;
372
+ locale: string;
373
+ }>;
374
+ forms: Array<{
375
+ id: number;
376
+ name: string;
377
+ }>;
378
+ }
379
+ interface AnalyticsFunnelDefinitionInput {
380
+ id?: string;
381
+ name: string;
382
+ steps: AnalyticsFunnelStepInput[];
383
+ windowMode: "session" | "time";
384
+ windowHours?: number;
385
+ }
285
386
  //#endregion
286
387
  //#region ../sdk/src/endpoints/pages.d.ts
287
388
  interface PageListParams {
288
389
  page?: number;
289
390
  limit?: number;
290
- status?: 'draft' | 'published';
391
+ status?: "draft" | "published";
291
392
  fields?: string[];
292
393
  /** When set, sort by created_at instead of path. */
293
394
  order?: SortOrder;
@@ -295,9 +396,9 @@ interface PageListParams {
295
396
  interface PageCreateParams {
296
397
  path: string;
297
398
  title?: string;
298
- type?: 'static' | 'dynamic';
299
- status?: 'draft' | 'published';
300
- visibility?: 'public' | 'hidden';
399
+ type?: "static" | "dynamic";
400
+ status?: "draft" | "published";
401
+ visibility?: "public" | "hidden";
301
402
  draftTsx?: string;
302
403
  draftI18n?: unknown;
303
404
  draftPublicLocales?: string[] | null;
@@ -310,7 +411,7 @@ interface PageUpdateParams {
310
411
  title?: string;
311
412
  type?: string;
312
413
  status?: string;
313
- visibility?: 'public' | 'hidden';
414
+ visibility?: string;
314
415
  draftTsx?: string;
315
416
  draftI18n?: unknown;
316
417
  draftPublicLocales?: string[] | null;
@@ -347,6 +448,12 @@ declare class CollectionsEndpoint {
347
448
  order?: SortOrder;
348
449
  }): Promise<Collection[]>;
349
450
  get(collectionId: number): Promise<Collection>;
451
+ /**
452
+ * Preset normalization (`normalizeCollectionPresetType` +
453
+ * `ensureCollectionPresetBaselineFields`) now runs server-side in
454
+ * `CollectionsApi.create`, which applies the identical pair in the identical
455
+ * order — the stored row is unchanged by the move to HTTP.
456
+ */
350
457
  create(data: CollectionCreateParams): Promise<Collection>;
351
458
  update(collectionId: number, data: Partial<CollectionCreateParams>): Promise<Collection>;
352
459
  delete(collectionId: number): Promise<void>;
@@ -355,10 +462,6 @@ declare class CollectionsEndpoint {
355
462
  draftSchema: unknown;
356
463
  prodSchema: unknown;
357
464
  }>;
358
- /**
359
- * Updates the collection's draft_schema directly in Supabase.
360
- * validateApiCollectionSchema (app-local) is skipped — CLI/MCP callers are trusted.
361
- */
362
465
  updateSchema(collectionId: number, schema: unknown): Promise<{
363
466
  collectionId: number;
364
467
  draftSchema: unknown;
@@ -370,7 +473,7 @@ declare class CollectionsEndpoint {
370
473
  interface EntryListParams {
371
474
  page?: number;
372
475
  limit?: number;
373
- status?: 'draft' | 'published' | 'archived';
476
+ status?: "draft" | "published" | "archived";
374
477
  locale?: string;
375
478
  /** created_at sort direction. Defaults to 'desc' (newest first). */
376
479
  order?: SortOrder;
@@ -394,9 +497,12 @@ interface EntryUpdateParams {
394
497
  declare class EntriesEndpoint {
395
498
  private readonly client;
396
499
  constructor(client: NeopressClient);
500
+ private get basePath();
397
501
  list(collectionId: number, params?: EntryListParams): Promise<PaginatedResponse<Entry>>;
398
502
  get(entryId: number): Promise<Entry>;
399
503
  create(collectionId: number, data: EntryCreateParams): Promise<Entry>;
504
+ /** Copy source content into a linked locale draft, or return its existing translation. */
505
+ createTranslation(entryId: number, locale: string): Promise<Entry>;
400
506
  update(entryId: number, data: EntryUpdateParams): Promise<Entry>;
401
507
  delete(entryId: number): Promise<void>;
402
508
  revalidateReaderCache(entryId: number): Promise<void>;
@@ -484,7 +590,12 @@ declare class AssetsEndpoint {
484
590
  }): Promise<PaginatedResponse<Asset>>;
485
591
  get(assetId: number): Promise<Asset>;
486
592
  register(data: AssetRegisterParams): Promise<Asset>;
487
- update(assetId: number, data: Partial<Omit<AssetRegisterParams, 'assetUrl'>>): Promise<Asset>;
593
+ update(assetId: number, data: Partial<Omit<AssetRegisterParams, "assetUrl">>): Promise<Asset>;
594
+ /**
595
+ * Hard delete. The route additionally requires the caller to be the site
596
+ * owner, matching `assets_delete_policy` = `is_site_owner(site_id)` — the
597
+ * registry axes alone would have widened this (W7 mapping §3-B1).
598
+ */
488
599
  delete(assetId: number): Promise<void>;
489
600
  presign(data: AssetPresignParams): Promise<{
490
601
  presignedUrl: string;
@@ -495,6 +606,12 @@ declare class AssetsEndpoint {
495
606
  //#endregion
496
607
  //#region ../sdk/src/endpoints/site.d.ts
497
608
  interface SiteWithRole extends Site {
609
+ /** Project role of the caller on this site: "owner" | "admin" | "member". */
610
+ projectRole: string;
611
+ /**
612
+ * @deprecated Legacy site role derived from `projectRole` for backwards
613
+ * compatibility ("owner" | "admin" | "member"). Use `projectRole` instead.
614
+ */
498
615
  role: string;
499
616
  }
500
617
  interface SiteCreateParams {
@@ -502,18 +619,25 @@ interface SiteCreateParams {
502
619
  siteHandle?: string;
503
620
  prompt?: string;
504
621
  language?: string;
505
- placeholder?: boolean;
506
622
  }
507
623
  declare class SiteEndpoint {
508
624
  private readonly client;
509
625
  constructor(client: NeopressClient);
626
+ /**
627
+ * Global (not site-scoped): lists the caller's own memberships, so it does not
628
+ * hang off `siteBasePath`. The membership + soft-delete filters that RLS used
629
+ * to apply now live in the route.
630
+ */
510
631
  list(params?: {
511
632
  order?: SortOrder;
512
633
  }): Promise<SiteWithRole[]>;
513
634
  get(): Promise<Site>;
514
635
  create(data: SiteCreateParams): Promise<Site>;
515
- update(data: Partial<Pick<Site, 'name' | 'metadata' | 'localeConfig'>> & {
516
- globalCss?: string;
636
+ /**
637
+ * Site settings only. The stylesheet is not here — it lives on the layout
638
+ * (`updateLayout({ draftGlobalCss })`), which is what the reader renders.
639
+ */
640
+ update(data: Partial<Pick<Site, "name" | "metadata" | "localeConfig">> & {
517
641
  instructions?: unknown;
518
642
  }): Promise<Site>;
519
643
  getLayout(): Promise<SiteLayout>;
@@ -521,6 +645,8 @@ declare class SiteEndpoint {
521
645
  draftTsx?: string;
522
646
  draftNotFoundTsx?: string;
523
647
  draftGlobalCss?: string;
648
+ /** Replaces draft translations; null clears them, omission preserves them. */
649
+ draftI18n?: unknown;
524
650
  }): Promise<SiteLayout>;
525
651
  delete(): Promise<{
526
652
  siteId: number;
@@ -541,11 +667,17 @@ interface RedirectCreateParams {
541
667
  declare class RedirectsEndpoint {
542
668
  private readonly client;
543
669
  constructor(client: NeopressClient);
670
+ private get basePath();
544
671
  list(params?: {
545
672
  order?: SortOrder;
546
673
  }): Promise<RedirectRule[]>;
547
674
  create(data: RedirectCreateParams): Promise<RedirectRule>;
548
- private assertRedirectRuleWritable;
675
+ /**
676
+ * The proxy preflight that used to run here (`assertRedirectRuleWritable`)
677
+ * needs a DB read, so it moved into the route. Its two error codes —
678
+ * `REDIRECT_RULE_NOT_FOUND` and `REDIRECT_RULE_DASHBOARD_ONLY` — are returned
679
+ * by the server verbatim, so `NeopressApiError.code` is unchanged for callers.
680
+ */
549
681
  update(ruleId: number, data: Partial<RedirectCreateParams>): Promise<RedirectRule>;
550
682
  delete(ruleId: number): Promise<void>;
551
683
  }
@@ -573,6 +705,7 @@ interface EntryReferenceCreateParams {
573
705
  declare class EntryReferencesEndpoint {
574
706
  private readonly client;
575
707
  constructor(client: NeopressClient);
708
+ private get basePath();
576
709
  list(params?: EntryReferenceListParams): Promise<EntryReference[]>;
577
710
  create(data: EntryReferenceCreateParams): Promise<EntryReference>;
578
711
  delete(referenceId: number): Promise<void>;
@@ -581,6 +714,8 @@ declare class EntryReferencesEndpoint {
581
714
  //#region ../sdk/src/endpoints/analytics.d.ts
582
715
  declare class AnalyticsEndpoint {
583
716
  private readonly client;
717
+ readonly events: AnalyticsEventsEndpoint;
718
+ readonly funnels: AnalyticsFunnelsEndpoint;
584
719
  constructor(client: NeopressClient);
585
720
  private get basePath();
586
721
  query(params: AnalyticsQueryParams): Promise<AnalyticsQueryResult>;
@@ -591,6 +726,30 @@ declare class AnalyticsEndpoint {
591
726
  formAnalytics(params?: AnalyticsDateRangeParams): Promise<AnalyticsQueryResult>;
592
727
  crawlerSummary(params?: AnalyticsDateRangeParams): Promise<AnalyticsQueryResult>;
593
728
  searchConsole(params: SearchConsoleQueryParams): Promise<SearchConsoleQueryResult>;
729
+ /** Page/form targets that can be referenced by an event definition. */
730
+ targets(): Promise<AnalyticsTargets>;
731
+ }
732
+ declare class AnalyticsEventsEndpoint {
733
+ private readonly client;
734
+ constructor(client: NeopressClient);
735
+ private get basePath();
736
+ list(): Promise<AnalyticsEventDefinition[]>;
737
+ get(eventId: string): Promise<AnalyticsEventDefinition>;
738
+ create(data: AnalyticsEventDefinitionInput): Promise<AnalyticsEventDefinition>;
739
+ update(eventId: string, data: AnalyticsEventDefinitionUpdate | {
740
+ display: AnalyticsEventDisplayUpdate;
741
+ }): Promise<AnalyticsEventDefinition>;
742
+ delete(eventId: string): Promise<void>;
743
+ }
744
+ declare class AnalyticsFunnelsEndpoint {
745
+ private readonly client;
746
+ constructor(client: NeopressClient);
747
+ private get basePath();
748
+ list(): Promise<AnalyticsFunnelDefinition[]>;
749
+ get(funnelId: string): Promise<AnalyticsFunnelDefinition>;
750
+ create(data: AnalyticsFunnelDefinitionInput): Promise<AnalyticsFunnelDefinition>;
751
+ update(funnelId: string, data: AnalyticsFunnelDefinitionInput): Promise<AnalyticsFunnelDefinition>;
752
+ delete(funnelId: string): Promise<void>;
594
753
  }
595
754
  //#endregion
596
755
  //#region ../sdk/src/endpoints/templates.d.ts
@@ -623,9 +782,8 @@ declare class TemplatesEndpoint {
623
782
  /**
624
783
  * List templates across the sites the caller can access.
625
784
  *
626
- * Direct-DB: `templates` is public-read and `sites` RLS scopes the visible set
627
- * (own active memberships, or all sites for the super admin), so no server
628
- * route is needed — the two RLS-scoped queries reproduce the old handler.
785
+ * Global (not site-scoped) — the accessible-site set is resolved server-side,
786
+ * where RLS used to do it.
629
787
  */
630
788
  list(params?: {
631
789
  namePrefix?: string;
@@ -635,10 +793,10 @@ declare class TemplatesEndpoint {
635
793
  }
636
794
  //#endregion
637
795
  //#region ../sdk/src/endpoints/headshots.d.ts
638
- type HeadshotAgeBand = 'young' | 'middle';
639
- type HeadshotGender = 'male' | 'female';
640
- type HeadshotEthnicity = 'asian' | 'white' | 'black' | 'latino';
641
- type HeadshotAttire = 'formal' | 'casual';
796
+ type HeadshotAgeBand = "young" | "middle";
797
+ type HeadshotGender = "male" | "female";
798
+ type HeadshotEthnicity = "asian" | "white" | "black" | "latino";
799
+ type HeadshotAttire = "formal" | "casual";
642
800
  interface HeadshotListParams {
643
801
  page?: number;
644
802
  limit?: number;
@@ -687,9 +845,11 @@ interface HeadshotDeleteResult {
687
845
  deleted: true;
688
846
  }
689
847
  /**
690
- * headshot_pool is a GLOBAL (not site-scoped) admin table. RLS exposes it only to
691
- * the super-admin user, so these direct-DB calls return empty/forbidden for any
692
- * other account. No site id is used.
848
+ * `headshot_pool` is a GLOBAL (not site-scoped) admin table, so these calls run
849
+ * over `/api/v1/headshots` — no `siteId` in the path — instead of the
850
+ * site-scoped `withAuth` routes (W7 T5). The routes gate on the super-admin
851
+ * user id, which is the same check the table's single RLS policy made; a
852
+ * non-super-admin now gets 403 where the direct-DB read returned an empty list.
693
853
  */
694
854
  declare class HeadshotsEndpoint {
695
855
  private readonly client;
@@ -702,8 +862,8 @@ declare class HeadshotsEndpoint {
702
862
  }
703
863
  //#endregion
704
864
  //#region ../sdk/src/endpoints/logos.d.ts
705
- type LogoType = 'wordmark' | 'lockup' | 'signature';
706
- type LogoTone = 'playful' | 'neutral' | 'serious';
865
+ type LogoType = "wordmark" | "lockup" | "signature";
866
+ type LogoTone = "playful" | "neutral" | "serious";
707
867
  interface LogoListParams {
708
868
  page?: number;
709
869
  limit?: number;
@@ -758,9 +918,11 @@ interface LogoDeleteResult {
758
918
  deleted: true;
759
919
  }
760
920
  /**
761
- * logo_pool is a GLOBAL (not site-scoped) admin table. RLS exposes it only to the
762
- * super-admin user, so these direct-DB calls return empty/forbidden for any other
763
- * account. No site id is used.
921
+ * `logo_pool` is a GLOBAL (not site-scoped) admin table, so these calls run over
922
+ * `/api/v1/logos` — no `siteId` in the path — instead of the site-scoped
923
+ * `withAuth` routes (W7 T5). The routes gate on the super-admin user id, which
924
+ * is the same check the table's single RLS policy made; a non-super-admin now
925
+ * gets 403 where the direct-DB read returned an empty list.
764
926
  */
765
927
  declare class LogosEndpoint {
766
928
  private readonly client;
@@ -773,28 +935,29 @@ declare class LogosEndpoint {
773
935
  }
774
936
  //#endregion
775
937
  //#region ../sdk/src/client.d.ts
938
+ /**
939
+ * ⚠️ No `supabaseUrl` / `supabaseAnonKey`. This client speaks HTTP to
940
+ * `/api/v1/**` and nothing else — the direct-DB escape hatch (`client.db`) was
941
+ * removed in W7 so the seven tables it wrote to could have their
942
+ * `authenticated` write grants revoked
943
+ * (`docs/plan/2026-08-17-w7-sdk-retirement.md`). `check-dist-contract.mjs` in
944
+ * `@neopress/cli` / `@neopress/mcp` fails the build if either option returns.
945
+ */
776
946
  interface NeopressClientOptions {
777
947
  /** OAuth access token */
778
948
  accessToken?: string;
779
949
  /** Site ID to operate on */
780
950
  siteId: number | string;
781
- /** Base URL (default: https://app.neopress.ai) — used by orchestration/analytics (HTTP) routes */
951
+ /** Base URL (default: https://app.neopress.ai) */
782
952
  baseUrl?: string;
783
953
  /** Request timeout in ms (default: 30000) */
784
954
  timeout?: number;
785
- /** Supabase project URL — enables direct-DB reads/CRUD (transparent routing). */
786
- supabaseUrl?: string;
787
- /** Supabase anon key (paired with the user accessToken so RLS applies). */
788
- supabaseAnonKey?: string;
789
955
  }
790
956
  declare class NeopressClient {
791
957
  private readonly baseUrl;
792
958
  private readonly accessToken;
793
959
  private readonly siteId;
794
960
  private readonly timeout;
795
- private readonly supabaseUrl?;
796
- private readonly supabaseAnonKey?;
797
- private _db;
798
961
  readonly pages: PagesEndpoint;
799
962
  readonly collections: CollectionsEndpoint;
800
963
  readonly entries: EntriesEndpoint;
@@ -810,26 +973,6 @@ declare class NeopressClient {
810
973
  readonly logos: LogosEndpoint;
811
974
  constructor(options: NeopressClientOptions);
812
975
  get siteBasePath(): string;
813
- /** Numeric site id for direct DB queries. */
814
- get siteIdNumber(): number;
815
- /**
816
- * Authenticated user's profile id, decoded locally from the access token's
817
- * JWT `sub` claim (no network round-trip). Returns null if there is no token
818
- * or it cannot be parsed. Subsequent DB queries are RLS-protected, so a local
819
- * decode is sufficient — no server-side validation is required here.
820
- */
821
- get authUserId(): string | null;
822
- /** Mirror of v1 parsePagination(defaultLimit=20, maxLimit=100). */
823
- resolvePagination(params?: {
824
- page?: number;
825
- limit?: number;
826
- }): {
827
- page: number;
828
- limit: number;
829
- offset: number;
830
- };
831
- /** Build the paginated envelope matching the v1 API. */
832
- paginationMeta(page: number, limit: number, offset: number, totalCount: number): PaginationMeta;
833
976
  request<T>(method: string, path: string, body?: unknown): Promise<T>;
834
977
  get<T>(path: string): Promise<ApiResponse<T>>;
835
978
  getList<T>(path: string): Promise<PaginatedResponse<T>>;