@ait-co/console-cli 0.1.10 → 0.1.12

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$4 = "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$4}/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$4}/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$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/with-draft`,
210
210
  cookies,
211
211
  ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
212
212
  });
@@ -222,9 +222,303 @@ 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$4}/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$4}/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$4}/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$4}/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$4}/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$4}/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 fetchSmartMessageCampaigns(params, cookies, opts = {}) {
355
+ const page = params.page ?? 0;
356
+ const size = params.size ?? 20;
357
+ const qs = new URLSearchParams();
358
+ qs.set("page", String(page));
359
+ qs.set("size", String(size));
360
+ const raw = await requestConsoleApi({
361
+ method: "POST",
362
+ url: `${BASE$4}/workspaces/${params.workspaceId}/mini-app/${params.miniAppId}/smart-message/campaigns?${qs.toString()}`,
363
+ body: {
364
+ sort: params.sort ?? [{
365
+ field: "regTs",
366
+ direction: "DESC"
367
+ }],
368
+ search: params.search ?? "",
369
+ filters: params.filters ?? {}
370
+ },
371
+ cookies,
372
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
373
+ });
374
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Unexpected smart-message campaigns shape for app=${params.miniAppId}`);
375
+ const data = raw;
376
+ const items = Array.isArray(data.items) ? data.items : [];
377
+ const rawPaging = data.paging;
378
+ const paging = {
379
+ pageNumber: typeof rawPaging?.pageNumber === "number" ? rawPaging.pageNumber : page,
380
+ pageSize: typeof rawPaging?.pageSize === "number" ? rawPaging.pageSize : size,
381
+ hasNext: Boolean(rawPaging?.hasNext),
382
+ totalCount: typeof rawPaging?.totalCount === "number" ? rawPaging.totalCount : items.length
383
+ };
384
+ return {
385
+ items: items.map((r) => r && typeof r === "object" ? r : {}),
386
+ paging
387
+ };
388
+ }
389
+ async function fetchAppEventCatalogs(params, cookies, opts = {}) {
390
+ const pageNumber = params.pageNumber ?? 0;
391
+ const pageSize = params.pageSize ?? 20;
392
+ const raw = await requestConsoleApi({
393
+ method: "POST",
394
+ url: `${BASE$4}/workspaces/${params.workspaceId}/mini-app/${params.miniAppId}/log/catalogs/search`,
395
+ body: {
396
+ isRefresh: params.refresh ?? false,
397
+ pageNumber,
398
+ pageSize,
399
+ search: params.search ?? ""
400
+ },
401
+ cookies,
402
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
403
+ });
404
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Unexpected event-catalogs shape for app=${params.miniAppId}`);
405
+ const data = raw;
406
+ const results = Array.isArray(data.results) ? data.results : [];
407
+ const rawPaging = data.paging;
408
+ const paging = {
409
+ pageNumber: typeof rawPaging?.pageNumber === "number" ? rawPaging.pageNumber : pageNumber,
410
+ pageSize: typeof rawPaging?.pageSize === "number" ? rawPaging.pageSize : pageSize,
411
+ hasNext: Boolean(rawPaging?.hasNext),
412
+ totalCount: typeof rawPaging?.totalCount === "number" ? rawPaging.totalCount : results.length,
413
+ totalPages: typeof rawPaging?.totalPages === "number" ? rawPaging.totalPages : 0
414
+ };
415
+ return {
416
+ results: results.map((r) => r && typeof r === "object" ? r : {}),
417
+ cacheTime: typeof data.cacheTime === "string" ? data.cacheTime : void 0,
418
+ paging
419
+ };
420
+ }
421
+ const TEMPLATE_CONTENT_REACH_TYPES = ["FUNCTIONAL", "MARKETING"];
422
+ async function fetchAppTemplates(params, cookies, opts = {}) {
423
+ const page = params.page ?? 0;
424
+ const size = params.size ?? 20;
425
+ const qs = new URLSearchParams();
426
+ qs.set("page", String(page));
427
+ qs.set("size", String(size));
428
+ if (params.contentReachType) qs.set("contentReachType", params.contentReachType);
429
+ if (params.isSmartMessage !== void 0) qs.set("isSmartMessage", String(params.isSmartMessage));
430
+ const raw = await requestConsoleApi({
431
+ url: `${BASE$4}/workspaces/${params.workspaceId}/mini-app/${params.miniAppId}/templates/search?${qs.toString()}`,
432
+ cookies,
433
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
434
+ });
435
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Unexpected templates shape for app=${params.miniAppId}`);
436
+ const data = raw;
437
+ const list = Array.isArray(data.groupSendContextSimpleView) ? data.groupSendContextSimpleView : [];
438
+ const pageMeta = data.page;
439
+ return {
440
+ templates: list.map((r) => r && typeof r === "object" ? r : {}),
441
+ totalPageCount: typeof pageMeta?.totalPageCount === "number" ? pageMeta.totalPageCount : 0
442
+ };
443
+ }
444
+ async function fetchImpressionCategoryList(cookies, opts = {}) {
445
+ const raw = await requestConsoleApi({
446
+ url: `${BASE$4}/impression/category-list`,
447
+ cookies,
448
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
449
+ });
450
+ if (!Array.isArray(raw)) throw new Error("Unexpected impression/category-list shape: not an array");
451
+ return raw.map((entry, i) => {
452
+ if (!entry || typeof entry !== "object") throw new Error(`Unexpected category-list entry at index ${i}`);
453
+ const e = entry;
454
+ const group = e.categoryGroup;
455
+ const list = Array.isArray(e.categoryList) ? e.categoryList : [];
456
+ return {
457
+ categoryGroup: {
458
+ id: typeof group?.id === "number" ? group.id : 0,
459
+ name: typeof group?.name === "string" ? group.name : "",
460
+ isSelectable: Boolean(group?.isSelectable)
461
+ },
462
+ categoryList: list.map((c) => {
463
+ if (!c || typeof c !== "object") return {
464
+ id: 0,
465
+ name: "",
466
+ isSelectable: false,
467
+ subCategoryList: []
468
+ };
469
+ const cr = c;
470
+ const subs = Array.isArray(cr.subCategoryList) ? cr.subCategoryList : [];
471
+ return {
472
+ id: typeof cr.id === "number" ? cr.id : 0,
473
+ name: typeof cr.name === "string" ? cr.name : "",
474
+ isSelectable: Boolean(cr.isSelectable),
475
+ subCategoryList: subs.map((s) => {
476
+ if (!s || typeof s !== "object") return {
477
+ id: 0,
478
+ name: "",
479
+ isSelectable: false
480
+ };
481
+ const sr = s;
482
+ return {
483
+ id: typeof sr.id === "number" ? sr.id : 0,
484
+ name: typeof sr.name === "string" ? sr.name : "",
485
+ isSelectable: Boolean(sr.isSelectable)
486
+ };
487
+ })
488
+ };
489
+ })
490
+ };
491
+ });
492
+ }
493
+ async function fetchAppServiceStatus(workspaceId, miniAppId, cookies, opts = {}) {
494
+ const raw = await requestConsoleApi({
495
+ url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/review-status`,
496
+ cookies,
497
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
498
+ });
499
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Unexpected app service-status shape for app=${miniAppId}`);
500
+ const data = raw;
501
+ const serviceStatus = data.serviceStatus;
502
+ if (typeof serviceStatus !== "string") throw new Error(`Unexpected app service-status shape for app=${miniAppId}: missing serviceStatus`);
503
+ return {
504
+ shutdownCandidateStatus: typeof data.shutdownCandidateStatus === "string" ? data.shutdownCandidateStatus : null,
505
+ scheduledShutdownAt: typeof data.scheduledShutdownAt === "string" ? data.scheduledShutdownAt : null,
506
+ serviceStatus
507
+ };
508
+ }
509
+ async function fetchDeployedBundle(workspaceId, miniAppId, cookies, opts = {}) {
510
+ const raw = await requestConsoleApi({
511
+ url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/bundles/deployed`,
512
+ cookies,
513
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
514
+ });
515
+ if (raw === null) return null;
516
+ if (typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Unexpected deployed-bundle shape for app=${miniAppId}`);
517
+ return raw;
518
+ }
225
519
  async function createMiniApp(workspaceId, payload, cookies, opts = {}) {
226
520
  return normalizeCreateResult(await requestConsoleApi({
227
- url: `${BASE$2}/workspaces/${workspaceId}/mini-app/review`,
521
+ url: `${BASE$4}/workspaces/${workspaceId}/mini-app/review`,
228
522
  method: "POST",
229
523
  cookies,
230
524
  body: payload,
@@ -257,7 +551,7 @@ function normalizeCreateResult(raw) {
257
551
  * the field name is actually `file` — if so, swap it in one place here.
258
552
  */
259
553
  async function uploadMiniAppResource(params, opts = {}) {
260
- const url = new URL(`${BASE$2}/resource/${params.workspaceId}/upload`);
554
+ const url = new URL(`${BASE$4}/resource/${params.workspaceId}/upload`);
261
555
  url.searchParams.set("validWidth", String(params.validWidth));
262
556
  url.searchParams.set("validHeight", String(params.validHeight));
263
557
  const form = new FormData();
@@ -507,7 +801,7 @@ async function emitFailureFromError(json, err) {
507
801
  emitApiError(json, err.message);
508
802
  return exitAfterFlush(ExitCode.ApiError);
509
803
  }
510
- function parsePositiveInt(raw) {
804
+ function parsePositiveInt$1(raw) {
511
805
  if (!/^[1-9]\d*$/.test(raw)) return null;
512
806
  const n = Number.parseInt(raw, 10);
513
807
  return Number.isSafeInteger(n) ? n : null;
@@ -535,7 +829,7 @@ async function resolveWorkspaceContext(args) {
535
829
  let workspaceId;
536
830
  if (args.workspace) {
537
831
  const raw = String(args.workspace);
538
- const parsed = parsePositiveInt(raw);
832
+ const parsed = parsePositiveInt$1(raw);
539
833
  if (parsed === null) {
540
834
  const message = `--workspace must be a positive integer (got ${raw})`;
541
835
  if (args.json) emitJson({
@@ -563,6 +857,21 @@ async function resolveWorkspaceContext(args) {
563
857
  workspaceId
564
858
  };
565
859
  }
860
+ /**
861
+ * Session-only sibling of `resolveWorkspaceContext` for commands that
862
+ * don't need a workspace id (notices come from a shared Toss workspace,
863
+ * whoami is self-scoped). Same "exits on miss, returns null to force
864
+ * `if (!session) return`" pattern.
865
+ */
866
+ async function requireSession(json) {
867
+ const session = await readSession();
868
+ if (!session) {
869
+ emitNotAuthenticated(json);
870
+ await exitAfterFlush(ExitCode.NotAuthenticated);
871
+ return null;
872
+ }
873
+ return session;
874
+ }
566
875
  //#endregion
567
876
  //#region src/config/app-manifest.ts
568
877
  var ManifestError = class extends Error {
@@ -1046,7 +1355,7 @@ function reviewStateFor(entry) {
1046
1355
  const raw = entry.reviewState ?? entry.status;
1047
1356
  return typeof raw === "string" ? raw : void 0;
1048
1357
  }
1049
- const lsCommand$3 = defineCommand({
1358
+ const lsCommand$4 = defineCommand({
1050
1359
  meta: {
1051
1360
  name: "ls",
1052
1361
  description: "List mini-apps in the selected workspace."
@@ -1126,7 +1435,7 @@ function parseAppId(raw) {
1126
1435
  if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) return null;
1127
1436
  return n;
1128
1437
  }
1129
- const showCommand$1 = defineCommand({
1438
+ const showCommand$2 = defineCommand({
1130
1439
  meta: {
1131
1440
  name: "show",
1132
1441
  description: "Show full details of a mini-app, including fields only visible in the draft view."
@@ -1261,150 +1570,1390 @@ function deriveReviewState(env) {
1261
1570
  }
1262
1571
  const POLL_MIN_INTERVAL_SEC = 30;
1263
1572
  const POLL_MAX_INTERVAL_SEC = 3600;
1264
- const appCommand = defineCommand({
1573
+ const statusCommand = defineCommand({
1265
1574
  meta: {
1266
- name: "app",
1267
- description: "Inspect mini-apps in a workspace."
1575
+ name: "status",
1576
+ description: "Show the derived review state of a mini-app (under-review / rejected / approved)."
1268
1577
  },
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
- }
1578
+ args: {
1579
+ id: {
1580
+ type: "positional",
1581
+ description: "Mini-app ID.",
1582
+ required: true
1583
+ },
1584
+ workspace: {
1585
+ type: "string",
1586
+ description: "Workspace ID. Defaults to the selected workspace."
1587
+ },
1588
+ watch: {
1589
+ type: "boolean",
1590
+ description: "Poll until the review state flips off `under-review` (rejected or approved). Combine with `--interval <seconds>`.",
1591
+ default: false
1592
+ },
1593
+ interval: {
1594
+ type: "string",
1595
+ description: "Polling interval in seconds when --watch is set. Clamped to [30, 3600].",
1596
+ default: "60"
1597
+ },
1598
+ json: {
1599
+ type: "boolean",
1600
+ description: "Emit machine-readable JSON.",
1601
+ default: false
1602
+ }
1603
+ },
1604
+ async run({ args }) {
1605
+ const appId = parseAppId(args.id);
1606
+ if (appId === null) {
1607
+ if (args.json) emitJson({
1608
+ ok: false,
1609
+ reason: "invalid-id",
1610
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
1611
+ });
1612
+ else process.stderr.write(`app status: invalid id ${JSON.stringify(args.id)}\n`);
1613
+ return exitAfterFlush(ExitCode.Usage);
1614
+ }
1615
+ const intervalRaw = Number(args.interval);
1616
+ if (!Number.isFinite(intervalRaw) || intervalRaw <= 0) {
1617
+ if (args.json) emitJson({
1618
+ ok: false,
1619
+ reason: "invalid-config",
1620
+ field: "interval",
1621
+ message: `--interval must be a positive number (got ${JSON.stringify(args.interval)})`
1622
+ });
1623
+ else process.stderr.write(`app status: invalid --interval ${JSON.stringify(args.interval)}\n`);
1624
+ return exitAfterFlush(ExitCode.Usage);
1625
+ }
1626
+ const intervalSec = Math.max(POLL_MIN_INTERVAL_SEC, Math.min(POLL_MAX_INTERVAL_SEC, Math.floor(intervalRaw)));
1627
+ const ctx = await resolveWorkspaceContext(args);
1628
+ if (!ctx) return;
1629
+ const { session, workspaceId } = ctx;
1630
+ const emit = (status) => {
1631
+ if (args.json) emitJson({
1632
+ ok: true,
1633
+ workspaceId,
1634
+ appId,
1635
+ ...status
1636
+ });
1637
+ else process.stdout.write(`App ${appId} (ws ${workspaceId}): ${status.state}` + (status.rejectedMessage ? `\n reason: ${status.rejectedMessage}` : "") + "\n");
1638
+ };
1639
+ try {
1640
+ const once = async () => {
1641
+ return deriveReviewState(await fetchMiniAppWithDraft(workspaceId, appId, session.cookies));
1642
+ };
1643
+ if (!args.watch) {
1644
+ emit(await once());
1645
+ return exitAfterFlush(ExitCode.Ok);
1358
1646
  }
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
- });
1647
+ let lastState = null;
1648
+ while (true) {
1649
+ const status = await once();
1650
+ if (args.json) emit(status);
1651
+ else if (status.state !== lastState) emit(status);
1652
+ lastState = status.state;
1653
+ if (status.state !== "under-review") return exitAfterFlush(ExitCode.Ok);
1654
+ await new Promise((resolve) => setTimeout(resolve, intervalSec * 1e3));
1398
1655
  }
1399
- })
1400
- }
1401
- });
1656
+ } catch (err) {
1657
+ return emitFailureFromError(args.json, err);
1658
+ }
1659
+ }
1660
+ });
1661
+ const VALID_SORT_FIELDS = ["CREATED_AT", "SCORE"];
1662
+ const VALID_SORT_DIRECTIONS = ["ASC", "DESC"];
1663
+ function parseNonNegativeInt(raw, field) {
1664
+ const n = Number(raw);
1665
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 0) return { error: `--${field} must be a non-negative integer (got ${JSON.stringify(raw)})` };
1666
+ return { value: n };
1667
+ }
1668
+ const ratingsCommand = defineCommand({
1669
+ meta: {
1670
+ name: "ratings",
1671
+ description: "List user ratings and reviews left for a mini-app."
1672
+ },
1673
+ args: {
1674
+ id: {
1675
+ type: "positional",
1676
+ description: "Mini-app ID.",
1677
+ required: true
1678
+ },
1679
+ workspace: {
1680
+ type: "string",
1681
+ description: "Workspace ID. Defaults to the selected workspace."
1682
+ },
1683
+ page: {
1684
+ type: "string",
1685
+ description: "Page number (0-indexed).",
1686
+ default: "0"
1687
+ },
1688
+ size: {
1689
+ type: "string",
1690
+ description: "Page size.",
1691
+ default: "20"
1692
+ },
1693
+ "sort-field": {
1694
+ type: "string",
1695
+ description: "Sort field: CREATED_AT (default) or SCORE.",
1696
+ default: "CREATED_AT"
1697
+ },
1698
+ "sort-direction": {
1699
+ type: "string",
1700
+ description: "Sort direction: ASC or DESC (default).",
1701
+ default: "DESC"
1702
+ },
1703
+ json: {
1704
+ type: "boolean",
1705
+ description: "Emit machine-readable JSON.",
1706
+ default: false
1707
+ }
1708
+ },
1709
+ async run({ args }) {
1710
+ const appId = parseAppId(args.id);
1711
+ if (appId === null) {
1712
+ if (args.json) emitJson({
1713
+ ok: false,
1714
+ reason: "invalid-id",
1715
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
1716
+ });
1717
+ else process.stderr.write(`app ratings: invalid id ${JSON.stringify(args.id)}\n`);
1718
+ return exitAfterFlush(ExitCode.Usage);
1719
+ }
1720
+ const pageResult = parseNonNegativeInt(args.page, "page");
1721
+ if ("error" in pageResult) {
1722
+ if (args.json) emitJson({
1723
+ ok: false,
1724
+ reason: "invalid-config",
1725
+ field: "page",
1726
+ message: pageResult.error
1727
+ });
1728
+ else process.stderr.write(`app ratings: ${pageResult.error}\n`);
1729
+ return exitAfterFlush(ExitCode.Usage);
1730
+ }
1731
+ const sizeResult = parseNonNegativeInt(args.size, "size");
1732
+ if ("error" in sizeResult) {
1733
+ if (args.json) emitJson({
1734
+ ok: false,
1735
+ reason: "invalid-config",
1736
+ field: "size",
1737
+ message: sizeResult.error
1738
+ });
1739
+ else process.stderr.write(`app ratings: ${sizeResult.error}\n`);
1740
+ return exitAfterFlush(ExitCode.Usage);
1741
+ }
1742
+ if (sizeResult.value === 0) {
1743
+ if (args.json) emitJson({
1744
+ ok: false,
1745
+ reason: "invalid-config",
1746
+ field: "size",
1747
+ message: "--size must be at least 1"
1748
+ });
1749
+ else process.stderr.write("app ratings: --size must be at least 1\n");
1750
+ return exitAfterFlush(ExitCode.Usage);
1751
+ }
1752
+ const sortField = args["sort-field"];
1753
+ if (!VALID_SORT_FIELDS.includes(sortField)) {
1754
+ if (args.json) emitJson({
1755
+ ok: false,
1756
+ reason: "invalid-config",
1757
+ field: "sort-field",
1758
+ message: `--sort-field must be one of ${VALID_SORT_FIELDS.join("|")} (got ${JSON.stringify(sortField)})`
1759
+ });
1760
+ else process.stderr.write(`app ratings: invalid --sort-field ${JSON.stringify(sortField)}\n`);
1761
+ return exitAfterFlush(ExitCode.Usage);
1762
+ }
1763
+ const sortDirection = args["sort-direction"];
1764
+ if (!VALID_SORT_DIRECTIONS.includes(sortDirection)) {
1765
+ if (args.json) emitJson({
1766
+ ok: false,
1767
+ reason: "invalid-config",
1768
+ field: "sort-direction",
1769
+ message: `--sort-direction must be one of ${VALID_SORT_DIRECTIONS.join("|")} (got ${JSON.stringify(sortDirection)})`
1770
+ });
1771
+ else process.stderr.write(`app ratings: invalid --sort-direction ${JSON.stringify(sortDirection)}\n`);
1772
+ return exitAfterFlush(ExitCode.Usage);
1773
+ }
1774
+ const ctx = await resolveWorkspaceContext(args);
1775
+ if (!ctx) return;
1776
+ const { session, workspaceId } = ctx;
1777
+ try {
1778
+ const result = await fetchMiniAppRatings({
1779
+ workspaceId,
1780
+ miniAppId: appId,
1781
+ page: pageResult.value,
1782
+ size: sizeResult.value,
1783
+ sortField,
1784
+ sortDirection
1785
+ }, session.cookies);
1786
+ if (args.json) {
1787
+ emitJson({
1788
+ ok: true,
1789
+ workspaceId,
1790
+ appId,
1791
+ page: pageResult.value,
1792
+ size: sizeResult.value,
1793
+ sortField,
1794
+ sortDirection,
1795
+ averageRating: result.averageRating,
1796
+ totalReviewCount: result.totalReviewCount,
1797
+ paging: result.paging,
1798
+ ratings: result.ratings
1799
+ });
1800
+ return exitAfterFlush(ExitCode.Ok);
1801
+ }
1802
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): ${result.totalReviewCount} review(s), avg ${result.averageRating.toFixed(2)}\n`);
1803
+ if (result.ratings.length === 0) {
1804
+ process.stdout.write("No ratings on this page.\n");
1805
+ return exitAfterFlush(ExitCode.Ok);
1806
+ }
1807
+ for (const r of result.ratings) {
1808
+ const score = typeof r.score === "number" ? r.score : r.rating ?? "-";
1809
+ const author = typeof r.nickname === "string" ? r.nickname : typeof r.userName === "string" ? r.userName : "(anon)";
1810
+ const text = typeof r.content === "string" ? r.content : typeof r.reviewContent === "string" ? r.reviewContent : "";
1811
+ const createdAt = typeof r.createdAt === "string" ? r.createdAt : typeof r.reviewedAt === "string" ? r.reviewedAt : "";
1812
+ process.stdout.write(`${score}\t${createdAt}\t${author}\t${text}\n`);
1813
+ }
1814
+ if (result.paging.hasNext) process.stdout.write(`(more: --page ${pageResult.value + 1} for next ${sizeResult.value})\n`);
1815
+ return exitAfterFlush(ExitCode.Ok);
1816
+ } catch (err) {
1817
+ return emitFailureFromError(args.json, err);
1818
+ }
1819
+ }
1820
+ });
1821
+ const reportsCommand = defineCommand({
1822
+ meta: {
1823
+ name: "reports",
1824
+ description: "List user-submitted reports (신고 내역) for a mini-app."
1825
+ },
1826
+ args: {
1827
+ id: {
1828
+ type: "positional",
1829
+ description: "Mini-app ID.",
1830
+ required: true
1831
+ },
1832
+ workspace: {
1833
+ type: "string",
1834
+ description: "Workspace ID. Defaults to the selected workspace."
1835
+ },
1836
+ "page-size": {
1837
+ type: "string",
1838
+ description: "Page size (default 20).",
1839
+ default: "20"
1840
+ },
1841
+ cursor: {
1842
+ type: "string",
1843
+ description: "Opaque cursor from a previous response `nextCursor`."
1844
+ },
1845
+ json: {
1846
+ type: "boolean",
1847
+ description: "Emit machine-readable JSON.",
1848
+ default: false
1849
+ }
1850
+ },
1851
+ async run({ args }) {
1852
+ const appId = parseAppId(args.id);
1853
+ if (appId === null) {
1854
+ if (args.json) emitJson({
1855
+ ok: false,
1856
+ reason: "invalid-id",
1857
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
1858
+ });
1859
+ else process.stderr.write(`app reports: invalid id ${JSON.stringify(args.id)}\n`);
1860
+ return exitAfterFlush(ExitCode.Usage);
1861
+ }
1862
+ const pageSizeResult = parseNonNegativeInt(args["page-size"], "page-size");
1863
+ if ("error" in pageSizeResult) {
1864
+ if (args.json) emitJson({
1865
+ ok: false,
1866
+ reason: "invalid-config",
1867
+ field: "page-size",
1868
+ message: pageSizeResult.error
1869
+ });
1870
+ else process.stderr.write(`app reports: ${pageSizeResult.error}\n`);
1871
+ return exitAfterFlush(ExitCode.Usage);
1872
+ }
1873
+ if (pageSizeResult.value === 0) {
1874
+ if (args.json) emitJson({
1875
+ ok: false,
1876
+ reason: "invalid-config",
1877
+ field: "page-size",
1878
+ message: "--page-size must be at least 1"
1879
+ });
1880
+ else process.stderr.write("app reports: --page-size must be at least 1\n");
1881
+ return exitAfterFlush(ExitCode.Usage);
1882
+ }
1883
+ const ctx = await resolveWorkspaceContext(args);
1884
+ if (!ctx) return;
1885
+ const { session, workspaceId } = ctx;
1886
+ try {
1887
+ const result = await fetchUserReports({
1888
+ workspaceId,
1889
+ miniAppId: appId,
1890
+ pageSize: pageSizeResult.value,
1891
+ ...typeof args.cursor === "string" && args.cursor.length > 0 ? { cursor: args.cursor } : {}
1892
+ }, session.cookies);
1893
+ if (args.json) {
1894
+ emitJson({
1895
+ ok: true,
1896
+ workspaceId,
1897
+ appId,
1898
+ pageSize: pageSizeResult.value,
1899
+ cursor: args.cursor ?? null,
1900
+ nextCursor: result.nextCursor,
1901
+ hasMore: result.hasMore,
1902
+ reports: result.reports
1903
+ });
1904
+ return exitAfterFlush(ExitCode.Ok);
1905
+ }
1906
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): ${result.reports.length} report(s) on this page\n`);
1907
+ if (result.reports.length === 0) {
1908
+ process.stdout.write("No reports.\n");
1909
+ return exitAfterFlush(ExitCode.Ok);
1910
+ }
1911
+ for (const r of result.reports) {
1912
+ const id = typeof r.id === "string" || typeof r.id === "number" ? r.id : "-";
1913
+ const reason = typeof r.reason === "string" ? r.reason : r.reportType ?? "-";
1914
+ const text = typeof r.content === "string" ? r.content : typeof r.detail === "string" ? r.detail : "";
1915
+ const createdAt = typeof r.createdAt === "string" ? r.createdAt : typeof r.reportedAt === "string" ? r.reportedAt : "";
1916
+ process.stdout.write(`${id}\t${createdAt}\t${reason}\t${text}\n`);
1917
+ }
1918
+ if (result.hasMore && result.nextCursor) process.stdout.write(`(more: --cursor ${result.nextCursor})\n`);
1919
+ return exitAfterFlush(ExitCode.Ok);
1920
+ } catch (err) {
1921
+ return emitFailureFromError(args.json, err);
1922
+ }
1923
+ }
1924
+ });
1925
+ const bundlesCommand = defineCommand({
1926
+ meta: {
1927
+ name: "bundles",
1928
+ description: "Inspect upload bundles for a mini-app."
1929
+ },
1930
+ subCommands: {
1931
+ ls: defineCommand({
1932
+ meta: {
1933
+ name: "ls",
1934
+ description: "List upload bundles for a mini-app (page-based pagination)."
1935
+ },
1936
+ args: {
1937
+ id: {
1938
+ type: "positional",
1939
+ description: "Mini-app ID.",
1940
+ required: true
1941
+ },
1942
+ workspace: {
1943
+ type: "string",
1944
+ description: "Workspace ID. Defaults to the selected workspace."
1945
+ },
1946
+ page: {
1947
+ type: "string",
1948
+ description: "Page number (0-indexed).",
1949
+ default: "0"
1950
+ },
1951
+ tested: {
1952
+ type: "string",
1953
+ description: "Filter: `true` to show only tested bundles (or `false`)."
1954
+ },
1955
+ "deploy-status": {
1956
+ type: "string",
1957
+ description: "Filter by deploy status (e.g. DEPLOYED)."
1958
+ },
1959
+ json: {
1960
+ type: "boolean",
1961
+ description: "Emit machine-readable JSON.",
1962
+ default: false
1963
+ }
1964
+ },
1965
+ async run({ args }) {
1966
+ const appId = parseAppId(args.id);
1967
+ if (appId === null) {
1968
+ if (args.json) emitJson({
1969
+ ok: false,
1970
+ reason: "invalid-id",
1971
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
1972
+ });
1973
+ else process.stderr.write(`app bundles ls: invalid id ${JSON.stringify(args.id)}\n`);
1974
+ return exitAfterFlush(ExitCode.Usage);
1975
+ }
1976
+ const pageResult = parseNonNegativeInt(args.page, "page");
1977
+ if ("error" in pageResult) {
1978
+ if (args.json) emitJson({
1979
+ ok: false,
1980
+ reason: "invalid-config",
1981
+ field: "page",
1982
+ message: pageResult.error
1983
+ });
1984
+ else process.stderr.write(`app bundles ls: ${pageResult.error}\n`);
1985
+ return exitAfterFlush(ExitCode.Usage);
1986
+ }
1987
+ let tested;
1988
+ if (args.tested !== void 0) if (args.tested === "true") tested = true;
1989
+ else if (args.tested === "false") tested = false;
1990
+ else {
1991
+ if (args.json) emitJson({
1992
+ ok: false,
1993
+ reason: "invalid-config",
1994
+ field: "tested",
1995
+ message: `--tested must be "true" or "false" (got ${JSON.stringify(args.tested)})`
1996
+ });
1997
+ else process.stderr.write(`app bundles ls: invalid --tested ${JSON.stringify(args.tested)}\n`);
1998
+ return exitAfterFlush(ExitCode.Usage);
1999
+ }
2000
+ const ctx = await resolveWorkspaceContext(args);
2001
+ if (!ctx) return;
2002
+ const { session, workspaceId } = ctx;
2003
+ try {
2004
+ const result = await fetchBundles({
2005
+ workspaceId,
2006
+ miniAppId: appId,
2007
+ page: pageResult.value,
2008
+ ...tested !== void 0 ? { tested } : {},
2009
+ ...typeof args["deploy-status"] === "string" && args["deploy-status"].length > 0 ? { deployStatus: args["deploy-status"] } : {}
2010
+ }, session.cookies);
2011
+ if (args.json) {
2012
+ emitJson({
2013
+ ok: true,
2014
+ workspaceId,
2015
+ appId,
2016
+ page: pageResult.value,
2017
+ totalPage: result.totalPage,
2018
+ currentPage: result.currentPage,
2019
+ bundles: result.contents
2020
+ });
2021
+ return exitAfterFlush(ExitCode.Ok);
2022
+ }
2023
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): page ${result.currentPage + 1}/${Math.max(result.totalPage, 1)}, ${result.contents.length} bundle(s)\n`);
2024
+ if (result.contents.length === 0) {
2025
+ process.stdout.write("No bundles on this page.\n");
2026
+ return exitAfterFlush(ExitCode.Ok);
2027
+ }
2028
+ for (const b of result.contents) {
2029
+ const id = typeof b.id === "string" || typeof b.id === "number" ? b.id : "-";
2030
+ const version = typeof b.version === "string" ? b.version : "-";
2031
+ const status = typeof b.deployStatus === "string" ? b.deployStatus : "-";
2032
+ const createdAt = typeof b.createdAt === "string" ? b.createdAt : "";
2033
+ process.stdout.write(`${id}\t${version}\t${status}\t${createdAt}\n`);
2034
+ }
2035
+ if (result.currentPage + 1 < result.totalPage) process.stdout.write(`(more: --page ${result.currentPage + 1})\n`);
2036
+ return exitAfterFlush(ExitCode.Ok);
2037
+ } catch (err) {
2038
+ return emitFailureFromError(args.json, err);
2039
+ }
2040
+ }
2041
+ }),
2042
+ deployed: defineCommand({
2043
+ meta: {
2044
+ name: "deployed",
2045
+ description: "Show the currently deployed bundle for a mini-app (or null if none)."
2046
+ },
2047
+ args: {
2048
+ id: {
2049
+ type: "positional",
2050
+ description: "Mini-app ID.",
2051
+ required: true
2052
+ },
2053
+ workspace: {
2054
+ type: "string",
2055
+ description: "Workspace ID. Defaults to the selected workspace."
2056
+ },
2057
+ json: {
2058
+ type: "boolean",
2059
+ description: "Emit machine-readable JSON.",
2060
+ default: false
2061
+ }
2062
+ },
2063
+ async run({ args }) {
2064
+ const appId = parseAppId(args.id);
2065
+ if (appId === null) {
2066
+ if (args.json) emitJson({
2067
+ ok: false,
2068
+ reason: "invalid-id",
2069
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
2070
+ });
2071
+ else process.stderr.write(`app bundles deployed: invalid id ${JSON.stringify(args.id)}\n`);
2072
+ return exitAfterFlush(ExitCode.Usage);
2073
+ }
2074
+ const ctx = await resolveWorkspaceContext(args);
2075
+ if (!ctx) return;
2076
+ const { session, workspaceId } = ctx;
2077
+ try {
2078
+ const bundle = await fetchDeployedBundle(workspaceId, appId, session.cookies);
2079
+ if (args.json) {
2080
+ emitJson({
2081
+ ok: true,
2082
+ workspaceId,
2083
+ appId,
2084
+ bundle
2085
+ });
2086
+ return exitAfterFlush(ExitCode.Ok);
2087
+ }
2088
+ if (bundle === null) {
2089
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): no deployed bundle\n`);
2090
+ return exitAfterFlush(ExitCode.Ok);
2091
+ }
2092
+ const id = typeof bundle.id === "string" || typeof bundle.id === "number" ? bundle.id : "-";
2093
+ const version = typeof bundle.version === "string" ? bundle.version : "-";
2094
+ const status = typeof bundle.deployStatus === "string" ? bundle.deployStatus : "-";
2095
+ const deployedAt = typeof bundle.deployedAt === "string" ? bundle.deployedAt : "";
2096
+ process.stdout.write(`App ${appId} deployed bundle:\n`);
2097
+ process.stdout.write(` id ${id}\n`);
2098
+ process.stdout.write(` version ${version}\n`);
2099
+ process.stdout.write(` status ${status}\n`);
2100
+ process.stdout.write(` deployedAt ${deployedAt}\n`);
2101
+ return exitAfterFlush(ExitCode.Ok);
2102
+ } catch (err) {
2103
+ return emitFailureFromError(args.json, err);
2104
+ }
2105
+ }
2106
+ })
2107
+ }
2108
+ });
2109
+ const certsCommand = defineCommand({
2110
+ meta: {
2111
+ name: "certs",
2112
+ description: "Inspect mTLS certificates for a mini-app."
2113
+ },
2114
+ subCommands: { ls: defineCommand({
2115
+ meta: {
2116
+ name: "ls",
2117
+ description: "List mTLS certificates issued for a mini-app."
2118
+ },
2119
+ args: {
2120
+ id: {
2121
+ type: "positional",
2122
+ description: "Mini-app ID.",
2123
+ required: true
2124
+ },
2125
+ workspace: {
2126
+ type: "string",
2127
+ description: "Workspace ID. Defaults to the selected workspace."
2128
+ },
2129
+ json: {
2130
+ type: "boolean",
2131
+ description: "Emit machine-readable JSON.",
2132
+ default: false
2133
+ }
2134
+ },
2135
+ async run({ args }) {
2136
+ const appId = parseAppId(args.id);
2137
+ if (appId === null) {
2138
+ if (args.json) emitJson({
2139
+ ok: false,
2140
+ reason: "invalid-id",
2141
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
2142
+ });
2143
+ else process.stderr.write(`app certs ls: invalid id ${JSON.stringify(args.id)}\n`);
2144
+ return exitAfterFlush(ExitCode.Usage);
2145
+ }
2146
+ const ctx = await resolveWorkspaceContext(args);
2147
+ if (!ctx) return;
2148
+ const { session, workspaceId } = ctx;
2149
+ try {
2150
+ const certs = await fetchCerts(workspaceId, appId, session.cookies);
2151
+ if (args.json) {
2152
+ emitJson({
2153
+ ok: true,
2154
+ workspaceId,
2155
+ appId,
2156
+ certs
2157
+ });
2158
+ return exitAfterFlush(ExitCode.Ok);
2159
+ }
2160
+ if (certs.length === 0) {
2161
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): no mTLS certs\n`);
2162
+ return exitAfterFlush(ExitCode.Ok);
2163
+ }
2164
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): ${certs.length} cert(s)\n`);
2165
+ for (const c of certs) {
2166
+ const id = typeof c.id === "string" || typeof c.id === "number" ? c.id : typeof c.certId === "string" || typeof c.certId === "number" ? c.certId : "-";
2167
+ const cn = typeof c.commonName === "string" ? c.commonName : "-";
2168
+ const createdAt = typeof c.createdAt === "string" ? c.createdAt : "";
2169
+ const expiresAt = typeof c.expiresAt === "string" ? c.expiresAt : typeof c.validUntil === "string" ? c.validUntil : "";
2170
+ process.stdout.write(`${id}\t${cn}\t${createdAt}\t${expiresAt}\n`);
2171
+ }
2172
+ return exitAfterFlush(ExitCode.Ok);
2173
+ } catch (err) {
2174
+ return emitFailureFromError(args.json, err);
2175
+ }
2176
+ }
2177
+ }) }
2178
+ });
2179
+ const VALID_TIME_UNITS = [
2180
+ "DAY",
2181
+ "WEEK",
2182
+ "MONTH"
2183
+ ];
2184
+ function parseIsoDate(raw, field) {
2185
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(raw)) return { error: `--${field} must be YYYY-MM-DD (got ${JSON.stringify(raw)})` };
2186
+ const d = /* @__PURE__ */ new Date(`${raw}T00:00:00Z`);
2187
+ if (Number.isNaN(d.getTime())) return { error: `--${field} is not a valid date (got ${JSON.stringify(raw)})` };
2188
+ return { value: raw };
2189
+ }
2190
+ function todayLocalIso() {
2191
+ const d = /* @__PURE__ */ new Date();
2192
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
2193
+ }
2194
+ function daysAgoLocalIso(days) {
2195
+ const d = /* @__PURE__ */ new Date();
2196
+ d.setDate(d.getDate() - days);
2197
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
2198
+ }
2199
+ const appCommand = defineCommand({
2200
+ meta: {
2201
+ name: "app",
2202
+ description: "Inspect mini-apps in a workspace."
2203
+ },
2204
+ subCommands: {
2205
+ ls: lsCommand$4,
2206
+ show: showCommand$2,
2207
+ status: statusCommand,
2208
+ ratings: ratingsCommand,
2209
+ reports: reportsCommand,
2210
+ bundles: bundlesCommand,
2211
+ certs: certsCommand,
2212
+ metrics: defineCommand({
2213
+ meta: {
2214
+ name: "metrics",
2215
+ description: "Show conversion metrics for a mini-app over a date range."
2216
+ },
2217
+ args: {
2218
+ id: {
2219
+ type: "positional",
2220
+ description: "Mini-app ID.",
2221
+ required: true
2222
+ },
2223
+ workspace: {
2224
+ type: "string",
2225
+ description: "Workspace ID. Defaults to the selected workspace."
2226
+ },
2227
+ "time-unit": {
2228
+ type: "string",
2229
+ description: "Bucket size: DAY | WEEK | MONTH.",
2230
+ default: "DAY"
2231
+ },
2232
+ start: {
2233
+ type: "string",
2234
+ description: "Start date (YYYY-MM-DD). Defaults to 30 days before --end."
2235
+ },
2236
+ end: {
2237
+ type: "string",
2238
+ description: "End date (YYYY-MM-DD). Defaults to today (host local)."
2239
+ },
2240
+ refresh: {
2241
+ type: "boolean",
2242
+ description: "Bypass server-side cache.",
2243
+ default: false
2244
+ },
2245
+ json: {
2246
+ type: "boolean",
2247
+ description: "Emit machine-readable JSON.",
2248
+ default: false
2249
+ }
2250
+ },
2251
+ async run({ args }) {
2252
+ const appId = parseAppId(args.id);
2253
+ if (appId === null) {
2254
+ if (args.json) emitJson({
2255
+ ok: false,
2256
+ reason: "invalid-id",
2257
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
2258
+ });
2259
+ else process.stderr.write(`app metrics: invalid id ${JSON.stringify(args.id)}\n`);
2260
+ return exitAfterFlush(ExitCode.Usage);
2261
+ }
2262
+ const timeUnit = String(args["time-unit"]).toUpperCase();
2263
+ if (!VALID_TIME_UNITS.includes(timeUnit)) {
2264
+ const message = `--time-unit must be one of ${VALID_TIME_UNITS.join("|")} (got ${JSON.stringify(args["time-unit"])})`;
2265
+ if (args.json) emitJson({
2266
+ ok: false,
2267
+ reason: "invalid-time-unit",
2268
+ message
2269
+ });
2270
+ else process.stderr.write(`${message}\n`);
2271
+ return exitAfterFlush(ExitCode.Usage);
2272
+ }
2273
+ const endResult = parseIsoDate(args.end ? String(args.end) : todayLocalIso(), "end");
2274
+ if ("error" in endResult) {
2275
+ if (args.json) emitJson({
2276
+ ok: false,
2277
+ reason: "invalid-date",
2278
+ message: endResult.error
2279
+ });
2280
+ else process.stderr.write(`${endResult.error}\n`);
2281
+ return exitAfterFlush(ExitCode.Usage);
2282
+ }
2283
+ const startResult = parseIsoDate(args.start ? String(args.start) : daysAgoLocalIso(30), "start");
2284
+ if ("error" in startResult) {
2285
+ if (args.json) emitJson({
2286
+ ok: false,
2287
+ reason: "invalid-date",
2288
+ message: startResult.error
2289
+ });
2290
+ else process.stderr.write(`${startResult.error}\n`);
2291
+ return exitAfterFlush(ExitCode.Usage);
2292
+ }
2293
+ if (startResult.value > endResult.value) {
2294
+ const message = `--start (${startResult.value}) must be on or before --end (${endResult.value})`;
2295
+ if (args.json) emitJson({
2296
+ ok: false,
2297
+ reason: "invalid-date",
2298
+ message
2299
+ });
2300
+ else process.stderr.write(`${message}\n`);
2301
+ return exitAfterFlush(ExitCode.Usage);
2302
+ }
2303
+ const ctx = await resolveWorkspaceContext(args);
2304
+ if (!ctx) return;
2305
+ const { session, workspaceId } = ctx;
2306
+ try {
2307
+ const result = await fetchConversionMetrics({
2308
+ workspaceId,
2309
+ miniAppId: appId,
2310
+ timeUnitType: timeUnit,
2311
+ startDate: startResult.value,
2312
+ endDate: endResult.value,
2313
+ refresh: args.refresh
2314
+ }, session.cookies);
2315
+ if (args.json) {
2316
+ emitJson({
2317
+ ok: true,
2318
+ workspaceId,
2319
+ appId,
2320
+ timeUnitType: timeUnit,
2321
+ startDate: startResult.value,
2322
+ endDate: endResult.value,
2323
+ ...result.cacheTime !== void 0 ? { cacheTime: result.cacheTime } : {},
2324
+ metrics: result.metrics
2325
+ });
2326
+ return exitAfterFlush(ExitCode.Ok);
2327
+ }
2328
+ const header = `App ${appId} (ws ${workspaceId}) · ${timeUnit} · ${startResult.value} → ${endResult.value}`;
2329
+ if (result.metrics.length === 0) {
2330
+ process.stdout.write(`${header}: no metrics\n`);
2331
+ return exitAfterFlush(ExitCode.Ok);
2332
+ }
2333
+ process.stdout.write(`${header}: ${result.metrics.length} bucket(s)\n`);
2334
+ for (const m of result.metrics) {
2335
+ const date = typeof m.date === "string" ? m.date : typeof m.bucketDate === "string" ? m.bucketDate : "";
2336
+ const impressions = typeof m.impressions === "number" ? m.impressions : typeof m.impressionCount === "number" ? m.impressionCount : "";
2337
+ const clicks = typeof m.clicks === "number" ? m.clicks : typeof m.clickCount === "number" ? m.clickCount : "";
2338
+ process.stdout.write(`${date}\t${impressions}\t${clicks}\n`);
2339
+ }
2340
+ return exitAfterFlush(ExitCode.Ok);
2341
+ } catch (err) {
2342
+ return emitFailureFromError(args.json, err);
2343
+ }
2344
+ }
2345
+ }),
2346
+ "share-rewards": defineCommand({
2347
+ meta: {
2348
+ name: "share-rewards",
2349
+ description: "Inspect share-reward promotions for a mini-app."
2350
+ },
2351
+ subCommands: { ls: defineCommand({
2352
+ meta: {
2353
+ name: "ls",
2354
+ description: "List share-reward promotions configured for a mini-app."
2355
+ },
2356
+ args: {
2357
+ id: {
2358
+ type: "positional",
2359
+ description: "Mini-app ID.",
2360
+ required: true
2361
+ },
2362
+ workspace: {
2363
+ type: "string",
2364
+ description: "Workspace ID. Defaults to the selected workspace."
2365
+ },
2366
+ search: {
2367
+ type: "string",
2368
+ description: "Filter by title (server-side title-contains match). Empty matches everything."
2369
+ },
2370
+ json: {
2371
+ type: "boolean",
2372
+ description: "Emit machine-readable JSON.",
2373
+ default: false
2374
+ }
2375
+ },
2376
+ async run({ args }) {
2377
+ const appId = parseAppId(args.id);
2378
+ if (appId === null) {
2379
+ if (args.json) emitJson({
2380
+ ok: false,
2381
+ reason: "invalid-id",
2382
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
2383
+ });
2384
+ else process.stderr.write(`app share-rewards ls: invalid id ${JSON.stringify(args.id)}\n`);
2385
+ return exitAfterFlush(ExitCode.Usage);
2386
+ }
2387
+ const ctx = await resolveWorkspaceContext(args);
2388
+ if (!ctx) return;
2389
+ const { session, workspaceId } = ctx;
2390
+ try {
2391
+ const rewards = await fetchShareRewards({
2392
+ workspaceId,
2393
+ miniAppId: appId,
2394
+ ...args.search !== void 0 ? { search: String(args.search) } : {}
2395
+ }, session.cookies);
2396
+ if (args.json) {
2397
+ emitJson({
2398
+ ok: true,
2399
+ workspaceId,
2400
+ appId,
2401
+ rewards
2402
+ });
2403
+ return exitAfterFlush(ExitCode.Ok);
2404
+ }
2405
+ if (rewards.length === 0) {
2406
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): no share-reward promotions\n`);
2407
+ return exitAfterFlush(ExitCode.Ok);
2408
+ }
2409
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): ${rewards.length} share-reward(s)\n`);
2410
+ for (const r of rewards) {
2411
+ const id = typeof r.id === "string" || typeof r.id === "number" ? r.id : typeof r.rewardId === "string" || typeof r.rewardId === "number" ? r.rewardId : "-";
2412
+ const title = typeof r.title === "string" ? r.title : typeof r.name === "string" ? r.name : "-";
2413
+ const status = typeof r.status === "string" ? r.status : "-";
2414
+ process.stdout.write(`${id}\t${title}\t${status}\n`);
2415
+ }
2416
+ return exitAfterFlush(ExitCode.Ok);
2417
+ } catch (err) {
2418
+ return emitFailureFromError(args.json, err);
2419
+ }
2420
+ }
2421
+ }) }
2422
+ }),
2423
+ messages: defineCommand({
2424
+ meta: {
2425
+ name: "messages",
2426
+ description: "Inspect smart-message (formerly push) campaigns for a mini-app."
2427
+ },
2428
+ subCommands: { ls: defineCommand({
2429
+ meta: {
2430
+ name: "ls",
2431
+ description: "List smart-message campaigns (formerly \"push\" — the 스마트 발송 menu)."
2432
+ },
2433
+ args: {
2434
+ id: {
2435
+ type: "positional",
2436
+ description: "Mini-app ID.",
2437
+ required: true
2438
+ },
2439
+ workspace: {
2440
+ type: "string",
2441
+ description: "Workspace ID. Defaults to the selected workspace."
2442
+ },
2443
+ page: {
2444
+ type: "string",
2445
+ description: "Page number (0-indexed).",
2446
+ default: "0"
2447
+ },
2448
+ size: {
2449
+ type: "string",
2450
+ description: "Page size.",
2451
+ default: "20"
2452
+ },
2453
+ search: {
2454
+ type: "string",
2455
+ description: "Title-contains filter. Empty matches everything."
2456
+ },
2457
+ json: {
2458
+ type: "boolean",
2459
+ description: "Emit machine-readable JSON.",
2460
+ default: false
2461
+ }
2462
+ },
2463
+ async run({ args }) {
2464
+ const appId = parseAppId(args.id);
2465
+ if (appId === null) {
2466
+ if (args.json) emitJson({
2467
+ ok: false,
2468
+ reason: "invalid-id",
2469
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
2470
+ });
2471
+ else process.stderr.write(`app messages ls: invalid id ${JSON.stringify(args.id)}\n`);
2472
+ return exitAfterFlush(ExitCode.Usage);
2473
+ }
2474
+ const pageResult = parseNonNegativeInt(String(args.page), "page");
2475
+ if ("error" in pageResult) {
2476
+ if (args.json) emitJson({
2477
+ ok: false,
2478
+ reason: "invalid-page",
2479
+ message: pageResult.error
2480
+ });
2481
+ else process.stderr.write(`${pageResult.error}\n`);
2482
+ return exitAfterFlush(ExitCode.Usage);
2483
+ }
2484
+ const sizeResult = parseNonNegativeInt(String(args.size), "size");
2485
+ if ("error" in sizeResult) {
2486
+ if (args.json) emitJson({
2487
+ ok: false,
2488
+ reason: "invalid-size",
2489
+ message: sizeResult.error
2490
+ });
2491
+ else process.stderr.write(`${sizeResult.error}\n`);
2492
+ return exitAfterFlush(ExitCode.Usage);
2493
+ }
2494
+ const ctx = await resolveWorkspaceContext(args);
2495
+ if (!ctx) return;
2496
+ const { session, workspaceId } = ctx;
2497
+ try {
2498
+ const result = await fetchSmartMessageCampaigns({
2499
+ workspaceId,
2500
+ miniAppId: appId,
2501
+ page: pageResult.value,
2502
+ size: sizeResult.value,
2503
+ ...args.search !== void 0 ? { search: String(args.search) } : {}
2504
+ }, session.cookies);
2505
+ if (args.json) {
2506
+ emitJson({
2507
+ ok: true,
2508
+ workspaceId,
2509
+ appId,
2510
+ campaigns: result.items,
2511
+ paging: result.paging
2512
+ });
2513
+ return exitAfterFlush(ExitCode.Ok);
2514
+ }
2515
+ if (result.items.length === 0) {
2516
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): no smart-message campaigns\n`);
2517
+ return exitAfterFlush(ExitCode.Ok);
2518
+ }
2519
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): ${result.items.length} campaign(s) on page ${result.paging.pageNumber} of ${result.paging.totalCount}\n`);
2520
+ for (const c of result.items) {
2521
+ const id = typeof c.id === "string" || typeof c.id === "number" ? c.id : typeof c.campaignId === "string" || typeof c.campaignId === "number" ? c.campaignId : "-";
2522
+ const title = typeof c.title === "string" ? c.title : typeof c.name === "string" ? c.name : "-";
2523
+ const status = typeof c.status === "string" ? c.status : "-";
2524
+ process.stdout.write(`${id}\t${title}\t${status}\n`);
2525
+ }
2526
+ return exitAfterFlush(ExitCode.Ok);
2527
+ } catch (err) {
2528
+ return emitFailureFromError(args.json, err);
2529
+ }
2530
+ }
2531
+ }) }
2532
+ }),
2533
+ events: defineCommand({
2534
+ meta: {
2535
+ name: "events",
2536
+ description: "Inspect custom event catalogs (log search) for a mini-app."
2537
+ },
2538
+ subCommands: { ls: defineCommand({
2539
+ meta: {
2540
+ name: "ls",
2541
+ description: "List custom event catalogs recorded for a mini-app (the 이벤트 menu)."
2542
+ },
2543
+ args: {
2544
+ id: {
2545
+ type: "positional",
2546
+ description: "Mini-app ID.",
2547
+ required: true
2548
+ },
2549
+ workspace: {
2550
+ type: "string",
2551
+ description: "Workspace ID. Defaults to the selected workspace."
2552
+ },
2553
+ page: {
2554
+ type: "string",
2555
+ description: "Page number (0-indexed).",
2556
+ default: "0"
2557
+ },
2558
+ size: {
2559
+ type: "string",
2560
+ description: "Page size.",
2561
+ default: "20"
2562
+ },
2563
+ search: {
2564
+ type: "string",
2565
+ description: "Event-name filter. Empty matches everything."
2566
+ },
2567
+ refresh: {
2568
+ type: "boolean",
2569
+ description: "Bypass the server cache and rebuild the catalog list.",
2570
+ default: false
2571
+ },
2572
+ json: {
2573
+ type: "boolean",
2574
+ description: "Emit machine-readable JSON.",
2575
+ default: false
2576
+ }
2577
+ },
2578
+ async run({ args }) {
2579
+ const appId = parseAppId(args.id);
2580
+ if (appId === null) {
2581
+ if (args.json) emitJson({
2582
+ ok: false,
2583
+ reason: "invalid-id",
2584
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
2585
+ });
2586
+ else process.stderr.write(`app events ls: invalid id ${JSON.stringify(args.id)}\n`);
2587
+ return exitAfterFlush(ExitCode.Usage);
2588
+ }
2589
+ const pageResult = parseNonNegativeInt(String(args.page), "page");
2590
+ if ("error" in pageResult) {
2591
+ if (args.json) emitJson({
2592
+ ok: false,
2593
+ reason: "invalid-page",
2594
+ message: pageResult.error
2595
+ });
2596
+ else process.stderr.write(`${pageResult.error}\n`);
2597
+ return exitAfterFlush(ExitCode.Usage);
2598
+ }
2599
+ const sizeResult = parseNonNegativeInt(String(args.size), "size");
2600
+ if ("error" in sizeResult) {
2601
+ if (args.json) emitJson({
2602
+ ok: false,
2603
+ reason: "invalid-size",
2604
+ message: sizeResult.error
2605
+ });
2606
+ else process.stderr.write(`${sizeResult.error}\n`);
2607
+ return exitAfterFlush(ExitCode.Usage);
2608
+ }
2609
+ const ctx = await resolveWorkspaceContext(args);
2610
+ if (!ctx) return;
2611
+ const { session, workspaceId } = ctx;
2612
+ try {
2613
+ const result = await fetchAppEventCatalogs({
2614
+ workspaceId,
2615
+ miniAppId: appId,
2616
+ pageNumber: pageResult.value,
2617
+ pageSize: sizeResult.value,
2618
+ ...args.search !== void 0 ? { search: String(args.search) } : {},
2619
+ ...args.refresh ? { refresh: true } : {}
2620
+ }, session.cookies);
2621
+ if (args.json) {
2622
+ emitJson({
2623
+ ok: true,
2624
+ workspaceId,
2625
+ appId,
2626
+ events: result.results,
2627
+ cacheTime: result.cacheTime ?? null,
2628
+ paging: result.paging
2629
+ });
2630
+ return exitAfterFlush(ExitCode.Ok);
2631
+ }
2632
+ if (result.results.length === 0) {
2633
+ const ct = result.cacheTime ? ` (cached ${result.cacheTime})` : "";
2634
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): no event catalogs${ct}\n`);
2635
+ return exitAfterFlush(ExitCode.Ok);
2636
+ }
2637
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): ${result.results.length} event(s) on page ${result.paging.pageNumber} of ${result.paging.totalPages}\n`);
2638
+ for (const e of result.results) {
2639
+ const name = typeof e.name === "string" ? e.name : typeof e.eventName === "string" ? e.eventName : "-";
2640
+ const count = typeof e.count === "number" ? String(e.count) : typeof e.totalCount === "number" ? String(e.totalCount) : "-";
2641
+ process.stdout.write(`${name}\t${count}\n`);
2642
+ }
2643
+ return exitAfterFlush(ExitCode.Ok);
2644
+ } catch (err) {
2645
+ return emitFailureFromError(args.json, err);
2646
+ }
2647
+ }
2648
+ }) }
2649
+ }),
2650
+ templates: defineCommand({
2651
+ meta: {
2652
+ name: "templates",
2653
+ description: "Inspect smart-message composer templates available for a mini-app."
2654
+ },
2655
+ subCommands: { ls: defineCommand({
2656
+ meta: {
2657
+ name: "ls",
2658
+ description: "List the smart-message composer templates available for a mini-app (the 템플릿 picker in 스마트 발송)."
2659
+ },
2660
+ args: {
2661
+ id: {
2662
+ type: "positional",
2663
+ description: "Mini-app ID.",
2664
+ required: true
2665
+ },
2666
+ workspace: {
2667
+ type: "string",
2668
+ description: "Workspace ID. Defaults to the selected workspace."
2669
+ },
2670
+ page: {
2671
+ type: "string",
2672
+ description: "Page number (0-indexed).",
2673
+ default: "0"
2674
+ },
2675
+ size: {
2676
+ type: "string",
2677
+ description: "Page size.",
2678
+ default: "20"
2679
+ },
2680
+ "content-reach-type": {
2681
+ type: "string",
2682
+ description: `Template reach bucket: ${TEMPLATE_CONTENT_REACH_TYPES.join(" | ")}. Omit for all.`
2683
+ },
2684
+ "smart-message": {
2685
+ type: "string",
2686
+ description: "Filter to templates compatible with smart-message (\"true\") or legacy push (\"false\"). Omit for all."
2687
+ },
2688
+ json: {
2689
+ type: "boolean",
2690
+ description: "Emit machine-readable JSON.",
2691
+ default: false
2692
+ }
2693
+ },
2694
+ async run({ args }) {
2695
+ const appId = parseAppId(args.id);
2696
+ if (appId === null) {
2697
+ if (args.json) emitJson({
2698
+ ok: false,
2699
+ reason: "invalid-id",
2700
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
2701
+ });
2702
+ else process.stderr.write(`app templates ls: invalid id ${JSON.stringify(args.id)}\n`);
2703
+ return exitAfterFlush(ExitCode.Usage);
2704
+ }
2705
+ const pageResult = parseNonNegativeInt(String(args.page), "page");
2706
+ if ("error" in pageResult) {
2707
+ if (args.json) emitJson({
2708
+ ok: false,
2709
+ reason: "invalid-page",
2710
+ message: pageResult.error
2711
+ });
2712
+ else process.stderr.write(`${pageResult.error}\n`);
2713
+ return exitAfterFlush(ExitCode.Usage);
2714
+ }
2715
+ const sizeResult = parseNonNegativeInt(String(args.size), "size");
2716
+ if ("error" in sizeResult) {
2717
+ if (args.json) emitJson({
2718
+ ok: false,
2719
+ reason: "invalid-size",
2720
+ message: sizeResult.error
2721
+ });
2722
+ else process.stderr.write(`${sizeResult.error}\n`);
2723
+ return exitAfterFlush(ExitCode.Usage);
2724
+ }
2725
+ let contentReachType;
2726
+ if (args["content-reach-type"] !== void 0) {
2727
+ const upper = String(args["content-reach-type"]).toUpperCase();
2728
+ if (TEMPLATE_CONTENT_REACH_TYPES.includes(upper)) contentReachType = upper;
2729
+ else {
2730
+ const message = `--content-reach-type must be one of: ${TEMPLATE_CONTENT_REACH_TYPES.join(", ")}`;
2731
+ if (args.json) emitJson({
2732
+ ok: false,
2733
+ reason: "invalid-content-reach-type",
2734
+ allowed: [...TEMPLATE_CONTENT_REACH_TYPES]
2735
+ });
2736
+ else process.stderr.write(`${message}\n`);
2737
+ return exitAfterFlush(ExitCode.Usage);
2738
+ }
2739
+ }
2740
+ let isSmartMessage;
2741
+ if (args["smart-message"] !== void 0) {
2742
+ const raw = String(args["smart-message"]).toLowerCase();
2743
+ if (raw === "true") isSmartMessage = true;
2744
+ else if (raw === "false") isSmartMessage = false;
2745
+ else {
2746
+ const message = "--smart-message must be \"true\" or \"false\"";
2747
+ if (args.json) emitJson({
2748
+ ok: false,
2749
+ reason: "invalid-smart-message",
2750
+ message
2751
+ });
2752
+ else process.stderr.write(`${message}\n`);
2753
+ return exitAfterFlush(ExitCode.Usage);
2754
+ }
2755
+ }
2756
+ const ctx = await resolveWorkspaceContext(args);
2757
+ if (!ctx) return;
2758
+ const { session, workspaceId } = ctx;
2759
+ try {
2760
+ const result = await fetchAppTemplates({
2761
+ workspaceId,
2762
+ miniAppId: appId,
2763
+ page: pageResult.value,
2764
+ size: sizeResult.value,
2765
+ ...contentReachType !== void 0 ? { contentReachType } : {},
2766
+ ...isSmartMessage !== void 0 ? { isSmartMessage } : {}
2767
+ }, session.cookies);
2768
+ if (args.json) {
2769
+ emitJson({
2770
+ ok: true,
2771
+ workspaceId,
2772
+ appId,
2773
+ templates: result.templates,
2774
+ totalPageCount: result.totalPageCount
2775
+ });
2776
+ return exitAfterFlush(ExitCode.Ok);
2777
+ }
2778
+ if (result.templates.length === 0) {
2779
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): no templates\n`);
2780
+ return exitAfterFlush(ExitCode.Ok);
2781
+ }
2782
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): ${result.templates.length} template(s) of ${result.totalPageCount} page(s)\n`);
2783
+ for (const t of result.templates) {
2784
+ const id = typeof t.id === "string" || typeof t.id === "number" ? t.id : typeof t.templateId === "string" || typeof t.templateId === "number" ? t.templateId : "-";
2785
+ const title = typeof t.title === "string" ? t.title : typeof t.name === "string" ? t.name : "-";
2786
+ const type = typeof t.templateType === "string" ? t.templateType : "-";
2787
+ process.stdout.write(`${id}\t${title}\t${type}\n`);
2788
+ }
2789
+ return exitAfterFlush(ExitCode.Ok);
2790
+ } catch (err) {
2791
+ return emitFailureFromError(args.json, err);
2792
+ }
2793
+ }
2794
+ }) }
2795
+ }),
2796
+ categories: defineCommand({
2797
+ meta: {
2798
+ name: "categories",
2799
+ description: "List the impression category tree used by `app register`'s `categoryIds` field."
2800
+ },
2801
+ args: {
2802
+ selectable: {
2803
+ type: "boolean",
2804
+ description: "Only show categories flagged `isSelectable: true` — the ones you can pick.",
2805
+ default: false
2806
+ },
2807
+ json: {
2808
+ type: "boolean",
2809
+ description: "Emit machine-readable JSON to stdout.",
2810
+ default: false
2811
+ }
2812
+ },
2813
+ async run({ args }) {
2814
+ const session = await readSession();
2815
+ if (!session) {
2816
+ emitNotAuthenticated(args.json);
2817
+ return exitAfterFlush(ExitCode.NotAuthenticated);
2818
+ }
2819
+ try {
2820
+ const tree = await fetchImpressionCategoryList(session.cookies);
2821
+ const filtered = args.selectable ? tree.filter((g) => g.categoryGroup.isSelectable).map((g) => ({
2822
+ ...g,
2823
+ categoryList: g.categoryList.filter((c) => c.isSelectable).map((c) => ({
2824
+ ...c,
2825
+ subCategoryList: c.subCategoryList.filter((s) => s.isSelectable)
2826
+ }))
2827
+ })) : tree;
2828
+ if (args.json) {
2829
+ emitJson({
2830
+ ok: true,
2831
+ categories: filtered
2832
+ });
2833
+ return exitAfterFlush(ExitCode.Ok);
2834
+ }
2835
+ for (const g of filtered) {
2836
+ const mark = g.categoryGroup.isSelectable ? "" : " (not selectable)";
2837
+ process.stdout.write(`[${g.categoryGroup.id}] ${g.categoryGroup.name}${mark}\n`);
2838
+ for (const c of g.categoryList) {
2839
+ const cmark = c.isSelectable ? "" : " (not selectable)";
2840
+ process.stdout.write(` ${c.id}\t${c.name}${cmark}\n`);
2841
+ for (const s of c.subCategoryList) {
2842
+ const smark = s.isSelectable ? "" : " (not selectable)";
2843
+ process.stdout.write(` ${s.id}\t${s.name}${smark}\n`);
2844
+ }
2845
+ }
2846
+ }
2847
+ return exitAfterFlush(ExitCode.Ok);
2848
+ } catch (err) {
2849
+ return emitFailureFromError(args.json, err);
2850
+ }
2851
+ }
2852
+ }),
2853
+ "service-status": defineCommand({
2854
+ meta: {
2855
+ name: "service-status",
2856
+ description: "Show the server-authoritative runtime status of a mini-app (serviceStatus, shutdown schedule)."
2857
+ },
2858
+ args: {
2859
+ id: {
2860
+ type: "positional",
2861
+ description: "Mini-app ID.",
2862
+ required: true
2863
+ },
2864
+ workspace: {
2865
+ type: "string",
2866
+ description: "Workspace ID. Defaults to the selected workspace."
2867
+ },
2868
+ json: {
2869
+ type: "boolean",
2870
+ description: "Emit machine-readable JSON.",
2871
+ default: false
2872
+ }
2873
+ },
2874
+ async run({ args }) {
2875
+ const appId = parseAppId(args.id);
2876
+ if (appId === null) {
2877
+ if (args.json) emitJson({
2878
+ ok: false,
2879
+ reason: "invalid-id",
2880
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
2881
+ });
2882
+ else process.stderr.write(`app service-status: invalid id ${JSON.stringify(args.id)}\n`);
2883
+ return exitAfterFlush(ExitCode.Usage);
2884
+ }
2885
+ const ctx = await resolveWorkspaceContext(args);
2886
+ if (!ctx) return;
2887
+ const { session, workspaceId } = ctx;
2888
+ try {
2889
+ const st = await fetchAppServiceStatus(workspaceId, appId, session.cookies);
2890
+ if (args.json) {
2891
+ emitJson({
2892
+ ok: true,
2893
+ workspaceId,
2894
+ appId,
2895
+ ...st
2896
+ });
2897
+ return exitAfterFlush(ExitCode.Ok);
2898
+ }
2899
+ process.stdout.write(`App ${appId} (ws ${workspaceId}):\n`);
2900
+ process.stdout.write(` serviceStatus: ${st.serviceStatus}\n`);
2901
+ process.stdout.write(` shutdownCandidateStatus: ${st.shutdownCandidateStatus ?? "null"}\n`);
2902
+ process.stdout.write(` scheduledShutdownAt: ${st.scheduledShutdownAt ?? "null"}\n`);
2903
+ return exitAfterFlush(ExitCode.Ok);
2904
+ } catch (err) {
2905
+ return emitFailureFromError(args.json, err);
2906
+ }
2907
+ }
2908
+ }),
2909
+ register: defineCommand({
2910
+ meta: {
2911
+ name: "register",
2912
+ description: "Register a mini-app in the selected workspace from a YAML/JSON manifest. Uploads logo/thumbnail/screenshots, then submits the create payload."
2913
+ },
2914
+ args: {
2915
+ workspace: {
2916
+ type: "string",
2917
+ description: "Workspace ID. Defaults to the selected workspace (`aitcc workspace use`)."
2918
+ },
2919
+ config: {
2920
+ type: "string",
2921
+ description: "Path to the app manifest. Defaults to `./aitcc.app.yaml`, then `./aitcc.app.json`."
2922
+ },
2923
+ "dry-run": {
2924
+ type: "boolean",
2925
+ description: "Validate manifest + images and print the inferred submit payload; no uploads.",
2926
+ default: false
2927
+ },
2928
+ "accept-terms": {
2929
+ type: "boolean",
2930
+ description: "Attest to the required console legal-agreement checkboxes (see VALIDATION-RULES.md). Required for real submits.",
2931
+ default: false
2932
+ },
2933
+ json: {
2934
+ type: "boolean",
2935
+ description: "Emit machine-readable JSON to stdout.",
2936
+ default: false
2937
+ }
2938
+ },
2939
+ async run({ args }) {
2940
+ await runRegister({
2941
+ json: args.json,
2942
+ dryRun: args["dry-run"],
2943
+ acceptTerms: args["accept-terms"],
2944
+ ...args.workspace !== void 0 ? { workspace: args.workspace } : {},
2945
+ ...args.config !== void 0 ? { config: args.config } : {}
2946
+ });
2947
+ }
2948
+ })
2949
+ }
2950
+ });
1402
2951
  //#endregion
1403
2952
  //#region src/api/api-keys.ts
1404
- const BASE$1 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
2953
+ const BASE$3 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
1405
2954
  async function fetchApiKeys(workspaceId, cookies, opts = {}) {
1406
2955
  const raw = await requestConsoleApi({
1407
- url: `${BASE$1}/workspaces/${workspaceId}/api-keys`,
2956
+ url: `${BASE$3}/workspaces/${workspaceId}/api-keys`,
1408
2957
  cookies,
1409
2958
  ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
1410
2959
  });
@@ -1484,7 +3033,8 @@ const keysCommand = defineCommand({
1484
3033
  });
1485
3034
  //#endregion
1486
3035
  //#region src/api/me.ts
1487
- const MEMBER_USER_INFO_URL = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole/members/me/user-info";
3036
+ const BASE$2 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
3037
+ const MEMBER_USER_INFO_URL = `${BASE$2}/members/me/user-info`;
1488
3038
  async function fetchConsoleMemberUserInfo(cookies, opts = {}) {
1489
3039
  return requestConsoleApi({
1490
3040
  url: MEMBER_USER_INFO_URL,
@@ -1492,6 +3042,28 @@ async function fetchConsoleMemberUserInfo(cookies, opts = {}) {
1492
3042
  ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
1493
3043
  });
1494
3044
  }
3045
+ async function fetchUserTerms(cookies, opts = {}) {
3046
+ const raw = await requestConsoleApi({
3047
+ url: `${BASE$2}/console-user-terms/me`,
3048
+ cookies,
3049
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
3050
+ });
3051
+ if (!Array.isArray(raw)) throw new Error("Unexpected user-terms shape: not an array");
3052
+ return raw.map((entry, i) => {
3053
+ if (!entry || typeof entry !== "object") throw new Error(`Unexpected user-terms entry at index ${i}`);
3054
+ const e = entry;
3055
+ return {
3056
+ required: Boolean(e.required),
3057
+ termsId: typeof e.termsId === "number" ? e.termsId : 0,
3058
+ revisionId: typeof e.revisionId === "number" ? e.revisionId : 0,
3059
+ title: typeof e.title === "string" ? e.title : "",
3060
+ contentsUrl: typeof e.contentsUrl === "string" ? e.contentsUrl : "",
3061
+ actionType: typeof e.actionType === "string" ? e.actionType : "",
3062
+ isAgreed: Boolean(e.isAgreed),
3063
+ isOneTimeConsent: Boolean(e.isOneTimeConsent)
3064
+ };
3065
+ });
3066
+ }
1495
3067
  //#endregion
1496
3068
  //#region src/cdp.ts
1497
3069
  function isResponse(m) {
@@ -2146,11 +3718,59 @@ const logoutCommand = defineCommand({
2146
3718
  }
2147
3719
  });
2148
3720
  //#endregion
3721
+ //#region src/commands/me.ts
3722
+ function formatTermLine(t) {
3723
+ return ` ${t.isAgreed ? "[agreed]" : "[pending]"}${t.required ? " required" : ""} ${t.title}\n ${t.contentsUrl}\n`;
3724
+ }
3725
+ const meCommand = defineCommand({
3726
+ meta: {
3727
+ name: "me",
3728
+ description: "Inspect account-level settings for the signed-in user."
3729
+ },
3730
+ subCommands: { terms: defineCommand({
3731
+ meta: {
3732
+ name: "terms",
3733
+ description: "Show the console-level terms of agreement for the signed-in account."
3734
+ },
3735
+ args: { json: {
3736
+ type: "boolean",
3737
+ description: "Emit machine-readable JSON to stdout.",
3738
+ default: false
3739
+ } },
3740
+ async run({ args }) {
3741
+ const session = await readSession();
3742
+ if (!session) {
3743
+ emitNotAuthenticated(args.json);
3744
+ return exitAfterFlush(ExitCode.NotAuthenticated);
3745
+ }
3746
+ try {
3747
+ const terms = await fetchUserTerms(session.cookies);
3748
+ if (args.json) {
3749
+ emitJson({
3750
+ ok: true,
3751
+ terms
3752
+ });
3753
+ return exitAfterFlush(ExitCode.Ok);
3754
+ }
3755
+ if (terms.length === 0) {
3756
+ process.stdout.write("No console-level terms required.\n");
3757
+ return exitAfterFlush(ExitCode.Ok);
3758
+ }
3759
+ process.stdout.write("Console account terms:\n");
3760
+ for (const t of terms) process.stdout.write(formatTermLine(t));
3761
+ return exitAfterFlush(ExitCode.Ok);
3762
+ } catch (err) {
3763
+ return emitFailureFromError(args.json, err);
3764
+ }
3765
+ }
3766
+ }) }
3767
+ });
3768
+ //#endregion
2149
3769
  //#region src/api/members.ts
2150
- const BASE = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
3770
+ const BASE$1 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
2151
3771
  async function fetchWorkspaceMembers(workspaceId, cookies, opts = {}) {
2152
3772
  const raw = await requestConsoleApi({
2153
- url: `${BASE}/workspaces/${workspaceId}/members`,
3773
+ url: `${BASE$1}/workspaces/${workspaceId}/members`,
2154
3774
  cookies,
2155
3775
  ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
2156
3776
  });
@@ -2235,6 +3855,257 @@ const membersCommand = defineCommand({
2235
3855
  }
2236
3856
  }) }
2237
3857
  });
3858
+ const BASE = "https://api-public.toss.im/api-public/v3/ipd-thor/api/v1";
3859
+ async function fetchNotices(params, cookies, opts = {}) {
3860
+ const qs = new URLSearchParams();
3861
+ qs.set("page", String(params.page ?? 0));
3862
+ qs.set("size", String(params.size ?? 20));
3863
+ if (params.titleContains !== void 0) qs.set("title__icontains", params.titleContains);
3864
+ const raw = await requestConsoleApi({
3865
+ url: `${BASE}/workspaces/129/posts?${qs.toString()}`,
3866
+ cookies,
3867
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
3868
+ });
3869
+ if (raw === null || typeof raw !== "object") throw new Error("Unexpected notices shape: not an object");
3870
+ const rec = raw;
3871
+ const resultsRaw = rec.results;
3872
+ if (!Array.isArray(resultsRaw)) throw new Error("Unexpected notices shape: results is not an array");
3873
+ const results = resultsRaw.map((r) => {
3874
+ if (r === null || typeof r !== "object") return {};
3875
+ return r;
3876
+ });
3877
+ return {
3878
+ page: typeof rec.page === "number" ? rec.page : 1,
3879
+ pageSize: typeof rec.pageSize === "number" ? rec.pageSize : params.size ?? 20,
3880
+ count: typeof rec.count === "number" ? rec.count : results.length,
3881
+ next: typeof rec.next === "string" ? rec.next : null,
3882
+ previous: typeof rec.previous === "string" ? rec.previous : null,
3883
+ results
3884
+ };
3885
+ }
3886
+ async function fetchNoticeCategories(cookies, opts = {}) {
3887
+ const raw = await requestConsoleApi({
3888
+ url: `${BASE}/workspaces/129/categories`,
3889
+ cookies,
3890
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
3891
+ });
3892
+ if (!Array.isArray(raw)) throw new Error("Unexpected categories shape: not an array");
3893
+ return raw.map((c) => {
3894
+ if (c === null || typeof c !== "object") return {};
3895
+ return c;
3896
+ });
3897
+ }
3898
+ async function fetchNoticePost(postId, cookies, opts = {}) {
3899
+ const raw = await requestConsoleApi({
3900
+ url: `${BASE}/workspaces/129/posts/${postId}`,
3901
+ cookies,
3902
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
3903
+ });
3904
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Unexpected notice-post shape for id=${postId}`);
3905
+ return raw;
3906
+ }
3907
+ //#endregion
3908
+ //#region src/commands/notices.ts
3909
+ function parsePositiveInt(raw, field) {
3910
+ const n = Number(raw);
3911
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 0) return { error: `--${field} must be a non-negative integer (got ${JSON.stringify(raw)})` };
3912
+ return { value: n };
3913
+ }
3914
+ const noticesCommand = defineCommand({
3915
+ meta: {
3916
+ name: "notices",
3917
+ description: "Read Apps in Toss notices (공지사항). Shared across all users."
3918
+ },
3919
+ subCommands: {
3920
+ ls: defineCommand({
3921
+ meta: {
3922
+ name: "ls",
3923
+ description: "List notices (공지사항) from Apps in Toss."
3924
+ },
3925
+ args: {
3926
+ page: {
3927
+ type: "string",
3928
+ description: "Page number (0-indexed).",
3929
+ default: "0"
3930
+ },
3931
+ size: {
3932
+ type: "string",
3933
+ description: "Page size.",
3934
+ default: "20"
3935
+ },
3936
+ search: {
3937
+ type: "string",
3938
+ description: "Filter by title substring (case-insensitive)."
3939
+ },
3940
+ json: {
3941
+ type: "boolean",
3942
+ description: "Emit machine-readable JSON.",
3943
+ default: false
3944
+ }
3945
+ },
3946
+ async run({ args }) {
3947
+ const session = await requireSession(args.json);
3948
+ if (!session) return;
3949
+ const pageResult = parsePositiveInt(args.page, "page");
3950
+ if ("error" in pageResult) {
3951
+ if (args.json) emitJson({
3952
+ ok: false,
3953
+ reason: "invalid-config",
3954
+ field: "page",
3955
+ message: pageResult.error
3956
+ });
3957
+ else process.stderr.write(`notices ls: ${pageResult.error}\n`);
3958
+ return exitAfterFlush(ExitCode.Usage);
3959
+ }
3960
+ const sizeResult = parsePositiveInt(args.size, "size");
3961
+ if ("error" in sizeResult) {
3962
+ if (args.json) emitJson({
3963
+ ok: false,
3964
+ reason: "invalid-config",
3965
+ field: "size",
3966
+ message: sizeResult.error
3967
+ });
3968
+ else process.stderr.write(`notices ls: ${sizeResult.error}\n`);
3969
+ return exitAfterFlush(ExitCode.Usage);
3970
+ }
3971
+ if (sizeResult.value === 0) {
3972
+ if (args.json) emitJson({
3973
+ ok: false,
3974
+ reason: "invalid-config",
3975
+ field: "size",
3976
+ message: "--size must be at least 1"
3977
+ });
3978
+ else process.stderr.write("notices ls: --size must be at least 1\n");
3979
+ return exitAfterFlush(ExitCode.Usage);
3980
+ }
3981
+ try {
3982
+ const result = await fetchNotices({
3983
+ page: pageResult.value,
3984
+ size: sizeResult.value,
3985
+ ...typeof args.search === "string" && args.search.length > 0 ? { titleContains: args.search } : {}
3986
+ }, session.cookies);
3987
+ if (args.json) {
3988
+ emitJson({
3989
+ ok: true,
3990
+ page: result.page,
3991
+ pageSize: result.pageSize,
3992
+ count: result.count,
3993
+ hasNext: result.next !== null,
3994
+ notices: result.results
3995
+ });
3996
+ return exitAfterFlush(ExitCode.Ok);
3997
+ }
3998
+ process.stdout.write(`Notices: page ${result.page}, ${result.results.length}/${result.count} shown\n`);
3999
+ if (result.results.length === 0) {
4000
+ process.stdout.write("No notices on this page.\n");
4001
+ return exitAfterFlush(ExitCode.Ok);
4002
+ }
4003
+ for (const n of result.results) {
4004
+ const id = typeof n.id === "string" || typeof n.id === "number" ? n.id : "-";
4005
+ const category = typeof n.category === "string" ? n.category : "-";
4006
+ const title = typeof n.title === "string" ? n.title : "";
4007
+ const publishedTime = typeof n.publishedTime === "string" ? n.publishedTime : "";
4008
+ process.stdout.write(`${id}\t${publishedTime}\t[${category}]\t${title}\n`);
4009
+ }
4010
+ if (result.next !== null) process.stdout.write(`(more: --page ${result.page + 1})\n`);
4011
+ return exitAfterFlush(ExitCode.Ok);
4012
+ } catch (err) {
4013
+ return emitFailureFromError(args.json, err);
4014
+ }
4015
+ }
4016
+ }),
4017
+ show: defineCommand({
4018
+ meta: {
4019
+ name: "show",
4020
+ description: "Show a single notice post by ID."
4021
+ },
4022
+ args: {
4023
+ id: {
4024
+ type: "positional",
4025
+ description: "Notice post ID.",
4026
+ required: true
4027
+ },
4028
+ json: {
4029
+ type: "boolean",
4030
+ description: "Emit machine-readable JSON.",
4031
+ default: false
4032
+ }
4033
+ },
4034
+ async run({ args }) {
4035
+ const postId = Number(args.id);
4036
+ if (!Number.isFinite(postId) || !Number.isInteger(postId) || postId <= 0) {
4037
+ if (args.json) emitJson({
4038
+ ok: false,
4039
+ reason: "invalid-id",
4040
+ message: `notice id must be a positive integer (got ${JSON.stringify(args.id)})`
4041
+ });
4042
+ else process.stderr.write(`notices show: invalid id ${JSON.stringify(args.id)}\n`);
4043
+ return exitAfterFlush(ExitCode.Usage);
4044
+ }
4045
+ const session = await requireSession(args.json);
4046
+ if (!session) return;
4047
+ try {
4048
+ const notice = await fetchNoticePost(postId, session.cookies);
4049
+ if (args.json) {
4050
+ emitJson({
4051
+ ok: true,
4052
+ id: postId,
4053
+ notice
4054
+ });
4055
+ return exitAfterFlush(ExitCode.Ok);
4056
+ }
4057
+ const title = typeof notice.title === "string" ? notice.title : "";
4058
+ const subtitle = typeof notice.subtitle === "string" ? notice.subtitle : "";
4059
+ const category = typeof notice.category === "string" ? notice.category : "";
4060
+ const publishedTime = typeof notice.publishedTime === "string" ? notice.publishedTime : "";
4061
+ const body = typeof notice.fullDescription === "string" ? notice.fullDescription : typeof notice.shortDescription === "string" ? notice.shortDescription : "";
4062
+ process.stdout.write(`# ${title}\n`);
4063
+ if (subtitle) process.stdout.write(`${subtitle}\n`);
4064
+ process.stdout.write(`\n[${category}] ${publishedTime}\n\n`);
4065
+ process.stdout.write(body);
4066
+ if (!body.endsWith("\n")) process.stdout.write("\n");
4067
+ return exitAfterFlush(ExitCode.Ok);
4068
+ } catch (err) {
4069
+ return emitFailureFromError(args.json, err);
4070
+ }
4071
+ }
4072
+ }),
4073
+ categories: defineCommand({
4074
+ meta: {
4075
+ name: "categories",
4076
+ description: "List notice categories and their post counts."
4077
+ },
4078
+ args: { json: {
4079
+ type: "boolean",
4080
+ description: "Emit machine-readable JSON.",
4081
+ default: false
4082
+ } },
4083
+ async run({ args }) {
4084
+ const session = await requireSession(args.json);
4085
+ if (!session) return;
4086
+ try {
4087
+ const categories = await fetchNoticeCategories(session.cookies);
4088
+ if (args.json) {
4089
+ emitJson({
4090
+ ok: true,
4091
+ categories
4092
+ });
4093
+ return exitAfterFlush(ExitCode.Ok);
4094
+ }
4095
+ for (const c of categories) {
4096
+ const id = typeof c.id === "string" || typeof c.id === "number" ? c.id : "-";
4097
+ const name = typeof c.name === "string" ? c.name : "-";
4098
+ const postCount = typeof c.postCount === "number" ? c.postCount : 0;
4099
+ process.stdout.write(`${id}\t${postCount}\t${name}\n`);
4100
+ }
4101
+ return exitAfterFlush(ExitCode.Ok);
4102
+ } catch (err) {
4103
+ return emitFailureFromError(args.json, err);
4104
+ }
4105
+ }
4106
+ })
4107
+ }
4108
+ });
2238
4109
  //#endregion
2239
4110
  //#region src/github.ts
2240
4111
  const REPO_OWNER = "apps-in-toss-community";
@@ -2348,7 +4219,7 @@ function resolveVersion() {
2348
4219
  if (typeof injected === "string" && injected.length > 0) return injected;
2349
4220
  } catch {}
2350
4221
  try {
2351
- return "0.1.10";
4222
+ return "0.1.12";
2352
4223
  } catch {}
2353
4224
  return "0.0.0-dev";
2354
4225
  }
@@ -2601,138 +4472,455 @@ async function runBackgroundUpdateCheck(json) {
2601
4472
  if (typeof t.unref === "function") t.unref();
2602
4473
  })]);
2603
4474
  }
2604
- const whoamiCommand = defineCommand({
4475
+ const whoamiCommand = defineCommand({
4476
+ meta: {
4477
+ name: "whoami",
4478
+ description: "Show the currently authenticated user (live from the console API by default)."
4479
+ },
4480
+ args: {
4481
+ json: {
4482
+ type: "boolean",
4483
+ description: "Emit machine-readable JSON to stdout.",
4484
+ default: false
4485
+ },
4486
+ offline: {
4487
+ type: "boolean",
4488
+ description: "Skip the live API call and read only the cached session summary.",
4489
+ default: false
4490
+ }
4491
+ },
4492
+ async run({ args }) {
4493
+ const session = await readSession();
4494
+ if (!session) {
4495
+ if (args.json) process.stdout.write(`${JSON.stringify({
4496
+ ok: true,
4497
+ authenticated: false
4498
+ })}\n`);
4499
+ else {
4500
+ process.stderr.write("Not logged in. Run `aitcc login` to start a session.\n");
4501
+ process.stderr.write(`Session file checked: ${sessionPathForDiagnostics()}\n`);
4502
+ }
4503
+ return exitAfterFlush(ExitCode.NotAuthenticated);
4504
+ }
4505
+ if (args.offline) {
4506
+ if (args.json) {
4507
+ process.stdout.write(`${JSON.stringify({
4508
+ ok: true,
4509
+ authenticated: true,
4510
+ source: "cache",
4511
+ user: session.user,
4512
+ capturedAt: session.capturedAt
4513
+ })}\n`);
4514
+ return exitAfterFlush(ExitCode.Ok);
4515
+ }
4516
+ const label = session.user.displayName ? `${session.user.displayName} <${session.user.email}>` : session.user.email;
4517
+ process.stdout.write(`Logged in as ${label} (cached)\n`);
4518
+ process.stdout.write(`Session captured: ${session.capturedAt}\n`);
4519
+ await runBackgroundUpdateCheck(args.json);
4520
+ return exitAfterFlush(ExitCode.Ok);
4521
+ }
4522
+ try {
4523
+ const info = await fetchConsoleMemberUserInfo(session.cookies);
4524
+ if (args.json) {
4525
+ process.stdout.write(`${JSON.stringify({
4526
+ ok: true,
4527
+ authenticated: true,
4528
+ source: "live",
4529
+ user: {
4530
+ id: String(info.id),
4531
+ bizUserNo: info.bizUserNo,
4532
+ name: info.name,
4533
+ email: info.email,
4534
+ role: info.role
4535
+ },
4536
+ workspaces: info.workspaces.map((w) => ({
4537
+ workspaceId: w.workspaceId,
4538
+ workspaceName: w.workspaceName,
4539
+ role: w.role
4540
+ })),
4541
+ capturedAt: session.capturedAt
4542
+ })}\n`);
4543
+ return exitAfterFlush(ExitCode.Ok);
4544
+ }
4545
+ process.stdout.write(`Logged in as ${info.name} <${info.email}> (${info.role})\n`);
4546
+ if (info.workspaces.length > 0) {
4547
+ process.stdout.write("Workspaces:\n");
4548
+ for (const w of info.workspaces) process.stdout.write(` - ${w.workspaceName} (id ${w.workspaceId}, ${w.role})\n`);
4549
+ }
4550
+ await runBackgroundUpdateCheck(args.json);
4551
+ return exitAfterFlush(ExitCode.Ok);
4552
+ } catch (err) {
4553
+ if (err instanceof TossApiError && err.isAuthError) {
4554
+ if (args.json) process.stdout.write(`${JSON.stringify({
4555
+ ok: true,
4556
+ authenticated: false,
4557
+ reason: "session-expired",
4558
+ errorCode: err.errorCode
4559
+ })}\n`);
4560
+ else process.stderr.write("Session is no longer valid. Run `aitcc login` again.\n");
4561
+ return exitAfterFlush(ExitCode.NotAuthenticated);
4562
+ }
4563
+ if (err instanceof NetworkError) {
4564
+ if (args.json) process.stdout.write(`${JSON.stringify({
4565
+ ok: false,
4566
+ reason: "network-error",
4567
+ message: err.message
4568
+ })}\n`);
4569
+ else process.stderr.write(`Network error reaching the console API: ${err.message}. Use \`aitcc whoami --offline\` for the cached identity.\n`);
4570
+ return exitAfterFlush(ExitCode.NetworkError);
4571
+ }
4572
+ if (args.json) process.stdout.write(`${JSON.stringify({
4573
+ ok: false,
4574
+ reason: "api-error",
4575
+ message: err.message
4576
+ })}\n`);
4577
+ else process.stderr.write(`Unexpected error: ${err.message}\n`);
4578
+ return exitAfterFlush(ExitCode.ApiError);
4579
+ }
4580
+ }
4581
+ });
4582
+ //#endregion
4583
+ //#region src/api/workspaces.ts
4584
+ const WORKSPACES_BASE = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
4585
+ async function fetchWorkspaceDetail(workspaceId, cookies, opts = {}) {
4586
+ const raw = await requestConsoleApi({
4587
+ url: `${WORKSPACES_BASE}/workspaces/${workspaceId}`,
4588
+ cookies,
4589
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
4590
+ });
4591
+ const id = raw.id;
4592
+ const name = raw.name;
4593
+ if (typeof id !== "number" || !Number.isInteger(id) || id <= 0 || typeof name !== "string") throw new Error(`Unexpected workspace detail shape for id=${workspaceId}`);
4594
+ const { id: _id, name: _name, ...extra } = raw;
4595
+ return {
4596
+ workspaceId: id,
4597
+ workspaceName: name,
4598
+ extra
4599
+ };
4600
+ }
4601
+ async function fetchWorkspacePartner(workspaceId, cookies, opts = {}) {
4602
+ const raw = await requestConsoleApi({
4603
+ url: `${WORKSPACES_BASE}/workspaces/${workspaceId}/partner`,
4604
+ cookies,
4605
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
4606
+ });
4607
+ const registered = raw.registered;
4608
+ if (typeof registered !== "boolean") throw new Error(`Unexpected workspace partner shape for id=${workspaceId}`);
4609
+ return {
4610
+ registered,
4611
+ approvalType: typeof raw.approvalType === "string" ? raw.approvalType : null,
4612
+ rejectMessage: typeof raw.rejectMessage === "string" ? raw.rejectMessage : null,
4613
+ partner: raw.partner && typeof raw.partner === "object" ? raw.partner : null
4614
+ };
4615
+ }
4616
+ const WORKSPACE_TERM_TYPES = [
4617
+ "TOSS_LOGIN",
4618
+ "BIZ_WORKSPACE",
4619
+ "TOSS_PROMOTION_MONEY",
4620
+ "IAA",
4621
+ "IAP"
4622
+ ];
4623
+ const DEFAULT_SEGMENT_CATEGORY = "생성된 세그먼트";
4624
+ async function fetchWorkspaceSegments(params, cookies, opts = {}) {
4625
+ const page = params.page ?? 0;
4626
+ const qs = new URLSearchParams();
4627
+ qs.set("category", params.category ?? DEFAULT_SEGMENT_CATEGORY);
4628
+ qs.set("search", params.search ?? "");
4629
+ qs.set("page", String(page));
4630
+ const raw = await requestConsoleApi({
4631
+ url: `${WORKSPACES_BASE}/workspaces/${params.workspaceId}/segments/list?${qs.toString()}`,
4632
+ cookies,
4633
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
4634
+ });
4635
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Unexpected segments shape for workspace=${params.workspaceId}`);
4636
+ const data = raw;
4637
+ return {
4638
+ contents: (Array.isArray(data.contents) ? data.contents : []).map((c) => c && typeof c === "object" ? c : {}),
4639
+ totalPage: typeof data.totalPage === "number" ? data.totalPage : 0,
4640
+ currentPage: typeof data.currentPage === "number" ? data.currentPage : page
4641
+ };
4642
+ }
4643
+ async function fetchWorkspaceTerms(workspaceId, type, cookies, opts = {}) {
4644
+ const raw = await requestConsoleApi({
4645
+ url: `${WORKSPACES_BASE}/workspaces/${workspaceId}/console-workspace-terms/${type}/skip-permission`,
4646
+ cookies,
4647
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
4648
+ });
4649
+ if (!Array.isArray(raw)) throw new Error(`Unexpected workspace terms shape for type=${type}`);
4650
+ return raw.map((entry, i) => {
4651
+ if (!entry || typeof entry !== "object") throw new Error(`Unexpected workspace terms entry at index ${i} for type=${type}`);
4652
+ const e = entry;
4653
+ return {
4654
+ required: Boolean(e.required),
4655
+ termsId: typeof e.termsId === "number" ? e.termsId : 0,
4656
+ revisionId: typeof e.revisionId === "number" ? e.revisionId : 0,
4657
+ title: typeof e.title === "string" ? e.title : "",
4658
+ contentsUrl: typeof e.contentsUrl === "string" ? e.contentsUrl : "",
4659
+ actionType: typeof e.actionType === "string" ? e.actionType : "",
4660
+ isAgreed: Boolean(e.isAgreed),
4661
+ isOneTimeConsent: Boolean(e.isOneTimeConsent)
4662
+ };
4663
+ });
4664
+ }
4665
+ //#endregion
4666
+ //#region src/commands/workspace.ts
4667
+ function formatScalar(v) {
4668
+ if (v === null) return "null";
4669
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return String(v);
4670
+ return JSON.stringify(v);
4671
+ }
4672
+ const lsCommand = defineCommand({
4673
+ meta: {
4674
+ name: "ls",
4675
+ description: "List workspaces the current user has access to."
4676
+ },
4677
+ args: { json: {
4678
+ type: "boolean",
4679
+ description: "Emit machine-readable JSON to stdout.",
4680
+ default: false
4681
+ } },
4682
+ async run({ args }) {
4683
+ const session = await readSession();
4684
+ if (!session) {
4685
+ emitNotAuthenticated(args.json);
4686
+ return exitAfterFlush(ExitCode.NotAuthenticated);
4687
+ }
4688
+ try {
4689
+ const info = await fetchConsoleMemberUserInfo(session.cookies);
4690
+ const current = session.currentWorkspaceId;
4691
+ if (args.json) {
4692
+ emitJson({
4693
+ ok: true,
4694
+ workspaces: info.workspaces.map((w) => ({
4695
+ workspaceId: w.workspaceId,
4696
+ workspaceName: w.workspaceName,
4697
+ role: w.role,
4698
+ current: w.workspaceId === current
4699
+ }))
4700
+ });
4701
+ return exitAfterFlush(ExitCode.Ok);
4702
+ }
4703
+ if (info.workspaces.length === 0) {
4704
+ process.stdout.write("No workspaces.\n");
4705
+ return exitAfterFlush(ExitCode.Ok);
4706
+ }
4707
+ for (const w of info.workspaces) {
4708
+ const marker = w.workspaceId === current ? "* " : " ";
4709
+ process.stdout.write(`${marker}${w.workspaceId} ${w.workspaceName} (${w.role})\n`);
4710
+ }
4711
+ if (current === void 0) process.stderr.write("No workspace selected. Run `aitcc workspace use <id>`.\n");
4712
+ return exitAfterFlush(ExitCode.Ok);
4713
+ } catch (err) {
4714
+ return emitFailureFromError(args.json, err);
4715
+ }
4716
+ }
4717
+ });
4718
+ const useCommand = defineCommand({
4719
+ meta: {
4720
+ name: "use",
4721
+ description: "Select the current workspace by ID. Subsequent commands use this."
4722
+ },
4723
+ args: {
4724
+ id: {
4725
+ type: "positional",
4726
+ description: "Workspace ID",
4727
+ required: true
4728
+ },
4729
+ json: {
4730
+ type: "boolean",
4731
+ description: "Emit machine-readable JSON to stdout.",
4732
+ default: false
4733
+ }
4734
+ },
4735
+ async run({ args }) {
4736
+ const raw = String(args.id);
4737
+ const parsed = parsePositiveInt$1(raw);
4738
+ if (parsed === null) {
4739
+ const message = `workspace id must be a positive integer (got ${raw})`;
4740
+ if (args.json) emitJson({
4741
+ ok: false,
4742
+ reason: "invalid-id",
4743
+ message
4744
+ });
4745
+ else process.stderr.write(`${message}\n`);
4746
+ return exitAfterFlush(ExitCode.Usage);
4747
+ }
4748
+ const session = await readSession();
4749
+ if (!session) {
4750
+ emitNotAuthenticated(args.json);
4751
+ return exitAfterFlush(ExitCode.NotAuthenticated);
4752
+ }
4753
+ try {
4754
+ const match = (await fetchConsoleMemberUserInfo(session.cookies)).workspaces.find((w) => w.workspaceId === parsed);
4755
+ if (!match) {
4756
+ if (args.json) emitJson({
4757
+ ok: false,
4758
+ reason: "not-found",
4759
+ workspaceId: parsed
4760
+ });
4761
+ else process.stderr.write(`Workspace ${parsed} is not accessible from this account. Run \`aitcc workspace ls\` to see available workspaces.\n`);
4762
+ return exitAfterFlush(ExitCode.Usage);
4763
+ }
4764
+ if (await setCurrentWorkspaceId(parsed) === null) {
4765
+ emitNotAuthenticated(args.json);
4766
+ return exitAfterFlush(ExitCode.NotAuthenticated);
4767
+ }
4768
+ if (args.json) emitJson({
4769
+ ok: true,
4770
+ workspaceId: match.workspaceId,
4771
+ workspaceName: match.workspaceName
4772
+ });
4773
+ else process.stdout.write(`Using workspace ${match.workspaceId} (${match.workspaceName}).\n`);
4774
+ return exitAfterFlush(ExitCode.Ok);
4775
+ } catch (err) {
4776
+ return emitFailureFromError(args.json, err);
4777
+ }
4778
+ }
4779
+ });
4780
+ const showCommand = defineCommand({
4781
+ meta: {
4782
+ name: "show",
4783
+ description: "Show details of the selected workspace (or the one passed with --workspace)."
4784
+ },
4785
+ args: {
4786
+ workspace: {
4787
+ type: "string",
4788
+ description: "Workspace ID to inspect. Defaults to the selected workspace."
4789
+ },
4790
+ json: {
4791
+ type: "boolean",
4792
+ description: "Emit machine-readable JSON to stdout.",
4793
+ default: false
4794
+ }
4795
+ },
4796
+ async run({ args }) {
4797
+ const session = await readSession();
4798
+ if (!session) {
4799
+ emitNotAuthenticated(args.json);
4800
+ return exitAfterFlush(ExitCode.NotAuthenticated);
4801
+ }
4802
+ let workspaceId;
4803
+ if (args.workspace) {
4804
+ const raw = String(args.workspace);
4805
+ const parsed = parsePositiveInt$1(raw);
4806
+ if (parsed === null) {
4807
+ const message = `--workspace must be a positive integer (got ${raw})`;
4808
+ if (args.json) emitJson({
4809
+ ok: false,
4810
+ reason: "invalid-id",
4811
+ message
4812
+ });
4813
+ else process.stderr.write(`${message}\n`);
4814
+ return exitAfterFlush(ExitCode.Usage);
4815
+ }
4816
+ workspaceId = parsed;
4817
+ } else workspaceId = session.currentWorkspaceId;
4818
+ if (workspaceId === void 0) {
4819
+ if (args.json) emitJson({
4820
+ ok: false,
4821
+ reason: "no-workspace-selected"
4822
+ });
4823
+ else process.stderr.write("No workspace selected. Pass `--workspace <id>` or run `aitcc workspace use <id>`.\n");
4824
+ return exitAfterFlush(ExitCode.Usage);
4825
+ }
4826
+ try {
4827
+ const detail = await fetchWorkspaceDetail(workspaceId, session.cookies);
4828
+ if (args.json) {
4829
+ emitJson({
4830
+ ok: true,
4831
+ workspaceId: detail.workspaceId,
4832
+ workspaceName: detail.workspaceName,
4833
+ extra: detail.extra ?? {}
4834
+ });
4835
+ return exitAfterFlush(ExitCode.Ok);
4836
+ }
4837
+ process.stdout.write(`Workspace ${detail.workspaceId}: ${detail.workspaceName}\n`);
4838
+ if (detail.extra) for (const [k, v] of Object.entries(detail.extra)) process.stdout.write(` ${k}: ${formatScalar(v)}\n`);
4839
+ return exitAfterFlush(ExitCode.Ok);
4840
+ } catch (err) {
4841
+ return emitFailureFromError(args.json, err);
4842
+ }
4843
+ }
4844
+ });
4845
+ async function resolveWorkspaceArg(args, selected) {
4846
+ if (args.workspace) {
4847
+ const raw = String(args.workspace);
4848
+ const parsed = parsePositiveInt$1(raw);
4849
+ if (parsed === null) {
4850
+ const message = `--workspace must be a positive integer (got ${raw})`;
4851
+ if (args.json) emitJson({
4852
+ ok: false,
4853
+ reason: "invalid-id",
4854
+ message
4855
+ });
4856
+ else process.stderr.write(`${message}\n`);
4857
+ return null;
4858
+ }
4859
+ return parsed;
4860
+ }
4861
+ if (selected === void 0) {
4862
+ if (args.json) emitJson({
4863
+ ok: false,
4864
+ reason: "no-workspace-selected"
4865
+ });
4866
+ else process.stderr.write("No workspace selected. Pass `--workspace <id>` or run `aitcc workspace use <id>`.\n");
4867
+ return null;
4868
+ }
4869
+ return selected;
4870
+ }
4871
+ const partnerCommand = defineCommand({
2605
4872
  meta: {
2606
- name: "whoami",
2607
- description: "Show the currently authenticated user (live from the console API by default)."
4873
+ name: "partner",
4874
+ description: "Show the partner (billing/payout) registration state for the selected workspace."
2608
4875
  },
2609
4876
  args: {
4877
+ workspace: {
4878
+ type: "string",
4879
+ description: "Workspace ID to inspect. Defaults to the selected workspace."
4880
+ },
2610
4881
  json: {
2611
4882
  type: "boolean",
2612
4883
  description: "Emit machine-readable JSON to stdout.",
2613
4884
  default: false
2614
- },
2615
- offline: {
2616
- type: "boolean",
2617
- description: "Skip the live API call and read only the cached session summary.",
2618
- default: false
2619
4885
  }
2620
4886
  },
2621
4887
  async run({ args }) {
2622
4888
  const session = await readSession();
2623
4889
  if (!session) {
2624
- if (args.json) process.stdout.write(`${JSON.stringify({
2625
- ok: true,
2626
- authenticated: false
2627
- })}\n`);
2628
- else {
2629
- process.stderr.write("Not logged in. Run `aitcc login` to start a session.\n");
2630
- process.stderr.write(`Session file checked: ${sessionPathForDiagnostics()}\n`);
2631
- }
4890
+ emitNotAuthenticated(args.json);
2632
4891
  return exitAfterFlush(ExitCode.NotAuthenticated);
2633
4892
  }
2634
- if (args.offline) {
2635
- if (args.json) {
2636
- process.stdout.write(`${JSON.stringify({
2637
- ok: true,
2638
- authenticated: true,
2639
- source: "cache",
2640
- user: session.user,
2641
- capturedAt: session.capturedAt
2642
- })}\n`);
2643
- return exitAfterFlush(ExitCode.Ok);
2644
- }
2645
- const label = session.user.displayName ? `${session.user.displayName} <${session.user.email}>` : session.user.email;
2646
- process.stdout.write(`Logged in as ${label} (cached)\n`);
2647
- process.stdout.write(`Session captured: ${session.capturedAt}\n`);
2648
- await runBackgroundUpdateCheck(args.json);
2649
- return exitAfterFlush(ExitCode.Ok);
2650
- }
4893
+ const workspaceId = await resolveWorkspaceArg(args, session.currentWorkspaceId);
4894
+ if (workspaceId === null) return exitAfterFlush(ExitCode.Usage);
2651
4895
  try {
2652
- const info = await fetchConsoleMemberUserInfo(session.cookies);
4896
+ const state = await fetchWorkspacePartner(workspaceId, session.cookies);
2653
4897
  if (args.json) {
2654
- process.stdout.write(`${JSON.stringify({
4898
+ emitJson({
2655
4899
  ok: true,
2656
- authenticated: true,
2657
- source: "live",
2658
- user: {
2659
- id: String(info.id),
2660
- bizUserNo: info.bizUserNo,
2661
- name: info.name,
2662
- email: info.email,
2663
- role: info.role
2664
- },
2665
- workspaces: info.workspaces.map((w) => ({
2666
- workspaceId: w.workspaceId,
2667
- workspaceName: w.workspaceName,
2668
- role: w.role
2669
- })),
2670
- capturedAt: session.capturedAt
2671
- })}\n`);
4900
+ workspaceId,
4901
+ registered: state.registered,
4902
+ approvalType: state.approvalType,
4903
+ rejectMessage: state.rejectMessage,
4904
+ partner: state.partner
4905
+ });
2672
4906
  return exitAfterFlush(ExitCode.Ok);
2673
4907
  }
2674
- process.stdout.write(`Logged in as ${info.name} <${info.email}> (${info.role})\n`);
2675
- if (info.workspaces.length > 0) {
2676
- process.stdout.write("Workspaces:\n");
2677
- for (const w of info.workspaces) process.stdout.write(` - ${w.workspaceName} (id ${w.workspaceId}, ${w.role})\n`);
4908
+ process.stdout.write(`Workspace ${workspaceId} partner:\n`);
4909
+ process.stdout.write(` registered: ${state.registered}\n`);
4910
+ process.stdout.write(` approvalType: ${state.approvalType ?? "null"}\n`);
4911
+ if (state.rejectMessage) process.stdout.write(` rejectMessage: ${state.rejectMessage}\n`);
4912
+ if (state.partner) {
4913
+ process.stdout.write(" partner:\n");
4914
+ for (const [k, v] of Object.entries(state.partner)) process.stdout.write(` ${k}: ${formatScalar(v)}\n`);
2678
4915
  }
2679
- await runBackgroundUpdateCheck(args.json);
2680
4916
  return exitAfterFlush(ExitCode.Ok);
2681
4917
  } catch (err) {
2682
- if (err instanceof TossApiError && err.isAuthError) {
2683
- if (args.json) process.stdout.write(`${JSON.stringify({
2684
- ok: true,
2685
- authenticated: false,
2686
- reason: "session-expired",
2687
- errorCode: err.errorCode
2688
- })}\n`);
2689
- else process.stderr.write("Session is no longer valid. Run `aitcc login` again.\n");
2690
- return exitAfterFlush(ExitCode.NotAuthenticated);
2691
- }
2692
- if (err instanceof NetworkError) {
2693
- if (args.json) process.stdout.write(`${JSON.stringify({
2694
- ok: false,
2695
- reason: "network-error",
2696
- message: err.message
2697
- })}\n`);
2698
- else process.stderr.write(`Network error reaching the console API: ${err.message}. Use \`aitcc whoami --offline\` for the cached identity.\n`);
2699
- return exitAfterFlush(ExitCode.NetworkError);
2700
- }
2701
- if (args.json) process.stdout.write(`${JSON.stringify({
2702
- ok: false,
2703
- reason: "api-error",
2704
- message: err.message
2705
- })}\n`);
2706
- else process.stderr.write(`Unexpected error: ${err.message}\n`);
2707
- return exitAfterFlush(ExitCode.ApiError);
4918
+ return emitFailureFromError(args.json, err);
2708
4919
  }
2709
4920
  }
2710
4921
  });
2711
- //#endregion
2712
- //#region src/api/workspaces.ts
2713
- const WORKSPACES_BASE = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
2714
- async function fetchWorkspaceDetail(workspaceId, cookies, opts = {}) {
2715
- const raw = await requestConsoleApi({
2716
- url: `${WORKSPACES_BASE}/workspaces/${workspaceId}`,
2717
- cookies,
2718
- ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
2719
- });
2720
- const id = raw.id;
2721
- const name = raw.name;
2722
- if (typeof id !== "number" || !Number.isInteger(id) || id <= 0 || typeof name !== "string") throw new Error(`Unexpected workspace detail shape for id=${workspaceId}`);
2723
- const { id: _id, name: _name, ...extra } = raw;
2724
- return {
2725
- workspaceId: id,
2726
- workspaceName: name,
2727
- extra
2728
- };
2729
- }
2730
- //#endregion
2731
- //#region src/commands/workspace.ts
2732
- function formatScalar(v) {
2733
- if (v === null) return "null";
2734
- if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return String(v);
2735
- return JSON.stringify(v);
4922
+ function formatTermLines(term) {
4923
+ return ` ${term.isAgreed ? "[agreed]" : "[pending]"}${term.required ? " required" : ""} ${term.title}\n ${term.contentsUrl}\n`;
2736
4924
  }
2737
4925
  const workspaceCommand = defineCommand({
2738
4926
  meta: {
@@ -2740,62 +4928,23 @@ const workspaceCommand = defineCommand({
2740
4928
  description: "Inspect and switch between the workspaces this account can access."
2741
4929
  },
2742
4930
  subCommands: {
2743
- ls: defineCommand({
2744
- meta: {
2745
- name: "ls",
2746
- description: "List workspaces the current user has access to."
2747
- },
2748
- args: { json: {
2749
- type: "boolean",
2750
- description: "Emit machine-readable JSON to stdout.",
2751
- default: false
2752
- } },
2753
- async run({ args }) {
2754
- const session = await readSession();
2755
- if (!session) {
2756
- emitNotAuthenticated(args.json);
2757
- return exitAfterFlush(ExitCode.NotAuthenticated);
2758
- }
2759
- try {
2760
- const info = await fetchConsoleMemberUserInfo(session.cookies);
2761
- const current = session.currentWorkspaceId;
2762
- if (args.json) {
2763
- emitJson({
2764
- ok: true,
2765
- workspaces: info.workspaces.map((w) => ({
2766
- workspaceId: w.workspaceId,
2767
- workspaceName: w.workspaceName,
2768
- role: w.role,
2769
- current: w.workspaceId === current
2770
- }))
2771
- });
2772
- return exitAfterFlush(ExitCode.Ok);
2773
- }
2774
- if (info.workspaces.length === 0) {
2775
- process.stdout.write("No workspaces.\n");
2776
- return exitAfterFlush(ExitCode.Ok);
2777
- }
2778
- for (const w of info.workspaces) {
2779
- const marker = w.workspaceId === current ? "* " : " ";
2780
- process.stdout.write(`${marker}${w.workspaceId} ${w.workspaceName} (${w.role})\n`);
2781
- }
2782
- if (current === void 0) process.stderr.write("No workspace selected. Run `aitcc workspace use <id>`.\n");
2783
- return exitAfterFlush(ExitCode.Ok);
2784
- } catch (err) {
2785
- return emitFailureFromError(args.json, err);
2786
- }
2787
- }
2788
- }),
2789
- use: defineCommand({
4931
+ ls: lsCommand,
4932
+ use: useCommand,
4933
+ show: showCommand,
4934
+ partner: partnerCommand,
4935
+ terms: defineCommand({
2790
4936
  meta: {
2791
- name: "use",
2792
- description: "Select the current workspace by ID. Subsequent commands use this."
4937
+ name: "terms",
4938
+ description: "Show the console terms-of-agreement state that gate workspace-level features (Toss login, IAP, IAA, biz workspace, promotion money)."
2793
4939
  },
2794
4940
  args: {
2795
- id: {
2796
- type: "positional",
2797
- description: "Workspace ID",
2798
- required: true
4941
+ type: {
4942
+ type: "string",
4943
+ description: `Term bucket to inspect: ${WORKSPACE_TERM_TYPES.join(" | ")}. Omit to query every bucket.`
4944
+ },
4945
+ workspace: {
4946
+ type: "string",
4947
+ description: "Workspace ID to inspect. Defaults to the selected workspace."
2799
4948
  },
2800
4949
  json: {
2801
4950
  type: "boolean",
@@ -2804,114 +4953,158 @@ const workspaceCommand = defineCommand({
2804
4953
  }
2805
4954
  },
2806
4955
  async run({ args }) {
2807
- const raw = String(args.id);
2808
- const parsed = parsePositiveInt(raw);
2809
- if (parsed === null) {
2810
- const message = `workspace id must be a positive integer (got ${raw})`;
4956
+ const session = await readSession();
4957
+ if (!session) {
4958
+ emitNotAuthenticated(args.json);
4959
+ return exitAfterFlush(ExitCode.NotAuthenticated);
4960
+ }
4961
+ const workspaceId = await resolveWorkspaceArg(args, session.currentWorkspaceId);
4962
+ if (workspaceId === null) return exitAfterFlush(ExitCode.Usage);
4963
+ const typesToQuery = (() => {
4964
+ if (!args.type) return WORKSPACE_TERM_TYPES;
4965
+ const raw = String(args.type).toUpperCase();
4966
+ if (WORKSPACE_TERM_TYPES.includes(raw)) return [raw];
4967
+ return [];
4968
+ })();
4969
+ if (typesToQuery.length === 0) {
4970
+ const message = `--type must be one of: ${WORKSPACE_TERM_TYPES.join(", ")}`;
2811
4971
  if (args.json) emitJson({
2812
4972
  ok: false,
2813
- reason: "invalid-id",
2814
- message
4973
+ reason: "invalid-type",
4974
+ allowed: [...WORKSPACE_TERM_TYPES]
2815
4975
  });
2816
4976
  else process.stderr.write(`${message}\n`);
2817
4977
  return exitAfterFlush(ExitCode.Usage);
2818
4978
  }
2819
- const session = await readSession();
2820
- if (!session) {
2821
- emitNotAuthenticated(args.json);
2822
- return exitAfterFlush(ExitCode.NotAuthenticated);
2823
- }
2824
4979
  try {
2825
- const match = (await fetchConsoleMemberUserInfo(session.cookies)).workspaces.find((w) => w.workspaceId === parsed);
2826
- if (!match) {
2827
- if (args.json) emitJson({
2828
- ok: false,
2829
- reason: "not-found",
2830
- workspaceId: parsed
4980
+ const results = await Promise.all(typesToQuery.map(async (t) => [t, await fetchWorkspaceTerms(workspaceId, t, session.cookies)]));
4981
+ if (typesToQuery.length === 1) {
4982
+ const [type, terms] = results[0];
4983
+ if (args.json) {
4984
+ emitJson({
4985
+ ok: true,
4986
+ workspaceId,
4987
+ type,
4988
+ terms
4989
+ });
4990
+ return exitAfterFlush(ExitCode.Ok);
4991
+ }
4992
+ process.stdout.write(`Workspace ${workspaceId} terms (${type}):\n`);
4993
+ if (terms.length === 0) process.stdout.write(" (no terms required)\n");
4994
+ else for (const t of terms) process.stdout.write(formatTermLines(t));
4995
+ return exitAfterFlush(ExitCode.Ok);
4996
+ }
4997
+ const byType = {};
4998
+ for (const [t, terms] of results) byType[t] = terms;
4999
+ if (args.json) {
5000
+ emitJson({
5001
+ ok: true,
5002
+ workspaceId,
5003
+ byType
2831
5004
  });
2832
- else process.stderr.write(`Workspace ${parsed} is not accessible from this account. Run \`aitcc workspace ls\` to see available workspaces.\n`);
2833
- return exitAfterFlush(ExitCode.Usage);
5005
+ return exitAfterFlush(ExitCode.Ok);
2834
5006
  }
2835
- if (await setCurrentWorkspaceId(parsed) === null) {
2836
- emitNotAuthenticated(args.json);
2837
- return exitAfterFlush(ExitCode.NotAuthenticated);
5007
+ for (const [type, terms] of results) {
5008
+ process.stdout.write(`\n[${type}]\n`);
5009
+ if (terms.length === 0) process.stdout.write(" (no terms required)\n");
5010
+ else for (const t of terms) process.stdout.write(formatTermLines(t));
2838
5011
  }
2839
- if (args.json) emitJson({
2840
- ok: true,
2841
- workspaceId: match.workspaceId,
2842
- workspaceName: match.workspaceName
2843
- });
2844
- else process.stdout.write(`Using workspace ${match.workspaceId} (${match.workspaceName}).\n`);
2845
5012
  return exitAfterFlush(ExitCode.Ok);
2846
5013
  } catch (err) {
2847
5014
  return emitFailureFromError(args.json, err);
2848
5015
  }
2849
5016
  }
2850
5017
  }),
2851
- show: defineCommand({
5018
+ segments: defineCommand({
2852
5019
  meta: {
2853
- name: "show",
2854
- description: "Show details of the selected workspace (or the one passed with --workspace)."
5020
+ name: "segments",
5021
+ description: "Inspect user segments defined in a workspace."
2855
5022
  },
2856
- args: {
2857
- workspace: {
2858
- type: "string",
2859
- description: "Workspace ID to inspect. Defaults to the selected workspace."
5023
+ subCommands: { ls: defineCommand({
5024
+ meta: {
5025
+ name: "ls",
5026
+ description: "List user segments in the selected workspace (the 세그먼트 menu)."
2860
5027
  },
2861
- json: {
2862
- type: "boolean",
2863
- description: "Emit machine-readable JSON to stdout.",
2864
- default: false
2865
- }
2866
- },
2867
- async run({ args }) {
2868
- const session = await readSession();
2869
- if (!session) {
2870
- emitNotAuthenticated(args.json);
2871
- return exitAfterFlush(ExitCode.NotAuthenticated);
2872
- }
2873
- let workspaceId;
2874
- if (args.workspace) {
2875
- const raw = String(args.workspace);
2876
- const parsed = parsePositiveInt(raw);
2877
- if (parsed === null) {
2878
- const message = `--workspace must be a positive integer (got ${raw})`;
5028
+ args: {
5029
+ workspace: {
5030
+ type: "string",
5031
+ description: "Workspace ID. Defaults to the selected workspace."
5032
+ },
5033
+ category: {
5034
+ type: "string",
5035
+ description: "Category bucket (tab). Defaults to \"생성된 세그먼트\" — the UI's initial tab."
5036
+ },
5037
+ search: {
5038
+ type: "string",
5039
+ description: "Name-contains filter. Empty matches everything."
5040
+ },
5041
+ page: {
5042
+ type: "string",
5043
+ description: "Page number (0-indexed).",
5044
+ default: "0"
5045
+ },
5046
+ json: {
5047
+ type: "boolean",
5048
+ description: "Emit machine-readable JSON to stdout.",
5049
+ default: false
5050
+ }
5051
+ },
5052
+ async run({ args }) {
5053
+ const session = await readSession();
5054
+ if (!session) {
5055
+ emitNotAuthenticated(args.json);
5056
+ return exitAfterFlush(ExitCode.NotAuthenticated);
5057
+ }
5058
+ const workspaceId = await resolveWorkspaceArg(args, session.currentWorkspaceId);
5059
+ if (workspaceId === null) return exitAfterFlush(ExitCode.Usage);
5060
+ const pageRaw = String(args.page);
5061
+ const pageNum = Number(pageRaw);
5062
+ if (!Number.isFinite(pageNum) || !Number.isInteger(pageNum) || pageNum < 0) {
5063
+ const message = `--page must be a non-negative integer (got ${JSON.stringify(pageRaw)})`;
2879
5064
  if (args.json) emitJson({
2880
5065
  ok: false,
2881
- reason: "invalid-id",
5066
+ reason: "invalid-page",
2882
5067
  message
2883
5068
  });
2884
5069
  else process.stderr.write(`${message}\n`);
2885
5070
  return exitAfterFlush(ExitCode.Usage);
2886
5071
  }
2887
- workspaceId = parsed;
2888
- } else workspaceId = session.currentWorkspaceId;
2889
- if (workspaceId === void 0) {
2890
- if (args.json) emitJson({
2891
- ok: false,
2892
- reason: "no-workspace-selected"
2893
- });
2894
- else process.stderr.write("No workspace selected. Pass `--workspace <id>` or run `aitcc workspace use <id>`.\n");
2895
- return exitAfterFlush(ExitCode.Usage);
2896
- }
2897
- try {
2898
- const detail = await fetchWorkspaceDetail(workspaceId, session.cookies);
2899
- if (args.json) {
2900
- emitJson({
2901
- ok: true,
2902
- workspaceId: detail.workspaceId,
2903
- workspaceName: detail.workspaceName,
2904
- extra: detail.extra ?? {}
2905
- });
5072
+ try {
5073
+ const page = await fetchWorkspaceSegments({
5074
+ workspaceId,
5075
+ ...args.category !== void 0 ? { category: String(args.category) } : {},
5076
+ ...args.search !== void 0 ? { search: String(args.search) } : {},
5077
+ page: pageNum
5078
+ }, session.cookies);
5079
+ const category = args.category !== void 0 ? String(args.category) : "생성된 세그먼트";
5080
+ if (args.json) {
5081
+ emitJson({
5082
+ ok: true,
5083
+ workspaceId,
5084
+ category,
5085
+ segments: page.contents,
5086
+ totalPage: page.totalPage,
5087
+ currentPage: page.currentPage
5088
+ });
5089
+ return exitAfterFlush(ExitCode.Ok);
5090
+ }
5091
+ if (page.contents.length === 0) {
5092
+ process.stdout.write(`Workspace ${workspaceId} (${category}): no segments on page ${page.currentPage}\n`);
5093
+ return exitAfterFlush(ExitCode.Ok);
5094
+ }
5095
+ process.stdout.write(`Workspace ${workspaceId} (${category}): ${page.contents.length} segment(s), page ${page.currentPage} of ${page.totalPage}\n`);
5096
+ for (const s of page.contents) {
5097
+ const id = typeof s.id === "string" || typeof s.id === "number" ? s.id : typeof s.segmentId === "string" || typeof s.segmentId === "number" ? s.segmentId : "-";
5098
+ const name = typeof s.name === "string" ? s.name : typeof s.title === "string" ? s.title : "-";
5099
+ const userCount = typeof s.userCount === "number" ? String(s.userCount) : typeof s.count === "number" ? String(s.count) : "-";
5100
+ process.stdout.write(`${id}\t${name}\t${userCount}\n`);
5101
+ }
2906
5102
  return exitAfterFlush(ExitCode.Ok);
5103
+ } catch (err) {
5104
+ return emitFailureFromError(args.json, err);
2907
5105
  }
2908
- process.stdout.write(`Workspace ${detail.workspaceId}: ${detail.workspaceName}\n`);
2909
- if (detail.extra) for (const [k, v] of Object.entries(detail.extra)) process.stdout.write(` ${k}: ${formatScalar(v)}\n`);
2910
- return exitAfterFlush(ExitCode.Ok);
2911
- } catch (err) {
2912
- return emitFailureFromError(args.json, err);
2913
5106
  }
2914
- }
5107
+ }) }
2915
5108
  })
2916
5109
  }
2917
5110
  });
@@ -2931,7 +5124,9 @@ runMain(defineCommand({
2931
5124
  workspace: workspaceCommand,
2932
5125
  app: appCommand,
2933
5126
  members: membersCommand,
2934
- keys: keysCommand
5127
+ keys: keysCommand,
5128
+ notices: noticesCommand,
5129
+ me: meCommand
2935
5130
  }
2936
5131
  }));
2937
5132
  //#endregion