@neopress/cli 4.8.1 → 4.10.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/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;
@@ -44,6 +44,8 @@ interface Template {
44
44
  referenceUrl: string;
45
45
  previewImgUrls: string[] | null;
46
46
  previewVideoUrl: string | null;
47
+ /** Primary language of the template's content, as an ISO 639-1 code (ko/en/es). */
48
+ language: string | null;
47
49
  createdAt: string | null;
48
50
  }
49
51
  interface TemplateListItem {
@@ -54,6 +56,7 @@ interface TemplateListItem {
54
56
  referenceUrl: string;
55
57
  previewImgUrls: string[] | null;
56
58
  description: string | null;
59
+ language: string | null;
57
60
  createdAt: string;
58
61
  }
59
62
  interface Page {
@@ -166,7 +169,7 @@ interface RedirectRule {
166
169
  siteId: number;
167
170
  source: string;
168
171
  destination: string;
169
- mode: 'redirect' | 'proxy';
172
+ mode: "redirect" | "proxy";
170
173
  redirectType: number;
171
174
  includeChildren: boolean;
172
175
  preservePath: boolean;
@@ -252,7 +255,7 @@ interface AnalyticsDateRangeParams {
252
255
  limit?: number;
253
256
  params?: Record<string, AnalyticsQueryValue>;
254
257
  }
255
- type SearchConsoleDimension = 'query' | 'page' | 'country' | 'device';
258
+ type SearchConsoleDimension = "query" | "page" | "country" | "device";
256
259
  interface SearchConsoleQueryParams {
257
260
  dateFrom: string;
258
261
  dateTo: string;
@@ -279,12 +282,108 @@ interface SearchConsoleQueryResult {
279
282
  };
280
283
  error?: string;
281
284
  }
285
+ type AnalyticsEventType = "custom" | "page" | "click" | "form" | "scroll" | "visibility" | "dwell";
286
+ 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";
287
+ type FunnelFilterOperator = "eq" | "neq" | "contains" | "not_contains" | "starts_with" | "ends_with" | "exists" | "not_exists" | "gt" | "gte" | "lt" | "lte";
288
+ type AnalyticsFunnelFilterCondition = {
289
+ kind: "condition";
290
+ field: FunnelFilterField;
291
+ operator: FunnelFilterOperator;
292
+ key?: string;
293
+ value?: string | number;
294
+ };
295
+ type AnalyticsFunnelFilterGroup = {
296
+ kind: "group";
297
+ op: "and" | "or";
298
+ children: AnalyticsFunnelFilterNode[];
299
+ };
300
+ type AnalyticsFunnelFilterNode = AnalyticsFunnelFilterGroup | AnalyticsFunnelFilterCondition;
301
+ type AnalyticsFunnelFilter = {
302
+ version: 1;
303
+ root: AnalyticsFunnelFilterGroup;
304
+ };
305
+ interface AnalyticsEventDefinition {
306
+ id: string;
307
+ chart_color: string | null;
308
+ name: string;
309
+ chart_visible: boolean;
310
+ is_pinned: boolean;
311
+ event_type: AnalyticsEventType;
312
+ event_name: string;
313
+ path: string;
314
+ selector: string;
315
+ form_id: string;
316
+ scroll_depth: number;
317
+ dwell_seconds: number;
318
+ tracking_version: number;
319
+ created_at: string;
320
+ updated_at: string;
321
+ }
322
+ interface AnalyticsEventDefinitionInput {
323
+ /** Optional client UUID; reuse it with the same payload after an uncertain create response. */
324
+ id?: string;
325
+ name: string;
326
+ eventType: AnalyticsEventType;
327
+ eventName?: string;
328
+ path?: string;
329
+ selector?: string;
330
+ formId?: string;
331
+ scrollDepth?: number;
332
+ dwellSeconds?: number;
333
+ }
334
+ type AnalyticsEventDefinitionUpdate = AnalyticsEventDefinitionInput | {
335
+ name: string;
336
+ };
337
+ interface AnalyticsEventDisplayUpdate {
338
+ chartVisible?: boolean;
339
+ pinned?: boolean;
340
+ chartColor?: string;
341
+ }
342
+ interface AnalyticsFunnelStepInput {
343
+ eventId: string;
344
+ path?: string;
345
+ filter?: AnalyticsFunnelFilter;
346
+ }
347
+ interface AnalyticsFunnelStep {
348
+ event_id: string;
349
+ path?: string;
350
+ filter?: AnalyticsFunnelFilter;
351
+ }
352
+ interface AnalyticsFunnelDefinition {
353
+ invalid?: true;
354
+ id: string;
355
+ name: string;
356
+ steps: AnalyticsFunnelStep[];
357
+ session_only: boolean;
358
+ window_minutes: number;
359
+ created_at: string;
360
+ updated_at: string;
361
+ }
362
+ interface AnalyticsTargets {
363
+ pages: Array<{
364
+ id: number;
365
+ name: string;
366
+ path: string;
367
+ locale: string;
368
+ }>;
369
+ forms: Array<{
370
+ id: number;
371
+ name: string;
372
+ }>;
373
+ }
374
+ interface AnalyticsFunnelDefinitionInput {
375
+ id?: string;
376
+ name: string;
377
+ steps: AnalyticsFunnelStepInput[];
378
+ windowMode: "session" | "time";
379
+ windowHours?: number;
380
+ }
282
381
  //#endregion
283
382
  //#region ../sdk/src/endpoints/pages.d.ts
284
383
  interface PageListParams {
285
384
  page?: number;
286
385
  limit?: number;
287
- status?: 'draft' | 'published';
386
+ status?: "draft" | "published";
288
387
  fields?: string[];
289
388
  /** When set, sort by created_at instead of path. */
290
389
  order?: SortOrder;
@@ -292,9 +391,9 @@ interface PageListParams {
292
391
  interface PageCreateParams {
293
392
  path: string;
294
393
  title?: string;
295
- type?: 'static' | 'dynamic';
296
- status?: 'draft' | 'published';
297
- visibility?: 'public' | 'hidden';
394
+ type?: "static" | "dynamic";
395
+ status?: "draft" | "published";
396
+ visibility?: "public" | "hidden";
298
397
  draftTsx?: string;
299
398
  draftI18n?: unknown;
300
399
  draftPublicLocales?: string[] | null;
@@ -307,7 +406,7 @@ interface PageUpdateParams {
307
406
  title?: string;
308
407
  type?: string;
309
408
  status?: string;
310
- visibility?: 'public' | 'hidden';
409
+ visibility?: string;
311
410
  draftTsx?: string;
312
411
  draftI18n?: unknown;
313
412
  draftPublicLocales?: string[] | null;
@@ -344,6 +443,12 @@ declare class CollectionsEndpoint {
344
443
  order?: SortOrder;
345
444
  }): Promise<Collection[]>;
346
445
  get(collectionId: number): Promise<Collection>;
446
+ /**
447
+ * Preset normalization (`normalizeCollectionPresetType` +
448
+ * `ensureCollectionPresetBaselineFields`) now runs server-side in
449
+ * `CollectionsApi.create`, which applies the identical pair in the identical
450
+ * order — the stored row is unchanged by the move to HTTP.
451
+ */
347
452
  create(data: CollectionCreateParams): Promise<Collection>;
348
453
  update(collectionId: number, data: Partial<CollectionCreateParams>): Promise<Collection>;
349
454
  delete(collectionId: number): Promise<void>;
@@ -352,10 +457,6 @@ declare class CollectionsEndpoint {
352
457
  draftSchema: unknown;
353
458
  prodSchema: unknown;
354
459
  }>;
355
- /**
356
- * Updates the collection's draft_schema directly in Supabase.
357
- * validateApiCollectionSchema (app-local) is skipped — CLI/MCP callers are trusted.
358
- */
359
460
  updateSchema(collectionId: number, schema: unknown): Promise<{
360
461
  collectionId: number;
361
462
  draftSchema: unknown;
@@ -367,7 +468,7 @@ declare class CollectionsEndpoint {
367
468
  interface EntryListParams {
368
469
  page?: number;
369
470
  limit?: number;
370
- status?: 'draft' | 'published' | 'archived';
471
+ status?: "draft" | "published" | "archived";
371
472
  locale?: string;
372
473
  /** created_at sort direction. Defaults to 'desc' (newest first). */
373
474
  order?: SortOrder;
@@ -391,9 +492,12 @@ interface EntryUpdateParams {
391
492
  declare class EntriesEndpoint {
392
493
  private readonly client;
393
494
  constructor(client: NeopressClient);
495
+ private get basePath();
394
496
  list(collectionId: number, params?: EntryListParams): Promise<PaginatedResponse<Entry>>;
395
497
  get(entryId: number): Promise<Entry>;
396
498
  create(collectionId: number, data: EntryCreateParams): Promise<Entry>;
499
+ /** Copy source content into a linked locale draft, or return its existing translation. */
500
+ createTranslation(entryId: number, locale: string): Promise<Entry>;
397
501
  update(entryId: number, data: EntryUpdateParams): Promise<Entry>;
398
502
  delete(entryId: number): Promise<void>;
399
503
  revalidateReaderCache(entryId: number): Promise<void>;
@@ -481,7 +585,12 @@ declare class AssetsEndpoint {
481
585
  }): Promise<PaginatedResponse<Asset>>;
482
586
  get(assetId: number): Promise<Asset>;
483
587
  register(data: AssetRegisterParams): Promise<Asset>;
484
- update(assetId: number, data: Partial<Omit<AssetRegisterParams, 'assetUrl'>>): Promise<Asset>;
588
+ update(assetId: number, data: Partial<Omit<AssetRegisterParams, "assetUrl">>): Promise<Asset>;
589
+ /**
590
+ * Hard delete. The route additionally requires the caller to be the site
591
+ * owner, matching `assets_delete_policy` = `is_site_owner(site_id)` — the
592
+ * registry axes alone would have widened this (W7 mapping §3-B1).
593
+ */
485
594
  delete(assetId: number): Promise<void>;
486
595
  presign(data: AssetPresignParams): Promise<{
487
596
  presignedUrl: string;
@@ -492,6 +601,12 @@ declare class AssetsEndpoint {
492
601
  //#endregion
493
602
  //#region ../sdk/src/endpoints/site.d.ts
494
603
  interface SiteWithRole extends Site {
604
+ /** Project role of the caller on this site: "owner" | "admin" | "member". */
605
+ projectRole: string;
606
+ /**
607
+ * @deprecated Legacy site role derived from `projectRole` for backwards
608
+ * compatibility ("owner" | "admin" | "member"). Use `projectRole` instead.
609
+ */
495
610
  role: string;
496
611
  }
497
612
  interface SiteCreateParams {
@@ -499,18 +614,25 @@ interface SiteCreateParams {
499
614
  siteHandle?: string;
500
615
  prompt?: string;
501
616
  language?: string;
502
- placeholder?: boolean;
503
617
  }
504
618
  declare class SiteEndpoint {
505
619
  private readonly client;
506
620
  constructor(client: NeopressClient);
621
+ /**
622
+ * Global (not site-scoped): lists the caller's own memberships, so it does not
623
+ * hang off `siteBasePath`. The membership + soft-delete filters that RLS used
624
+ * to apply now live in the route.
625
+ */
507
626
  list(params?: {
508
627
  order?: SortOrder;
509
628
  }): Promise<SiteWithRole[]>;
510
629
  get(): Promise<Site>;
511
630
  create(data: SiteCreateParams): Promise<Site>;
512
- update(data: Partial<Pick<Site, 'name' | 'metadata' | 'localeConfig'>> & {
513
- globalCss?: string;
631
+ /**
632
+ * Site settings only. The stylesheet is not here — it lives on the layout
633
+ * (`updateLayout({ draftGlobalCss })`), which is what the reader renders.
634
+ */
635
+ update(data: Partial<Pick<Site, "name" | "metadata" | "localeConfig">> & {
514
636
  instructions?: unknown;
515
637
  }): Promise<Site>;
516
638
  getLayout(): Promise<SiteLayout>;
@@ -538,11 +660,17 @@ interface RedirectCreateParams {
538
660
  declare class RedirectsEndpoint {
539
661
  private readonly client;
540
662
  constructor(client: NeopressClient);
663
+ private get basePath();
541
664
  list(params?: {
542
665
  order?: SortOrder;
543
666
  }): Promise<RedirectRule[]>;
544
667
  create(data: RedirectCreateParams): Promise<RedirectRule>;
545
- private assertRedirectRuleWritable;
668
+ /**
669
+ * The proxy preflight that used to run here (`assertRedirectRuleWritable`)
670
+ * needs a DB read, so it moved into the route. Its two error codes —
671
+ * `REDIRECT_RULE_NOT_FOUND` and `REDIRECT_RULE_DASHBOARD_ONLY` — are returned
672
+ * by the server verbatim, so `NeopressApiError.code` is unchanged for callers.
673
+ */
546
674
  update(ruleId: number, data: Partial<RedirectCreateParams>): Promise<RedirectRule>;
547
675
  delete(ruleId: number): Promise<void>;
548
676
  }
@@ -570,6 +698,7 @@ interface EntryReferenceCreateParams {
570
698
  declare class EntryReferencesEndpoint {
571
699
  private readonly client;
572
700
  constructor(client: NeopressClient);
701
+ private get basePath();
573
702
  list(params?: EntryReferenceListParams): Promise<EntryReference[]>;
574
703
  create(data: EntryReferenceCreateParams): Promise<EntryReference>;
575
704
  delete(referenceId: number): Promise<void>;
@@ -578,6 +707,8 @@ declare class EntryReferencesEndpoint {
578
707
  //#region ../sdk/src/endpoints/analytics.d.ts
579
708
  declare class AnalyticsEndpoint {
580
709
  private readonly client;
710
+ readonly events: AnalyticsEventsEndpoint;
711
+ readonly funnels: AnalyticsFunnelsEndpoint;
581
712
  constructor(client: NeopressClient);
582
713
  private get basePath();
583
714
  query(params: AnalyticsQueryParams): Promise<AnalyticsQueryResult>;
@@ -588,6 +719,30 @@ declare class AnalyticsEndpoint {
588
719
  formAnalytics(params?: AnalyticsDateRangeParams): Promise<AnalyticsQueryResult>;
589
720
  crawlerSummary(params?: AnalyticsDateRangeParams): Promise<AnalyticsQueryResult>;
590
721
  searchConsole(params: SearchConsoleQueryParams): Promise<SearchConsoleQueryResult>;
722
+ /** Page/form targets that can be referenced by an event definition. */
723
+ targets(): Promise<AnalyticsTargets>;
724
+ }
725
+ declare class AnalyticsEventsEndpoint {
726
+ private readonly client;
727
+ constructor(client: NeopressClient);
728
+ private get basePath();
729
+ list(): Promise<AnalyticsEventDefinition[]>;
730
+ get(eventId: string): Promise<AnalyticsEventDefinition>;
731
+ create(data: AnalyticsEventDefinitionInput): Promise<AnalyticsEventDefinition>;
732
+ update(eventId: string, data: AnalyticsEventDefinitionUpdate | {
733
+ display: AnalyticsEventDisplayUpdate;
734
+ }): Promise<AnalyticsEventDefinition>;
735
+ delete(eventId: string): Promise<void>;
736
+ }
737
+ declare class AnalyticsFunnelsEndpoint {
738
+ private readonly client;
739
+ constructor(client: NeopressClient);
740
+ private get basePath();
741
+ list(): Promise<AnalyticsFunnelDefinition[]>;
742
+ get(funnelId: string): Promise<AnalyticsFunnelDefinition>;
743
+ create(data: AnalyticsFunnelDefinitionInput): Promise<AnalyticsFunnelDefinition>;
744
+ update(funnelId: string, data: AnalyticsFunnelDefinitionInput): Promise<AnalyticsFunnelDefinition>;
745
+ delete(funnelId: string): Promise<void>;
591
746
  }
592
747
  //#endregion
593
748
  //#region ../sdk/src/endpoints/templates.d.ts
@@ -600,6 +755,11 @@ interface TemplateMarkParams {
600
755
  * card cross-fades from poster to video, so the poster must be the video's
601
756
  * first frame. Omit to leave an existing value untouched; `null` clears it. */
602
757
  previewVideoUrl?: string | null;
758
+ /** Primary language of the template's content — required, ISO 639-1 lowercase
759
+ * two-letter code (`ko`, `en`, `es`). Region-qualified locales (`en-US`) are
760
+ * rejected: the gallery filters on this key and must not see one language
761
+ * under two spellings. */
762
+ language: string;
603
763
  }
604
764
  interface TemplateDeleteResult {
605
765
  siteId: number;
@@ -615,9 +775,8 @@ declare class TemplatesEndpoint {
615
775
  /**
616
776
  * List templates across the sites the caller can access.
617
777
  *
618
- * Direct-DB: `templates` is public-read and `sites` RLS scopes the visible set
619
- * (own active memberships, or all sites for the super admin), so no server
620
- * route is needed — the two RLS-scoped queries reproduce the old handler.
778
+ * Global (not site-scoped) — the accessible-site set is resolved server-side,
779
+ * where RLS used to do it.
621
780
  */
622
781
  list(params?: {
623
782
  namePrefix?: string;
@@ -627,10 +786,10 @@ declare class TemplatesEndpoint {
627
786
  }
628
787
  //#endregion
629
788
  //#region ../sdk/src/endpoints/headshots.d.ts
630
- type HeadshotAgeBand = 'young' | 'middle';
631
- type HeadshotGender = 'male' | 'female';
632
- type HeadshotEthnicity = 'asian' | 'white' | 'black' | 'latino';
633
- type HeadshotAttire = 'formal' | 'casual';
789
+ type HeadshotAgeBand = "young" | "middle";
790
+ type HeadshotGender = "male" | "female";
791
+ type HeadshotEthnicity = "asian" | "white" | "black" | "latino";
792
+ type HeadshotAttire = "formal" | "casual";
634
793
  interface HeadshotListParams {
635
794
  page?: number;
636
795
  limit?: number;
@@ -679,9 +838,11 @@ interface HeadshotDeleteResult {
679
838
  deleted: true;
680
839
  }
681
840
  /**
682
- * headshot_pool is a GLOBAL (not site-scoped) admin table. RLS exposes it only to
683
- * the super-admin user, so these direct-DB calls return empty/forbidden for any
684
- * other account. No site id is used.
841
+ * `headshot_pool` is a GLOBAL (not site-scoped) admin table, so these calls run
842
+ * over `/api/v1/headshots` — no `siteId` in the path — instead of the
843
+ * site-scoped `withAuth` routes (W7 T5). The routes gate on the super-admin
844
+ * user id, which is the same check the table's single RLS policy made; a
845
+ * non-super-admin now gets 403 where the direct-DB read returned an empty list.
685
846
  */
686
847
  declare class HeadshotsEndpoint {
687
848
  private readonly client;
@@ -694,8 +855,8 @@ declare class HeadshotsEndpoint {
694
855
  }
695
856
  //#endregion
696
857
  //#region ../sdk/src/endpoints/logos.d.ts
697
- type LogoType = 'wordmark' | 'lockup' | 'signature';
698
- type LogoTone = 'playful' | 'neutral' | 'serious';
858
+ type LogoType = "wordmark" | "lockup" | "signature";
859
+ type LogoTone = "playful" | "neutral" | "serious";
699
860
  interface LogoListParams {
700
861
  page?: number;
701
862
  limit?: number;
@@ -750,9 +911,11 @@ interface LogoDeleteResult {
750
911
  deleted: true;
751
912
  }
752
913
  /**
753
- * logo_pool is a GLOBAL (not site-scoped) admin table. RLS exposes it only to the
754
- * super-admin user, so these direct-DB calls return empty/forbidden for any other
755
- * account. No site id is used.
914
+ * `logo_pool` is a GLOBAL (not site-scoped) admin table, so these calls run over
915
+ * `/api/v1/logos` — no `siteId` in the path — instead of the site-scoped
916
+ * `withAuth` routes (W7 T5). The routes gate on the super-admin user id, which
917
+ * is the same check the table's single RLS policy made; a non-super-admin now
918
+ * gets 403 where the direct-DB read returned an empty list.
756
919
  */
757
920
  declare class LogosEndpoint {
758
921
  private readonly client;
@@ -765,28 +928,29 @@ declare class LogosEndpoint {
765
928
  }
766
929
  //#endregion
767
930
  //#region ../sdk/src/client.d.ts
931
+ /**
932
+ * ⚠️ No `supabaseUrl` / `supabaseAnonKey`. This client speaks HTTP to
933
+ * `/api/v1/**` and nothing else — the direct-DB escape hatch (`client.db`) was
934
+ * removed in W7 so the seven tables it wrote to could have their
935
+ * `authenticated` write grants revoked
936
+ * (`docs/plan/2026-08-17-w7-sdk-retirement.md`). `check-dist-contract.mjs` in
937
+ * `@neopress/cli` / `@neopress/mcp` fails the build if either option returns.
938
+ */
768
939
  interface NeopressClientOptions {
769
940
  /** OAuth access token */
770
941
  accessToken?: string;
771
942
  /** Site ID to operate on */
772
943
  siteId: number | string;
773
- /** Base URL (default: https://app.neopress.ai) — used by orchestration/analytics (HTTP) routes */
944
+ /** Base URL (default: https://app.neopress.ai) */
774
945
  baseUrl?: string;
775
946
  /** Request timeout in ms (default: 30000) */
776
947
  timeout?: number;
777
- /** Supabase project URL — enables direct-DB reads/CRUD (transparent routing). */
778
- supabaseUrl?: string;
779
- /** Supabase anon key (paired with the user accessToken so RLS applies). */
780
- supabaseAnonKey?: string;
781
948
  }
782
949
  declare class NeopressClient {
783
950
  private readonly baseUrl;
784
951
  private readonly accessToken;
785
952
  private readonly siteId;
786
953
  private readonly timeout;
787
- private readonly supabaseUrl?;
788
- private readonly supabaseAnonKey?;
789
- private _db;
790
954
  readonly pages: PagesEndpoint;
791
955
  readonly collections: CollectionsEndpoint;
792
956
  readonly entries: EntriesEndpoint;
@@ -802,26 +966,6 @@ declare class NeopressClient {
802
966
  readonly logos: LogosEndpoint;
803
967
  constructor(options: NeopressClientOptions);
804
968
  get siteBasePath(): string;
805
- /** Numeric site id for direct DB queries. */
806
- get siteIdNumber(): number;
807
- /**
808
- * Authenticated user's profile id, decoded locally from the access token's
809
- * JWT `sub` claim (no network round-trip). Returns null if there is no token
810
- * or it cannot be parsed. Subsequent DB queries are RLS-protected, so a local
811
- * decode is sufficient — no server-side validation is required here.
812
- */
813
- get authUserId(): string | null;
814
- /** Mirror of v1 parsePagination(defaultLimit=20, maxLimit=100). */
815
- resolvePagination(params?: {
816
- page?: number;
817
- limit?: number;
818
- }): {
819
- page: number;
820
- limit: number;
821
- offset: number;
822
- };
823
- /** Build the paginated envelope matching the v1 API. */
824
- paginationMeta(page: number, limit: number, offset: number, totalCount: number): PaginationMeta;
825
969
  request<T>(method: string, path: string, body?: unknown): Promise<T>;
826
970
  get<T>(path: string): Promise<ApiResponse<T>>;
827
971
  getList<T>(path: string): Promise<PaginatedResponse<T>>;