@ait-co/console-cli 0.1.20 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -340,6 +340,33 @@ async function fetchCerts(workspaceId, miniAppId, cookies, opts = {}) {
340
340
  return c;
341
341
  });
342
342
  }
343
+ async function issueCert(workspaceId, miniAppId, name, cookies, opts = {}) {
344
+ const raw = await requestConsoleApi({
345
+ url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/cert/issue`,
346
+ method: "POST",
347
+ body: { name },
348
+ cookies,
349
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
350
+ });
351
+ if (raw === null || typeof raw !== "object") throw new Error(`Unexpected issue-cert shape for app=${miniAppId}: not an object`);
352
+ const rec = raw;
353
+ const privateKey = typeof rec.privateKey === "string" ? rec.privateKey : null;
354
+ const publicKey = typeof rec.publicKey === "string" ? rec.publicKey : null;
355
+ if (privateKey === null || publicKey === null) throw new Error(`Unexpected issue-cert shape for app=${miniAppId}: missing privateKey/publicKey`);
356
+ return {
357
+ privateKey,
358
+ publicKey
359
+ };
360
+ }
361
+ async function revokeCert(workspaceId, miniAppId, certId, cookies, opts = {}) {
362
+ await requestConsoleApi({
363
+ url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/certs/${encodeURIComponent(certId)}/disable`,
364
+ method: "POST",
365
+ body: {},
366
+ cookies,
367
+ ...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
368
+ });
369
+ }
343
370
  async function fetchShareRewards(params, cookies, opts = {}) {
344
371
  const qs = new URLSearchParams();
345
372
  qs.set("search", params.search ?? "");
@@ -1579,7 +1606,26 @@ const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))
1579
1606
  function isValidEmail(v) {
1580
1607
  return EMAIL_REGEX.test(v.toLowerCase());
1581
1608
  }
1582
- const TITLE_EN_REGEX = /^[A-Za-z0-9 :]+$/;
1609
+ const TITLE_KO_REGEX = /^[가-힣A-Za-z0-9 :·?]+$/;
1610
+ const TITLE_EN_REGEX = /^[A-Za-z0-9 :·?]+$/;
1611
+ const TITLE_KO_MAX_CODEPOINTS_NO_SPACE = 10;
1612
+ const TITLE_EN_MAX_CODEPOINTS_NO_SPACE = 15;
1613
+ function codePointsExcludingSpaces(v) {
1614
+ return [...v].filter((ch) => ch !== " ").length;
1615
+ }
1616
+ function isTitleCaseWord(word) {
1617
+ const chars = [...word];
1618
+ const firstLetterIdx = chars.findIndex((ch) => /[A-Za-z]/.test(ch));
1619
+ if (firstLetterIdx === -1) return true;
1620
+ for (let i = 0; i < chars.length; i++) {
1621
+ const ch = chars[i];
1622
+ if (ch === void 0 || !/[A-Za-z]/.test(ch)) continue;
1623
+ const expectUpper = i === firstLetterIdx;
1624
+ if (expectUpper && ch !== ch.toUpperCase()) return false;
1625
+ if (!expectUpper && ch !== ch.toLowerCase()) return false;
1626
+ }
1627
+ return true;
1628
+ }
1583
1629
  const DETAIL_DESCRIPTION_MAX_CODEPOINTS = 500;
1584
1630
  function isValidHttpUrl(v) {
1585
1631
  try {
@@ -1591,8 +1637,17 @@ function isValidHttpUrl(v) {
1591
1637
  }
1592
1638
  function validateManifest(raw, configDir) {
1593
1639
  const titleKo = requireString(raw, "titleKo");
1640
+ if (!TITLE_KO_REGEX.test(titleKo)) throw new ManifestError("invalid-config", `titleKo may only contain Korean/English letters, digits, spaces, and ":·?" (got "${titleKo}"; server-side rule, errorCode: miniApp.InvalidTitle)`, "titleKo");
1641
+ const titleKoLen = codePointsExcludingSpaces(titleKo);
1642
+ if (titleKoLen > TITLE_KO_MAX_CODEPOINTS_NO_SPACE) throw new ManifestError("invalid-config", `titleKo must be ${TITLE_KO_MAX_CODEPOINTS_NO_SPACE} characters or fewer excluding spaces (got ${titleKoLen}; server-side rule, errorCode: miniApp.InvalidTitle)`, "titleKo");
1594
1643
  const titleEn = requireString(raw, "titleEn");
1595
- if (!TITLE_EN_REGEX.test(titleEn)) throw new ManifestError("invalid-config", `titleEn may only contain English letters, digits, spaces, and colons (got "${titleEn}")`, "titleEn");
1644
+ if (!TITLE_EN_REGEX.test(titleEn)) throw new ManifestError("invalid-config", `titleEn may only contain English letters, digits, spaces, and ":·?" (got "${titleEn}"; server-side rule, errorCode: miniApp.InvalidTitleEn)`, "titleEn");
1645
+ const titleEnLen = codePointsExcludingSpaces(titleEn);
1646
+ if (titleEnLen > TITLE_EN_MAX_CODEPOINTS_NO_SPACE) throw new ManifestError("invalid-config", `titleEn must be ${TITLE_EN_MAX_CODEPOINTS_NO_SPACE} characters or fewer excluding spaces (got ${titleEnLen}; server-side rule, errorCode: miniApp.InvalidTitleEn)`, "titleEn");
1647
+ for (const word of titleEn.split(" ")) {
1648
+ if (word.length === 0) continue;
1649
+ if (!isTitleCaseWord(word)) throw new ManifestError("invalid-config", `titleEn word "${word}" must be title-case (first letter uppercase, rest lowercase); server-side rule, errorCode: miniApp.InvalidTitleEn`, "titleEn");
1650
+ }
1596
1651
  const appName = requireString(raw, "appName");
1597
1652
  const csEmail = requireString(raw, "csEmail");
1598
1653
  if (!isValidEmail(csEmail)) throw new ManifestError("invalid-config", `csEmail is not a valid email address (got ${csEmail})`, "csEmail");
@@ -1927,14 +1982,22 @@ function emitDryRun(json, workspaceId, payload) {
1927
1982
  process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
1928
1983
  }
1929
1984
  }
1985
+ function consoleUrlFor(workspaceId, appId) {
1986
+ return `https://apps-in-toss.toss.im/console/workspace/${workspaceId}/mini-app/${appId}`;
1987
+ }
1930
1988
  function emitSuccess(json, workspaceId, result) {
1989
+ const consoleUrl = result.miniAppId !== void 0 ? consoleUrlFor(workspaceId, result.miniAppId) : null;
1931
1990
  if (json) emitJson({
1932
1991
  ok: true,
1933
1992
  workspaceId,
1934
1993
  appId: result.miniAppId ?? null,
1935
- reviewState: result.reviewState ?? null
1994
+ reviewState: result.reviewState ?? null,
1995
+ consoleUrl
1936
1996
  });
1937
- else process.stdout.write(`Registered mini-app ${result.miniAppId ?? "(id unknown)"} in workspace ${workspaceId} (reviewState=${result.reviewState ?? "unknown"}).\n`);
1997
+ else {
1998
+ process.stdout.write(`Registered mini-app ${result.miniAppId ?? "(id unknown)"} in workspace ${workspaceId} (reviewState=${result.reviewState ?? "unknown"}).\n`);
1999
+ if (consoleUrl !== null) process.stdout.write(`🔗 console: ${consoleUrl}\n`);
2000
+ }
1938
2001
  }
1939
2002
  async function emitFailureAndExit(json, err) {
1940
2003
  return emitFailureFromError(json, err);
@@ -3175,75 +3238,257 @@ const bundlesCommand = defineCommand({
3175
3238
  })
3176
3239
  }
3177
3240
  });
3241
+ const certsLsCommand = defineCommand({
3242
+ meta: {
3243
+ name: "ls",
3244
+ description: "List mTLS certificates issued for a mini-app."
3245
+ },
3246
+ args: {
3247
+ id: {
3248
+ type: "positional",
3249
+ description: "Mini-app ID.",
3250
+ required: true
3251
+ },
3252
+ workspace: {
3253
+ type: "string",
3254
+ description: "Workspace ID. Defaults to the selected workspace."
3255
+ },
3256
+ json: {
3257
+ type: "boolean",
3258
+ description: "Emit machine-readable JSON.",
3259
+ default: false
3260
+ }
3261
+ },
3262
+ async run({ args }) {
3263
+ const appId = parseAppId(args.id);
3264
+ if (appId === null) {
3265
+ if (args.json) emitJson({
3266
+ ok: false,
3267
+ reason: "invalid-id",
3268
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
3269
+ });
3270
+ else process.stderr.write(`app certs ls: invalid id ${JSON.stringify(args.id)}\n`);
3271
+ return exitAfterFlush(ExitCode.Usage);
3272
+ }
3273
+ const ctx = await resolveWorkspaceContext(args);
3274
+ if (!ctx) return;
3275
+ const { session, workspaceId } = ctx;
3276
+ try {
3277
+ const certs = await fetchCerts(workspaceId, appId, session.cookies);
3278
+ if (args.json) {
3279
+ emitJson({
3280
+ ok: true,
3281
+ workspaceId,
3282
+ appId,
3283
+ certs
3284
+ });
3285
+ return exitAfterFlush(ExitCode.Ok);
3286
+ }
3287
+ if (certs.length === 0) {
3288
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): no mTLS certs\n`);
3289
+ return exitAfterFlush(ExitCode.Ok);
3290
+ }
3291
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): ${certs.length} cert(s)\n`);
3292
+ for (const c of certs) {
3293
+ const id = typeof c.id === "string" || typeof c.id === "number" ? c.id : typeof c.certId === "string" || typeof c.certId === "number" ? c.certId : "-";
3294
+ const cn = typeof c.commonName === "string" ? c.commonName : "-";
3295
+ const createdAt = typeof c.createdAt === "string" ? c.createdAt : "";
3296
+ const expiresAt = typeof c.expiresAt === "string" ? c.expiresAt : typeof c.validUntil === "string" ? c.validUntil : "";
3297
+ process.stdout.write(`${id}\t${cn}\t${createdAt}\t${expiresAt}\n`);
3298
+ }
3299
+ return exitAfterFlush(ExitCode.Ok);
3300
+ } catch (err) {
3301
+ return emitFailureFromError(args.json, err);
3302
+ }
3303
+ }
3304
+ });
3305
+ const CERT_NAME_RE = /^[A-Za-z0-9_-]+$/;
3306
+ function parseCertName(raw) {
3307
+ if (typeof raw !== "string" || raw.length === 0) return { error: "--name is required (cert display name)" };
3308
+ if (!CERT_NAME_RE.test(raw)) return { error: "--name must contain only ASCII letters, digits, `-`, or `_` (no spaces, no Korean, no special chars)" };
3309
+ return { value: raw };
3310
+ }
3178
3311
  const certsCommand = defineCommand({
3179
3312
  meta: {
3180
3313
  name: "certs",
3181
3314
  description: "Inspect mTLS certificates for a mini-app."
3182
3315
  },
3183
- subCommands: { ls: defineCommand({
3184
- meta: {
3185
- name: "ls",
3186
- description: "List mTLS certificates issued for a mini-app."
3187
- },
3188
- args: {
3189
- id: {
3190
- type: "positional",
3191
- description: "Mini-app ID.",
3192
- required: true
3316
+ subCommands: {
3317
+ ls: certsLsCommand,
3318
+ issue: defineCommand({
3319
+ meta: {
3320
+ name: "issue",
3321
+ description: "Issue a new mTLS certificate for a mini-app."
3193
3322
  },
3194
- workspace: {
3195
- type: "string",
3196
- description: "Workspace ID. Defaults to the selected workspace."
3323
+ args: {
3324
+ id: {
3325
+ type: "positional",
3326
+ description: "Mini-app ID.",
3327
+ required: true
3328
+ },
3329
+ workspace: {
3330
+ type: "string",
3331
+ description: "Workspace ID. Defaults to the selected workspace."
3332
+ },
3333
+ name: {
3334
+ type: "string",
3335
+ description: "Cert display name. ASCII letters/digits/`-`/`_` only."
3336
+ },
3337
+ out: {
3338
+ type: "string",
3339
+ description: "Directory to write `<name>.crt` and `<name>.key` (mode 0600)."
3340
+ },
3341
+ "print-key": {
3342
+ type: "boolean",
3343
+ description: "Include the private key in the response (default: cert only).",
3344
+ default: false
3345
+ },
3346
+ json: {
3347
+ type: "boolean",
3348
+ description: "Emit machine-readable JSON.",
3349
+ default: false
3350
+ }
3197
3351
  },
3198
- json: {
3199
- type: "boolean",
3200
- description: "Emit machine-readable JSON.",
3201
- default: false
3202
- }
3203
- },
3204
- async run({ args }) {
3205
- const appId = parseAppId(args.id);
3206
- if (appId === null) {
3207
- if (args.json) emitJson({
3208
- ok: false,
3209
- reason: "invalid-id",
3210
- message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
3211
- });
3212
- else process.stderr.write(`app certs ls: invalid id ${JSON.stringify(args.id)}\n`);
3213
- return exitAfterFlush(ExitCode.Usage);
3214
- }
3215
- const ctx = await resolveWorkspaceContext(args);
3216
- if (!ctx) return;
3217
- const { session, workspaceId } = ctx;
3218
- try {
3219
- const certs = await fetchCerts(workspaceId, appId, session.cookies);
3220
- if (args.json) {
3221
- emitJson({
3222
- ok: true,
3223
- workspaceId,
3224
- appId,
3225
- certs
3352
+ async run({ args }) {
3353
+ const appId = parseAppId(args.id);
3354
+ if (appId === null) {
3355
+ if (args.json) emitJson({
3356
+ ok: false,
3357
+ reason: "invalid-id",
3358
+ message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`
3226
3359
  });
3227
- return exitAfterFlush(ExitCode.Ok);
3360
+ else process.stderr.write(`app certs issue: invalid id ${JSON.stringify(args.id)}\n`);
3361
+ return exitAfterFlush(ExitCode.Usage);
3362
+ }
3363
+ const nameResult = parseCertName(args.name);
3364
+ if ("error" in nameResult) {
3365
+ if (args.json) emitJson({
3366
+ ok: false,
3367
+ reason: "invalid-name",
3368
+ message: nameResult.error
3369
+ });
3370
+ else process.stderr.write(`app certs issue: ${nameResult.error}\n`);
3371
+ return exitAfterFlush(ExitCode.Usage);
3228
3372
  }
3229
- if (certs.length === 0) {
3230
- process.stdout.write(`App ${appId} (ws ${workspaceId}): no mTLS certs\n`);
3373
+ const name = nameResult.value;
3374
+ const ctx = await resolveWorkspaceContext(args);
3375
+ if (!ctx) return;
3376
+ const { session, workspaceId } = ctx;
3377
+ try {
3378
+ const result = await issueCert(workspaceId, appId, name, session.cookies);
3379
+ let savedTo;
3380
+ if (typeof args.out === "string" && args.out.length > 0) {
3381
+ const dir = resolve(args.out);
3382
+ try {
3383
+ await mkdir(dir, { recursive: true });
3384
+ } catch (err) {
3385
+ const message = err instanceof Error ? err.message : String(err);
3386
+ if (args.json) emitJson({
3387
+ ok: false,
3388
+ reason: "invalid-out-dir",
3389
+ message: `--out: cannot create ${JSON.stringify(args.out)}: ${message}`
3390
+ });
3391
+ else process.stderr.write(`app certs issue: cannot create --out directory ${JSON.stringify(args.out)}: ${message}\n`);
3392
+ return exitAfterFlush(ExitCode.Usage);
3393
+ }
3394
+ const certPath = resolve(dir, `${name}.crt`);
3395
+ const keyPath = resolve(dir, `${name}.key`);
3396
+ await writeFile(certPath, result.publicKey, { mode: 384 });
3397
+ await writeFile(keyPath, result.privateKey, { mode: 384 });
3398
+ savedTo = {
3399
+ publicKey: certPath,
3400
+ privateKey: keyPath
3401
+ };
3402
+ }
3403
+ if (args.json) {
3404
+ const includeKey = args["print-key"] === true || savedTo === void 0;
3405
+ emitJson({
3406
+ ok: true,
3407
+ workspaceId,
3408
+ appId,
3409
+ name,
3410
+ publicKey: result.publicKey,
3411
+ ...includeKey ? { privateKey: result.privateKey } : {},
3412
+ ...savedTo ? { savedTo } : {}
3413
+ });
3414
+ return exitAfterFlush(ExitCode.Ok);
3415
+ }
3416
+ if (savedTo) process.stdout.write(`App ${appId} (ws ${workspaceId}): issued cert "${name}"\n cert: ${savedTo.publicKey}\n key: ${savedTo.privateKey}\n`);
3417
+ else process.stdout.write(`App ${appId} (ws ${workspaceId}): issued cert "${name}"\n${result.publicKey}` + (result.publicKey.endsWith("\n") ? "" : "\n") + "Private key not shown. Re-run with --out <dir> or --json --print-key to capture it.\n");
3231
3418
  return exitAfterFlush(ExitCode.Ok);
3419
+ } catch (err) {
3420
+ return emitFailureFromError(args.json, err);
3421
+ }
3422
+ }
3423
+ }),
3424
+ revoke: defineCommand({
3425
+ meta: {
3426
+ name: "revoke",
3427
+ description: "Revoke (disable) an mTLS certificate."
3428
+ },
3429
+ args: {
3430
+ certId: {
3431
+ type: "positional",
3432
+ description: "Cert ID (from `app certs ls`).",
3433
+ required: true
3434
+ },
3435
+ app: {
3436
+ type: "string",
3437
+ description: "Mini-app ID the cert belongs to."
3438
+ },
3439
+ workspace: {
3440
+ type: "string",
3441
+ description: "Workspace ID. Defaults to the selected workspace."
3442
+ },
3443
+ json: {
3444
+ type: "boolean",
3445
+ description: "Emit machine-readable JSON.",
3446
+ default: false
3232
3447
  }
3233
- process.stdout.write(`App ${appId} (ws ${workspaceId}): ${certs.length} cert(s)\n`);
3234
- for (const c of certs) {
3235
- const id = typeof c.id === "string" || typeof c.id === "number" ? c.id : typeof c.certId === "string" || typeof c.certId === "number" ? c.certId : "-";
3236
- const cn = typeof c.commonName === "string" ? c.commonName : "-";
3237
- const createdAt = typeof c.createdAt === "string" ? c.createdAt : "";
3238
- const expiresAt = typeof c.expiresAt === "string" ? c.expiresAt : typeof c.validUntil === "string" ? c.validUntil : "";
3239
- process.stdout.write(`${id}\t${cn}\t${createdAt}\t${expiresAt}\n`);
3448
+ },
3449
+ async run({ args }) {
3450
+ const certId = typeof args.certId === "string" ? args.certId.trim() : "";
3451
+ if (certId.length === 0) {
3452
+ if (args.json) emitJson({
3453
+ ok: false,
3454
+ reason: "missing-cert-id",
3455
+ message: "certId positional is required"
3456
+ });
3457
+ else process.stderr.write("app certs revoke: certId is required\n");
3458
+ return exitAfterFlush(ExitCode.Usage);
3459
+ }
3460
+ const appId = parseAppId(args.app);
3461
+ if (appId === null) {
3462
+ if (args.json) emitJson({
3463
+ ok: false,
3464
+ reason: "invalid-id",
3465
+ message: `--app must be a positive integer (got ${JSON.stringify(args.app)})`
3466
+ });
3467
+ else process.stderr.write(`app certs revoke: invalid --app ${JSON.stringify(args.app)}\n`);
3468
+ return exitAfterFlush(ExitCode.Usage);
3469
+ }
3470
+ const ctx = await resolveWorkspaceContext(args);
3471
+ if (!ctx) return;
3472
+ const { session, workspaceId } = ctx;
3473
+ try {
3474
+ await revokeCert(workspaceId, appId, certId, session.cookies);
3475
+ if (args.json) {
3476
+ emitJson({
3477
+ ok: true,
3478
+ workspaceId,
3479
+ appId,
3480
+ certId
3481
+ });
3482
+ return exitAfterFlush(ExitCode.Ok);
3483
+ }
3484
+ process.stdout.write(`App ${appId} (ws ${workspaceId}): revoked cert ${certId}\n`);
3485
+ return exitAfterFlush(ExitCode.Ok);
3486
+ } catch (err) {
3487
+ return emitFailureFromError(args.json, err);
3240
3488
  }
3241
- return exitAfterFlush(ExitCode.Ok);
3242
- } catch (err) {
3243
- return emitFailureFromError(args.json, err);
3244
3489
  }
3245
- }
3246
- }) }
3490
+ })
3491
+ }
3247
3492
  });
3248
3493
  const VALID_TIME_UNITS = [
3249
3494
  "DAY",
@@ -5561,7 +5806,7 @@ function resolveVersion() {
5561
5806
  if (typeof injected === "string" && injected.length > 0) return injected;
5562
5807
  } catch {}
5563
5808
  try {
5564
- return "0.1.20";
5809
+ return "0.1.22";
5565
5810
  } catch {}
5566
5811
  return "0.0.0-dev";
5567
5812
  }