@ait-co/console-cli 0.1.9 → 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
  });
@@ -214,15 +214,156 @@ async function fetchMiniAppWithDraft(workspaceId, miniAppId, cookies, opts = {})
214
214
  const rec = raw;
215
215
  return {
216
216
  current: isRecordOrNull(rec.current) ? rec.current : null,
217
- draft: isRecordOrNull(rec.draft) ? rec.draft : null
217
+ draft: isRecordOrNull(rec.draft) ? rec.draft : null,
218
+ approvalType: typeof rec.approvalType === "string" ? rec.approvalType : null,
219
+ rejectedMessage: typeof rec.rejectedMessage === "string" ? rec.rejectedMessage : null
218
220
  };
219
221
  }
220
222
  function isRecordOrNull(v) {
221
223
  return v === null || typeof v === "object" && !Array.isArray(v);
222
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
+ }
223
364
  async function createMiniApp(workspaceId, payload, cookies, opts = {}) {
224
365
  return normalizeCreateResult(await requestConsoleApi({
225
- url: `${BASE$2}/workspaces/${workspaceId}/mini-app/review`,
366
+ url: `${BASE$3}/workspaces/${workspaceId}/mini-app/review`,
226
367
  method: "POST",
227
368
  cookies,
228
369
  body: payload,
@@ -255,7 +396,7 @@ function normalizeCreateResult(raw) {
255
396
  * the field name is actually `file` — if so, swap it in one place here.
256
397
  */
257
398
  async function uploadMiniAppResource(params, opts = {}) {
258
- const url = new URL(`${BASE$2}/resource/${params.workspaceId}/upload`);
399
+ const url = new URL(`${BASE$3}/resource/${params.workspaceId}/upload`);
259
400
  url.searchParams.set("validWidth", String(params.validWidth));
260
401
  url.searchParams.set("validHeight", String(params.validHeight));
261
402
  const form = new FormData();
@@ -505,7 +646,7 @@ async function emitFailureFromError(json, err) {
505
646
  emitApiError(json, err.message);
506
647
  return exitAfterFlush(ExitCode.ApiError);
507
648
  }
508
- function parsePositiveInt(raw) {
649
+ function parsePositiveInt$1(raw) {
509
650
  if (!/^[1-9]\d*$/.test(raw)) return null;
510
651
  const n = Number.parseInt(raw, 10);
511
652
  return Number.isSafeInteger(n) ? n : null;
@@ -533,7 +674,7 @@ async function resolveWorkspaceContext(args) {
533
674
  let workspaceId;
534
675
  if (args.workspace) {
535
676
  const raw = String(args.workspace);
536
- const parsed = parsePositiveInt(raw);
677
+ const parsed = parsePositiveInt$1(raw);
537
678
  if (parsed === null) {
538
679
  const message = `--workspace must be a positive integer (got ${raw})`;
539
680
  if (args.json) emitJson({
@@ -561,6 +702,21 @@ async function resolveWorkspaceContext(args) {
561
702
  workspaceId
562
703
  };
563
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
+ }
564
720
  //#endregion
565
721
  //#region src/config/app-manifest.ts
566
722
  var ManifestError = class extends Error {
@@ -1044,7 +1200,7 @@ function reviewStateFor(entry) {
1044
1200
  const raw = entry.reviewState ?? entry.status;
1045
1201
  return typeof raw === "string" ? raw : void 0;
1046
1202
  }
1047
- const lsCommand$3 = defineCommand({
1203
+ const lsCommand$4 = defineCommand({
1048
1204
  meta: {
1049
1205
  name: "ls",
1050
1206
  description: "List mini-apps in the selected workspace."
@@ -1124,220 +1280,1085 @@ function parseAppId(raw) {
1124
1280
  if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) return null;
1125
1281
  return n;
1126
1282
  }
1127
- const appCommand = defineCommand({
1283
+ const showCommand$2 = defineCommand({
1128
1284
  meta: {
1129
- name: "app",
1130
- description: "Inspect mini-apps in a workspace."
1285
+ name: "show",
1286
+ description: "Show full details of a mini-app, including fields only visible in the draft view."
1131
1287
  },
1132
- subCommands: {
1133
- ls: lsCommand$3,
1134
- show: defineCommand({
1135
- meta: {
1136
- name: "show",
1137
- description: "Show full details of a mini-app, including fields only visible in the draft view."
1138
- },
1139
- args: {
1140
- id: {
1141
- type: "positional",
1142
- description: "Mini-app ID (the numeric `appId` from `app ls` or `app register`).",
1143
- required: true
1144
- },
1145
- workspace: {
1146
- type: "string",
1147
- description: "Workspace ID. Defaults to the selected workspace."
1148
- },
1149
- view: {
1150
- type: "string",
1151
- description: "Which view to render: `draft` (default), `current`, or `merged`.",
1152
- default: "draft"
1153
- },
1154
- json: {
1155
- type: "boolean",
1156
- description: "Emit machine-readable JSON to stdout.",
1157
- default: false
1158
- }
1159
- },
1160
- async run({ args }) {
1161
- const appId = parseAppId(args.id);
1162
- if (appId === null) {
1163
- if (args.json) emitJson({
1164
- ok: false,
1165
- reason: "invalid-id",
1166
- message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
1167
- });
1168
- else process.stderr.write(`app show: invalid id ${JSON.stringify(args.id)}\n`);
1169
- return exitAfterFlush(ExitCode.Usage);
1170
- }
1171
- const view = args.view;
1172
- if (view !== "draft" && view !== "current" && view !== "merged") {
1173
- if (args.json) emitJson({
1174
- ok: false,
1175
- reason: "invalid-config",
1176
- field: "view",
1177
- message: `--view must be one of draft|current|merged (got ${JSON.stringify(view)})`
1178
- });
1179
- else process.stderr.write(`app show: invalid --view ${JSON.stringify(view)}\n`);
1180
- return exitAfterFlush(ExitCode.Usage);
1181
- }
1182
- const ctx = await resolveWorkspaceContext(args);
1183
- if (!ctx) return;
1184
- const { session, workspaceId } = ctx;
1185
- try {
1186
- const envelope = await fetchMiniAppWithDraft(workspaceId, appId, session.cookies);
1187
- const miniApp = pickMiniAppView(envelope, view);
1188
- if (args.json) {
1189
- emitJson({
1190
- ok: true,
1191
- workspaceId,
1192
- appId,
1193
- view,
1194
- miniApp
1195
- });
1196
- return exitAfterFlush(ExitCode.Ok);
1197
- }
1198
- if (miniApp === null) {
1199
- if (view === "current" && envelope.draft !== null) process.stdout.write(`App ${appId} has no \`current\` view yet (not reviewed). Try --view draft.\n`);
1200
- else process.stdout.write(`App ${appId} has no data for view=${view}.\n`);
1201
- return exitAfterFlush(ExitCode.Ok);
1202
- }
1203
- const pick = (k) => {
1204
- const v = miniApp[k];
1205
- return v === null || v === void 0 ? "-" : String(v);
1206
- };
1207
- const images = Array.isArray(miniApp.images) ? miniApp.images : [];
1208
- const impression = miniApp.impression !== null && typeof miniApp.impression === "object" ? miniApp.impression : {};
1209
- const keywords = Array.isArray(impression.keywordList) ? impression.keywordList : [];
1210
- const categoryPaths = Array.isArray(impression.categoryPaths) ? impression.categoryPaths : [];
1211
- process.stdout.write(`# App ${appId} (view=${view})\n\n`);
1212
- process.stdout.write(`Name (ko) ${pick("title")}\n`);
1213
- process.stdout.write(`Name (en) ${pick("titleEn")}\n`);
1214
- process.stdout.write(`App slug ${pick("appName")}\n`);
1215
- process.stdout.write(`Status ${pick("status")}\n`);
1216
- process.stdout.write(`Home page ${pick("homePageUri")}\n`);
1217
- process.stdout.write(`CS email ${pick("csEmail")}\n`);
1218
- process.stdout.write(`Logo ${pick("iconUri")}\n`);
1219
- process.stdout.write(`Subtitle ${pick("description")}\n`);
1220
- const detail = typeof miniApp.detailDescription === "string" ? `${[...miniApp.detailDescription].length} chars` : "-";
1221
- process.stdout.write(`Detail desc ${detail}\n`);
1222
- process.stdout.write(`Images ${images.length}\n`);
1223
- process.stdout.write(`Keywords ${keywords.length} (${keywords.join(", ")})\n`);
1224
- const firstPath = categoryPaths[0];
1225
- if (firstPath && typeof firstPath === "object") {
1226
- const fp = firstPath;
1227
- const parts = [];
1228
- for (const key of [
1229
- "group",
1230
- "category",
1231
- "subCategory"
1232
- ]) {
1233
- const node = fp[key];
1234
- if (node !== null && typeof node === "object") {
1235
- const nm = node.name;
1236
- if (typeof nm === "string") parts.push(nm);
1237
- }
1238
- }
1239
- process.stdout.write(`Category ${parts.join(" > ") || "-"}\n`);
1240
- } else process.stdout.write(`Category -\n`);
1241
- return exitAfterFlush(ExitCode.Ok);
1242
- } catch (err) {
1243
- return emitFailureFromError(args.json, err);
1244
- }
1245
- }
1246
- }),
1247
- register: defineCommand({
1248
- meta: {
1249
- name: "register",
1250
- description: "Register a mini-app in the selected workspace from a YAML/JSON manifest. Uploads logo/thumbnail/screenshots, then submits the create payload."
1251
- },
1252
- args: {
1253
- workspace: {
1254
- type: "string",
1255
- description: "Workspace ID. Defaults to the selected workspace (`aitcc workspace use`)."
1256
- },
1257
- config: {
1258
- type: "string",
1259
- description: "Path to the app manifest. Defaults to `./aitcc.app.yaml`, then `./aitcc.app.json`."
1260
- },
1261
- "dry-run": {
1262
- type: "boolean",
1263
- description: "Validate manifest + images and print the inferred submit payload; no uploads.",
1264
- default: false
1265
- },
1266
- "accept-terms": {
1267
- type: "boolean",
1268
- description: "Attest to the required console legal-agreement checkboxes (see VALIDATION-RULES.md). Required for real submits.",
1269
- default: false
1270
- },
1271
- json: {
1272
- type: "boolean",
1273
- description: "Emit machine-readable JSON to stdout.",
1274
- default: false
1275
- }
1276
- },
1277
- async run({ args }) {
1278
- await runRegister({
1279
- json: args.json,
1280
- dryRun: args["dry-run"],
1281
- acceptTerms: args["accept-terms"],
1282
- ...args.workspace !== void 0 ? { workspace: args.workspace } : {},
1283
- ...args.config !== void 0 ? { config: args.config } : {}
1288
+ args: {
1289
+ id: {
1290
+ type: "positional",
1291
+ description: "Mini-app ID (the numeric `appId` from `app ls` or `app register`).",
1292
+ required: true
1293
+ },
1294
+ workspace: {
1295
+ type: "string",
1296
+ description: "Workspace ID. Defaults to the selected workspace."
1297
+ },
1298
+ view: {
1299
+ type: "string",
1300
+ description: "Which view to render: `draft` (default), `current`, or `merged`.",
1301
+ default: "draft"
1302
+ },
1303
+ json: {
1304
+ type: "boolean",
1305
+ description: "Emit machine-readable JSON to stdout.",
1306
+ default: false
1307
+ }
1308
+ },
1309
+ async run({ args }) {
1310
+ const appId = parseAppId(args.id);
1311
+ if (appId === null) {
1312
+ if (args.json) emitJson({
1313
+ ok: false,
1314
+ reason: "invalid-id",
1315
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
1316
+ });
1317
+ else process.stderr.write(`app show: invalid id ${JSON.stringify(args.id)}\n`);
1318
+ return exitAfterFlush(ExitCode.Usage);
1319
+ }
1320
+ const view = args.view;
1321
+ if (view !== "draft" && view !== "current" && view !== "merged") {
1322
+ if (args.json) emitJson({
1323
+ ok: false,
1324
+ reason: "invalid-config",
1325
+ field: "view",
1326
+ message: `--view must be one of draft|current|merged (got ${JSON.stringify(view)})`
1327
+ });
1328
+ else process.stderr.write(`app show: invalid --view ${JSON.stringify(view)}\n`);
1329
+ return exitAfterFlush(ExitCode.Usage);
1330
+ }
1331
+ const ctx = await resolveWorkspaceContext(args);
1332
+ if (!ctx) return;
1333
+ const { session, workspaceId } = ctx;
1334
+ try {
1335
+ const envelope = await fetchMiniAppWithDraft(workspaceId, appId, session.cookies);
1336
+ const miniApp = pickMiniAppView(envelope, view);
1337
+ if (args.json) {
1338
+ emitJson({
1339
+ ok: true,
1340
+ workspaceId,
1341
+ appId,
1342
+ view,
1343
+ miniApp
1284
1344
  });
1345
+ return exitAfterFlush(ExitCode.Ok);
1285
1346
  }
1286
- })
1347
+ if (miniApp === null) {
1348
+ if (view === "current" && envelope.draft !== null) process.stdout.write(`App ${appId} has no \`current\` view yet (not reviewed). Try --view draft.\n`);
1349
+ else process.stdout.write(`App ${appId} has no data for view=${view}.\n`);
1350
+ return exitAfterFlush(ExitCode.Ok);
1351
+ }
1352
+ const pick = (k) => {
1353
+ const v = miniApp[k];
1354
+ return v === null || v === void 0 ? "-" : String(v);
1355
+ };
1356
+ const images = Array.isArray(miniApp.images) ? miniApp.images : [];
1357
+ const impression = miniApp.impression !== null && typeof miniApp.impression === "object" ? miniApp.impression : {};
1358
+ const keywords = Array.isArray(impression.keywordList) ? impression.keywordList : [];
1359
+ const categoryPaths = Array.isArray(impression.categoryPaths) ? impression.categoryPaths : [];
1360
+ process.stdout.write(`# App ${appId} (view=${view})\n\n`);
1361
+ process.stdout.write(`Name (ko) ${pick("title")}\n`);
1362
+ process.stdout.write(`Name (en) ${pick("titleEn")}\n`);
1363
+ process.stdout.write(`App slug ${pick("appName")}\n`);
1364
+ process.stdout.write(`Status ${pick("status")}\n`);
1365
+ process.stdout.write(`Home page ${pick("homePageUri")}\n`);
1366
+ process.stdout.write(`CS email ${pick("csEmail")}\n`);
1367
+ process.stdout.write(`Logo ${pick("iconUri")}\n`);
1368
+ process.stdout.write(`Subtitle ${pick("description")}\n`);
1369
+ const detail = typeof miniApp.detailDescription === "string" ? `${[...miniApp.detailDescription].length} chars` : "-";
1370
+ process.stdout.write(`Detail desc ${detail}\n`);
1371
+ process.stdout.write(`Images ${images.length}\n`);
1372
+ process.stdout.write(`Keywords ${keywords.length} (${keywords.join(", ")})\n`);
1373
+ const firstPath = categoryPaths[0];
1374
+ if (firstPath && typeof firstPath === "object") {
1375
+ const fp = firstPath;
1376
+ const parts = [];
1377
+ for (const key of [
1378
+ "group",
1379
+ "category",
1380
+ "subCategory"
1381
+ ]) {
1382
+ const node = fp[key];
1383
+ if (node !== null && typeof node === "object") {
1384
+ const nm = node.name;
1385
+ if (typeof nm === "string") parts.push(nm);
1386
+ }
1387
+ }
1388
+ process.stdout.write(`Category ${parts.join(" > ") || "-"}\n`);
1389
+ } else process.stdout.write(`Category -\n`);
1390
+ return exitAfterFlush(ExitCode.Ok);
1391
+ } catch (err) {
1392
+ return emitFailureFromError(args.json, err);
1393
+ }
1287
1394
  }
1288
1395
  });
1289
- //#endregion
1290
- //#region src/api/api-keys.ts
1291
- const BASE$1 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
1292
- async function fetchApiKeys(workspaceId, cookies, opts = {}) {
1293
- const raw = await requestConsoleApi({
1294
- url: `${BASE$1}/workspaces/${workspaceId}/api-keys`,
1295
- cookies,
1296
- ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
1297
- });
1298
- if (!Array.isArray(raw)) throw new Error(`Unexpected api-keys shape for workspace=${workspaceId}: not an array`);
1299
- return raw.map((entry, index) => normalizeKey(entry, workspaceId, index));
1300
- }
1301
- function normalizeKey(raw, workspaceId, index) {
1302
- if (raw === null || typeof raw !== "object") throw new Error(`Unexpected api-key entry at index ${index} for workspace=${workspaceId}: not an object`);
1303
- const rec = raw;
1304
- const rawId = rec.id ?? rec.apiKeyId ?? rec.keyId;
1305
- if (typeof rawId !== "string" && typeof rawId !== "number") throw new Error(`Unexpected api-key entry at index ${index} for workspace=${workspaceId}: missing id`);
1306
- const rawName = rec.name ?? rec.apiKeyName ?? rec.keyName ?? rec.description;
1307
- const name = typeof rawName === "string" ? rawName : void 0;
1308
- const { id: _id, apiKeyId: _aid, keyId: _kid, name: _n, apiKeyName: _an, keyName: _kn, description: _d, ...extra } = rec;
1396
+ function deriveReviewState(env) {
1397
+ const hasCurrent = env.current !== null;
1398
+ const hasDraft = env.draft !== null;
1399
+ const approvalType = env.approvalType;
1400
+ const rejectedMessage = env.rejectedMessage;
1401
+ let state;
1402
+ if (approvalType === null) state = "not-submitted";
1403
+ else if (rejectedMessage !== null) state = "rejected";
1404
+ else if (!hasCurrent) state = "under-review";
1405
+ else if (hasDraft) state = "approved-with-edits";
1406
+ else state = "approved";
1407
+ if (approvalType !== null && approvalType !== "REVIEW" && state === "under-review") state = "unknown";
1309
1408
  return {
1310
- id: rawId,
1311
- name,
1312
- extra
1409
+ state,
1410
+ approvalType,
1411
+ rejectedMessage,
1412
+ hasCurrent,
1413
+ hasDraft
1313
1414
  };
1314
1415
  }
1315
- const keysCommand = defineCommand({
1416
+ const POLL_MIN_INTERVAL_SEC = 30;
1417
+ const POLL_MAX_INTERVAL_SEC = 3600;
1418
+ const statusCommand = defineCommand({
1316
1419
  meta: {
1317
- name: "keys",
1318
- description: "Inspect console API keys used for deploy automation."
1420
+ name: "status",
1421
+ description: "Show the derived review state of a mini-app (under-review / rejected / approved)."
1319
1422
  },
1320
- subCommands: { ls: defineCommand({
1321
- meta: {
1322
- name: "ls",
1323
- description: "List console API keys in the selected workspace."
1423
+ args: {
1424
+ id: {
1425
+ type: "positional",
1426
+ description: "Mini-app ID.",
1427
+ required: true
1324
1428
  },
1325
- args: {
1326
- workspace: {
1327
- type: "string",
1328
- description: "Workspace ID. Defaults to the selected workspace (`aitcc workspace use`)."
1329
- },
1330
- json: {
1331
- type: "boolean",
1332
- description: "Emit machine-readable JSON to stdout.",
1333
- default: false
1334
- }
1429
+ workspace: {
1430
+ type: "string",
1431
+ description: "Workspace ID. Defaults to the selected workspace."
1335
1432
  },
1336
- async run({ args }) {
1337
- const ctx = await resolveWorkspaceContext(args);
1338
- if (!ctx) return;
1339
- const { session, workspaceId } = ctx;
1340
- try {
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);
1491
+ }
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));
1500
+ }
1501
+ } catch (err) {
1502
+ return emitFailureFromError(args.json, err);
1503
+ }
1504
+ }
1505
+ });
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 };
1512
+ }
1513
+ const ratingsCommand = defineCommand({
1514
+ meta: {
1515
+ name: "ratings",
1516
+ description: "List user ratings and reviews left for a mini-app."
1517
+ },
1518
+ args: {
1519
+ id: {
1520
+ type: "positional",
1521
+ description: "Mini-app ID.",
1522
+ required: true
1523
+ },
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.",
2354
+ default: false
2355
+ }
2356
+ },
2357
+ async run({ args }) {
2358
+ const ctx = await resolveWorkspaceContext(args);
2359
+ if (!ctx) return;
2360
+ const { session, workspaceId } = ctx;
2361
+ try {
1341
2362
  const keys = await fetchApiKeys(workspaceId, session.cookies);
1342
2363
  if (args.json) {
1343
2364
  emitJson({
@@ -2034,10 +3055,10 @@ const logoutCommand = defineCommand({
2034
3055
  });
2035
3056
  //#endregion
2036
3057
  //#region src/api/members.ts
2037
- 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";
2038
3059
  async function fetchWorkspaceMembers(workspaceId, cookies, opts = {}) {
2039
3060
  const raw = await requestConsoleApi({
2040
- url: `${BASE}/workspaces/${workspaceId}/members`,
3061
+ url: `${BASE$1}/workspaces/${workspaceId}/members`,
2041
3062
  cookies,
2042
3063
  ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
2043
3064
  });
@@ -2122,6 +3143,257 @@ const membersCommand = defineCommand({
2122
3143
  }
2123
3144
  }) }
2124
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
+ });
2125
3397
  //#endregion
2126
3398
  //#region src/github.ts
2127
3399
  const REPO_OWNER = "apps-in-toss-community";
@@ -2235,7 +3507,7 @@ function resolveVersion() {
2235
3507
  if (typeof injected === "string" && injected.length > 0) return injected;
2236
3508
  } catch {}
2237
3509
  try {
2238
- return "0.1.9";
3510
+ return "0.1.11";
2239
3511
  } catch {}
2240
3512
  return "0.0.0-dev";
2241
3513
  }
@@ -2692,7 +3964,7 @@ const workspaceCommand = defineCommand({
2692
3964
  },
2693
3965
  async run({ args }) {
2694
3966
  const raw = String(args.id);
2695
- const parsed = parsePositiveInt(raw);
3967
+ const parsed = parsePositiveInt$1(raw);
2696
3968
  if (parsed === null) {
2697
3969
  const message = `workspace id must be a positive integer (got ${raw})`;
2698
3970
  if (args.json) emitJson({
@@ -2760,7 +4032,7 @@ const workspaceCommand = defineCommand({
2760
4032
  let workspaceId;
2761
4033
  if (args.workspace) {
2762
4034
  const raw = String(args.workspace);
2763
- const parsed = parsePositiveInt(raw);
4035
+ const parsed = parsePositiveInt$1(raw);
2764
4036
  if (parsed === null) {
2765
4037
  const message = `--workspace must be a positive integer (got ${raw})`;
2766
4038
  if (args.json) emitJson({
@@ -2818,7 +4090,8 @@ runMain(defineCommand({
2818
4090
  workspace: workspaceCommand,
2819
4091
  app: appCommand,
2820
4092
  members: membersCommand,
2821
- keys: keysCommand
4093
+ keys: keysCommand,
4094
+ notices: noticesCommand
2822
4095
  }
2823
4096
  }));
2824
4097
  //#endregion