@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 +2585 -390
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
1573
|
+
const statusCommand = defineCommand({
|
|
1265
1574
|
meta: {
|
|
1266
|
-
name: "
|
|
1267
|
-
description: "
|
|
1575
|
+
name: "status",
|
|
1576
|
+
description: "Show the derived review state of a mini-app (under-review / rejected / approved)."
|
|
1268
1577
|
},
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
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
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
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$
|
|
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$
|
|
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
|
|
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.
|
|
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: "
|
|
2607
|
-
description: "Show the
|
|
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
|
-
|
|
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
|
-
|
|
2635
|
-
|
|
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
|
|
4896
|
+
const state = await fetchWorkspacePartner(workspaceId, session.cookies);
|
|
2653
4897
|
if (args.json) {
|
|
2654
|
-
|
|
4898
|
+
emitJson({
|
|
2655
4899
|
ok: true,
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
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(`
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2712
|
-
|
|
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:
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
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: "
|
|
2792
|
-
description: "
|
|
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
|
-
|
|
2796
|
-
type: "
|
|
2797
|
-
description: "
|
|
2798
|
-
|
|
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
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
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-
|
|
2814
|
-
|
|
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
|
|
2826
|
-
if (
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
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
|
-
|
|
2833
|
-
return exitAfterFlush(ExitCode.Usage);
|
|
5005
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2834
5006
|
}
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
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
|
-
|
|
5018
|
+
segments: defineCommand({
|
|
2852
5019
|
meta: {
|
|
2853
|
-
name: "
|
|
2854
|
-
description: "
|
|
5020
|
+
name: "segments",
|
|
5021
|
+
description: "Inspect user segments defined in a workspace."
|
|
2855
5022
|
},
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
description: "
|
|
5023
|
+
subCommands: { ls: defineCommand({
|
|
5024
|
+
meta: {
|
|
5025
|
+
name: "ls",
|
|
5026
|
+
description: "List user segments in the selected workspace (the 세그먼트 menu)."
|
|
2860
5027
|
},
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
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-
|
|
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
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
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
|