@neopress/cli 4.2.0 → 4.4.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.
Files changed (3) hide show
  1. package/dist/bin.cjs +116 -55
  2. package/dist/index.js +77 -26
  3. package/package.json +1 -1
package/dist/bin.cjs CHANGED
@@ -24917,7 +24917,6 @@ function serializeTemplate(t) {
24917
24917
  id: t.id,
24918
24918
  name: t.name,
24919
24919
  description: t.description ?? null,
24920
- industry: t.industry,
24921
24920
  referenceUrl: t.reference_url,
24922
24921
  previewImgUrls: t.preview_img_urls,
24923
24922
  previewVideoUrl: t.preview_video_url ?? null,
@@ -24998,7 +24997,8 @@ var PagesEndpoint = class {
24998
24997
  }
24999
24998
  async list(params) {
25000
24999
  const { page, limit, offset } = this.client.resolvePagination(params);
25001
- let query = this.client.db.from("pages").select("*", { count: "exact" }).eq("site_id", this.client.siteIdNumber).is("draft_deleted_at", null).order("path");
25000
+ let query = this.client.db.from("pages").select("*", { count: "exact" }).eq("site_id", this.client.siteIdNumber).is("draft_deleted_at", null);
25001
+ query = params?.order ? query.order("created_at", { ascending: params.order === "asc" }) : query.order("path");
25002
25002
  if (params?.status)
25003
25003
  query = query.eq("status", params.status);
25004
25004
  const { data, error: error48, count } = await query.range(offset, offset + limit - 1);
@@ -40052,8 +40052,10 @@ var CollectionsEndpoint = class {
40052
40052
  get basePath() {
40053
40053
  return `${this.client.siteBasePath}/collections`;
40054
40054
  }
40055
- async list() {
40056
- const { data, error: error48 } = await this.client.db.from("collections").select("*").eq("site_id", this.client.siteIdNumber).is("draft_deleted_at", null).order("name");
40055
+ async list(params) {
40056
+ let query = this.client.db.from("collections").select("*").eq("site_id", this.client.siteIdNumber).is("draft_deleted_at", null);
40057
+ query = params?.order ? query.order("created_at", { ascending: params.order === "asc" }) : query.order("name");
40058
+ const { data, error: error48 } = await query;
40057
40059
  if (error48)
40058
40060
  throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40059
40061
  return (data ?? []).map((r) => serializeCollection(r));
@@ -40126,7 +40128,7 @@ var EntriesEndpoint = class {
40126
40128
  }
40127
40129
  async list(collectionId, params) {
40128
40130
  const { page, limit, offset } = this.client.resolvePagination(params);
40129
- let query = this.client.db.from("entries").select("*", { count: "exact" }).eq("site_id", this.client.siteIdNumber).eq("collection_id", collectionId).is("deleted_at", null).order("created_at", { ascending: false });
40131
+ let query = this.client.db.from("entries").select("*", { count: "exact" }).eq("site_id", this.client.siteIdNumber).eq("collection_id", collectionId).is("deleted_at", null).order("created_at", { ascending: params?.order === "asc" });
40130
40132
  if (params?.status)
40131
40133
  query = query.eq("status", params.status);
40132
40134
  if (params?.locale)
@@ -40217,8 +40219,10 @@ var FormsEndpoint = class {
40217
40219
  get basePath() {
40218
40220
  return `${this.client.siteBasePath}/forms`;
40219
40221
  }
40220
- async list() {
40221
- const { data, error: error48 } = await this.client.db.from("forms").select("*").eq("site_id", this.client.siteIdNumber).is("draft_deleted_at", null).order("name");
40222
+ async list(params) {
40223
+ let query = this.client.db.from("forms").select("*").eq("site_id", this.client.siteIdNumber).is("draft_deleted_at", null);
40224
+ query = params?.order ? query.order("created_at", { ascending: params.order === "asc" }) : query.order("name");
40225
+ const { data, error: error48 } = await query;
40222
40226
  if (error48)
40223
40227
  throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40224
40228
  const rows = data ?? [];
@@ -40297,7 +40301,7 @@ var AssetsEndpoint = class {
40297
40301
  }
40298
40302
  async list(params) {
40299
40303
  const { page, limit, offset } = this.client.resolvePagination(params);
40300
- const { data, error: error48, count } = await this.client.db.from("assets").select("*", { count: "exact" }).eq("site_id", this.client.siteIdNumber).order("created_at", { ascending: false }).range(offset, offset + limit - 1);
40304
+ const { data, error: error48, count } = await this.client.db.from("assets").select("*", { count: "exact" }).eq("site_id", this.client.siteIdNumber).order("created_at", { ascending: params?.order === "asc" }).range(offset, offset + limit - 1);
40301
40305
  if (error48)
40302
40306
  throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40303
40307
  const rows = data ?? [];
@@ -40363,7 +40367,7 @@ var SiteEndpoint = class {
40363
40367
  constructor(client) {
40364
40368
  this.client = client;
40365
40369
  }
40366
- async list() {
40370
+ async list(params) {
40367
40371
  const profileId = this.client.authUserId;
40368
40372
  if (!profileId) {
40369
40373
  throw new NeopressApiError("NO_AUTH", "site.list requires an authenticated access token", 0);
@@ -40371,10 +40375,15 @@ var SiteEndpoint = class {
40371
40375
  const { data, error: error48 } = await this.client.db.from("sites_profiles").select("role, sites!inner(*)").eq("profile_id", profileId).eq("status", "active");
40372
40376
  if (error48)
40373
40377
  throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40374
- return (data ?? []).map((m) => ({
40378
+ const sites = (data ?? []).map((m) => ({
40375
40379
  ...serializeSite(m.sites),
40376
40380
  role: m.role
40377
40381
  }));
40382
+ if (params?.order) {
40383
+ const dir = params.order === "asc" ? 1 : -1;
40384
+ sites.sort((a, b) => (a.createdAt ?? "") < (b.createdAt ?? "") ? -dir : (a.createdAt ?? "") > (b.createdAt ?? "") ? dir : 0);
40385
+ }
40386
+ return sites;
40378
40387
  }
40379
40388
  async get() {
40380
40389
  const { data, error: error48 } = await this.client.db.from("sites").select("*").eq("id", this.client.siteIdNumber).single();
@@ -40412,8 +40421,10 @@ var RedirectsEndpoint = class {
40412
40421
  constructor(client) {
40413
40422
  this.client = client;
40414
40423
  }
40415
- async list() {
40416
- const { data, error: error48 } = await this.client.db.from("redirect_rules").select("*").eq("site_id", this.client.siteIdNumber).order("priority", { ascending: true });
40424
+ async list(params) {
40425
+ let query = this.client.db.from("redirect_rules").select("*").eq("site_id", this.client.siteIdNumber);
40426
+ query = params?.order ? query.order("created_at", { ascending: params.order === "asc" }) : query.order("priority", { ascending: true });
40427
+ const { data, error: error48 } = await query;
40417
40428
  if (error48)
40418
40429
  throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40419
40430
  return (data ?? []).map((r) => serializeRedirect(r));
@@ -40423,7 +40434,7 @@ var RedirectsEndpoint = class {
40423
40434
  site_id: this.client.siteIdNumber,
40424
40435
  source: data.source,
40425
40436
  destination: data.destination,
40426
- redirect_type: data.redirectType ?? 307,
40437
+ redirect_type: data.redirectType ?? 308,
40427
40438
  include_children: data.includeChildren ?? false,
40428
40439
  preserve_path: data.preservePath ?? false,
40429
40440
  enabled: data.enabled !== false,
@@ -40490,7 +40501,7 @@ var EntryReferencesEndpoint = class {
40490
40501
  query = query.eq("from_entry_id", params.fromEntryId);
40491
40502
  if (params?.toEntryId)
40492
40503
  query = query.eq("to_entry_id", params.toEntryId);
40493
- const { data, error: error48 } = await query.order("created_at", { ascending: false });
40504
+ const { data, error: error48 } = await query.order("created_at", { ascending: params?.order === "asc" });
40494
40505
  if (error48)
40495
40506
  throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40496
40507
  return (data ?? []).map((r) => serializeEntryReference(r));
@@ -40612,18 +40623,53 @@ var TemplatesEndpoint = class {
40612
40623
  const res = await this.client.delete(this.basePath);
40613
40624
  return res.data;
40614
40625
  }
40626
+ /**
40627
+ * List templates across the sites the caller can access.
40628
+ *
40629
+ * Direct-DB: `templates` is public-read and `sites` RLS scopes the visible set
40630
+ * (own active memberships, or all sites for the super admin), so no server
40631
+ * route is needed — the two RLS-scoped queries reproduce the old handler.
40632
+ */
40615
40633
  async list(params) {
40616
- const qs = new URLSearchParams();
40617
- if (params?.namePrefix)
40618
- qs.set("namePrefix", params.namePrefix);
40619
- if (params?.industry)
40620
- qs.set("industry", params.industry);
40621
- if (params?.limit != null)
40622
- qs.set("limit", String(params.limit));
40623
- const query = qs.toString();
40624
- const path = query ? `/api/v1/templates?${query}` : "/api/v1/templates";
40625
- const res = await this.client.get(path);
40626
- return res.data;
40634
+ const { data: sitesData, error: sitesError } = await this.client.db.from("sites").select("id, handle, name");
40635
+ if (sitesError)
40636
+ throw new NeopressApiError("DB_ERROR", sitesError.message, 0, sitesError);
40637
+ const siteMeta = /* @__PURE__ */ new Map();
40638
+ for (const site of sitesData ?? []) {
40639
+ if (typeof site.id !== "number")
40640
+ continue;
40641
+ siteMeta.set(site.id, {
40642
+ handle: site.handle ?? null,
40643
+ name: site.name ?? null
40644
+ });
40645
+ }
40646
+ const siteIds = [...siteMeta.keys()];
40647
+ if (siteIds.length === 0)
40648
+ return [];
40649
+ const namePrefix = params?.namePrefix?.trim();
40650
+ const limit = Math.min(Math.max(params?.limit ?? 100, 1), 500);
40651
+ let query = this.client.db.from("templates").select("id, name, reference_url, preview_img_urls, description, created_at").in("id", siteIds);
40652
+ if (namePrefix)
40653
+ query = query.ilike("name", `${namePrefix}%`);
40654
+ if (params?.order)
40655
+ query = query.order("created_at", { ascending: params.order === "asc" });
40656
+ query = query.limit(limit);
40657
+ const { data, error: error48 } = await query;
40658
+ if (error48)
40659
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40660
+ return (data ?? []).map((t) => {
40661
+ const siteId = t.id;
40662
+ return {
40663
+ siteId,
40664
+ handle: siteMeta.get(siteId)?.handle ?? null,
40665
+ siteName: siteMeta.get(siteId)?.name ?? null,
40666
+ name: t.name,
40667
+ referenceUrl: t.reference_url,
40668
+ previewImgUrls: t.preview_img_urls ?? null,
40669
+ description: t.description ?? null,
40670
+ createdAt: t.created_at
40671
+ };
40672
+ });
40627
40673
  }
40628
40674
  };
40629
40675
 
@@ -40646,7 +40692,12 @@ var HeadshotsEndpoint = class {
40646
40692
  query = query.eq("attire", params.attire);
40647
40693
  if (params?.isActive !== void 0)
40648
40694
  query = query.eq("is_active", params.isActive);
40649
- 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);
40695
+ if (params?.order) {
40696
+ query = query.order("created_at", { ascending: params.order === "asc" });
40697
+ } else {
40698
+ query = query.order("age_band", { ascending: true }).order("gender", { ascending: true }).order("ethnicity", { ascending: true }).order("variation_index", { ascending: true });
40699
+ }
40700
+ const { data, error: error48, count } = await query.range(offset, offset + limit - 1);
40650
40701
  if (error48)
40651
40702
  throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40652
40703
  return {
@@ -40912,6 +40963,16 @@ async function getGlobalClientAsync() {
40912
40963
  return new NeopressClient({ accessToken: token, siteId: 0, baseUrl: getBaseUrl(), supabaseUrl: sb.url, supabaseAnonKey: sb.anonKey });
40913
40964
  }
40914
40965
 
40966
+ // src/utils/order.ts
40967
+ function parseOrder(value) {
40968
+ if (value === void 0) return void 0;
40969
+ if (value !== "asc" && value !== "desc") {
40970
+ fail(`Invalid --order value "${value}". Use "asc" or "desc".`);
40971
+ }
40972
+ return value;
40973
+ }
40974
+ var ORDER_OPTION_DESC = "Sort by created_at (asc or desc)";
40975
+
40915
40976
  // src/commands/sites.ts
40916
40977
  function registerSiteCommands(program3) {
40917
40978
  const sites = program3.command("sites").description("Manage sites").addHelpText("after", `
@@ -40922,9 +40983,9 @@ Examples:
40922
40983
  $ neopress sites use my-site
40923
40984
  $ neopress sites current
40924
40985
  Run \`sites use <id|handle>\` to set the global active site, or \`sites create --pin\` to scope a site to the current directory. \`sites current\` shows which site commands will target and why.`);
40925
- sites.command("list").description("List accessible sites").action(async () => {
40986
+ sites.command("list").description("List accessible sites").option("--order <asc|desc>", ORDER_OPTION_DESC).action(async (options) => {
40926
40987
  const client = await getGlobalClientAsync();
40927
- const sitesList = await client.site.list();
40988
+ const sitesList = await client.site.list({ order: parseOrder(options.order) });
40928
40989
  output(sitesList);
40929
40990
  });
40930
40991
  sites.command("use <siteIdOrHandle>").description("Set active site (ID or handle)").addHelpText("after", `
@@ -41078,10 +41139,11 @@ Examples:
41078
41139
  $ neopress pages compile <pageId>
41079
41140
 
41080
41141
  Draft content fields (--tsx/--i18n/--draft-meta) need \`neopress publish\` to go live. \`pages delete\` is a soft delete.`);
41081
- 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) => {
41142
+ pages.command("list").description("List pages").option("--status <status>", "Filter by status (draft/published)").option("--order <asc|desc>", ORDER_OPTION_DESC).option("--page <n>", "Page number", "1").option("--limit <n>", "Results per page", "20").action(async (options) => {
41082
41143
  const client = await getClientAsync();
41083
41144
  const res = await client.pages.list({
41084
41145
  status: options.status,
41146
+ order: parseOrder(options.order),
41085
41147
  page: parseInt(options.page, 10),
41086
41148
  limit: parseInt(options.limit, 10)
41087
41149
  });
@@ -41178,9 +41240,9 @@ Examples:
41178
41240
  $ neopress collections update 12 --schema @schema.json
41179
41241
 
41180
41242
  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.`);
41181
- collections.command("list").description("List collections").action(async () => {
41243
+ collections.command("list").description("List collections").option("--order <asc|desc>", ORDER_OPTION_DESC).action(async (options) => {
41182
41244
  const client = await getClientAsync();
41183
- const data = await client.collections.list();
41245
+ const data = await client.collections.list({ order: parseOrder(options.order) });
41184
41246
  output(data);
41185
41247
  });
41186
41248
  collections.command("get <collectionId>").description("Get collection details").action(async (collectionId) => {
@@ -41238,11 +41300,12 @@ Examples:
41238
41300
  $ neopress entries publish <entryId>
41239
41301
 
41240
41302
  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.`);
41241
- 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) => {
41303
+ 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("--order <asc|desc>", ORDER_OPTION_DESC).option("--page <n>", "Page number", "1").option("--limit <n>", "Results per page", "20").action(async (options) => {
41242
41304
  const client = await getClientAsync();
41243
41305
  const res = await client.entries.list(parseInt(options.collection, 10), {
41244
41306
  status: options.status,
41245
41307
  locale: options.locale,
41308
+ order: parseOrder(options.order),
41246
41309
  page: parseInt(options.page, 10),
41247
41310
  limit: parseInt(options.limit, 10)
41248
41311
  });
@@ -41332,9 +41395,9 @@ Examples:
41332
41395
  $ neopress forms publish <formId>
41333
41396
  $ neopress forms responses <formId> --limit 50
41334
41397
  Forms are created as drafts \u2014 run \`neopress forms publish <formId>\` to make them live. JSON options take inline JSON or @file.`);
41335
- forms.command("list").description("List forms").action(async () => {
41398
+ forms.command("list").description("List forms").option("--order <asc|desc>", ORDER_OPTION_DESC).action(async (options) => {
41336
41399
  const client = await getClientAsync();
41337
- const data = await client.forms.list();
41400
+ const data = await client.forms.list({ order: parseOrder(options.order) });
41338
41401
  output(data);
41339
41402
  });
41340
41403
  forms.command("get <formId>").description("Get form details").action(async (formId) => {
@@ -41414,9 +41477,10 @@ Examples:
41414
41477
  $ neopress assets upload ./hero.png --folder images
41415
41478
  $ neopress assets list --limit 50
41416
41479
  Upload runs presign \u2192 PUT to storage \u2192 register in one step; content type is inferred from the file extension.`);
41417
- assets.command("list").description("List assets").option("--page <n>", "Page number", "1").option("--limit <n>", "Results per page", "20").action(async (options) => {
41480
+ assets.command("list").description("List assets").option("--order <asc|desc>", ORDER_OPTION_DESC).option("--page <n>", "Page number", "1").option("--limit <n>", "Results per page", "20").action(async (options) => {
41418
41481
  const client = await getClientAsync();
41419
41482
  const res = await client.assets.list({
41483
+ order: parseOrder(options.order),
41420
41484
  page: parseInt(options.page, 10),
41421
41485
  limit: parseInt(options.limit, 10)
41422
41486
  });
@@ -41499,16 +41563,16 @@ Examples:
41499
41563
  $ neopress redirects create --source /docs --dest /guide --type 308
41500
41564
  $ neopress redirects list
41501
41565
  Rules are created enabled. delete <ruleId> removes a rule permanently.`);
41502
- redirects.command("list").description("List redirect rules").action(async () => {
41566
+ redirects.command("list").description("List redirect rules").option("--order <asc|desc>", `${ORDER_OPTION_DESC} (default sort: priority)`).action(async (options) => {
41503
41567
  const client = await getClientAsync();
41504
- const data = await client.redirects.list();
41568
+ const data = await client.redirects.list({ order: parseOrder(options.order) });
41505
41569
  output(data);
41506
41570
  });
41507
41571
  redirects.command("create").description("Create a redirect rule").addHelpText("after", `
41508
41572
  Examples:
41509
41573
  $ neopress redirects create --source /old --dest /about
41510
41574
  $ neopress redirects create --source /docs --dest /guide --type 308
41511
- --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) => {
41575
+ --source and --dest are both required. --type is 307 (temporary) or 308 (permanent, default). The rule is created enabled.`).requiredOption("--source <path>", "Source path").requiredOption("--dest <path>", "Destination path").option("--type <type>", "Redirect type (307/308)", "308").action(async (options) => {
41512
41576
  const client = await getClientAsync();
41513
41577
  const rule = await client.redirects.create({
41514
41578
  source: options.source,
@@ -41564,11 +41628,12 @@ Examples:
41564
41628
  $ neopress entry-references list --from-entry 12
41565
41629
  $ neopress entry-references delete <referenceId>
41566
41630
  --field is a reference field key from the source entry's collection schema. delete is a hard delete and removes the relation immediately.`);
41567
- 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) => {
41631
+ 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").option("--order <asc|desc>", ORDER_OPTION_DESC).action(async (options) => {
41568
41632
  const client = await getClientAsync();
41569
41633
  const data = await client.entryReferences.list({
41570
41634
  fromEntryId: options.fromEntry ? parseInt(options.fromEntry, 10) : void 0,
41571
- toEntryId: options.toEntry ? parseInt(options.toEntry, 10) : void 0
41635
+ toEntryId: options.toEntry ? parseInt(options.toEntry, 10) : void 0,
41636
+ order: parseOrder(options.order)
41572
41637
  });
41573
41638
  output(data);
41574
41639
  });
@@ -41726,19 +41791,15 @@ function collectPreviewImgUrl(value, previous = []) {
41726
41791
  function registerTemplateCommands(program3) {
41727
41792
  const templates = program3.command("templates").description("Manage template metadata for the active site").addHelpText("after", `
41728
41793
  Examples:
41729
- $ neopress templates mark --name "SaaS Landing" --industry saas --reference-url https://example.com --preview-img-url https://cdn.example.com/1.png
41730
- $ neopress templates list --industry saas --limit 50
41794
+ $ neopress templates mark --name "SaaS Landing" --reference-url https://example.com --preview-img-url https://cdn.example.com/1.png
41795
+ $ neopress templates list --limit 50
41731
41796
  $ neopress templates delete
41732
41797
  \`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.`);
41733
- 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) => {
41734
- const token = process.env.NEOPRESS_ACCESS_TOKEN || await getValidAccessToken() || void 0;
41735
- if (!token) {
41736
- fail("Not authenticated. Run `neopress login`.");
41737
- }
41738
- const client = new NeopressClient({ accessToken: token, siteId: 0, baseUrl: getBaseUrl() });
41798
+ templates.command("list").description("List templates across sites you can access").option("--name-prefix <prefix>", "Filter by template name prefix").option("--order <asc|desc>", ORDER_OPTION_DESC).option("--limit <n>", "Max results (default 100, max 500)").action(async (options) => {
41799
+ const client = await getGlobalClientAsync();
41739
41800
  const templates2 = await client.templates.list({
41740
41801
  namePrefix: options.namePrefix,
41741
- industry: options.industry,
41802
+ order: parseOrder(options.order),
41742
41803
  limit: options.limit ? parseInt(options.limit, 10) : void 0
41743
41804
  });
41744
41805
  output(templates2);
@@ -41750,9 +41811,9 @@ Examples:
41750
41811
  });
41751
41812
  templates.command("mark").description("Mark or update the active site as a template").addHelpText("after", `
41752
41813
  Examples:
41753
- $ neopress templates mark --name "SaaS Landing" --industry saas --reference-url https://example.com --preview-img-url https://cdn.example.com/1.png
41754
- $ 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"
41755
- --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(
41814
+ $ neopress templates mark --name "SaaS Landing" --reference-url https://example.com --preview-img-url https://cdn.example.com/1.png
41815
+ $ neopress templates mark --name Portfolio --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"
41816
+ --name and --reference-url are 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(
41756
41817
  "--reference-url <url>",
41757
41818
  "Reference URL for template provenance"
41758
41819
  ).option(
@@ -41765,7 +41826,6 @@ Examples:
41765
41826
  const template = await client.templates.mark({
41766
41827
  name: options.name,
41767
41828
  description: options.description,
41768
- industry: options.industry,
41769
41829
  referenceUrl: options.referenceUrl,
41770
41830
  previewImgUrls: options.previewImgUrl.length > 0 ? options.previewImgUrl : void 0
41771
41831
  });
@@ -41803,7 +41863,7 @@ Examples:
41803
41863
  $ neopress headshots create --image-url https://cdn/x.webp --age-band young --gender male --ethnicity white --variation-index 0
41804
41864
  $ neopress headshots update 12 --inactive
41805
41865
  The pool is global and RLS-gated: only the super-admin account can read or write it. No active site is needed.`);
41806
- 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", `
41866
+ 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("--order <asc|desc>", ORDER_OPTION_DESC).option("--page <n>", "Page number", "1").option("--limit <n>", "Results per page", "20").addHelpText("after", `
41807
41867
  Examples:
41808
41868
  $ neopress headshots list
41809
41869
  $ neopress headshots list --gender female --age-band middle --active
@@ -41818,6 +41878,7 @@ Filters are equality matches on the pool's demographic columns; results are pagi
41818
41878
  const params = {
41819
41879
  page: parseInt(options.page, 10),
41820
41880
  limit: parseInt(options.limit, 10),
41881
+ order: parseOrder(options.order),
41821
41882
  ageBand: options.ageBand,
41822
41883
  gender: options.gender,
41823
41884
  ethnicity: options.ethnicity,
@@ -41997,7 +42058,7 @@ function registerGuideCommands(program3) {
41997
42058
 
41998
42059
  // src/bin.ts
41999
42060
  var program2 = new Command();
42000
- program2.name("neopress").description("Neopress CLI \u2014 manage your site from the terminal").version("4.2.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) => {
42061
+ program2.name("neopress").description("Neopress CLI \u2014 manage your site from the terminal").version("4.4.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) => {
42001
42062
  const opts = thisCommand.optsWithGlobals();
42002
42063
  if (opts.site) setSiteOverride(String(opts.site));
42003
42064
  if (opts.json || !process.stdout.isTTY) setJsonMode(true);
package/dist/index.js CHANGED
@@ -21139,7 +21139,6 @@ function serializeTemplate(t) {
21139
21139
  id: t.id,
21140
21140
  name: t.name,
21141
21141
  description: t.description ?? null,
21142
- industry: t.industry,
21143
21142
  referenceUrl: t.reference_url,
21144
21143
  previewImgUrls: t.preview_img_urls,
21145
21144
  previewVideoUrl: t.preview_video_url ?? null,
@@ -21220,7 +21219,8 @@ var PagesEndpoint = class {
21220
21219
  }
21221
21220
  async list(params) {
21222
21221
  const { page, limit, offset } = this.client.resolvePagination(params);
21223
- let query = this.client.db.from("pages").select("*", { count: "exact" }).eq("site_id", this.client.siteIdNumber).is("draft_deleted_at", null).order("path");
21222
+ let query = this.client.db.from("pages").select("*", { count: "exact" }).eq("site_id", this.client.siteIdNumber).is("draft_deleted_at", null);
21223
+ query = params?.order ? query.order("created_at", { ascending: params.order === "asc" }) : query.order("path");
21224
21224
  if (params?.status)
21225
21225
  query = query.eq("status", params.status);
21226
21226
  const { data, error: error48, count } = await query.range(offset, offset + limit - 1);
@@ -36274,8 +36274,10 @@ var CollectionsEndpoint = class {
36274
36274
  get basePath() {
36275
36275
  return `${this.client.siteBasePath}/collections`;
36276
36276
  }
36277
- async list() {
36278
- const { data, error: error48 } = await this.client.db.from("collections").select("*").eq("site_id", this.client.siteIdNumber).is("draft_deleted_at", null).order("name");
36277
+ async list(params) {
36278
+ let query = this.client.db.from("collections").select("*").eq("site_id", this.client.siteIdNumber).is("draft_deleted_at", null);
36279
+ query = params?.order ? query.order("created_at", { ascending: params.order === "asc" }) : query.order("name");
36280
+ const { data, error: error48 } = await query;
36279
36281
  if (error48)
36280
36282
  throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
36281
36283
  return (data ?? []).map((r) => serializeCollection(r));
@@ -36348,7 +36350,7 @@ var EntriesEndpoint = class {
36348
36350
  }
36349
36351
  async list(collectionId, params) {
36350
36352
  const { page, limit, offset } = this.client.resolvePagination(params);
36351
- let query = this.client.db.from("entries").select("*", { count: "exact" }).eq("site_id", this.client.siteIdNumber).eq("collection_id", collectionId).is("deleted_at", null).order("created_at", { ascending: false });
36353
+ let query = this.client.db.from("entries").select("*", { count: "exact" }).eq("site_id", this.client.siteIdNumber).eq("collection_id", collectionId).is("deleted_at", null).order("created_at", { ascending: params?.order === "asc" });
36352
36354
  if (params?.status)
36353
36355
  query = query.eq("status", params.status);
36354
36356
  if (params?.locale)
@@ -36439,8 +36441,10 @@ var FormsEndpoint = class {
36439
36441
  get basePath() {
36440
36442
  return `${this.client.siteBasePath}/forms`;
36441
36443
  }
36442
- async list() {
36443
- const { data, error: error48 } = await this.client.db.from("forms").select("*").eq("site_id", this.client.siteIdNumber).is("draft_deleted_at", null).order("name");
36444
+ async list(params) {
36445
+ let query = this.client.db.from("forms").select("*").eq("site_id", this.client.siteIdNumber).is("draft_deleted_at", null);
36446
+ query = params?.order ? query.order("created_at", { ascending: params.order === "asc" }) : query.order("name");
36447
+ const { data, error: error48 } = await query;
36444
36448
  if (error48)
36445
36449
  throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
36446
36450
  const rows = data ?? [];
@@ -36519,7 +36523,7 @@ var AssetsEndpoint = class {
36519
36523
  }
36520
36524
  async list(params) {
36521
36525
  const { page, limit, offset } = this.client.resolvePagination(params);
36522
- const { data, error: error48, count } = await this.client.db.from("assets").select("*", { count: "exact" }).eq("site_id", this.client.siteIdNumber).order("created_at", { ascending: false }).range(offset, offset + limit - 1);
36526
+ const { data, error: error48, count } = await this.client.db.from("assets").select("*", { count: "exact" }).eq("site_id", this.client.siteIdNumber).order("created_at", { ascending: params?.order === "asc" }).range(offset, offset + limit - 1);
36523
36527
  if (error48)
36524
36528
  throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
36525
36529
  const rows = data ?? [];
@@ -36585,7 +36589,7 @@ var SiteEndpoint = class {
36585
36589
  constructor(client) {
36586
36590
  this.client = client;
36587
36591
  }
36588
- async list() {
36592
+ async list(params) {
36589
36593
  const profileId = this.client.authUserId;
36590
36594
  if (!profileId) {
36591
36595
  throw new NeopressApiError("NO_AUTH", "site.list requires an authenticated access token", 0);
@@ -36593,10 +36597,15 @@ var SiteEndpoint = class {
36593
36597
  const { data, error: error48 } = await this.client.db.from("sites_profiles").select("role, sites!inner(*)").eq("profile_id", profileId).eq("status", "active");
36594
36598
  if (error48)
36595
36599
  throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
36596
- return (data ?? []).map((m) => ({
36600
+ const sites = (data ?? []).map((m) => ({
36597
36601
  ...serializeSite(m.sites),
36598
36602
  role: m.role
36599
36603
  }));
36604
+ if (params?.order) {
36605
+ const dir = params.order === "asc" ? 1 : -1;
36606
+ sites.sort((a, b) => (a.createdAt ?? "") < (b.createdAt ?? "") ? -dir : (a.createdAt ?? "") > (b.createdAt ?? "") ? dir : 0);
36607
+ }
36608
+ return sites;
36600
36609
  }
36601
36610
  async get() {
36602
36611
  const { data, error: error48 } = await this.client.db.from("sites").select("*").eq("id", this.client.siteIdNumber).single();
@@ -36634,8 +36643,10 @@ var RedirectsEndpoint = class {
36634
36643
  constructor(client) {
36635
36644
  this.client = client;
36636
36645
  }
36637
- async list() {
36638
- const { data, error: error48 } = await this.client.db.from("redirect_rules").select("*").eq("site_id", this.client.siteIdNumber).order("priority", { ascending: true });
36646
+ async list(params) {
36647
+ let query = this.client.db.from("redirect_rules").select("*").eq("site_id", this.client.siteIdNumber);
36648
+ query = params?.order ? query.order("created_at", { ascending: params.order === "asc" }) : query.order("priority", { ascending: true });
36649
+ const { data, error: error48 } = await query;
36639
36650
  if (error48)
36640
36651
  throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
36641
36652
  return (data ?? []).map((r) => serializeRedirect(r));
@@ -36645,7 +36656,7 @@ var RedirectsEndpoint = class {
36645
36656
  site_id: this.client.siteIdNumber,
36646
36657
  source: data.source,
36647
36658
  destination: data.destination,
36648
- redirect_type: data.redirectType ?? 307,
36659
+ redirect_type: data.redirectType ?? 308,
36649
36660
  include_children: data.includeChildren ?? false,
36650
36661
  preserve_path: data.preservePath ?? false,
36651
36662
  enabled: data.enabled !== false,
@@ -36712,7 +36723,7 @@ var EntryReferencesEndpoint = class {
36712
36723
  query = query.eq("from_entry_id", params.fromEntryId);
36713
36724
  if (params?.toEntryId)
36714
36725
  query = query.eq("to_entry_id", params.toEntryId);
36715
- const { data, error: error48 } = await query.order("created_at", { ascending: false });
36726
+ const { data, error: error48 } = await query.order("created_at", { ascending: params?.order === "asc" });
36716
36727
  if (error48)
36717
36728
  throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
36718
36729
  return (data ?? []).map((r) => serializeEntryReference(r));
@@ -36834,18 +36845,53 @@ var TemplatesEndpoint = class {
36834
36845
  const res = await this.client.delete(this.basePath);
36835
36846
  return res.data;
36836
36847
  }
36848
+ /**
36849
+ * List templates across the sites the caller can access.
36850
+ *
36851
+ * Direct-DB: `templates` is public-read and `sites` RLS scopes the visible set
36852
+ * (own active memberships, or all sites for the super admin), so no server
36853
+ * route is needed — the two RLS-scoped queries reproduce the old handler.
36854
+ */
36837
36855
  async list(params) {
36838
- const qs = new URLSearchParams();
36839
- if (params?.namePrefix)
36840
- qs.set("namePrefix", params.namePrefix);
36841
- if (params?.industry)
36842
- qs.set("industry", params.industry);
36843
- if (params?.limit != null)
36844
- qs.set("limit", String(params.limit));
36845
- const query = qs.toString();
36846
- const path = query ? `/api/v1/templates?${query}` : "/api/v1/templates";
36847
- const res = await this.client.get(path);
36848
- return res.data;
36856
+ const { data: sitesData, error: sitesError } = await this.client.db.from("sites").select("id, handle, name");
36857
+ if (sitesError)
36858
+ throw new NeopressApiError("DB_ERROR", sitesError.message, 0, sitesError);
36859
+ const siteMeta = /* @__PURE__ */ new Map();
36860
+ for (const site of sitesData ?? []) {
36861
+ if (typeof site.id !== "number")
36862
+ continue;
36863
+ siteMeta.set(site.id, {
36864
+ handle: site.handle ?? null,
36865
+ name: site.name ?? null
36866
+ });
36867
+ }
36868
+ const siteIds = [...siteMeta.keys()];
36869
+ if (siteIds.length === 0)
36870
+ return [];
36871
+ const namePrefix = params?.namePrefix?.trim();
36872
+ const limit = Math.min(Math.max(params?.limit ?? 100, 1), 500);
36873
+ let query = this.client.db.from("templates").select("id, name, reference_url, preview_img_urls, description, created_at").in("id", siteIds);
36874
+ if (namePrefix)
36875
+ query = query.ilike("name", `${namePrefix}%`);
36876
+ if (params?.order)
36877
+ query = query.order("created_at", { ascending: params.order === "asc" });
36878
+ query = query.limit(limit);
36879
+ const { data, error: error48 } = await query;
36880
+ if (error48)
36881
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
36882
+ return (data ?? []).map((t) => {
36883
+ const siteId = t.id;
36884
+ return {
36885
+ siteId,
36886
+ handle: siteMeta.get(siteId)?.handle ?? null,
36887
+ siteName: siteMeta.get(siteId)?.name ?? null,
36888
+ name: t.name,
36889
+ referenceUrl: t.reference_url,
36890
+ previewImgUrls: t.preview_img_urls ?? null,
36891
+ description: t.description ?? null,
36892
+ createdAt: t.created_at
36893
+ };
36894
+ });
36849
36895
  }
36850
36896
  };
36851
36897
 
@@ -36868,7 +36914,12 @@ var HeadshotsEndpoint = class {
36868
36914
  query = query.eq("attire", params.attire);
36869
36915
  if (params?.isActive !== void 0)
36870
36916
  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);
36917
+ if (params?.order) {
36918
+ query = query.order("created_at", { ascending: params.order === "asc" });
36919
+ } else {
36920
+ query = query.order("age_band", { ascending: true }).order("gender", { ascending: true }).order("ethnicity", { ascending: true }).order("variation_index", { ascending: true });
36921
+ }
36922
+ const { data, error: error48, count } = await query.range(offset, offset + limit - 1);
36872
36923
  if (error48)
36873
36924
  throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
36874
36925
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neopress/cli",
3
- "version": "4.2.0",
3
+ "version": "4.4.0",
4
4
  "description": "Neopress CLI — manage your Neopress site from the terminal",
5
5
  "type": "module",
6
6
  "keywords": [