@autoposting.ai/cli 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -46,7 +46,7 @@ var VERSION, AutopostingError, AuthenticationError, ScopeError, NotFoundError, V
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.1";
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,6 +322,10 @@ 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
+ * NOTE: this route is session-only (better-auth session); an API key receives 401.
328
+ */
308
329
  list() {
309
330
  return this.get("/orgs");
310
331
  }
@@ -316,7 +337,7 @@ var init_dist = __esm({
316
337
  )
317
338
  );
318
339
  }
319
- return this.post("/orgs/set-active", { organizationId: id });
340
+ return this.client.request("PUT", "/orgs/active", { organizationId: id });
320
341
  }
321
342
  };
322
343
  Autoposting = class {
@@ -346,7 +367,7 @@ var init_dist = __esm({
346
367
  );
347
368
  }
348
369
  this._apiKey = key;
349
- this.baseUrl = (config.baseUrl ?? "https://app.autoposting.ai").replace(/\/$/, "");
370
+ this.baseUrl = (config.baseUrl || process.env.AUTOPOSTING_BASE_URL || "https://app.autoposting.ai/api-proxy").replace(/\/$/, "");
350
371
  this.timeout = config.timeout ?? 3e4;
351
372
  this.extraHeaders = config.headers ?? {};
352
373
  this.authSource = config.authSource ?? "api-key";
@@ -375,46 +396,101 @@ var init_dist = __esm({
375
396
  }
376
397
  const isFormData = body instanceof FormData;
377
398
  const headers = {
399
+ ...this.extraHeaders,
378
400
  authorization: `Bearer ${this._apiKey}`,
379
401
  "user-agent": `autoposting-sdk/${VERSION}`,
380
- "x-source": "sdk",
381
- ...this.extraHeaders
402
+ "x-source": "sdk"
382
403
  };
383
404
  if (!isFormData) {
384
405
  headers["content-type"] = "application/json";
385
406
  }
386
407
  const controller = new AbortController();
387
408
  const timer = setTimeout(() => controller.abort(), this.timeout);
388
- let response;
389
409
  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 = {};
410
+ let response;
401
411
  try {
402
- errorBody = await response.json();
403
- } catch {
412
+ response = await fetch(url, {
413
+ method,
414
+ headers,
415
+ body: isFormData ? body : body !== void 0 ? JSON.stringify(body) : void 0,
416
+ signal: controller.signal
417
+ });
418
+ } catch (err) {
419
+ if (err instanceof Error && err.name === "AbortError") {
420
+ throw new AutopostingError(
421
+ `Request to ${method} ${path2} timed out after ${this.timeout}ms.`,
422
+ 0,
423
+ "TIMEOUT"
424
+ );
425
+ }
426
+ throw new AutopostingError(
427
+ `Network request to ${method} ${path2} failed: ${err instanceof Error ? err.message : String(err)}`,
428
+ 0,
429
+ "NETWORK_ERROR"
430
+ );
404
431
  }
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);
432
+ if (!response.ok) {
433
+ const raw = await response.text();
434
+ let errorBody = {};
435
+ if (raw) {
436
+ try {
437
+ errorBody = JSON.parse(raw);
438
+ } catch {
439
+ }
440
+ }
441
+ if (!errorBody.error) {
442
+ const snippet = raw.replace(/\s+/g, " ").trim().slice(0, 120);
443
+ errorBody.error = snippet ? `HTTP ${response.status} ${response.statusText}: ${snippet}` : `HTTP ${response.status} ${response.statusText}`;
444
+ }
445
+ const err = createError(response.status, errorBody);
446
+ if (err instanceof RateLimitError) {
447
+ const retryAfter = response.headers.get("Retry-After");
448
+ if (retryAfter) {
449
+ const seconds = Number(retryAfter);
450
+ if (Number.isFinite(seconds)) {
451
+ err.retryAfter = Math.max(0, Math.round(seconds));
452
+ } else {
453
+ const dateMs = Date.parse(retryAfter);
454
+ if (!Number.isNaN(dateMs)) {
455
+ err.retryAfter = Math.max(0, Math.round((dateMs - Date.now()) / 1e3));
456
+ }
457
+ }
458
+ }
410
459
  }
460
+ throw err;
461
+ }
462
+ if (response.status === 204) {
463
+ return {};
464
+ }
465
+ const text = await response.text();
466
+ if (!text) return {};
467
+ let parsed;
468
+ try {
469
+ parsed = JSON.parse(text);
470
+ } catch {
471
+ throw new AutopostingError(
472
+ `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.`,
473
+ response.status,
474
+ "INVALID_RESPONSE"
475
+ );
476
+ }
477
+ if (typeof parsed === "object" && parsed !== null && parsed.success === true) {
478
+ const env = parsed;
479
+ return env.data !== void 0 ? env.data : {};
480
+ }
481
+ return parsed;
482
+ } catch (err) {
483
+ if (err instanceof Error && err.name === "AbortError") {
484
+ throw new AutopostingError(
485
+ `Request to ${method} ${path2} timed out after ${this.timeout}ms.`,
486
+ 0,
487
+ "TIMEOUT"
488
+ );
411
489
  }
412
490
  throw err;
491
+ } finally {
492
+ clearTimeout(timer);
413
493
  }
414
- if (response.status === 204) {
415
- return {};
416
- }
417
- return response.json();
418
494
  }
419
495
  };
420
496
  }
@@ -862,13 +938,26 @@ var init_tools = __esm({
862
938
  },
863
939
  {
864
940
  name: "enrich-idea",
865
- description: "Enrich an idea with additional AI-generated context.",
941
+ description: "Enrich an idea (title/hook/angle) into platform-ready drafts across 1..5 platforms. Async \u2014 returns a job ID.",
866
942
  inputSchema: {
867
943
  type: "object",
868
944
  properties: {
869
- id: { type: "string", description: "Idea ID to enrich" }
945
+ title: { type: "string", description: "Idea title" },
946
+ hook: { type: "string", description: "Idea hook" },
947
+ angle: { type: "string", description: "Idea angle" },
948
+ platforms: {
949
+ type: "array",
950
+ description: "Target platforms (1..5)",
951
+ items: {
952
+ type: "string",
953
+ enum: ["twitter", "linkedin", "instagram", "youtube", "threads"]
954
+ },
955
+ minItems: 1,
956
+ maxItems: 5
957
+ },
958
+ kbId: { type: "string", description: "Optional knowledge base ID for context" }
870
959
  },
871
- required: ["id"],
960
+ required: ["title", "hook", "angle", "platforms"],
872
961
  additionalProperties: false
873
962
  }
874
963
  },
@@ -1345,7 +1434,17 @@ async function dispatchToolCall(name, args, client) {
1345
1434
  return ok(result);
1346
1435
  }
1347
1436
  case "enrich-idea": {
1348
- const result = await client.ideas.enrich(args.id);
1437
+ const result = await client.ideas.enrich({
1438
+ idea: {
1439
+ title: args.title,
1440
+ hook: args.hook,
1441
+ angle: args.angle
1442
+ },
1443
+ platforms: args.platforms.map((platform) => ({
1444
+ platform
1445
+ })),
1446
+ ...args.kbId ? { kbId: args.kbId } : {}
1447
+ });
1349
1448
  return ok(result);
1350
1449
  }
1351
1450
  case "delete-idea": {
@@ -1523,7 +1622,8 @@ var import_node_fs = __toESM(require("fs"));
1523
1622
  var import_node_os = __toESM(require("os"));
1524
1623
  var import_node_path = __toESM(require("path"));
1525
1624
  function getCredentialsPath() {
1526
- const configBase = process.env.XDG_CONFIG_HOME ?? import_node_path.default.join(import_node_os.default.homedir(), ".config");
1625
+ const xdg = process.env.XDG_CONFIG_HOME;
1626
+ const configBase = xdg && import_node_path.default.isAbsolute(xdg) ? xdg : import_node_path.default.join(import_node_os.default.homedir(), ".config");
1527
1627
  return import_node_path.default.join(configBase, "autoposting", "credentials.json");
1528
1628
  }
1529
1629
  function readCredentials() {
@@ -1539,11 +1639,27 @@ function writeCredentials(creds) {
1539
1639
  const filePath = getCredentialsPath();
1540
1640
  const dir = import_node_path.default.dirname(filePath);
1541
1641
  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 });
1642
+ const tmpPath = `${filePath}.${process.pid}.tmp`;
1643
+ try {
1644
+ import_node_fs.default.writeFileSync(tmpPath, JSON.stringify(creds, null, 2), { encoding: "utf8", mode: 384 });
1645
+ import_node_fs.default.renameSync(tmpPath, filePath);
1646
+ } catch (err) {
1647
+ try {
1648
+ import_node_fs.default.unlinkSync(tmpPath);
1649
+ } catch {
1650
+ }
1651
+ throw err;
1652
+ }
1543
1653
  import_node_fs.default.chmodSync(filePath, 384);
1544
1654
  }
1545
1655
  function saveProfile(name, profile) {
1546
1656
  const existing = readCredentials();
1657
+ if (!existing && import_node_fs.default.existsSync(getCredentialsPath())) {
1658
+ try {
1659
+ import_node_fs.default.renameSync(getCredentialsPath(), `${getCredentialsPath()}.corrupt`);
1660
+ } catch {
1661
+ }
1662
+ }
1547
1663
  const creds = existing ?? { activeProfile: name, profiles: {} };
1548
1664
  creds.profiles[name] = profile;
1549
1665
  creds.activeProfile = name;
@@ -1608,7 +1724,9 @@ function red(text) {
1608
1724
  }
1609
1725
  function detectOutputMode(options) {
1610
1726
  if (options.quiet) return "quiet";
1611
- if (options.json || !process.stdout.isTTY) return "json";
1727
+ if (options.format === "json" || options.json) return "json";
1728
+ if (options.format === "table") return "tty";
1729
+ if (!process.stdout.isTTY) return "json";
1612
1730
  return "tty";
1613
1731
  }
1614
1732
  function formatOutput(data, mode) {
@@ -1741,12 +1859,18 @@ function matchesClass(error, className) {
1741
1859
  return false;
1742
1860
  }
1743
1861
  function exitCodeFromError(error) {
1862
+ const attached = error?.exitCode;
1863
+ if (typeof attached === "number" && Number.isInteger(attached) && attached >= 0) {
1864
+ return attached;
1865
+ }
1744
1866
  if (matchesClass(error, "AuthenticationError")) return EXIT_CODES.AUTH_ERROR;
1745
1867
  if (matchesClass(error, "ScopeError")) return EXIT_CODES.SCOPE_ERROR;
1746
1868
  if (matchesClass(error, "NotFoundError")) return EXIT_CODES.NOT_FOUND;
1747
1869
  if (matchesClass(error, "RateLimitError")) return EXIT_CODES.RATE_LIMITED;
1748
1870
  if (matchesClass(error, "ValidationError")) return EXIT_CODES.VALIDATION_ERROR;
1749
1871
  if (matchesClass(error, "NetworkError")) return EXIT_CODES.NETWORK_ERROR;
1872
+ const code = error?.code;
1873
+ if (code === "NETWORK_ERROR" || code === "TIMEOUT") return EXIT_CODES.NETWORK_ERROR;
1750
1874
  return EXIT_CODES.GENERAL_ERROR;
1751
1875
  }
1752
1876
 
@@ -1767,7 +1891,7 @@ function createAgentsCommand() {
1767
1891
  const client = new Autoposting({ apiKey: cred.apiKey });
1768
1892
  const list = await client.agents.list();
1769
1893
  spinner.stop();
1770
- const rows = list.map((a) => ({
1894
+ const rows = list.items.map((a) => ({
1771
1895
  id: a.id,
1772
1896
  name: a.name,
1773
1897
  type: a.type,
@@ -1876,9 +2000,9 @@ function createAgentsCommand() {
1876
2000
  try {
1877
2001
  const cred = resolveAuth({ apiKey: globals.apiKey });
1878
2002
  const client = new Autoposting({ apiKey: cred.apiKey });
1879
- const agentRun = await client.agents.run(id);
2003
+ const queued = await client.agents.run(id);
1880
2004
  spinner.stop();
1881
- printer.log(agentRun);
2005
+ printer.log({ runId: queued.runId, status: queued.status });
1882
2006
  } catch (err) {
1883
2007
  spinner.stop();
1884
2008
  printer.error(err);
@@ -1910,7 +2034,7 @@ function createAgentsCommand() {
1910
2034
  const client = new Autoposting({ apiKey: cred.apiKey });
1911
2035
  const list = await client.agents.runs(id);
1912
2036
  spinner.stop();
1913
- const rows = list.map((r) => ({
2037
+ const rows = list.items.map((r) => ({
1914
2038
  id: r.id,
1915
2039
  status: r.status,
1916
2040
  output: r.output ? r.output.length > 60 ? `${r.output.slice(0, 57)}\u2026` : r.output : "\u2014",
@@ -1931,8 +2055,32 @@ var import_commander2 = require("commander");
1931
2055
  var import_node_child_process = require("child_process");
1932
2056
 
1933
2057
  // src/auth/device-code-client.ts
2058
+ async function fetchWithTimeout(url, init = {}, timeoutMs = 3e4) {
2059
+ const controller = new AbortController();
2060
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
2061
+ try {
2062
+ return await fetch(url, { ...init, signal: controller.signal });
2063
+ } catch (err) {
2064
+ if (err instanceof Error && err.name === "AbortError") {
2065
+ throw new Error(`Request to ${url} timed out after ${timeoutMs}ms`);
2066
+ }
2067
+ throw err;
2068
+ } finally {
2069
+ clearTimeout(timer);
2070
+ }
2071
+ }
2072
+ async function parseJson(res) {
2073
+ const text = await res.text();
2074
+ try {
2075
+ return JSON.parse(text);
2076
+ } catch {
2077
+ throw new Error(
2078
+ `Expected a JSON response (HTTP ${res.status}) but received non-JSON. Verify the API base URL points at the API, not the web app.`
2079
+ );
2080
+ }
2081
+ }
1934
2082
  async function requestDeviceCode(baseUrl) {
1935
- const res = await fetch(`${baseUrl}/auth/cli/device-code`, {
2083
+ const res = await fetchWithTimeout(`${baseUrl}/auth/cli/device-code`, {
1936
2084
  method: "POST",
1937
2085
  headers: { "Content-Type": "application/json" }
1938
2086
  });
@@ -1940,22 +2088,20 @@ async function requestDeviceCode(baseUrl) {
1940
2088
  const text = await res.text().catch(() => res.statusText);
1941
2089
  throw new Error(`Device code request failed (${res.status}): ${text}`);
1942
2090
  }
1943
- const data = await res.json();
1944
- return data;
2091
+ return parseJson(res);
1945
2092
  }
1946
2093
  async function pollDeviceCode(baseUrl, deviceCode) {
1947
2094
  const url = `${baseUrl}/auth/cli/poll?device_code=${encodeURIComponent(deviceCode)}`;
1948
- const res = await fetch(url);
2095
+ const res = await fetchWithTimeout(url);
1949
2096
  if (!res.ok) {
1950
2097
  const text = await res.text().catch(() => res.statusText);
1951
2098
  throw new Error(`Poll request failed (${res.status}): ${text}`);
1952
2099
  }
1953
- const data = await res.json();
1954
- return data;
2100
+ return parseJson(res);
1955
2101
  }
1956
2102
 
1957
2103
  // src/commands/auth.ts
1958
- var DEFAULT_BASE_URL = "https://app.autoposting.ai";
2104
+ var DEFAULT_BASE_URL = "https://app.autoposting.ai/api-proxy";
1959
2105
  function resolveBaseUrl(globals) {
1960
2106
  return globals.baseUrl ?? process.env.AUTOPOSTING_BASE_URL ?? DEFAULT_BASE_URL;
1961
2107
  }
@@ -2129,14 +2275,16 @@ function createBillingCommand() {
2129
2275
  printer.table(
2130
2276
  [
2131
2277
  {
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"
2278
+ plan: status.planName,
2279
+ status: status.status ?? "\u2014",
2280
+ cycle: status.billingCycle ?? "\u2014",
2281
+ accounts: `${status.accountsUsed}/${status.accountLimit === -1 ? "\u221E" : status.accountLimit}`,
2282
+ credits: status.creditBalance,
2283
+ renews: status.currentPeriodEnd ?? "\u2014",
2284
+ "trial ends": status.trialEnd ?? "\u2014"
2137
2285
  }
2138
2286
  ],
2139
- ["plan", "status", "trial ends", "renewal date", "cancels at"]
2287
+ ["plan", "status", "cycle", "accounts", "credits", "renews", "trial ends"]
2140
2288
  );
2141
2289
  } catch (err) {
2142
2290
  spinner.stop();
@@ -2156,19 +2304,20 @@ function createBillingCommand() {
2156
2304
  printer.table(
2157
2305
  [
2158
2306
  {
2159
- total: credits.total,
2160
- used: credits.used,
2161
- remaining: credits.remaining
2307
+ balance: credits.balanceFormatted,
2308
+ "spent (30d)": credits.totalSpent30d
2162
2309
  }
2163
2310
  ],
2164
- ["total", "used", "remaining"]
2311
+ ["balance", "spent (30d)"]
2165
2312
  );
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
2313
+ if (credits.recentUsage.length > 0) {
2314
+ const usageRows = credits.recentUsage.map((e) => ({
2315
+ date: e.date,
2316
+ description: e.description,
2317
+ amount: e.amount,
2318
+ type: e.type
2170
2319
  }));
2171
- printer.table(breakdownRows, ["category", "credits"]);
2320
+ printer.table(usageRows, ["date", "description", "amount", "type"]);
2172
2321
  }
2173
2322
  } catch (err) {
2174
2323
  spinner.stop();
@@ -2324,7 +2473,7 @@ function createCarouselsCommand() {
2324
2473
  const client = new Autoposting({ apiKey: cred.apiKey });
2325
2474
  const list = await client.carousels.list();
2326
2475
  spinner.stop();
2327
- const rows = list.map((c) => ({
2476
+ const rows = list.items.map((c) => ({
2328
2477
  id: c.id,
2329
2478
  title: c.title ?? "\u2014",
2330
2479
  slides: c.slides.length,
@@ -2450,7 +2599,7 @@ function createClipsCommand() {
2450
2599
  const client = new Autoposting({ apiKey: cred.apiKey });
2451
2600
  const list = await client.clips.list();
2452
2601
  spinner.stop();
2453
- const rows = list.map((c) => ({
2602
+ const rows = list.clips.map((c) => ({
2454
2603
  id: c.id,
2455
2604
  name: c.name,
2456
2605
  status: c.status,
@@ -2507,12 +2656,12 @@ function createClipsCommand() {
2507
2656
  try {
2508
2657
  const cred = resolveAuth({ apiKey: globals.apiKey });
2509
2658
  const client = new Autoposting({ apiKey: cred.apiKey });
2510
- const clip = await client.clips.importUrl({
2659
+ const { clipId } = await client.clips.importUrl({
2511
2660
  url: opts.url,
2512
2661
  ...opts.name ? { name: opts.name } : {}
2513
2662
  });
2514
2663
  spinner.stop();
2515
- printer.log(clip);
2664
+ printer.log({ clipId });
2516
2665
  } catch (err) {
2517
2666
  spinner.stop();
2518
2667
  printer.error(err);
@@ -2526,9 +2675,9 @@ function createClipsCommand() {
2526
2675
  try {
2527
2676
  const cred = resolveAuth({ apiKey: globals.apiKey });
2528
2677
  const client = new Autoposting({ apiKey: cred.apiKey });
2529
- const clip = await client.clips.render(id);
2678
+ const { jobIds, activeJobIds, reusedJobIds } = await client.clips.render(id);
2530
2679
  spinner.stop();
2531
- printer.log(clip);
2680
+ printer.log({ jobIds, activeJobIds, reusedJobIds });
2532
2681
  } catch (err) {
2533
2682
  spinner.stop();
2534
2683
  printer.error(err);
@@ -2561,8 +2710,34 @@ function createClipsCommand() {
2561
2710
  // src/commands/posts.ts
2562
2711
  var import_commander7 = require("commander");
2563
2712
  init_dist();
2713
+ var VALID_PLATFORMS = ["x", "linkedin", "instagram", "threads", "youtube"];
2564
2714
  function parsePlatforms(raw) {
2565
- return raw.split(",").map((p) => p.trim());
2715
+ const parts = raw.split(",").map((p) => p.trim()).filter(Boolean);
2716
+ if (parts.length === 0) {
2717
+ throw new Error("No platforms provided. Pass a comma-separated list, e.g. --platforms x,linkedin");
2718
+ }
2719
+ const invalid = parts.filter((p) => !VALID_PLATFORMS.includes(p));
2720
+ if (invalid.length > 0) {
2721
+ throw new Error(
2722
+ `Unsupported platform(s): ${invalid.join(", ")}. Valid platforms: ${VALID_PLATFORMS.join(", ")}`
2723
+ );
2724
+ }
2725
+ return parts;
2726
+ }
2727
+ function parsePositiveInt(value, flag) {
2728
+ const n = Number(value);
2729
+ if (!Number.isInteger(n) || n < 1) {
2730
+ throw new Error(`${flag} must be a positive integer (received "${value}").`);
2731
+ }
2732
+ return n;
2733
+ }
2734
+ function validateScheduledAt(value) {
2735
+ if (Number.isNaN(Date.parse(value)) || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(value)) {
2736
+ throw new Error(
2737
+ `--at must be a valid ISO 8601 datetime, e.g. 2026-06-30T14:00:00Z (received "${value}").`
2738
+ );
2739
+ }
2740
+ return value;
2566
2741
  }
2567
2742
  function resolveExitCode5(err) {
2568
2743
  const attached = err.exitCode;
@@ -2581,11 +2756,11 @@ function createPostsCommand() {
2581
2756
  const result = await client.posts.list({
2582
2757
  brandSlug: opts.brand,
2583
2758
  status: opts.status,
2584
- limit: parseInt(opts.limit, 10),
2585
- page: parseInt(opts.page, 10)
2759
+ limit: parsePositiveInt(opts.limit, "--limit"),
2760
+ page: parsePositiveInt(opts.page, "--page")
2586
2761
  });
2587
2762
  spinner.stop();
2588
- const rows = result.data.map((p) => ({
2763
+ const rows = result.map((p) => ({
2589
2764
  id: p.id,
2590
2765
  brand: p.brandSlug,
2591
2766
  status: p.status,
@@ -2616,7 +2791,10 @@ function createPostsCommand() {
2616
2791
  process.exit(resolveExitCode5(err));
2617
2792
  }
2618
2793
  });
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(
2794
+ 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(
2795
+ "--thread <text...>",
2796
+ "Additional posts appended after --text to form a thread (X/Threads only, max 25)"
2797
+ ).action(
2620
2798
  async (opts, cmd) => {
2621
2799
  const globals = cmd.optsWithGlobals();
2622
2800
  const printer = createPrinter(globals);
@@ -2628,7 +2806,8 @@ function createPostsCommand() {
2628
2806
  brandSlug: opts.brand,
2629
2807
  text: opts.text,
2630
2808
  platforms: parsePlatforms(opts.platforms),
2631
- ...opts.at ? { scheduledAt: opts.at } : {}
2809
+ ...opts.at ? { scheduledAt: validateScheduledAt(opts.at) } : {},
2810
+ ...opts.thread && opts.thread.length > 0 ? { thread: opts.thread } : {}
2632
2811
  });
2633
2812
  spinner.stop();
2634
2813
  printer.log(post);
@@ -2650,7 +2829,7 @@ function createPostsCommand() {
2650
2829
  const post = await client.posts.update(id, {
2651
2830
  ...opts.text ? { text: opts.text } : {},
2652
2831
  ...opts.platforms ? { platforms: parsePlatforms(opts.platforms) } : {},
2653
- ...opts.at ? { scheduledAt: opts.at } : {}
2832
+ ...opts.at ? { scheduledAt: validateScheduledAt(opts.at) } : {}
2654
2833
  });
2655
2834
  spinner.stop();
2656
2835
  printer.log(post);
@@ -2704,7 +2883,7 @@ function createPostsCommand() {
2704
2883
  try {
2705
2884
  const cred = resolveAuth({ apiKey: globals.apiKey });
2706
2885
  const client = new Autoposting({ apiKey: cred.apiKey });
2707
- const post = await client.posts.schedule(id, opts.at);
2886
+ const post = await client.posts.schedule(id, validateScheduledAt(opts.at));
2708
2887
  spinner.stop();
2709
2888
  printer.log(post);
2710
2889
  } catch (err) {
@@ -2855,14 +3034,15 @@ function createKbCommand() {
2855
3034
  try {
2856
3035
  const cred = resolveAuth({ apiKey: globals.apiKey });
2857
3036
  const client = new Autoposting({ apiKey: cred.apiKey });
2858
- const results = await client.kb.search(id, opts.query);
3037
+ const res = await client.kb.search(id, opts.query);
2859
3038
  spinner.stop();
2860
- const rows = results.map((r) => ({
2861
- docId: r.docId,
2862
- score: r.score.toFixed(3),
3039
+ const rows = res.results.map((r) => ({
3040
+ kind: r.kind,
3041
+ score: r.score != null ? r.score.toFixed(3) : "\u2014",
3042
+ uri: r.uri ?? "\u2014",
2863
3043
  content: r.content.length > 80 ? `${r.content.slice(0, 77)}\u2026` : r.content
2864
3044
  }));
2865
- printer.table(rows, ["docId", "score", "content"]);
3045
+ printer.table(rows, ["kind", "score", "uri", "content"]);
2866
3046
  } catch (err) {
2867
3047
  spinner.stop();
2868
3048
  printer.error(err);
@@ -2919,6 +3099,23 @@ function resolveExitCode7(err) {
2919
3099
  if (typeof attached === "number") return attached;
2920
3100
  return exitCodeFromError(err);
2921
3101
  }
3102
+ var VALID_IDEA_PLATFORMS = ["twitter", "linkedin", "instagram", "youtube", "threads"];
3103
+ function parseIdeaPlatforms(raw) {
3104
+ const parts = raw.split(",").map((p) => p.trim()).filter(Boolean);
3105
+ if (parts.length === 0) {
3106
+ throw new Error("No platforms provided. e.g. --platforms twitter,linkedin");
3107
+ }
3108
+ if (parts.length > 5) {
3109
+ throw new Error("At most 5 platforms allowed per enrichment request.");
3110
+ }
3111
+ const invalid = parts.filter((p) => !VALID_IDEA_PLATFORMS.includes(p));
3112
+ if (invalid.length > 0) {
3113
+ throw new Error(
3114
+ `Unsupported platform(s): ${invalid.join(", ")}. Valid: ${VALID_IDEA_PLATFORMS.join(", ")}`
3115
+ );
3116
+ }
3117
+ return parts.map((platform) => ({ platform }));
3118
+ }
2922
3119
  function createIdeasCommand() {
2923
3120
  const ideas = new import_commander9.Command("ideas").description("Manage content ideas");
2924
3121
  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,13 +3131,13 @@ function createIdeasCommand() {
2934
3131
  count: parseInt(opts.count, 10)
2935
3132
  });
2936
3133
  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
3134
+ const rows = (result.ideas ?? []).map((i) => ({
3135
+ id: i.id ?? "\u2014",
3136
+ platform: i.targetPlatform,
3137
+ score: i.viralityScore,
3138
+ title: i.title.length > 80 ? `${i.title.slice(0, 77)}\u2026` : i.title
2942
3139
  }));
2943
- printer.table(rows, ["id", "topic", "enriched", "text"]);
3140
+ printer.table(rows, ["id", "platform", "score", "title"]);
2944
3141
  } catch (err) {
2945
3142
  spinner.stop();
2946
3143
  printer.error(err);
@@ -2956,36 +3153,43 @@ function createIdeasCommand() {
2956
3153
  const client = new Autoposting({ apiKey: cred.apiKey });
2957
3154
  const list = await client.ideas.list();
2958
3155
  spinner.stop();
2959
- const rows = list.map((i) => ({
3156
+ const rows = list.items.map((i) => ({
2960
3157
  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
3158
+ topic: i.topic || "\u2014",
3159
+ platform: i.targetPlatform,
3160
+ score: i.viralityScore,
3161
+ status: i.status,
3162
+ title: i.title.length > 60 ? `${i.title.slice(0, 57)}\u2026` : i.title
2965
3163
  }));
2966
- printer.table(rows, ["id", "topic", "enriched", "text", "createdAt"]);
3164
+ printer.table(rows, ["id", "topic", "platform", "score", "status", "title"]);
2967
3165
  } catch (err) {
2968
3166
  spinner.stop();
2969
3167
  printer.error(err);
2970
3168
  process.exit(resolveExitCode7(err));
2971
3169
  }
2972
3170
  });
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));
3171
+ 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(
3172
+ async (opts, cmd) => {
3173
+ const globals = cmd.optsWithGlobals();
3174
+ const printer = createPrinter(globals);
3175
+ const spinner = printer.spinner("Queuing enrichment\u2026");
3176
+ try {
3177
+ const cred = resolveAuth({ apiKey: globals.apiKey });
3178
+ const client = new Autoposting({ apiKey: cred.apiKey });
3179
+ const { jobId } = await client.ideas.enrich({
3180
+ idea: { title: opts.title, hook: opts.hook, angle: opts.angle },
3181
+ platforms: parseIdeaPlatforms(opts.platforms),
3182
+ ...opts.kb ? { kbId: opts.kb } : {}
3183
+ });
3184
+ spinner.stop();
3185
+ printer.log(`Enrichment queued. Job ID: ${jobId}`);
3186
+ } catch (err) {
3187
+ spinner.stop();
3188
+ printer.error(err);
3189
+ process.exit(resolveExitCode7(err));
3190
+ }
2987
3191
  }
2988
- });
3192
+ );
2989
3193
  ideas.command("delete <id>").description("Delete an idea (requires --force)").option("--force", "Confirm deletion without interactive prompt").action(async (id, opts, cmd) => {
2990
3194
  const globals = cmd.optsWithGlobals();
2991
3195
  const printer = createPrinter(globals);
@@ -3028,18 +3232,25 @@ function createUsageCommand() {
3028
3232
  const client = new Autoposting({ apiKey: cred.apiKey });
3029
3233
  const summary = await client.usage.summary();
3030
3234
  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
3235
+ printer.log(`Period: ${summary.range.from.slice(0, 10)} \u2192 ${summary.range.to.slice(0, 10)}`);
3236
+ printer.table(
3237
+ [
3238
+ {
3239
+ posts: summary.posts.total,
3240
+ published: summary.posts.published,
3241
+ agents: summary.agents.total,
3242
+ "active agents": summary.agents.active,
3243
+ "ai cost (usd)": summary.ai.totalCostUsd,
3244
+ "ai requests": summary.ai.requests
3245
+ }
3246
+ ],
3247
+ ["posts", "published", "agents", "active agents", "ai cost (usd)", "ai requests"]
3248
+ );
3249
+ const sourceRows = Object.entries(summary.posts.bySource).map(([source, count]) => ({
3250
+ source,
3251
+ posts: count
3036
3252
  }));
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
- }
3253
+ printer.table(sourceRows, ["source", "posts"]);
3043
3254
  } catch (err) {
3044
3255
  spinner.stop();
3045
3256
  printer.error(err);
@@ -3169,9 +3380,11 @@ function createWebhooksCommand() {
3169
3380
  try {
3170
3381
  const cred = resolveAuth({ apiKey: globals.apiKey });
3171
3382
  const client = new Autoposting({ apiKey: cred.apiKey });
3172
- await client.webhooks.test(id);
3383
+ const result = await client.webhooks.test(id);
3173
3384
  spinner.stop();
3174
- printer.log(`Test event sent to webhook "${id}".`);
3385
+ printer.log(
3386
+ `Test event sent to webhook "${id}" \u2014 delivered: ${result.delivered ? "yes" : "no"} (HTTP ${result.httpStatus}).`
3387
+ );
3175
3388
  } catch (err) {
3176
3389
  spinner.stop();
3177
3390
  printer.error(err);
@@ -3198,13 +3411,13 @@ function createWorkspacesCommand() {
3198
3411
  try {
3199
3412
  const cred = resolveAuth({ apiKey: globals.apiKey });
3200
3413
  const client = new Autoposting({ apiKey: cred.apiKey });
3201
- const list = await client.workspaces.list();
3414
+ const res = await client.workspaces.list();
3202
3415
  spinner.stop();
3203
- const rows = list.map((w) => ({
3416
+ const rows = res.organizations.map((w) => ({
3204
3417
  id: w.id,
3205
3418
  name: w.name,
3206
3419
  slug: w.slug,
3207
- active: w.isActive ? "yes" : "no",
3420
+ active: w.id === res.activeOrgId ? "yes" : "no",
3208
3421
  createdAt: w.createdAt
3209
3422
  }));
3210
3423
  printer.table(rows, ["id", "name", "slug", "active", "createdAt"]);
@@ -3238,7 +3451,7 @@ function createWorkspacesCommand() {
3238
3451
  var import_commander13 = require("commander");
3239
3452
  init_dist();
3240
3453
  var import_node_fs2 = __toESM(require("fs"));
3241
- var API_HEALTH_URL = "https://app.autoposting.ai/api/health";
3454
+ var API_HEALTH_URL = "https://app.autoposting.ai/api-proxy/health";
3242
3455
  async function runChecks() {
3243
3456
  const results = [];
3244
3457
  results.push({ name: "CLI Version", status: "pass", value: VERSION });
@@ -3251,7 +3464,7 @@ async function runChecks() {
3251
3464
  });
3252
3465
  try {
3253
3466
  resolveAuth();
3254
- results.push({ name: "Auth", status: "pass", value: "authenticated" });
3467
+ results.push({ name: "Auth", status: "pass", value: "credentials configured" });
3255
3468
  } catch {
3256
3469
  results.push({ name: "Auth", status: "fail", value: "not configured" });
3257
3470
  }
@@ -3309,39 +3522,55 @@ function createDoctorCommand() {
3309
3522
 
3310
3523
  // src/commands/whoami.ts
3311
3524
  var import_commander14 = require("commander");
3525
+ init_dist();
3312
3526
  function createWhoamiCommand() {
3313
- return new import_commander14.Command("whoami").description("Show current authentication identity (alias for `auth whoami`)").action((_opts, cmd) => {
3527
+ return new import_commander14.Command("whoami").description("Show the current identity and validate the key against the server").action(async (_opts, cmd) => {
3528
+ const globals = cmd.optsWithGlobals();
3529
+ let cred;
3314
3530
  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;
3330
- }
3331
- console.log(`Source: ${cred.source}`);
3332
- 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}`);
3339
- }
3340
- console.log(`API key: ${masked}`);
3531
+ cred = resolveAuth({ apiKey: globals.apiKey });
3341
3532
  } catch (err) {
3342
3533
  console.error(`Error: ${err.message}`);
3343
- process.exit(2);
3534
+ process.exitCode = exitCodeFromError(err);
3535
+ return;
3536
+ }
3537
+ const masked = `${cred.apiKey.slice(0, 8)}${"*".repeat(Math.max(0, cred.apiKey.length - 8))}`;
3538
+ let validity = "unverified";
3539
+ try {
3540
+ const client = new Autoposting({ apiKey: cred.apiKey });
3541
+ await client.usage.summary();
3542
+ validity = "valid";
3543
+ } catch (err) {
3544
+ const code = exitCodeFromError(err);
3545
+ validity = code === EXIT_CODES.AUTH_ERROR || code === EXIT_CODES.SCOPE_ERROR ? "rejected" : "unverified";
3546
+ }
3547
+ const exitCode = validity === "rejected" ? EXIT_CODES.AUTH_ERROR : 0;
3548
+ const creds = cred.source === "stored" ? readCredentials() : void 0;
3549
+ const profileName = creds?.activeProfile ?? "unknown";
3550
+ const email = creds?.profiles[profileName]?.email;
3551
+ if (globals.json) {
3552
+ const out = {
3553
+ source: cred.source,
3554
+ apiKey: masked,
3555
+ valid: validity === "valid",
3556
+ validity
3557
+ };
3558
+ if (cred.source === "stored") {
3559
+ out.profile = profileName;
3560
+ if (email) out.email = email;
3561
+ }
3562
+ console.log(JSON.stringify(out, null, 2));
3563
+ process.exitCode = exitCode;
3564
+ return;
3565
+ }
3566
+ console.log(`Source: ${cred.source}`);
3567
+ if (cred.source === "stored") {
3568
+ console.log(`Profile: ${profileName}`);
3569
+ if (email) console.log(`Email: ${email}`);
3344
3570
  }
3571
+ const tag = validity === "valid" ? "\u2713 valid" : validity === "rejected" ? "\u2717 rejected by server" : "\u26A0 unverified (could not reach server)";
3572
+ console.log(`API key: ${masked} ${tag}`);
3573
+ process.exitCode = exitCode;
3345
3574
  });
3346
3575
  }
3347
3576