@ait-co/console-cli 0.1.10 → 0.1.11

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.mjs CHANGED
@@ -161,10 +161,10 @@ async function executeAndUnwrap(url, init, fetchImpl) {
161
161
  }
162
162
  //#endregion
163
163
  //#region src/api/mini-apps.ts
164
- const BASE$2 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
164
+ const BASE$3 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
165
165
  async function fetchMiniApps(workspaceId, cookies, opts = {}) {
166
166
  const raw = await requestConsoleApi({
167
- url: `${BASE$2}/workspaces/${workspaceId}/mini-app`,
167
+ url: `${BASE$3}/workspaces/${workspaceId}/mini-app`,
168
168
  cookies,
169
169
  ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
170
170
  });
@@ -187,7 +187,7 @@ function normalizeMiniApp(item, workspaceId, index) {
187
187
  }
188
188
  async function fetchReviewStatus(workspaceId, cookies, opts = {}) {
189
189
  const raw = await requestConsoleApi({
190
- url: `${BASE$2}/workspaces/${workspaceId}/mini-apps/review-status`,
190
+ url: `${BASE$3}/workspaces/${workspaceId}/mini-apps/review-status`,
191
191
  cookies,
192
192
  ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
193
193
  });
@@ -206,7 +206,7 @@ async function fetchReviewStatus(workspaceId, cookies, opts = {}) {
206
206
  }
207
207
  async function fetchMiniAppWithDraft(workspaceId, miniAppId, cookies, opts = {}) {
208
208
  const raw = await requestConsoleApi({
209
- url: `${BASE$2}/workspaces/${workspaceId}/mini-app/${miniAppId}/with-draft`,
209
+ url: `${BASE$3}/workspaces/${workspaceId}/mini-app/${miniAppId}/with-draft`,
210
210
  cookies,
211
211
  ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
212
212
  });
@@ -222,9 +222,148 @@ async function fetchMiniAppWithDraft(workspaceId, miniAppId, cookies, opts = {})
222
222
  function isRecordOrNull(v) {
223
223
  return v === null || typeof v === "object" && !Array.isArray(v);
224
224
  }
225
+ async function fetchMiniAppRatings(params, cookies, opts = {}) {
226
+ const page = params.page ?? 0;
227
+ const size = params.size ?? 20;
228
+ const sortField = params.sortField ?? "CREATED_AT";
229
+ const sortDirection = params.sortDirection ?? "DESC";
230
+ const raw = await requestConsoleApi({
231
+ url: `${BASE$3}/workspaces/${params.workspaceId}/mini-app/${params.miniAppId}/app-ratings?page=${page}&size=${size}&sortField=${sortField}&sortDirection=${sortDirection}`,
232
+ cookies,
233
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
234
+ });
235
+ if (raw === null || typeof raw !== "object") throw new Error(`Unexpected ratings shape for app=${params.miniAppId}`);
236
+ const rec = raw;
237
+ const ratingsRaw = rec.ratings;
238
+ if (!Array.isArray(ratingsRaw)) throw new Error(`Unexpected ratings shape: ratings is not an array (app=${params.miniAppId})`);
239
+ const ratings = ratingsRaw.map((r) => {
240
+ if (r === null || typeof r !== "object") return {};
241
+ return r;
242
+ });
243
+ const pagingRaw = rec.paging;
244
+ if (pagingRaw === null || typeof pagingRaw !== "object") throw new Error(`Unexpected ratings shape: paging missing (app=${params.miniAppId})`);
245
+ const p = pagingRaw;
246
+ return {
247
+ ratings,
248
+ paging: {
249
+ pageNumber: typeof p.pageNumber === "number" ? p.pageNumber : page,
250
+ pageSize: typeof p.pageSize === "number" ? p.pageSize : size,
251
+ hasNext: Boolean(p.hasNext),
252
+ totalCount: typeof p.totalCount === "number" ? p.totalCount : 0
253
+ },
254
+ averageRating: typeof rec.averageRating === "number" ? rec.averageRating : 0,
255
+ totalReviewCount: typeof rec.totalReviewCount === "number" ? rec.totalReviewCount : 0
256
+ };
257
+ }
258
+ async function fetchUserReports(params, cookies, opts = {}) {
259
+ const pageSize = params.pageSize ?? 20;
260
+ const qs = new URLSearchParams();
261
+ qs.set("pageSize", String(pageSize));
262
+ if (params.cursor !== void 0) qs.set("cursor", params.cursor);
263
+ const raw = await requestConsoleApi({
264
+ url: `${BASE$3}/workspaces/${params.workspaceId}/mini-apps/${params.miniAppId}/user-reports?` + qs.toString(),
265
+ cookies,
266
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
267
+ });
268
+ if (raw === null || typeof raw !== "object") throw new Error(`Unexpected user-reports shape for app=${params.miniAppId}`);
269
+ const rec = raw;
270
+ const reportsRaw = rec.reports;
271
+ if (!Array.isArray(reportsRaw)) throw new Error(`Unexpected user-reports shape: reports is not an array (app=${params.miniAppId})`);
272
+ return {
273
+ reports: reportsRaw.map((r) => {
274
+ if (r === null || typeof r !== "object") return {};
275
+ return r;
276
+ }),
277
+ nextCursor: typeof rec.nextCursor === "string" ? rec.nextCursor : null,
278
+ hasMore: Boolean(rec.hasMore)
279
+ };
280
+ }
281
+ async function fetchBundles(params, cookies, opts = {}) {
282
+ const qs = new URLSearchParams();
283
+ if (params.page !== void 0) qs.set("page", String(params.page));
284
+ if (params.tested !== void 0) qs.set("tested", String(params.tested));
285
+ if (params.deployStatus !== void 0) qs.set("deployStatus", params.deployStatus);
286
+ const query = qs.toString();
287
+ const raw = await requestConsoleApi({
288
+ url: `${BASE$3}/workspaces/${params.workspaceId}/mini-app/${params.miniAppId}/bundles` + (query ? `?${query}` : ""),
289
+ cookies,
290
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
291
+ });
292
+ if (raw === null || typeof raw !== "object") throw new Error(`Unexpected bundles shape for app=${params.miniAppId}`);
293
+ const rec = raw;
294
+ const contentsRaw = rec.contents;
295
+ if (!Array.isArray(contentsRaw)) throw new Error(`Unexpected bundles shape: contents is not an array (app=${params.miniAppId})`);
296
+ return {
297
+ contents: contentsRaw.map((b) => {
298
+ if (b === null || typeof b !== "object") return {};
299
+ return b;
300
+ }),
301
+ totalPage: typeof rec.totalPage === "number" ? rec.totalPage : 0,
302
+ currentPage: typeof rec.currentPage === "number" ? rec.currentPage : 0
303
+ };
304
+ }
305
+ async function fetchConversionMetrics(params, cookies, opts = {}) {
306
+ const qs = new URLSearchParams();
307
+ qs.set("refresh", String(params.refresh ?? false));
308
+ qs.set("timeUnitType", params.timeUnitType);
309
+ qs.set("startDate", params.startDate);
310
+ qs.set("endDate", params.endDate);
311
+ const raw = await requestConsoleApi({
312
+ url: `${BASE$3}/workspaces/${params.workspaceId}/mini-app/${params.miniAppId}/conversion-metrics?${qs.toString()}`,
313
+ cookies,
314
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
315
+ });
316
+ if (raw === null || typeof raw !== "object") throw new Error(`Unexpected metrics shape for app=${params.miniAppId}`);
317
+ const rec = raw;
318
+ const metricsRaw = rec.metrics;
319
+ if (!Array.isArray(metricsRaw)) throw new Error(`Unexpected metrics shape: metrics is not an array (app=${params.miniAppId})`);
320
+ return {
321
+ metrics: metricsRaw.map((m) => {
322
+ if (m === null || typeof m !== "object") return {};
323
+ return m;
324
+ }),
325
+ cacheTime: typeof rec.cacheTime === "string" ? rec.cacheTime : void 0
326
+ };
327
+ }
328
+ async function fetchCerts(workspaceId, miniAppId, cookies, opts = {}) {
329
+ const raw = await requestConsoleApi({
330
+ url: `${BASE$3}/workspaces/${workspaceId}/mini-app/${miniAppId}/certs`,
331
+ cookies,
332
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
333
+ });
334
+ if (!Array.isArray(raw)) throw new Error(`Unexpected certs shape for app=${miniAppId}: not an array`);
335
+ return raw.map((c) => {
336
+ if (c === null || typeof c !== "object") return {};
337
+ return c;
338
+ });
339
+ }
340
+ async function fetchShareRewards(params, cookies, opts = {}) {
341
+ const qs = new URLSearchParams();
342
+ qs.set("search", params.search ?? "");
343
+ const raw = await requestConsoleApi({
344
+ url: `${BASE$3}/workspaces/${params.workspaceId}/mini-app/${params.miniAppId}/share-rewards?${qs.toString()}`,
345
+ cookies,
346
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
347
+ });
348
+ if (!Array.isArray(raw)) throw new Error(`Unexpected share-rewards shape for app=${params.miniAppId}: not an array`);
349
+ return raw.map((r) => {
350
+ if (r === null || typeof r !== "object") return {};
351
+ return r;
352
+ });
353
+ }
354
+ async function fetchDeployedBundle(workspaceId, miniAppId, cookies, opts = {}) {
355
+ const raw = await requestConsoleApi({
356
+ url: `${BASE$3}/workspaces/${workspaceId}/mini-app/${miniAppId}/bundles/deployed`,
357
+ cookies,
358
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
359
+ });
360
+ if (raw === null) return null;
361
+ if (typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Unexpected deployed-bundle shape for app=${miniAppId}`);
362
+ return raw;
363
+ }
225
364
  async function createMiniApp(workspaceId, payload, cookies, opts = {}) {
226
365
  return normalizeCreateResult(await requestConsoleApi({
227
- url: `${BASE$2}/workspaces/${workspaceId}/mini-app/review`,
366
+ url: `${BASE$3}/workspaces/${workspaceId}/mini-app/review`,
228
367
  method: "POST",
229
368
  cookies,
230
369
  body: payload,
@@ -257,7 +396,7 @@ function normalizeCreateResult(raw) {
257
396
  * the field name is actually `file` — if so, swap it in one place here.
258
397
  */
259
398
  async function uploadMiniAppResource(params, opts = {}) {
260
- const url = new URL(`${BASE$2}/resource/${params.workspaceId}/upload`);
399
+ const url = new URL(`${BASE$3}/resource/${params.workspaceId}/upload`);
261
400
  url.searchParams.set("validWidth", String(params.validWidth));
262
401
  url.searchParams.set("validHeight", String(params.validHeight));
263
402
  const form = new FormData();
@@ -507,7 +646,7 @@ async function emitFailureFromError(json, err) {
507
646
  emitApiError(json, err.message);
508
647
  return exitAfterFlush(ExitCode.ApiError);
509
648
  }
510
- function parsePositiveInt(raw) {
649
+ function parsePositiveInt$1(raw) {
511
650
  if (!/^[1-9]\d*$/.test(raw)) return null;
512
651
  const n = Number.parseInt(raw, 10);
513
652
  return Number.isSafeInteger(n) ? n : null;
@@ -535,7 +674,7 @@ async function resolveWorkspaceContext(args) {
535
674
  let workspaceId;
536
675
  if (args.workspace) {
537
676
  const raw = String(args.workspace);
538
- const parsed = parsePositiveInt(raw);
677
+ const parsed = parsePositiveInt$1(raw);
539
678
  if (parsed === null) {
540
679
  const message = `--workspace must be a positive integer (got ${raw})`;
541
680
  if (args.json) emitJson({
@@ -563,6 +702,21 @@ async function resolveWorkspaceContext(args) {
563
702
  workspaceId
564
703
  };
565
704
  }
705
+ /**
706
+ * Session-only sibling of `resolveWorkspaceContext` for commands that
707
+ * don't need a workspace id (notices come from a shared Toss workspace,
708
+ * whoami is self-scoped). Same "exits on miss, returns null to force
709
+ * `if (!session) return`" pattern.
710
+ */
711
+ async function requireSession(json) {
712
+ const session = await readSession();
713
+ if (!session) {
714
+ emitNotAuthenticated(json);
715
+ await exitAfterFlush(ExitCode.NotAuthenticated);
716
+ return null;
717
+ }
718
+ return session;
719
+ }
566
720
  //#endregion
567
721
  //#region src/config/app-manifest.ts
568
722
  var ManifestError = class extends Error {
@@ -1046,7 +1200,7 @@ function reviewStateFor(entry) {
1046
1200
  const raw = entry.reviewState ?? entry.status;
1047
1201
  return typeof raw === "string" ? raw : void 0;
1048
1202
  }
1049
- const lsCommand$3 = defineCommand({
1203
+ const lsCommand$4 = defineCommand({
1050
1204
  meta: {
1051
1205
  name: "ls",
1052
1206
  description: "List mini-apps in the selected workspace."
@@ -1126,7 +1280,7 @@ function parseAppId(raw) {
1126
1280
  if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) return null;
1127
1281
  return n;
1128
1282
  }
1129
- const showCommand$1 = defineCommand({
1283
+ const showCommand$2 = defineCommand({
1130
1284
  meta: {
1131
1285
  name: "show",
1132
1286
  description: "Show full details of a mini-app, including fields only visible in the draft view."
@@ -1261,188 +1415,942 @@ function deriveReviewState(env) {
1261
1415
  }
1262
1416
  const POLL_MIN_INTERVAL_SEC = 30;
1263
1417
  const POLL_MAX_INTERVAL_SEC = 3600;
1264
- const appCommand = defineCommand({
1418
+ const statusCommand = defineCommand({
1265
1419
  meta: {
1266
- name: "app",
1267
- description: "Inspect mini-apps in a workspace."
1420
+ name: "status",
1421
+ description: "Show the derived review state of a mini-app (under-review / rejected / approved)."
1268
1422
  },
1269
- subCommands: {
1270
- ls: lsCommand$3,
1271
- show: showCommand$1,
1272
- status: defineCommand({
1273
- meta: {
1274
- name: "status",
1275
- description: "Show the derived review state of a mini-app (under-review / rejected / approved)."
1276
- },
1277
- args: {
1278
- id: {
1279
- type: "positional",
1280
- description: "Mini-app ID.",
1281
- required: true
1282
- },
1283
- workspace: {
1284
- type: "string",
1285
- description: "Workspace ID. Defaults to the selected workspace."
1286
- },
1287
- watch: {
1288
- type: "boolean",
1289
- description: "Poll until the review state flips off `under-review` (rejected or approved). Combine with `--interval <seconds>`.",
1290
- default: false
1291
- },
1292
- interval: {
1293
- type: "string",
1294
- description: "Polling interval in seconds when --watch is set. Clamped to [30, 3600].",
1295
- default: "60"
1296
- },
1297
- json: {
1298
- type: "boolean",
1299
- description: "Emit machine-readable JSON.",
1300
- default: false
1301
- }
1302
- },
1303
- async run({ args }) {
1304
- const appId = parseAppId(args.id);
1305
- if (appId === null) {
1306
- if (args.json) emitJson({
1307
- ok: false,
1308
- reason: "invalid-id",
1309
- message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
1310
- });
1311
- else process.stderr.write(`app status: invalid id ${JSON.stringify(args.id)}\n`);
1312
- return exitAfterFlush(ExitCode.Usage);
1313
- }
1314
- const intervalRaw = Number(args.interval);
1315
- if (!Number.isFinite(intervalRaw) || intervalRaw <= 0) {
1316
- if (args.json) emitJson({
1317
- ok: false,
1318
- reason: "invalid-config",
1319
- field: "interval",
1320
- message: `--interval must be a positive number (got ${JSON.stringify(args.interval)})`
1321
- });
1322
- else process.stderr.write(`app status: invalid --interval ${JSON.stringify(args.interval)}\n`);
1323
- return exitAfterFlush(ExitCode.Usage);
1324
- }
1325
- const intervalSec = Math.max(POLL_MIN_INTERVAL_SEC, Math.min(POLL_MAX_INTERVAL_SEC, Math.floor(intervalRaw)));
1326
- const ctx = await resolveWorkspaceContext(args);
1327
- if (!ctx) return;
1328
- const { session, workspaceId } = ctx;
1329
- const emit = (status) => {
1330
- if (args.json) emitJson({
1331
- ok: true,
1332
- workspaceId,
1333
- appId,
1334
- ...status
1335
- });
1336
- else process.stdout.write(`App ${appId} (ws ${workspaceId}): ${status.state}` + (status.rejectedMessage ? `\n reason: ${status.rejectedMessage}` : "") + "\n");
1337
- };
1338
- try {
1339
- const once = async () => {
1340
- return deriveReviewState(await fetchMiniAppWithDraft(workspaceId, appId, session.cookies));
1341
- };
1342
- if (!args.watch) {
1343
- emit(await once());
1344
- return exitAfterFlush(ExitCode.Ok);
1345
- }
1346
- let lastState = null;
1347
- while (true) {
1348
- const status = await once();
1349
- if (args.json) emit(status);
1350
- else if (status.state !== lastState) emit(status);
1351
- lastState = status.state;
1352
- if (status.state !== "under-review") return exitAfterFlush(ExitCode.Ok);
1353
- await new Promise((resolve) => setTimeout(resolve, intervalSec * 1e3));
1354
- }
1355
- } catch (err) {
1356
- return emitFailureFromError(args.json, err);
1357
- }
1423
+ args: {
1424
+ id: {
1425
+ type: "positional",
1426
+ description: "Mini-app ID.",
1427
+ required: true
1428
+ },
1429
+ workspace: {
1430
+ type: "string",
1431
+ description: "Workspace ID. Defaults to the selected workspace."
1432
+ },
1433
+ watch: {
1434
+ type: "boolean",
1435
+ description: "Poll until the review state flips off `under-review` (rejected or approved). Combine with `--interval <seconds>`.",
1436
+ default: false
1437
+ },
1438
+ interval: {
1439
+ type: "string",
1440
+ description: "Polling interval in seconds when --watch is set. Clamped to [30, 3600].",
1441
+ default: "60"
1442
+ },
1443
+ json: {
1444
+ type: "boolean",
1445
+ description: "Emit machine-readable JSON.",
1446
+ default: false
1447
+ }
1448
+ },
1449
+ async run({ args }) {
1450
+ const appId = parseAppId(args.id);
1451
+ if (appId === null) {
1452
+ if (args.json) emitJson({
1453
+ ok: false,
1454
+ reason: "invalid-id",
1455
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
1456
+ });
1457
+ else process.stderr.write(`app status: invalid id ${JSON.stringify(args.id)}\n`);
1458
+ return exitAfterFlush(ExitCode.Usage);
1459
+ }
1460
+ const intervalRaw = Number(args.interval);
1461
+ if (!Number.isFinite(intervalRaw) || intervalRaw <= 0) {
1462
+ if (args.json) emitJson({
1463
+ ok: false,
1464
+ reason: "invalid-config",
1465
+ field: "interval",
1466
+ message: `--interval must be a positive number (got ${JSON.stringify(args.interval)})`
1467
+ });
1468
+ else process.stderr.write(`app status: invalid --interval ${JSON.stringify(args.interval)}\n`);
1469
+ return exitAfterFlush(ExitCode.Usage);
1470
+ }
1471
+ const intervalSec = Math.max(POLL_MIN_INTERVAL_SEC, Math.min(POLL_MAX_INTERVAL_SEC, Math.floor(intervalRaw)));
1472
+ const ctx = await resolveWorkspaceContext(args);
1473
+ if (!ctx) return;
1474
+ const { session, workspaceId } = ctx;
1475
+ const emit = (status) => {
1476
+ if (args.json) emitJson({
1477
+ ok: true,
1478
+ workspaceId,
1479
+ appId,
1480
+ ...status
1481
+ });
1482
+ else process.stdout.write(`App ${appId} (ws ${workspaceId}): ${status.state}` + (status.rejectedMessage ? `\n reason: ${status.rejectedMessage}` : "") + "\n");
1483
+ };
1484
+ try {
1485
+ const once = async () => {
1486
+ return deriveReviewState(await fetchMiniAppWithDraft(workspaceId, appId, session.cookies));
1487
+ };
1488
+ if (!args.watch) {
1489
+ emit(await once());
1490
+ return exitAfterFlush(ExitCode.Ok);
1358
1491
  }
1359
- }),
1360
- register: defineCommand({
1361
- meta: {
1362
- name: "register",
1363
- description: "Register a mini-app in the selected workspace from a YAML/JSON manifest. Uploads logo/thumbnail/screenshots, then submits the create payload."
1364
- },
1365
- args: {
1366
- workspace: {
1367
- type: "string",
1368
- description: "Workspace ID. Defaults to the selected workspace (`aitcc workspace use`)."
1369
- },
1370
- config: {
1371
- type: "string",
1372
- description: "Path to the app manifest. Defaults to `./aitcc.app.yaml`, then `./aitcc.app.json`."
1373
- },
1374
- "dry-run": {
1375
- type: "boolean",
1376
- description: "Validate manifest + images and print the inferred submit payload; no uploads.",
1377
- default: false
1378
- },
1379
- "accept-terms": {
1380
- type: "boolean",
1381
- description: "Attest to the required console legal-agreement checkboxes (see VALIDATION-RULES.md). Required for real submits.",
1382
- default: false
1383
- },
1384
- json: {
1385
- type: "boolean",
1386
- description: "Emit machine-readable JSON to stdout.",
1387
- default: false
1388
- }
1389
- },
1390
- async run({ args }) {
1391
- await runRegister({
1392
- json: args.json,
1393
- dryRun: args["dry-run"],
1394
- acceptTerms: args["accept-terms"],
1395
- ...args.workspace !== void 0 ? { workspace: args.workspace } : {},
1396
- ...args.config !== void 0 ? { config: args.config } : {}
1397
- });
1492
+ let lastState = null;
1493
+ while (true) {
1494
+ const status = await once();
1495
+ if (args.json) emit(status);
1496
+ else if (status.state !== lastState) emit(status);
1497
+ lastState = status.state;
1498
+ if (status.state !== "under-review") return exitAfterFlush(ExitCode.Ok);
1499
+ await new Promise((resolve) => setTimeout(resolve, intervalSec * 1e3));
1398
1500
  }
1399
- })
1501
+ } catch (err) {
1502
+ return emitFailureFromError(args.json, err);
1503
+ }
1400
1504
  }
1401
1505
  });
1402
- //#endregion
1403
- //#region src/api/api-keys.ts
1404
- const BASE$1 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
1405
- async function fetchApiKeys(workspaceId, cookies, opts = {}) {
1406
- const raw = await requestConsoleApi({
1407
- url: `${BASE$1}/workspaces/${workspaceId}/api-keys`,
1408
- cookies,
1409
- ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
1410
- });
1411
- if (!Array.isArray(raw)) throw new Error(`Unexpected api-keys shape for workspace=${workspaceId}: not an array`);
1412
- return raw.map((entry, index) => normalizeKey(entry, workspaceId, index));
1413
- }
1414
- function normalizeKey(raw, workspaceId, index) {
1415
- if (raw === null || typeof raw !== "object") throw new Error(`Unexpected api-key entry at index ${index} for workspace=${workspaceId}: not an object`);
1416
- const rec = raw;
1417
- const rawId = rec.id ?? rec.apiKeyId ?? rec.keyId;
1418
- if (typeof rawId !== "string" && typeof rawId !== "number") throw new Error(`Unexpected api-key entry at index ${index} for workspace=${workspaceId}: missing id`);
1419
- const rawName = rec.name ?? rec.apiKeyName ?? rec.keyName ?? rec.description;
1420
- const name = typeof rawName === "string" ? rawName : void 0;
1421
- const { id: _id, apiKeyId: _aid, keyId: _kid, name: _n, apiKeyName: _an, keyName: _kn, description: _d, ...extra } = rec;
1422
- return {
1423
- id: rawId,
1424
- name,
1425
- extra
1426
- };
1506
+ const VALID_SORT_FIELDS = ["CREATED_AT", "SCORE"];
1507
+ const VALID_SORT_DIRECTIONS = ["ASC", "DESC"];
1508
+ function parseNonNegativeInt(raw, field) {
1509
+ const n = Number(raw);
1510
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 0) return { error: `--${field} must be a non-negative integer (got ${JSON.stringify(raw)})` };
1511
+ return { value: n };
1427
1512
  }
1428
- const keysCommand = defineCommand({
1513
+ const ratingsCommand = defineCommand({
1429
1514
  meta: {
1430
- name: "keys",
1431
- description: "Inspect console API keys used for deploy automation."
1515
+ name: "ratings",
1516
+ description: "List user ratings and reviews left for a mini-app."
1432
1517
  },
1433
- subCommands: { ls: defineCommand({
1434
- meta: {
1435
- name: "ls",
1436
- description: "List console API keys in the selected workspace."
1518
+ args: {
1519
+ id: {
1520
+ type: "positional",
1521
+ description: "Mini-app ID.",
1522
+ required: true
1437
1523
  },
1438
- args: {
1439
- workspace: {
1440
- type: "string",
1441
- description: "Workspace ID. Defaults to the selected workspace (`aitcc workspace use`)."
1442
- },
1443
- json: {
1444
- type: "boolean",
1445
- description: "Emit machine-readable JSON to stdout.",
1524
+ workspace: {
1525
+ type: "string",
1526
+ description: "Workspace ID. Defaults to the selected workspace."
1527
+ },
1528
+ page: {
1529
+ type: "string",
1530
+ description: "Page number (0-indexed).",
1531
+ default: "0"
1532
+ },
1533
+ size: {
1534
+ type: "string",
1535
+ description: "Page size.",
1536
+ default: "20"
1537
+ },
1538
+ "sort-field": {
1539
+ type: "string",
1540
+ description: "Sort field: CREATED_AT (default) or SCORE.",
1541
+ default: "CREATED_AT"
1542
+ },
1543
+ "sort-direction": {
1544
+ type: "string",
1545
+ description: "Sort direction: ASC or DESC (default).",
1546
+ default: "DESC"
1547
+ },
1548
+ json: {
1549
+ type: "boolean",
1550
+ description: "Emit machine-readable JSON.",
1551
+ default: false
1552
+ }
1553
+ },
1554
+ async run({ args }) {
1555
+ const appId = parseAppId(args.id);
1556
+ if (appId === null) {
1557
+ if (args.json) emitJson({
1558
+ ok: false,
1559
+ reason: "invalid-id",
1560
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
1561
+ });
1562
+ else process.stderr.write(`app ratings: invalid id ${JSON.stringify(args.id)}\n`);
1563
+ return exitAfterFlush(ExitCode.Usage);
1564
+ }
1565
+ const pageResult = parseNonNegativeInt(args.page, "page");
1566
+ if ("error" in pageResult) {
1567
+ if (args.json) emitJson({
1568
+ ok: false,
1569
+ reason: "invalid-config",
1570
+ field: "page",
1571
+ message: pageResult.error
1572
+ });
1573
+ else process.stderr.write(`app ratings: ${pageResult.error}\n`);
1574
+ return exitAfterFlush(ExitCode.Usage);
1575
+ }
1576
+ const sizeResult = parseNonNegativeInt(args.size, "size");
1577
+ if ("error" in sizeResult) {
1578
+ if (args.json) emitJson({
1579
+ ok: false,
1580
+ reason: "invalid-config",
1581
+ field: "size",
1582
+ message: sizeResult.error
1583
+ });
1584
+ else process.stderr.write(`app ratings: ${sizeResult.error}\n`);
1585
+ return exitAfterFlush(ExitCode.Usage);
1586
+ }
1587
+ if (sizeResult.value === 0) {
1588
+ if (args.json) emitJson({
1589
+ ok: false,
1590
+ reason: "invalid-config",
1591
+ field: "size",
1592
+ message: "--size must be at least 1"
1593
+ });
1594
+ else process.stderr.write("app ratings: --size must be at least 1\n");
1595
+ return exitAfterFlush(ExitCode.Usage);
1596
+ }
1597
+ const sortField = args["sort-field"];
1598
+ if (!VALID_SORT_FIELDS.includes(sortField)) {
1599
+ if (args.json) emitJson({
1600
+ ok: false,
1601
+ reason: "invalid-config",
1602
+ field: "sort-field",
1603
+ message: `--sort-field must be one of ${VALID_SORT_FIELDS.join("|")} (got ${JSON.stringify(sortField)})`
1604
+ });
1605
+ else process.stderr.write(`app ratings: invalid --sort-field ${JSON.stringify(sortField)}\n`);
1606
+ return exitAfterFlush(ExitCode.Usage);
1607
+ }
1608
+ const sortDirection = args["sort-direction"];
1609
+ if (!VALID_SORT_DIRECTIONS.includes(sortDirection)) {
1610
+ if (args.json) emitJson({
1611
+ ok: false,
1612
+ reason: "invalid-config",
1613
+ field: "sort-direction",
1614
+ message: `--sort-direction must be one of ${VALID_SORT_DIRECTIONS.join("|")} (got ${JSON.stringify(sortDirection)})`
1615
+ });
1616
+ else process.stderr.write(`app ratings: invalid --sort-direction ${JSON.stringify(sortDirection)}\n`);
1617
+ return exitAfterFlush(ExitCode.Usage);
1618
+ }
1619
+ const ctx = await resolveWorkspaceContext(args);
1620
+ if (!ctx) return;
1621
+ const { session, workspaceId } = ctx;
1622
+ try {
1623
+ const result = await fetchMiniAppRatings({
1624
+ workspaceId,
1625
+ miniAppId: appId,
1626
+ page: pageResult.value,
1627
+ size: sizeResult.value,
1628
+ sortField,
1629
+ sortDirection
1630
+ }, session.cookies);
1631
+ if (args.json) {
1632
+ emitJson({
1633
+ ok: true,
1634
+ workspaceId,
1635
+ appId,
1636
+ page: pageResult.value,
1637
+ size: sizeResult.value,
1638
+ sortField,
1639
+ sortDirection,
1640
+ averageRating: result.averageRating,
1641
+ totalReviewCount: result.totalReviewCount,
1642
+ paging: result.paging,
1643
+ ratings: result.ratings
1644
+ });
1645
+ return exitAfterFlush(ExitCode.Ok);
1646
+ }
1647
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): ${result.totalReviewCount} review(s), avg ${result.averageRating.toFixed(2)}\n`);
1648
+ if (result.ratings.length === 0) {
1649
+ process.stdout.write("No ratings on this page.\n");
1650
+ return exitAfterFlush(ExitCode.Ok);
1651
+ }
1652
+ for (const r of result.ratings) {
1653
+ const score = typeof r.score === "number" ? r.score : r.rating ?? "-";
1654
+ const author = typeof r.nickname === "string" ? r.nickname : typeof r.userName === "string" ? r.userName : "(anon)";
1655
+ const text = typeof r.content === "string" ? r.content : typeof r.reviewContent === "string" ? r.reviewContent : "";
1656
+ const createdAt = typeof r.createdAt === "string" ? r.createdAt : typeof r.reviewedAt === "string" ? r.reviewedAt : "";
1657
+ process.stdout.write(`${score}\t${createdAt}\t${author}\t${text}\n`);
1658
+ }
1659
+ if (result.paging.hasNext) process.stdout.write(`(more: --page ${pageResult.value + 1} for next ${sizeResult.value})\n`);
1660
+ return exitAfterFlush(ExitCode.Ok);
1661
+ } catch (err) {
1662
+ return emitFailureFromError(args.json, err);
1663
+ }
1664
+ }
1665
+ });
1666
+ const reportsCommand = defineCommand({
1667
+ meta: {
1668
+ name: "reports",
1669
+ description: "List user-submitted reports (신고 내역) for a mini-app."
1670
+ },
1671
+ args: {
1672
+ id: {
1673
+ type: "positional",
1674
+ description: "Mini-app ID.",
1675
+ required: true
1676
+ },
1677
+ workspace: {
1678
+ type: "string",
1679
+ description: "Workspace ID. Defaults to the selected workspace."
1680
+ },
1681
+ "page-size": {
1682
+ type: "string",
1683
+ description: "Page size (default 20).",
1684
+ default: "20"
1685
+ },
1686
+ cursor: {
1687
+ type: "string",
1688
+ description: "Opaque cursor from a previous response `nextCursor`."
1689
+ },
1690
+ json: {
1691
+ type: "boolean",
1692
+ description: "Emit machine-readable JSON.",
1693
+ default: false
1694
+ }
1695
+ },
1696
+ async run({ args }) {
1697
+ const appId = parseAppId(args.id);
1698
+ if (appId === null) {
1699
+ if (args.json) emitJson({
1700
+ ok: false,
1701
+ reason: "invalid-id",
1702
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
1703
+ });
1704
+ else process.stderr.write(`app reports: invalid id ${JSON.stringify(args.id)}\n`);
1705
+ return exitAfterFlush(ExitCode.Usage);
1706
+ }
1707
+ const pageSizeResult = parseNonNegativeInt(args["page-size"], "page-size");
1708
+ if ("error" in pageSizeResult) {
1709
+ if (args.json) emitJson({
1710
+ ok: false,
1711
+ reason: "invalid-config",
1712
+ field: "page-size",
1713
+ message: pageSizeResult.error
1714
+ });
1715
+ else process.stderr.write(`app reports: ${pageSizeResult.error}\n`);
1716
+ return exitAfterFlush(ExitCode.Usage);
1717
+ }
1718
+ if (pageSizeResult.value === 0) {
1719
+ if (args.json) emitJson({
1720
+ ok: false,
1721
+ reason: "invalid-config",
1722
+ field: "page-size",
1723
+ message: "--page-size must be at least 1"
1724
+ });
1725
+ else process.stderr.write("app reports: --page-size must be at least 1\n");
1726
+ return exitAfterFlush(ExitCode.Usage);
1727
+ }
1728
+ const ctx = await resolveWorkspaceContext(args);
1729
+ if (!ctx) return;
1730
+ const { session, workspaceId } = ctx;
1731
+ try {
1732
+ const result = await fetchUserReports({
1733
+ workspaceId,
1734
+ miniAppId: appId,
1735
+ pageSize: pageSizeResult.value,
1736
+ ...typeof args.cursor === "string" && args.cursor.length > 0 ? { cursor: args.cursor } : {}
1737
+ }, session.cookies);
1738
+ if (args.json) {
1739
+ emitJson({
1740
+ ok: true,
1741
+ workspaceId,
1742
+ appId,
1743
+ pageSize: pageSizeResult.value,
1744
+ cursor: args.cursor ?? null,
1745
+ nextCursor: result.nextCursor,
1746
+ hasMore: result.hasMore,
1747
+ reports: result.reports
1748
+ });
1749
+ return exitAfterFlush(ExitCode.Ok);
1750
+ }
1751
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): ${result.reports.length} report(s) on this page\n`);
1752
+ if (result.reports.length === 0) {
1753
+ process.stdout.write("No reports.\n");
1754
+ return exitAfterFlush(ExitCode.Ok);
1755
+ }
1756
+ for (const r of result.reports) {
1757
+ const id = typeof r.id === "string" || typeof r.id === "number" ? r.id : "-";
1758
+ const reason = typeof r.reason === "string" ? r.reason : r.reportType ?? "-";
1759
+ const text = typeof r.content === "string" ? r.content : typeof r.detail === "string" ? r.detail : "";
1760
+ const createdAt = typeof r.createdAt === "string" ? r.createdAt : typeof r.reportedAt === "string" ? r.reportedAt : "";
1761
+ process.stdout.write(`${id}\t${createdAt}\t${reason}\t${text}\n`);
1762
+ }
1763
+ if (result.hasMore && result.nextCursor) process.stdout.write(`(more: --cursor ${result.nextCursor})\n`);
1764
+ return exitAfterFlush(ExitCode.Ok);
1765
+ } catch (err) {
1766
+ return emitFailureFromError(args.json, err);
1767
+ }
1768
+ }
1769
+ });
1770
+ const bundlesCommand = defineCommand({
1771
+ meta: {
1772
+ name: "bundles",
1773
+ description: "Inspect upload bundles for a mini-app."
1774
+ },
1775
+ subCommands: {
1776
+ ls: defineCommand({
1777
+ meta: {
1778
+ name: "ls",
1779
+ description: "List upload bundles for a mini-app (page-based pagination)."
1780
+ },
1781
+ args: {
1782
+ id: {
1783
+ type: "positional",
1784
+ description: "Mini-app ID.",
1785
+ required: true
1786
+ },
1787
+ workspace: {
1788
+ type: "string",
1789
+ description: "Workspace ID. Defaults to the selected workspace."
1790
+ },
1791
+ page: {
1792
+ type: "string",
1793
+ description: "Page number (0-indexed).",
1794
+ default: "0"
1795
+ },
1796
+ tested: {
1797
+ type: "string",
1798
+ description: "Filter: `true` to show only tested bundles (or `false`)."
1799
+ },
1800
+ "deploy-status": {
1801
+ type: "string",
1802
+ description: "Filter by deploy status (e.g. DEPLOYED)."
1803
+ },
1804
+ json: {
1805
+ type: "boolean",
1806
+ description: "Emit machine-readable JSON.",
1807
+ default: false
1808
+ }
1809
+ },
1810
+ async run({ args }) {
1811
+ const appId = parseAppId(args.id);
1812
+ if (appId === null) {
1813
+ if (args.json) emitJson({
1814
+ ok: false,
1815
+ reason: "invalid-id",
1816
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
1817
+ });
1818
+ else process.stderr.write(`app bundles ls: invalid id ${JSON.stringify(args.id)}\n`);
1819
+ return exitAfterFlush(ExitCode.Usage);
1820
+ }
1821
+ const pageResult = parseNonNegativeInt(args.page, "page");
1822
+ if ("error" in pageResult) {
1823
+ if (args.json) emitJson({
1824
+ ok: false,
1825
+ reason: "invalid-config",
1826
+ field: "page",
1827
+ message: pageResult.error
1828
+ });
1829
+ else process.stderr.write(`app bundles ls: ${pageResult.error}\n`);
1830
+ return exitAfterFlush(ExitCode.Usage);
1831
+ }
1832
+ let tested;
1833
+ if (args.tested !== void 0) if (args.tested === "true") tested = true;
1834
+ else if (args.tested === "false") tested = false;
1835
+ else {
1836
+ if (args.json) emitJson({
1837
+ ok: false,
1838
+ reason: "invalid-config",
1839
+ field: "tested",
1840
+ message: `--tested must be "true" or "false" (got ${JSON.stringify(args.tested)})`
1841
+ });
1842
+ else process.stderr.write(`app bundles ls: invalid --tested ${JSON.stringify(args.tested)}\n`);
1843
+ return exitAfterFlush(ExitCode.Usage);
1844
+ }
1845
+ const ctx = await resolveWorkspaceContext(args);
1846
+ if (!ctx) return;
1847
+ const { session, workspaceId } = ctx;
1848
+ try {
1849
+ const result = await fetchBundles({
1850
+ workspaceId,
1851
+ miniAppId: appId,
1852
+ page: pageResult.value,
1853
+ ...tested !== void 0 ? { tested } : {},
1854
+ ...typeof args["deploy-status"] === "string" && args["deploy-status"].length > 0 ? { deployStatus: args["deploy-status"] } : {}
1855
+ }, session.cookies);
1856
+ if (args.json) {
1857
+ emitJson({
1858
+ ok: true,
1859
+ workspaceId,
1860
+ appId,
1861
+ page: pageResult.value,
1862
+ totalPage: result.totalPage,
1863
+ currentPage: result.currentPage,
1864
+ bundles: result.contents
1865
+ });
1866
+ return exitAfterFlush(ExitCode.Ok);
1867
+ }
1868
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): page ${result.currentPage + 1}/${Math.max(result.totalPage, 1)}, ${result.contents.length} bundle(s)\n`);
1869
+ if (result.contents.length === 0) {
1870
+ process.stdout.write("No bundles on this page.\n");
1871
+ return exitAfterFlush(ExitCode.Ok);
1872
+ }
1873
+ for (const b of result.contents) {
1874
+ const id = typeof b.id === "string" || typeof b.id === "number" ? b.id : "-";
1875
+ const version = typeof b.version === "string" ? b.version : "-";
1876
+ const status = typeof b.deployStatus === "string" ? b.deployStatus : "-";
1877
+ const createdAt = typeof b.createdAt === "string" ? b.createdAt : "";
1878
+ process.stdout.write(`${id}\t${version}\t${status}\t${createdAt}\n`);
1879
+ }
1880
+ if (result.currentPage + 1 < result.totalPage) process.stdout.write(`(more: --page ${result.currentPage + 1})\n`);
1881
+ return exitAfterFlush(ExitCode.Ok);
1882
+ } catch (err) {
1883
+ return emitFailureFromError(args.json, err);
1884
+ }
1885
+ }
1886
+ }),
1887
+ deployed: defineCommand({
1888
+ meta: {
1889
+ name: "deployed",
1890
+ description: "Show the currently deployed bundle for a mini-app (or null if none)."
1891
+ },
1892
+ args: {
1893
+ id: {
1894
+ type: "positional",
1895
+ description: "Mini-app ID.",
1896
+ required: true
1897
+ },
1898
+ workspace: {
1899
+ type: "string",
1900
+ description: "Workspace ID. Defaults to the selected workspace."
1901
+ },
1902
+ json: {
1903
+ type: "boolean",
1904
+ description: "Emit machine-readable JSON.",
1905
+ default: false
1906
+ }
1907
+ },
1908
+ async run({ args }) {
1909
+ const appId = parseAppId(args.id);
1910
+ if (appId === null) {
1911
+ if (args.json) emitJson({
1912
+ ok: false,
1913
+ reason: "invalid-id",
1914
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
1915
+ });
1916
+ else process.stderr.write(`app bundles deployed: invalid id ${JSON.stringify(args.id)}\n`);
1917
+ return exitAfterFlush(ExitCode.Usage);
1918
+ }
1919
+ const ctx = await resolveWorkspaceContext(args);
1920
+ if (!ctx) return;
1921
+ const { session, workspaceId } = ctx;
1922
+ try {
1923
+ const bundle = await fetchDeployedBundle(workspaceId, appId, session.cookies);
1924
+ if (args.json) {
1925
+ emitJson({
1926
+ ok: true,
1927
+ workspaceId,
1928
+ appId,
1929
+ bundle
1930
+ });
1931
+ return exitAfterFlush(ExitCode.Ok);
1932
+ }
1933
+ if (bundle === null) {
1934
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): no deployed bundle\n`);
1935
+ return exitAfterFlush(ExitCode.Ok);
1936
+ }
1937
+ const id = typeof bundle.id === "string" || typeof bundle.id === "number" ? bundle.id : "-";
1938
+ const version = typeof bundle.version === "string" ? bundle.version : "-";
1939
+ const status = typeof bundle.deployStatus === "string" ? bundle.deployStatus : "-";
1940
+ const deployedAt = typeof bundle.deployedAt === "string" ? bundle.deployedAt : "";
1941
+ process.stdout.write(`App ${appId} deployed bundle:\n`);
1942
+ process.stdout.write(` id ${id}\n`);
1943
+ process.stdout.write(` version ${version}\n`);
1944
+ process.stdout.write(` status ${status}\n`);
1945
+ process.stdout.write(` deployedAt ${deployedAt}\n`);
1946
+ return exitAfterFlush(ExitCode.Ok);
1947
+ } catch (err) {
1948
+ return emitFailureFromError(args.json, err);
1949
+ }
1950
+ }
1951
+ })
1952
+ }
1953
+ });
1954
+ const certsCommand = defineCommand({
1955
+ meta: {
1956
+ name: "certs",
1957
+ description: "Inspect mTLS certificates for a mini-app."
1958
+ },
1959
+ subCommands: { ls: defineCommand({
1960
+ meta: {
1961
+ name: "ls",
1962
+ description: "List mTLS certificates issued for a mini-app."
1963
+ },
1964
+ args: {
1965
+ id: {
1966
+ type: "positional",
1967
+ description: "Mini-app ID.",
1968
+ required: true
1969
+ },
1970
+ workspace: {
1971
+ type: "string",
1972
+ description: "Workspace ID. Defaults to the selected workspace."
1973
+ },
1974
+ json: {
1975
+ type: "boolean",
1976
+ description: "Emit machine-readable JSON.",
1977
+ default: false
1978
+ }
1979
+ },
1980
+ async run({ args }) {
1981
+ const appId = parseAppId(args.id);
1982
+ if (appId === null) {
1983
+ if (args.json) emitJson({
1984
+ ok: false,
1985
+ reason: "invalid-id",
1986
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
1987
+ });
1988
+ else process.stderr.write(`app certs ls: invalid id ${JSON.stringify(args.id)}\n`);
1989
+ return exitAfterFlush(ExitCode.Usage);
1990
+ }
1991
+ const ctx = await resolveWorkspaceContext(args);
1992
+ if (!ctx) return;
1993
+ const { session, workspaceId } = ctx;
1994
+ try {
1995
+ const certs = await fetchCerts(workspaceId, appId, session.cookies);
1996
+ if (args.json) {
1997
+ emitJson({
1998
+ ok: true,
1999
+ workspaceId,
2000
+ appId,
2001
+ certs
2002
+ });
2003
+ return exitAfterFlush(ExitCode.Ok);
2004
+ }
2005
+ if (certs.length === 0) {
2006
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): no mTLS certs\n`);
2007
+ return exitAfterFlush(ExitCode.Ok);
2008
+ }
2009
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): ${certs.length} cert(s)\n`);
2010
+ for (const c of certs) {
2011
+ const id = typeof c.id === "string" || typeof c.id === "number" ? c.id : typeof c.certId === "string" || typeof c.certId === "number" ? c.certId : "-";
2012
+ const cn = typeof c.commonName === "string" ? c.commonName : "-";
2013
+ const createdAt = typeof c.createdAt === "string" ? c.createdAt : "";
2014
+ const expiresAt = typeof c.expiresAt === "string" ? c.expiresAt : typeof c.validUntil === "string" ? c.validUntil : "";
2015
+ process.stdout.write(`${id}\t${cn}\t${createdAt}\t${expiresAt}\n`);
2016
+ }
2017
+ return exitAfterFlush(ExitCode.Ok);
2018
+ } catch (err) {
2019
+ return emitFailureFromError(args.json, err);
2020
+ }
2021
+ }
2022
+ }) }
2023
+ });
2024
+ const VALID_TIME_UNITS = [
2025
+ "DAY",
2026
+ "WEEK",
2027
+ "MONTH"
2028
+ ];
2029
+ function parseIsoDate(raw, field) {
2030
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(raw)) return { error: `--${field} must be YYYY-MM-DD (got ${JSON.stringify(raw)})` };
2031
+ const d = /* @__PURE__ */ new Date(`${raw}T00:00:00Z`);
2032
+ if (Number.isNaN(d.getTime())) return { error: `--${field} is not a valid date (got ${JSON.stringify(raw)})` };
2033
+ return { value: raw };
2034
+ }
2035
+ function todayLocalIso() {
2036
+ const d = /* @__PURE__ */ new Date();
2037
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
2038
+ }
2039
+ function daysAgoLocalIso(days) {
2040
+ const d = /* @__PURE__ */ new Date();
2041
+ d.setDate(d.getDate() - days);
2042
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
2043
+ }
2044
+ const appCommand = defineCommand({
2045
+ meta: {
2046
+ name: "app",
2047
+ description: "Inspect mini-apps in a workspace."
2048
+ },
2049
+ subCommands: {
2050
+ ls: lsCommand$4,
2051
+ show: showCommand$2,
2052
+ status: statusCommand,
2053
+ ratings: ratingsCommand,
2054
+ reports: reportsCommand,
2055
+ bundles: bundlesCommand,
2056
+ certs: certsCommand,
2057
+ metrics: defineCommand({
2058
+ meta: {
2059
+ name: "metrics",
2060
+ description: "Show conversion metrics for a mini-app over a date range."
2061
+ },
2062
+ args: {
2063
+ id: {
2064
+ type: "positional",
2065
+ description: "Mini-app ID.",
2066
+ required: true
2067
+ },
2068
+ workspace: {
2069
+ type: "string",
2070
+ description: "Workspace ID. Defaults to the selected workspace."
2071
+ },
2072
+ "time-unit": {
2073
+ type: "string",
2074
+ description: "Bucket size: DAY | WEEK | MONTH.",
2075
+ default: "DAY"
2076
+ },
2077
+ start: {
2078
+ type: "string",
2079
+ description: "Start date (YYYY-MM-DD). Defaults to 30 days before --end."
2080
+ },
2081
+ end: {
2082
+ type: "string",
2083
+ description: "End date (YYYY-MM-DD). Defaults to today (host local)."
2084
+ },
2085
+ refresh: {
2086
+ type: "boolean",
2087
+ description: "Bypass server-side cache.",
2088
+ default: false
2089
+ },
2090
+ json: {
2091
+ type: "boolean",
2092
+ description: "Emit machine-readable JSON.",
2093
+ default: false
2094
+ }
2095
+ },
2096
+ async run({ args }) {
2097
+ const appId = parseAppId(args.id);
2098
+ if (appId === null) {
2099
+ if (args.json) emitJson({
2100
+ ok: false,
2101
+ reason: "invalid-id",
2102
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
2103
+ });
2104
+ else process.stderr.write(`app metrics: invalid id ${JSON.stringify(args.id)}\n`);
2105
+ return exitAfterFlush(ExitCode.Usage);
2106
+ }
2107
+ const timeUnit = String(args["time-unit"]).toUpperCase();
2108
+ if (!VALID_TIME_UNITS.includes(timeUnit)) {
2109
+ const message = `--time-unit must be one of ${VALID_TIME_UNITS.join("|")} (got ${JSON.stringify(args["time-unit"])})`;
2110
+ if (args.json) emitJson({
2111
+ ok: false,
2112
+ reason: "invalid-time-unit",
2113
+ message
2114
+ });
2115
+ else process.stderr.write(`${message}\n`);
2116
+ return exitAfterFlush(ExitCode.Usage);
2117
+ }
2118
+ const endResult = parseIsoDate(args.end ? String(args.end) : todayLocalIso(), "end");
2119
+ if ("error" in endResult) {
2120
+ if (args.json) emitJson({
2121
+ ok: false,
2122
+ reason: "invalid-date",
2123
+ message: endResult.error
2124
+ });
2125
+ else process.stderr.write(`${endResult.error}\n`);
2126
+ return exitAfterFlush(ExitCode.Usage);
2127
+ }
2128
+ const startResult = parseIsoDate(args.start ? String(args.start) : daysAgoLocalIso(30), "start");
2129
+ if ("error" in startResult) {
2130
+ if (args.json) emitJson({
2131
+ ok: false,
2132
+ reason: "invalid-date",
2133
+ message: startResult.error
2134
+ });
2135
+ else process.stderr.write(`${startResult.error}\n`);
2136
+ return exitAfterFlush(ExitCode.Usage);
2137
+ }
2138
+ if (startResult.value > endResult.value) {
2139
+ const message = `--start (${startResult.value}) must be on or before --end (${endResult.value})`;
2140
+ if (args.json) emitJson({
2141
+ ok: false,
2142
+ reason: "invalid-date",
2143
+ message
2144
+ });
2145
+ else process.stderr.write(`${message}\n`);
2146
+ return exitAfterFlush(ExitCode.Usage);
2147
+ }
2148
+ const ctx = await resolveWorkspaceContext(args);
2149
+ if (!ctx) return;
2150
+ const { session, workspaceId } = ctx;
2151
+ try {
2152
+ const result = await fetchConversionMetrics({
2153
+ workspaceId,
2154
+ miniAppId: appId,
2155
+ timeUnitType: timeUnit,
2156
+ startDate: startResult.value,
2157
+ endDate: endResult.value,
2158
+ refresh: args.refresh
2159
+ }, session.cookies);
2160
+ if (args.json) {
2161
+ emitJson({
2162
+ ok: true,
2163
+ workspaceId,
2164
+ appId,
2165
+ timeUnitType: timeUnit,
2166
+ startDate: startResult.value,
2167
+ endDate: endResult.value,
2168
+ ...result.cacheTime !== void 0 ? { cacheTime: result.cacheTime } : {},
2169
+ metrics: result.metrics
2170
+ });
2171
+ return exitAfterFlush(ExitCode.Ok);
2172
+ }
2173
+ const header = `App ${appId} (ws ${workspaceId}) · ${timeUnit} · ${startResult.value} → ${endResult.value}`;
2174
+ if (result.metrics.length === 0) {
2175
+ process.stdout.write(`${header}: no metrics\n`);
2176
+ return exitAfterFlush(ExitCode.Ok);
2177
+ }
2178
+ process.stdout.write(`${header}: ${result.metrics.length} bucket(s)\n`);
2179
+ for (const m of result.metrics) {
2180
+ const date = typeof m.date === "string" ? m.date : typeof m.bucketDate === "string" ? m.bucketDate : "";
2181
+ const impressions = typeof m.impressions === "number" ? m.impressions : typeof m.impressionCount === "number" ? m.impressionCount : "";
2182
+ const clicks = typeof m.clicks === "number" ? m.clicks : typeof m.clickCount === "number" ? m.clickCount : "";
2183
+ process.stdout.write(`${date}\t${impressions}\t${clicks}\n`);
2184
+ }
2185
+ return exitAfterFlush(ExitCode.Ok);
2186
+ } catch (err) {
2187
+ return emitFailureFromError(args.json, err);
2188
+ }
2189
+ }
2190
+ }),
2191
+ "share-rewards": defineCommand({
2192
+ meta: {
2193
+ name: "share-rewards",
2194
+ description: "Inspect share-reward promotions for a mini-app."
2195
+ },
2196
+ subCommands: { ls: defineCommand({
2197
+ meta: {
2198
+ name: "ls",
2199
+ description: "List share-reward promotions configured for a mini-app."
2200
+ },
2201
+ args: {
2202
+ id: {
2203
+ type: "positional",
2204
+ description: "Mini-app ID.",
2205
+ required: true
2206
+ },
2207
+ workspace: {
2208
+ type: "string",
2209
+ description: "Workspace ID. Defaults to the selected workspace."
2210
+ },
2211
+ search: {
2212
+ type: "string",
2213
+ description: "Filter by title (server-side title-contains match). Empty matches everything."
2214
+ },
2215
+ json: {
2216
+ type: "boolean",
2217
+ description: "Emit machine-readable JSON.",
2218
+ default: false
2219
+ }
2220
+ },
2221
+ async run({ args }) {
2222
+ const appId = parseAppId(args.id);
2223
+ if (appId === null) {
2224
+ if (args.json) emitJson({
2225
+ ok: false,
2226
+ reason: "invalid-id",
2227
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
2228
+ });
2229
+ else process.stderr.write(`app share-rewards ls: invalid id ${JSON.stringify(args.id)}\n`);
2230
+ return exitAfterFlush(ExitCode.Usage);
2231
+ }
2232
+ const ctx = await resolveWorkspaceContext(args);
2233
+ if (!ctx) return;
2234
+ const { session, workspaceId } = ctx;
2235
+ try {
2236
+ const rewards = await fetchShareRewards({
2237
+ workspaceId,
2238
+ miniAppId: appId,
2239
+ ...args.search !== void 0 ? { search: String(args.search) } : {}
2240
+ }, session.cookies);
2241
+ if (args.json) {
2242
+ emitJson({
2243
+ ok: true,
2244
+ workspaceId,
2245
+ appId,
2246
+ rewards
2247
+ });
2248
+ return exitAfterFlush(ExitCode.Ok);
2249
+ }
2250
+ if (rewards.length === 0) {
2251
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): no share-reward promotions\n`);
2252
+ return exitAfterFlush(ExitCode.Ok);
2253
+ }
2254
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): ${rewards.length} share-reward(s)\n`);
2255
+ for (const r of rewards) {
2256
+ const id = typeof r.id === "string" || typeof r.id === "number" ? r.id : typeof r.rewardId === "string" || typeof r.rewardId === "number" ? r.rewardId : "-";
2257
+ const title = typeof r.title === "string" ? r.title : typeof r.name === "string" ? r.name : "-";
2258
+ const status = typeof r.status === "string" ? r.status : "-";
2259
+ process.stdout.write(`${id}\t${title}\t${status}\n`);
2260
+ }
2261
+ return exitAfterFlush(ExitCode.Ok);
2262
+ } catch (err) {
2263
+ return emitFailureFromError(args.json, err);
2264
+ }
2265
+ }
2266
+ }) }
2267
+ }),
2268
+ register: defineCommand({
2269
+ meta: {
2270
+ name: "register",
2271
+ description: "Register a mini-app in the selected workspace from a YAML/JSON manifest. Uploads logo/thumbnail/screenshots, then submits the create payload."
2272
+ },
2273
+ args: {
2274
+ workspace: {
2275
+ type: "string",
2276
+ description: "Workspace ID. Defaults to the selected workspace (`aitcc workspace use`)."
2277
+ },
2278
+ config: {
2279
+ type: "string",
2280
+ description: "Path to the app manifest. Defaults to `./aitcc.app.yaml`, then `./aitcc.app.json`."
2281
+ },
2282
+ "dry-run": {
2283
+ type: "boolean",
2284
+ description: "Validate manifest + images and print the inferred submit payload; no uploads.",
2285
+ default: false
2286
+ },
2287
+ "accept-terms": {
2288
+ type: "boolean",
2289
+ description: "Attest to the required console legal-agreement checkboxes (see VALIDATION-RULES.md). Required for real submits.",
2290
+ default: false
2291
+ },
2292
+ json: {
2293
+ type: "boolean",
2294
+ description: "Emit machine-readable JSON to stdout.",
2295
+ default: false
2296
+ }
2297
+ },
2298
+ async run({ args }) {
2299
+ await runRegister({
2300
+ json: args.json,
2301
+ dryRun: args["dry-run"],
2302
+ acceptTerms: args["accept-terms"],
2303
+ ...args.workspace !== void 0 ? { workspace: args.workspace } : {},
2304
+ ...args.config !== void 0 ? { config: args.config } : {}
2305
+ });
2306
+ }
2307
+ })
2308
+ }
2309
+ });
2310
+ //#endregion
2311
+ //#region src/api/api-keys.ts
2312
+ const BASE$2 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
2313
+ async function fetchApiKeys(workspaceId, cookies, opts = {}) {
2314
+ const raw = await requestConsoleApi({
2315
+ url: `${BASE$2}/workspaces/${workspaceId}/api-keys`,
2316
+ cookies,
2317
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
2318
+ });
2319
+ if (!Array.isArray(raw)) throw new Error(`Unexpected api-keys shape for workspace=${workspaceId}: not an array`);
2320
+ return raw.map((entry, index) => normalizeKey(entry, workspaceId, index));
2321
+ }
2322
+ function normalizeKey(raw, workspaceId, index) {
2323
+ if (raw === null || typeof raw !== "object") throw new Error(`Unexpected api-key entry at index ${index} for workspace=${workspaceId}: not an object`);
2324
+ const rec = raw;
2325
+ const rawId = rec.id ?? rec.apiKeyId ?? rec.keyId;
2326
+ if (typeof rawId !== "string" && typeof rawId !== "number") throw new Error(`Unexpected api-key entry at index ${index} for workspace=${workspaceId}: missing id`);
2327
+ const rawName = rec.name ?? rec.apiKeyName ?? rec.keyName ?? rec.description;
2328
+ const name = typeof rawName === "string" ? rawName : void 0;
2329
+ const { id: _id, apiKeyId: _aid, keyId: _kid, name: _n, apiKeyName: _an, keyName: _kn, description: _d, ...extra } = rec;
2330
+ return {
2331
+ id: rawId,
2332
+ name,
2333
+ extra
2334
+ };
2335
+ }
2336
+ const keysCommand = defineCommand({
2337
+ meta: {
2338
+ name: "keys",
2339
+ description: "Inspect console API keys used for deploy automation."
2340
+ },
2341
+ subCommands: { ls: defineCommand({
2342
+ meta: {
2343
+ name: "ls",
2344
+ description: "List console API keys in the selected workspace."
2345
+ },
2346
+ args: {
2347
+ workspace: {
2348
+ type: "string",
2349
+ description: "Workspace ID. Defaults to the selected workspace (`aitcc workspace use`)."
2350
+ },
2351
+ json: {
2352
+ type: "boolean",
2353
+ description: "Emit machine-readable JSON to stdout.",
1446
2354
  default: false
1447
2355
  }
1448
2356
  },
@@ -2147,10 +3055,10 @@ const logoutCommand = defineCommand({
2147
3055
  });
2148
3056
  //#endregion
2149
3057
  //#region src/api/members.ts
2150
- const BASE = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
3058
+ const BASE$1 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
2151
3059
  async function fetchWorkspaceMembers(workspaceId, cookies, opts = {}) {
2152
3060
  const raw = await requestConsoleApi({
2153
- url: `${BASE}/workspaces/${workspaceId}/members`,
3061
+ url: `${BASE$1}/workspaces/${workspaceId}/members`,
2154
3062
  cookies,
2155
3063
  ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
2156
3064
  });
@@ -2235,6 +3143,257 @@ const membersCommand = defineCommand({
2235
3143
  }
2236
3144
  }) }
2237
3145
  });
3146
+ const BASE = "https://api-public.toss.im/api-public/v3/ipd-thor/api/v1";
3147
+ async function fetchNotices(params, cookies, opts = {}) {
3148
+ const qs = new URLSearchParams();
3149
+ qs.set("page", String(params.page ?? 0));
3150
+ qs.set("size", String(params.size ?? 20));
3151
+ if (params.titleContains !== void 0) qs.set("title__icontains", params.titleContains);
3152
+ const raw = await requestConsoleApi({
3153
+ url: `${BASE}/workspaces/129/posts?${qs.toString()}`,
3154
+ cookies,
3155
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
3156
+ });
3157
+ if (raw === null || typeof raw !== "object") throw new Error("Unexpected notices shape: not an object");
3158
+ const rec = raw;
3159
+ const resultsRaw = rec.results;
3160
+ if (!Array.isArray(resultsRaw)) throw new Error("Unexpected notices shape: results is not an array");
3161
+ const results = resultsRaw.map((r) => {
3162
+ if (r === null || typeof r !== "object") return {};
3163
+ return r;
3164
+ });
3165
+ return {
3166
+ page: typeof rec.page === "number" ? rec.page : 1,
3167
+ pageSize: typeof rec.pageSize === "number" ? rec.pageSize : params.size ?? 20,
3168
+ count: typeof rec.count === "number" ? rec.count : results.length,
3169
+ next: typeof rec.next === "string" ? rec.next : null,
3170
+ previous: typeof rec.previous === "string" ? rec.previous : null,
3171
+ results
3172
+ };
3173
+ }
3174
+ async function fetchNoticeCategories(cookies, opts = {}) {
3175
+ const raw = await requestConsoleApi({
3176
+ url: `${BASE}/workspaces/129/categories`,
3177
+ cookies,
3178
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
3179
+ });
3180
+ if (!Array.isArray(raw)) throw new Error("Unexpected categories shape: not an array");
3181
+ return raw.map((c) => {
3182
+ if (c === null || typeof c !== "object") return {};
3183
+ return c;
3184
+ });
3185
+ }
3186
+ async function fetchNoticePost(postId, cookies, opts = {}) {
3187
+ const raw = await requestConsoleApi({
3188
+ url: `${BASE}/workspaces/129/posts/${postId}`,
3189
+ cookies,
3190
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
3191
+ });
3192
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Unexpected notice-post shape for id=${postId}`);
3193
+ return raw;
3194
+ }
3195
+ //#endregion
3196
+ //#region src/commands/notices.ts
3197
+ function parsePositiveInt(raw, field) {
3198
+ const n = Number(raw);
3199
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 0) return { error: `--${field} must be a non-negative integer (got ${JSON.stringify(raw)})` };
3200
+ return { value: n };
3201
+ }
3202
+ const noticesCommand = defineCommand({
3203
+ meta: {
3204
+ name: "notices",
3205
+ description: "Read Apps in Toss notices (공지사항). Shared across all users."
3206
+ },
3207
+ subCommands: {
3208
+ ls: defineCommand({
3209
+ meta: {
3210
+ name: "ls",
3211
+ description: "List notices (공지사항) from Apps in Toss."
3212
+ },
3213
+ args: {
3214
+ page: {
3215
+ type: "string",
3216
+ description: "Page number (0-indexed).",
3217
+ default: "0"
3218
+ },
3219
+ size: {
3220
+ type: "string",
3221
+ description: "Page size.",
3222
+ default: "20"
3223
+ },
3224
+ search: {
3225
+ type: "string",
3226
+ description: "Filter by title substring (case-insensitive)."
3227
+ },
3228
+ json: {
3229
+ type: "boolean",
3230
+ description: "Emit machine-readable JSON.",
3231
+ default: false
3232
+ }
3233
+ },
3234
+ async run({ args }) {
3235
+ const session = await requireSession(args.json);
3236
+ if (!session) return;
3237
+ const pageResult = parsePositiveInt(args.page, "page");
3238
+ if ("error" in pageResult) {
3239
+ if (args.json) emitJson({
3240
+ ok: false,
3241
+ reason: "invalid-config",
3242
+ field: "page",
3243
+ message: pageResult.error
3244
+ });
3245
+ else process.stderr.write(`notices ls: ${pageResult.error}\n`);
3246
+ return exitAfterFlush(ExitCode.Usage);
3247
+ }
3248
+ const sizeResult = parsePositiveInt(args.size, "size");
3249
+ if ("error" in sizeResult) {
3250
+ if (args.json) emitJson({
3251
+ ok: false,
3252
+ reason: "invalid-config",
3253
+ field: "size",
3254
+ message: sizeResult.error
3255
+ });
3256
+ else process.stderr.write(`notices ls: ${sizeResult.error}\n`);
3257
+ return exitAfterFlush(ExitCode.Usage);
3258
+ }
3259
+ if (sizeResult.value === 0) {
3260
+ if (args.json) emitJson({
3261
+ ok: false,
3262
+ reason: "invalid-config",
3263
+ field: "size",
3264
+ message: "--size must be at least 1"
3265
+ });
3266
+ else process.stderr.write("notices ls: --size must be at least 1\n");
3267
+ return exitAfterFlush(ExitCode.Usage);
3268
+ }
3269
+ try {
3270
+ const result = await fetchNotices({
3271
+ page: pageResult.value,
3272
+ size: sizeResult.value,
3273
+ ...typeof args.search === "string" && args.search.length > 0 ? { titleContains: args.search } : {}
3274
+ }, session.cookies);
3275
+ if (args.json) {
3276
+ emitJson({
3277
+ ok: true,
3278
+ page: result.page,
3279
+ pageSize: result.pageSize,
3280
+ count: result.count,
3281
+ hasNext: result.next !== null,
3282
+ notices: result.results
3283
+ });
3284
+ return exitAfterFlush(ExitCode.Ok);
3285
+ }
3286
+ process.stdout.write(`Notices: page ${result.page}, ${result.results.length}/${result.count} shown\n`);
3287
+ if (result.results.length === 0) {
3288
+ process.stdout.write("No notices on this page.\n");
3289
+ return exitAfterFlush(ExitCode.Ok);
3290
+ }
3291
+ for (const n of result.results) {
3292
+ const id = typeof n.id === "string" || typeof n.id === "number" ? n.id : "-";
3293
+ const category = typeof n.category === "string" ? n.category : "-";
3294
+ const title = typeof n.title === "string" ? n.title : "";
3295
+ const publishedTime = typeof n.publishedTime === "string" ? n.publishedTime : "";
3296
+ process.stdout.write(`${id}\t${publishedTime}\t[${category}]\t${title}\n`);
3297
+ }
3298
+ if (result.next !== null) process.stdout.write(`(more: --page ${result.page + 1})\n`);
3299
+ return exitAfterFlush(ExitCode.Ok);
3300
+ } catch (err) {
3301
+ return emitFailureFromError(args.json, err);
3302
+ }
3303
+ }
3304
+ }),
3305
+ show: defineCommand({
3306
+ meta: {
3307
+ name: "show",
3308
+ description: "Show a single notice post by ID."
3309
+ },
3310
+ args: {
3311
+ id: {
3312
+ type: "positional",
3313
+ description: "Notice post ID.",
3314
+ required: true
3315
+ },
3316
+ json: {
3317
+ type: "boolean",
3318
+ description: "Emit machine-readable JSON.",
3319
+ default: false
3320
+ }
3321
+ },
3322
+ async run({ args }) {
3323
+ const postId = Number(args.id);
3324
+ if (!Number.isFinite(postId) || !Number.isInteger(postId) || postId <= 0) {
3325
+ if (args.json) emitJson({
3326
+ ok: false,
3327
+ reason: "invalid-id",
3328
+ message: `notice id must be a positive integer (got ${JSON.stringify(args.id)})`
3329
+ });
3330
+ else process.stderr.write(`notices show: invalid id ${JSON.stringify(args.id)}\n`);
3331
+ return exitAfterFlush(ExitCode.Usage);
3332
+ }
3333
+ const session = await requireSession(args.json);
3334
+ if (!session) return;
3335
+ try {
3336
+ const notice = await fetchNoticePost(postId, session.cookies);
3337
+ if (args.json) {
3338
+ emitJson({
3339
+ ok: true,
3340
+ id: postId,
3341
+ notice
3342
+ });
3343
+ return exitAfterFlush(ExitCode.Ok);
3344
+ }
3345
+ const title = typeof notice.title === "string" ? notice.title : "";
3346
+ const subtitle = typeof notice.subtitle === "string" ? notice.subtitle : "";
3347
+ const category = typeof notice.category === "string" ? notice.category : "";
3348
+ const publishedTime = typeof notice.publishedTime === "string" ? notice.publishedTime : "";
3349
+ const body = typeof notice.fullDescription === "string" ? notice.fullDescription : typeof notice.shortDescription === "string" ? notice.shortDescription : "";
3350
+ process.stdout.write(`# ${title}\n`);
3351
+ if (subtitle) process.stdout.write(`${subtitle}\n`);
3352
+ process.stdout.write(`\n[${category}] ${publishedTime}\n\n`);
3353
+ process.stdout.write(body);
3354
+ if (!body.endsWith("\n")) process.stdout.write("\n");
3355
+ return exitAfterFlush(ExitCode.Ok);
3356
+ } catch (err) {
3357
+ return emitFailureFromError(args.json, err);
3358
+ }
3359
+ }
3360
+ }),
3361
+ categories: defineCommand({
3362
+ meta: {
3363
+ name: "categories",
3364
+ description: "List notice categories and their post counts."
3365
+ },
3366
+ args: { json: {
3367
+ type: "boolean",
3368
+ description: "Emit machine-readable JSON.",
3369
+ default: false
3370
+ } },
3371
+ async run({ args }) {
3372
+ const session = await requireSession(args.json);
3373
+ if (!session) return;
3374
+ try {
3375
+ const categories = await fetchNoticeCategories(session.cookies);
3376
+ if (args.json) {
3377
+ emitJson({
3378
+ ok: true,
3379
+ categories
3380
+ });
3381
+ return exitAfterFlush(ExitCode.Ok);
3382
+ }
3383
+ for (const c of categories) {
3384
+ const id = typeof c.id === "string" || typeof c.id === "number" ? c.id : "-";
3385
+ const name = typeof c.name === "string" ? c.name : "-";
3386
+ const postCount = typeof c.postCount === "number" ? c.postCount : 0;
3387
+ process.stdout.write(`${id}\t${postCount}\t${name}\n`);
3388
+ }
3389
+ return exitAfterFlush(ExitCode.Ok);
3390
+ } catch (err) {
3391
+ return emitFailureFromError(args.json, err);
3392
+ }
3393
+ }
3394
+ })
3395
+ }
3396
+ });
2238
3397
  //#endregion
2239
3398
  //#region src/github.ts
2240
3399
  const REPO_OWNER = "apps-in-toss-community";
@@ -2348,7 +3507,7 @@ function resolveVersion() {
2348
3507
  if (typeof injected === "string" && injected.length > 0) return injected;
2349
3508
  } catch {}
2350
3509
  try {
2351
- return "0.1.10";
3510
+ return "0.1.11";
2352
3511
  } catch {}
2353
3512
  return "0.0.0-dev";
2354
3513
  }
@@ -2805,7 +3964,7 @@ const workspaceCommand = defineCommand({
2805
3964
  },
2806
3965
  async run({ args }) {
2807
3966
  const raw = String(args.id);
2808
- const parsed = parsePositiveInt(raw);
3967
+ const parsed = parsePositiveInt$1(raw);
2809
3968
  if (parsed === null) {
2810
3969
  const message = `workspace id must be a positive integer (got ${raw})`;
2811
3970
  if (args.json) emitJson({
@@ -2873,7 +4032,7 @@ const workspaceCommand = defineCommand({
2873
4032
  let workspaceId;
2874
4033
  if (args.workspace) {
2875
4034
  const raw = String(args.workspace);
2876
- const parsed = parsePositiveInt(raw);
4035
+ const parsed = parsePositiveInt$1(raw);
2877
4036
  if (parsed === null) {
2878
4037
  const message = `--workspace must be a positive integer (got ${raw})`;
2879
4038
  if (args.json) emitJson({
@@ -2931,7 +4090,8 @@ runMain(defineCommand({
2931
4090
  workspace: workspaceCommand,
2932
4091
  app: appCommand,
2933
4092
  members: membersCommand,
2934
- keys: keysCommand
4093
+ keys: keysCommand,
4094
+ notices: noticesCommand
2935
4095
  }
2936
4096
  }));
2937
4097
  //#endregion