@autoposting.ai/cli 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -42,11 +42,11 @@ function createError(status, body) {
42
42
  if (status >= 500) return new ServerError(message, status, code);
43
43
  return new AutopostingError(message, status, code);
44
44
  }
45
- var VERSION, AutopostingError, AuthenticationError, ScopeError, NotFoundError, ValidationError, RateLimitError, ServerError, Resource, AgentsResource, BillingResource, BrandsResource, CarouselsResource, ClipsResource, PostsResource, KbResource, IdeasResource, UsageResource, WebhooksResource, WorkspacesResource, Autoposting;
45
+ var VERSION, AutopostingError, AuthenticationError, ScopeError, NotFoundError, ValidationError, RateLimitError, ServerError, Resource, AgentsResource, BillingResource, BrandsResource, CarouselsResource, ClipsResource, PostsResource, KbResource, IdeasResource, UsageResource, WebhooksResource, WorkspacesResource, IDEMPOTENT_METHODS, RETRYABLE_STATUS, sleep, Autoposting;
46
46
  var init_dist = __esm({
47
47
  "../sdk/dist/index.mjs"() {
48
48
  "use strict";
49
- VERSION = "0.3.0";
49
+ VERSION = "0.3.2";
50
50
  AutopostingError = class extends Error {
51
51
  status;
52
52
  code;
@@ -93,6 +93,7 @@ var init_dist = __esm({
93
93
  }
94
94
  };
95
95
  AgentsResource = class extends Resource {
96
+ /** GET /agents — backend returns `{ items, total, limit, offset }`. */
96
97
  list() {
97
98
  return this.get("/agents");
98
99
  }
@@ -108,12 +109,14 @@ var init_dist = __esm({
108
109
  remove(id) {
109
110
  return this.delete(`/agents/${id}`);
110
111
  }
112
+ /** POST /agents/:id/run — queues a run; returns `{ runId, status }`, not a full AgentRun. */
111
113
  run(id) {
112
114
  return this.post(`/agents/${id}/run`);
113
115
  }
114
116
  toggle(id) {
115
117
  return this.post(`/agents/${id}/toggle`);
116
118
  }
119
+ /** GET /agents/:id/runs — backend returns `{ items, total, limit, offset }`. */
117
120
  runs(id) {
118
121
  return this.get(`/agents/${id}/runs`);
119
122
  }
@@ -147,6 +150,7 @@ var init_dist = __esm({
147
150
  }
148
151
  };
149
152
  CarouselsResource = class extends Resource {
153
+ /** GET /carousels — backend paginates: `{ items, total, limit, offset }`. */
150
154
  list() {
151
155
  return this.get("/carousels");
152
156
  }
@@ -159,37 +163,38 @@ var init_dist = __esm({
159
163
  generate(params) {
160
164
  return this.post("/carousels/generate", params);
161
165
  }
162
- /** POST /carousels/:id/draft — converts carousel to a post draft */
166
+ /** POST /carousels/:id/create-draft — converts carousel to a post draft */
163
167
  draft(id) {
164
- return this.post(`/carousels/${id}/draft`);
168
+ return this.post(`/carousels/${id}/create-draft`);
165
169
  }
166
170
  remove(id) {
167
171
  return this.client.request("DELETE", `/carousels/${id}`);
168
172
  }
169
173
  };
170
174
  ClipsResource = class extends Resource {
175
+ /** GET /clips — backend returns `{ clips, pagination }`, not a bare array. */
171
176
  list() {
172
177
  return this.client.request("GET", "/clips");
173
178
  }
174
179
  retrieve(id) {
175
180
  return this.client.request("GET", `/clips/${id}`);
176
181
  }
177
- async upload(filePath, options) {
178
- const fs3 = await import("fs");
179
- const path2 = await import("path");
180
- const data = fs3.readFileSync(filePath);
181
- const name = options?.name ?? path2.basename(filePath);
182
- const formData = new FormData();
183
- formData.append("file", new Blob([data]), name);
184
- formData.append("name", name);
185
- options?.onProgress?.(0);
186
- const clip = await this.client.request("POST", "/clips/upload", formData);
187
- options?.onProgress?.(100);
188
- return clip;
182
+ // ponytail: the backend has no single-request /clips/upload; uploading a file means the
183
+ // multipart init/presign/complete flow (upload-initiate → upload-presign → upload-complete).
184
+ // That's too much surface for v0.3.1 — fail honestly and point at importUrl. Upgrade path:
185
+ // implement the init/presign/complete dance here when direct upload is actually needed.
186
+ async upload(_filePath, _options) {
187
+ throw new AutopostingError(
188
+ "Direct clip file upload is not supported by the API yet \u2014 use `importUrl` with a public video URL instead.",
189
+ 0,
190
+ "NOT_IMPLEMENTED"
191
+ );
189
192
  }
193
+ /** POST /clips/import-url — backend returns `{ clipId }`, not a full Clip. */
190
194
  importUrl(params) {
191
- return this.client.request("POST", "/clips/import", params);
195
+ return this.client.request("POST", "/clips/import-url", params);
192
196
  }
197
+ /** POST /clips/:id/render — backend returns render job ids, not a Clip. */
193
198
  render(id) {
194
199
  return this.client.request("POST", `/clips/${id}/render`);
195
200
  }
@@ -198,45 +203,45 @@ var init_dist = __esm({
198
203
  }
199
204
  };
200
205
  PostsResource = class extends Resource {
201
- list(params) {
202
- return this.client.request(
203
- "GET",
204
- "/v1/posts",
205
- void 0,
206
- params
207
- );
208
- }
209
- /** GET /v1/posts/:id */
206
+ /** GET /posts — backend returns a bare array (no pagination metadata). */
207
+ list(params = {}) {
208
+ const { brandSlug, status, limit, page } = params;
209
+ const query = {};
210
+ if (brandSlug) query.brandSlug = brandSlug;
211
+ if (status) query.status = status;
212
+ if (limit !== void 0) query.limit = limit;
213
+ if (page !== void 0 && page > 1) query.offset = (page - 1) * (limit ?? 20);
214
+ return this.client.request("GET", "/posts", void 0, query);
215
+ }
216
+ /** GET /posts/:id */
210
217
  getById(id) {
211
- return this.client.request("GET", `/v1/posts/${id}`);
218
+ return this.client.request("GET", `/posts/${id}`);
212
219
  }
213
220
  create(params) {
214
- return this.client.request("POST", "/v1/posts", params);
221
+ return this.client.request("POST", "/posts", params);
215
222
  }
216
223
  update(id, params) {
217
- return this.client.request("PUT", `/v1/posts/${id}`, params);
224
+ return this.client.request("PUT", `/posts/${id}`, params);
218
225
  }
219
- /** DELETE /v1/posts/:id */
226
+ /** DELETE /posts/:id */
220
227
  remove(id) {
221
- return this.client.request("DELETE", `/v1/posts/${id}`);
228
+ return this.client.request("DELETE", `/posts/${id}`);
222
229
  }
223
230
  publish(id) {
224
- return this.client.request("POST", `/v1/posts/${id}/publish`);
231
+ return this.client.request("POST", `/posts/${id}/publish`);
225
232
  }
226
233
  schedule(id, scheduledAt) {
227
- return this.client.request("PUT", `/v1/posts/${id}/schedule`, { scheduledAt });
234
+ return this.client.request("PUT", `/posts/${id}/schedule`, { scheduledAt });
228
235
  }
229
236
  retry(id) {
230
- return this.client.request("POST", `/v1/posts/${id}/retry`);
237
+ return this.client.request("POST", `/posts/${id}/retry`);
231
238
  }
232
239
  rewrite(id) {
233
- return this.client.request("POST", `/v1/posts/${id}/rewrite`);
240
+ return this.client.request("POST", `/posts/${id}/rewrite`);
234
241
  }
242
+ /** POST /posts/:id/score — backend returns `{ score }` only (no feedback). */
235
243
  score(id) {
236
- return this.client.request(
237
- "POST",
238
- `/v1/posts/${id}/score`
239
- );
244
+ return this.client.request("POST", `/posts/${id}/score`);
240
245
  }
241
246
  };
242
247
  KbResource = class extends Resource {
@@ -253,7 +258,10 @@ var init_dist = __esm({
253
258
  return this.delete(`/kbs/${id}`);
254
259
  }
255
260
  search(id, query) {
256
- return this.post(`/kbs/${id}/search`, { query });
261
+ return this.post(
262
+ `/kbs/${id}/search`,
263
+ { query }
264
+ );
257
265
  }
258
266
  ingestUrl(id, url) {
259
267
  return this.post(`/kbs/${id}/docs/ingest-url`, { url });
@@ -263,14 +271,21 @@ var init_dist = __esm({
263
271
  }
264
272
  };
265
273
  IdeasResource = class extends Resource {
274
+ /** POST /ideas/generate-topic — returns an object whose `ideas` holds the generated ideas. */
266
275
  generate(params = {}) {
267
- return this.post("/ideas/generate", params);
276
+ return this.post("/ideas/generate-topic", params);
268
277
  }
278
+ /** GET /ideas — backend paginates: `{ items, total, limit, offset }`. */
269
279
  list() {
270
280
  return this.get("/ideas");
271
281
  }
272
- enrich(id) {
273
- return this.post(`/ideas/${id}/enrich`);
282
+ /**
283
+ * POST /ideas/enrich — enrich an idea (title/hook/angle) across 1..5 platforms.
284
+ * Async: returns a job id (202). NOTE: the backend enriches the idea *object*, not an
285
+ * id — there is no enrich-by-id route. Poll the result separately (not yet wrapped).
286
+ */
287
+ enrich(params) {
288
+ return this.post("/ideas/enrich", params);
274
289
  }
275
290
  remove(id) {
276
291
  return this.delete(`/ideas/${id}`);
@@ -285,6 +300,7 @@ var init_dist = __esm({
285
300
  list() {
286
301
  return this.get("/webhooks");
287
302
  }
303
+ // NOTE: the backend has no GET /webhooks/:id route — this 404s until front-back adds it.
288
304
  retrieve(id) {
289
305
  return this.get(`/webhooks/${id}`);
290
306
  }
@@ -297,6 +313,7 @@ var init_dist = __esm({
297
313
  remove(id) {
298
314
  return this.client.request("DELETE", `/webhooks/${id}`);
299
315
  }
316
+ // POST /webhooks/:id/test — backend returns `{ delivered, httpStatus }`.
300
317
  test(id) {
301
318
  return this.post(`/webhooks/${id}/test`);
302
319
  }
@@ -305,7 +322,19 @@ var init_dist = __esm({
305
322
  constructor(client) {
306
323
  super(client);
307
324
  }
325
+ /**
326
+ * GET /orgs — backend returns `{ organizations, activeOrgId }`, not a bare array.
327
+ * This route is session-only (better-auth session); an API key receives a bare 401,
328
+ * so guard up front with accurate guidance instead of an opaque "Unauthorized".
329
+ */
308
330
  list() {
331
+ if (this.client.authSource === "api-key") {
332
+ return Promise.reject(
333
+ new Error(
334
+ "Listing workspaces requires session auth. An API key is scoped to a single workspace and cannot list organizations \u2014 manage workspaces from the web dashboard."
335
+ )
336
+ );
337
+ }
309
338
  return this.get("/orgs");
310
339
  }
311
340
  switchWorkspace(id) {
@@ -316,9 +345,12 @@ var init_dist = __esm({
316
345
  )
317
346
  );
318
347
  }
319
- return this.post("/orgs/set-active", { organizationId: id });
348
+ return this.client.request("PUT", "/orgs/active", { organizationId: id });
320
349
  }
321
350
  };
351
+ IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "PUT", "DELETE"]);
352
+ RETRYABLE_STATUS = /* @__PURE__ */ new Set([500, 502, 503, 504]);
353
+ sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
322
354
  Autoposting = class {
323
355
  /** @internal Not part of the public API — use SDK methods to make requests. */
324
356
  _apiKey;
@@ -327,6 +359,8 @@ var init_dist = __esm({
327
359
  extraHeaders;
328
360
  /** Indicates how the client was authenticated; used by WorkspacesResource. */
329
361
  authSource;
362
+ maxRetries;
363
+ retryBaseMs;
330
364
  agents;
331
365
  billing;
332
366
  brands;
@@ -346,10 +380,12 @@ var init_dist = __esm({
346
380
  );
347
381
  }
348
382
  this._apiKey = key;
349
- this.baseUrl = (config.baseUrl ?? "https://app.autoposting.ai").replace(/\/$/, "");
383
+ this.baseUrl = (config.baseUrl || process.env.AUTOPOSTING_BASE_URL || "https://app.autoposting.ai/api-proxy").replace(/\/$/, "");
350
384
  this.timeout = config.timeout ?? 3e4;
351
385
  this.extraHeaders = config.headers ?? {};
352
386
  this.authSource = config.authSource ?? "api-key";
387
+ this.maxRetries = Math.max(0, config.maxRetries ?? 2);
388
+ this.retryBaseMs = Math.max(0, config.retryBaseMs ?? 300);
353
389
  this.agents = new AgentsResource(this);
354
390
  this.billing = new BillingResource(this);
355
391
  this.brands = new BrandsResource(this);
@@ -362,7 +398,42 @@ var init_dist = __esm({
362
398
  this.webhooks = new WebhooksResource(this);
363
399
  this.workspaces = new WorkspacesResource(this);
364
400
  }
401
+ /**
402
+ * GET /auth/profile — resolve the server-side identity behind the current credential.
403
+ * Validates the key (401 on reject) and returns who/what it's scoped to.
404
+ */
405
+ getProfile() {
406
+ return this.request("GET", "/auth/profile");
407
+ }
408
+ /**
409
+ * Public entry point. Retries idempotent requests (GET/PUT/DELETE) on transient
410
+ * failures — network errors, timeouts, and 5xx — with exponential backoff + jitter.
411
+ * Non-idempotent methods (POST/PATCH) are never retried, to avoid duplicate side effects.
412
+ */
365
413
  async request(method, path2, body, query) {
414
+ let lastErr;
415
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
416
+ if (attempt > 0) {
417
+ const backoff = this.retryBaseMs * 2 ** (attempt - 1);
418
+ await sleep(backoff + Math.floor(Math.random() * this.retryBaseMs));
419
+ }
420
+ try {
421
+ return await this._send(method, path2, body, query);
422
+ } catch (err) {
423
+ lastErr = err;
424
+ if (attempt < this.maxRetries && this.isRetryable(method, err)) continue;
425
+ throw err;
426
+ }
427
+ }
428
+ throw lastErr;
429
+ }
430
+ isRetryable(method, err) {
431
+ if (!IDEMPOTENT_METHODS.has(method)) return false;
432
+ if (!(err instanceof AutopostingError)) return false;
433
+ if (err.code === "NETWORK_ERROR" || err.code === "TIMEOUT") return true;
434
+ return RETRYABLE_STATUS.has(err.status);
435
+ }
436
+ async _send(method, path2, body, query) {
366
437
  let url = `${this.baseUrl}${path2}`;
367
438
  if (query && Object.keys(query).length > 0) {
368
439
  const params = new URLSearchParams();
@@ -375,46 +446,101 @@ var init_dist = __esm({
375
446
  }
376
447
  const isFormData = body instanceof FormData;
377
448
  const headers = {
449
+ ...this.extraHeaders,
378
450
  authorization: `Bearer ${this._apiKey}`,
379
451
  "user-agent": `autoposting-sdk/${VERSION}`,
380
- "x-source": "sdk",
381
- ...this.extraHeaders
452
+ "x-source": "sdk"
382
453
  };
383
454
  if (!isFormData) {
384
455
  headers["content-type"] = "application/json";
385
456
  }
386
457
  const controller = new AbortController();
387
458
  const timer = setTimeout(() => controller.abort(), this.timeout);
388
- let response;
389
459
  try {
390
- response = await fetch(url, {
391
- method,
392
- headers,
393
- body: isFormData ? body : body !== void 0 ? JSON.stringify(body) : void 0,
394
- signal: controller.signal
395
- });
396
- } finally {
397
- clearTimeout(timer);
398
- }
399
- if (!response.ok) {
400
- let errorBody = {};
460
+ let response;
401
461
  try {
402
- errorBody = await response.json();
403
- } catch {
462
+ response = await fetch(url, {
463
+ method,
464
+ headers,
465
+ body: isFormData ? body : body !== void 0 ? JSON.stringify(body) : void 0,
466
+ signal: controller.signal
467
+ });
468
+ } catch (err) {
469
+ if (err instanceof Error && err.name === "AbortError") {
470
+ throw new AutopostingError(
471
+ `Request to ${method} ${path2} timed out after ${this.timeout}ms.`,
472
+ 0,
473
+ "TIMEOUT"
474
+ );
475
+ }
476
+ throw new AutopostingError(
477
+ `Network request to ${method} ${path2} failed: ${err instanceof Error ? err.message : String(err)}`,
478
+ 0,
479
+ "NETWORK_ERROR"
480
+ );
404
481
  }
405
- const err = createError(response.status, errorBody);
406
- if (err instanceof RateLimitError) {
407
- const retryAfter = response.headers.get("Retry-After");
408
- if (retryAfter) {
409
- err.retryAfter = parseInt(retryAfter, 10);
482
+ if (!response.ok) {
483
+ const raw = await response.text();
484
+ let errorBody = {};
485
+ if (raw) {
486
+ try {
487
+ errorBody = JSON.parse(raw);
488
+ } catch {
489
+ }
490
+ }
491
+ if (!errorBody.error) {
492
+ const snippet = raw.replace(/\s+/g, " ").trim().slice(0, 120);
493
+ errorBody.error = snippet ? `HTTP ${response.status} ${response.statusText}: ${snippet}` : `HTTP ${response.status} ${response.statusText}`;
410
494
  }
495
+ const err = createError(response.status, errorBody);
496
+ if (err instanceof RateLimitError) {
497
+ const retryAfter = response.headers.get("Retry-After");
498
+ if (retryAfter) {
499
+ const seconds = Number(retryAfter);
500
+ if (Number.isFinite(seconds)) {
501
+ err.retryAfter = Math.max(0, Math.round(seconds));
502
+ } else {
503
+ const dateMs = Date.parse(retryAfter);
504
+ if (!Number.isNaN(dateMs)) {
505
+ err.retryAfter = Math.max(0, Math.round((dateMs - Date.now()) / 1e3));
506
+ }
507
+ }
508
+ }
509
+ }
510
+ throw err;
511
+ }
512
+ if (response.status === 204) {
513
+ return {};
514
+ }
515
+ const text = await response.text();
516
+ if (!text) return {};
517
+ let parsed;
518
+ try {
519
+ parsed = JSON.parse(text);
520
+ } catch {
521
+ throw new AutopostingError(
522
+ `Expected a JSON response from ${method} ${path2} but received non-JSON (HTTP ${response.status}). Verify the API base URL (AUTOPOSTING_BASE_URL / --base-url) points at the API, not the web app.`,
523
+ response.status,
524
+ "INVALID_RESPONSE"
525
+ );
526
+ }
527
+ if (typeof parsed === "object" && parsed !== null && parsed.success === true) {
528
+ const env = parsed;
529
+ return env.data !== void 0 ? env.data : {};
530
+ }
531
+ return parsed;
532
+ } catch (err) {
533
+ if (err instanceof Error && err.name === "AbortError") {
534
+ throw new AutopostingError(
535
+ `Request to ${method} ${path2} timed out after ${this.timeout}ms.`,
536
+ 0,
537
+ "TIMEOUT"
538
+ );
411
539
  }
412
540
  throw err;
541
+ } finally {
542
+ clearTimeout(timer);
413
543
  }
414
- if (response.status === 204) {
415
- return {};
416
- }
417
- return response.json();
418
544
  }
419
545
  };
420
546
  }
@@ -862,13 +988,26 @@ var init_tools = __esm({
862
988
  },
863
989
  {
864
990
  name: "enrich-idea",
865
- description: "Enrich an idea with additional AI-generated context.",
991
+ description: "Enrich an idea (title/hook/angle) into platform-ready drafts across 1..5 platforms. Async \u2014 returns a job ID.",
866
992
  inputSchema: {
867
993
  type: "object",
868
994
  properties: {
869
- id: { type: "string", description: "Idea ID to enrich" }
995
+ title: { type: "string", description: "Idea title" },
996
+ hook: { type: "string", description: "Idea hook" },
997
+ angle: { type: "string", description: "Idea angle" },
998
+ platforms: {
999
+ type: "array",
1000
+ description: "Target platforms (1..5)",
1001
+ items: {
1002
+ type: "string",
1003
+ enum: ["twitter", "linkedin", "instagram", "youtube", "threads"]
1004
+ },
1005
+ minItems: 1,
1006
+ maxItems: 5
1007
+ },
1008
+ kbId: { type: "string", description: "Optional knowledge base ID for context" }
870
1009
  },
871
- required: ["id"],
1010
+ required: ["title", "hook", "angle", "platforms"],
872
1011
  additionalProperties: false
873
1012
  }
874
1013
  },
@@ -1345,7 +1484,17 @@ async function dispatchToolCall(name, args, client) {
1345
1484
  return ok(result);
1346
1485
  }
1347
1486
  case "enrich-idea": {
1348
- const result = await client.ideas.enrich(args.id);
1487
+ const result = await client.ideas.enrich({
1488
+ idea: {
1489
+ title: args.title,
1490
+ hook: args.hook,
1491
+ angle: args.angle
1492
+ },
1493
+ platforms: args.platforms.map((platform) => ({
1494
+ platform
1495
+ })),
1496
+ ...args.kbId ? { kbId: args.kbId } : {}
1497
+ });
1349
1498
  return ok(result);
1350
1499
  }
1351
1500
  case "delete-idea": {
@@ -1523,7 +1672,8 @@ var import_node_fs = __toESM(require("fs"));
1523
1672
  var import_node_os = __toESM(require("os"));
1524
1673
  var import_node_path = __toESM(require("path"));
1525
1674
  function getCredentialsPath() {
1526
- const configBase = process.env.XDG_CONFIG_HOME ?? import_node_path.default.join(import_node_os.default.homedir(), ".config");
1675
+ const xdg = process.env.XDG_CONFIG_HOME;
1676
+ const configBase = xdg && import_node_path.default.isAbsolute(xdg) ? xdg : import_node_path.default.join(import_node_os.default.homedir(), ".config");
1527
1677
  return import_node_path.default.join(configBase, "autoposting", "credentials.json");
1528
1678
  }
1529
1679
  function readCredentials() {
@@ -1539,11 +1689,27 @@ function writeCredentials(creds) {
1539
1689
  const filePath = getCredentialsPath();
1540
1690
  const dir = import_node_path.default.dirname(filePath);
1541
1691
  import_node_fs.default.mkdirSync(dir, { recursive: true, mode: 448 });
1542
- import_node_fs.default.writeFileSync(filePath, JSON.stringify(creds, null, 2), { encoding: "utf8", mode: 384 });
1692
+ const tmpPath = `${filePath}.${process.pid}.tmp`;
1693
+ try {
1694
+ import_node_fs.default.writeFileSync(tmpPath, JSON.stringify(creds, null, 2), { encoding: "utf8", mode: 384 });
1695
+ import_node_fs.default.renameSync(tmpPath, filePath);
1696
+ } catch (err) {
1697
+ try {
1698
+ import_node_fs.default.unlinkSync(tmpPath);
1699
+ } catch {
1700
+ }
1701
+ throw err;
1702
+ }
1543
1703
  import_node_fs.default.chmodSync(filePath, 384);
1544
1704
  }
1545
1705
  function saveProfile(name, profile) {
1546
1706
  const existing = readCredentials();
1707
+ if (!existing && import_node_fs.default.existsSync(getCredentialsPath())) {
1708
+ try {
1709
+ import_node_fs.default.renameSync(getCredentialsPath(), `${getCredentialsPath()}.corrupt`);
1710
+ } catch {
1711
+ }
1712
+ }
1547
1713
  const creds = existing ?? { activeProfile: name, profiles: {} };
1548
1714
  creds.profiles[name] = profile;
1549
1715
  creds.activeProfile = name;
@@ -1608,7 +1774,9 @@ function red(text) {
1608
1774
  }
1609
1775
  function detectOutputMode(options) {
1610
1776
  if (options.quiet) return "quiet";
1611
- if (options.json || !process.stdout.isTTY) return "json";
1777
+ if (options.format === "json" || options.json) return "json";
1778
+ if (options.format === "table") return "tty";
1779
+ if (!process.stdout.isTTY) return "json";
1612
1780
  return "tty";
1613
1781
  }
1614
1782
  function formatOutput(data, mode) {
@@ -1674,7 +1842,7 @@ var OraSpinner = class {
1674
1842
  if (this.instance) {
1675
1843
  this.instance.fail(message);
1676
1844
  this.instance = null;
1677
- } else {
1845
+ } else if (message) {
1678
1846
  process.stderr.write(`${message}
1679
1847
  `);
1680
1848
  }
@@ -1691,6 +1859,10 @@ var Printer = class {
1691
1859
  this.mode = mode;
1692
1860
  }
1693
1861
  mode;
1862
+ /** True only for human-readable output, where extra hint lines are safe to print. */
1863
+ isTty() {
1864
+ return this.mode === "tty";
1865
+ }
1694
1866
  log(data) {
1695
1867
  const out = formatOutput(data, this.mode);
1696
1868
  if (out !== "") console.log(out);
@@ -1741,12 +1913,18 @@ function matchesClass(error, className) {
1741
1913
  return false;
1742
1914
  }
1743
1915
  function exitCodeFromError(error) {
1916
+ const attached = error?.exitCode;
1917
+ if (typeof attached === "number" && Number.isInteger(attached) && attached >= 0) {
1918
+ return attached;
1919
+ }
1744
1920
  if (matchesClass(error, "AuthenticationError")) return EXIT_CODES.AUTH_ERROR;
1745
1921
  if (matchesClass(error, "ScopeError")) return EXIT_CODES.SCOPE_ERROR;
1746
1922
  if (matchesClass(error, "NotFoundError")) return EXIT_CODES.NOT_FOUND;
1747
1923
  if (matchesClass(error, "RateLimitError")) return EXIT_CODES.RATE_LIMITED;
1748
1924
  if (matchesClass(error, "ValidationError")) return EXIT_CODES.VALIDATION_ERROR;
1749
1925
  if (matchesClass(error, "NetworkError")) return EXIT_CODES.NETWORK_ERROR;
1926
+ const code = error?.code;
1927
+ if (code === "NETWORK_ERROR" || code === "TIMEOUT") return EXIT_CODES.NETWORK_ERROR;
1750
1928
  return EXIT_CODES.GENERAL_ERROR;
1751
1929
  }
1752
1930
 
@@ -1767,7 +1945,7 @@ function createAgentsCommand() {
1767
1945
  const client = new Autoposting({ apiKey: cred.apiKey });
1768
1946
  const list = await client.agents.list();
1769
1947
  spinner.stop();
1770
- const rows = list.map((a) => ({
1948
+ const rows = list.items.map((a) => ({
1771
1949
  id: a.id,
1772
1950
  name: a.name,
1773
1951
  type: a.type,
@@ -1777,7 +1955,7 @@ function createAgentsCommand() {
1777
1955
  }));
1778
1956
  printer.table(rows, ["id", "name", "type", "brand", "frequency", "enabled"]);
1779
1957
  } catch (err) {
1780
- spinner.stop();
1958
+ spinner.fail();
1781
1959
  printer.error(err);
1782
1960
  process.exit(resolveExitCode(err));
1783
1961
  }
@@ -1793,7 +1971,7 @@ function createAgentsCommand() {
1793
1971
  spinner.stop();
1794
1972
  printer.log(agent);
1795
1973
  } catch (err) {
1796
- spinner.stop();
1974
+ spinner.fail();
1797
1975
  printer.error(err);
1798
1976
  process.exit(resolveExitCode(err));
1799
1977
  }
@@ -1819,7 +1997,7 @@ function createAgentsCommand() {
1819
1997
  spinner.stop();
1820
1998
  printer.log(agent);
1821
1999
  } catch (err) {
1822
- spinner.stop();
2000
+ spinner.fail();
1823
2001
  printer.error(err);
1824
2002
  process.exit(resolveExitCode(err));
1825
2003
  }
@@ -1843,7 +2021,7 @@ function createAgentsCommand() {
1843
2021
  spinner.stop();
1844
2022
  printer.log(agent);
1845
2023
  } catch (err) {
1846
- spinner.stop();
2024
+ spinner.fail();
1847
2025
  printer.error(err);
1848
2026
  process.exit(resolveExitCode(err));
1849
2027
  }
@@ -1864,7 +2042,7 @@ function createAgentsCommand() {
1864
2042
  spinner.stop();
1865
2043
  printer.log(`Agent "${id}" deleted.`);
1866
2044
  } catch (err) {
1867
- spinner.stop();
2045
+ spinner.fail();
1868
2046
  printer.error(err);
1869
2047
  process.exit(resolveExitCode(err));
1870
2048
  }
@@ -1876,11 +2054,11 @@ function createAgentsCommand() {
1876
2054
  try {
1877
2055
  const cred = resolveAuth({ apiKey: globals.apiKey });
1878
2056
  const client = new Autoposting({ apiKey: cred.apiKey });
1879
- const agentRun = await client.agents.run(id);
2057
+ const queued = await client.agents.run(id);
1880
2058
  spinner.stop();
1881
- printer.log(agentRun);
2059
+ printer.log({ runId: queued.runId, status: queued.status });
1882
2060
  } catch (err) {
1883
- spinner.stop();
2061
+ spinner.fail();
1884
2062
  printer.error(err);
1885
2063
  process.exit(resolveExitCode(err));
1886
2064
  }
@@ -1896,7 +2074,7 @@ function createAgentsCommand() {
1896
2074
  spinner.stop();
1897
2075
  printer.log(agent);
1898
2076
  } catch (err) {
1899
- spinner.stop();
2077
+ spinner.fail();
1900
2078
  printer.error(err);
1901
2079
  process.exit(resolveExitCode(err));
1902
2080
  }
@@ -1910,7 +2088,7 @@ function createAgentsCommand() {
1910
2088
  const client = new Autoposting({ apiKey: cred.apiKey });
1911
2089
  const list = await client.agents.runs(id);
1912
2090
  spinner.stop();
1913
- const rows = list.map((r) => ({
2091
+ const rows = list.items.map((r) => ({
1914
2092
  id: r.id,
1915
2093
  status: r.status,
1916
2094
  output: r.output ? r.output.length > 60 ? `${r.output.slice(0, 57)}\u2026` : r.output : "\u2014",
@@ -1918,7 +2096,7 @@ function createAgentsCommand() {
1918
2096
  }));
1919
2097
  printer.table(rows, ["id", "status", "output", "createdAt"]);
1920
2098
  } catch (err) {
1921
- spinner.stop();
2099
+ spinner.fail();
1922
2100
  printer.error(err);
1923
2101
  process.exit(resolveExitCode(err));
1924
2102
  }
@@ -1931,8 +2109,32 @@ var import_commander2 = require("commander");
1931
2109
  var import_node_child_process = require("child_process");
1932
2110
 
1933
2111
  // src/auth/device-code-client.ts
2112
+ async function fetchWithTimeout(url, init = {}, timeoutMs = 3e4) {
2113
+ const controller = new AbortController();
2114
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
2115
+ try {
2116
+ return await fetch(url, { ...init, signal: controller.signal });
2117
+ } catch (err) {
2118
+ if (err instanceof Error && err.name === "AbortError") {
2119
+ throw new Error(`Request to ${url} timed out after ${timeoutMs}ms`);
2120
+ }
2121
+ throw err;
2122
+ } finally {
2123
+ clearTimeout(timer);
2124
+ }
2125
+ }
2126
+ async function parseJson(res) {
2127
+ const text = await res.text();
2128
+ try {
2129
+ return JSON.parse(text);
2130
+ } catch {
2131
+ throw new Error(
2132
+ `Expected a JSON response (HTTP ${res.status}) but received non-JSON. Verify the API base URL points at the API, not the web app.`
2133
+ );
2134
+ }
2135
+ }
1934
2136
  async function requestDeviceCode(baseUrl) {
1935
- const res = await fetch(`${baseUrl}/auth/cli/device-code`, {
2137
+ const res = await fetchWithTimeout(`${baseUrl}/auth/cli/device-code`, {
1936
2138
  method: "POST",
1937
2139
  headers: { "Content-Type": "application/json" }
1938
2140
  });
@@ -1940,22 +2142,20 @@ async function requestDeviceCode(baseUrl) {
1940
2142
  const text = await res.text().catch(() => res.statusText);
1941
2143
  throw new Error(`Device code request failed (${res.status}): ${text}`);
1942
2144
  }
1943
- const data = await res.json();
1944
- return data;
2145
+ return parseJson(res);
1945
2146
  }
1946
2147
  async function pollDeviceCode(baseUrl, deviceCode) {
1947
2148
  const url = `${baseUrl}/auth/cli/poll?device_code=${encodeURIComponent(deviceCode)}`;
1948
- const res = await fetch(url);
2149
+ const res = await fetchWithTimeout(url);
1949
2150
  if (!res.ok) {
1950
2151
  const text = await res.text().catch(() => res.statusText);
1951
2152
  throw new Error(`Poll request failed (${res.status}): ${text}`);
1952
2153
  }
1953
- const data = await res.json();
1954
- return data;
2154
+ return parseJson(res);
1955
2155
  }
1956
2156
 
1957
2157
  // src/commands/auth.ts
1958
- var DEFAULT_BASE_URL = "https://app.autoposting.ai";
2158
+ var DEFAULT_BASE_URL = "https://app.autoposting.ai/api-proxy";
1959
2159
  function resolveBaseUrl(globals) {
1960
2160
  return globals.baseUrl ?? process.env.AUTOPOSTING_BASE_URL ?? DEFAULT_BASE_URL;
1961
2161
  }
@@ -2129,17 +2329,19 @@ function createBillingCommand() {
2129
2329
  printer.table(
2130
2330
  [
2131
2331
  {
2132
- plan: status.plan,
2133
- status: status.status,
2134
- "trial ends": status.trialEndsAt ?? "\u2014",
2135
- "renewal date": status.renewalDate ?? "\u2014",
2136
- "cancels at": status.cancelAt ?? "\u2014"
2332
+ plan: status.planName,
2333
+ status: status.status ?? "\u2014",
2334
+ cycle: status.billingCycle ?? "\u2014",
2335
+ accounts: `${status.accountsUsed}/${status.accountLimit === -1 ? "\u221E" : status.accountLimit}`,
2336
+ credits: status.creditBalance,
2337
+ renews: status.currentPeriodEnd ?? "\u2014",
2338
+ "trial ends": status.trialEnd ?? "\u2014"
2137
2339
  }
2138
2340
  ],
2139
- ["plan", "status", "trial ends", "renewal date", "cancels at"]
2341
+ ["plan", "status", "cycle", "accounts", "credits", "renews", "trial ends"]
2140
2342
  );
2141
2343
  } catch (err) {
2142
- spinner.stop();
2344
+ spinner.fail();
2143
2345
  printer.error(err);
2144
2346
  process.exit(resolveExitCode2(err));
2145
2347
  }
@@ -2156,22 +2358,23 @@ function createBillingCommand() {
2156
2358
  printer.table(
2157
2359
  [
2158
2360
  {
2159
- total: credits.total,
2160
- used: credits.used,
2161
- remaining: credits.remaining
2361
+ balance: credits.balanceFormatted,
2362
+ "spent (30d)": credits.totalSpent30d
2162
2363
  }
2163
2364
  ],
2164
- ["total", "used", "remaining"]
2365
+ ["balance", "spent (30d)"]
2165
2366
  );
2166
- if (credits.breakdown && Object.keys(credits.breakdown).length > 0) {
2167
- const breakdownRows = Object.entries(credits.breakdown).map(([key, val]) => ({
2168
- category: key,
2169
- credits: val
2367
+ if (credits.recentUsage.length > 0) {
2368
+ const usageRows = credits.recentUsage.map((e) => ({
2369
+ date: e.date,
2370
+ description: e.description,
2371
+ amount: e.amount,
2372
+ type: e.type
2170
2373
  }));
2171
- printer.table(breakdownRows, ["category", "credits"]);
2374
+ printer.table(usageRows, ["date", "description", "amount", "type"]);
2172
2375
  }
2173
2376
  } catch (err) {
2174
- spinner.stop();
2377
+ spinner.fail();
2175
2378
  printer.error(err);
2176
2379
  process.exit(resolveExitCode2(err));
2177
2380
  }
@@ -2182,6 +2385,24 @@ function createBillingCommand() {
2182
2385
  // src/commands/brands.ts
2183
2386
  var import_commander4 = require("commander");
2184
2387
  init_dist();
2388
+ function tokenStatus(c, now = Date.now()) {
2389
+ if (!c.connected) return "\u2014";
2390
+ if (c.refreshError) return "expired";
2391
+ if (c.expiresAt) {
2392
+ const expMs = Date.parse(c.expiresAt);
2393
+ if (Number.isNaN(expMs) || expMs <= now) return "expired";
2394
+ }
2395
+ return "ok";
2396
+ }
2397
+ function buildAuthStatusRow(c, now = Date.now()) {
2398
+ return {
2399
+ platform: c.platform,
2400
+ connected: c.connected ? "yes" : "no",
2401
+ username: c.platformUsername ?? "\u2014",
2402
+ "token status": tokenStatus(c, now),
2403
+ expires: c.expiresAt ?? "\u2014"
2404
+ };
2405
+ }
2185
2406
  function createBrandsCommand() {
2186
2407
  const brands = new import_commander4.Command("brands").description("Manage brands");
2187
2408
  brands.command("list").description("List all brands").action(async (_opts, cmd) => {
@@ -2201,7 +2422,7 @@ function createBrandsCommand() {
2201
2422
  }));
2202
2423
  printer.table(rows, ["slug", "name", "platforms", "timezone"]);
2203
2424
  } catch (err) {
2204
- spinner.stop();
2425
+ spinner.fail();
2205
2426
  printer.error(err);
2206
2427
  process.exit(exitCodeFromError(err));
2207
2428
  }
@@ -2217,7 +2438,7 @@ function createBrandsCommand() {
2217
2438
  spinner.stop();
2218
2439
  printer.log(brand);
2219
2440
  } catch (err) {
2220
- spinner.stop();
2441
+ spinner.fail();
2221
2442
  printer.error(err);
2222
2443
  process.exit(exitCodeFromError(err));
2223
2444
  }
@@ -2236,7 +2457,7 @@ function createBrandsCommand() {
2236
2457
  spinner.stop();
2237
2458
  printer.log(brand);
2238
2459
  } catch (err) {
2239
- spinner.stop();
2460
+ spinner.fail();
2240
2461
  printer.error(err);
2241
2462
  process.exit(exitCodeFromError(err));
2242
2463
  }
@@ -2255,7 +2476,7 @@ function createBrandsCommand() {
2255
2476
  spinner.stop();
2256
2477
  printer.log(brand);
2257
2478
  } catch (err) {
2258
- spinner.stop();
2479
+ spinner.fail();
2259
2480
  printer.error(err);
2260
2481
  process.exit(exitCodeFromError(err));
2261
2482
  }
@@ -2275,7 +2496,7 @@ function createBrandsCommand() {
2275
2496
  spinner.stop();
2276
2497
  printer.log(`Brand "${slug}" deleted.`);
2277
2498
  } catch (err) {
2278
- spinner.stop();
2499
+ spinner.fail();
2279
2500
  printer.error(err);
2280
2501
  process.exit(exitCodeFromError(err));
2281
2502
  }
@@ -2289,15 +2510,10 @@ function createBrandsCommand() {
2289
2510
  const client = new Autoposting({ apiKey: cred.apiKey });
2290
2511
  const connections = await client.brands.authStatus(slug);
2291
2512
  spinner.stop();
2292
- const rows = connections.map((c) => ({
2293
- platform: c.platform,
2294
- connected: c.connected ? "yes" : "no",
2295
- username: c.username ?? "\u2014",
2296
- "token status": c.tokenExpired === true ? "expired" : c.connected ? "ok" : "\u2014"
2297
- }));
2298
- printer.table(rows, ["platform", "connected", "username", "token status"]);
2513
+ const rows = connections.map((c) => buildAuthStatusRow(c));
2514
+ printer.table(rows, ["platform", "connected", "username", "token status", "expires"]);
2299
2515
  } catch (err) {
2300
- spinner.stop();
2516
+ spinner.fail();
2301
2517
  printer.error(err);
2302
2518
  process.exit(exitCodeFromError(err));
2303
2519
  }
@@ -2324,7 +2540,7 @@ function createCarouselsCommand() {
2324
2540
  const client = new Autoposting({ apiKey: cred.apiKey });
2325
2541
  const list = await client.carousels.list();
2326
2542
  spinner.stop();
2327
- const rows = list.map((c) => ({
2543
+ const rows = list.items.map((c) => ({
2328
2544
  id: c.id,
2329
2545
  title: c.title ?? "\u2014",
2330
2546
  slides: c.slides.length,
@@ -2333,7 +2549,7 @@ function createCarouselsCommand() {
2333
2549
  }));
2334
2550
  printer.table(rows, ["id", "title", "slides", "status", "createdAt"]);
2335
2551
  } catch (err) {
2336
- spinner.stop();
2552
+ spinner.fail();
2337
2553
  printer.error(err);
2338
2554
  process.exit(resolveExitCode3(err));
2339
2555
  }
@@ -2349,7 +2565,7 @@ function createCarouselsCommand() {
2349
2565
  spinner.stop();
2350
2566
  printer.log(carousel);
2351
2567
  } catch (err) {
2352
- spinner.stop();
2568
+ spinner.fail();
2353
2569
  printer.error(err);
2354
2570
  process.exit(resolveExitCode3(err));
2355
2571
  }
@@ -2367,7 +2583,7 @@ function createCarouselsCommand() {
2367
2583
  spinner.stop();
2368
2584
  printer.log(carousel);
2369
2585
  } catch (err) {
2370
- spinner.stop();
2586
+ spinner.fail();
2371
2587
  printer.error(err);
2372
2588
  process.exit(resolveExitCode3(err));
2373
2589
  }
@@ -2387,7 +2603,7 @@ function createCarouselsCommand() {
2387
2603
  spinner.stop();
2388
2604
  printer.log(carousel);
2389
2605
  } catch (err) {
2390
- spinner.stop();
2606
+ spinner.fail();
2391
2607
  printer.error(err);
2392
2608
  process.exit(resolveExitCode3(err));
2393
2609
  }
@@ -2403,7 +2619,7 @@ function createCarouselsCommand() {
2403
2619
  spinner.stop();
2404
2620
  printer.log(result);
2405
2621
  } catch (err) {
2406
- spinner.stop();
2622
+ spinner.fail();
2407
2623
  printer.error(err);
2408
2624
  process.exit(resolveExitCode3(err));
2409
2625
  }
@@ -2423,7 +2639,7 @@ function createCarouselsCommand() {
2423
2639
  spinner.stop();
2424
2640
  printer.log(`Carousel "${id}" deleted.`);
2425
2641
  } catch (err) {
2426
- spinner.stop();
2642
+ spinner.fail();
2427
2643
  printer.error(err);
2428
2644
  process.exit(resolveExitCode3(err));
2429
2645
  }
@@ -2450,7 +2666,7 @@ function createClipsCommand() {
2450
2666
  const client = new Autoposting({ apiKey: cred.apiKey });
2451
2667
  const list = await client.clips.list();
2452
2668
  spinner.stop();
2453
- const rows = list.map((c) => ({
2669
+ const rows = list.clips.map((c) => ({
2454
2670
  id: c.id,
2455
2671
  name: c.name,
2456
2672
  status: c.status,
@@ -2458,7 +2674,7 @@ function createClipsCommand() {
2458
2674
  }));
2459
2675
  printer.table(rows, ["id", "name", "status", "duration"]);
2460
2676
  } catch (err) {
2461
- spinner.stop();
2677
+ spinner.fail();
2462
2678
  printer.error(err);
2463
2679
  process.exit(resolveExitCode4(err));
2464
2680
  }
@@ -2474,7 +2690,7 @@ function createClipsCommand() {
2474
2690
  spinner.stop();
2475
2691
  printer.log(clip);
2476
2692
  } catch (err) {
2477
- spinner.stop();
2693
+ spinner.fail();
2478
2694
  printer.error(err);
2479
2695
  process.exit(resolveExitCode4(err));
2480
2696
  }
@@ -2495,7 +2711,7 @@ function createClipsCommand() {
2495
2711
  spinner.stop();
2496
2712
  printer.log(clip);
2497
2713
  } catch (err) {
2498
- spinner.stop();
2714
+ spinner.fail();
2499
2715
  printer.error(err);
2500
2716
  process.exit(resolveExitCode4(err));
2501
2717
  }
@@ -2507,14 +2723,14 @@ function createClipsCommand() {
2507
2723
  try {
2508
2724
  const cred = resolveAuth({ apiKey: globals.apiKey });
2509
2725
  const client = new Autoposting({ apiKey: cred.apiKey });
2510
- const clip = await client.clips.importUrl({
2726
+ const { clipId } = await client.clips.importUrl({
2511
2727
  url: opts.url,
2512
2728
  ...opts.name ? { name: opts.name } : {}
2513
2729
  });
2514
2730
  spinner.stop();
2515
- printer.log(clip);
2731
+ printer.log({ clipId });
2516
2732
  } catch (err) {
2517
- spinner.stop();
2733
+ spinner.fail();
2518
2734
  printer.error(err);
2519
2735
  process.exit(resolveExitCode4(err));
2520
2736
  }
@@ -2526,11 +2742,11 @@ function createClipsCommand() {
2526
2742
  try {
2527
2743
  const cred = resolveAuth({ apiKey: globals.apiKey });
2528
2744
  const client = new Autoposting({ apiKey: cred.apiKey });
2529
- const clip = await client.clips.render(id);
2745
+ const { jobIds, activeJobIds, reusedJobIds } = await client.clips.render(id);
2530
2746
  spinner.stop();
2531
- printer.log(clip);
2747
+ printer.log({ jobIds, activeJobIds, reusedJobIds });
2532
2748
  } catch (err) {
2533
- spinner.stop();
2749
+ spinner.fail();
2534
2750
  printer.error(err);
2535
2751
  process.exit(resolveExitCode4(err));
2536
2752
  }
@@ -2550,7 +2766,7 @@ function createClipsCommand() {
2550
2766
  spinner.stop();
2551
2767
  printer.log(`Clip "${id}" deleted.`);
2552
2768
  } catch (err) {
2553
- spinner.stop();
2769
+ spinner.fail();
2554
2770
  printer.error(err);
2555
2771
  process.exit(resolveExitCode4(err));
2556
2772
  }
@@ -2561,8 +2777,40 @@ function createClipsCommand() {
2561
2777
  // src/commands/posts.ts
2562
2778
  var import_commander7 = require("commander");
2563
2779
  init_dist();
2780
+ var VALID_PLATFORMS = ["x", "linkedin", "instagram", "threads", "youtube"];
2564
2781
  function parsePlatforms(raw) {
2565
- return raw.split(",").map((p) => p.trim());
2782
+ const parts = raw.split(",").map((p) => p.trim()).filter(Boolean);
2783
+ if (parts.length === 0) {
2784
+ throw new Error("No platforms provided. Pass a comma-separated list, e.g. --platforms x,linkedin");
2785
+ }
2786
+ const invalid = parts.filter((p) => !VALID_PLATFORMS.includes(p));
2787
+ if (invalid.length > 0) {
2788
+ throw new Error(
2789
+ `Unsupported platform(s): ${invalid.join(", ")}. Valid platforms: ${VALID_PLATFORMS.join(", ")}`
2790
+ );
2791
+ }
2792
+ return parts;
2793
+ }
2794
+ function parsePositiveInt(value, flag) {
2795
+ const n = Number(value);
2796
+ if (!Number.isInteger(n) || n < 1) {
2797
+ throw new Error(`${flag} must be a positive integer (received "${value}").`);
2798
+ }
2799
+ return n;
2800
+ }
2801
+ function validateScheduledAt(value) {
2802
+ const ms = Date.parse(value);
2803
+ if (Number.isNaN(ms) || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(value)) {
2804
+ throw new Error(
2805
+ `--at must be a valid ISO 8601 datetime, e.g. 2026-06-30T14:00:00Z (received "${value}").`
2806
+ );
2807
+ }
2808
+ if (ms <= Date.now()) {
2809
+ throw new Error(
2810
+ `--at must be in the future (received "${value}", which is in the past). A past schedule time publishes immediately.`
2811
+ );
2812
+ }
2813
+ return value;
2566
2814
  }
2567
2815
  function resolveExitCode5(err) {
2568
2816
  const attached = err.exitCode;
@@ -2581,11 +2829,11 @@ function createPostsCommand() {
2581
2829
  const result = await client.posts.list({
2582
2830
  brandSlug: opts.brand,
2583
2831
  status: opts.status,
2584
- limit: parseInt(opts.limit, 10),
2585
- page: parseInt(opts.page, 10)
2832
+ limit: parsePositiveInt(opts.limit, "--limit"),
2833
+ page: parsePositiveInt(opts.page, "--page")
2586
2834
  });
2587
2835
  spinner.stop();
2588
- const rows = result.data.map((p) => ({
2836
+ const rows = result.map((p) => ({
2589
2837
  id: p.id,
2590
2838
  brand: p.brandSlug,
2591
2839
  status: p.status,
@@ -2595,7 +2843,7 @@ function createPostsCommand() {
2595
2843
  }));
2596
2844
  printer.table(rows, ["id", "brand", "status", "platforms", "text", "scheduledAt"]);
2597
2845
  } catch (err) {
2598
- spinner.stop();
2846
+ spinner.fail();
2599
2847
  printer.error(err);
2600
2848
  process.exit(resolveExitCode5(err));
2601
2849
  }
@@ -2611,12 +2859,15 @@ function createPostsCommand() {
2611
2859
  spinner.stop();
2612
2860
  printer.log(post);
2613
2861
  } catch (err) {
2614
- spinner.stop();
2862
+ spinner.fail();
2615
2863
  printer.error(err);
2616
2864
  process.exit(resolveExitCode5(err));
2617
2865
  }
2618
2866
  });
2619
- posts.command("create").description("Create a new post").requiredOption("--brand <slug>", "Brand slug").requiredOption("--text <text>", "Post text content").requiredOption("--platforms <list>", "Comma-separated platforms (e.g. x,linkedin)").option("--at <iso>", "Schedule date/time (ISO 8601)").action(
2867
+ posts.command("create").description("Create a new post").requiredOption("--brand <slug>", "Brand slug").requiredOption("--text <text>", "Post text content").requiredOption("--platforms <list>", "Comma-separated platforms (e.g. x,linkedin)").option("--at <iso>", "Schedule date/time (ISO 8601)").option(
2868
+ "--thread <text...>",
2869
+ "Additional posts appended after --text to form a thread (X/Threads only, max 25)"
2870
+ ).action(
2620
2871
  async (opts, cmd) => {
2621
2872
  const globals = cmd.optsWithGlobals();
2622
2873
  const printer = createPrinter(globals);
@@ -2628,12 +2879,13 @@ function createPostsCommand() {
2628
2879
  brandSlug: opts.brand,
2629
2880
  text: opts.text,
2630
2881
  platforms: parsePlatforms(opts.platforms),
2631
- ...opts.at ? { scheduledAt: opts.at } : {}
2882
+ ...opts.at ? { scheduledAt: validateScheduledAt(opts.at) } : {},
2883
+ ...opts.thread && opts.thread.length > 0 ? { thread: opts.thread } : {}
2632
2884
  });
2633
2885
  spinner.stop();
2634
2886
  printer.log(post);
2635
2887
  } catch (err) {
2636
- spinner.stop();
2888
+ spinner.fail();
2637
2889
  printer.error(err);
2638
2890
  process.exit(resolveExitCode5(err));
2639
2891
  }
@@ -2650,12 +2902,12 @@ function createPostsCommand() {
2650
2902
  const post = await client.posts.update(id, {
2651
2903
  ...opts.text ? { text: opts.text } : {},
2652
2904
  ...opts.platforms ? { platforms: parsePlatforms(opts.platforms) } : {},
2653
- ...opts.at ? { scheduledAt: opts.at } : {}
2905
+ ...opts.at ? { scheduledAt: validateScheduledAt(opts.at) } : {}
2654
2906
  });
2655
2907
  spinner.stop();
2656
2908
  printer.log(post);
2657
2909
  } catch (err) {
2658
- spinner.stop();
2910
+ spinner.fail();
2659
2911
  printer.error(err);
2660
2912
  process.exit(resolveExitCode5(err));
2661
2913
  }
@@ -2676,8 +2928,11 @@ function createPostsCommand() {
2676
2928
  spinner.stop();
2677
2929
  printer.log(`Post "${id}" deleted.`);
2678
2930
  } catch (err) {
2679
- spinner.stop();
2931
+ spinner.fail();
2680
2932
  printer.error(err);
2933
+ if (!(err instanceof NotFoundError) && printer.isTty()) {
2934
+ printer.error(`The post may not have been deleted \u2014 verify with: ap posts get "${id}"`);
2935
+ }
2681
2936
  process.exit(resolveExitCode5(err));
2682
2937
  }
2683
2938
  });
@@ -2692,7 +2947,7 @@ function createPostsCommand() {
2692
2947
  spinner.stop();
2693
2948
  printer.log(post);
2694
2949
  } catch (err) {
2695
- spinner.stop();
2950
+ spinner.fail();
2696
2951
  printer.error(err);
2697
2952
  process.exit(resolveExitCode5(err));
2698
2953
  }
@@ -2704,11 +2959,11 @@ function createPostsCommand() {
2704
2959
  try {
2705
2960
  const cred = resolveAuth({ apiKey: globals.apiKey });
2706
2961
  const client = new Autoposting({ apiKey: cred.apiKey });
2707
- const post = await client.posts.schedule(id, opts.at);
2962
+ const post = await client.posts.schedule(id, validateScheduledAt(opts.at));
2708
2963
  spinner.stop();
2709
2964
  printer.log(post);
2710
2965
  } catch (err) {
2711
- spinner.stop();
2966
+ spinner.fail();
2712
2967
  printer.error(err);
2713
2968
  process.exit(resolveExitCode5(err));
2714
2969
  }
@@ -2724,7 +2979,7 @@ function createPostsCommand() {
2724
2979
  spinner.stop();
2725
2980
  printer.log(post);
2726
2981
  } catch (err) {
2727
- spinner.stop();
2982
+ spinner.fail();
2728
2983
  printer.error(err);
2729
2984
  process.exit(resolveExitCode5(err));
2730
2985
  }
@@ -2740,7 +2995,7 @@ function createPostsCommand() {
2740
2995
  spinner.stop();
2741
2996
  printer.log(post);
2742
2997
  } catch (err) {
2743
- spinner.stop();
2998
+ spinner.fail();
2744
2999
  printer.error(err);
2745
3000
  process.exit(resolveExitCode5(err));
2746
3001
  }
@@ -2756,7 +3011,7 @@ function createPostsCommand() {
2756
3011
  spinner.stop();
2757
3012
  printer.log(result);
2758
3013
  } catch (err) {
2759
- spinner.stop();
3014
+ spinner.fail();
2760
3015
  printer.error(err);
2761
3016
  process.exit(resolveExitCode5(err));
2762
3017
  }
@@ -2791,7 +3046,7 @@ function createKbCommand() {
2791
3046
  }));
2792
3047
  printer.table(rows, ["id", "name", "docs", "createdAt"]);
2793
3048
  } catch (err) {
2794
- spinner.stop();
3049
+ spinner.fail();
2795
3050
  printer.error(err);
2796
3051
  process.exit(resolveExitCode6(err));
2797
3052
  }
@@ -2807,7 +3062,7 @@ function createKbCommand() {
2807
3062
  spinner.stop();
2808
3063
  printer.log(result);
2809
3064
  } catch (err) {
2810
- spinner.stop();
3065
+ spinner.fail();
2811
3066
  printer.error(err);
2812
3067
  process.exit(resolveExitCode6(err));
2813
3068
  }
@@ -2823,7 +3078,7 @@ function createKbCommand() {
2823
3078
  spinner.stop();
2824
3079
  printer.log(result);
2825
3080
  } catch (err) {
2826
- spinner.stop();
3081
+ spinner.fail();
2827
3082
  printer.error(err);
2828
3083
  process.exit(resolveExitCode6(err));
2829
3084
  }
@@ -2843,7 +3098,7 @@ function createKbCommand() {
2843
3098
  spinner.stop();
2844
3099
  printer.log(`Knowledge base "${id}" deleted.`);
2845
3100
  } catch (err) {
2846
- spinner.stop();
3101
+ spinner.fail();
2847
3102
  printer.error(err);
2848
3103
  process.exit(resolveExitCode6(err));
2849
3104
  }
@@ -2855,16 +3110,17 @@ function createKbCommand() {
2855
3110
  try {
2856
3111
  const cred = resolveAuth({ apiKey: globals.apiKey });
2857
3112
  const client = new Autoposting({ apiKey: cred.apiKey });
2858
- const results = await client.kb.search(id, opts.query);
3113
+ const res = await client.kb.search(id, opts.query);
2859
3114
  spinner.stop();
2860
- const rows = results.map((r) => ({
2861
- docId: r.docId,
2862
- score: r.score.toFixed(3),
3115
+ const rows = res.results.map((r) => ({
3116
+ kind: r.kind,
3117
+ score: r.score != null ? r.score.toFixed(3) : "\u2014",
3118
+ uri: r.uri ?? "\u2014",
2863
3119
  content: r.content.length > 80 ? `${r.content.slice(0, 77)}\u2026` : r.content
2864
3120
  }));
2865
- printer.table(rows, ["docId", "score", "content"]);
3121
+ printer.table(rows, ["kind", "score", "uri", "content"]);
2866
3122
  } catch (err) {
2867
- spinner.stop();
3123
+ spinner.fail();
2868
3124
  printer.error(err);
2869
3125
  process.exit(resolveExitCode6(err));
2870
3126
  }
@@ -2880,7 +3136,7 @@ function createKbCommand() {
2880
3136
  spinner.stop();
2881
3137
  printer.log(doc);
2882
3138
  } catch (err) {
2883
- spinner.stop();
3139
+ spinner.fail();
2884
3140
  printer.error(err);
2885
3141
  process.exit(resolveExitCode6(err));
2886
3142
  }
@@ -2903,7 +3159,7 @@ function createKbCommand() {
2903
3159
  }));
2904
3160
  printer.table(rows, ["id", "name", "type", "status", "createdAt"]);
2905
3161
  } catch (err) {
2906
- spinner.stop();
3162
+ spinner.fail();
2907
3163
  printer.error(err);
2908
3164
  process.exit(resolveExitCode6(err));
2909
3165
  }
@@ -2919,6 +3175,23 @@ function resolveExitCode7(err) {
2919
3175
  if (typeof attached === "number") return attached;
2920
3176
  return exitCodeFromError(err);
2921
3177
  }
3178
+ var VALID_IDEA_PLATFORMS = ["twitter", "linkedin", "instagram", "youtube", "threads"];
3179
+ function parseIdeaPlatforms(raw) {
3180
+ const parts = raw.split(",").map((p) => p.trim()).filter(Boolean);
3181
+ if (parts.length === 0) {
3182
+ throw new Error("No platforms provided. e.g. --platforms twitter,linkedin");
3183
+ }
3184
+ if (parts.length > 5) {
3185
+ throw new Error("At most 5 platforms allowed per enrichment request.");
3186
+ }
3187
+ const invalid = parts.filter((p) => !VALID_IDEA_PLATFORMS.includes(p));
3188
+ if (invalid.length > 0) {
3189
+ throw new Error(
3190
+ `Unsupported platform(s): ${invalid.join(", ")}. Valid: ${VALID_IDEA_PLATFORMS.join(", ")}`
3191
+ );
3192
+ }
3193
+ return parts.map((platform) => ({ platform }));
3194
+ }
2922
3195
  function createIdeasCommand() {
2923
3196
  const ideas = new import_commander9.Command("ideas").description("Manage content ideas");
2924
3197
  ideas.command("generate").description("Generate content ideas").option("--kb <id>", "Knowledge base ID to draw context from").option("--topic <text>", "Topic or theme for ideas").option("--count <n>", "Number of ideas to generate", "5").action(async (opts, cmd) => {
@@ -2934,15 +3207,15 @@ function createIdeasCommand() {
2934
3207
  count: parseInt(opts.count, 10)
2935
3208
  });
2936
3209
  spinner.stop();
2937
- const rows = result.map((i) => ({
2938
- id: i.id,
2939
- topic: i.topic ?? "\u2014",
2940
- enriched: i.enriched ? "yes" : "no",
2941
- text: i.text.length > 80 ? `${i.text.slice(0, 77)}\u2026` : i.text
3210
+ const rows = (result.ideas ?? []).map((i) => ({
3211
+ id: i.id ?? "\u2014",
3212
+ platform: i.targetPlatform,
3213
+ score: i.viralityScore,
3214
+ title: i.title.length > 80 ? `${i.title.slice(0, 77)}\u2026` : i.title
2942
3215
  }));
2943
- printer.table(rows, ["id", "topic", "enriched", "text"]);
3216
+ printer.table(rows, ["id", "platform", "score", "title"]);
2944
3217
  } catch (err) {
2945
- spinner.stop();
3218
+ spinner.fail();
2946
3219
  printer.error(err);
2947
3220
  process.exit(resolveExitCode7(err));
2948
3221
  }
@@ -2956,36 +3229,43 @@ function createIdeasCommand() {
2956
3229
  const client = new Autoposting({ apiKey: cred.apiKey });
2957
3230
  const list = await client.ideas.list();
2958
3231
  spinner.stop();
2959
- const rows = list.map((i) => ({
3232
+ const rows = list.items.map((i) => ({
2960
3233
  id: i.id,
2961
- topic: i.topic ?? "\u2014",
2962
- enriched: i.enriched ? "yes" : "no",
2963
- text: i.text.length > 80 ? `${i.text.slice(0, 77)}\u2026` : i.text,
2964
- createdAt: i.createdAt
3234
+ topic: i.topic || "\u2014",
3235
+ platform: i.targetPlatform,
3236
+ score: i.viralityScore,
3237
+ status: i.status,
3238
+ title: i.title.length > 60 ? `${i.title.slice(0, 57)}\u2026` : i.title
2965
3239
  }));
2966
- printer.table(rows, ["id", "topic", "enriched", "text", "createdAt"]);
3240
+ printer.table(rows, ["id", "topic", "platform", "score", "status", "title"]);
2967
3241
  } catch (err) {
2968
- spinner.stop();
3242
+ spinner.fail();
2969
3243
  printer.error(err);
2970
3244
  process.exit(resolveExitCode7(err));
2971
3245
  }
2972
3246
  });
2973
- ideas.command("enrich <id>").description("Enrich an idea with AI context").action(async (id, _opts, cmd) => {
2974
- const globals = cmd.optsWithGlobals();
2975
- const printer = createPrinter(globals);
2976
- const spinner = printer.spinner(`Enriching idea "${id}"\u2026`);
2977
- try {
2978
- const cred = resolveAuth({ apiKey: globals.apiKey });
2979
- const client = new Autoposting({ apiKey: cred.apiKey });
2980
- const idea = await client.ideas.enrich(id);
2981
- spinner.stop();
2982
- printer.log(idea);
2983
- } catch (err) {
2984
- spinner.stop();
2985
- printer.error(err);
2986
- process.exit(resolveExitCode7(err));
3247
+ ideas.command("enrich").description("Enrich an idea into platform-ready drafts (async; returns a job ID)").requiredOption("--title <text>", "Idea title").requiredOption("--hook <text>", "Idea hook").requiredOption("--angle <text>", "Idea angle").requiredOption("--platforms <list>", `Comma-separated platforms (${VALID_IDEA_PLATFORMS.join(",")})`).option("--kb <id>", "Knowledge base ID for additional context").action(
3248
+ async (opts, cmd) => {
3249
+ const globals = cmd.optsWithGlobals();
3250
+ const printer = createPrinter(globals);
3251
+ const spinner = printer.spinner("Queuing enrichment\u2026");
3252
+ try {
3253
+ const cred = resolveAuth({ apiKey: globals.apiKey });
3254
+ const client = new Autoposting({ apiKey: cred.apiKey });
3255
+ const { jobId } = await client.ideas.enrich({
3256
+ idea: { title: opts.title, hook: opts.hook, angle: opts.angle },
3257
+ platforms: parseIdeaPlatforms(opts.platforms),
3258
+ ...opts.kb ? { kbId: opts.kb } : {}
3259
+ });
3260
+ spinner.stop();
3261
+ printer.log(`Enrichment queued. Job ID: ${jobId}`);
3262
+ } catch (err) {
3263
+ spinner.fail();
3264
+ printer.error(err);
3265
+ process.exit(resolveExitCode7(err));
3266
+ }
2987
3267
  }
2988
- });
3268
+ );
2989
3269
  ideas.command("delete <id>").description("Delete an idea (requires --force)").option("--force", "Confirm deletion without interactive prompt").action(async (id, opts, cmd) => {
2990
3270
  const globals = cmd.optsWithGlobals();
2991
3271
  const printer = createPrinter(globals);
@@ -3001,7 +3281,7 @@ function createIdeasCommand() {
3001
3281
  spinner.stop();
3002
3282
  printer.log(`Idea "${id}" deleted.`);
3003
3283
  } catch (err) {
3004
- spinner.stop();
3284
+ spinner.fail();
3005
3285
  printer.error(err);
3006
3286
  process.exit(resolveExitCode7(err));
3007
3287
  }
@@ -3028,20 +3308,27 @@ function createUsageCommand() {
3028
3308
  const client = new Autoposting({ apiKey: cred.apiKey });
3029
3309
  const summary = await client.usage.summary();
3030
3310
  spinner.stop();
3031
- const rows = Object.entries(summary.platforms).map(([platform, stats]) => ({
3032
- platform,
3033
- posts: stats.posts,
3034
- published: stats.published,
3035
- failed: stats.failed
3311
+ printer.log(`Period: ${summary.range.from.slice(0, 10)} \u2192 ${summary.range.to.slice(0, 10)}`);
3312
+ printer.table(
3313
+ [
3314
+ {
3315
+ posts: summary.posts.total,
3316
+ published: summary.posts.published,
3317
+ agents: summary.agents.total,
3318
+ "active agents": summary.agents.active,
3319
+ "ai cost (usd)": summary.ai.totalCostUsd,
3320
+ "ai requests": summary.ai.requests
3321
+ }
3322
+ ],
3323
+ ["posts", "published", "agents", "active agents", "ai cost (usd)", "ai requests"]
3324
+ );
3325
+ const sourceRows = Object.entries(summary.posts.bySource).map(([source, count]) => ({
3326
+ source,
3327
+ posts: count
3036
3328
  }));
3037
- if (rows.length === 0) {
3038
- printer.log(`No usage data for period: ${summary.period}`);
3039
- } else {
3040
- printer.log(`Period: ${summary.period}`);
3041
- printer.table(rows, ["platform", "posts", "published", "failed"]);
3042
- }
3329
+ printer.table(sourceRows, ["source", "posts"]);
3043
3330
  } catch (err) {
3044
- spinner.stop();
3331
+ spinner.fail();
3045
3332
  printer.error(err);
3046
3333
  process.exit(resolveExitCode8(err));
3047
3334
  }
@@ -3077,7 +3364,7 @@ function createWebhooksCommand() {
3077
3364
  }));
3078
3365
  printer.table(rows, ["id", "url", "events", "active", "createdAt"]);
3079
3366
  } catch (err) {
3080
- spinner.stop();
3367
+ spinner.fail();
3081
3368
  printer.error(err);
3082
3369
  process.exit(resolveExitCode9(err));
3083
3370
  }
@@ -3093,7 +3380,7 @@ function createWebhooksCommand() {
3093
3380
  spinner.stop();
3094
3381
  printer.log(webhook);
3095
3382
  } catch (err) {
3096
- spinner.stop();
3383
+ spinner.fail();
3097
3384
  printer.error(err);
3098
3385
  process.exit(resolveExitCode9(err));
3099
3386
  }
@@ -3114,7 +3401,7 @@ function createWebhooksCommand() {
3114
3401
  spinner.stop();
3115
3402
  printer.log(webhook);
3116
3403
  } catch (err) {
3117
- spinner.stop();
3404
+ spinner.fail();
3118
3405
  printer.error(err);
3119
3406
  process.exit(resolveExitCode9(err));
3120
3407
  }
@@ -3136,7 +3423,7 @@ function createWebhooksCommand() {
3136
3423
  spinner.stop();
3137
3424
  printer.log(webhook);
3138
3425
  } catch (err) {
3139
- spinner.stop();
3426
+ spinner.fail();
3140
3427
  printer.error(err);
3141
3428
  process.exit(resolveExitCode9(err));
3142
3429
  }
@@ -3157,7 +3444,7 @@ function createWebhooksCommand() {
3157
3444
  spinner.stop();
3158
3445
  printer.log(`Webhook "${id}" deleted.`);
3159
3446
  } catch (err) {
3160
- spinner.stop();
3447
+ spinner.fail();
3161
3448
  printer.error(err);
3162
3449
  process.exit(resolveExitCode9(err));
3163
3450
  }
@@ -3169,11 +3456,13 @@ function createWebhooksCommand() {
3169
3456
  try {
3170
3457
  const cred = resolveAuth({ apiKey: globals.apiKey });
3171
3458
  const client = new Autoposting({ apiKey: cred.apiKey });
3172
- await client.webhooks.test(id);
3459
+ const result = await client.webhooks.test(id);
3173
3460
  spinner.stop();
3174
- printer.log(`Test event sent to webhook "${id}".`);
3461
+ printer.log(
3462
+ `Test event sent to webhook "${id}" \u2014 delivered: ${result.delivered ? "yes" : "no"} (HTTP ${result.httpStatus}).`
3463
+ );
3175
3464
  } catch (err) {
3176
- spinner.stop();
3465
+ spinner.fail();
3177
3466
  printer.error(err);
3178
3467
  process.exit(resolveExitCode9(err));
3179
3468
  }
@@ -3198,18 +3487,18 @@ function createWorkspacesCommand() {
3198
3487
  try {
3199
3488
  const cred = resolveAuth({ apiKey: globals.apiKey });
3200
3489
  const client = new Autoposting({ apiKey: cred.apiKey });
3201
- const list = await client.workspaces.list();
3490
+ const res = await client.workspaces.list();
3202
3491
  spinner.stop();
3203
- const rows = list.map((w) => ({
3492
+ const rows = res.organizations.map((w) => ({
3204
3493
  id: w.id,
3205
3494
  name: w.name,
3206
3495
  slug: w.slug,
3207
- active: w.isActive ? "yes" : "no",
3496
+ active: w.id === res.activeOrgId ? "yes" : "no",
3208
3497
  createdAt: w.createdAt
3209
3498
  }));
3210
3499
  printer.table(rows, ["id", "name", "slug", "active", "createdAt"]);
3211
3500
  } catch (err) {
3212
- spinner.stop();
3501
+ spinner.fail();
3213
3502
  printer.error(err);
3214
3503
  process.exit(resolveExitCode10(err));
3215
3504
  }
@@ -3220,13 +3509,12 @@ function createWorkspacesCommand() {
3220
3509
  const spinner = printer.spinner(`Switching to workspace "${id}"\u2026`);
3221
3510
  try {
3222
3511
  const cred = resolveAuth({ apiKey: globals.apiKey });
3223
- const authSource = cred.source === "flag" || cred.source === "env" || cred.source === "stored" ? "api-key" : "session";
3224
- const client = new Autoposting({ apiKey: cred.apiKey, authSource });
3512
+ const client = new Autoposting({ apiKey: cred.apiKey });
3225
3513
  await client.workspaces.switchWorkspace(id);
3226
3514
  spinner.stop();
3227
3515
  printer.log(`Switched to workspace "${id}".`);
3228
3516
  } catch (err) {
3229
- spinner.stop();
3517
+ spinner.fail();
3230
3518
  printer.error(err);
3231
3519
  process.exit(resolveExitCode10(err));
3232
3520
  }
@@ -3238,7 +3526,7 @@ function createWorkspacesCommand() {
3238
3526
  var import_commander13 = require("commander");
3239
3527
  init_dist();
3240
3528
  var import_node_fs2 = __toESM(require("fs"));
3241
- var API_HEALTH_URL = "https://app.autoposting.ai/api/health";
3529
+ var API_HEALTH_URL = "https://app.autoposting.ai/api-proxy/health";
3242
3530
  async function runChecks() {
3243
3531
  const results = [];
3244
3532
  results.push({ name: "CLI Version", status: "pass", value: VERSION });
@@ -3251,7 +3539,7 @@ async function runChecks() {
3251
3539
  });
3252
3540
  try {
3253
3541
  resolveAuth();
3254
- results.push({ name: "Auth", status: "pass", value: "authenticated" });
3542
+ results.push({ name: "Auth", status: "pass", value: "credentials configured" });
3255
3543
  } catch {
3256
3544
  results.push({ name: "Auth", status: "fail", value: "not configured" });
3257
3545
  }
@@ -3309,39 +3597,65 @@ function createDoctorCommand() {
3309
3597
 
3310
3598
  // src/commands/whoami.ts
3311
3599
  var import_commander14 = require("commander");
3600
+ init_dist();
3312
3601
  function createWhoamiCommand() {
3313
- return new import_commander14.Command("whoami").description("Show current authentication identity (alias for `auth whoami`)").action((_opts, cmd) => {
3602
+ return new import_commander14.Command("whoami").description("Show the current identity and validate the key against the server").action(async (_opts, cmd) => {
3603
+ const globals = cmd.optsWithGlobals();
3604
+ let cred;
3314
3605
  try {
3315
- const globals = cmd.optsWithGlobals();
3316
- const cred = resolveAuth({ apiKey: globals.apiKey });
3317
- const masked = `${cred.apiKey.slice(0, 8)}${"*".repeat(Math.max(0, cred.apiKey.length - 8))}`;
3318
- if (globals.json) {
3319
- const out = { source: cred.source, apiKey: masked };
3320
- if (cred.source === "stored") {
3321
- const creds = readCredentials();
3322
- const profileName = creds?.activeProfile ?? "unknown";
3323
- const profile = creds?.profiles[profileName];
3324
- out.profile = profileName;
3325
- if (profile?.workspace) out.workspace = profile.workspace;
3326
- if (profile?.email) out.email = profile.email;
3327
- }
3328
- console.log(JSON.stringify(out, null, 2));
3329
- return;
3606
+ cred = resolveAuth({ apiKey: globals.apiKey });
3607
+ } catch (err) {
3608
+ console.error(`Error: ${err.message}`);
3609
+ process.exitCode = exitCodeFromError(err);
3610
+ return;
3611
+ }
3612
+ const masked = `${cred.apiKey.slice(0, 8)}${"*".repeat(Math.max(0, cred.apiKey.length - 8))}`;
3613
+ let validity = "unverified";
3614
+ let identity;
3615
+ try {
3616
+ const client = new Autoposting({ apiKey: cred.apiKey, maxRetries: 0 });
3617
+ identity = await client.getProfile();
3618
+ validity = "valid";
3619
+ } catch (err) {
3620
+ const code = exitCodeFromError(err);
3621
+ validity = code === EXIT_CODES.AUTH_ERROR || code === EXIT_CODES.SCOPE_ERROR ? "rejected" : "unverified";
3622
+ }
3623
+ const exitCode = validity === "rejected" ? EXIT_CODES.AUTH_ERROR : 0;
3624
+ const creds = cred.source === "stored" ? readCredentials() : void 0;
3625
+ const profileName = creds?.activeProfile ?? "unknown";
3626
+ const email = creds?.profiles[profileName]?.email;
3627
+ if (globals.json) {
3628
+ const out = {
3629
+ source: cred.source,
3630
+ apiKey: masked,
3631
+ valid: validity === "valid",
3632
+ validity
3633
+ };
3634
+ if (identity?.orgId) {
3635
+ out.orgId = identity.orgId;
3636
+ out.authType = identity.authType;
3637
+ if (identity.email) out.email = identity.email;
3330
3638
  }
3331
- console.log(`Source: ${cred.source}`);
3332
3639
  if (cred.source === "stored") {
3333
- const creds = readCredentials();
3334
- const profileName = creds?.activeProfile ?? "unknown";
3335
- const profile = creds?.profiles[profileName];
3336
- console.log(`Profile: ${profileName}`);
3337
- if (profile?.workspace) console.log(`Workspace: ${profile.workspace}`);
3338
- if (profile?.email) console.log(`Email: ${profile.email}`);
3640
+ out.profile = profileName;
3641
+ if (email) out.email = email;
3339
3642
  }
3340
- console.log(`API key: ${masked}`);
3341
- } catch (err) {
3342
- console.error(`Error: ${err.message}`);
3343
- process.exit(2);
3643
+ console.log(JSON.stringify(out, null, 2));
3644
+ process.exitCode = exitCode;
3645
+ return;
3646
+ }
3647
+ console.log(`Source: ${cred.source}`);
3648
+ if (cred.source === "stored") {
3649
+ console.log(`Profile: ${profileName}`);
3650
+ if (email) console.log(`Email: ${email}`);
3651
+ }
3652
+ if (identity?.orgId) {
3653
+ console.log(`Org: ${identity.orgId}`);
3654
+ console.log(`Auth: ${identity.authType}`);
3344
3655
  }
3656
+ const tag = validity === "valid" ? "\u2713 valid" : validity === "rejected" ? "\u2717 rejected by server" : "\u26A0 unverified (could not reach server)";
3657
+ console.log(`API key: ${masked} ${tag}`);
3658
+ process.exitCode = exitCode;
3345
3659
  });
3346
3660
  }
3347
3661