@neopress/cli 4.0.0 → 4.2.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 (4) hide show
  1. package/README.md +109 -0
  2. package/dist/bin.cjs +697 -117
  3. package/dist/index.js +207 -44
  4. package/package.json +12 -2
package/dist/bin.cjs CHANGED
@@ -3387,6 +3387,7 @@ function fail(message, code = 1) {
3387
3387
  // src/utils/config.ts
3388
3388
  var import_node_fs2 = require("fs");
3389
3389
  var import_node_path2 = require("path");
3390
+ var import_node_os2 = require("os");
3390
3391
 
3391
3392
  // src/utils/token-store.ts
3392
3393
  var import_node_fs = require("fs");
@@ -3421,31 +3422,55 @@ function clearSession() {
3421
3422
  // src/utils/config.ts
3422
3423
  var PROJECT_CONFIG = ".neopressrc.json";
3423
3424
  var WORKSPACE_CONFIG = ".neopress/config.json";
3424
- function readProjectConfig() {
3425
- let config2 = {};
3426
- for (const file2 of [PROJECT_CONFIG, WORKSPACE_CONFIG]) {
3427
- try {
3428
- const parsed = JSON.parse((0, import_node_fs2.readFileSync)(file2, "utf-8"));
3429
- config2 = {
3430
- ...config2,
3431
- ...typeof parsed.siteId === "number" ? { siteId: parsed.siteId } : {},
3432
- ...typeof parsed.baseUrl === "string" ? { baseUrl: parsed.baseUrl } : {}
3433
- };
3434
- } catch {
3425
+ function readConfigFileAt(path) {
3426
+ try {
3427
+ const parsed = JSON.parse((0, import_node_fs2.readFileSync)(path, "utf-8"));
3428
+ return {
3429
+ ...typeof parsed.siteId === "number" ? { siteId: parsed.siteId } : {},
3430
+ ...typeof parsed.baseUrl === "string" ? { baseUrl: parsed.baseUrl } : {}
3431
+ };
3432
+ } catch {
3433
+ return {};
3434
+ }
3435
+ }
3436
+ function locateProjectConfig(startDir = process.cwd()) {
3437
+ const home = (0, import_node_path2.resolve)((0, import_node_os2.homedir)());
3438
+ let dir = (0, import_node_path2.resolve)(startDir);
3439
+ while (true) {
3440
+ let merged = {};
3441
+ let foundPath;
3442
+ for (const rel of [PROJECT_CONFIG, WORKSPACE_CONFIG]) {
3443
+ const p = (0, import_node_path2.join)(dir, rel);
3444
+ const cfg = readConfigFileAt(p);
3445
+ if (Object.keys(cfg).length > 0) {
3446
+ merged = { ...merged, ...cfg };
3447
+ if (!foundPath) foundPath = p;
3448
+ }
3435
3449
  }
3450
+ if (Object.keys(merged).length > 0) return { config: merged, path: foundPath };
3451
+ const parent = (0, import_node_path2.dirname)(dir);
3452
+ if (dir === home || parent === dir) break;
3453
+ dir = parent;
3436
3454
  }
3437
- return config2;
3455
+ return { config: {} };
3456
+ }
3457
+ function readProjectConfig() {
3458
+ return locateProjectConfig().config;
3438
3459
  }
3439
3460
  function writeWorkspaceConfig(next) {
3440
3461
  (0, import_node_fs2.mkdirSync)((0, import_node_path2.dirname)(WORKSPACE_CONFIG), { recursive: true });
3441
3462
  (0, import_node_fs2.writeFileSync)(WORKSPACE_CONFIG, JSON.stringify(next, null, 2) + "\n", "utf-8");
3442
3463
  }
3443
- function getSiteId() {
3444
- const envSiteId = process.env.NEOPRESS_SITE_ID;
3445
- if (envSiteId) return parseInt(envSiteId, 10);
3446
- const projectConfig = readProjectConfig();
3447
- if (projectConfig.siteId) return projectConfig.siteId;
3448
- return loadSession().activeSiteId;
3464
+ function writeProjectSiteId(siteId) {
3465
+ let raw = {};
3466
+ try {
3467
+ const parsed = JSON.parse((0, import_node_fs2.readFileSync)(PROJECT_CONFIG, "utf-8"));
3468
+ if (parsed && typeof parsed === "object") raw = parsed;
3469
+ } catch {
3470
+ }
3471
+ const next = { ...raw, siteId };
3472
+ (0, import_node_fs2.writeFileSync)(PROJECT_CONFIG, JSON.stringify(next, null, 2) + "\n", "utf-8");
3473
+ return (0, import_node_path2.resolve)(PROJECT_CONFIG);
3449
3474
  }
3450
3475
  function getBaseUrl() {
3451
3476
  return process.env.NEOPRESS_BASE_URL || readProjectConfig().baseUrl || "https://app.neopress.ai";
@@ -3461,12 +3486,24 @@ var siteOverride;
3461
3486
  function setSiteOverride(value) {
3462
3487
  siteOverride = value;
3463
3488
  }
3464
- function getSiteTarget() {
3489
+ function resolveSiteTarget() {
3465
3490
  if (siteOverride) {
3466
3491
  const n = parseInt(siteOverride, 10);
3467
- return String(n) === siteOverride ? n : siteOverride;
3492
+ const value = String(n) === siteOverride ? n : siteOverride;
3493
+ return { value, source: "flag", detail: "--site" };
3468
3494
  }
3469
- return getSiteId();
3495
+ const envSiteId = process.env.NEOPRESS_SITE_ID;
3496
+ if (envSiteId) return { value: parseInt(envSiteId, 10), source: "env", detail: "NEOPRESS_SITE_ID" };
3497
+ const located = locateProjectConfig();
3498
+ if (typeof located.config.siteId === "number") {
3499
+ return { value: located.config.siteId, source: "project-config", detail: located.path };
3500
+ }
3501
+ const active = loadSession().activeSiteId;
3502
+ if (active) return { value: active, source: "global-active", detail: "sites use" };
3503
+ return { value: void 0, source: "none" };
3504
+ }
3505
+ function getSiteTarget() {
3506
+ return resolveSiteTarget().value;
3470
3507
  }
3471
3508
 
3472
3509
  // src/utils/pkce.ts
@@ -3652,7 +3689,12 @@ function parseImplicitCallbackTokens(callbackUrl) {
3652
3689
  };
3653
3690
  }
3654
3691
  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) => {
3692
+ program3.command("login").description("Authenticate with Neopress").addHelpText("after", `
3693
+ Examples:
3694
+ $ neopress login
3695
+ $ neopress login --manual
3696
+ $ neopress login --provider google
3697
+ 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
3698
  console.log(options.manual ? "Starting manual browser login..." : "Opening browser for login...");
3657
3699
  const codeVerifier = generateCodeVerifier();
3658
3700
  const codeChallenge = generateCodeChallenge(codeVerifier);
@@ -3754,11 +3796,11 @@ function __rest(s, e) {
3754
3796
  }
3755
3797
  function __awaiter(thisArg, _arguments, P, generator) {
3756
3798
  function adopt(value) {
3757
- return value instanceof P ? value : new P(function(resolve) {
3758
- resolve(value);
3799
+ return value instanceof P ? value : new P(function(resolve2) {
3800
+ resolve2(value);
3759
3801
  });
3760
3802
  }
3761
- return new (P || (P = Promise))(function(resolve, reject) {
3803
+ return new (P || (P = Promise))(function(resolve2, reject) {
3762
3804
  function fulfilled(value) {
3763
3805
  try {
3764
3806
  step(generator.next(value));
@@ -3774,7 +3816,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
3774
3816
  }
3775
3817
  }
3776
3818
  function step(result) {
3777
- result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
3819
+ result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
3778
3820
  }
3779
3821
  step((generator = generator.apply(thisArg, _arguments || [])).next());
3780
3822
  });
@@ -4145,18 +4187,18 @@ var PostgrestError = class extends Error {
4145
4187
  }
4146
4188
  };
4147
4189
  function sleep(ms, signal) {
4148
- return new Promise((resolve) => {
4190
+ return new Promise((resolve2) => {
4149
4191
  if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
4150
- resolve();
4192
+ resolve2();
4151
4193
  return;
4152
4194
  }
4153
4195
  const id = setTimeout(() => {
4154
4196
  signal === null || signal === void 0 || signal.removeEventListener("abort", onAbort);
4155
- resolve();
4197
+ resolve2();
4156
4198
  }, ms);
4157
4199
  function onAbort() {
4158
4200
  clearTimeout(id);
4159
- resolve();
4201
+ resolve2();
4160
4202
  }
4161
4203
  signal === null || signal === void 0 || signal.addEventListener("abort", onAbort);
4162
4204
  });
@@ -12105,15 +12147,15 @@ var RealtimeChannel = class _RealtimeChannel {
12105
12147
  }
12106
12148
  }
12107
12149
  } else {
12108
- return new Promise((resolve) => {
12150
+ return new Promise((resolve2) => {
12109
12151
  var _a3, _b2, _c;
12110
12152
  const push = this.channelAdapter.push(args.type, args, opts.timeout || this.timeout);
12111
12153
  if (args.type === "broadcast" && !((_c = (_b2 = (_a3 = this.params) === null || _a3 === void 0 ? void 0 : _a3.config) === null || _b2 === void 0 ? void 0 : _b2.broadcast) === null || _c === void 0 ? void 0 : _c.ack)) {
12112
- resolve("ok");
12154
+ resolve2("ok");
12113
12155
  }
12114
- push.receive("ok", () => resolve("ok"));
12115
- push.receive("error", () => resolve("error"));
12116
- push.receive("timeout", () => resolve("timed out"));
12156
+ push.receive("ok", () => resolve2("ok"));
12157
+ push.receive("error", () => resolve2("error"));
12158
+ push.receive("timeout", () => resolve2("timed out"));
12117
12159
  });
12118
12160
  }
12119
12161
  }
@@ -12138,8 +12180,8 @@ var RealtimeChannel = class _RealtimeChannel {
12138
12180
  * @category Realtime
12139
12181
  */
12140
12182
  async unsubscribe(timeout = this.timeout) {
12141
- return new Promise((resolve) => {
12142
- this.channelAdapter.unsubscribe(timeout).receive("ok", () => resolve("ok")).receive("timeout", () => resolve("timed out")).receive("error", () => resolve("error"));
12183
+ return new Promise((resolve2) => {
12184
+ this.channelAdapter.unsubscribe(timeout).receive("ok", () => resolve2("ok")).receive("timeout", () => resolve2("timed out")).receive("error", () => resolve2("error"));
12143
12185
  });
12144
12186
  }
12145
12187
  /**
@@ -12220,8 +12262,8 @@ var RealtimeChannel = class _RealtimeChannel {
12220
12262
  }
12221
12263
  /** @internal */
12222
12264
  _notThisChannelEvent(event, ref) {
12223
- const { close, error: error48, leave, join: join2 } = CHANNEL_EVENTS;
12224
- const events = [close, error48, leave, join2];
12265
+ const { close, error: error48, leave, join: join3 } = CHANNEL_EVENTS;
12266
+ const events = [close, error48, leave, join3];
12225
12267
  return ref && events.includes(event) && ref !== this.joinPush.ref;
12226
12268
  }
12227
12269
  /** @internal */
@@ -12334,11 +12376,11 @@ var SocketAdapter = class {
12334
12376
  this.socket.connect();
12335
12377
  }
12336
12378
  disconnect(callback, code, reason, timeout = 1e4) {
12337
- return new Promise((resolve) => {
12338
- setTimeout(() => resolve("timeout"), timeout);
12379
+ return new Promise((resolve2) => {
12380
+ setTimeout(() => resolve2("timeout"), timeout);
12339
12381
  this.socket.disconnect(() => {
12340
12382
  callback();
12341
- resolve("ok");
12383
+ resolve2("ok");
12342
12384
  }, code, reason);
12343
12385
  });
12344
12386
  }
@@ -13752,7 +13794,7 @@ var _getRequestParams = (method, options, parameters, body) => {
13752
13794
  return _objectSpread22(_objectSpread22({}, params), parameters);
13753
13795
  };
13754
13796
  async function _handleRequest(fetcher, method, url2, options, parameters, body, namespace) {
13755
- return new Promise((resolve, reject) => {
13797
+ return new Promise((resolve2, reject) => {
13756
13798
  fetcher(url2, _getRequestParams(method, options, parameters, body)).then((result) => {
13757
13799
  if (!result.ok) throw result;
13758
13800
  if (options === null || options === void 0 ? void 0 : options.noResolveJson) return result;
@@ -13762,7 +13804,7 @@ async function _handleRequest(fetcher, method, url2, options, parameters, body,
13762
13804
  if (!contentType || !contentType.includes("application/json")) return {};
13763
13805
  }
13764
13806
  return result.json();
13765
- }).then((data) => resolve(data)).catch((error48) => handleError(error48, reject, options, namespace));
13807
+ }).then((data) => resolve2(data)).catch((error48) => handleError(error48, reject, options, namespace));
13766
13808
  });
13767
13809
  }
13768
13810
  function createFetchApi(namespace = "storage") {
@@ -24007,11 +24049,11 @@ var DEFAULT_TRACE_PROPAGATION_OPTIONS = {
24007
24049
  };
24008
24050
  function __awaiter2(thisArg, _arguments, P, generator) {
24009
24051
  function adopt(value) {
24010
- return value instanceof P ? value : new P(function(resolve) {
24011
- resolve(value);
24052
+ return value instanceof P ? value : new P(function(resolve2) {
24053
+ resolve2(value);
24012
24054
  });
24013
24055
  }
24014
- return new (P || (P = Promise))(function(resolve, reject) {
24056
+ return new (P || (P = Promise))(function(resolve2, reject) {
24015
24057
  function fulfilled(value) {
24016
24058
  try {
24017
24059
  step(generator.next(value));
@@ -24027,7 +24069,7 @@ function __awaiter2(thisArg, _arguments, P, generator) {
24027
24069
  }
24028
24070
  }
24029
24071
  function step(result) {
24030
- result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
24072
+ result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
24031
24073
  }
24032
24074
  step((generator = generator.apply(thisArg, _arguments || [])).next());
24033
24075
  });
@@ -24882,6 +24924,27 @@ function serializeTemplate(t) {
24882
24924
  createdAt: t.created_at
24883
24925
  };
24884
24926
  }
24927
+ function serializeHeadshot(h) {
24928
+ return {
24929
+ id: h.id,
24930
+ imageUrl: h.image_url,
24931
+ width: h.width,
24932
+ height: h.height,
24933
+ aspectRatio: h.aspect_ratio,
24934
+ mimeType: h.mime_type,
24935
+ byteSize: h.byte_size,
24936
+ ageBand: h.age_band,
24937
+ gender: h.gender,
24938
+ ethnicity: h.ethnicity,
24939
+ attire: h.attire,
24940
+ variationIndex: h.variation_index,
24941
+ gptPrompt: h.gpt_prompt,
24942
+ tags: h.tags ?? [],
24943
+ isActive: h.is_active,
24944
+ createdAt: h.created_at,
24945
+ updatedAt: h.updated_at
24946
+ };
24947
+ }
24885
24948
  function serializeSite(site) {
24886
24949
  return {
24887
24950
  id: site.id,
@@ -39228,8 +39291,8 @@ var SiteHistoryService = class _SiteHistoryService {
39228
39291
  const previousLock = _SiteHistoryService.sessionLocks.get(key) ?? Promise.resolve();
39229
39292
  let releaseCurrentLock = () => {
39230
39293
  };
39231
- const currentLock = new Promise((resolve) => {
39232
- releaseCurrentLock = resolve;
39294
+ const currentLock = new Promise((resolve2) => {
39295
+ releaseCurrentLock = resolve2;
39233
39296
  });
39234
39297
  const queuedLock = previousLock.catch(() => {
39235
39298
  }).then(() => currentLock);
@@ -40564,6 +40627,113 @@ var TemplatesEndpoint = class {
40564
40627
  }
40565
40628
  };
40566
40629
 
40630
+ // ../sdk/dist/endpoints/headshots.js
40631
+ var HeadshotsEndpoint = class {
40632
+ client;
40633
+ constructor(client) {
40634
+ this.client = client;
40635
+ }
40636
+ async list(params) {
40637
+ const { page, limit, offset } = this.client.resolvePagination(params);
40638
+ let query = this.client.db.from("headshot_pool").select("*", { count: "exact" });
40639
+ if (params?.ageBand)
40640
+ query = query.eq("age_band", params.ageBand);
40641
+ if (params?.gender)
40642
+ query = query.eq("gender", params.gender);
40643
+ if (params?.ethnicity)
40644
+ query = query.eq("ethnicity", params.ethnicity);
40645
+ if (params?.attire)
40646
+ query = query.eq("attire", params.attire);
40647
+ if (params?.isActive !== void 0)
40648
+ 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);
40650
+ if (error48)
40651
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40652
+ return {
40653
+ data: (data ?? []).map((r) => serializeHeadshot(r)),
40654
+ pagination: this.client.paginationMeta(page, limit, offset, count ?? 0)
40655
+ };
40656
+ }
40657
+ async get(id) {
40658
+ const { data, error: error48 } = await this.client.db.from("headshot_pool").select("*").eq("id", id).single();
40659
+ if (error48)
40660
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40661
+ return serializeHeadshot(data);
40662
+ }
40663
+ async create(params) {
40664
+ const values = {
40665
+ image_url: params.imageUrl,
40666
+ age_band: params.ageBand,
40667
+ gender: params.gender,
40668
+ ethnicity: params.ethnicity,
40669
+ variation_index: params.variationIndex
40670
+ };
40671
+ if (params.attire !== void 0)
40672
+ values.attire = params.attire;
40673
+ if (params.width !== void 0)
40674
+ values.width = params.width;
40675
+ if (params.height !== void 0)
40676
+ values.height = params.height;
40677
+ if (params.aspectRatio !== void 0)
40678
+ values.aspect_ratio = params.aspectRatio;
40679
+ if (params.mimeType !== void 0)
40680
+ values.mime_type = params.mimeType;
40681
+ if (params.byteSize !== void 0)
40682
+ values.byte_size = params.byteSize;
40683
+ if (params.gptPrompt !== void 0)
40684
+ values.gpt_prompt = params.gptPrompt;
40685
+ if (params.tags !== void 0)
40686
+ values.tags = params.tags;
40687
+ if (params.isActive !== void 0)
40688
+ values.is_active = params.isActive;
40689
+ const { data, error: error48 } = await this.client.db.from("headshot_pool").insert(values).select().single();
40690
+ if (error48)
40691
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40692
+ return serializeHeadshot(data);
40693
+ }
40694
+ async update(id, params) {
40695
+ const values = {};
40696
+ if (params.imageUrl !== void 0)
40697
+ values.image_url = params.imageUrl;
40698
+ if (params.ageBand !== void 0)
40699
+ values.age_band = params.ageBand;
40700
+ if (params.gender !== void 0)
40701
+ values.gender = params.gender;
40702
+ if (params.ethnicity !== void 0)
40703
+ values.ethnicity = params.ethnicity;
40704
+ if (params.attire !== void 0)
40705
+ values.attire = params.attire;
40706
+ if (params.variationIndex !== void 0)
40707
+ values.variation_index = params.variationIndex;
40708
+ if (params.width !== void 0)
40709
+ values.width = params.width;
40710
+ if (params.height !== void 0)
40711
+ values.height = params.height;
40712
+ if (params.aspectRatio !== void 0)
40713
+ values.aspect_ratio = params.aspectRatio;
40714
+ if (params.mimeType !== void 0)
40715
+ values.mime_type = params.mimeType;
40716
+ if (params.byteSize !== void 0)
40717
+ values.byte_size = params.byteSize;
40718
+ if (params.gptPrompt !== void 0)
40719
+ values.gpt_prompt = params.gptPrompt;
40720
+ if (params.tags !== void 0)
40721
+ values.tags = params.tags;
40722
+ if (params.isActive !== void 0)
40723
+ values.is_active = params.isActive;
40724
+ const { data, error: error48 } = await this.client.db.from("headshot_pool").update(values).eq("id", id).select().single();
40725
+ if (error48)
40726
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40727
+ return serializeHeadshot(data);
40728
+ }
40729
+ async delete(id) {
40730
+ const { error: error48 } = await this.client.db.from("headshot_pool").delete().eq("id", id);
40731
+ if (error48)
40732
+ throw new NeopressApiError("DB_ERROR", error48.message, 0, error48);
40733
+ return { id, deleted: true };
40734
+ }
40735
+ };
40736
+
40567
40737
  // ../sdk/dist/client.js
40568
40738
  var NeopressClient = class {
40569
40739
  baseUrl;
@@ -40584,6 +40754,7 @@ var NeopressClient = class {
40584
40754
  entryReferences;
40585
40755
  analytics;
40586
40756
  templates;
40757
+ headshots;
40587
40758
  constructor(options) {
40588
40759
  this.baseUrl = (options.baseUrl || "https://app.neopress.ai").replace(/\/$/, "");
40589
40760
  this.accessToken = options.accessToken || "";
@@ -40602,6 +40773,7 @@ var NeopressClient = class {
40602
40773
  this.entryReferences = new EntryReferencesEndpoint(this);
40603
40774
  this.analytics = new AnalyticsEndpoint(this);
40604
40775
  this.templates = new TemplatesEndpoint(this);
40776
+ this.headshots = new HeadshotsEndpoint(this);
40605
40777
  }
40606
40778
  get siteBasePath() {
40607
40779
  return `/api/v1/sites/${this.siteId}`;
@@ -40731,39 +40903,42 @@ async function getClientAsync(siteIdOverride) {
40731
40903
  const sb = getSupabaseConfig();
40732
40904
  return new NeopressClient({ accessToken, siteId, baseUrl: getBaseUrl(), supabaseUrl: sb.url, supabaseAnonKey: sb.anonKey });
40733
40905
  }
40906
+ async function getGlobalClientAsync() {
40907
+ const token = process.env.NEOPRESS_ACCESS_TOKEN || await getValidAccessToken();
40908
+ if (!token) {
40909
+ fail("Not authenticated. Run `neopress login` or set NEOPRESS_ACCESS_TOKEN.");
40910
+ }
40911
+ const sb = getSupabaseConfig();
40912
+ return new NeopressClient({ accessToken: token, siteId: 0, baseUrl: getBaseUrl(), supabaseUrl: sb.url, supabaseAnonKey: sb.anonKey });
40913
+ }
40734
40914
 
40735
40915
  // src/commands/sites.ts
40736
40916
  function registerSiteCommands(program3) {
40737
- const sites = program3.command("sites").description("Manage sites");
40917
+ const sites = program3.command("sites").description("Manage sites").addHelpText("after", `
40918
+ Examples:
40919
+ $ neopress sites list
40920
+ $ neopress sites create --name "My Site" --handle my-site --use
40921
+ $ neopress sites create --name "My Site" --handle my-site --pin
40922
+ $ neopress sites use my-site
40923
+ $ neopress sites current
40924
+ 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.`);
40738
40925
  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
- });
40926
+ const client = await getGlobalClientAsync();
40748
40927
  const sitesList = await client.site.list();
40749
40928
  output(sitesList);
40750
40929
  });
40751
- sites.command("use <siteIdOrHandle>").description("Set active site (ID or handle)").action(async (siteIdOrHandle) => {
40930
+ sites.command("use <siteIdOrHandle>").description("Set active site (ID or handle)").addHelpText("after", `
40931
+ Examples:
40932
+ $ neopress sites use 12
40933
+ $ neopress sites use my-site
40934
+ 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
40935
  const id = parseInt(siteIdOrHandle, 10);
40753
40936
  if (!isNaN(id)) {
40754
40937
  setActiveSiteId(id);
40755
40938
  ok(`Active site set to ${id}`);
40756
40939
  return;
40757
40940
  }
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
- });
40941
+ const client = await getGlobalClientAsync();
40767
40942
  const sitesList = await client.site.list();
40768
40943
  const match = sitesList.find((s) => s.handle === siteIdOrHandle);
40769
40944
  if (!match) {
@@ -40772,37 +40947,51 @@ function registerSiteCommands(program3) {
40772
40947
  setActiveSiteId(match.id);
40773
40948
  ok(`Active site set to ${match.id} (${match.handle})`);
40774
40949
  });
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) => {
40950
+ sites.command("create").description("Create a new site").addHelpText("after", `
40951
+ Examples:
40952
+ $ neopress sites create --name "My Site" --handle my-site
40953
+ $ neopress sites create --prompt "a portfolio for a freelance photographer" --use
40954
+ $ neopress sites create --name Acme --handle acme --language en
40955
+ Pass --prompt to auto-generate name/handle, or supply both --name and --handle. --language sets the default content locale (default en).
40956
+ --use sets the new site as the GLOBAL active site (shared across all directories). --pin instead writes ./.neopressrc.json for THIS directory only, leaving the global active site untouched \u2014 prefer it for parallel/automated runs so concurrent builds don't stomp each other's active 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 the global active site").option("--pin", "Pin the created site to this directory via ./.neopressrc.json (no global state)").action(async (options) => {
40776
40957
  if (!options.prompt && !(options.name && options.handle)) {
40777
40958
  fail("Provide --prompt or both --name and --handle.");
40778
40959
  }
40779
- const token = process.env.NEOPRESS_ACCESS_TOKEN || await getValidAccessToken() || void 0;
40780
- if (!token) {
40781
- fail("Not authenticated. Run `neopress login`.");
40960
+ if (options.use && options.pin) {
40961
+ fail("Pass only one of --use (global active site) or --pin (this directory only).");
40782
40962
  }
40783
- const client = new NeopressClient({
40784
- accessToken: token,
40785
- siteId: 0,
40786
- baseUrl: getBaseUrl()
40787
- });
40963
+ const client = await getGlobalClientAsync();
40788
40964
  const site = await client.site.create({
40789
40965
  siteName: options.name,
40790
40966
  siteHandle: options.handle,
40791
40967
  prompt: options.prompt,
40792
40968
  language: options.language
40793
40969
  });
40794
- if (options.use) {
40795
- setActiveSiteId(site.id);
40796
- }
40970
+ if (options.use) setActiveSiteId(site.id);
40971
+ let pinnedPath;
40972
+ if (options.pin) pinnedPath = writeProjectSiteId(site.id);
40797
40973
  output(site);
40798
- if (options.use && !isJsonMode()) ok(`Active site set to ${site.id}`);
40974
+ if (!isJsonMode()) {
40975
+ if (options.use) ok(`Active site set to ${site.id}`);
40976
+ if (options.pin) ok(`Pinned site ${site.id} for this directory (${pinnedPath})`);
40977
+ }
40799
40978
  });
40800
40979
  sites.command("get").description("Get current site info").action(async () => {
40801
40980
  const client = await getClientAsync();
40802
40981
  const site = await client.site.get();
40803
40982
  output(site);
40804
40983
  });
40805
- sites.command("update").description("Update site settings").option("--name <name>", "Site name").option("--locale-config <json>", "Locale config JSON").action(async (options) => {
40984
+ sites.command("current").description("Show which site commands will target, and where it resolved from").addHelpText("after", `
40985
+ Resolution order: --site flag > NEOPRESS_SITE_ID > .neopressrc.json/.neopress/config.json (cwd and up) > global active site (set by \`sites use\`).
40986
+ Local check only \u2014 it does not contact the server. Unlike \`whoami\` (which always reports the global active site), this reports the site your next command will actually hit.`).action(() => {
40987
+ const { value, source, detail } = resolveSiteTarget();
40988
+ output({ siteId: value ?? null, source, resolvedFrom: detail ?? null });
40989
+ });
40990
+ sites.command("update").description("Update site settings").addHelpText("after", `
40991
+ Examples:
40992
+ $ neopress sites update --name "New Name"
40993
+ $ neopress sites update --locale-config '{"locales":["en","ko"],"defaultLocale":"en"}'
40994
+ 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
40995
  const client = await getClientAsync();
40807
40996
  const data = {};
40808
40997
  if (options.name) data.name = options.name;
@@ -40819,7 +41008,11 @@ function registerSiteCommands(program3) {
40819
41008
  const site = await client.site.update(data);
40820
41009
  output(site);
40821
41010
  });
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) => {
41011
+ sites.command("delete <siteIdOrHandle>").description("Delete a site and all its content (workspace admin only)").addHelpText("after", `
41012
+ Examples:
41013
+ $ neopress sites delete my-site
41014
+ $ neopress sites delete 12 --yes
41015
+ 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
41016
  const interactive = process.stdout.isTTY && process.stdin.isTTY;
40824
41017
  if (!options.yes && !interactive) {
40825
41018
  fail("Refusing to delete without --yes in non-interactive mode.");
@@ -40828,9 +41021,9 @@ function registerSiteCommands(program3) {
40828
41021
  const { createInterface } = await import("readline");
40829
41022
  const rl = createInterface({ input: process.stdin, output: process.stdout });
40830
41023
  const answer = await new Promise(
40831
- (resolve) => rl.question(`Delete site "${siteIdOrHandle}" and ALL its content? Type the id/handle to confirm: `, (a) => {
41024
+ (resolve2) => rl.question(`Delete site "${siteIdOrHandle}" and ALL its content? Type the id/handle to confirm: `, (a) => {
40832
41025
  rl.close();
40833
- resolve(a.trim());
41026
+ resolve2(a.trim());
40834
41027
  })
40835
41028
  );
40836
41029
  if (answer !== siteIdOrHandle) {
@@ -40878,7 +41071,13 @@ function mergeQueryConfigIntoDraftMeta(draftMetaInput, queryConfigInput) {
40878
41071
  };
40879
41072
  }
40880
41073
  function registerPageCommands(program3) {
40881
- const pages = program3.command("pages").description("Manage pages");
41074
+ const pages = program3.command("pages").description("Manage pages").addHelpText("after", `
41075
+ Examples:
41076
+ $ neopress pages create --path /about --title About --tsx ./about.tsx
41077
+ $ neopress pages update <pageId> --tsx ./about.tsx
41078
+ $ neopress pages compile <pageId>
41079
+
41080
+ Draft content fields (--tsx/--i18n/--draft-meta) need \`neopress publish\` to go live. \`pages delete\` is a soft delete.`);
40882
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) => {
40883
41082
  const client = await getClientAsync();
40884
41083
  const res = await client.pages.list({
@@ -40893,7 +41092,17 @@ function registerPageCommands(program3) {
40893
41092
  const page = await client.pages.get(parseInt(pageId, 10));
40894
41093
  output(page);
40895
41094
  });
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) => {
41095
+ 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(
41096
+ "after",
41097
+ `
41098
+ Examples:
41099
+ $ neopress pages create --path /about --title About --tsx ./about.tsx
41100
+ $ neopress pages create --path /blog --type dynamic --collection 12 --tsx ./blog.tsx
41101
+ $ neopress pages create --path /about --i18n @i18n.json --public-locales en,ko
41102
+
41103
+ --tsx reads the component from a file. JSON options take inline JSON or @file (e.g. --i18n @i18n.json).
41104
+ Pages are created as drafts \u2014 run \`neopress publish\` to make them live.`
41105
+ ).action(async (options) => {
40897
41106
  const client = await getClientAsync();
40898
41107
  const draftTsx = options.tsx ? (0, import_node_fs3.readFileSync)(options.tsx, "utf-8") : void 0;
40899
41108
  const draftI18n = parseJsonInput(options.i18n);
@@ -40914,7 +41123,13 @@ function registerPageCommands(program3) {
40914
41123
  });
40915
41124
  output(page);
40916
41125
  });
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) => {
41126
+ pages.command("update <pageId>").description("Update a page").addHelpText("after", `
41127
+ Examples:
41128
+ $ neopress pages update 12 --tsx ./about.tsx
41129
+ $ neopress pages update 12 --title About --path /about-us
41130
+ $ neopress pages update 12 --collection 12 --query-config @query.json
41131
+
41132
+ 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
41133
  const client = await getClientAsync();
40919
41134
  const data = {};
40920
41135
  if (options.path) data.path = options.path;
@@ -40938,7 +41153,11 @@ function registerPageCommands(program3) {
40938
41153
  await client.pages.delete(parseInt(pageId, 10));
40939
41154
  ok(`Page ${pageId} deleted`);
40940
41155
  });
40941
- pages.command("compile <pageId>").description("Validate TSX compilation").action(async (pageId) => {
41156
+ pages.command("compile <pageId>").description("Validate TSX compilation").addHelpText("after", `
41157
+ Examples:
41158
+ $ neopress pages compile 12
41159
+
41160
+ Validates the page's draft TSX and reports compile status \u2014 it does not save or publish.`).action(async (pageId) => {
40942
41161
  const client = await getClientAsync();
40943
41162
  const result = await client.pages.compile(parseInt(pageId, 10));
40944
41163
  output(result);
@@ -40952,7 +41171,13 @@ function parseJsonInput2(input) {
40952
41171
  return input.startsWith("@") ? JSON.parse((0, import_node_fs4.readFileSync)(input.slice(1), "utf-8")) : JSON.parse(input);
40953
41172
  }
40954
41173
  function registerCollectionCommands(program3) {
40955
- const collections = program3.command("collections").description("Manage collections");
41174
+ const collections = program3.command("collections").description("Manage collections").addHelpText("after", `
41175
+ Examples:
41176
+ $ neopress collections create --name Posts --schema @schema.json
41177
+ $ neopress collections list
41178
+ $ neopress collections update 12 --schema @schema.json
41179
+
41180
+ 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
41181
  collections.command("list").description("List collections").action(async () => {
40957
41182
  const client = await getClientAsync();
40958
41183
  const data = await client.collections.list();
@@ -40963,7 +41188,13 @@ function registerCollectionCommands(program3) {
40963
41188
  const data = await client.collections.get(parseInt(collectionId, 10));
40964
41189
  output(data);
40965
41190
  });
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) => {
41191
+ collections.command("create").description("Create a collection").addHelpText("after", `
41192
+ Examples:
41193
+ $ neopress collections create --name Posts
41194
+ $ neopress collections create --name Posts --schema @schema.json
41195
+ $ neopress collections create --name Posts --schema '{"title":{"type":"text"}}'
41196
+
41197
+ --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
41198
  const client = await getClientAsync();
40968
41199
  const data = await client.collections.create({
40969
41200
  name: options.name,
@@ -40971,7 +41202,12 @@ function registerCollectionCommands(program3) {
40971
41202
  });
40972
41203
  output(data);
40973
41204
  });
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) => {
41205
+ collections.command("update <collectionId>").description("Update a collection").addHelpText("after", `
41206
+ Examples:
41207
+ $ neopress collections update 12 --name Articles
41208
+ $ neopress collections update 12 --schema @schema.json
41209
+
41210
+ 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
41211
  const client = await getClientAsync();
40976
41212
  const data = {};
40977
41213
  if (options.name) data.name = options.name;
@@ -40995,7 +41231,13 @@ function registerCollectionCommands(program3) {
40995
41231
  // src/commands/entries.ts
40996
41232
  var import_node_fs5 = require("fs");
40997
41233
  function registerEntryCommands(program3) {
40998
- const entries = program3.command("entries").description("Manage entries");
41234
+ const entries = program3.command("entries").description("Manage entries").addHelpText("after", `
41235
+ Examples:
41236
+ $ neopress entries list --collection 12 --status published --locale en
41237
+ $ neopress entries create --collection 12 --data @entry.json --title Hello
41238
+ $ neopress entries publish <entryId>
41239
+
41240
+ 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
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) => {
41000
41242
  const client = await getClientAsync();
41001
41243
  const res = await client.entries.list(parseInt(options.collection, 10), {
@@ -41011,7 +41253,15 @@ function registerEntryCommands(program3) {
41011
41253
  const data = await client.entries.get(parseInt(entryId, 10));
41012
41254
  output(data);
41013
41255
  });
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) => {
41256
+ 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(
41257
+ "after",
41258
+ `
41259
+ Examples:
41260
+ $ neopress entries create --collection 12 --data '{"body":"Hello"}' --title Hello
41261
+ $ neopress entries create --collection 12 --data @entry.json --locale ko
41262
+
41263
+ --data takes inline JSON or @file. After creating, run \`neopress entries publish <entryId>\` to publish it.`
41264
+ ).action(async (options) => {
41015
41265
  const client = await getClientAsync();
41016
41266
  let data;
41017
41267
  if (options.data?.startsWith("@")) {
@@ -41030,7 +41280,12 @@ function registerEntryCommands(program3) {
41030
41280
  await client.entries.revalidateReaderCache(entry.id);
41031
41281
  output(entry);
41032
41282
  });
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) => {
41283
+ entries.command("update <entryId>").description("Update an entry").addHelpText("after", `
41284
+ Examples:
41285
+ $ neopress entries update <entryId> --title "New title"
41286
+ $ neopress entries update <entryId> --data @entry.json --locale ko
41287
+
41288
+ --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
41289
  const client = await getClientAsync();
41035
41290
  const updateData = {};
41036
41291
  if (options.data) {
@@ -41071,7 +41326,12 @@ function parseJsonInput3(input) {
41071
41326
  return input.startsWith("@") ? JSON.parse((0, import_node_fs6.readFileSync)(input.slice(1), "utf-8")) : JSON.parse(input);
41072
41327
  }
41073
41328
  function registerFormCommands(program3) {
41074
- const forms = program3.command("forms").description("Manage forms");
41329
+ const forms = program3.command("forms").description("Manage forms").addHelpText("after", `
41330
+ Examples:
41331
+ $ neopress forms create --name Contact --schema @schema.json --layout @layout.json
41332
+ $ neopress forms publish <formId>
41333
+ $ neopress forms responses <formId> --limit 50
41334
+ Forms are created as drafts \u2014 run \`neopress forms publish <formId>\` to make them live. JSON options take inline JSON or @file.`);
41075
41335
  forms.command("list").description("List forms").action(async () => {
41076
41336
  const client = await getClientAsync();
41077
41337
  const data = await client.forms.list();
@@ -41082,7 +41342,12 @@ function registerFormCommands(program3) {
41082
41342
  const data = await client.forms.get(parseInt(formId, 10));
41083
41343
  output(data);
41084
41344
  });
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) => {
41345
+ forms.command("create").description("Create a form").addHelpText("after", `
41346
+ Examples:
41347
+ $ neopress forms create --name Contact
41348
+ $ neopress forms create --name Contact --schema @schema.json --layout @layout.json --i18n @i18n.json
41349
+ $ neopress forms create --name Signup --settings '{"redirectUrl":"/thanks"}'
41350
+ 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
41351
  const client = await getClientAsync();
41087
41352
  const data = await client.forms.create({
41088
41353
  name: options.name,
@@ -41094,7 +41359,11 @@ function registerFormCommands(program3) {
41094
41359
  });
41095
41360
  output(data);
41096
41361
  });
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) => {
41362
+ forms.command("update <formId>").description("Update a form").addHelpText("after", `
41363
+ Examples:
41364
+ $ neopress forms update <formId> --name Contact
41365
+ $ neopress forms update <formId> --schema @schema.json --layout @layout.json
41366
+ 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
41367
  const client = await getClientAsync();
41099
41368
  const data = {};
41100
41369
  if (options.name) data.name = options.name;
@@ -41109,12 +41378,19 @@ function registerFormCommands(program3) {
41109
41378
  const updated = await client.forms.update(parseInt(formId, 10), data);
41110
41379
  output(updated);
41111
41380
  });
41112
- forms.command("publish <formId>").description("Publish a form").action(async (formId) => {
41381
+ forms.command("publish <formId>").description("Publish a form").addHelpText("after", `
41382
+ Examples:
41383
+ $ neopress forms publish <formId>
41384
+ 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
41385
  const client = await getClientAsync();
41114
41386
  const result = await client.forms.publish(parseInt(formId, 10));
41115
41387
  output(result);
41116
41388
  });
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) => {
41389
+ forms.command("responses <formId>").description("List form responses").addHelpText("after", `
41390
+ Examples:
41391
+ $ neopress forms responses <formId>
41392
+ $ neopress forms responses <formId> --page 2 --limit 50
41393
+ 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
41394
  const client = await getClientAsync();
41119
41395
  const res = await client.forms.listResponses(parseInt(formId, 10), {
41120
41396
  page: parseInt(options.page, 10),
@@ -41133,7 +41409,11 @@ function registerFormCommands(program3) {
41133
41409
  var import_node_fs7 = require("fs");
41134
41410
  var import_node_path3 = require("path");
41135
41411
  function registerAssetCommands(program3) {
41136
- const assets = program3.command("assets").description("Manage assets");
41412
+ const assets = program3.command("assets").description("Manage assets").addHelpText("after", `
41413
+ Examples:
41414
+ $ neopress assets upload ./hero.png --folder images
41415
+ $ neopress assets list --limit 50
41416
+ Upload runs presign \u2192 PUT to storage \u2192 register in one step; content type is inferred from the file extension.`);
41137
41417
  assets.command("list").description("List assets").option("--page <n>", "Page number", "1").option("--limit <n>", "Results per page", "20").action(async (options) => {
41138
41418
  const client = await getClientAsync();
41139
41419
  const res = await client.assets.list({
@@ -41142,7 +41422,11 @@ function registerAssetCommands(program3) {
41142
41422
  });
41143
41423
  output(res);
41144
41424
  });
41145
- assets.command("upload <filePath>").description("Upload a file (presign \u2192 upload \u2192 register)").option("--folder <folder>", "Target folder path").action(async (filePath, options) => {
41425
+ assets.command("upload <filePath>").description("Upload a file (presign \u2192 upload \u2192 register)").addHelpText("after", `
41426
+ Examples:
41427
+ $ neopress assets upload ./hero.png
41428
+ $ neopress assets upload ./docs/guide.pdf --folder downloads
41429
+ <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
41430
  const client = await getClientAsync();
41147
41431
  const fileName = (0, import_node_path3.basename)(filePath);
41148
41432
  const stats = (0, import_node_fs7.statSync)(filePath);
@@ -41188,7 +41472,13 @@ function registerAssetCommands(program3) {
41188
41472
 
41189
41473
  // src/commands/publish.ts
41190
41474
  function registerPublishCommands(program3) {
41191
- const publish = program3.command("publish").description("Publish site or preview changes").action(async () => {
41475
+ const publish = program3.command("publish").description("Publish site or preview changes").addHelpText(
41476
+ "after",
41477
+ `
41478
+ Examples:
41479
+ $ neopress publish preview Show what would change
41480
+ $ neopress publish Publish all pending changes live`
41481
+ ).action(async () => {
41192
41482
  const client = await getClientAsync();
41193
41483
  const result = await client.publish.site();
41194
41484
  output(result);
@@ -41203,13 +41493,22 @@ function registerPublishCommands(program3) {
41203
41493
 
41204
41494
  // src/commands/redirects.ts
41205
41495
  function registerRedirectCommands(program3) {
41206
- const redirects = program3.command("redirects").description("Manage redirect rules");
41496
+ const redirects = program3.command("redirects").description("Manage redirect rules").addHelpText("after", `
41497
+ Examples:
41498
+ $ neopress redirects create --source /old --dest /about
41499
+ $ neopress redirects create --source /docs --dest /guide --type 308
41500
+ $ neopress redirects list
41501
+ Rules are created enabled. delete <ruleId> removes a rule permanently.`);
41207
41502
  redirects.command("list").description("List redirect rules").action(async () => {
41208
41503
  const client = await getClientAsync();
41209
41504
  const data = await client.redirects.list();
41210
41505
  output(data);
41211
41506
  });
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) => {
41507
+ redirects.command("create").description("Create a redirect rule").addHelpText("after", `
41508
+ Examples:
41509
+ $ neopress redirects create --source /old --dest /about
41510
+ $ 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) => {
41213
41512
  const client = await getClientAsync();
41214
41513
  const rule = await client.redirects.create({
41215
41514
  source: options.source,
@@ -41228,13 +41527,22 @@ function registerRedirectCommands(program3) {
41228
41527
  // src/commands/layout.ts
41229
41528
  var import_node_fs8 = require("fs");
41230
41529
  function registerLayoutCommands(program3) {
41231
- const layout = program3.command("layout").description("Manage site layout");
41530
+ const layout = program3.command("layout").description("Manage site layout").addHelpText("after", `
41531
+ Examples:
41532
+ $ neopress layout get
41533
+ $ neopress layout update --tsx ./layout.tsx --global-css ./globals.css
41534
+ $ neopress layout update --not-found-tsx ./not-found.tsx
41535
+ Layout edits land in draft state \u2014 run \`neopress publish\` to make them live.`);
41232
41536
  layout.command("get").description("Get current site layout").action(async () => {
41233
41537
  const client = await getClientAsync();
41234
41538
  const data = await client.site.getLayout();
41235
41539
  output(data);
41236
41540
  });
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) => {
41541
+ layout.command("update").description("Update site layout").addHelpText("after", `
41542
+ Examples:
41543
+ $ neopress layout update --tsx ./layout.tsx
41544
+ $ neopress layout update --tsx ./layout.tsx --not-found-tsx ./not-found.tsx --global-css ./globals.css
41545
+ 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
41546
  const client = await getClientAsync();
41239
41547
  const data = {};
41240
41548
  if (options.tsx) data.draftTsx = (0, import_node_fs8.readFileSync)(options.tsx, "utf-8");
@@ -41250,7 +41558,12 @@ function registerLayoutCommands(program3) {
41250
41558
 
41251
41559
  // src/commands/entry-references.ts
41252
41560
  function registerEntryReferenceCommands(program3) {
41253
- const refs = program3.command("entry-references").description("Manage entry reference relations");
41561
+ const refs = program3.command("entry-references").description("Manage entry reference relations").addHelpText("after", `
41562
+ Examples:
41563
+ $ neopress entry-references create --from-entry 12 --to-entry 34 --field author
41564
+ $ neopress entry-references list --from-entry 12
41565
+ $ neopress entry-references delete <referenceId>
41566
+ --field is a reference field key from the source entry's collection schema. delete is a hard delete and removes the relation immediately.`);
41254
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) => {
41255
41568
  const client = await getClientAsync();
41256
41569
  const data = await client.entryReferences.list({
@@ -41259,7 +41572,11 @@ function registerEntryReferenceCommands(program3) {
41259
41572
  });
41260
41573
  output(data);
41261
41574
  });
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) => {
41575
+ refs.command("create").description("Create an entry reference").addHelpText("after", `
41576
+ Examples:
41577
+ $ neopress entry-references create --from-entry 12 --to-entry 34 --field author
41578
+ $ neopress entry-references create --from-entry 100 --to-entry 7 --field relatedPosts
41579
+ 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
41580
  const client = await getClientAsync();
41264
41581
  const fromEntryId = parseInt(options.fromEntry, 10);
41265
41582
  const toEntryId = parseInt(options.toEntry, 10);
@@ -41326,9 +41643,19 @@ function parseSearchConsoleDimensions(raw) {
41326
41643
  return dimensions;
41327
41644
  }
41328
41645
  function registerAnalyticsCommands(program3) {
41329
- const analytics = program3.command("analytics").description("Read site analytics and Search Console data");
41646
+ const analytics = program3.command("analytics").description("Read site analytics and Search Console data").addHelpText("after", `
41647
+ Examples:
41648
+ $ neopress analytics summary --date-from 2026-01-01 --date-to 2026-01-31
41649
+ $ neopress analytics pages --date-from 2026-01-01 --date-to 2026-01-31 --limit 20
41650
+ $ neopress analytics search-console --date-from 2026-01-01 --date-to 2026-01-31 --dimensions query,page
41651
+ All analytics commands are read-only. Convenience commands (summary, pages, sources, forms, crawlers, pageviews) share --date-from/--date-to/--timezone/--limit.`);
41330
41652
  addDateOptions(
41331
- analytics.command("query").description("Run an allowed analytics pipe").requiredOption("--pipe <pipe>", "Analytics pipe name")
41653
+ analytics.command("query").description("Run an allowed analytics pipe").addHelpText("after", `
41654
+ Examples:
41655
+ $ neopress analytics query --pipe get_top_pages --date-from 2026-01-01 --date-to 2026-01-31
41656
+ $ neopress analytics query --pipe get_pageviews --params '{"device":"mobile"}'
41657
+ $ neopress analytics query --pipe get_top_pages --params @params.json --limit 50
41658
+ --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
41659
  ).action(async (options) => {
41333
41660
  const client = await getClientAsync();
41334
41661
  const data = await client.analytics.query({
@@ -41373,7 +41700,12 @@ function registerAnalyticsCommands(program3) {
41373
41700
  const client = await getClientAsync();
41374
41701
  output(await client.analytics.crawlerSummary(readDateOptions(options)));
41375
41702
  });
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) => {
41703
+ analytics.command("search-console").description("Query Google Search Console performance data").addHelpText("after", `
41704
+ Examples:
41705
+ $ neopress analytics search-console --date-from 2026-01-01 --date-to 2026-01-31
41706
+ $ neopress analytics search-console --date-from 2026-01-01 --date-to 2026-01-31 --dimensions query,page --row-limit 50
41707
+ $ neopress analytics search-console --date-from 2026-01-01 --date-to 2026-01-31 --page-filter https://example.com/about
41708
+ --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
41709
  const client = await getClientAsync();
41378
41710
  const data = await client.analytics.searchConsole({
41379
41711
  dateFrom: options.dateFrom,
@@ -41392,7 +41724,12 @@ function collectPreviewImgUrl(value, previous = []) {
41392
41724
  return previous.concat(value);
41393
41725
  }
41394
41726
  function registerTemplateCommands(program3) {
41395
- const templates = program3.command("templates").description("Manage template metadata for the active site");
41727
+ const templates = program3.command("templates").description("Manage template metadata for the active site").addHelpText("after", `
41728
+ 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
41731
+ $ neopress templates delete
41732
+ \`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
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) => {
41397
41734
  const token = process.env.NEOPRESS_ACCESS_TOKEN || await getValidAccessToken() || void 0;
41398
41735
  if (!token) {
@@ -41411,7 +41748,11 @@ function registerTemplateCommands(program3) {
41411
41748
  const template = await client.templates.get();
41412
41749
  output(template);
41413
41750
  });
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(
41751
+ templates.command("mark").description("Mark or update the active site as a template").addHelpText("after", `
41752
+ 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(
41415
41756
  "--reference-url <url>",
41416
41757
  "Reference URL for template provenance"
41417
41758
  ).option(
@@ -41437,9 +41778,226 @@ function registerTemplateCommands(program3) {
41437
41778
  });
41438
41779
  }
41439
41780
 
41781
+ // src/commands/headshots.ts
41782
+ var AGE_BANDS = ["young", "middle"];
41783
+ var GENDERS = ["male", "female"];
41784
+ var ETHNICITIES = ["asian", "white", "black", "latino"];
41785
+ var ATTIRES = ["formal"];
41786
+ function validateEnum(value, allowed, flag) {
41787
+ if (value !== void 0 && !allowed.includes(value)) {
41788
+ fail(`${flag} must be one of: ${allowed.join(", ")}`);
41789
+ }
41790
+ }
41791
+ function parseNonNegativeInt(raw, flag) {
41792
+ if (raw === void 0) return void 0;
41793
+ const value = Number(raw);
41794
+ if (!Number.isInteger(value) || value < 0) {
41795
+ fail(`${flag} must be a non-negative integer`);
41796
+ }
41797
+ return value;
41798
+ }
41799
+ function registerHeadshotCommands(program3) {
41800
+ const headshots = program3.command("headshots").description("Manage the shared headshot pool (super-admin only)").addHelpText("after", `
41801
+ Examples:
41802
+ $ neopress headshots list --gender female --ethnicity asian
41803
+ $ neopress headshots create --image-url https://cdn/x.webp --age-band young --gender male --ethnicity white --variation-index 0
41804
+ $ neopress headshots update 12 --inactive
41805
+ 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", `
41807
+ Examples:
41808
+ $ neopress headshots list
41809
+ $ neopress headshots list --gender female --age-band middle --active
41810
+ Filters are equality matches on the pool's demographic columns; results are paginated.`).action(async (options) => {
41811
+ validateEnum(options.ageBand, AGE_BANDS, "--age-band");
41812
+ validateEnum(options.gender, GENDERS, "--gender");
41813
+ validateEnum(options.ethnicity, ETHNICITIES, "--ethnicity");
41814
+ validateEnum(options.attire, ATTIRES, "--attire");
41815
+ if (options.active && options.inactive) {
41816
+ fail("Use only one of --active or --inactive.");
41817
+ }
41818
+ const params = {
41819
+ page: parseInt(options.page, 10),
41820
+ limit: parseInt(options.limit, 10),
41821
+ ageBand: options.ageBand,
41822
+ gender: options.gender,
41823
+ ethnicity: options.ethnicity,
41824
+ attire: options.attire
41825
+ };
41826
+ if (options.active) params.isActive = true;
41827
+ else if (options.inactive) params.isActive = false;
41828
+ const client = await getGlobalClientAsync();
41829
+ const res = await client.headshots.list(params);
41830
+ output(res);
41831
+ });
41832
+ headshots.command("get <id>").description("Get a headshot pool entry").action(async (id) => {
41833
+ const client = await getGlobalClientAsync();
41834
+ const data = await client.headshots.get(parseInt(id, 10));
41835
+ output(data);
41836
+ });
41837
+ 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", `
41838
+ Examples:
41839
+ $ neopress headshots create --image-url https://cdn/a.webp --age-band young --gender female --ethnicity asian --variation-index 0
41840
+ $ neopress headshots create --image-url https://cdn/b.webp --age-band middle --gender male --ethnicity black --variation-index 1 --tags suit,studio
41841
+ (age-band, gender, ethnicity, attire, variation-index) must be unique. The DB rejects out-of-range enum values.`).action(async (options) => {
41842
+ validateEnum(options.ageBand, AGE_BANDS, "--age-band");
41843
+ validateEnum(options.gender, GENDERS, "--gender");
41844
+ validateEnum(options.ethnicity, ETHNICITIES, "--ethnicity");
41845
+ validateEnum(options.attire, ATTIRES, "--attire");
41846
+ const variationIndex = parseNonNegativeInt(options.variationIndex, "--variation-index");
41847
+ const params = {
41848
+ imageUrl: options.imageUrl,
41849
+ ageBand: options.ageBand,
41850
+ gender: options.gender,
41851
+ ethnicity: options.ethnicity,
41852
+ variationIndex
41853
+ };
41854
+ if (options.attire) params.attire = options.attire;
41855
+ const width = parseNonNegativeInt(options.width, "--width");
41856
+ if (width !== void 0) params.width = width;
41857
+ const height = parseNonNegativeInt(options.height, "--height");
41858
+ if (height !== void 0) params.height = height;
41859
+ if (options.aspectRatio) params.aspectRatio = options.aspectRatio;
41860
+ if (options.mimeType) params.mimeType = options.mimeType;
41861
+ const byteSize = parseNonNegativeInt(options.byteSize, "--byte-size");
41862
+ if (byteSize !== void 0) params.byteSize = byteSize;
41863
+ if (options.gptPrompt) params.gptPrompt = options.gptPrompt;
41864
+ if (options.tags) params.tags = String(options.tags).split(",").map((t) => t.trim()).filter(Boolean);
41865
+ if (options.inactive) params.isActive = false;
41866
+ const client = await getGlobalClientAsync();
41867
+ const data = await client.headshots.create(params);
41868
+ output(data);
41869
+ });
41870
+ 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", `
41871
+ Examples:
41872
+ $ neopress headshots update 12 --inactive
41873
+ $ neopress headshots update 12 --image-url https://cdn/new.webp --tags suit,studio
41874
+ Pass at least one field. --active/--inactive toggle is_active; --tags replaces the tag list.`).action(async (id, options) => {
41875
+ validateEnum(options.ageBand, AGE_BANDS, "--age-band");
41876
+ validateEnum(options.gender, GENDERS, "--gender");
41877
+ validateEnum(options.ethnicity, ETHNICITIES, "--ethnicity");
41878
+ validateEnum(options.attire, ATTIRES, "--attire");
41879
+ if (options.active && options.inactive) {
41880
+ fail("Use only one of --active or --inactive.");
41881
+ }
41882
+ const params = {};
41883
+ if (options.imageUrl) params.imageUrl = options.imageUrl;
41884
+ if (options.ageBand) params.ageBand = options.ageBand;
41885
+ if (options.gender) params.gender = options.gender;
41886
+ if (options.ethnicity) params.ethnicity = options.ethnicity;
41887
+ if (options.attire) params.attire = options.attire;
41888
+ const variationIndex = parseNonNegativeInt(options.variationIndex, "--variation-index");
41889
+ if (variationIndex !== void 0) params.variationIndex = variationIndex;
41890
+ const width = parseNonNegativeInt(options.width, "--width");
41891
+ if (width !== void 0) params.width = width;
41892
+ const height = parseNonNegativeInt(options.height, "--height");
41893
+ if (height !== void 0) params.height = height;
41894
+ if (options.aspectRatio) params.aspectRatio = options.aspectRatio;
41895
+ if (options.mimeType) params.mimeType = options.mimeType;
41896
+ const byteSize = parseNonNegativeInt(options.byteSize, "--byte-size");
41897
+ if (byteSize !== void 0) params.byteSize = byteSize;
41898
+ if (options.gptPrompt) params.gptPrompt = options.gptPrompt;
41899
+ if (options.tags) params.tags = String(options.tags).split(",").map((t) => t.trim()).filter(Boolean);
41900
+ if (options.active) params.isActive = true;
41901
+ else if (options.inactive) params.isActive = false;
41902
+ if (Object.keys(params).length === 0) {
41903
+ fail("No fields to update.");
41904
+ }
41905
+ const client = await getGlobalClientAsync();
41906
+ const data = await client.headshots.update(parseInt(id, 10), params);
41907
+ output(data);
41908
+ });
41909
+ headshots.command("delete <id>").description("Delete a headshot pool entry (hard delete)").action(async (id) => {
41910
+ const client = await getGlobalClientAsync();
41911
+ await client.headshots.delete(parseInt(id, 10));
41912
+ ok(`Headshot ${id} deleted`);
41913
+ });
41914
+ }
41915
+
41916
+ // src/commands/guide.ts
41917
+ function describeOption(opt) {
41918
+ const info = { flags: opt.flags, description: opt.description || "" };
41919
+ if (opt.defaultValue !== void 0) info.default = opt.defaultValue;
41920
+ return info;
41921
+ }
41922
+ function describeArgument(arg) {
41923
+ return {
41924
+ name: arg.name(),
41925
+ required: arg.required,
41926
+ variadic: arg.variadic,
41927
+ description: arg.description || ""
41928
+ };
41929
+ }
41930
+ function describeCommand(cmd, prefix) {
41931
+ const path = `${prefix} ${cmd.name()}`;
41932
+ return {
41933
+ path,
41934
+ aliases: cmd.aliases(),
41935
+ description: cmd.description(),
41936
+ arguments: cmd.registeredArguments.map(describeArgument),
41937
+ options: cmd.options.map(describeOption),
41938
+ subcommands: cmd.commands.filter((c) => c.name() !== "help").map((c) => describeCommand(c, path))
41939
+ };
41940
+ }
41941
+ function buildGuide(program3) {
41942
+ return {
41943
+ name: program3.name(),
41944
+ version: program3.version() ?? "",
41945
+ description: program3.description(),
41946
+ globalOptions: program3.options.map(describeOption),
41947
+ commands: program3.commands.filter((c) => c.name() !== "help").map((c) => describeCommand(c, program3.name()))
41948
+ };
41949
+ }
41950
+ function pad(s, width) {
41951
+ return s.length >= width ? `${s} ` : s + " ".repeat(width - s.length);
41952
+ }
41953
+ function argSignature(args) {
41954
+ return args.map((a) => {
41955
+ const inner = a.variadic ? `${a.name}...` : a.name;
41956
+ return a.required ? `<${inner}>` : `[${inner}]`;
41957
+ }).join(" ");
41958
+ }
41959
+ function printCommandNode(node, lines, depth) {
41960
+ const indent = " ".repeat(depth * 2);
41961
+ const sig = argSignature(node.arguments);
41962
+ const head2 = `${indent}${node.path}${sig ? ` ${sig}` : ""}`;
41963
+ lines.push(`${pad(head2, 46)}${node.description}`);
41964
+ if (node.subcommands.length === 0) {
41965
+ for (const o of node.options) {
41966
+ const def = o.default !== void 0 ? ` (default: ${JSON.stringify(o.default)})` : "";
41967
+ lines.push(`${pad(`${indent} ${o.flags}`, 46)}${o.description}${def}`);
41968
+ }
41969
+ }
41970
+ for (const sub of node.subcommands) printCommandNode(sub, lines, depth + 1);
41971
+ }
41972
+ function printHuman(guide) {
41973
+ const lines = [];
41974
+ lines.push(`${guide.name} ${guide.version} \u2014 ${guide.description}`);
41975
+ lines.push("");
41976
+ if (guide.globalOptions.length) {
41977
+ lines.push("GLOBAL OPTIONS");
41978
+ for (const o of guide.globalOptions) lines.push(`${pad(` ${o.flags}`, 46)}${o.description}`);
41979
+ lines.push("");
41980
+ }
41981
+ lines.push("COMMANDS");
41982
+ for (const c of guide.commands) printCommandNode(c, lines, 1);
41983
+ lines.push("");
41984
+ lines.push("Run `neopress <command> --help` for details, or `neopress guide --json` for the full surface.");
41985
+ console.log(lines.join("\n"));
41986
+ }
41987
+ function registerGuideCommands(program3) {
41988
+ 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(() => {
41989
+ const guide = buildGuide(program3);
41990
+ if (isJsonMode()) {
41991
+ console.log(JSON.stringify(guide, null, 2));
41992
+ } else {
41993
+ printHuman(guide);
41994
+ }
41995
+ });
41996
+ }
41997
+
41440
41998
  // src/bin.ts
41441
41999
  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) => {
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) => {
41443
42001
  const opts = thisCommand.optsWithGlobals();
41444
42002
  if (opts.site) setSiteOverride(String(opts.site));
41445
42003
  if (opts.json || !process.stdout.isTTY) setJsonMode(true);
@@ -41457,6 +42015,28 @@ registerLayoutCommands(program2);
41457
42015
  registerEntryReferenceCommands(program2);
41458
42016
  registerAnalyticsCommands(program2);
41459
42017
  registerTemplateCommands(program2);
42018
+ registerHeadshotCommands(program2);
42019
+ registerGuideCommands(program2);
42020
+ program2.addHelpText(
42021
+ "after",
42022
+ `
42023
+ Quickstart:
42024
+ $ neopress login Authenticate (or set NEOPRESS_ACCESS_TOKEN)
42025
+ $ neopress sites list See sites you can access
42026
+ $ neopress sites use <id|handle> Pick the active site
42027
+ $ neopress pages list List pages on the active site
42028
+ $ neopress guide Full command reference (add --json for AI tools)
42029
+
42030
+ AI / scripting:
42031
+ Output is JSON automatically when piped or redirected; force it with --json.
42032
+ Run \`neopress guide --json\` to get the entire command surface in one call.
42033
+
42034
+ Env vars:
42035
+ NEOPRESS_ACCESS_TOKEN Auth token (skips \`neopress login\`)
42036
+ NEOPRESS_SITE_ID Default site id (overrides the active site)
42037
+ NEOPRESS_BASE_URL API base URL (defaults to the hosted Neopress app)
42038
+ `
42039
+ );
41460
42040
  program2.parseAsync(process.argv).catch((err) => {
41461
42041
  if (err?.code) {
41462
42042
  console.error(JSON.stringify({ ok: false, error: err.message, code: err.code }));