@ait-co/console-cli 0.1.11 → 0.1.14
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 +1935 -707
- 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
|
});
|
|
@@ -228,7 +228,7 @@ async function fetchMiniAppRatings(params, cookies, opts = {}) {
|
|
|
228
228
|
const sortField = params.sortField ?? "CREATED_AT";
|
|
229
229
|
const sortDirection = params.sortDirection ?? "DESC";
|
|
230
230
|
const raw = await requestConsoleApi({
|
|
231
|
-
url: `${BASE$
|
|
231
|
+
url: `${BASE$4}/workspaces/${params.workspaceId}/mini-app/${params.miniAppId}/app-ratings?page=${page}&size=${size}&sortField=${sortField}&sortDirection=${sortDirection}`,
|
|
232
232
|
cookies,
|
|
233
233
|
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
234
234
|
});
|
|
@@ -261,7 +261,7 @@ async function fetchUserReports(params, cookies, opts = {}) {
|
|
|
261
261
|
qs.set("pageSize", String(pageSize));
|
|
262
262
|
if (params.cursor !== void 0) qs.set("cursor", params.cursor);
|
|
263
263
|
const raw = await requestConsoleApi({
|
|
264
|
-
url: `${BASE$
|
|
264
|
+
url: `${BASE$4}/workspaces/${params.workspaceId}/mini-apps/${params.miniAppId}/user-reports?` + qs.toString(),
|
|
265
265
|
cookies,
|
|
266
266
|
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
267
267
|
});
|
|
@@ -285,7 +285,7 @@ async function fetchBundles(params, cookies, opts = {}) {
|
|
|
285
285
|
if (params.deployStatus !== void 0) qs.set("deployStatus", params.deployStatus);
|
|
286
286
|
const query = qs.toString();
|
|
287
287
|
const raw = await requestConsoleApi({
|
|
288
|
-
url: `${BASE$
|
|
288
|
+
url: `${BASE$4}/workspaces/${params.workspaceId}/mini-app/${params.miniAppId}/bundles` + (query ? `?${query}` : ""),
|
|
289
289
|
cookies,
|
|
290
290
|
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
291
291
|
});
|
|
@@ -309,7 +309,7 @@ async function fetchConversionMetrics(params, cookies, opts = {}) {
|
|
|
309
309
|
qs.set("startDate", params.startDate);
|
|
310
310
|
qs.set("endDate", params.endDate);
|
|
311
311
|
const raw = await requestConsoleApi({
|
|
312
|
-
url: `${BASE$
|
|
312
|
+
url: `${BASE$4}/workspaces/${params.workspaceId}/mini-app/${params.miniAppId}/conversion-metrics?${qs.toString()}`,
|
|
313
313
|
cookies,
|
|
314
314
|
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
315
315
|
});
|
|
@@ -327,7 +327,7 @@ async function fetchConversionMetrics(params, cookies, opts = {}) {
|
|
|
327
327
|
}
|
|
328
328
|
async function fetchCerts(workspaceId, miniAppId, cookies, opts = {}) {
|
|
329
329
|
const raw = await requestConsoleApi({
|
|
330
|
-
url: `${BASE$
|
|
330
|
+
url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/certs`,
|
|
331
331
|
cookies,
|
|
332
332
|
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
333
333
|
});
|
|
@@ -341,7 +341,7 @@ async function fetchShareRewards(params, cookies, opts = {}) {
|
|
|
341
341
|
const qs = new URLSearchParams();
|
|
342
342
|
qs.set("search", params.search ?? "");
|
|
343
343
|
const raw = await requestConsoleApi({
|
|
344
|
-
url: `${BASE$
|
|
344
|
+
url: `${BASE$4}/workspaces/${params.workspaceId}/mini-app/${params.miniAppId}/share-rewards?${qs.toString()}`,
|
|
345
345
|
cookies,
|
|
346
346
|
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
347
347
|
});
|
|
@@ -351,9 +351,164 @@ async function fetchShareRewards(params, cookies, opts = {}) {
|
|
|
351
351
|
return r;
|
|
352
352
|
});
|
|
353
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
|
+
}
|
|
354
509
|
async function fetchDeployedBundle(workspaceId, miniAppId, cookies, opts = {}) {
|
|
355
510
|
const raw = await requestConsoleApi({
|
|
356
|
-
url: `${BASE$
|
|
511
|
+
url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/bundles/deployed`,
|
|
357
512
|
cookies,
|
|
358
513
|
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
359
514
|
});
|
|
@@ -363,7 +518,7 @@ async function fetchDeployedBundle(workspaceId, miniAppId, cookies, opts = {}) {
|
|
|
363
518
|
}
|
|
364
519
|
async function createMiniApp(workspaceId, payload, cookies, opts = {}) {
|
|
365
520
|
return normalizeCreateResult(await requestConsoleApi({
|
|
366
|
-
url: `${BASE$
|
|
521
|
+
url: `${BASE$4}/workspaces/${workspaceId}/mini-app/review`,
|
|
367
522
|
method: "POST",
|
|
368
523
|
cookies,
|
|
369
524
|
body: payload,
|
|
@@ -396,7 +551,7 @@ function normalizeCreateResult(raw) {
|
|
|
396
551
|
* the field name is actually `file` — if so, swap it in one place here.
|
|
397
552
|
*/
|
|
398
553
|
async function uploadMiniAppResource(params, opts = {}) {
|
|
399
|
-
const url = new URL(`${BASE$
|
|
554
|
+
const url = new URL(`${BASE$4}/resource/${params.workspaceId}/upload`);
|
|
400
555
|
url.searchParams.set("validWidth", String(params.validWidth));
|
|
401
556
|
url.searchParams.set("validHeight", String(params.validHeight));
|
|
402
557
|
const form = new FormData();
|
|
@@ -1120,6 +1275,10 @@ async function uploadOne(uploadImpl, input) {
|
|
|
1120
1275
|
}
|
|
1121
1276
|
});
|
|
1122
1277
|
}
|
|
1278
|
+
function categoryHintFor(err) {
|
|
1279
|
+
if ((err.field ?? "") === "categoryIds" || /categoryIds/.test(err.message)) return "Tip: run `aitcc app categories --selectable` to list valid category ids.";
|
|
1280
|
+
return null;
|
|
1281
|
+
}
|
|
1123
1282
|
function emitManifestError(json, err) {
|
|
1124
1283
|
if (json) if (err.kind === "missing-required-field") emitJson({
|
|
1125
1284
|
ok: false,
|
|
@@ -1132,7 +1291,11 @@ function emitManifestError(json, err) {
|
|
|
1132
1291
|
reason: "invalid-config",
|
|
1133
1292
|
message: err.message
|
|
1134
1293
|
});
|
|
1135
|
-
else
|
|
1294
|
+
else {
|
|
1295
|
+
process.stderr.write(`${err.message}\n`);
|
|
1296
|
+
const hint = categoryHintFor(err);
|
|
1297
|
+
if (hint) process.stderr.write(`${hint}\n`);
|
|
1298
|
+
}
|
|
1136
1299
|
}
|
|
1137
1300
|
function emitImageDimensionError(json, err) {
|
|
1138
1301
|
if (json) if (err.reason === "mismatch") emitJson({
|
|
@@ -1334,6 +1497,7 @@ const showCommand$2 = defineCommand({
|
|
|
1334
1497
|
try {
|
|
1335
1498
|
const envelope = await fetchMiniAppWithDraft(workspaceId, appId, session.cookies);
|
|
1336
1499
|
const miniApp = pickMiniAppView(envelope, view);
|
|
1500
|
+
if (miniApp === null && view === "current" && envelope.draft !== null) process.stderr.write(`App ${appId} has no \`current\` view yet (not reviewed). Re-run with \`--view draft\` to see the pending record.\n`);
|
|
1337
1501
|
if (args.json) {
|
|
1338
1502
|
emitJson({
|
|
1339
1503
|
ok: true,
|
|
@@ -1345,7 +1509,7 @@ const showCommand$2 = defineCommand({
|
|
|
1345
1509
|
return exitAfterFlush(ExitCode.Ok);
|
|
1346
1510
|
}
|
|
1347
1511
|
if (miniApp === null) {
|
|
1348
|
-
if (view === "current" && envelope.draft !== null) process.stdout.write(`App ${appId} has no \`current\` view yet
|
|
1512
|
+
if (view === "current" && envelope.draft !== null) process.stdout.write(`App ${appId} has no \`current\` view yet.\n`);
|
|
1349
1513
|
else process.stdout.write(`App ${appId} has no data for view=${view}.\n`);
|
|
1350
1514
|
return exitAfterFlush(ExitCode.Ok);
|
|
1351
1515
|
}
|
|
@@ -1472,29 +1636,40 @@ const statusCommand = defineCommand({
|
|
|
1472
1636
|
const ctx = await resolveWorkspaceContext(args);
|
|
1473
1637
|
if (!ctx) return;
|
|
1474
1638
|
const { session, workspaceId } = ctx;
|
|
1475
|
-
const emit = (status) => {
|
|
1639
|
+
const emit = (status, service) => {
|
|
1476
1640
|
if (args.json) emitJson({
|
|
1477
1641
|
ok: true,
|
|
1478
1642
|
workspaceId,
|
|
1479
1643
|
appId,
|
|
1480
|
-
...status
|
|
1644
|
+
...status,
|
|
1645
|
+
serviceStatus: service?.serviceStatus ?? null,
|
|
1646
|
+
shutdownCandidateStatus: service?.shutdownCandidateStatus ?? null,
|
|
1647
|
+
scheduledShutdownAt: service?.scheduledShutdownAt ?? null
|
|
1481
1648
|
});
|
|
1482
|
-
else
|
|
1649
|
+
else {
|
|
1650
|
+
const svc = service ? ` [${service.serviceStatus}]` : "";
|
|
1651
|
+
process.stdout.write(`App ${appId} (ws ${workspaceId}): ${status.state}${svc}` + (status.rejectedMessage ? `\n reason: ${status.rejectedMessage}` : "") + (service?.scheduledShutdownAt ? `\n scheduled shutdown: ${service.scheduledShutdownAt}` : "") + "\n");
|
|
1652
|
+
}
|
|
1483
1653
|
};
|
|
1484
1654
|
try {
|
|
1485
1655
|
const once = async () => {
|
|
1486
|
-
|
|
1656
|
+
const [env, service] = await Promise.all([fetchMiniAppWithDraft(workspaceId, appId, session.cookies), fetchAppServiceStatus(workspaceId, appId, session.cookies).catch(() => null)]);
|
|
1657
|
+
return [deriveReviewState(env), service];
|
|
1487
1658
|
};
|
|
1488
1659
|
if (!args.watch) {
|
|
1489
|
-
|
|
1660
|
+
const [status, service] = await once();
|
|
1661
|
+
emit(status, service);
|
|
1490
1662
|
return exitAfterFlush(ExitCode.Ok);
|
|
1491
1663
|
}
|
|
1492
1664
|
let lastState = null;
|
|
1665
|
+
let lastServiceStatus = null;
|
|
1493
1666
|
while (true) {
|
|
1494
|
-
const status = await once();
|
|
1495
|
-
|
|
1496
|
-
|
|
1667
|
+
const [status, service] = await once();
|
|
1668
|
+
const svc = service?.serviceStatus ?? null;
|
|
1669
|
+
if (args.json) emit(status, service);
|
|
1670
|
+
else if (status.state !== lastState || svc !== lastServiceStatus) emit(status, service);
|
|
1497
1671
|
lastState = status.state;
|
|
1672
|
+
lastServiceStatus = svc;
|
|
1498
1673
|
if (status.state !== "under-review") return exitAfterFlush(ExitCode.Ok);
|
|
1499
1674
|
await new Promise((resolve) => setTimeout(resolve, intervalSec * 1e3));
|
|
1500
1675
|
}
|
|
@@ -2265,140 +2440,821 @@ const appCommand = defineCommand({
|
|
|
2265
2440
|
}
|
|
2266
2441
|
}) }
|
|
2267
2442
|
}),
|
|
2268
|
-
|
|
2443
|
+
messages: defineCommand({
|
|
2269
2444
|
meta: {
|
|
2270
|
-
name: "
|
|
2271
|
-
description: "
|
|
2445
|
+
name: "messages",
|
|
2446
|
+
description: "Inspect smart-message (formerly push) campaigns for a mini-app."
|
|
2272
2447
|
},
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
description: "
|
|
2448
|
+
subCommands: { ls: defineCommand({
|
|
2449
|
+
meta: {
|
|
2450
|
+
name: "ls",
|
|
2451
|
+
description: "List smart-message campaigns (formerly \"push\" — the 스마트 발송 menu)."
|
|
2277
2452
|
},
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2453
|
+
args: {
|
|
2454
|
+
id: {
|
|
2455
|
+
type: "positional",
|
|
2456
|
+
description: "Mini-app ID.",
|
|
2457
|
+
required: true
|
|
2458
|
+
},
|
|
2459
|
+
workspace: {
|
|
2460
|
+
type: "string",
|
|
2461
|
+
description: "Workspace ID. Defaults to the selected workspace."
|
|
2462
|
+
},
|
|
2463
|
+
page: {
|
|
2464
|
+
type: "string",
|
|
2465
|
+
description: "Page number (0-indexed).",
|
|
2466
|
+
default: "0"
|
|
2467
|
+
},
|
|
2468
|
+
size: {
|
|
2469
|
+
type: "string",
|
|
2470
|
+
description: "Page size.",
|
|
2471
|
+
default: "20"
|
|
2472
|
+
},
|
|
2473
|
+
search: {
|
|
2474
|
+
type: "string",
|
|
2475
|
+
description: "Title-contains filter. Empty matches everything."
|
|
2476
|
+
},
|
|
2477
|
+
json: {
|
|
2478
|
+
type: "boolean",
|
|
2479
|
+
description: "Emit machine-readable JSON.",
|
|
2480
|
+
default: false
|
|
2481
|
+
}
|
|
2281
2482
|
},
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2483
|
+
async run({ args }) {
|
|
2484
|
+
const appId = parseAppId(args.id);
|
|
2485
|
+
if (appId === null) {
|
|
2486
|
+
if (args.json) emitJson({
|
|
2487
|
+
ok: false,
|
|
2488
|
+
reason: "invalid-id",
|
|
2489
|
+
message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
|
|
2490
|
+
});
|
|
2491
|
+
else process.stderr.write(`app messages ls: invalid id ${JSON.stringify(args.id)}\n`);
|
|
2492
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2493
|
+
}
|
|
2494
|
+
const pageResult = parseNonNegativeInt(String(args.page), "page");
|
|
2495
|
+
if ("error" in pageResult) {
|
|
2496
|
+
if (args.json) emitJson({
|
|
2497
|
+
ok: false,
|
|
2498
|
+
reason: "invalid-page",
|
|
2499
|
+
message: pageResult.error
|
|
2500
|
+
});
|
|
2501
|
+
else process.stderr.write(`${pageResult.error}\n`);
|
|
2502
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2503
|
+
}
|
|
2504
|
+
const sizeResult = parseNonNegativeInt(String(args.size), "size");
|
|
2505
|
+
if ("error" in sizeResult) {
|
|
2506
|
+
if (args.json) emitJson({
|
|
2507
|
+
ok: false,
|
|
2508
|
+
reason: "invalid-size",
|
|
2509
|
+
message: sizeResult.error
|
|
2510
|
+
});
|
|
2511
|
+
else process.stderr.write(`${sizeResult.error}\n`);
|
|
2512
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2513
|
+
}
|
|
2514
|
+
const ctx = await resolveWorkspaceContext(args);
|
|
2515
|
+
if (!ctx) return;
|
|
2516
|
+
const { session, workspaceId } = ctx;
|
|
2517
|
+
try {
|
|
2518
|
+
const result = await fetchSmartMessageCampaigns({
|
|
2519
|
+
workspaceId,
|
|
2520
|
+
miniAppId: appId,
|
|
2521
|
+
page: pageResult.value,
|
|
2522
|
+
size: sizeResult.value,
|
|
2523
|
+
...args.search !== void 0 ? { search: String(args.search) } : {}
|
|
2524
|
+
}, session.cookies);
|
|
2525
|
+
if (args.json) {
|
|
2526
|
+
emitJson({
|
|
2527
|
+
ok: true,
|
|
2528
|
+
workspaceId,
|
|
2529
|
+
appId,
|
|
2530
|
+
campaigns: result.items,
|
|
2531
|
+
paging: result.paging
|
|
2532
|
+
});
|
|
2533
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2534
|
+
}
|
|
2535
|
+
if (result.items.length === 0) {
|
|
2536
|
+
process.stdout.write(`App ${appId} (ws ${workspaceId}): no smart-message campaigns\n`);
|
|
2537
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2538
|
+
}
|
|
2539
|
+
process.stdout.write(`App ${appId} (ws ${workspaceId}): ${result.items.length} campaign(s) on page ${result.paging.pageNumber} of ${result.paging.totalCount}\n`);
|
|
2540
|
+
for (const c of result.items) {
|
|
2541
|
+
const id = typeof c.id === "string" || typeof c.id === "number" ? c.id : typeof c.campaignId === "string" || typeof c.campaignId === "number" ? c.campaignId : "-";
|
|
2542
|
+
const title = typeof c.title === "string" ? c.title : typeof c.name === "string" ? c.name : "-";
|
|
2543
|
+
const status = typeof c.status === "string" ? c.status : "-";
|
|
2544
|
+
process.stdout.write(`${id}\t${title}\t${status}\n`);
|
|
2545
|
+
}
|
|
2546
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2547
|
+
} catch (err) {
|
|
2548
|
+
return emitFailureFromError(args.json, err);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
}) }
|
|
2552
|
+
}),
|
|
2553
|
+
events: defineCommand({
|
|
2554
|
+
meta: {
|
|
2555
|
+
name: "events",
|
|
2556
|
+
description: "Inspect custom event catalogs (log search) for a mini-app."
|
|
2557
|
+
},
|
|
2558
|
+
subCommands: { ls: defineCommand({
|
|
2559
|
+
meta: {
|
|
2560
|
+
name: "ls",
|
|
2561
|
+
description: "List custom event catalogs recorded for a mini-app (the 이벤트 menu)."
|
|
2286
2562
|
},
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2563
|
+
args: {
|
|
2564
|
+
id: {
|
|
2565
|
+
type: "positional",
|
|
2566
|
+
description: "Mini-app ID.",
|
|
2567
|
+
required: true
|
|
2568
|
+
},
|
|
2569
|
+
workspace: {
|
|
2570
|
+
type: "string",
|
|
2571
|
+
description: "Workspace ID. Defaults to the selected workspace."
|
|
2572
|
+
},
|
|
2573
|
+
page: {
|
|
2574
|
+
type: "string",
|
|
2575
|
+
description: "Page number (0-indexed).",
|
|
2576
|
+
default: "0"
|
|
2577
|
+
},
|
|
2578
|
+
size: {
|
|
2579
|
+
type: "string",
|
|
2580
|
+
description: "Page size.",
|
|
2581
|
+
default: "20"
|
|
2582
|
+
},
|
|
2583
|
+
search: {
|
|
2584
|
+
type: "string",
|
|
2585
|
+
description: "Event-name filter. Empty matches everything."
|
|
2586
|
+
},
|
|
2587
|
+
refresh: {
|
|
2588
|
+
type: "boolean",
|
|
2589
|
+
description: "Bypass the server cache and rebuild the catalog list.",
|
|
2590
|
+
default: false
|
|
2591
|
+
},
|
|
2592
|
+
json: {
|
|
2593
|
+
type: "boolean",
|
|
2594
|
+
description: "Emit machine-readable JSON.",
|
|
2595
|
+
default: false
|
|
2596
|
+
}
|
|
2291
2597
|
},
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2598
|
+
async run({ args }) {
|
|
2599
|
+
const appId = parseAppId(args.id);
|
|
2600
|
+
if (appId === null) {
|
|
2601
|
+
if (args.json) emitJson({
|
|
2602
|
+
ok: false,
|
|
2603
|
+
reason: "invalid-id",
|
|
2604
|
+
message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
|
|
2605
|
+
});
|
|
2606
|
+
else process.stderr.write(`app events ls: invalid id ${JSON.stringify(args.id)}\n`);
|
|
2607
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2608
|
+
}
|
|
2609
|
+
const pageResult = parseNonNegativeInt(String(args.page), "page");
|
|
2610
|
+
if ("error" in pageResult) {
|
|
2611
|
+
if (args.json) emitJson({
|
|
2612
|
+
ok: false,
|
|
2613
|
+
reason: "invalid-page",
|
|
2614
|
+
message: pageResult.error
|
|
2615
|
+
});
|
|
2616
|
+
else process.stderr.write(`${pageResult.error}\n`);
|
|
2617
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2618
|
+
}
|
|
2619
|
+
const sizeResult = parseNonNegativeInt(String(args.size), "size");
|
|
2620
|
+
if ("error" in sizeResult) {
|
|
2621
|
+
if (args.json) emitJson({
|
|
2622
|
+
ok: false,
|
|
2623
|
+
reason: "invalid-size",
|
|
2624
|
+
message: sizeResult.error
|
|
2625
|
+
});
|
|
2626
|
+
else process.stderr.write(`${sizeResult.error}\n`);
|
|
2627
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2628
|
+
}
|
|
2629
|
+
const ctx = await resolveWorkspaceContext(args);
|
|
2630
|
+
if (!ctx) return;
|
|
2631
|
+
const { session, workspaceId } = ctx;
|
|
2632
|
+
try {
|
|
2633
|
+
const result = await fetchAppEventCatalogs({
|
|
2634
|
+
workspaceId,
|
|
2635
|
+
miniAppId: appId,
|
|
2636
|
+
pageNumber: pageResult.value,
|
|
2637
|
+
pageSize: sizeResult.value,
|
|
2638
|
+
...args.search !== void 0 ? { search: String(args.search) } : {},
|
|
2639
|
+
...args.refresh ? { refresh: true } : {}
|
|
2640
|
+
}, session.cookies);
|
|
2641
|
+
if (args.json) {
|
|
2642
|
+
emitJson({
|
|
2643
|
+
ok: true,
|
|
2644
|
+
workspaceId,
|
|
2645
|
+
appId,
|
|
2646
|
+
events: result.results,
|
|
2647
|
+
cacheTime: result.cacheTime ?? null,
|
|
2648
|
+
paging: result.paging
|
|
2649
|
+
});
|
|
2650
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2651
|
+
}
|
|
2652
|
+
if (result.results.length === 0) {
|
|
2653
|
+
const ct = result.cacheTime ? ` (cached ${result.cacheTime})` : "";
|
|
2654
|
+
process.stdout.write(`App ${appId} (ws ${workspaceId}): no event catalogs${ct}\n`);
|
|
2655
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2656
|
+
}
|
|
2657
|
+
process.stdout.write(`App ${appId} (ws ${workspaceId}): ${result.results.length} event(s) on page ${result.paging.pageNumber} of ${result.paging.totalPages}\n`);
|
|
2658
|
+
for (const e of result.results) {
|
|
2659
|
+
const name = typeof e.name === "string" ? e.name : typeof e.eventName === "string" ? e.eventName : "-";
|
|
2660
|
+
const count = typeof e.count === "number" ? String(e.count) : typeof e.totalCount === "number" ? String(e.totalCount) : "-";
|
|
2661
|
+
process.stdout.write(`${name}\t${count}\n`);
|
|
2662
|
+
}
|
|
2663
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2664
|
+
} catch (err) {
|
|
2665
|
+
return emitFailureFromError(args.json, err);
|
|
2666
|
+
}
|
|
2296
2667
|
}
|
|
2668
|
+
}) }
|
|
2669
|
+
}),
|
|
2670
|
+
templates: defineCommand({
|
|
2671
|
+
meta: {
|
|
2672
|
+
name: "templates",
|
|
2673
|
+
description: "Inspect smart-message composer templates available for a mini-app."
|
|
2297
2674
|
},
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2675
|
+
subCommands: { ls: defineCommand({
|
|
2676
|
+
meta: {
|
|
2677
|
+
name: "ls",
|
|
2678
|
+
description: "List the smart-message composer templates available for a mini-app (the 템플릿 picker in 스마트 발송)."
|
|
2679
|
+
},
|
|
2680
|
+
args: {
|
|
2681
|
+
id: {
|
|
2682
|
+
type: "positional",
|
|
2683
|
+
description: "Mini-app ID.",
|
|
2684
|
+
required: true
|
|
2685
|
+
},
|
|
2686
|
+
workspace: {
|
|
2687
|
+
type: "string",
|
|
2688
|
+
description: "Workspace ID. Defaults to the selected workspace."
|
|
2689
|
+
},
|
|
2690
|
+
page: {
|
|
2691
|
+
type: "string",
|
|
2692
|
+
description: "Page number (0-indexed).",
|
|
2693
|
+
default: "0"
|
|
2694
|
+
},
|
|
2695
|
+
size: {
|
|
2696
|
+
type: "string",
|
|
2697
|
+
description: "Page size.",
|
|
2698
|
+
default: "20"
|
|
2699
|
+
},
|
|
2700
|
+
"content-reach-type": {
|
|
2701
|
+
type: "string",
|
|
2702
|
+
description: `Template reach bucket: ${TEMPLATE_CONTENT_REACH_TYPES.join(" | ")}. Omit for all.`
|
|
2703
|
+
},
|
|
2704
|
+
"smart-message": {
|
|
2705
|
+
type: "string",
|
|
2706
|
+
description: "Filter to templates compatible with smart-message (\"true\") or legacy push (\"false\"). Omit for all."
|
|
2707
|
+
},
|
|
2708
|
+
json: {
|
|
2709
|
+
type: "boolean",
|
|
2710
|
+
description: "Emit machine-readable JSON.",
|
|
2711
|
+
default: false
|
|
2712
|
+
}
|
|
2713
|
+
},
|
|
2714
|
+
async run({ args }) {
|
|
2715
|
+
const appId = parseAppId(args.id);
|
|
2716
|
+
if (appId === null) {
|
|
2717
|
+
if (args.json) emitJson({
|
|
2718
|
+
ok: false,
|
|
2719
|
+
reason: "invalid-id",
|
|
2720
|
+
message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
|
|
2721
|
+
});
|
|
2722
|
+
else process.stderr.write(`app templates ls: invalid id ${JSON.stringify(args.id)}\n`);
|
|
2723
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2724
|
+
}
|
|
2725
|
+
const pageResult = parseNonNegativeInt(String(args.page), "page");
|
|
2726
|
+
if ("error" in pageResult) {
|
|
2727
|
+
if (args.json) emitJson({
|
|
2728
|
+
ok: false,
|
|
2729
|
+
reason: "invalid-page",
|
|
2730
|
+
message: pageResult.error
|
|
2731
|
+
});
|
|
2732
|
+
else process.stderr.write(`${pageResult.error}\n`);
|
|
2733
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2734
|
+
}
|
|
2735
|
+
const sizeResult = parseNonNegativeInt(String(args.size), "size");
|
|
2736
|
+
if ("error" in sizeResult) {
|
|
2737
|
+
if (args.json) emitJson({
|
|
2738
|
+
ok: false,
|
|
2739
|
+
reason: "invalid-size",
|
|
2740
|
+
message: sizeResult.error
|
|
2741
|
+
});
|
|
2742
|
+
else process.stderr.write(`${sizeResult.error}\n`);
|
|
2743
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2744
|
+
}
|
|
2745
|
+
let contentReachType;
|
|
2746
|
+
if (args["content-reach-type"] !== void 0) {
|
|
2747
|
+
const upper = String(args["content-reach-type"]).toUpperCase();
|
|
2748
|
+
if (TEMPLATE_CONTENT_REACH_TYPES.includes(upper)) contentReachType = upper;
|
|
2749
|
+
else {
|
|
2750
|
+
const message = `--content-reach-type must be one of: ${TEMPLATE_CONTENT_REACH_TYPES.join(", ")}`;
|
|
2751
|
+
if (args.json) emitJson({
|
|
2752
|
+
ok: false,
|
|
2753
|
+
reason: "invalid-content-reach-type",
|
|
2754
|
+
allowed: [...TEMPLATE_CONTENT_REACH_TYPES]
|
|
2755
|
+
});
|
|
2756
|
+
else process.stderr.write(`${message}\n`);
|
|
2757
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
let isSmartMessage;
|
|
2761
|
+
if (args["smart-message"] !== void 0) {
|
|
2762
|
+
const raw = String(args["smart-message"]).toLowerCase();
|
|
2763
|
+
if (raw === "true") isSmartMessage = true;
|
|
2764
|
+
else if (raw === "false") isSmartMessage = false;
|
|
2765
|
+
else {
|
|
2766
|
+
const message = "--smart-message must be \"true\" or \"false\"";
|
|
2767
|
+
if (args.json) emitJson({
|
|
2768
|
+
ok: false,
|
|
2769
|
+
reason: "invalid-smart-message",
|
|
2770
|
+
message
|
|
2771
|
+
});
|
|
2772
|
+
else process.stderr.write(`${message}\n`);
|
|
2773
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
const ctx = await resolveWorkspaceContext(args);
|
|
2777
|
+
if (!ctx) return;
|
|
2778
|
+
const { session, workspaceId } = ctx;
|
|
2779
|
+
try {
|
|
2780
|
+
const result = await fetchAppTemplates({
|
|
2781
|
+
workspaceId,
|
|
2782
|
+
miniAppId: appId,
|
|
2783
|
+
page: pageResult.value,
|
|
2784
|
+
size: sizeResult.value,
|
|
2785
|
+
...contentReachType !== void 0 ? { contentReachType } : {},
|
|
2786
|
+
...isSmartMessage !== void 0 ? { isSmartMessage } : {}
|
|
2787
|
+
}, session.cookies);
|
|
2788
|
+
if (args.json) {
|
|
2789
|
+
emitJson({
|
|
2790
|
+
ok: true,
|
|
2791
|
+
workspaceId,
|
|
2792
|
+
appId,
|
|
2793
|
+
templates: result.templates,
|
|
2794
|
+
totalPageCount: result.totalPageCount
|
|
2795
|
+
});
|
|
2796
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2797
|
+
}
|
|
2798
|
+
if (result.templates.length === 0) {
|
|
2799
|
+
process.stdout.write(`App ${appId} (ws ${workspaceId}): no templates\n`);
|
|
2800
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2801
|
+
}
|
|
2802
|
+
process.stdout.write(`App ${appId} (ws ${workspaceId}): ${result.templates.length} template(s) of ${result.totalPageCount} page(s)\n`);
|
|
2803
|
+
for (const t of result.templates) {
|
|
2804
|
+
const id = typeof t.id === "string" || typeof t.id === "number" ? t.id : typeof t.templateId === "string" || typeof t.templateId === "number" ? t.templateId : "-";
|
|
2805
|
+
const title = typeof t.title === "string" ? t.title : typeof t.name === "string" ? t.name : "-";
|
|
2806
|
+
const type = typeof t.templateType === "string" ? t.templateType : "-";
|
|
2807
|
+
process.stdout.write(`${id}\t${title}\t${type}\n`);
|
|
2808
|
+
}
|
|
2809
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2810
|
+
} catch (err) {
|
|
2811
|
+
return emitFailureFromError(args.json, err);
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
}) }
|
|
2815
|
+
}),
|
|
2816
|
+
categories: defineCommand({
|
|
2817
|
+
meta: {
|
|
2818
|
+
name: "categories",
|
|
2819
|
+
description: "List the impression category tree used by `app register`'s `categoryIds` field."
|
|
2350
2820
|
},
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2821
|
+
args: {
|
|
2822
|
+
selectable: {
|
|
2823
|
+
type: "boolean",
|
|
2824
|
+
description: "Only show categories flagged `isSelectable: true` — the ones you can pick.",
|
|
2825
|
+
default: false
|
|
2826
|
+
},
|
|
2827
|
+
json: {
|
|
2828
|
+
type: "boolean",
|
|
2829
|
+
description: "Emit machine-readable JSON to stdout.",
|
|
2830
|
+
default: false
|
|
2831
|
+
}
|
|
2832
|
+
},
|
|
2833
|
+
async run({ args }) {
|
|
2834
|
+
const session = await readSession();
|
|
2835
|
+
if (!session) {
|
|
2836
|
+
emitNotAuthenticated(args.json);
|
|
2837
|
+
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
2838
|
+
}
|
|
2839
|
+
try {
|
|
2840
|
+
const tree = await fetchImpressionCategoryList(session.cookies);
|
|
2841
|
+
const filtered = args.selectable ? tree.filter((g) => g.categoryGroup.isSelectable).map((g) => ({
|
|
2842
|
+
...g,
|
|
2843
|
+
categoryList: g.categoryList.filter((c) => c.isSelectable).map((c) => ({
|
|
2844
|
+
...c,
|
|
2845
|
+
subCategoryList: c.subCategoryList.filter((s) => s.isSelectable)
|
|
2846
|
+
}))
|
|
2847
|
+
})) : tree;
|
|
2848
|
+
if (args.json) {
|
|
2849
|
+
emitJson({
|
|
2850
|
+
ok: true,
|
|
2851
|
+
categories: filtered
|
|
2852
|
+
});
|
|
2853
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2854
|
+
}
|
|
2855
|
+
for (const g of filtered) {
|
|
2856
|
+
const mark = g.categoryGroup.isSelectable ? "" : " (not selectable)";
|
|
2857
|
+
process.stdout.write(`[${g.categoryGroup.id}] ${g.categoryGroup.name}${mark}\n`);
|
|
2858
|
+
for (const c of g.categoryList) {
|
|
2859
|
+
const cmark = c.isSelectable ? "" : " (not selectable)";
|
|
2860
|
+
process.stdout.write(` ${c.id}\t${c.name}${cmark}\n`);
|
|
2861
|
+
for (const s of c.subCategoryList) {
|
|
2862
|
+
const smark = s.isSelectable ? "" : " (not selectable)";
|
|
2863
|
+
process.stdout.write(` ${s.id}\t${s.name}${smark}\n`);
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2868
|
+
} catch (err) {
|
|
2869
|
+
return emitFailureFromError(args.json, err);
|
|
2870
|
+
}
|
|
2355
2871
|
}
|
|
2356
|
-
},
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2872
|
+
}),
|
|
2873
|
+
"service-status": defineCommand({
|
|
2874
|
+
meta: {
|
|
2875
|
+
name: "service-status",
|
|
2876
|
+
description: "Show the server-authoritative runtime status of a mini-app (serviceStatus, shutdown schedule)."
|
|
2877
|
+
},
|
|
2878
|
+
args: {
|
|
2879
|
+
id: {
|
|
2880
|
+
type: "positional",
|
|
2881
|
+
description: "Mini-app ID.",
|
|
2882
|
+
required: true
|
|
2883
|
+
},
|
|
2884
|
+
workspace: {
|
|
2885
|
+
type: "string",
|
|
2886
|
+
description: "Workspace ID. Defaults to the selected workspace."
|
|
2887
|
+
},
|
|
2888
|
+
json: {
|
|
2889
|
+
type: "boolean",
|
|
2890
|
+
description: "Emit machine-readable JSON.",
|
|
2891
|
+
default: false
|
|
2892
|
+
}
|
|
2893
|
+
},
|
|
2894
|
+
async run({ args }) {
|
|
2895
|
+
const appId = parseAppId(args.id);
|
|
2896
|
+
if (appId === null) {
|
|
2897
|
+
if (args.json) emitJson({
|
|
2898
|
+
ok: false,
|
|
2899
|
+
reason: "invalid-id",
|
|
2900
|
+
message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
|
|
2373
2901
|
});
|
|
2374
|
-
|
|
2902
|
+
else process.stderr.write(`app service-status: invalid id ${JSON.stringify(args.id)}\n`);
|
|
2903
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
2375
2904
|
}
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2905
|
+
const ctx = await resolveWorkspaceContext(args);
|
|
2906
|
+
if (!ctx) return;
|
|
2907
|
+
const { session, workspaceId } = ctx;
|
|
2908
|
+
try {
|
|
2909
|
+
const st = await fetchAppServiceStatus(workspaceId, appId, session.cookies);
|
|
2910
|
+
if (args.json) {
|
|
2911
|
+
emitJson({
|
|
2912
|
+
ok: true,
|
|
2913
|
+
workspaceId,
|
|
2914
|
+
appId,
|
|
2915
|
+
...st
|
|
2916
|
+
});
|
|
2917
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2918
|
+
}
|
|
2919
|
+
process.stdout.write(`App ${appId} (ws ${workspaceId}):\n`);
|
|
2920
|
+
process.stdout.write(` serviceStatus: ${st.serviceStatus}\n`);
|
|
2921
|
+
process.stdout.write(` shutdownCandidateStatus: ${st.shutdownCandidateStatus ?? "null"}\n`);
|
|
2922
|
+
process.stdout.write(` scheduledShutdownAt: ${st.scheduledShutdownAt ?? "null"}\n`);
|
|
2379
2923
|
return exitAfterFlush(ExitCode.Ok);
|
|
2924
|
+
} catch (err) {
|
|
2925
|
+
return emitFailureFromError(args.json, err);
|
|
2380
2926
|
}
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2927
|
+
}
|
|
2928
|
+
}),
|
|
2929
|
+
register: defineCommand({
|
|
2930
|
+
meta: {
|
|
2931
|
+
name: "register",
|
|
2932
|
+
description: "Register a mini-app in the selected workspace from a YAML/JSON manifest. Uploads logo/thumbnail/screenshots, then submits the create payload."
|
|
2933
|
+
},
|
|
2934
|
+
args: {
|
|
2935
|
+
workspace: {
|
|
2936
|
+
type: "string",
|
|
2937
|
+
description: "Workspace ID. Defaults to the selected workspace (`aitcc workspace use`)."
|
|
2938
|
+
},
|
|
2939
|
+
config: {
|
|
2940
|
+
type: "string",
|
|
2941
|
+
description: "Path to the app manifest. Defaults to `./aitcc.app.yaml`, then `./aitcc.app.json`."
|
|
2942
|
+
},
|
|
2943
|
+
"dry-run": {
|
|
2944
|
+
type: "boolean",
|
|
2945
|
+
description: "Validate manifest + images and print the inferred submit payload; no uploads.",
|
|
2946
|
+
default: false
|
|
2947
|
+
},
|
|
2948
|
+
"accept-terms": {
|
|
2949
|
+
type: "boolean",
|
|
2950
|
+
description: "Attest to the required console legal-agreement checkboxes (see VALIDATION-RULES.md). Required for real submits.",
|
|
2951
|
+
default: false
|
|
2952
|
+
},
|
|
2953
|
+
json: {
|
|
2954
|
+
type: "boolean",
|
|
2955
|
+
description: "Emit machine-readable JSON to stdout.",
|
|
2956
|
+
default: false
|
|
2385
2957
|
}
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2958
|
+
},
|
|
2959
|
+
async run({ args }) {
|
|
2960
|
+
await runRegister({
|
|
2961
|
+
json: args.json,
|
|
2962
|
+
dryRun: args["dry-run"],
|
|
2963
|
+
acceptTerms: args["accept-terms"],
|
|
2964
|
+
...args.workspace !== void 0 ? { workspace: args.workspace } : {},
|
|
2965
|
+
...args.config !== void 0 ? { config: args.config } : {}
|
|
2966
|
+
});
|
|
2389
2967
|
}
|
|
2390
|
-
}
|
|
2391
|
-
}
|
|
2968
|
+
})
|
|
2969
|
+
}
|
|
2392
2970
|
});
|
|
2393
2971
|
//#endregion
|
|
2394
|
-
//#region src/
|
|
2395
|
-
const
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2972
|
+
//#region src/commands/completion.ts
|
|
2973
|
+
const TOP_LEVEL = [
|
|
2974
|
+
"whoami",
|
|
2975
|
+
"login",
|
|
2976
|
+
"logout",
|
|
2977
|
+
"upgrade",
|
|
2978
|
+
"workspace",
|
|
2979
|
+
"app",
|
|
2980
|
+
"members",
|
|
2981
|
+
"keys",
|
|
2982
|
+
"notices",
|
|
2983
|
+
"me",
|
|
2984
|
+
"completion"
|
|
2985
|
+
];
|
|
2986
|
+
const SUB_COMMANDS = {
|
|
2987
|
+
workspace: [
|
|
2988
|
+
"ls",
|
|
2989
|
+
"partner",
|
|
2990
|
+
"segments",
|
|
2991
|
+
"show",
|
|
2992
|
+
"terms",
|
|
2993
|
+
"use"
|
|
2994
|
+
],
|
|
2995
|
+
app: [
|
|
2996
|
+
"bundles",
|
|
2997
|
+
"categories",
|
|
2998
|
+
"certs",
|
|
2999
|
+
"events",
|
|
3000
|
+
"ls",
|
|
3001
|
+
"messages",
|
|
3002
|
+
"metrics",
|
|
3003
|
+
"ratings",
|
|
3004
|
+
"register",
|
|
3005
|
+
"reports",
|
|
3006
|
+
"service-status",
|
|
3007
|
+
"share-rewards",
|
|
3008
|
+
"show",
|
|
3009
|
+
"status",
|
|
3010
|
+
"templates"
|
|
3011
|
+
],
|
|
3012
|
+
notices: [
|
|
3013
|
+
"categories",
|
|
3014
|
+
"ls",
|
|
3015
|
+
"show"
|
|
3016
|
+
],
|
|
3017
|
+
me: ["terms"],
|
|
3018
|
+
completion: [
|
|
3019
|
+
"bash",
|
|
3020
|
+
"zsh",
|
|
3021
|
+
"fish"
|
|
3022
|
+
]
|
|
3023
|
+
};
|
|
3024
|
+
function emitBash() {
|
|
3025
|
+
const top = TOP_LEVEL.join(" ");
|
|
3026
|
+
const cases = [];
|
|
3027
|
+
for (const [ns, subs] of Object.entries(SUB_COMMANDS)) {
|
|
3028
|
+
cases.push(` ${ns})`);
|
|
3029
|
+
cases.push(` COMPREPLY=( $(compgen -W "${subs.join(" ")}" -- "$cur") )`);
|
|
3030
|
+
cases.push(" return 0 ;;");
|
|
3031
|
+
}
|
|
3032
|
+
return `# bash completion for aitcc. Generated by \`aitcc completion bash\`.
|
|
3033
|
+
# Install:
|
|
3034
|
+
# aitcc completion bash > /etc/bash_completion.d/aitcc
|
|
3035
|
+
# # or add to ~/.bashrc:
|
|
3036
|
+
# source <(aitcc completion bash)
|
|
3037
|
+
|
|
3038
|
+
_aitcc_completion() {
|
|
3039
|
+
local cur prev
|
|
3040
|
+
COMPREPLY=()
|
|
3041
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
3042
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
3043
|
+
|
|
3044
|
+
if [[ $COMP_CWORD -eq 1 ]]; then
|
|
3045
|
+
COMPREPLY=( $(compgen -W "${top}" -- "$cur") )
|
|
3046
|
+
return 0
|
|
3047
|
+
fi
|
|
3048
|
+
|
|
3049
|
+
case "\${COMP_WORDS[1]}" in
|
|
3050
|
+
${cases.join("\n")}
|
|
3051
|
+
esac
|
|
3052
|
+
|
|
3053
|
+
return 0
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
complete -F _aitcc_completion aitcc
|
|
3057
|
+
`;
|
|
3058
|
+
}
|
|
3059
|
+
function emitZsh() {
|
|
3060
|
+
const top = TOP_LEVEL.join(" ");
|
|
3061
|
+
const nsClauses = [];
|
|
3062
|
+
for (const [ns, subs] of Object.entries(SUB_COMMANDS)) nsClauses.push(` ${ns}) _values 'subcommand' ${subs.map((s) => `'${s}'`).join(" ")} ;;`);
|
|
3063
|
+
return `#compdef aitcc
|
|
3064
|
+
# zsh completion for aitcc. Generated by \`aitcc completion zsh\`.
|
|
3065
|
+
# Install:
|
|
3066
|
+
# aitcc completion zsh > "\${fpath[1]}/_aitcc"
|
|
3067
|
+
# # then in a fresh shell (or run \`autoload -U compinit && compinit\`)
|
|
3068
|
+
|
|
3069
|
+
_aitcc() {
|
|
3070
|
+
local -a commands
|
|
3071
|
+
commands=(${TOP_LEVEL.map((c) => `'${c}'`).join(" ")})
|
|
3072
|
+
|
|
3073
|
+
if (( CURRENT == 2 )); then
|
|
3074
|
+
_values 'command' ${TOP_LEVEL.map((c) => `'${c}'`).join(" ")}
|
|
3075
|
+
return
|
|
3076
|
+
fi
|
|
3077
|
+
|
|
3078
|
+
case "\${words[2]}" in
|
|
3079
|
+
${nsClauses.join("\n")}
|
|
3080
|
+
esac
|
|
3081
|
+
}
|
|
3082
|
+
|
|
3083
|
+
_aitcc "$@"
|
|
3084
|
+
# ${top}
|
|
3085
|
+
`;
|
|
3086
|
+
}
|
|
3087
|
+
function emitFish() {
|
|
3088
|
+
const lines = [
|
|
3089
|
+
"# fish completion for aitcc. Generated by `aitcc completion fish`.",
|
|
3090
|
+
"# Install:",
|
|
3091
|
+
"# aitcc completion fish > ~/.config/fish/completions/aitcc.fish",
|
|
3092
|
+
""
|
|
3093
|
+
];
|
|
3094
|
+
for (const c of TOP_LEVEL) lines.push(`complete -c aitcc -n "__fish_use_subcommand" -a "${c}" -f`);
|
|
3095
|
+
for (const [ns, subs] of Object.entries(SUB_COMMANDS)) for (const s of subs) lines.push(`complete -c aitcc -n "__fish_seen_subcommand_from ${ns}" -a "${s}" -f`);
|
|
3096
|
+
return `${lines.join("\n")}\n`;
|
|
3097
|
+
}
|
|
3098
|
+
const completionCommand = defineCommand({
|
|
3099
|
+
meta: {
|
|
3100
|
+
name: "completion",
|
|
3101
|
+
description: "Emit a shell completion script for bash, zsh, or fish."
|
|
3102
|
+
},
|
|
3103
|
+
args: {
|
|
3104
|
+
shell: {
|
|
3105
|
+
type: "positional",
|
|
3106
|
+
description: "Target shell: bash, zsh, or fish.",
|
|
3107
|
+
required: false
|
|
3108
|
+
},
|
|
3109
|
+
json: {
|
|
3110
|
+
type: "boolean",
|
|
3111
|
+
description: "Emit { ok: false, reason: 'invalid-shell' } on bad input.",
|
|
3112
|
+
default: false
|
|
3113
|
+
}
|
|
3114
|
+
},
|
|
3115
|
+
async run({ args }) {
|
|
3116
|
+
const raw = args.shell === void 0 ? "" : String(args.shell).toLowerCase();
|
|
3117
|
+
if (raw === "bash") {
|
|
3118
|
+
process.stdout.write(emitBash());
|
|
3119
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
3120
|
+
}
|
|
3121
|
+
if (raw === "zsh") {
|
|
3122
|
+
process.stdout.write(emitZsh());
|
|
3123
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
3124
|
+
}
|
|
3125
|
+
if (raw === "fish") {
|
|
3126
|
+
process.stdout.write(emitFish());
|
|
3127
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
3128
|
+
}
|
|
3129
|
+
if (args.json) emitJson({
|
|
3130
|
+
ok: false,
|
|
3131
|
+
reason: "invalid-shell",
|
|
3132
|
+
allowed: [
|
|
3133
|
+
"bash",
|
|
3134
|
+
"zsh",
|
|
3135
|
+
"fish"
|
|
3136
|
+
],
|
|
3137
|
+
message: `completion shell must be one of: bash, zsh, fish (got ${JSON.stringify(raw)})`
|
|
3138
|
+
});
|
|
3139
|
+
else process.stderr.write("Usage: aitcc completion <bash|zsh|fish>\n\nExamples:\n aitcc completion bash > /etc/bash_completion.d/aitcc\n aitcc completion zsh > \"${fpath[1]}/_aitcc\"\n aitcc completion fish > ~/.config/fish/completions/aitcc.fish\n");
|
|
3140
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
3141
|
+
}
|
|
3142
|
+
});
|
|
3143
|
+
//#endregion
|
|
3144
|
+
//#region src/api/api-keys.ts
|
|
3145
|
+
const BASE$3 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
|
|
3146
|
+
async function fetchApiKeys(workspaceId, cookies, opts = {}) {
|
|
3147
|
+
const raw = await requestConsoleApi({
|
|
3148
|
+
url: `${BASE$3}/workspaces/${workspaceId}/api-keys`,
|
|
3149
|
+
cookies,
|
|
3150
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
3151
|
+
});
|
|
3152
|
+
if (!Array.isArray(raw)) throw new Error(`Unexpected api-keys shape for workspace=${workspaceId}: not an array`);
|
|
3153
|
+
return raw.map((entry, index) => normalizeKey(entry, workspaceId, index));
|
|
3154
|
+
}
|
|
3155
|
+
function normalizeKey(raw, workspaceId, index) {
|
|
3156
|
+
if (raw === null || typeof raw !== "object") throw new Error(`Unexpected api-key entry at index ${index} for workspace=${workspaceId}: not an object`);
|
|
3157
|
+
const rec = raw;
|
|
3158
|
+
const rawId = rec.id ?? rec.apiKeyId ?? rec.keyId;
|
|
3159
|
+
if (typeof rawId !== "string" && typeof rawId !== "number") throw new Error(`Unexpected api-key entry at index ${index} for workspace=${workspaceId}: missing id`);
|
|
3160
|
+
const rawName = rec.name ?? rec.apiKeyName ?? rec.keyName ?? rec.description;
|
|
3161
|
+
const name = typeof rawName === "string" ? rawName : void 0;
|
|
3162
|
+
const { id: _id, apiKeyId: _aid, keyId: _kid, name: _n, apiKeyName: _an, keyName: _kn, description: _d, ...extra } = rec;
|
|
3163
|
+
return {
|
|
3164
|
+
id: rawId,
|
|
3165
|
+
name,
|
|
3166
|
+
extra
|
|
3167
|
+
};
|
|
3168
|
+
}
|
|
3169
|
+
const keysCommand = defineCommand({
|
|
3170
|
+
meta: {
|
|
3171
|
+
name: "keys",
|
|
3172
|
+
description: "Inspect console API keys used for deploy automation."
|
|
3173
|
+
},
|
|
3174
|
+
subCommands: { ls: defineCommand({
|
|
3175
|
+
meta: {
|
|
3176
|
+
name: "ls",
|
|
3177
|
+
description: "List console API keys in the selected workspace."
|
|
3178
|
+
},
|
|
3179
|
+
args: {
|
|
3180
|
+
workspace: {
|
|
3181
|
+
type: "string",
|
|
3182
|
+
description: "Workspace ID. Defaults to the selected workspace (`aitcc workspace use`)."
|
|
3183
|
+
},
|
|
3184
|
+
json: {
|
|
3185
|
+
type: "boolean",
|
|
3186
|
+
description: "Emit machine-readable JSON to stdout.",
|
|
3187
|
+
default: false
|
|
3188
|
+
}
|
|
3189
|
+
},
|
|
3190
|
+
async run({ args }) {
|
|
3191
|
+
const ctx = await resolveWorkspaceContext(args);
|
|
3192
|
+
if (!ctx) return;
|
|
3193
|
+
const { session, workspaceId } = ctx;
|
|
3194
|
+
try {
|
|
3195
|
+
const keys = await fetchApiKeys(workspaceId, session.cookies);
|
|
3196
|
+
if (args.json) {
|
|
3197
|
+
emitJson({
|
|
3198
|
+
ok: true,
|
|
3199
|
+
workspaceId,
|
|
3200
|
+
keys: keys.map((k) => ({
|
|
3201
|
+
id: k.id,
|
|
3202
|
+
name: k.name ?? null,
|
|
3203
|
+
extra: k.extra
|
|
3204
|
+
})),
|
|
3205
|
+
...keys.length === 0 ? { needsKey: true } : {}
|
|
3206
|
+
});
|
|
3207
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
3208
|
+
}
|
|
3209
|
+
if (keys.length === 0) {
|
|
3210
|
+
process.stdout.write(`No API keys in workspace ${workspaceId}.\n`);
|
|
3211
|
+
process.stderr.write("Hint: issue a key from the console UI (API 키 → 발급받기) to enable deploy automation.\n");
|
|
3212
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
3213
|
+
}
|
|
3214
|
+
process.stdout.write(`${keys.length} API key(s) in workspace ${workspaceId}:\n`);
|
|
3215
|
+
for (const k of keys) {
|
|
3216
|
+
const name = k.name ?? "(unnamed)";
|
|
3217
|
+
process.stdout.write(`${k.id}\t${name}\n`);
|
|
3218
|
+
}
|
|
3219
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
3220
|
+
} catch (err) {
|
|
3221
|
+
return emitFailureFromError(args.json, err);
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
}) }
|
|
3225
|
+
});
|
|
3226
|
+
//#endregion
|
|
3227
|
+
//#region src/api/me.ts
|
|
3228
|
+
const BASE$2 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
|
|
3229
|
+
const MEMBER_USER_INFO_URL = `${BASE$2}/members/me/user-info`;
|
|
3230
|
+
async function fetchConsoleMemberUserInfo(cookies, opts = {}) {
|
|
3231
|
+
return requestConsoleApi({
|
|
3232
|
+
url: MEMBER_USER_INFO_URL,
|
|
3233
|
+
cookies,
|
|
3234
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
3235
|
+
});
|
|
3236
|
+
}
|
|
3237
|
+
async function fetchUserTerms(cookies, opts = {}) {
|
|
3238
|
+
const raw = await requestConsoleApi({
|
|
3239
|
+
url: `${BASE$2}/console-user-terms/me`,
|
|
3240
|
+
cookies,
|
|
2400
3241
|
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
2401
3242
|
});
|
|
3243
|
+
if (!Array.isArray(raw)) throw new Error("Unexpected user-terms shape: not an array");
|
|
3244
|
+
return raw.map((entry, i) => {
|
|
3245
|
+
if (!entry || typeof entry !== "object") throw new Error(`Unexpected user-terms entry at index ${i}`);
|
|
3246
|
+
const e = entry;
|
|
3247
|
+
return {
|
|
3248
|
+
required: Boolean(e.required),
|
|
3249
|
+
termsId: typeof e.termsId === "number" ? e.termsId : 0,
|
|
3250
|
+
revisionId: typeof e.revisionId === "number" ? e.revisionId : 0,
|
|
3251
|
+
title: typeof e.title === "string" ? e.title : "",
|
|
3252
|
+
contentsUrl: typeof e.contentsUrl === "string" ? e.contentsUrl : "",
|
|
3253
|
+
actionType: typeof e.actionType === "string" ? e.actionType : "",
|
|
3254
|
+
isAgreed: Boolean(e.isAgreed),
|
|
3255
|
+
isOneTimeConsent: Boolean(e.isOneTimeConsent)
|
|
3256
|
+
};
|
|
3257
|
+
});
|
|
2402
3258
|
}
|
|
2403
3259
|
//#endregion
|
|
2404
3260
|
//#region src/cdp.ts
|
|
@@ -3054,6 +3910,54 @@ const logoutCommand = defineCommand({
|
|
|
3054
3910
|
}
|
|
3055
3911
|
});
|
|
3056
3912
|
//#endregion
|
|
3913
|
+
//#region src/commands/me.ts
|
|
3914
|
+
function formatTermLine(t) {
|
|
3915
|
+
return ` ${t.isAgreed ? "[agreed]" : "[pending]"}${t.required ? " required" : ""} ${t.title}\n ${t.contentsUrl}\n`;
|
|
3916
|
+
}
|
|
3917
|
+
const meCommand = defineCommand({
|
|
3918
|
+
meta: {
|
|
3919
|
+
name: "me",
|
|
3920
|
+
description: "Inspect account-level settings for the signed-in user."
|
|
3921
|
+
},
|
|
3922
|
+
subCommands: { terms: defineCommand({
|
|
3923
|
+
meta: {
|
|
3924
|
+
name: "terms",
|
|
3925
|
+
description: "Show the console-level terms of agreement for the signed-in account."
|
|
3926
|
+
},
|
|
3927
|
+
args: { json: {
|
|
3928
|
+
type: "boolean",
|
|
3929
|
+
description: "Emit machine-readable JSON to stdout.",
|
|
3930
|
+
default: false
|
|
3931
|
+
} },
|
|
3932
|
+
async run({ args }) {
|
|
3933
|
+
const session = await readSession();
|
|
3934
|
+
if (!session) {
|
|
3935
|
+
emitNotAuthenticated(args.json);
|
|
3936
|
+
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
3937
|
+
}
|
|
3938
|
+
try {
|
|
3939
|
+
const terms = await fetchUserTerms(session.cookies);
|
|
3940
|
+
if (args.json) {
|
|
3941
|
+
emitJson({
|
|
3942
|
+
ok: true,
|
|
3943
|
+
terms
|
|
3944
|
+
});
|
|
3945
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
3946
|
+
}
|
|
3947
|
+
if (terms.length === 0) {
|
|
3948
|
+
process.stdout.write("No console-level terms required.\n");
|
|
3949
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
3950
|
+
}
|
|
3951
|
+
process.stdout.write("Console account terms:\n");
|
|
3952
|
+
for (const t of terms) process.stdout.write(formatTermLine(t));
|
|
3953
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
3954
|
+
} catch (err) {
|
|
3955
|
+
return emitFailureFromError(args.json, err);
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
}) }
|
|
3959
|
+
});
|
|
3960
|
+
//#endregion
|
|
3057
3961
|
//#region src/api/members.ts
|
|
3058
3962
|
const BASE$1 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
|
|
3059
3963
|
async function fetchWorkspaceMembers(workspaceId, cookies, opts = {}) {
|
|
@@ -3391,507 +4295,824 @@ const noticesCommand = defineCommand({
|
|
|
3391
4295
|
return emitFailureFromError(args.json, err);
|
|
3392
4296
|
}
|
|
3393
4297
|
}
|
|
3394
|
-
})
|
|
4298
|
+
})
|
|
4299
|
+
}
|
|
4300
|
+
});
|
|
4301
|
+
//#endregion
|
|
4302
|
+
//#region src/github.ts
|
|
4303
|
+
const REPO_OWNER = "apps-in-toss-community";
|
|
4304
|
+
const REPO_NAME = "console-cli";
|
|
4305
|
+
function defaultHeaders() {
|
|
4306
|
+
const headers = {
|
|
4307
|
+
Accept: "application/vnd.github+json",
|
|
4308
|
+
"User-Agent": "aitcc",
|
|
4309
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
4310
|
+
};
|
|
4311
|
+
const token = process.env.GITHUB_TOKEN;
|
|
4312
|
+
if (token && token.length > 0) headers.Authorization = `Bearer ${token}`;
|
|
4313
|
+
return headers;
|
|
4314
|
+
}
|
|
4315
|
+
async function fetchLatestRelease() {
|
|
4316
|
+
const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;
|
|
4317
|
+
const res = await fetch(url, { headers: defaultHeaders() });
|
|
4318
|
+
if (!res.ok) throw new Error(`GitHub releases/latest returned ${res.status} ${res.statusText}`);
|
|
4319
|
+
return await res.json();
|
|
4320
|
+
}
|
|
4321
|
+
/**
|
|
4322
|
+
* Conditional GET against `releases/latest`. If the server returns 304 we
|
|
4323
|
+
* learn "no change" without consuming a core rate-limit slot. Intended for
|
|
4324
|
+
* the background update check, which re-runs often; `fetchLatestRelease()`
|
|
4325
|
+
* remains the right call when the upgrade command actually needs the body.
|
|
4326
|
+
*/
|
|
4327
|
+
async function fetchLatestReleaseConditional(previousEtag) {
|
|
4328
|
+
const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;
|
|
4329
|
+
const headers = defaultHeaders();
|
|
4330
|
+
if (previousEtag && previousEtag.length > 0) headers["If-None-Match"] = previousEtag;
|
|
4331
|
+
const res = await fetch(url, { headers });
|
|
4332
|
+
const etag = res.headers.get("etag") ?? void 0;
|
|
4333
|
+
if (res.status === 304) return {
|
|
4334
|
+
status: "not-modified",
|
|
4335
|
+
etag
|
|
4336
|
+
};
|
|
4337
|
+
if (!res.ok) throw new Error(`GitHub releases/latest returned ${res.status} ${res.statusText}`);
|
|
4338
|
+
return {
|
|
4339
|
+
status: "updated",
|
|
4340
|
+
release: await res.json(),
|
|
4341
|
+
etag
|
|
4342
|
+
};
|
|
4343
|
+
}
|
|
4344
|
+
function versionFromTag(tag) {
|
|
4345
|
+
const at = tag.lastIndexOf("@");
|
|
4346
|
+
const candidate = at >= 0 ? tag.slice(at + 1) : tag;
|
|
4347
|
+
return candidate.startsWith("v") ? candidate.slice(1) : candidate;
|
|
4348
|
+
}
|
|
4349
|
+
//#endregion
|
|
4350
|
+
//#region src/platform.ts
|
|
4351
|
+
function detectPlatform() {
|
|
4352
|
+
let os;
|
|
4353
|
+
switch (process.platform) {
|
|
4354
|
+
case "linux":
|
|
4355
|
+
os = "linux";
|
|
4356
|
+
break;
|
|
4357
|
+
case "darwin":
|
|
4358
|
+
os = "darwin";
|
|
4359
|
+
break;
|
|
4360
|
+
case "win32":
|
|
4361
|
+
os = "windows";
|
|
4362
|
+
break;
|
|
4363
|
+
default: return null;
|
|
4364
|
+
}
|
|
4365
|
+
let arch;
|
|
4366
|
+
switch (process.arch) {
|
|
4367
|
+
case "x64":
|
|
4368
|
+
arch = "x64";
|
|
4369
|
+
break;
|
|
4370
|
+
case "arm64":
|
|
4371
|
+
arch = "arm64";
|
|
4372
|
+
break;
|
|
4373
|
+
default: return null;
|
|
4374
|
+
}
|
|
4375
|
+
if (os === "windows" && arch === "arm64") return null;
|
|
4376
|
+
return {
|
|
4377
|
+
os,
|
|
4378
|
+
arch,
|
|
4379
|
+
assetName: `aitcc-${os}-${arch}${os === "windows" ? ".exe" : ""}`
|
|
4380
|
+
};
|
|
4381
|
+
}
|
|
4382
|
+
//#endregion
|
|
4383
|
+
//#region src/semver.ts
|
|
4384
|
+
function parseSemver(v) {
|
|
4385
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v);
|
|
4386
|
+
if (!m) return null;
|
|
4387
|
+
return {
|
|
4388
|
+
major: +m[1],
|
|
4389
|
+
minor: +m[2],
|
|
4390
|
+
patch: +m[3],
|
|
4391
|
+
pre: m[4] ?? ""
|
|
4392
|
+
};
|
|
4393
|
+
}
|
|
4394
|
+
function compareSemver(a, b) {
|
|
4395
|
+
const pa = parseSemver(a);
|
|
4396
|
+
const pb = parseSemver(b);
|
|
4397
|
+
if (!pa || !pb) return 0;
|
|
4398
|
+
if (pa.major !== pb.major) return pa.major > pb.major ? 1 : -1;
|
|
4399
|
+
if (pa.minor !== pb.minor) return pa.minor > pb.minor ? 1 : -1;
|
|
4400
|
+
if (pa.patch !== pb.patch) return pa.patch > pb.patch ? 1 : -1;
|
|
4401
|
+
if (pa.pre === pb.pre) return 0;
|
|
4402
|
+
if (pa.pre === "") return 1;
|
|
4403
|
+
if (pb.pre === "") return -1;
|
|
4404
|
+
return pa.pre > pb.pre ? 1 : -1;
|
|
4405
|
+
}
|
|
4406
|
+
//#endregion
|
|
4407
|
+
//#region src/version.ts
|
|
4408
|
+
function resolveVersion() {
|
|
4409
|
+
try {
|
|
4410
|
+
const injected = globalThis.AITCC_VERSION;
|
|
4411
|
+
if (typeof injected === "string" && injected.length > 0) return injected;
|
|
4412
|
+
} catch {}
|
|
4413
|
+
try {
|
|
4414
|
+
return "0.1.14";
|
|
4415
|
+
} catch {}
|
|
4416
|
+
return "0.0.0-dev";
|
|
4417
|
+
}
|
|
4418
|
+
const VERSION = resolveVersion();
|
|
4419
|
+
//#endregion
|
|
4420
|
+
//#region src/commands/upgrade.ts
|
|
4421
|
+
function isStandaloneBinary() {
|
|
4422
|
+
return basename(process.execPath).toLowerCase().startsWith("aitcc");
|
|
4423
|
+
}
|
|
4424
|
+
const upgradeCommand = defineCommand({
|
|
4425
|
+
meta: {
|
|
4426
|
+
name: "upgrade",
|
|
4427
|
+
description: "Download the latest release binary from GitHub and replace the current one."
|
|
4428
|
+
},
|
|
4429
|
+
args: {
|
|
4430
|
+
json: {
|
|
4431
|
+
type: "boolean",
|
|
4432
|
+
description: "Emit machine-readable JSON to stdout.",
|
|
4433
|
+
default: false
|
|
4434
|
+
},
|
|
4435
|
+
force: {
|
|
4436
|
+
type: "boolean",
|
|
4437
|
+
description: "Re-install even if already on the latest version.",
|
|
4438
|
+
default: false
|
|
4439
|
+
},
|
|
4440
|
+
"dry-run": {
|
|
4441
|
+
type: "boolean",
|
|
4442
|
+
description: "Check for updates without downloading or replacing.",
|
|
4443
|
+
default: false
|
|
4444
|
+
}
|
|
4445
|
+
},
|
|
4446
|
+
async run({ args }) {
|
|
4447
|
+
const emit = (payload, human) => {
|
|
4448
|
+
if (args.json) process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
4449
|
+
else process.stdout.write(`${human}\n`);
|
|
4450
|
+
};
|
|
4451
|
+
const emitError = (payload, human) => {
|
|
4452
|
+
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
4453
|
+
ok: false,
|
|
4454
|
+
...payload
|
|
4455
|
+
})}\n`);
|
|
4456
|
+
else process.stderr.write(`${human}\n`);
|
|
4457
|
+
};
|
|
4458
|
+
let release;
|
|
4459
|
+
try {
|
|
4460
|
+
release = await fetchLatestRelease();
|
|
4461
|
+
} catch (err) {
|
|
4462
|
+
emitError({
|
|
4463
|
+
reason: "network-error",
|
|
4464
|
+
message: err.message
|
|
4465
|
+
}, `Failed to query GitHub releases: ${err.message}`);
|
|
4466
|
+
process.exit(ExitCode.NetworkError);
|
|
4467
|
+
}
|
|
4468
|
+
const latest = versionFromTag(release.tag_name);
|
|
4469
|
+
const current = VERSION;
|
|
4470
|
+
if (!(compareSemver(latest, current) > 0 || args.force)) {
|
|
4471
|
+
emit({
|
|
4472
|
+
ok: true,
|
|
4473
|
+
status: "already-latest",
|
|
4474
|
+
current,
|
|
4475
|
+
latest
|
|
4476
|
+
}, `Already on the latest version (${current}).`);
|
|
4477
|
+
process.exit(ExitCode.UpgradeAlreadyLatest);
|
|
4478
|
+
}
|
|
4479
|
+
if (args["dry-run"]) {
|
|
4480
|
+
emit({
|
|
4481
|
+
ok: true,
|
|
4482
|
+
status: "update-available",
|
|
4483
|
+
current,
|
|
4484
|
+
latest,
|
|
4485
|
+
url: release.html_url
|
|
4486
|
+
}, `Update available: ${current} → ${latest}\n${release.html_url}`);
|
|
4487
|
+
return;
|
|
4488
|
+
}
|
|
4489
|
+
if (!isStandaloneBinary()) {
|
|
4490
|
+
emitError({
|
|
4491
|
+
reason: "not-standalone",
|
|
4492
|
+
current,
|
|
4493
|
+
latest,
|
|
4494
|
+
hint: "npm i -g @ait-co/console-cli@latest"
|
|
4495
|
+
}, [
|
|
4496
|
+
"This install was launched via Node, not the standalone binary.",
|
|
4497
|
+
"Self-upgrade is only supported for the compiled binary.",
|
|
4498
|
+
`Run: npm i -g @ait-co/console-cli@latest (currently ${current}, latest ${latest})`
|
|
4499
|
+
].join("\n"));
|
|
4500
|
+
process.exit(ExitCode.UpgradeUnavailable);
|
|
4501
|
+
}
|
|
4502
|
+
const platform = detectPlatform();
|
|
4503
|
+
if (!platform) {
|
|
4504
|
+
emitError({
|
|
4505
|
+
reason: "unsupported-platform",
|
|
4506
|
+
platform: process.platform,
|
|
4507
|
+
arch: process.arch
|
|
4508
|
+
}, `No prebuilt binary for ${process.platform}/${process.arch}.`);
|
|
4509
|
+
process.exit(ExitCode.UpgradeUnavailable);
|
|
4510
|
+
}
|
|
4511
|
+
const asset = release.assets.find((a) => a.name === platform.assetName);
|
|
4512
|
+
if (!asset) {
|
|
4513
|
+
emitError({
|
|
4514
|
+
reason: "asset-missing",
|
|
4515
|
+
assetName: platform.assetName,
|
|
4516
|
+
tag: release.tag_name
|
|
4517
|
+
}, `Release ${release.tag_name} has no asset named ${platform.assetName}. It may still be uploading.`);
|
|
4518
|
+
process.exit(ExitCode.UpgradeUnavailable);
|
|
4519
|
+
}
|
|
4520
|
+
const exePath = process.execPath;
|
|
4521
|
+
const stagingPath = `${exePath}.new.${Date.now()}`;
|
|
4522
|
+
if (!args.json) process.stdout.write(`Downloading ${asset.name} (${latest})...\n`);
|
|
4523
|
+
try {
|
|
4524
|
+
const res = await fetch(asset.browser_download_url);
|
|
4525
|
+
if (!res.ok || !res.body) throw new Error(`Download failed: ${res.status} ${res.statusText}`);
|
|
4526
|
+
await writeFile(stagingPath, new Uint8Array(await res.arrayBuffer()), { mode: 493 });
|
|
4527
|
+
await chmod(stagingPath, 493);
|
|
4528
|
+
} catch (err) {
|
|
4529
|
+
emitError({
|
|
4530
|
+
reason: "download-failed",
|
|
4531
|
+
message: err.message
|
|
4532
|
+
}, `Failed to download new binary: ${err.message}`);
|
|
4533
|
+
process.exit(ExitCode.NetworkError);
|
|
4534
|
+
}
|
|
4535
|
+
try {
|
|
4536
|
+
if (process.platform === "win32") {
|
|
4537
|
+
await rename(exePath, `${exePath}.old`);
|
|
4538
|
+
await rename(stagingPath, exePath);
|
|
4539
|
+
} else await rename(stagingPath, exePath);
|
|
4540
|
+
} catch (err) {
|
|
4541
|
+
emitError({
|
|
4542
|
+
reason: "replace-failed",
|
|
4543
|
+
message: err.message,
|
|
4544
|
+
exePath,
|
|
4545
|
+
stagingPath
|
|
4546
|
+
}, `Failed to replace binary at ${exePath}: ${err.message}`);
|
|
4547
|
+
process.exit(ExitCode.Generic);
|
|
4548
|
+
}
|
|
4549
|
+
emit({
|
|
4550
|
+
ok: true,
|
|
4551
|
+
status: "upgraded",
|
|
4552
|
+
from: current,
|
|
4553
|
+
to: latest,
|
|
4554
|
+
installedAt: exePath,
|
|
4555
|
+
installedIn: dirname(exePath)
|
|
4556
|
+
}, `Upgraded aitcc: ${current} → ${latest}`);
|
|
4557
|
+
}
|
|
4558
|
+
});
|
|
4559
|
+
//#endregion
|
|
4560
|
+
//#region src/update-check.ts
|
|
4561
|
+
const UPDATE_CHECK_INTERVAL_MS = 1440 * 60 * 1e3;
|
|
4562
|
+
async function readCache() {
|
|
4563
|
+
let raw;
|
|
4564
|
+
try {
|
|
4565
|
+
raw = await readFile(upgradeCheckPath(), "utf8");
|
|
4566
|
+
} catch {
|
|
4567
|
+
return null;
|
|
4568
|
+
}
|
|
4569
|
+
let parsed;
|
|
4570
|
+
try {
|
|
4571
|
+
parsed = JSON.parse(raw);
|
|
4572
|
+
} catch {
|
|
4573
|
+
return null;
|
|
4574
|
+
}
|
|
4575
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
4576
|
+
const obj = parsed;
|
|
4577
|
+
if (typeof obj.lastCheckedAt !== "string") return null;
|
|
4578
|
+
if (obj.latestTag !== void 0 && typeof obj.latestTag !== "string") return null;
|
|
4579
|
+
if (obj.etag !== void 0 && typeof obj.etag !== "string") return null;
|
|
4580
|
+
return {
|
|
4581
|
+
lastCheckedAt: obj.lastCheckedAt,
|
|
4582
|
+
...obj.latestTag !== void 0 ? { latestTag: obj.latestTag } : {},
|
|
4583
|
+
...obj.etag !== void 0 ? { etag: obj.etag } : {}
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4586
|
+
async function writeCache(entry) {
|
|
4587
|
+
const path = upgradeCheckPath();
|
|
4588
|
+
await mkdir(dirname(path), { recursive: true });
|
|
4589
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 10)}.tmp`;
|
|
4590
|
+
try {
|
|
4591
|
+
await writeFile(tmp, JSON.stringify(entry, null, 2), { mode: 384 });
|
|
4592
|
+
await rename(tmp, path);
|
|
4593
|
+
} catch (err) {
|
|
4594
|
+
await unlink(tmp).catch(() => {});
|
|
4595
|
+
throw err;
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4598
|
+
/** Has the throttle window elapsed since the last recorded check? */
|
|
4599
|
+
function isDueForCheck(cache, now = Date.now(), intervalMs = UPDATE_CHECK_INTERVAL_MS) {
|
|
4600
|
+
if (!cache) return true;
|
|
4601
|
+
const last = Date.parse(cache.lastCheckedAt);
|
|
4602
|
+
if (!Number.isFinite(last)) return true;
|
|
4603
|
+
if (now < last) return true;
|
|
4604
|
+
return now - last >= intervalMs;
|
|
4605
|
+
}
|
|
4606
|
+
/**
|
|
4607
|
+
* Perform the throttled update check. Returns the final cache entry (for
|
|
4608
|
+
* testing) or null when skipped. Never throws — network errors are
|
|
4609
|
+
* intentionally swallowed so they never interrupt the foreground command.
|
|
4610
|
+
*/
|
|
4611
|
+
async function maybeCheckForUpdate(opts = {}) {
|
|
4612
|
+
const env = opts.env ?? process.env;
|
|
4613
|
+
const isTTY = opts.isTTY ?? Boolean(process.stderr.isTTY);
|
|
4614
|
+
const now = opts.now ?? Date.now();
|
|
4615
|
+
const intervalMs = opts.intervalMs ?? 864e5;
|
|
4616
|
+
const optOut = env.AITCC_NO_UPDATE_CHECK;
|
|
4617
|
+
if (optOut && optOut !== "0" && optOut.toLowerCase() !== "false") return null;
|
|
4618
|
+
if (!isTTY) return null;
|
|
4619
|
+
const cache = await readCache();
|
|
4620
|
+
if (!isDueForCheck(cache, now, intervalMs)) return null;
|
|
4621
|
+
const nowIso = new Date(now).toISOString();
|
|
4622
|
+
const placeholder = {
|
|
4623
|
+
lastCheckedAt: nowIso,
|
|
4624
|
+
...cache?.latestTag !== void 0 ? { latestTag: cache.latestTag } : {},
|
|
4625
|
+
...cache?.etag !== void 0 ? { etag: cache.etag } : {}
|
|
4626
|
+
};
|
|
4627
|
+
await writeCache(placeholder).catch(() => {});
|
|
4628
|
+
const previousEtag = cache?.etag;
|
|
4629
|
+
let entry = placeholder;
|
|
4630
|
+
try {
|
|
4631
|
+
const result = await fetchLatestReleaseConditional(previousEtag);
|
|
4632
|
+
if (result.status === "not-modified") entry = {
|
|
4633
|
+
lastCheckedAt: nowIso,
|
|
4634
|
+
...cache?.latestTag !== void 0 ? { latestTag: cache.latestTag } : {},
|
|
4635
|
+
...result.etag !== void 0 ? { etag: result.etag } : cache?.etag !== void 0 ? { etag: cache.etag } : {}
|
|
4636
|
+
};
|
|
4637
|
+
else entry = {
|
|
4638
|
+
lastCheckedAt: nowIso,
|
|
4639
|
+
latestTag: result.release.tag_name,
|
|
4640
|
+
...result.etag !== void 0 ? { etag: result.etag } : {}
|
|
4641
|
+
};
|
|
4642
|
+
await writeCache(entry).catch(() => {});
|
|
4643
|
+
} catch {}
|
|
4644
|
+
maybeEmitNotice(entry, env);
|
|
4645
|
+
return entry;
|
|
4646
|
+
}
|
|
4647
|
+
function maybeEmitNotice(entry, env) {
|
|
4648
|
+
if (!entry.latestTag) return;
|
|
4649
|
+
if (VERSION.startsWith("0.0.0-dev")) return;
|
|
4650
|
+
const latest = versionFromTag(entry.latestTag);
|
|
4651
|
+
if (!latest) return;
|
|
4652
|
+
if (compareSemver(latest, VERSION) <= 0) return;
|
|
4653
|
+
const dim = env.NO_COLOR ? "" : "\x1B[2m";
|
|
4654
|
+
const reset = env.NO_COLOR ? "" : "\x1B[0m";
|
|
4655
|
+
process.stderr.write(`\n${dim}(aitcc ${latest} is available — run \`aitcc upgrade\` to install)${reset}\n`);
|
|
4656
|
+
}
|
|
4657
|
+
//#endregion
|
|
4658
|
+
//#region src/commands/whoami.ts
|
|
4659
|
+
async function runBackgroundUpdateCheck(json) {
|
|
4660
|
+
if (json) return;
|
|
4661
|
+
const timeoutMs = 500;
|
|
4662
|
+
await Promise.race([maybeCheckForUpdate().catch(() => null), new Promise((resolve) => {
|
|
4663
|
+
const t = setTimeout(() => resolve(null), timeoutMs);
|
|
4664
|
+
if (typeof t.unref === "function") t.unref();
|
|
4665
|
+
})]);
|
|
4666
|
+
}
|
|
4667
|
+
const whoamiCommand = defineCommand({
|
|
4668
|
+
meta: {
|
|
4669
|
+
name: "whoami",
|
|
4670
|
+
description: "Show the currently authenticated user (live from the console API by default)."
|
|
4671
|
+
},
|
|
4672
|
+
args: {
|
|
4673
|
+
json: {
|
|
4674
|
+
type: "boolean",
|
|
4675
|
+
description: "Emit machine-readable JSON to stdout.",
|
|
4676
|
+
default: false
|
|
4677
|
+
},
|
|
4678
|
+
offline: {
|
|
4679
|
+
type: "boolean",
|
|
4680
|
+
description: "Skip the live API call and read only the cached session summary.",
|
|
4681
|
+
default: false
|
|
4682
|
+
}
|
|
4683
|
+
},
|
|
4684
|
+
async run({ args }) {
|
|
4685
|
+
const session = await readSession();
|
|
4686
|
+
if (!session) {
|
|
4687
|
+
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
4688
|
+
ok: true,
|
|
4689
|
+
authenticated: false
|
|
4690
|
+
})}\n`);
|
|
4691
|
+
else {
|
|
4692
|
+
process.stderr.write("Not logged in. Run `aitcc login` to start a session.\n");
|
|
4693
|
+
process.stderr.write(`Session file checked: ${sessionPathForDiagnostics()}\n`);
|
|
4694
|
+
}
|
|
4695
|
+
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
4696
|
+
}
|
|
4697
|
+
if (args.offline) {
|
|
4698
|
+
if (args.json) {
|
|
4699
|
+
process.stdout.write(`${JSON.stringify({
|
|
4700
|
+
ok: true,
|
|
4701
|
+
authenticated: true,
|
|
4702
|
+
source: "cache",
|
|
4703
|
+
user: session.user,
|
|
4704
|
+
capturedAt: session.capturedAt
|
|
4705
|
+
})}\n`);
|
|
4706
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
4707
|
+
}
|
|
4708
|
+
const label = session.user.displayName ? `${session.user.displayName} <${session.user.email}>` : session.user.email;
|
|
4709
|
+
process.stdout.write(`Logged in as ${label} (cached)\n`);
|
|
4710
|
+
process.stdout.write(`Session captured: ${session.capturedAt}\n`);
|
|
4711
|
+
await runBackgroundUpdateCheck(args.json);
|
|
4712
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
4713
|
+
}
|
|
4714
|
+
try {
|
|
4715
|
+
const info = await fetchConsoleMemberUserInfo(session.cookies);
|
|
4716
|
+
if (args.json) {
|
|
4717
|
+
process.stdout.write(`${JSON.stringify({
|
|
4718
|
+
ok: true,
|
|
4719
|
+
authenticated: true,
|
|
4720
|
+
source: "live",
|
|
4721
|
+
user: {
|
|
4722
|
+
id: String(info.id),
|
|
4723
|
+
bizUserNo: info.bizUserNo,
|
|
4724
|
+
name: info.name,
|
|
4725
|
+
email: info.email,
|
|
4726
|
+
role: info.role
|
|
4727
|
+
},
|
|
4728
|
+
workspaces: info.workspaces.map((w) => ({
|
|
4729
|
+
workspaceId: w.workspaceId,
|
|
4730
|
+
workspaceName: w.workspaceName,
|
|
4731
|
+
role: w.role
|
|
4732
|
+
})),
|
|
4733
|
+
capturedAt: session.capturedAt
|
|
4734
|
+
})}\n`);
|
|
4735
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
4736
|
+
}
|
|
4737
|
+
process.stdout.write(`Logged in as ${info.name} <${info.email}> (${info.role})\n`);
|
|
4738
|
+
if (info.workspaces.length > 0) {
|
|
4739
|
+
process.stdout.write("Workspaces:\n");
|
|
4740
|
+
for (const w of info.workspaces) process.stdout.write(` - ${w.workspaceName} (id ${w.workspaceId}, ${w.role})\n`);
|
|
4741
|
+
}
|
|
4742
|
+
await runBackgroundUpdateCheck(args.json);
|
|
4743
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
4744
|
+
} catch (err) {
|
|
4745
|
+
if (err instanceof TossApiError && err.isAuthError) {
|
|
4746
|
+
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
4747
|
+
ok: true,
|
|
4748
|
+
authenticated: false,
|
|
4749
|
+
reason: "session-expired",
|
|
4750
|
+
errorCode: err.errorCode
|
|
4751
|
+
})}\n`);
|
|
4752
|
+
else process.stderr.write("Session is no longer valid. Run `aitcc login` again.\n");
|
|
4753
|
+
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
4754
|
+
}
|
|
4755
|
+
if (err instanceof NetworkError) {
|
|
4756
|
+
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
4757
|
+
ok: false,
|
|
4758
|
+
reason: "network-error",
|
|
4759
|
+
message: err.message
|
|
4760
|
+
})}\n`);
|
|
4761
|
+
else process.stderr.write(`Network error reaching the console API: ${err.message}. Use \`aitcc whoami --offline\` for the cached identity.\n`);
|
|
4762
|
+
return exitAfterFlush(ExitCode.NetworkError);
|
|
4763
|
+
}
|
|
4764
|
+
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
4765
|
+
ok: false,
|
|
4766
|
+
reason: "api-error",
|
|
4767
|
+
message: err.message
|
|
4768
|
+
})}\n`);
|
|
4769
|
+
else process.stderr.write(`Unexpected error: ${err.message}\n`);
|
|
4770
|
+
return exitAfterFlush(ExitCode.ApiError);
|
|
4771
|
+
}
|
|
3395
4772
|
}
|
|
3396
4773
|
});
|
|
3397
4774
|
//#endregion
|
|
3398
|
-
//#region src/
|
|
3399
|
-
const
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
const
|
|
3408
|
-
if (
|
|
3409
|
-
|
|
3410
|
-
}
|
|
3411
|
-
async function fetchLatestRelease() {
|
|
3412
|
-
const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;
|
|
3413
|
-
const res = await fetch(url, { headers: defaultHeaders() });
|
|
3414
|
-
if (!res.ok) throw new Error(`GitHub releases/latest returned ${res.status} ${res.statusText}`);
|
|
3415
|
-
return await res.json();
|
|
3416
|
-
}
|
|
3417
|
-
/**
|
|
3418
|
-
* Conditional GET against `releases/latest`. If the server returns 304 we
|
|
3419
|
-
* learn "no change" without consuming a core rate-limit slot. Intended for
|
|
3420
|
-
* the background update check, which re-runs often; `fetchLatestRelease()`
|
|
3421
|
-
* remains the right call when the upgrade command actually needs the body.
|
|
3422
|
-
*/
|
|
3423
|
-
async function fetchLatestReleaseConditional(previousEtag) {
|
|
3424
|
-
const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;
|
|
3425
|
-
const headers = defaultHeaders();
|
|
3426
|
-
if (previousEtag && previousEtag.length > 0) headers["If-None-Match"] = previousEtag;
|
|
3427
|
-
const res = await fetch(url, { headers });
|
|
3428
|
-
const etag = res.headers.get("etag") ?? void 0;
|
|
3429
|
-
if (res.status === 304) return {
|
|
3430
|
-
status: "not-modified",
|
|
3431
|
-
etag
|
|
3432
|
-
};
|
|
3433
|
-
if (!res.ok) throw new Error(`GitHub releases/latest returned ${res.status} ${res.statusText}`);
|
|
4775
|
+
//#region src/api/workspaces.ts
|
|
4776
|
+
const WORKSPACES_BASE = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
|
|
4777
|
+
async function fetchWorkspaceDetail(workspaceId, cookies, opts = {}) {
|
|
4778
|
+
const raw = await requestConsoleApi({
|
|
4779
|
+
url: `${WORKSPACES_BASE}/workspaces/${workspaceId}`,
|
|
4780
|
+
cookies,
|
|
4781
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
4782
|
+
});
|
|
4783
|
+
const id = raw.id;
|
|
4784
|
+
const name = raw.name;
|
|
4785
|
+
if (typeof id !== "number" || !Number.isInteger(id) || id <= 0 || typeof name !== "string") throw new Error(`Unexpected workspace detail shape for id=${workspaceId}`);
|
|
4786
|
+
const { id: _id, name: _name, ...extra } = raw;
|
|
3434
4787
|
return {
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
4788
|
+
workspaceId: id,
|
|
4789
|
+
workspaceName: name,
|
|
4790
|
+
extra
|
|
3438
4791
|
};
|
|
3439
4792
|
}
|
|
3440
|
-
function
|
|
3441
|
-
const
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
}
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
let os;
|
|
3449
|
-
switch (process.platform) {
|
|
3450
|
-
case "linux":
|
|
3451
|
-
os = "linux";
|
|
3452
|
-
break;
|
|
3453
|
-
case "darwin":
|
|
3454
|
-
os = "darwin";
|
|
3455
|
-
break;
|
|
3456
|
-
case "win32":
|
|
3457
|
-
os = "windows";
|
|
3458
|
-
break;
|
|
3459
|
-
default: return null;
|
|
3460
|
-
}
|
|
3461
|
-
let arch;
|
|
3462
|
-
switch (process.arch) {
|
|
3463
|
-
case "x64":
|
|
3464
|
-
arch = "x64";
|
|
3465
|
-
break;
|
|
3466
|
-
case "arm64":
|
|
3467
|
-
arch = "arm64";
|
|
3468
|
-
break;
|
|
3469
|
-
default: return null;
|
|
3470
|
-
}
|
|
3471
|
-
if (os === "windows" && arch === "arm64") return null;
|
|
4793
|
+
async function fetchWorkspacePartner(workspaceId, cookies, opts = {}) {
|
|
4794
|
+
const raw = await requestConsoleApi({
|
|
4795
|
+
url: `${WORKSPACES_BASE}/workspaces/${workspaceId}/partner`,
|
|
4796
|
+
cookies,
|
|
4797
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
4798
|
+
});
|
|
4799
|
+
const registered = raw.registered;
|
|
4800
|
+
if (typeof registered !== "boolean") throw new Error(`Unexpected workspace partner shape for id=${workspaceId}`);
|
|
3472
4801
|
return {
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
4802
|
+
registered,
|
|
4803
|
+
approvalType: typeof raw.approvalType === "string" ? raw.approvalType : null,
|
|
4804
|
+
rejectMessage: typeof raw.rejectMessage === "string" ? raw.rejectMessage : null,
|
|
4805
|
+
partner: raw.partner && typeof raw.partner === "object" ? raw.partner : null
|
|
3476
4806
|
};
|
|
3477
4807
|
}
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
4808
|
+
const WORKSPACE_TERM_TYPES = [
|
|
4809
|
+
"TOSS_LOGIN",
|
|
4810
|
+
"BIZ_WORKSPACE",
|
|
4811
|
+
"TOSS_PROMOTION_MONEY",
|
|
4812
|
+
"IAA",
|
|
4813
|
+
"IAP"
|
|
4814
|
+
];
|
|
4815
|
+
const DEFAULT_SEGMENT_CATEGORY = "생성된 세그먼트";
|
|
4816
|
+
async function fetchWorkspaceSegments(params, cookies, opts = {}) {
|
|
4817
|
+
const page = params.page ?? 0;
|
|
4818
|
+
const qs = new URLSearchParams();
|
|
4819
|
+
qs.set("category", params.category ?? DEFAULT_SEGMENT_CATEGORY);
|
|
4820
|
+
qs.set("search", params.search ?? "");
|
|
4821
|
+
qs.set("page", String(page));
|
|
4822
|
+
const raw = await requestConsoleApi({
|
|
4823
|
+
url: `${WORKSPACES_BASE}/workspaces/${params.workspaceId}/segments/list?${qs.toString()}`,
|
|
4824
|
+
cookies,
|
|
4825
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
4826
|
+
});
|
|
4827
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Unexpected segments shape for workspace=${params.workspaceId}`);
|
|
4828
|
+
const data = raw;
|
|
3483
4829
|
return {
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
pre: m[4] ?? ""
|
|
4830
|
+
contents: (Array.isArray(data.contents) ? data.contents : []).map((c) => c && typeof c === "object" ? c : {}),
|
|
4831
|
+
totalPage: typeof data.totalPage === "number" ? data.totalPage : 0,
|
|
4832
|
+
currentPage: typeof data.currentPage === "number" ? data.currentPage : page
|
|
3488
4833
|
};
|
|
3489
4834
|
}
|
|
3490
|
-
function
|
|
3491
|
-
const
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
if (
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
} catch {}
|
|
3512
|
-
return "0.0.0-dev";
|
|
4835
|
+
async function fetchWorkspaceTerms(workspaceId, type, cookies, opts = {}) {
|
|
4836
|
+
const raw = await requestConsoleApi({
|
|
4837
|
+
url: `${WORKSPACES_BASE}/workspaces/${workspaceId}/console-workspace-terms/${type}/skip-permission`,
|
|
4838
|
+
cookies,
|
|
4839
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
4840
|
+
});
|
|
4841
|
+
if (!Array.isArray(raw)) throw new Error(`Unexpected workspace terms shape for type=${type}`);
|
|
4842
|
+
return raw.map((entry, i) => {
|
|
4843
|
+
if (!entry || typeof entry !== "object") throw new Error(`Unexpected workspace terms entry at index ${i} for type=${type}`);
|
|
4844
|
+
const e = entry;
|
|
4845
|
+
return {
|
|
4846
|
+
required: Boolean(e.required),
|
|
4847
|
+
termsId: typeof e.termsId === "number" ? e.termsId : 0,
|
|
4848
|
+
revisionId: typeof e.revisionId === "number" ? e.revisionId : 0,
|
|
4849
|
+
title: typeof e.title === "string" ? e.title : "",
|
|
4850
|
+
contentsUrl: typeof e.contentsUrl === "string" ? e.contentsUrl : "",
|
|
4851
|
+
actionType: typeof e.actionType === "string" ? e.actionType : "",
|
|
4852
|
+
isAgreed: Boolean(e.isAgreed),
|
|
4853
|
+
isOneTimeConsent: Boolean(e.isOneTimeConsent)
|
|
4854
|
+
};
|
|
4855
|
+
});
|
|
3513
4856
|
}
|
|
3514
|
-
const VERSION = resolveVersion();
|
|
3515
4857
|
//#endregion
|
|
3516
|
-
//#region src/commands/
|
|
3517
|
-
function
|
|
3518
|
-
|
|
4858
|
+
//#region src/commands/workspace.ts
|
|
4859
|
+
function formatScalar(v) {
|
|
4860
|
+
if (v === null) return "null";
|
|
4861
|
+
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return String(v);
|
|
4862
|
+
return JSON.stringify(v);
|
|
3519
4863
|
}
|
|
3520
|
-
const
|
|
4864
|
+
const lsCommand = defineCommand({
|
|
3521
4865
|
meta: {
|
|
3522
|
-
name: "
|
|
3523
|
-
description: "
|
|
4866
|
+
name: "ls",
|
|
4867
|
+
description: "List workspaces the current user has access to."
|
|
4868
|
+
},
|
|
4869
|
+
args: { json: {
|
|
4870
|
+
type: "boolean",
|
|
4871
|
+
description: "Emit machine-readable JSON to stdout.",
|
|
4872
|
+
default: false
|
|
4873
|
+
} },
|
|
4874
|
+
async run({ args }) {
|
|
4875
|
+
const session = await readSession();
|
|
4876
|
+
if (!session) {
|
|
4877
|
+
emitNotAuthenticated(args.json);
|
|
4878
|
+
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
4879
|
+
}
|
|
4880
|
+
try {
|
|
4881
|
+
const info = await fetchConsoleMemberUserInfo(session.cookies);
|
|
4882
|
+
const current = session.currentWorkspaceId;
|
|
4883
|
+
if (args.json) {
|
|
4884
|
+
emitJson({
|
|
4885
|
+
ok: true,
|
|
4886
|
+
workspaces: info.workspaces.map((w) => ({
|
|
4887
|
+
workspaceId: w.workspaceId,
|
|
4888
|
+
workspaceName: w.workspaceName,
|
|
4889
|
+
role: w.role,
|
|
4890
|
+
current: w.workspaceId === current
|
|
4891
|
+
}))
|
|
4892
|
+
});
|
|
4893
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
4894
|
+
}
|
|
4895
|
+
if (info.workspaces.length === 0) {
|
|
4896
|
+
process.stdout.write("No workspaces.\n");
|
|
4897
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
4898
|
+
}
|
|
4899
|
+
for (const w of info.workspaces) {
|
|
4900
|
+
const marker = w.workspaceId === current ? "* " : " ";
|
|
4901
|
+
process.stdout.write(`${marker}${w.workspaceId} ${w.workspaceName} (${w.role})\n`);
|
|
4902
|
+
}
|
|
4903
|
+
if (current === void 0) process.stderr.write("No workspace selected. Run `aitcc workspace use <id>`.\n");
|
|
4904
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
4905
|
+
} catch (err) {
|
|
4906
|
+
return emitFailureFromError(args.json, err);
|
|
4907
|
+
}
|
|
4908
|
+
}
|
|
4909
|
+
});
|
|
4910
|
+
const useCommand = defineCommand({
|
|
4911
|
+
meta: {
|
|
4912
|
+
name: "use",
|
|
4913
|
+
description: "Select the current workspace by ID. Subsequent commands use this."
|
|
3524
4914
|
},
|
|
3525
4915
|
args: {
|
|
4916
|
+
id: {
|
|
4917
|
+
type: "positional",
|
|
4918
|
+
description: "Workspace ID",
|
|
4919
|
+
required: true
|
|
4920
|
+
},
|
|
3526
4921
|
json: {
|
|
3527
4922
|
type: "boolean",
|
|
3528
4923
|
description: "Emit machine-readable JSON to stdout.",
|
|
3529
4924
|
default: false
|
|
4925
|
+
}
|
|
4926
|
+
},
|
|
4927
|
+
async run({ args }) {
|
|
4928
|
+
const raw = String(args.id);
|
|
4929
|
+
const parsed = parsePositiveInt$1(raw);
|
|
4930
|
+
if (parsed === null) {
|
|
4931
|
+
const message = `workspace id must be a positive integer (got ${raw})`;
|
|
4932
|
+
if (args.json) emitJson({
|
|
4933
|
+
ok: false,
|
|
4934
|
+
reason: "invalid-id",
|
|
4935
|
+
message
|
|
4936
|
+
});
|
|
4937
|
+
else process.stderr.write(`${message}\n`);
|
|
4938
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
4939
|
+
}
|
|
4940
|
+
const session = await readSession();
|
|
4941
|
+
if (!session) {
|
|
4942
|
+
emitNotAuthenticated(args.json);
|
|
4943
|
+
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
4944
|
+
}
|
|
4945
|
+
try {
|
|
4946
|
+
const match = (await fetchConsoleMemberUserInfo(session.cookies)).workspaces.find((w) => w.workspaceId === parsed);
|
|
4947
|
+
if (!match) {
|
|
4948
|
+
if (args.json) emitJson({
|
|
4949
|
+
ok: false,
|
|
4950
|
+
reason: "not-found",
|
|
4951
|
+
workspaceId: parsed
|
|
4952
|
+
});
|
|
4953
|
+
else process.stderr.write(`Workspace ${parsed} is not accessible from this account. Run \`aitcc workspace ls\` to see available workspaces.\n`);
|
|
4954
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
4955
|
+
}
|
|
4956
|
+
if (await setCurrentWorkspaceId(parsed) === null) {
|
|
4957
|
+
emitNotAuthenticated(args.json);
|
|
4958
|
+
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
4959
|
+
}
|
|
4960
|
+
if (args.json) emitJson({
|
|
4961
|
+
ok: true,
|
|
4962
|
+
workspaceId: match.workspaceId,
|
|
4963
|
+
workspaceName: match.workspaceName
|
|
4964
|
+
});
|
|
4965
|
+
else process.stdout.write(`Using workspace ${match.workspaceId} (${match.workspaceName}).\n`);
|
|
4966
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
4967
|
+
} catch (err) {
|
|
4968
|
+
return emitFailureFromError(args.json, err);
|
|
4969
|
+
}
|
|
4970
|
+
}
|
|
4971
|
+
});
|
|
4972
|
+
const showCommand = defineCommand({
|
|
4973
|
+
meta: {
|
|
4974
|
+
name: "show",
|
|
4975
|
+
description: "Show details of the selected workspace (or the one passed with --workspace)."
|
|
4976
|
+
},
|
|
4977
|
+
args: {
|
|
4978
|
+
workspace: {
|
|
4979
|
+
type: "string",
|
|
4980
|
+
description: "Workspace ID to inspect. Defaults to the selected workspace."
|
|
3530
4981
|
},
|
|
3531
|
-
|
|
3532
|
-
type: "boolean",
|
|
3533
|
-
description: "Re-install even if already on the latest version.",
|
|
3534
|
-
default: false
|
|
3535
|
-
},
|
|
3536
|
-
"dry-run": {
|
|
4982
|
+
json: {
|
|
3537
4983
|
type: "boolean",
|
|
3538
|
-
description: "
|
|
4984
|
+
description: "Emit machine-readable JSON to stdout.",
|
|
3539
4985
|
default: false
|
|
3540
4986
|
}
|
|
3541
4987
|
},
|
|
3542
4988
|
async run({ args }) {
|
|
3543
|
-
const
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
const emitError = (payload, human) => {
|
|
3548
|
-
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
3549
|
-
ok: false,
|
|
3550
|
-
...payload
|
|
3551
|
-
})}\n`);
|
|
3552
|
-
else process.stderr.write(`${human}\n`);
|
|
3553
|
-
};
|
|
3554
|
-
let release;
|
|
3555
|
-
try {
|
|
3556
|
-
release = await fetchLatestRelease();
|
|
3557
|
-
} catch (err) {
|
|
3558
|
-
emitError({
|
|
3559
|
-
reason: "network-error",
|
|
3560
|
-
message: err.message
|
|
3561
|
-
}, `Failed to query GitHub releases: ${err.message}`);
|
|
3562
|
-
process.exit(ExitCode.NetworkError);
|
|
3563
|
-
}
|
|
3564
|
-
const latest = versionFromTag(release.tag_name);
|
|
3565
|
-
const current = VERSION;
|
|
3566
|
-
if (!(compareSemver(latest, current) > 0 || args.force)) {
|
|
3567
|
-
emit({
|
|
3568
|
-
ok: true,
|
|
3569
|
-
status: "already-latest",
|
|
3570
|
-
current,
|
|
3571
|
-
latest
|
|
3572
|
-
}, `Already on the latest version (${current}).`);
|
|
3573
|
-
process.exit(ExitCode.UpgradeAlreadyLatest);
|
|
3574
|
-
}
|
|
3575
|
-
if (args["dry-run"]) {
|
|
3576
|
-
emit({
|
|
3577
|
-
ok: true,
|
|
3578
|
-
status: "update-available",
|
|
3579
|
-
current,
|
|
3580
|
-
latest,
|
|
3581
|
-
url: release.html_url
|
|
3582
|
-
}, `Update available: ${current} → ${latest}\n${release.html_url}`);
|
|
3583
|
-
return;
|
|
3584
|
-
}
|
|
3585
|
-
if (!isStandaloneBinary()) {
|
|
3586
|
-
emitError({
|
|
3587
|
-
reason: "not-standalone",
|
|
3588
|
-
current,
|
|
3589
|
-
latest,
|
|
3590
|
-
hint: "npm i -g @ait-co/console-cli@latest"
|
|
3591
|
-
}, [
|
|
3592
|
-
"This install was launched via Node, not the standalone binary.",
|
|
3593
|
-
"Self-upgrade is only supported for the compiled binary.",
|
|
3594
|
-
`Run: npm i -g @ait-co/console-cli@latest (currently ${current}, latest ${latest})`
|
|
3595
|
-
].join("\n"));
|
|
3596
|
-
process.exit(ExitCode.UpgradeUnavailable);
|
|
3597
|
-
}
|
|
3598
|
-
const platform = detectPlatform();
|
|
3599
|
-
if (!platform) {
|
|
3600
|
-
emitError({
|
|
3601
|
-
reason: "unsupported-platform",
|
|
3602
|
-
platform: process.platform,
|
|
3603
|
-
arch: process.arch
|
|
3604
|
-
}, `No prebuilt binary for ${process.platform}/${process.arch}.`);
|
|
3605
|
-
process.exit(ExitCode.UpgradeUnavailable);
|
|
3606
|
-
}
|
|
3607
|
-
const asset = release.assets.find((a) => a.name === platform.assetName);
|
|
3608
|
-
if (!asset) {
|
|
3609
|
-
emitError({
|
|
3610
|
-
reason: "asset-missing",
|
|
3611
|
-
assetName: platform.assetName,
|
|
3612
|
-
tag: release.tag_name
|
|
3613
|
-
}, `Release ${release.tag_name} has no asset named ${platform.assetName}. It may still be uploading.`);
|
|
3614
|
-
process.exit(ExitCode.UpgradeUnavailable);
|
|
4989
|
+
const session = await readSession();
|
|
4990
|
+
if (!session) {
|
|
4991
|
+
emitNotAuthenticated(args.json);
|
|
4992
|
+
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
3615
4993
|
}
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
4994
|
+
let workspaceId;
|
|
4995
|
+
if (args.workspace) {
|
|
4996
|
+
const raw = String(args.workspace);
|
|
4997
|
+
const parsed = parsePositiveInt$1(raw);
|
|
4998
|
+
if (parsed === null) {
|
|
4999
|
+
const message = `--workspace must be a positive integer (got ${raw})`;
|
|
5000
|
+
if (args.json) emitJson({
|
|
5001
|
+
ok: false,
|
|
5002
|
+
reason: "invalid-id",
|
|
5003
|
+
message
|
|
5004
|
+
});
|
|
5005
|
+
else process.stderr.write(`${message}\n`);
|
|
5006
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
5007
|
+
}
|
|
5008
|
+
workspaceId = parsed;
|
|
5009
|
+
} else workspaceId = session.currentWorkspaceId;
|
|
5010
|
+
if (workspaceId === void 0) {
|
|
5011
|
+
if (args.json) emitJson({
|
|
5012
|
+
ok: false,
|
|
5013
|
+
reason: "no-workspace-selected"
|
|
5014
|
+
});
|
|
5015
|
+
else process.stderr.write("No workspace selected. Pass `--workspace <id>` or run `aitcc workspace use <id>`.\n");
|
|
5016
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
3630
5017
|
}
|
|
3631
5018
|
try {
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
5019
|
+
const detail = await fetchWorkspaceDetail(workspaceId, session.cookies);
|
|
5020
|
+
if (args.json) {
|
|
5021
|
+
emitJson({
|
|
5022
|
+
ok: true,
|
|
5023
|
+
workspaceId: detail.workspaceId,
|
|
5024
|
+
workspaceName: detail.workspaceName,
|
|
5025
|
+
extra: detail.extra ?? {}
|
|
5026
|
+
});
|
|
5027
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
5028
|
+
}
|
|
5029
|
+
process.stdout.write(`Workspace ${detail.workspaceId}: ${detail.workspaceName}\n`);
|
|
5030
|
+
if (detail.extra) for (const [k, v] of Object.entries(detail.extra)) process.stdout.write(` ${k}: ${formatScalar(v)}\n`);
|
|
5031
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
3636
5032
|
} catch (err) {
|
|
3637
|
-
|
|
3638
|
-
reason: "replace-failed",
|
|
3639
|
-
message: err.message,
|
|
3640
|
-
exePath,
|
|
3641
|
-
stagingPath
|
|
3642
|
-
}, `Failed to replace binary at ${exePath}: ${err.message}`);
|
|
3643
|
-
process.exit(ExitCode.Generic);
|
|
5033
|
+
return emitFailureFromError(args.json, err);
|
|
3644
5034
|
}
|
|
3645
|
-
emit({
|
|
3646
|
-
ok: true,
|
|
3647
|
-
status: "upgraded",
|
|
3648
|
-
from: current,
|
|
3649
|
-
to: latest,
|
|
3650
|
-
installedAt: exePath,
|
|
3651
|
-
installedIn: dirname(exePath)
|
|
3652
|
-
}, `Upgraded aitcc: ${current} → ${latest}`);
|
|
3653
5035
|
}
|
|
3654
5036
|
});
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
const
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
5037
|
+
async function resolveWorkspaceArg(args, selected) {
|
|
5038
|
+
if (args.workspace) {
|
|
5039
|
+
const raw = String(args.workspace);
|
|
5040
|
+
const parsed = parsePositiveInt$1(raw);
|
|
5041
|
+
if (parsed === null) {
|
|
5042
|
+
const message = `--workspace must be a positive integer (got ${raw})`;
|
|
5043
|
+
if (args.json) emitJson({
|
|
5044
|
+
ok: false,
|
|
5045
|
+
reason: "invalid-id",
|
|
5046
|
+
message
|
|
5047
|
+
});
|
|
5048
|
+
else process.stderr.write(`${message}\n`);
|
|
5049
|
+
return null;
|
|
5050
|
+
}
|
|
5051
|
+
return parsed;
|
|
3664
5052
|
}
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
5053
|
+
if (selected === void 0) {
|
|
5054
|
+
if (args.json) emitJson({
|
|
5055
|
+
ok: false,
|
|
5056
|
+
reason: "no-workspace-selected"
|
|
5057
|
+
});
|
|
5058
|
+
else process.stderr.write("No workspace selected. Pass `--workspace <id>` or run `aitcc workspace use <id>`.\n");
|
|
3669
5059
|
return null;
|
|
3670
5060
|
}
|
|
3671
|
-
|
|
3672
|
-
const obj = parsed;
|
|
3673
|
-
if (typeof obj.lastCheckedAt !== "string") return null;
|
|
3674
|
-
if (obj.latestTag !== void 0 && typeof obj.latestTag !== "string") return null;
|
|
3675
|
-
if (obj.etag !== void 0 && typeof obj.etag !== "string") return null;
|
|
3676
|
-
return {
|
|
3677
|
-
lastCheckedAt: obj.lastCheckedAt,
|
|
3678
|
-
...obj.latestTag !== void 0 ? { latestTag: obj.latestTag } : {},
|
|
3679
|
-
...obj.etag !== void 0 ? { etag: obj.etag } : {}
|
|
3680
|
-
};
|
|
3681
|
-
}
|
|
3682
|
-
async function writeCache(entry) {
|
|
3683
|
-
const path = upgradeCheckPath();
|
|
3684
|
-
await mkdir(dirname(path), { recursive: true });
|
|
3685
|
-
const tmp = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 10)}.tmp`;
|
|
3686
|
-
try {
|
|
3687
|
-
await writeFile(tmp, JSON.stringify(entry, null, 2), { mode: 384 });
|
|
3688
|
-
await rename(tmp, path);
|
|
3689
|
-
} catch (err) {
|
|
3690
|
-
await unlink(tmp).catch(() => {});
|
|
3691
|
-
throw err;
|
|
3692
|
-
}
|
|
3693
|
-
}
|
|
3694
|
-
/** Has the throttle window elapsed since the last recorded check? */
|
|
3695
|
-
function isDueForCheck(cache, now = Date.now(), intervalMs = UPDATE_CHECK_INTERVAL_MS) {
|
|
3696
|
-
if (!cache) return true;
|
|
3697
|
-
const last = Date.parse(cache.lastCheckedAt);
|
|
3698
|
-
if (!Number.isFinite(last)) return true;
|
|
3699
|
-
if (now < last) return true;
|
|
3700
|
-
return now - last >= intervalMs;
|
|
3701
|
-
}
|
|
3702
|
-
/**
|
|
3703
|
-
* Perform the throttled update check. Returns the final cache entry (for
|
|
3704
|
-
* testing) or null when skipped. Never throws — network errors are
|
|
3705
|
-
* intentionally swallowed so they never interrupt the foreground command.
|
|
3706
|
-
*/
|
|
3707
|
-
async function maybeCheckForUpdate(opts = {}) {
|
|
3708
|
-
const env = opts.env ?? process.env;
|
|
3709
|
-
const isTTY = opts.isTTY ?? Boolean(process.stderr.isTTY);
|
|
3710
|
-
const now = opts.now ?? Date.now();
|
|
3711
|
-
const intervalMs = opts.intervalMs ?? 864e5;
|
|
3712
|
-
const optOut = env.AITCC_NO_UPDATE_CHECK;
|
|
3713
|
-
if (optOut && optOut !== "0" && optOut.toLowerCase() !== "false") return null;
|
|
3714
|
-
if (!isTTY) return null;
|
|
3715
|
-
const cache = await readCache();
|
|
3716
|
-
if (!isDueForCheck(cache, now, intervalMs)) return null;
|
|
3717
|
-
const nowIso = new Date(now).toISOString();
|
|
3718
|
-
const placeholder = {
|
|
3719
|
-
lastCheckedAt: nowIso,
|
|
3720
|
-
...cache?.latestTag !== void 0 ? { latestTag: cache.latestTag } : {},
|
|
3721
|
-
...cache?.etag !== void 0 ? { etag: cache.etag } : {}
|
|
3722
|
-
};
|
|
3723
|
-
await writeCache(placeholder).catch(() => {});
|
|
3724
|
-
const previousEtag = cache?.etag;
|
|
3725
|
-
let entry = placeholder;
|
|
3726
|
-
try {
|
|
3727
|
-
const result = await fetchLatestReleaseConditional(previousEtag);
|
|
3728
|
-
if (result.status === "not-modified") entry = {
|
|
3729
|
-
lastCheckedAt: nowIso,
|
|
3730
|
-
...cache?.latestTag !== void 0 ? { latestTag: cache.latestTag } : {},
|
|
3731
|
-
...result.etag !== void 0 ? { etag: result.etag } : cache?.etag !== void 0 ? { etag: cache.etag } : {}
|
|
3732
|
-
};
|
|
3733
|
-
else entry = {
|
|
3734
|
-
lastCheckedAt: nowIso,
|
|
3735
|
-
latestTag: result.release.tag_name,
|
|
3736
|
-
...result.etag !== void 0 ? { etag: result.etag } : {}
|
|
3737
|
-
};
|
|
3738
|
-
await writeCache(entry).catch(() => {});
|
|
3739
|
-
} catch {}
|
|
3740
|
-
maybeEmitNotice(entry, env);
|
|
3741
|
-
return entry;
|
|
3742
|
-
}
|
|
3743
|
-
function maybeEmitNotice(entry, env) {
|
|
3744
|
-
if (!entry.latestTag) return;
|
|
3745
|
-
if (VERSION.startsWith("0.0.0-dev")) return;
|
|
3746
|
-
const latest = versionFromTag(entry.latestTag);
|
|
3747
|
-
if (!latest) return;
|
|
3748
|
-
if (compareSemver(latest, VERSION) <= 0) return;
|
|
3749
|
-
const dim = env.NO_COLOR ? "" : "\x1B[2m";
|
|
3750
|
-
const reset = env.NO_COLOR ? "" : "\x1B[0m";
|
|
3751
|
-
process.stderr.write(`\n${dim}(aitcc ${latest} is available — run \`aitcc upgrade\` to install)${reset}\n`);
|
|
3752
|
-
}
|
|
3753
|
-
//#endregion
|
|
3754
|
-
//#region src/commands/whoami.ts
|
|
3755
|
-
async function runBackgroundUpdateCheck(json) {
|
|
3756
|
-
if (json) return;
|
|
3757
|
-
const timeoutMs = 500;
|
|
3758
|
-
await Promise.race([maybeCheckForUpdate().catch(() => null), new Promise((resolve) => {
|
|
3759
|
-
const t = setTimeout(() => resolve(null), timeoutMs);
|
|
3760
|
-
if (typeof t.unref === "function") t.unref();
|
|
3761
|
-
})]);
|
|
5061
|
+
return selected;
|
|
3762
5062
|
}
|
|
3763
|
-
const
|
|
5063
|
+
const partnerCommand = defineCommand({
|
|
3764
5064
|
meta: {
|
|
3765
|
-
name: "
|
|
3766
|
-
description: "Show the
|
|
5065
|
+
name: "partner",
|
|
5066
|
+
description: "Show the partner (billing/payout) registration state for the selected workspace."
|
|
3767
5067
|
},
|
|
3768
5068
|
args: {
|
|
5069
|
+
workspace: {
|
|
5070
|
+
type: "string",
|
|
5071
|
+
description: "Workspace ID to inspect. Defaults to the selected workspace."
|
|
5072
|
+
},
|
|
3769
5073
|
json: {
|
|
3770
5074
|
type: "boolean",
|
|
3771
5075
|
description: "Emit machine-readable JSON to stdout.",
|
|
3772
5076
|
default: false
|
|
3773
|
-
},
|
|
3774
|
-
offline: {
|
|
3775
|
-
type: "boolean",
|
|
3776
|
-
description: "Skip the live API call and read only the cached session summary.",
|
|
3777
|
-
default: false
|
|
3778
5077
|
}
|
|
3779
5078
|
},
|
|
3780
5079
|
async run({ args }) {
|
|
3781
5080
|
const session = await readSession();
|
|
3782
5081
|
if (!session) {
|
|
3783
|
-
|
|
3784
|
-
ok: true,
|
|
3785
|
-
authenticated: false
|
|
3786
|
-
})}\n`);
|
|
3787
|
-
else {
|
|
3788
|
-
process.stderr.write("Not logged in. Run `aitcc login` to start a session.\n");
|
|
3789
|
-
process.stderr.write(`Session file checked: ${sessionPathForDiagnostics()}\n`);
|
|
3790
|
-
}
|
|
5082
|
+
emitNotAuthenticated(args.json);
|
|
3791
5083
|
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
3792
5084
|
}
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
process.stdout.write(`${JSON.stringify({
|
|
3796
|
-
ok: true,
|
|
3797
|
-
authenticated: true,
|
|
3798
|
-
source: "cache",
|
|
3799
|
-
user: session.user,
|
|
3800
|
-
capturedAt: session.capturedAt
|
|
3801
|
-
})}\n`);
|
|
3802
|
-
return exitAfterFlush(ExitCode.Ok);
|
|
3803
|
-
}
|
|
3804
|
-
const label = session.user.displayName ? `${session.user.displayName} <${session.user.email}>` : session.user.email;
|
|
3805
|
-
process.stdout.write(`Logged in as ${label} (cached)\n`);
|
|
3806
|
-
process.stdout.write(`Session captured: ${session.capturedAt}\n`);
|
|
3807
|
-
await runBackgroundUpdateCheck(args.json);
|
|
3808
|
-
return exitAfterFlush(ExitCode.Ok);
|
|
3809
|
-
}
|
|
5085
|
+
const workspaceId = await resolveWorkspaceArg(args, session.currentWorkspaceId);
|
|
5086
|
+
if (workspaceId === null) return exitAfterFlush(ExitCode.Usage);
|
|
3810
5087
|
try {
|
|
3811
|
-
const
|
|
5088
|
+
const state = await fetchWorkspacePartner(workspaceId, session.cookies);
|
|
3812
5089
|
if (args.json) {
|
|
3813
|
-
|
|
5090
|
+
emitJson({
|
|
3814
5091
|
ok: true,
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
email: info.email,
|
|
3822
|
-
role: info.role
|
|
3823
|
-
},
|
|
3824
|
-
workspaces: info.workspaces.map((w) => ({
|
|
3825
|
-
workspaceId: w.workspaceId,
|
|
3826
|
-
workspaceName: w.workspaceName,
|
|
3827
|
-
role: w.role
|
|
3828
|
-
})),
|
|
3829
|
-
capturedAt: session.capturedAt
|
|
3830
|
-
})}\n`);
|
|
5092
|
+
workspaceId,
|
|
5093
|
+
registered: state.registered,
|
|
5094
|
+
approvalType: state.approvalType,
|
|
5095
|
+
rejectMessage: state.rejectMessage,
|
|
5096
|
+
partner: state.partner
|
|
5097
|
+
});
|
|
3831
5098
|
return exitAfterFlush(ExitCode.Ok);
|
|
3832
5099
|
}
|
|
3833
|
-
process.stdout.write(`
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
5100
|
+
process.stdout.write(`Workspace ${workspaceId} partner:\n`);
|
|
5101
|
+
process.stdout.write(` registered: ${state.registered}\n`);
|
|
5102
|
+
process.stdout.write(` approvalType: ${state.approvalType ?? "null"}\n`);
|
|
5103
|
+
if (state.rejectMessage) process.stdout.write(` rejectMessage: ${state.rejectMessage}\n`);
|
|
5104
|
+
if (state.partner) {
|
|
5105
|
+
process.stdout.write(" partner:\n");
|
|
5106
|
+
for (const [k, v] of Object.entries(state.partner)) process.stdout.write(` ${k}: ${formatScalar(v)}\n`);
|
|
3837
5107
|
}
|
|
3838
|
-
await runBackgroundUpdateCheck(args.json);
|
|
3839
5108
|
return exitAfterFlush(ExitCode.Ok);
|
|
3840
5109
|
} catch (err) {
|
|
3841
|
-
|
|
3842
|
-
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
3843
|
-
ok: true,
|
|
3844
|
-
authenticated: false,
|
|
3845
|
-
reason: "session-expired",
|
|
3846
|
-
errorCode: err.errorCode
|
|
3847
|
-
})}\n`);
|
|
3848
|
-
else process.stderr.write("Session is no longer valid. Run `aitcc login` again.\n");
|
|
3849
|
-
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
3850
|
-
}
|
|
3851
|
-
if (err instanceof NetworkError) {
|
|
3852
|
-
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
3853
|
-
ok: false,
|
|
3854
|
-
reason: "network-error",
|
|
3855
|
-
message: err.message
|
|
3856
|
-
})}\n`);
|
|
3857
|
-
else process.stderr.write(`Network error reaching the console API: ${err.message}. Use \`aitcc whoami --offline\` for the cached identity.\n`);
|
|
3858
|
-
return exitAfterFlush(ExitCode.NetworkError);
|
|
3859
|
-
}
|
|
3860
|
-
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
3861
|
-
ok: false,
|
|
3862
|
-
reason: "api-error",
|
|
3863
|
-
message: err.message
|
|
3864
|
-
})}\n`);
|
|
3865
|
-
else process.stderr.write(`Unexpected error: ${err.message}\n`);
|
|
3866
|
-
return exitAfterFlush(ExitCode.ApiError);
|
|
5110
|
+
return emitFailureFromError(args.json, err);
|
|
3867
5111
|
}
|
|
3868
5112
|
}
|
|
3869
5113
|
});
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
const WORKSPACES_BASE = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
|
|
3873
|
-
async function fetchWorkspaceDetail(workspaceId, cookies, opts = {}) {
|
|
3874
|
-
const raw = await requestConsoleApi({
|
|
3875
|
-
url: `${WORKSPACES_BASE}/workspaces/${workspaceId}`,
|
|
3876
|
-
cookies,
|
|
3877
|
-
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
3878
|
-
});
|
|
3879
|
-
const id = raw.id;
|
|
3880
|
-
const name = raw.name;
|
|
3881
|
-
if (typeof id !== "number" || !Number.isInteger(id) || id <= 0 || typeof name !== "string") throw new Error(`Unexpected workspace detail shape for id=${workspaceId}`);
|
|
3882
|
-
const { id: _id, name: _name, ...extra } = raw;
|
|
3883
|
-
return {
|
|
3884
|
-
workspaceId: id,
|
|
3885
|
-
workspaceName: name,
|
|
3886
|
-
extra
|
|
3887
|
-
};
|
|
3888
|
-
}
|
|
3889
|
-
//#endregion
|
|
3890
|
-
//#region src/commands/workspace.ts
|
|
3891
|
-
function formatScalar(v) {
|
|
3892
|
-
if (v === null) return "null";
|
|
3893
|
-
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") return String(v);
|
|
3894
|
-
return JSON.stringify(v);
|
|
5114
|
+
function formatTermLines(term) {
|
|
5115
|
+
return ` ${term.isAgreed ? "[agreed]" : "[pending]"}${term.required ? " required" : ""} ${term.title}\n ${term.contentsUrl}\n`;
|
|
3895
5116
|
}
|
|
3896
5117
|
const workspaceCommand = defineCommand({
|
|
3897
5118
|
meta: {
|
|
@@ -3899,62 +5120,23 @@ const workspaceCommand = defineCommand({
|
|
|
3899
5120
|
description: "Inspect and switch between the workspaces this account can access."
|
|
3900
5121
|
},
|
|
3901
5122
|
subCommands: {
|
|
3902
|
-
ls:
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
args: { json: {
|
|
3908
|
-
type: "boolean",
|
|
3909
|
-
description: "Emit machine-readable JSON to stdout.",
|
|
3910
|
-
default: false
|
|
3911
|
-
} },
|
|
3912
|
-
async run({ args }) {
|
|
3913
|
-
const session = await readSession();
|
|
3914
|
-
if (!session) {
|
|
3915
|
-
emitNotAuthenticated(args.json);
|
|
3916
|
-
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
3917
|
-
}
|
|
3918
|
-
try {
|
|
3919
|
-
const info = await fetchConsoleMemberUserInfo(session.cookies);
|
|
3920
|
-
const current = session.currentWorkspaceId;
|
|
3921
|
-
if (args.json) {
|
|
3922
|
-
emitJson({
|
|
3923
|
-
ok: true,
|
|
3924
|
-
workspaces: info.workspaces.map((w) => ({
|
|
3925
|
-
workspaceId: w.workspaceId,
|
|
3926
|
-
workspaceName: w.workspaceName,
|
|
3927
|
-
role: w.role,
|
|
3928
|
-
current: w.workspaceId === current
|
|
3929
|
-
}))
|
|
3930
|
-
});
|
|
3931
|
-
return exitAfterFlush(ExitCode.Ok);
|
|
3932
|
-
}
|
|
3933
|
-
if (info.workspaces.length === 0) {
|
|
3934
|
-
process.stdout.write("No workspaces.\n");
|
|
3935
|
-
return exitAfterFlush(ExitCode.Ok);
|
|
3936
|
-
}
|
|
3937
|
-
for (const w of info.workspaces) {
|
|
3938
|
-
const marker = w.workspaceId === current ? "* " : " ";
|
|
3939
|
-
process.stdout.write(`${marker}${w.workspaceId} ${w.workspaceName} (${w.role})\n`);
|
|
3940
|
-
}
|
|
3941
|
-
if (current === void 0) process.stderr.write("No workspace selected. Run `aitcc workspace use <id>`.\n");
|
|
3942
|
-
return exitAfterFlush(ExitCode.Ok);
|
|
3943
|
-
} catch (err) {
|
|
3944
|
-
return emitFailureFromError(args.json, err);
|
|
3945
|
-
}
|
|
3946
|
-
}
|
|
3947
|
-
}),
|
|
3948
|
-
use: defineCommand({
|
|
5123
|
+
ls: lsCommand,
|
|
5124
|
+
use: useCommand,
|
|
5125
|
+
show: showCommand,
|
|
5126
|
+
partner: partnerCommand,
|
|
5127
|
+
terms: defineCommand({
|
|
3949
5128
|
meta: {
|
|
3950
|
-
name: "
|
|
3951
|
-
description: "
|
|
5129
|
+
name: "terms",
|
|
5130
|
+
description: "Show the console terms-of-agreement state that gate workspace-level features (Toss login, IAP, IAA, biz workspace, promotion money)."
|
|
3952
5131
|
},
|
|
3953
5132
|
args: {
|
|
3954
|
-
|
|
3955
|
-
type: "
|
|
3956
|
-
description: "
|
|
3957
|
-
|
|
5133
|
+
type: {
|
|
5134
|
+
type: "string",
|
|
5135
|
+
description: `Term bucket to inspect: ${WORKSPACE_TERM_TYPES.join(" | ")}. Omit to query every bucket.`
|
|
5136
|
+
},
|
|
5137
|
+
workspace: {
|
|
5138
|
+
type: "string",
|
|
5139
|
+
description: "Workspace ID to inspect. Defaults to the selected workspace."
|
|
3958
5140
|
},
|
|
3959
5141
|
json: {
|
|
3960
5142
|
type: "boolean",
|
|
@@ -3963,114 +5145,158 @@ const workspaceCommand = defineCommand({
|
|
|
3963
5145
|
}
|
|
3964
5146
|
},
|
|
3965
5147
|
async run({ args }) {
|
|
3966
|
-
const
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
5148
|
+
const session = await readSession();
|
|
5149
|
+
if (!session) {
|
|
5150
|
+
emitNotAuthenticated(args.json);
|
|
5151
|
+
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
5152
|
+
}
|
|
5153
|
+
const workspaceId = await resolveWorkspaceArg(args, session.currentWorkspaceId);
|
|
5154
|
+
if (workspaceId === null) return exitAfterFlush(ExitCode.Usage);
|
|
5155
|
+
const typesToQuery = (() => {
|
|
5156
|
+
if (!args.type) return WORKSPACE_TERM_TYPES;
|
|
5157
|
+
const raw = String(args.type).toUpperCase();
|
|
5158
|
+
if (WORKSPACE_TERM_TYPES.includes(raw)) return [raw];
|
|
5159
|
+
return [];
|
|
5160
|
+
})();
|
|
5161
|
+
if (typesToQuery.length === 0) {
|
|
5162
|
+
const message = `--type must be one of: ${WORKSPACE_TERM_TYPES.join(", ")}`;
|
|
3970
5163
|
if (args.json) emitJson({
|
|
3971
5164
|
ok: false,
|
|
3972
|
-
reason: "invalid-
|
|
3973
|
-
|
|
5165
|
+
reason: "invalid-type",
|
|
5166
|
+
allowed: [...WORKSPACE_TERM_TYPES]
|
|
3974
5167
|
});
|
|
3975
5168
|
else process.stderr.write(`${message}\n`);
|
|
3976
5169
|
return exitAfterFlush(ExitCode.Usage);
|
|
3977
5170
|
}
|
|
3978
|
-
const session = await readSession();
|
|
3979
|
-
if (!session) {
|
|
3980
|
-
emitNotAuthenticated(args.json);
|
|
3981
|
-
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
3982
|
-
}
|
|
3983
5171
|
try {
|
|
3984
|
-
const
|
|
3985
|
-
if (
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
5172
|
+
const results = await Promise.all(typesToQuery.map(async (t) => [t, await fetchWorkspaceTerms(workspaceId, t, session.cookies)]));
|
|
5173
|
+
if (typesToQuery.length === 1) {
|
|
5174
|
+
const [type, terms] = results[0];
|
|
5175
|
+
if (args.json) {
|
|
5176
|
+
emitJson({
|
|
5177
|
+
ok: true,
|
|
5178
|
+
workspaceId,
|
|
5179
|
+
type,
|
|
5180
|
+
terms
|
|
5181
|
+
});
|
|
5182
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
5183
|
+
}
|
|
5184
|
+
process.stdout.write(`Workspace ${workspaceId} terms (${type}):\n`);
|
|
5185
|
+
if (terms.length === 0) process.stdout.write(" (no terms required)\n");
|
|
5186
|
+
else for (const t of terms) process.stdout.write(formatTermLines(t));
|
|
5187
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
5188
|
+
}
|
|
5189
|
+
const byType = {};
|
|
5190
|
+
for (const [t, terms] of results) byType[t] = terms;
|
|
5191
|
+
if (args.json) {
|
|
5192
|
+
emitJson({
|
|
5193
|
+
ok: true,
|
|
5194
|
+
workspaceId,
|
|
5195
|
+
byType
|
|
3990
5196
|
});
|
|
3991
|
-
|
|
3992
|
-
return exitAfterFlush(ExitCode.Usage);
|
|
5197
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
3993
5198
|
}
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
5199
|
+
for (const [type, terms] of results) {
|
|
5200
|
+
process.stdout.write(`\n[${type}]\n`);
|
|
5201
|
+
if (terms.length === 0) process.stdout.write(" (no terms required)\n");
|
|
5202
|
+
else for (const t of terms) process.stdout.write(formatTermLines(t));
|
|
3997
5203
|
}
|
|
3998
|
-
if (args.json) emitJson({
|
|
3999
|
-
ok: true,
|
|
4000
|
-
workspaceId: match.workspaceId,
|
|
4001
|
-
workspaceName: match.workspaceName
|
|
4002
|
-
});
|
|
4003
|
-
else process.stdout.write(`Using workspace ${match.workspaceId} (${match.workspaceName}).\n`);
|
|
4004
5204
|
return exitAfterFlush(ExitCode.Ok);
|
|
4005
5205
|
} catch (err) {
|
|
4006
5206
|
return emitFailureFromError(args.json, err);
|
|
4007
5207
|
}
|
|
4008
5208
|
}
|
|
4009
5209
|
}),
|
|
4010
|
-
|
|
5210
|
+
segments: defineCommand({
|
|
4011
5211
|
meta: {
|
|
4012
|
-
name: "
|
|
4013
|
-
description: "
|
|
5212
|
+
name: "segments",
|
|
5213
|
+
description: "Inspect user segments defined in a workspace."
|
|
4014
5214
|
},
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
description: "
|
|
5215
|
+
subCommands: { ls: defineCommand({
|
|
5216
|
+
meta: {
|
|
5217
|
+
name: "ls",
|
|
5218
|
+
description: "List user segments in the selected workspace (the 세그먼트 menu)."
|
|
4019
5219
|
},
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
5220
|
+
args: {
|
|
5221
|
+
workspace: {
|
|
5222
|
+
type: "string",
|
|
5223
|
+
description: "Workspace ID. Defaults to the selected workspace."
|
|
5224
|
+
},
|
|
5225
|
+
category: {
|
|
5226
|
+
type: "string",
|
|
5227
|
+
description: "Category bucket (tab). Defaults to \"생성된 세그먼트\" — the UI's initial tab."
|
|
5228
|
+
},
|
|
5229
|
+
search: {
|
|
5230
|
+
type: "string",
|
|
5231
|
+
description: "Name-contains filter. Empty matches everything."
|
|
5232
|
+
},
|
|
5233
|
+
page: {
|
|
5234
|
+
type: "string",
|
|
5235
|
+
description: "Page number (0-indexed).",
|
|
5236
|
+
default: "0"
|
|
5237
|
+
},
|
|
5238
|
+
json: {
|
|
5239
|
+
type: "boolean",
|
|
5240
|
+
description: "Emit machine-readable JSON to stdout.",
|
|
5241
|
+
default: false
|
|
5242
|
+
}
|
|
5243
|
+
},
|
|
5244
|
+
async run({ args }) {
|
|
5245
|
+
const session = await readSession();
|
|
5246
|
+
if (!session) {
|
|
5247
|
+
emitNotAuthenticated(args.json);
|
|
5248
|
+
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
5249
|
+
}
|
|
5250
|
+
const workspaceId = await resolveWorkspaceArg(args, session.currentWorkspaceId);
|
|
5251
|
+
if (workspaceId === null) return exitAfterFlush(ExitCode.Usage);
|
|
5252
|
+
const pageRaw = String(args.page);
|
|
5253
|
+
const pageNum = Number(pageRaw);
|
|
5254
|
+
if (!Number.isFinite(pageNum) || !Number.isInteger(pageNum) || pageNum < 0) {
|
|
5255
|
+
const message = `--page must be a non-negative integer (got ${JSON.stringify(pageRaw)})`;
|
|
4038
5256
|
if (args.json) emitJson({
|
|
4039
5257
|
ok: false,
|
|
4040
|
-
reason: "invalid-
|
|
5258
|
+
reason: "invalid-page",
|
|
4041
5259
|
message
|
|
4042
5260
|
});
|
|
4043
5261
|
else process.stderr.write(`${message}\n`);
|
|
4044
5262
|
return exitAfterFlush(ExitCode.Usage);
|
|
4045
5263
|
}
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
}
|
|
5264
|
+
try {
|
|
5265
|
+
const page = await fetchWorkspaceSegments({
|
|
5266
|
+
workspaceId,
|
|
5267
|
+
...args.category !== void 0 ? { category: String(args.category) } : {},
|
|
5268
|
+
...args.search !== void 0 ? { search: String(args.search) } : {},
|
|
5269
|
+
page: pageNum
|
|
5270
|
+
}, session.cookies);
|
|
5271
|
+
const category = args.category !== void 0 ? String(args.category) : "생성된 세그먼트";
|
|
5272
|
+
if (args.json) {
|
|
5273
|
+
emitJson({
|
|
5274
|
+
ok: true,
|
|
5275
|
+
workspaceId,
|
|
5276
|
+
category,
|
|
5277
|
+
segments: page.contents,
|
|
5278
|
+
totalPage: page.totalPage,
|
|
5279
|
+
currentPage: page.currentPage
|
|
5280
|
+
});
|
|
5281
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
5282
|
+
}
|
|
5283
|
+
if (page.contents.length === 0) {
|
|
5284
|
+
process.stdout.write(`Workspace ${workspaceId} (${category}): no segments on page ${page.currentPage}\n`);
|
|
5285
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
5286
|
+
}
|
|
5287
|
+
process.stdout.write(`Workspace ${workspaceId} (${category}): ${page.contents.length} segment(s), page ${page.currentPage} of ${page.totalPage}\n`);
|
|
5288
|
+
for (const s of page.contents) {
|
|
5289
|
+
const id = typeof s.id === "string" || typeof s.id === "number" ? s.id : typeof s.segmentId === "string" || typeof s.segmentId === "number" ? s.segmentId : "-";
|
|
5290
|
+
const name = typeof s.name === "string" ? s.name : typeof s.title === "string" ? s.title : "-";
|
|
5291
|
+
const userCount = typeof s.userCount === "number" ? String(s.userCount) : typeof s.count === "number" ? String(s.count) : "-";
|
|
5292
|
+
process.stdout.write(`${id}\t${name}\t${userCount}\n`);
|
|
5293
|
+
}
|
|
4065
5294
|
return exitAfterFlush(ExitCode.Ok);
|
|
5295
|
+
} catch (err) {
|
|
5296
|
+
return emitFailureFromError(args.json, err);
|
|
4066
5297
|
}
|
|
4067
|
-
process.stdout.write(`Workspace ${detail.workspaceId}: ${detail.workspaceName}\n`);
|
|
4068
|
-
if (detail.extra) for (const [k, v] of Object.entries(detail.extra)) process.stdout.write(` ${k}: ${formatScalar(v)}\n`);
|
|
4069
|
-
return exitAfterFlush(ExitCode.Ok);
|
|
4070
|
-
} catch (err) {
|
|
4071
|
-
return emitFailureFromError(args.json, err);
|
|
4072
5298
|
}
|
|
4073
|
-
}
|
|
5299
|
+
}) }
|
|
4074
5300
|
})
|
|
4075
5301
|
}
|
|
4076
5302
|
});
|
|
@@ -4091,7 +5317,9 @@ runMain(defineCommand({
|
|
|
4091
5317
|
app: appCommand,
|
|
4092
5318
|
members: membersCommand,
|
|
4093
5319
|
keys: keysCommand,
|
|
4094
|
-
notices: noticesCommand
|
|
5320
|
+
notices: noticesCommand,
|
|
5321
|
+
me: meCommand,
|
|
5322
|
+
completion: completionCommand
|
|
4095
5323
|
}
|
|
4096
5324
|
}));
|
|
4097
5325
|
//#endregion
|