@ait-co/console-cli 0.1.23 → 0.1.25
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/README.md +17 -3
- package/dist/cli.mjs +872 -320
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -164,6 +164,48 @@ async function executeAndUnwrap(url, init, fetchImpl) {
|
|
|
164
164
|
throw new TossApiError(res.status, parsed.error.errorCode, parsed.error.reason, parsed.error.errorType);
|
|
165
165
|
}
|
|
166
166
|
//#endregion
|
|
167
|
+
//#region src/api/certs.ts
|
|
168
|
+
const BASE$5 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
|
|
169
|
+
async function fetchCerts(workspaceId, miniAppId, cookies, opts = {}) {
|
|
170
|
+
const raw = await requestConsoleApi({
|
|
171
|
+
url: `${BASE$5}/workspaces/${workspaceId}/mini-app/${miniAppId}/certs`,
|
|
172
|
+
cookies,
|
|
173
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
174
|
+
});
|
|
175
|
+
if (!Array.isArray(raw)) throw new Error(`Unexpected certs shape for app=${miniAppId}: not an array`);
|
|
176
|
+
return raw.map((c) => {
|
|
177
|
+
if (c === null || typeof c !== "object") return {};
|
|
178
|
+
return c;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
async function issueCert(workspaceId, miniAppId, name, cookies, opts = {}) {
|
|
182
|
+
const raw = await requestConsoleApi({
|
|
183
|
+
url: `${BASE$5}/workspaces/${workspaceId}/mini-app/${miniAppId}/cert/issue`,
|
|
184
|
+
method: "POST",
|
|
185
|
+
body: { name },
|
|
186
|
+
cookies,
|
|
187
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
188
|
+
});
|
|
189
|
+
if (raw === null || typeof raw !== "object") throw new Error(`Unexpected issue-cert shape for app=${miniAppId}: not an object`);
|
|
190
|
+
const rec = raw;
|
|
191
|
+
const privateKey = typeof rec.privateKey === "string" ? rec.privateKey : null;
|
|
192
|
+
const publicKey = typeof rec.publicKey === "string" ? rec.publicKey : null;
|
|
193
|
+
if (privateKey === null || publicKey === null) throw new Error(`Unexpected issue-cert shape for app=${miniAppId}: missing privateKey/publicKey`);
|
|
194
|
+
return {
|
|
195
|
+
privateKey,
|
|
196
|
+
publicKey
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
async function revokeCert(workspaceId, miniAppId, certId, cookies, opts = {}) {
|
|
200
|
+
await requestConsoleApi({
|
|
201
|
+
url: `${BASE$5}/workspaces/${workspaceId}/mini-app/${miniAppId}/certs/${encodeURIComponent(certId)}/disable`,
|
|
202
|
+
method: "POST",
|
|
203
|
+
body: {},
|
|
204
|
+
cookies,
|
|
205
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
167
209
|
//#region src/api/mini-apps.ts
|
|
168
210
|
const BASE$4 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
|
|
169
211
|
async function fetchMiniApps(workspaceId, cookies, opts = {}) {
|
|
@@ -329,45 +371,6 @@ async function fetchConversionMetrics(params, cookies, opts = {}) {
|
|
|
329
371
|
cacheTime: typeof rec.cacheTime === "string" ? rec.cacheTime : void 0
|
|
330
372
|
};
|
|
331
373
|
}
|
|
332
|
-
async function fetchCerts(workspaceId, miniAppId, cookies, opts = {}) {
|
|
333
|
-
const raw = await requestConsoleApi({
|
|
334
|
-
url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/certs`,
|
|
335
|
-
cookies,
|
|
336
|
-
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
337
|
-
});
|
|
338
|
-
if (!Array.isArray(raw)) throw new Error(`Unexpected certs shape for app=${miniAppId}: not an array`);
|
|
339
|
-
return raw.map((c) => {
|
|
340
|
-
if (c === null || typeof c !== "object") return {};
|
|
341
|
-
return c;
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
async function issueCert(workspaceId, miniAppId, name, cookies, opts = {}) {
|
|
345
|
-
const raw = await requestConsoleApi({
|
|
346
|
-
url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/cert/issue`,
|
|
347
|
-
method: "POST",
|
|
348
|
-
body: { name },
|
|
349
|
-
cookies,
|
|
350
|
-
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
351
|
-
});
|
|
352
|
-
if (raw === null || typeof raw !== "object") throw new Error(`Unexpected issue-cert shape for app=${miniAppId}: not an object`);
|
|
353
|
-
const rec = raw;
|
|
354
|
-
const privateKey = typeof rec.privateKey === "string" ? rec.privateKey : null;
|
|
355
|
-
const publicKey = typeof rec.publicKey === "string" ? rec.publicKey : null;
|
|
356
|
-
if (privateKey === null || publicKey === null) throw new Error(`Unexpected issue-cert shape for app=${miniAppId}: missing privateKey/publicKey`);
|
|
357
|
-
return {
|
|
358
|
-
privateKey,
|
|
359
|
-
publicKey
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
async function revokeCert(workspaceId, miniAppId, certId, cookies, opts = {}) {
|
|
363
|
-
await requestConsoleApi({
|
|
364
|
-
url: `${BASE$4}/workspaces/${workspaceId}/mini-app/${miniAppId}/certs/${encodeURIComponent(certId)}/disable`,
|
|
365
|
-
method: "POST",
|
|
366
|
-
body: {},
|
|
367
|
-
cookies,
|
|
368
|
-
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
374
|
async function fetchShareRewards(params, cookies, opts = {}) {
|
|
372
375
|
const qs = new URLSearchParams();
|
|
373
376
|
qs.set("search", params.search ?? "");
|
|
@@ -1587,6 +1590,153 @@ function printContextHeader(ctx, opts) {
|
|
|
1587
1590
|
process.stderr.write(line);
|
|
1588
1591
|
}
|
|
1589
1592
|
//#endregion
|
|
1593
|
+
//#region src/api/me.ts
|
|
1594
|
+
const BASE$3 = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
|
|
1595
|
+
const MEMBER_USER_INFO_URL = `${BASE$3}/members/me/user-info`;
|
|
1596
|
+
async function fetchConsoleMemberUserInfo(cookies, opts = {}) {
|
|
1597
|
+
return requestConsoleApi({
|
|
1598
|
+
url: MEMBER_USER_INFO_URL,
|
|
1599
|
+
cookies,
|
|
1600
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
async function fetchUserTerms(cookies, opts = {}) {
|
|
1604
|
+
const raw = await requestConsoleApi({
|
|
1605
|
+
url: `${BASE$3}/console-user-terms/me`,
|
|
1606
|
+
cookies,
|
|
1607
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
1608
|
+
});
|
|
1609
|
+
if (!Array.isArray(raw)) throw new Error("Unexpected user-terms shape: not an array");
|
|
1610
|
+
return raw.map((entry, i) => {
|
|
1611
|
+
if (!entry || typeof entry !== "object") throw new Error(`Unexpected user-terms entry at index ${i}`);
|
|
1612
|
+
const e = entry;
|
|
1613
|
+
return {
|
|
1614
|
+
required: Boolean(e.required),
|
|
1615
|
+
termsId: typeof e.termsId === "number" ? e.termsId : 0,
|
|
1616
|
+
revisionId: typeof e.revisionId === "number" ? e.revisionId : 0,
|
|
1617
|
+
title: typeof e.title === "string" ? e.title : "",
|
|
1618
|
+
contentsUrl: typeof e.contentsUrl === "string" ? e.contentsUrl : "",
|
|
1619
|
+
actionType: typeof e.actionType === "string" ? e.actionType : "",
|
|
1620
|
+
isAgreed: Boolean(e.isAgreed),
|
|
1621
|
+
isOneTimeConsent: Boolean(e.isOneTimeConsent)
|
|
1622
|
+
};
|
|
1623
|
+
});
|
|
1624
|
+
}
|
|
1625
|
+
//#endregion
|
|
1626
|
+
//#region src/api/workspaces.ts
|
|
1627
|
+
const WORKSPACES_BASE = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
|
|
1628
|
+
async function fetchWorkspaceDetail(workspaceId, cookies, opts = {}) {
|
|
1629
|
+
const raw = await requestConsoleApi({
|
|
1630
|
+
url: `${WORKSPACES_BASE}/workspaces/${workspaceId}`,
|
|
1631
|
+
cookies,
|
|
1632
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
1633
|
+
});
|
|
1634
|
+
const id = raw.id;
|
|
1635
|
+
const name = raw.name;
|
|
1636
|
+
if (typeof id !== "number" || !Number.isInteger(id) || id <= 0 || typeof name !== "string") throw new Error(`Unexpected workspace detail shape for id=${workspaceId}`);
|
|
1637
|
+
const { id: _id, name: _name, ...extra } = raw;
|
|
1638
|
+
return {
|
|
1639
|
+
workspaceId: id,
|
|
1640
|
+
workspaceName: name,
|
|
1641
|
+
extra
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
async function fetchWorkspacePartner(workspaceId, cookies, opts = {}) {
|
|
1645
|
+
const raw = await requestConsoleApi({
|
|
1646
|
+
url: `${WORKSPACES_BASE}/workspaces/${workspaceId}/partner`,
|
|
1647
|
+
cookies,
|
|
1648
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
1649
|
+
});
|
|
1650
|
+
const registered = raw.registered;
|
|
1651
|
+
if (typeof registered !== "boolean") throw new Error(`Unexpected workspace partner shape for id=${workspaceId}`);
|
|
1652
|
+
return {
|
|
1653
|
+
registered,
|
|
1654
|
+
approvalType: typeof raw.approvalType === "string" ? raw.approvalType : null,
|
|
1655
|
+
rejectMessage: typeof raw.rejectMessage === "string" ? raw.rejectMessage : null,
|
|
1656
|
+
partner: raw.partner && typeof raw.partner === "object" ? raw.partner : null
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
const WORKSPACE_TERM_TYPES = [
|
|
1660
|
+
"TOSS_LOGIN",
|
|
1661
|
+
"BIZ_WORKSPACE",
|
|
1662
|
+
"TOSS_PROMOTION_MONEY",
|
|
1663
|
+
"IAA",
|
|
1664
|
+
"IAP"
|
|
1665
|
+
];
|
|
1666
|
+
const DEFAULT_SEGMENT_CATEGORY = "생성된 세그먼트";
|
|
1667
|
+
async function fetchWorkspaceSegments(params, cookies, opts = {}) {
|
|
1668
|
+
const page = params.page ?? 0;
|
|
1669
|
+
const qs = new URLSearchParams();
|
|
1670
|
+
qs.set("category", params.category ?? DEFAULT_SEGMENT_CATEGORY);
|
|
1671
|
+
qs.set("search", params.search ?? "");
|
|
1672
|
+
qs.set("page", String(page));
|
|
1673
|
+
const raw = await requestConsoleApi({
|
|
1674
|
+
url: `${WORKSPACES_BASE}/workspaces/${params.workspaceId}/segments/list?${qs.toString()}`,
|
|
1675
|
+
cookies,
|
|
1676
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
1677
|
+
});
|
|
1678
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Unexpected segments shape for workspace=${params.workspaceId}`);
|
|
1679
|
+
const data = raw;
|
|
1680
|
+
return {
|
|
1681
|
+
contents: (Array.isArray(data.contents) ? data.contents : []).map((c) => c && typeof c === "object" ? c : {}),
|
|
1682
|
+
totalPage: typeof data.totalPage === "number" ? data.totalPage : 0,
|
|
1683
|
+
currentPage: typeof data.currentPage === "number" ? data.currentPage : page
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
async function fetchWorkspaceTerms(workspaceId, type, cookies, opts = {}) {
|
|
1687
|
+
const raw = await requestConsoleApi({
|
|
1688
|
+
url: `${WORKSPACES_BASE}/workspaces/${workspaceId}/console-workspace-terms/${type}/skip-permission`,
|
|
1689
|
+
cookies,
|
|
1690
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
1691
|
+
});
|
|
1692
|
+
if (!Array.isArray(raw)) throw new Error(`Unexpected workspace terms shape for type=${type}`);
|
|
1693
|
+
return raw.map((entry, i) => {
|
|
1694
|
+
if (!entry || typeof entry !== "object") throw new Error(`Unexpected workspace terms entry at index ${i} for type=${type}`);
|
|
1695
|
+
const e = entry;
|
|
1696
|
+
return {
|
|
1697
|
+
required: Boolean(e.required),
|
|
1698
|
+
termsId: typeof e.termsId === "number" ? e.termsId : 0,
|
|
1699
|
+
revisionId: typeof e.revisionId === "number" ? e.revisionId : 0,
|
|
1700
|
+
title: typeof e.title === "string" ? e.title : "",
|
|
1701
|
+
contentsUrl: typeof e.contentsUrl === "string" ? e.contentsUrl : "",
|
|
1702
|
+
actionType: typeof e.actionType === "string" ? e.actionType : "",
|
|
1703
|
+
isAgreed: Boolean(e.isAgreed),
|
|
1704
|
+
isOneTimeConsent: Boolean(e.isOneTimeConsent)
|
|
1705
|
+
};
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
/**
|
|
1709
|
+
* Persist agreement for one-or-more workspace terms. The endpoint takes a
|
|
1710
|
+
* single `agreedList` regardless of which bucket the terms came from — the
|
|
1711
|
+
* type tag is implicit in the (termsId, revisionId) pairs.
|
|
1712
|
+
*
|
|
1713
|
+
* Captured behaviour (2026-05-08, ws=36577):
|
|
1714
|
+
* - `POST /workspaces/<wid>/console-workspace-terms` with body
|
|
1715
|
+
* `{"agreedList":[{"termsId": <int>, "revisionId": <int>}, ...]}`
|
|
1716
|
+
* - Response on success: `{"resultType":"SUCCESS","success":{}}` — no
|
|
1717
|
+
* useful payload. We resolve `void`.
|
|
1718
|
+
* - Re-submitting an already-agreed term returns `errorCode: 500`
|
|
1719
|
+
* (Internal Server Error). The server is NOT idempotent, so callers
|
|
1720
|
+
* must filter to `isAgreed === false` before invoking.
|
|
1721
|
+
* - Empty `agreedList` returns SUCCESS (no-op), but we throw client-side
|
|
1722
|
+
* before sending — round-tripping a no-op request is wasted.
|
|
1723
|
+
*
|
|
1724
|
+
* Failure surfaces through `TossApiError` like every other write helper.
|
|
1725
|
+
*/
|
|
1726
|
+
async function agreeWorkspaceTerms(workspaceId, terms, cookies, opts = {}) {
|
|
1727
|
+
if (terms.length === 0) throw new Error("agreeWorkspaceTerms requires at least one term");
|
|
1728
|
+
await requestConsoleApi({
|
|
1729
|
+
method: "POST",
|
|
1730
|
+
url: `${WORKSPACES_BASE}/workspaces/${workspaceId}/console-workspace-terms`,
|
|
1731
|
+
cookies,
|
|
1732
|
+
body: { agreedList: terms.map(({ termsId, revisionId }) => ({
|
|
1733
|
+
termsId,
|
|
1734
|
+
revisionId
|
|
1735
|
+
})) },
|
|
1736
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
//#endregion
|
|
1590
1740
|
//#region src/config/ait-bundle.ts
|
|
1591
1741
|
var AitBundleError = class extends Error {
|
|
1592
1742
|
path;
|
|
@@ -1878,30 +2028,21 @@ async function runDeploy(args, deps = {}) {
|
|
|
1878
2028
|
const steps = ["upload"];
|
|
1879
2029
|
if (requestReview) steps.push("review");
|
|
1880
2030
|
if (release) steps.push("release");
|
|
1881
|
-
if (args.dryRun) {
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
const stepsLine = steps.map((s) => {
|
|
1897
|
-
if (s === "review") return `review (releaseNotes: ${JSON.stringify(releaseNotes ?? "")})`;
|
|
1898
|
-
if (s === "release") return `release (${confirm ? "confirmed" : "NOT confirmed"})`;
|
|
1899
|
-
return s;
|
|
1900
|
-
}).join(" → ");
|
|
1901
|
-
process.stdout.write(`DRY RUN\n app ${appId}\n workspace ${workspaceId}\n bundle ${args.path} (${bundleInfo.bytes.byteLength} bytes)\n deploymentId ${deploymentId}\n memo ${memo ?? "(none)"}\n steps ${stepsLine}\n`);
|
|
1902
|
-
}
|
|
1903
|
-
return exitAfterFlush(ExitCode.Ok);
|
|
1904
|
-
}
|
|
2031
|
+
if (args.dryRun) return runDryRun({
|
|
2032
|
+
json: args.json,
|
|
2033
|
+
path: args.path,
|
|
2034
|
+
bundleInfo,
|
|
2035
|
+
deploymentId,
|
|
2036
|
+
explicitDeploymentId: typeof args.deploymentId === "string" && args.deploymentId !== "",
|
|
2037
|
+
workspaceId,
|
|
2038
|
+
appId,
|
|
2039
|
+
session,
|
|
2040
|
+
steps,
|
|
2041
|
+
memo,
|
|
2042
|
+
releaseNotes,
|
|
2043
|
+
confirm,
|
|
2044
|
+
fetchImpl: deps.fetchImpl
|
|
2045
|
+
});
|
|
1905
2046
|
const apiOpts = deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {};
|
|
1906
2047
|
let uploaded = false;
|
|
1907
2048
|
let bundleRecord = null;
|
|
@@ -2037,38 +2178,154 @@ async function emitPartialFailure(json, err, progress) {
|
|
|
2037
2178
|
else process.stderr.write(`Unexpected error: ${err.message}\n`);
|
|
2038
2179
|
return exitAfterFlush(ExitCode.ApiError);
|
|
2039
2180
|
}
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2181
|
+
const WORKSPACE_TERM_ERROR_CODES = {
|
|
2182
|
+
TOSS_LOGIN: 4037,
|
|
2183
|
+
BIZ_WORKSPACE: 4040,
|
|
2184
|
+
TOSS_PROMOTION_MONEY: 4039,
|
|
2185
|
+
IAA: 4099,
|
|
2186
|
+
IAP: 5001
|
|
2187
|
+
};
|
|
2188
|
+
async function runDryRun(input) {
|
|
2189
|
+
const apiOpts = input.fetchImpl ? { fetchImpl: input.fetchImpl } : {};
|
|
2190
|
+
const embedded = input.bundleInfo.deploymentId;
|
|
2191
|
+
const flagMatch = input.explicitDeploymentId ? input.deploymentId === embedded : null;
|
|
2192
|
+
const [permissions, terms] = await Promise.all([fetchPermissions(input.workspaceId, input.session, apiOpts), fetchTermsBlockers(input.workspaceId, input.session, apiOpts)]);
|
|
2193
|
+
const wouldSucceed = (flagMatch === null || flagMatch === true) && terms.blockers.length === 0;
|
|
2194
|
+
if (input.json) {
|
|
2195
|
+
emitJson({
|
|
2196
|
+
ok: true,
|
|
2197
|
+
dryRun: true,
|
|
2198
|
+
wouldSucceed,
|
|
2199
|
+
workspaceId: input.workspaceId,
|
|
2200
|
+
appId: input.appId,
|
|
2201
|
+
deploymentId: input.deploymentId,
|
|
2202
|
+
bundleFormat: input.bundleInfo.format,
|
|
2203
|
+
bytes: input.bundleInfo.bytes.byteLength,
|
|
2204
|
+
steps: input.steps,
|
|
2205
|
+
memo: input.memo ?? null,
|
|
2206
|
+
releaseNotes: input.releaseNotes ?? null,
|
|
2207
|
+
confirmed: input.confirm,
|
|
2208
|
+
bundle: {
|
|
2209
|
+
path: input.path,
|
|
2210
|
+
format: input.bundleInfo.format,
|
|
2211
|
+
deploymentId: input.deploymentId,
|
|
2212
|
+
embeddedDeploymentId: embedded,
|
|
2213
|
+
deploymentIdSource: input.explicitDeploymentId ? "flag" : "bundle",
|
|
2214
|
+
flagMatch,
|
|
2215
|
+
size: input.bundleInfo.bytes.byteLength
|
|
2216
|
+
},
|
|
2217
|
+
context: {
|
|
2218
|
+
workspaceId: input.workspaceId,
|
|
2219
|
+
appId: input.appId,
|
|
2220
|
+
sessionValid: true,
|
|
2221
|
+
permissions
|
|
2222
|
+
},
|
|
2223
|
+
terms
|
|
2224
|
+
});
|
|
2225
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2226
|
+
}
|
|
2227
|
+
process.stdout.write(renderDryRunText(input, {
|
|
2228
|
+
embedded,
|
|
2229
|
+
flagMatch,
|
|
2230
|
+
permissions,
|
|
2231
|
+
terms,
|
|
2232
|
+
wouldSucceed
|
|
2233
|
+
}));
|
|
2234
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
2050
2235
|
}
|
|
2051
|
-
async function
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
if (!entry || typeof entry !== "object") throw new Error(`Unexpected user-terms entry at index ${i}`);
|
|
2060
|
-
const e = entry;
|
|
2236
|
+
async function fetchPermissions(workspaceId, session, apiOpts) {
|
|
2237
|
+
try {
|
|
2238
|
+
const ws = (await fetchConsoleMemberUserInfo(session.cookies, apiOpts)).workspaces.find((w) => w.workspaceId === workspaceId);
|
|
2239
|
+
if (!ws) return {
|
|
2240
|
+
role: null,
|
|
2241
|
+
source: "unknown",
|
|
2242
|
+
error: `current user has no membership in workspace ${workspaceId}`
|
|
2243
|
+
};
|
|
2061
2244
|
return {
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
revisionId: typeof e.revisionId === "number" ? e.revisionId : 0,
|
|
2065
|
-
title: typeof e.title === "string" ? e.title : "",
|
|
2066
|
-
contentsUrl: typeof e.contentsUrl === "string" ? e.contentsUrl : "",
|
|
2067
|
-
actionType: typeof e.actionType === "string" ? e.actionType : "",
|
|
2068
|
-
isAgreed: Boolean(e.isAgreed),
|
|
2069
|
-
isOneTimeConsent: Boolean(e.isOneTimeConsent)
|
|
2245
|
+
role: ws.role,
|
|
2246
|
+
source: "members/me"
|
|
2070
2247
|
};
|
|
2071
|
-
})
|
|
2248
|
+
} catch (err) {
|
|
2249
|
+
return {
|
|
2250
|
+
role: null,
|
|
2251
|
+
source: "unknown",
|
|
2252
|
+
error: err.message
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
async function fetchTermsBlockers(workspaceId, session, apiOpts) {
|
|
2257
|
+
try {
|
|
2258
|
+
const [userTerms, workspaceResults] = await Promise.all([fetchUserTerms(session.cookies, apiOpts), Promise.all(WORKSPACE_TERM_TYPES.map(async (t) => [t, await fetchWorkspaceTerms(workspaceId, t, session.cookies, apiOpts)]))]);
|
|
2259
|
+
const blockers = [];
|
|
2260
|
+
for (const t of userTerms) if (t.required && !t.isAgreed) blockers.push({
|
|
2261
|
+
scope: "user",
|
|
2262
|
+
type: "USER_TERMS",
|
|
2263
|
+
errorCode: 4036,
|
|
2264
|
+
title: t.title,
|
|
2265
|
+
action: "aitcc me terms"
|
|
2266
|
+
});
|
|
2267
|
+
for (const [type, terms] of workspaceResults) for (const t of terms) {
|
|
2268
|
+
if (!t.required || t.isAgreed) continue;
|
|
2269
|
+
blockers.push({
|
|
2270
|
+
scope: "workspace",
|
|
2271
|
+
type,
|
|
2272
|
+
errorCode: WORKSPACE_TERM_ERROR_CODES[type],
|
|
2273
|
+
title: t.title,
|
|
2274
|
+
action: `aitcc workspace terms --type ${type}`
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
return {
|
|
2278
|
+
blockers,
|
|
2279
|
+
checked: true
|
|
2280
|
+
};
|
|
2281
|
+
} catch (err) {
|
|
2282
|
+
return {
|
|
2283
|
+
blockers: [],
|
|
2284
|
+
checked: false,
|
|
2285
|
+
error: err.message
|
|
2286
|
+
};
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
function renderDryRunText(input, derived) {
|
|
2290
|
+
const lines = [];
|
|
2291
|
+
lines.push(`DRY RUN — app deploy ${input.appId}\n`);
|
|
2292
|
+
lines.push("\nBundle\n");
|
|
2293
|
+
lines.push(` path ${input.path}\n`);
|
|
2294
|
+
lines.push(` format ${input.bundleInfo.format.toUpperCase()}\n`);
|
|
2295
|
+
lines.push(` deploymentId ${input.deploymentId}\n`);
|
|
2296
|
+
if (derived.flagMatch === false) lines.push(` flag match MISMATCH (bundle embeds ${derived.embedded})\n`);
|
|
2297
|
+
else if (derived.flagMatch === true) lines.push(` flag match ok (matches embedded)\n`);
|
|
2298
|
+
lines.push(` size ${formatBytes(input.bundleInfo.bytes.byteLength)}\n`);
|
|
2299
|
+
lines.push("\nContext\n");
|
|
2300
|
+
lines.push(` workspace ${input.workspaceId}\n`);
|
|
2301
|
+
lines.push(` app ${input.appId}\n`);
|
|
2302
|
+
lines.push(` session valid\n`);
|
|
2303
|
+
if (derived.permissions.role !== null) lines.push(` permissions ${derived.permissions.role}\n`);
|
|
2304
|
+
else lines.push(` permissions unknown${derived.permissions.error ? ` (${derived.permissions.error})` : ""}\n`);
|
|
2305
|
+
lines.push("\nTerms\n");
|
|
2306
|
+
if (!derived.terms.checked) {
|
|
2307
|
+
lines.push(` warning: could not check terms status (${derived.terms.error ?? "unknown error"}).\n`);
|
|
2308
|
+
lines.push(" live deploy may still fail with a 4032/4036/4037/4039/4040/4099/5001 errorCode.\n");
|
|
2309
|
+
} else if (derived.terms.blockers.length === 0) lines.push(" all deploy-related terms are agreed\n");
|
|
2310
|
+
else for (const b of derived.terms.blockers) lines.push(` blocked: ${b.scope}/${b.type} — ${b.title} (errorCode ${b.errorCode})\n action: ${b.action}\n`);
|
|
2311
|
+
lines.push("\nPlan\n");
|
|
2312
|
+
const stepsLine = input.steps.map((s) => {
|
|
2313
|
+
if (s === "review") return `review (releaseNotes: ${JSON.stringify(input.releaseNotes ?? "")})`;
|
|
2314
|
+
if (s === "release") return `release (${input.confirm ? "confirmed" : "NOT confirmed"})`;
|
|
2315
|
+
return s;
|
|
2316
|
+
}).join(" → ");
|
|
2317
|
+
lines.push(` steps ${stepsLine}\n`);
|
|
2318
|
+
lines.push(` memo ${input.memo ?? "(none)"}\n`);
|
|
2319
|
+
lines.push("\nResult\n");
|
|
2320
|
+
if (!derived.wouldSucceed) lines.push(" Live deploy would fail. Resolve the blocked items above, then re-run.\n");
|
|
2321
|
+
else if (derived.permissions.role === null) lines.push(" Live deploy would clear bundle + terms checks. Workspace membership could not be confirmed; live deploy may still fail with a permissions error.\n");
|
|
2322
|
+
else lines.push(" Live deploy would clear every pre-flight check.\n");
|
|
2323
|
+
return lines.join("");
|
|
2324
|
+
}
|
|
2325
|
+
function formatBytes(n) {
|
|
2326
|
+
if (n < 1024) return `${n} B`;
|
|
2327
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
2328
|
+
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
2072
2329
|
}
|
|
2073
2330
|
//#endregion
|
|
2074
2331
|
//#region src/config/app-manifest.ts
|
|
@@ -2336,7 +2593,7 @@ async function runAppInit(args) {
|
|
|
2336
2593
|
workspaceId = await pickWorkspace(session.cookies);
|
|
2337
2594
|
categoryIds = await pickCategories(session.cookies);
|
|
2338
2595
|
} catch (err) {
|
|
2339
|
-
if (isPromptCancelled$
|
|
2596
|
+
if (isPromptCancelled$2(err)) {
|
|
2340
2597
|
process.stderr.write("Aborted.\n");
|
|
2341
2598
|
return exitAfterFlush(ExitCode.Usage);
|
|
2342
2599
|
}
|
|
@@ -2379,7 +2636,7 @@ async function runAppInit(args) {
|
|
|
2379
2636
|
categoryIds
|
|
2380
2637
|
};
|
|
2381
2638
|
} catch (err) {
|
|
2382
|
-
if (isPromptCancelled$
|
|
2639
|
+
if (isPromptCancelled$2(err)) {
|
|
2383
2640
|
process.stderr.write("Aborted.\n");
|
|
2384
2641
|
return exitAfterFlush(ExitCode.Usage);
|
|
2385
2642
|
}
|
|
@@ -2438,7 +2695,7 @@ async function fileExists(path) {
|
|
|
2438
2695
|
return false;
|
|
2439
2696
|
}
|
|
2440
2697
|
}
|
|
2441
|
-
function isPromptCancelled$
|
|
2698
|
+
function isPromptCancelled$2(err) {
|
|
2442
2699
|
return err instanceof Error && err.name === "ExitPromptError";
|
|
2443
2700
|
}
|
|
2444
2701
|
async function pickWorkspace(cookies) {
|
|
@@ -4296,6 +4553,25 @@ const bundlesCommand = defineCommand({
|
|
|
4296
4553
|
})
|
|
4297
4554
|
}
|
|
4298
4555
|
});
|
|
4556
|
+
const CERT_EXPIRY_WARN_DAYS = 30;
|
|
4557
|
+
const MS_PER_DAY = 864e5;
|
|
4558
|
+
function deriveDaysUntilExpiry(cert, now) {
|
|
4559
|
+
const raw = cert.expireTs;
|
|
4560
|
+
let ts = null;
|
|
4561
|
+
if (typeof raw === "number" && Number.isFinite(raw)) ts = raw;
|
|
4562
|
+
else if (typeof raw === "string") {
|
|
4563
|
+
const parsed = Date.parse(raw);
|
|
4564
|
+
if (Number.isFinite(parsed)) ts = parsed;
|
|
4565
|
+
}
|
|
4566
|
+
if (ts === null) return null;
|
|
4567
|
+
return Math.floor((ts - now) / MS_PER_DAY);
|
|
4568
|
+
}
|
|
4569
|
+
function expiryMarker(days) {
|
|
4570
|
+
if (days === null) return "";
|
|
4571
|
+
if (days < 0) return `\t⚠ 만료됨`;
|
|
4572
|
+
if (days <= CERT_EXPIRY_WARN_DAYS) return `\t⚠ 만료 임박 (${days}일)`;
|
|
4573
|
+
return "";
|
|
4574
|
+
}
|
|
4299
4575
|
const certsLsCommand = defineCommand({
|
|
4300
4576
|
meta: {
|
|
4301
4577
|
name: "ls",
|
|
@@ -4330,27 +4606,153 @@ const certsLsCommand = defineCommand({
|
|
|
4330
4606
|
const { session, workspaceId } = ctx;
|
|
4331
4607
|
try {
|
|
4332
4608
|
const certs = await fetchCerts(workspaceId, appId, session.cookies);
|
|
4609
|
+
const now = Date.now();
|
|
4610
|
+
const augmented = certs.map((c) => ({
|
|
4611
|
+
...c,
|
|
4612
|
+
daysUntilExpiry: deriveDaysUntilExpiry(c, now)
|
|
4613
|
+
}));
|
|
4614
|
+
if (args.json) {
|
|
4615
|
+
emitJson({
|
|
4616
|
+
ok: true,
|
|
4617
|
+
workspaceId,
|
|
4618
|
+
appId,
|
|
4619
|
+
certs: augmented
|
|
4620
|
+
});
|
|
4621
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
4622
|
+
}
|
|
4623
|
+
if (augmented.length === 0) {
|
|
4624
|
+
process.stdout.write(`App ${appId} (ws ${workspaceId}): no mTLS certs\n`);
|
|
4625
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
4626
|
+
}
|
|
4627
|
+
process.stdout.write(`App ${appId} (ws ${workspaceId}): ${augmented.length} cert(s)\n`);
|
|
4628
|
+
for (const c of augmented) {
|
|
4629
|
+
const id = typeof c.id === "string" || typeof c.id === "number" ? c.id : typeof c.certId === "string" || typeof c.certId === "number" ? c.certId : "-";
|
|
4630
|
+
const cn = typeof c.commonName === "string" ? c.commonName : "-";
|
|
4631
|
+
const createdAt = typeof c.createdAt === "string" ? c.createdAt : "";
|
|
4632
|
+
const expiresAt = typeof c.expiresAt === "string" ? c.expiresAt : typeof c.validUntil === "string" ? c.validUntil : typeof c.expireTs === "number" && Number.isFinite(c.expireTs) ? new Date(c.expireTs).toISOString() : "";
|
|
4633
|
+
process.stdout.write(`${id}\t${cn}\t${createdAt}\t${expiresAt}${expiryMarker(c.daysUntilExpiry)}\n`);
|
|
4634
|
+
}
|
|
4635
|
+
return exitAfterFlush(ExitCode.Ok);
|
|
4636
|
+
} catch (err) {
|
|
4637
|
+
return emitFailureFromError(args.json, err);
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
});
|
|
4641
|
+
function pickCertById(certs, certId) {
|
|
4642
|
+
const target = certId.trim();
|
|
4643
|
+
if (target.length === 0) return null;
|
|
4644
|
+
for (const c of certs) {
|
|
4645
|
+
const candidate = typeof c.id === "string" || typeof c.id === "number" ? c.id : typeof c.certId === "string" || typeof c.certId === "number" ? c.certId : null;
|
|
4646
|
+
if (candidate !== null && String(candidate) === target) return c;
|
|
4647
|
+
}
|
|
4648
|
+
return null;
|
|
4649
|
+
}
|
|
4650
|
+
function augmentCertExpiry(cert, now = Date.now()) {
|
|
4651
|
+
let expiresAtMs;
|
|
4652
|
+
if (typeof cert.expireTs === "number" && Number.isFinite(cert.expireTs)) expiresAtMs = cert.expireTs;
|
|
4653
|
+
else if (typeof cert.expiresAt === "string") {
|
|
4654
|
+
const t = Date.parse(cert.expiresAt);
|
|
4655
|
+
if (Number.isFinite(t)) expiresAtMs = t;
|
|
4656
|
+
} else if (typeof cert.validUntil === "string") {
|
|
4657
|
+
const t = Date.parse(cert.validUntil);
|
|
4658
|
+
if (Number.isFinite(t)) expiresAtMs = t;
|
|
4659
|
+
}
|
|
4660
|
+
if (expiresAtMs === void 0) return {};
|
|
4661
|
+
const daysUntilExpiry = Math.floor((expiresAtMs - now) / 864e5);
|
|
4662
|
+
return {
|
|
4663
|
+
expiresAtMs,
|
|
4664
|
+
daysUntilExpiry
|
|
4665
|
+
};
|
|
4666
|
+
}
|
|
4667
|
+
const certsShowCommand = defineCommand({
|
|
4668
|
+
meta: {
|
|
4669
|
+
name: "show",
|
|
4670
|
+
description: "Show a single mTLS certificate by id (metadata only — no PEM)."
|
|
4671
|
+
},
|
|
4672
|
+
args: {
|
|
4673
|
+
certId: {
|
|
4674
|
+
type: "positional",
|
|
4675
|
+
description: "Cert ID (from `app certs ls`).",
|
|
4676
|
+
required: true
|
|
4677
|
+
},
|
|
4678
|
+
app: {
|
|
4679
|
+
type: "string",
|
|
4680
|
+
description: "Mini-app ID the cert belongs to."
|
|
4681
|
+
},
|
|
4682
|
+
workspace: {
|
|
4683
|
+
type: "string",
|
|
4684
|
+
description: "Workspace ID. Defaults to the selected workspace."
|
|
4685
|
+
},
|
|
4686
|
+
json: {
|
|
4687
|
+
type: "boolean",
|
|
4688
|
+
description: "Emit machine-readable JSON.",
|
|
4689
|
+
default: false
|
|
4690
|
+
}
|
|
4691
|
+
},
|
|
4692
|
+
async run({ args }) {
|
|
4693
|
+
const certId = typeof args.certId === "string" ? args.certId.trim() : "";
|
|
4694
|
+
if (certId.length === 0) {
|
|
4695
|
+
if (args.json) emitJson({
|
|
4696
|
+
ok: false,
|
|
4697
|
+
reason: "missing-cert-id",
|
|
4698
|
+
message: "certId positional is required"
|
|
4699
|
+
});
|
|
4700
|
+
else process.stderr.write("app certs show: certId is required\n");
|
|
4701
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
4702
|
+
}
|
|
4703
|
+
const ctx = await resolveAppOrFail({
|
|
4704
|
+
json: args.json,
|
|
4705
|
+
appIdRaw: args.app,
|
|
4706
|
+
appIdField: "app",
|
|
4707
|
+
...args.workspace !== void 0 ? { workspace: args.workspace } : {}
|
|
4708
|
+
});
|
|
4709
|
+
if (!ctx) return;
|
|
4710
|
+
const appId = await requireMiniAppId(ctx, args.json);
|
|
4711
|
+
if (appId === null) return;
|
|
4712
|
+
printContextHeader(ctx, { json: args.json });
|
|
4713
|
+
const { session, workspaceId } = ctx;
|
|
4714
|
+
try {
|
|
4715
|
+
const match = pickCertById(await fetchCerts(workspaceId, appId, session.cookies), certId);
|
|
4716
|
+
if (match === null) {
|
|
4717
|
+
if (args.json) emitJson({
|
|
4718
|
+
ok: false,
|
|
4719
|
+
reason: "not-found",
|
|
4720
|
+
workspaceId,
|
|
4721
|
+
appId,
|
|
4722
|
+
certId,
|
|
4723
|
+
message: `cert ${certId} not found on app ${appId}`
|
|
4724
|
+
});
|
|
4725
|
+
else process.stderr.write(`app certs show: cert ${certId} not found on app ${appId} (ws ${workspaceId})\n`);
|
|
4726
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
4727
|
+
}
|
|
4728
|
+
const expiry = augmentCertExpiry(match);
|
|
4333
4729
|
if (args.json) {
|
|
4334
4730
|
emitJson({
|
|
4335
4731
|
ok: true,
|
|
4336
4732
|
workspaceId,
|
|
4337
4733
|
appId,
|
|
4338
|
-
|
|
4734
|
+
cert: match,
|
|
4735
|
+
...expiry
|
|
4339
4736
|
});
|
|
4340
4737
|
return exitAfterFlush(ExitCode.Ok);
|
|
4341
4738
|
}
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4739
|
+
const id = typeof match.id === "string" || typeof match.id === "number" ? match.id : typeof match.certId === "string" || typeof match.certId === "number" ? match.certId : "-";
|
|
4740
|
+
const name = typeof match.name === "string" ? match.name : "-";
|
|
4741
|
+
const cn = typeof match.commonName === "string" ? match.commonName : "";
|
|
4742
|
+
const createdAt = typeof match.createdAt === "string" ? match.createdAt : "";
|
|
4743
|
+
const expiresAtIso = expiry.expiresAtMs !== void 0 ? new Date(expiry.expiresAtMs).toISOString() : "";
|
|
4744
|
+
const status = typeof match.status === "string" ? match.status : "";
|
|
4745
|
+
const lines = [`App ${appId} (ws ${workspaceId}): cert ${id}`, ` name: ${name}`];
|
|
4746
|
+
if (cn) lines.push(` commonName: ${cn}`);
|
|
4747
|
+
if (createdAt) lines.push(` createdAt: ${createdAt}`);
|
|
4748
|
+
if (expiresAtIso) lines.push(` expiresAt: ${expiresAtIso}`);
|
|
4749
|
+
if (expiry.daysUntilExpiry !== void 0) {
|
|
4750
|
+
const d = expiry.daysUntilExpiry;
|
|
4751
|
+
const suffix = d > 0 ? ` (D-${d})` : d === 0 ? " (expires today)" : ` (expired ${-d} day(s) ago)`;
|
|
4752
|
+
lines.push(` daysUntilExpiry: ${d}${suffix}`);
|
|
4353
4753
|
}
|
|
4754
|
+
if (status) lines.push(` status: ${status}`);
|
|
4755
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
4354
4756
|
return exitAfterFlush(ExitCode.Ok);
|
|
4355
4757
|
} catch (err) {
|
|
4356
4758
|
return emitFailureFromError(args.json, err);
|
|
@@ -4370,6 +4772,7 @@ const certsCommand = defineCommand({
|
|
|
4370
4772
|
},
|
|
4371
4773
|
subCommands: {
|
|
4372
4774
|
ls: certsLsCommand,
|
|
4775
|
+
show: certsShowCommand,
|
|
4373
4776
|
issue: defineCommand({
|
|
4374
4777
|
meta: {
|
|
4375
4778
|
name: "issue",
|
|
@@ -5453,7 +5856,7 @@ async function runCommand(command, opts) {
|
|
|
5453
5856
|
function isCommandNotFound(err) {
|
|
5454
5857
|
return err.code === "ENOENT";
|
|
5455
5858
|
}
|
|
5456
|
-
function stripTrailingNewline(s) {
|
|
5859
|
+
function stripTrailingNewline$1(s) {
|
|
5457
5860
|
return s.replace(/\r?\n$/, "");
|
|
5458
5861
|
}
|
|
5459
5862
|
function redactStderr(stderr) {
|
|
@@ -5487,7 +5890,7 @@ const LINUX_BACKEND = {
|
|
|
5487
5890
|
throw new CredentialBackendCommandError("secret-tool lookup", result.exitCode, redactStderr(result.stderr));
|
|
5488
5891
|
}
|
|
5489
5892
|
if (result.stdout.length === 0) return null;
|
|
5490
|
-
const password = stripTrailingNewline(result.stdout);
|
|
5893
|
+
const password = stripTrailingNewline$1(result.stdout);
|
|
5491
5894
|
return password.length > 0 ? password : null;
|
|
5492
5895
|
},
|
|
5493
5896
|
async set(account, password) {
|
|
@@ -5551,7 +5954,7 @@ const MACOS_BACKEND = {
|
|
|
5551
5954
|
}
|
|
5552
5955
|
if (result.exitCode === 44) return null;
|
|
5553
5956
|
if (result.exitCode !== 0) return null;
|
|
5554
|
-
const password = stripTrailingNewline(result.stdout);
|
|
5957
|
+
const password = stripTrailingNewline$1(result.stdout);
|
|
5555
5958
|
return password.length > 0 ? password : null;
|
|
5556
5959
|
},
|
|
5557
5960
|
async set(account, password) {
|
|
@@ -6086,13 +6489,17 @@ const authImportCommand = defineCommand({
|
|
|
6086
6489
|
});
|
|
6087
6490
|
//#endregion
|
|
6088
6491
|
//#region src/commands/auth.ts
|
|
6492
|
+
function emitDeprecation(replacement) {
|
|
6493
|
+
process.stderr.write(`warning: this command is deprecated and will be removed in 1.0; ${replacement}\n`);
|
|
6494
|
+
}
|
|
6089
6495
|
async function runAuthSet(args, deps = {}) {
|
|
6496
|
+
emitDeprecation("use `aitcc login` (interactive prompt offers a save option).");
|
|
6090
6497
|
const env = deps.env ?? process.env;
|
|
6091
6498
|
let email = args.email?.trim();
|
|
6092
|
-
let password$
|
|
6093
|
-
const argvPasswordUsed = password$
|
|
6499
|
+
let password$1 = args.password;
|
|
6500
|
+
const argvPasswordUsed = password$1 !== void 0;
|
|
6094
6501
|
if (!email && env.AITCC_EMAIL) email = env.AITCC_EMAIL;
|
|
6095
|
-
if (password$
|
|
6502
|
+
if (password$1 === void 0 && env.AITCC_PASSWORD) password$1 = env.AITCC_PASSWORD;
|
|
6096
6503
|
if (argvPasswordUsed) process.stderr.write("Warning: --password on argv is visible in `ps`/Task Manager. Prefer the AITCC_PASSWORD environment variable for scripted use.\n");
|
|
6097
6504
|
const interactive = process.stdout.isTTY && process.stdin.isTTY && !args.json;
|
|
6098
6505
|
if (!email) {
|
|
@@ -6106,7 +6513,7 @@ async function runAuthSet(args, deps = {}) {
|
|
|
6106
6513
|
validate: (raw) => raw.trim().length > 0 ? true : "email is required"
|
|
6107
6514
|
})).trim();
|
|
6108
6515
|
} catch (err) {
|
|
6109
|
-
if (isPromptCancelled(err)) {
|
|
6516
|
+
if (isPromptCancelled$1(err)) {
|
|
6110
6517
|
process.stderr.write("Aborted.\n");
|
|
6111
6518
|
return exitAfterFlush(ExitCode.Usage);
|
|
6112
6519
|
}
|
|
@@ -6122,26 +6529,26 @@ async function runAuthSet(args, deps = {}) {
|
|
|
6122
6529
|
else process.stderr.write(`Invalid email: ${email}\n`);
|
|
6123
6530
|
return exitAfterFlush(ExitCode.Usage);
|
|
6124
6531
|
}
|
|
6125
|
-
if (password$
|
|
6532
|
+
if (password$1 === void 0) {
|
|
6126
6533
|
if (!interactive) {
|
|
6127
6534
|
emitInteractiveRequired(args.json, "password");
|
|
6128
6535
|
return exitAfterFlush(ExitCode.Usage);
|
|
6129
6536
|
}
|
|
6130
6537
|
try {
|
|
6131
|
-
password$
|
|
6538
|
+
password$1 = await password({
|
|
6132
6539
|
message: "Password:",
|
|
6133
6540
|
mask: true,
|
|
6134
6541
|
validate: (raw) => raw.length > 0 ? true : "password is required"
|
|
6135
6542
|
});
|
|
6136
6543
|
} catch (err) {
|
|
6137
|
-
if (isPromptCancelled(err)) {
|
|
6544
|
+
if (isPromptCancelled$1(err)) {
|
|
6138
6545
|
process.stderr.write("Aborted.\n");
|
|
6139
6546
|
return exitAfterFlush(ExitCode.Usage);
|
|
6140
6547
|
}
|
|
6141
6548
|
throw err;
|
|
6142
6549
|
}
|
|
6143
6550
|
}
|
|
6144
|
-
if (password$
|
|
6551
|
+
if (password$1.length === 0) {
|
|
6145
6552
|
if (args.json) emitJson({
|
|
6146
6553
|
ok: false,
|
|
6147
6554
|
reason: "invalid-password",
|
|
@@ -6152,7 +6559,7 @@ async function runAuthSet(args, deps = {}) {
|
|
|
6152
6559
|
}
|
|
6153
6560
|
let result;
|
|
6154
6561
|
try {
|
|
6155
|
-
result = await saveCredentials(email, password$
|
|
6562
|
+
result = await saveCredentials(email, password$1, deps.backend ? { override: deps.backend } : {});
|
|
6156
6563
|
} catch (err) {
|
|
6157
6564
|
const message = err.message;
|
|
6158
6565
|
if (args.json) emitJson({
|
|
@@ -6173,6 +6580,7 @@ async function runAuthSet(args, deps = {}) {
|
|
|
6173
6580
|
return exitAfterFlush(ExitCode.Ok);
|
|
6174
6581
|
}
|
|
6175
6582
|
async function runAuthClear(args, deps = {}) {
|
|
6583
|
+
emitDeprecation("use `aitcc logout --purge` to remove session and saved credentials together.");
|
|
6176
6584
|
const interactive = process.stdout.isTTY && process.stdin.isTTY && !args.json;
|
|
6177
6585
|
const active = await getActiveCredentialEmail(deps.env ? { env: deps.env } : {}).catch(() => null);
|
|
6178
6586
|
if (!args.yes) {
|
|
@@ -6193,7 +6601,7 @@ async function runAuthClear(args, deps = {}) {
|
|
|
6193
6601
|
default: false
|
|
6194
6602
|
});
|
|
6195
6603
|
} catch (err) {
|
|
6196
|
-
if (isPromptCancelled(err)) {
|
|
6604
|
+
if (isPromptCancelled$1(err)) {
|
|
6197
6605
|
process.stderr.write("Aborted.\n");
|
|
6198
6606
|
return exitAfterFlush(ExitCode.Usage);
|
|
6199
6607
|
}
|
|
@@ -6231,6 +6639,7 @@ async function runAuthClear(args, deps = {}) {
|
|
|
6231
6639
|
return exitAfterFlush(ExitCode.Ok);
|
|
6232
6640
|
}
|
|
6233
6641
|
async function runAuthStatus(args, deps = {}) {
|
|
6642
|
+
emitDeprecation("use `aitcc whoami` (now reports credential source).");
|
|
6234
6643
|
const active = await getActiveCredentialEmail(deps.env ? { env: deps.env } : {}).catch(() => null);
|
|
6235
6644
|
const session = await readSession();
|
|
6236
6645
|
if (args.json) {
|
|
@@ -6269,7 +6678,7 @@ function emitInteractiveRequired(json, missing) {
|
|
|
6269
6678
|
});
|
|
6270
6679
|
else process.stderr.write(`Cannot prompt for ${missing} in non-interactive mode. Use --${missing} or set AITCC_${missing.toUpperCase()}.\n`);
|
|
6271
6680
|
}
|
|
6272
|
-
function isPromptCancelled(err) {
|
|
6681
|
+
function isPromptCancelled$1(err) {
|
|
6273
6682
|
return err instanceof Error && err.name === "ExitPromptError";
|
|
6274
6683
|
}
|
|
6275
6684
|
const authCommand = defineCommand({
|
|
@@ -6281,7 +6690,7 @@ const authCommand = defineCommand({
|
|
|
6281
6690
|
set: defineCommand({
|
|
6282
6691
|
meta: {
|
|
6283
6692
|
name: "set",
|
|
6284
|
-
description: "
|
|
6693
|
+
description: "[deprecated] Use `aitcc login` instead — the prompt now offers a save option."
|
|
6285
6694
|
},
|
|
6286
6695
|
args: {
|
|
6287
6696
|
json: {
|
|
@@ -6309,7 +6718,7 @@ const authCommand = defineCommand({
|
|
|
6309
6718
|
clear: defineCommand({
|
|
6310
6719
|
meta: {
|
|
6311
6720
|
name: "clear",
|
|
6312
|
-
description: "
|
|
6721
|
+
description: "[deprecated] Use `aitcc logout --purge` instead."
|
|
6313
6722
|
},
|
|
6314
6723
|
args: {
|
|
6315
6724
|
json: {
|
|
@@ -6334,7 +6743,7 @@ const authCommand = defineCommand({
|
|
|
6334
6743
|
status: defineCommand({
|
|
6335
6744
|
meta: {
|
|
6336
6745
|
name: "status",
|
|
6337
|
-
description: "
|
|
6746
|
+
description: "[deprecated] Use `aitcc whoami` (now reports credential source)."
|
|
6338
6747
|
},
|
|
6339
6748
|
args: { json: {
|
|
6340
6749
|
type: "boolean",
|
|
@@ -7494,10 +7903,38 @@ function chooseLoginMode(input) {
|
|
|
7494
7903
|
if (input.interactiveFlag) return "interactive";
|
|
7495
7904
|
return input.hasCredentials ? "headless" : "interactive";
|
|
7496
7905
|
}
|
|
7906
|
+
const defaultPromptDeps = {
|
|
7907
|
+
email: (defaultValue) => input({
|
|
7908
|
+
message: "Email:",
|
|
7909
|
+
...defaultValue !== void 0 ? { default: defaultValue } : {},
|
|
7910
|
+
validate: (raw) => {
|
|
7911
|
+
const trimmed = raw.trim();
|
|
7912
|
+
if (trimmed.length === 0) return "email is required";
|
|
7913
|
+
if (!trimmed.includes("@")) return "must contain \"@\"";
|
|
7914
|
+
return true;
|
|
7915
|
+
}
|
|
7916
|
+
}).then((s) => s.trim()),
|
|
7917
|
+
password: () => password({
|
|
7918
|
+
message: "Password:",
|
|
7919
|
+
mask: true,
|
|
7920
|
+
validate: (raw) => raw.length > 0 ? true : "password is required"
|
|
7921
|
+
}),
|
|
7922
|
+
saveTarget: () => select({
|
|
7923
|
+
message: "Where would you like to save the credentials?",
|
|
7924
|
+
default: "keychain",
|
|
7925
|
+
choices: [{
|
|
7926
|
+
name: "OS keychain (recommended) — next login runs headlessly",
|
|
7927
|
+
value: "keychain"
|
|
7928
|
+
}, {
|
|
7929
|
+
name: "Do not save — one-shot. (Tip: AITCC_EMAIL/AITCC_PASSWORD env for CI.)",
|
|
7930
|
+
value: "none"
|
|
7931
|
+
}]
|
|
7932
|
+
})
|
|
7933
|
+
};
|
|
7497
7934
|
const loginCommand = defineCommand({
|
|
7498
7935
|
meta: {
|
|
7499
7936
|
name: "login",
|
|
7500
|
-
description: "
|
|
7937
|
+
description: "Sign in to the Apps in Toss console and capture the session cookies."
|
|
7501
7938
|
},
|
|
7502
7939
|
args: {
|
|
7503
7940
|
json: {
|
|
@@ -7515,9 +7952,26 @@ const loginCommand = defineCommand({
|
|
|
7515
7952
|
description: "Force the visible-browser flow even if credentials are configured.",
|
|
7516
7953
|
default: false
|
|
7517
7954
|
},
|
|
7955
|
+
email: {
|
|
7956
|
+
type: "string",
|
|
7957
|
+
description: "Email (skip prompt; required for non-interactive use)."
|
|
7958
|
+
},
|
|
7959
|
+
password: {
|
|
7960
|
+
type: "string",
|
|
7961
|
+
description: "Password (skip prompt; visible in `ps`/Task Manager — prefer --password-stdin or AITCC_PASSWORD env)."
|
|
7962
|
+
},
|
|
7963
|
+
"password-stdin": {
|
|
7964
|
+
type: "boolean",
|
|
7965
|
+
description: "Read the password from stdin (recommended for non-interactive use).",
|
|
7966
|
+
default: false
|
|
7967
|
+
},
|
|
7968
|
+
save: {
|
|
7969
|
+
type: "string",
|
|
7970
|
+
description: "Where to persist credentials when --email/--password* are passed: \"keychain\" or \"none\" (default)."
|
|
7971
|
+
},
|
|
7518
7972
|
"skip-onboarding": {
|
|
7519
7973
|
type: "boolean",
|
|
7520
|
-
description: "
|
|
7974
|
+
description: "Deprecated no-op; kept so existing scripts do not break.",
|
|
7521
7975
|
default: false
|
|
7522
7976
|
}
|
|
7523
7977
|
},
|
|
@@ -7526,7 +7980,10 @@ const loginCommand = defineCommand({
|
|
|
7526
7980
|
json: args.json,
|
|
7527
7981
|
timeout: args.timeout,
|
|
7528
7982
|
interactive: args.interactive,
|
|
7529
|
-
|
|
7983
|
+
email: typeof args.email === "string" ? args.email : void 0,
|
|
7984
|
+
password: typeof args.password === "string" ? args.password : void 0,
|
|
7985
|
+
passwordStdin: args["password-stdin"],
|
|
7986
|
+
save: typeof args.save === "string" ? args.save : void 0
|
|
7530
7987
|
}, {
|
|
7531
7988
|
getCredentials: loadCredentials,
|
|
7532
7989
|
saveCredentials
|
|
@@ -7570,17 +8027,41 @@ async function runLoginCommand(args, deps) {
|
|
|
7570
8027
|
}
|
|
7571
8028
|
process.stderr.write(`Using custom authorize URL from AITCC_OAUTH_URL: ${authorizeUrl}\n`);
|
|
7572
8029
|
}
|
|
7573
|
-
|
|
7574
|
-
if (
|
|
7575
|
-
|
|
7576
|
-
|
|
7577
|
-
|
|
7578
|
-
|
|
7579
|
-
|
|
8030
|
+
const resolved = await resolveCredentialsForLogin(args, deps);
|
|
8031
|
+
if (resolved.kind === "error") {
|
|
8032
|
+
emitError({
|
|
8033
|
+
reason: resolved.reason,
|
|
8034
|
+
message: resolved.message
|
|
8035
|
+
}, resolved.message);
|
|
8036
|
+
return exitAfterFlush(resolved.exitCode);
|
|
8037
|
+
}
|
|
8038
|
+
let saved = "skipped";
|
|
8039
|
+
if (resolved.saveTarget === "keychain" && resolved.credentials !== null) {
|
|
8040
|
+
const save = deps.saveCredentials;
|
|
8041
|
+
if (!save) {
|
|
8042
|
+
emitError({
|
|
8043
|
+
reason: "save-unavailable",
|
|
8044
|
+
message: "no save backend configured"
|
|
8045
|
+
}, "Cannot save credentials: no backend configured.");
|
|
8046
|
+
return exitAfterFlush(ExitCode.Generic);
|
|
8047
|
+
}
|
|
8048
|
+
try {
|
|
8049
|
+
const result = await save(resolved.credentials.email, resolved.credentials.password);
|
|
8050
|
+
saved = result.status;
|
|
8051
|
+
if (!args.json) if (result.status === "unchanged") process.stderr.write("Credentials already saved (no change).\n");
|
|
8052
|
+
else process.stderr.write(`Credentials saved to OS keychain (${resolved.credentials.email}).\n`);
|
|
8053
|
+
} catch (err) {
|
|
8054
|
+
const message = err.message;
|
|
8055
|
+
emitError({
|
|
8056
|
+
reason: "keychain-save-failed",
|
|
8057
|
+
message
|
|
8058
|
+
}, `Failed to save credentials to the OS keychain: ${message}\nOn Linux, install libsecret (\`secret-tool\`) and retry. Re-run with \`--save none\` to skip persistence.`);
|
|
8059
|
+
return exitAfterFlush(ExitCode.Usage);
|
|
8060
|
+
}
|
|
7580
8061
|
}
|
|
7581
8062
|
const initialMode = chooseLoginMode({
|
|
7582
8063
|
interactiveFlag: args.interactive,
|
|
7583
|
-
hasCredentials: credentials !== null
|
|
8064
|
+
hasCredentials: resolved.credentials !== null
|
|
7584
8065
|
});
|
|
7585
8066
|
const endpointTimeoutMs = Math.min(6e4, Math.max(3e4, Math.floor(timeoutMs / 2)));
|
|
7586
8067
|
const firstAttemptStart = Date.now();
|
|
@@ -7590,9 +8071,9 @@ async function runLoginCommand(args, deps) {
|
|
|
7590
8071
|
endpointTimeoutMs,
|
|
7591
8072
|
authorizeUrl,
|
|
7592
8073
|
mode: initialMode,
|
|
7593
|
-
credentials,
|
|
7594
|
-
|
|
7595
|
-
|
|
8074
|
+
credentials: resolved.credentials,
|
|
8075
|
+
saved,
|
|
8076
|
+
emitError
|
|
7596
8077
|
});
|
|
7597
8078
|
if (result.status === "fallback-to-interactive") {
|
|
7598
8079
|
process.stderr.write(`${result.message}\n`);
|
|
@@ -7603,8 +8084,8 @@ async function runLoginCommand(args, deps) {
|
|
|
7603
8084
|
authorizeUrl,
|
|
7604
8085
|
mode: "interactive",
|
|
7605
8086
|
credentials: null,
|
|
7606
|
-
|
|
7607
|
-
|
|
8087
|
+
saved,
|
|
8088
|
+
emitError
|
|
7608
8089
|
});
|
|
7609
8090
|
if (second.status === "exit") return exitAfterFlush(second.code);
|
|
7610
8091
|
second.status;
|
|
@@ -7612,8 +8093,167 @@ async function runLoginCommand(args, deps) {
|
|
|
7612
8093
|
}
|
|
7613
8094
|
return exitAfterFlush(result.code);
|
|
7614
8095
|
}
|
|
8096
|
+
/**
|
|
8097
|
+
* Resolve credentials and the requested save target for `aitcc login`.
|
|
8098
|
+
* Pure-ish: only side-effect is reading stdin via `deps.readStdin` (when
|
|
8099
|
+
* `--password-stdin` is set) and prompting via `deps.prompts` (when TTY).
|
|
8100
|
+
*/
|
|
8101
|
+
async function resolveCredentialsForLogin(args, deps, opts = {}) {
|
|
8102
|
+
const env = opts.env ?? process.env;
|
|
8103
|
+
const stdoutIsTTY = opts.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
|
|
8104
|
+
const stdinIsTTY = opts.stdinIsTTY ?? Boolean(process.stdin.isTTY);
|
|
8105
|
+
const interactiveTty = stdoutIsTTY && stdinIsTTY && !args.json;
|
|
8106
|
+
if (args.password !== void 0 && args.passwordStdin) return {
|
|
8107
|
+
kind: "error",
|
|
8108
|
+
reason: "conflicting-password-source",
|
|
8109
|
+
message: "--password and --password-stdin cannot be used together.",
|
|
8110
|
+
exitCode: ExitCode.Usage
|
|
8111
|
+
};
|
|
8112
|
+
if (args.interactive && (args.email !== void 0 || args.password !== void 0 || args.passwordStdin || args.save !== void 0)) return {
|
|
8113
|
+
kind: "error",
|
|
8114
|
+
reason: "conflicting-interactive-flags",
|
|
8115
|
+
message: "--interactive cannot be combined with --email/--password/--password-stdin/--save. Drop --interactive to use credentials, or drop the credential flags to type in the browser.",
|
|
8116
|
+
exitCode: ExitCode.Usage
|
|
8117
|
+
};
|
|
8118
|
+
let saveTarget;
|
|
8119
|
+
if (args.save !== void 0) {
|
|
8120
|
+
if (args.save !== "keychain" && args.save !== "none") return {
|
|
8121
|
+
kind: "error",
|
|
8122
|
+
reason: "invalid-save",
|
|
8123
|
+
message: `--save must be "keychain" or "none" (got "${args.save}").`,
|
|
8124
|
+
exitCode: ExitCode.Usage
|
|
8125
|
+
};
|
|
8126
|
+
saveTarget = args.save;
|
|
8127
|
+
}
|
|
8128
|
+
if (args.email !== void 0 || args.password !== void 0 || args.passwordStdin) {
|
|
8129
|
+
if (args.email === void 0 || args.email.trim().length === 0) return {
|
|
8130
|
+
kind: "error",
|
|
8131
|
+
reason: "missing-email",
|
|
8132
|
+
message: "--email is required when --password / --password-stdin is passed.",
|
|
8133
|
+
exitCode: ExitCode.Usage
|
|
8134
|
+
};
|
|
8135
|
+
if (!args.email.includes("@")) return {
|
|
8136
|
+
kind: "error",
|
|
8137
|
+
reason: "invalid-email",
|
|
8138
|
+
message: `Invalid email: ${args.email}`,
|
|
8139
|
+
exitCode: ExitCode.Usage
|
|
8140
|
+
};
|
|
8141
|
+
let password;
|
|
8142
|
+
if (args.passwordStdin) {
|
|
8143
|
+
password = stripTrailingNewline(await (deps.readStdin ?? readStdinAll)());
|
|
8144
|
+
if (password.length === 0) return {
|
|
8145
|
+
kind: "error",
|
|
8146
|
+
reason: "invalid-password",
|
|
8147
|
+
message: "--password-stdin received an empty password on stdin.",
|
|
8148
|
+
exitCode: ExitCode.Usage
|
|
8149
|
+
};
|
|
8150
|
+
} else if (args.password !== void 0) {
|
|
8151
|
+
process.stderr.write("Warning: --password on argv is visible in `ps`/Task Manager. Prefer --password-stdin or the AITCC_PASSWORD environment variable.\n");
|
|
8152
|
+
password = args.password;
|
|
8153
|
+
if (password.length === 0) return {
|
|
8154
|
+
kind: "error",
|
|
8155
|
+
reason: "invalid-password",
|
|
8156
|
+
message: "--password value is empty.",
|
|
8157
|
+
exitCode: ExitCode.Usage
|
|
8158
|
+
};
|
|
8159
|
+
} else return {
|
|
8160
|
+
kind: "error",
|
|
8161
|
+
reason: "missing-password",
|
|
8162
|
+
message: "--email passed without a password. Add --password-stdin (recommended) or --password.",
|
|
8163
|
+
exitCode: ExitCode.Usage
|
|
8164
|
+
};
|
|
8165
|
+
return {
|
|
8166
|
+
kind: "ok",
|
|
8167
|
+
credentials: {
|
|
8168
|
+
source: "argv",
|
|
8169
|
+
email: args.email.trim(),
|
|
8170
|
+
password
|
|
8171
|
+
},
|
|
8172
|
+
saveTarget: saveTarget ?? "none"
|
|
8173
|
+
};
|
|
8174
|
+
}
|
|
8175
|
+
if (args.interactive) return {
|
|
8176
|
+
kind: "ok",
|
|
8177
|
+
credentials: null,
|
|
8178
|
+
saveTarget: saveTarget ?? "none"
|
|
8179
|
+
};
|
|
8180
|
+
if (env.AITCC_EMAIL && env.AITCC_PASSWORD) return {
|
|
8181
|
+
kind: "ok",
|
|
8182
|
+
credentials: {
|
|
8183
|
+
source: "env",
|
|
8184
|
+
email: env.AITCC_EMAIL,
|
|
8185
|
+
password: env.AITCC_PASSWORD
|
|
8186
|
+
},
|
|
8187
|
+
saveTarget: saveTarget ?? "none"
|
|
8188
|
+
};
|
|
8189
|
+
const getCredentials = deps.getCredentials;
|
|
8190
|
+
if (getCredentials) {
|
|
8191
|
+
const fromStore = await getCredentials().catch((err) => {
|
|
8192
|
+
process.stderr.write(`Credential lookup failed (${err.message}); ignoring.\n`);
|
|
8193
|
+
return null;
|
|
8194
|
+
});
|
|
8195
|
+
if (fromStore) {
|
|
8196
|
+
if (!args.json) process.stderr.write(`Using credentials from OS keychain for ${fromStore.email}. Pass --interactive to type a different account.
|
|
8197
|
+
`);
|
|
8198
|
+
return {
|
|
8199
|
+
kind: "ok",
|
|
8200
|
+
credentials: {
|
|
8201
|
+
source: fromStore.kind,
|
|
8202
|
+
email: fromStore.email,
|
|
8203
|
+
password: fromStore.password
|
|
8204
|
+
},
|
|
8205
|
+
saveTarget: saveTarget ?? "none"
|
|
8206
|
+
};
|
|
8207
|
+
}
|
|
8208
|
+
}
|
|
8209
|
+
if (interactiveTty) {
|
|
8210
|
+
const prompts = deps.prompts ?? defaultPromptDeps;
|
|
8211
|
+
let email;
|
|
8212
|
+
let password;
|
|
8213
|
+
try {
|
|
8214
|
+
email = await prompts.email();
|
|
8215
|
+
password = await prompts.password();
|
|
8216
|
+
} catch (err) {
|
|
8217
|
+
if (isPromptCancelled(err)) return {
|
|
8218
|
+
kind: "error",
|
|
8219
|
+
reason: "aborted",
|
|
8220
|
+
message: "Aborted.",
|
|
8221
|
+
exitCode: ExitCode.Usage
|
|
8222
|
+
};
|
|
8223
|
+
throw err;
|
|
8224
|
+
}
|
|
8225
|
+
let promptedSave;
|
|
8226
|
+
if (saveTarget !== void 0) promptedSave = saveTarget;
|
|
8227
|
+
else try {
|
|
8228
|
+
promptedSave = await prompts.saveTarget();
|
|
8229
|
+
} catch (err) {
|
|
8230
|
+
if (isPromptCancelled(err)) return {
|
|
8231
|
+
kind: "error",
|
|
8232
|
+
reason: "aborted",
|
|
8233
|
+
message: "Aborted.",
|
|
8234
|
+
exitCode: ExitCode.Usage
|
|
8235
|
+
};
|
|
8236
|
+
throw err;
|
|
8237
|
+
}
|
|
8238
|
+
return {
|
|
8239
|
+
kind: "ok",
|
|
8240
|
+
credentials: {
|
|
8241
|
+
source: "prompt",
|
|
8242
|
+
email,
|
|
8243
|
+
password
|
|
8244
|
+
},
|
|
8245
|
+
saveTarget: promptedSave
|
|
8246
|
+
};
|
|
8247
|
+
}
|
|
8248
|
+
return {
|
|
8249
|
+
kind: "error",
|
|
8250
|
+
reason: "interactive-required",
|
|
8251
|
+
message: "No credentials configured and stdin is not a TTY. Pass --email + --password-stdin (or set AITCC_EMAIL + AITCC_PASSWORD).",
|
|
8252
|
+
exitCode: ExitCode.Usage
|
|
8253
|
+
};
|
|
8254
|
+
}
|
|
7615
8255
|
async function attemptLogin(opts) {
|
|
7616
|
-
const { args, timeoutMs, endpointTimeoutMs, authorizeUrl, mode, credentials,
|
|
8256
|
+
const { args, timeoutMs, endpointTimeoutMs, authorizeUrl, mode, credentials, saved, emitError } = opts;
|
|
7617
8257
|
const launched = await launchChrome({
|
|
7618
8258
|
initialUrl: authorizeUrl,
|
|
7619
8259
|
endpointTimeoutMs,
|
|
@@ -7652,8 +8292,8 @@ async function attemptLogin(opts) {
|
|
|
7652
8292
|
}
|
|
7653
8293
|
if (mode === "interactive") process.stderr.write("Opened a browser window — complete the sign-in there. The CLI will capture the session automatically.\n");
|
|
7654
8294
|
else {
|
|
7655
|
-
const
|
|
7656
|
-
process.stderr.write(`Signing in headlessly with credentials from ${
|
|
8295
|
+
const sourceLabel = credentials?.source ?? "configured store";
|
|
8296
|
+
process.stderr.write(`Signing in headlessly with credentials from ${sourceLabel}…\n`);
|
|
7657
8297
|
}
|
|
7658
8298
|
let client = null;
|
|
7659
8299
|
const disposeAll = async () => {
|
|
@@ -7820,57 +8460,29 @@ async function attemptLogin(opts) {
|
|
|
7820
8460
|
capturedAt: session.capturedAt,
|
|
7821
8461
|
cookieCount: cookies.length,
|
|
7822
8462
|
mode,
|
|
8463
|
+
credentialSource: credentials?.source ?? "browser",
|
|
8464
|
+
saved,
|
|
7823
8465
|
stepUp
|
|
7824
8466
|
})}\n`);
|
|
7825
8467
|
else process.stdout.write(`Logged in as ${user.name} <${user.email}>\n`);
|
|
7826
8468
|
await disposeAll();
|
|
7827
|
-
if (mode === "interactive" && credentials === null && !args.json && !args.skipOnboarding && process.stdout.isTTY && process.stdin.isTTY && deps.saveCredentials) await runOnboardingPrompt(user.email, deps.saveCredentials);
|
|
7828
8469
|
return {
|
|
7829
8470
|
status: "exit",
|
|
7830
8471
|
code: ExitCode.Ok
|
|
7831
8472
|
};
|
|
7832
8473
|
}
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
agreed = await confirm({
|
|
7846
|
-
message: "Save credentials?",
|
|
7847
|
-
default: true
|
|
7848
|
-
});
|
|
7849
|
-
} catch (err) {
|
|
7850
|
-
if (err instanceof Error && err.name === "ExitPromptError") return;
|
|
7851
|
-
process.stderr.write(`Onboarding prompt failed: ${err.message}\n`);
|
|
7852
|
-
return;
|
|
7853
|
-
}
|
|
7854
|
-
if (!agreed) return;
|
|
7855
|
-
let password$1;
|
|
7856
|
-
try {
|
|
7857
|
-
password$1 = await password({
|
|
7858
|
-
message: "Password:",
|
|
7859
|
-
mask: true,
|
|
7860
|
-
validate: (raw) => raw.length > 0 ? true : "password is required"
|
|
7861
|
-
});
|
|
7862
|
-
} catch (err) {
|
|
7863
|
-
if (err instanceof Error && err.name === "ExitPromptError") return;
|
|
7864
|
-
process.stderr.write(`Could not read password: ${err.message}\n`);
|
|
7865
|
-
return;
|
|
7866
|
-
}
|
|
7867
|
-
try {
|
|
7868
|
-
await save(email, password$1);
|
|
7869
|
-
process.stdout.write("Saved. Next `aitcc login` will run headlessly.\n");
|
|
7870
|
-
} catch (err) {
|
|
7871
|
-
process.stderr.write(`Could not save credentials: ${err.message}. You can retry later with \`aitcc auth set\`.
|
|
7872
|
-
`);
|
|
7873
|
-
}
|
|
8474
|
+
async function readStdinAll() {
|
|
8475
|
+
if (process.stdin.isTTY) throw new Error("--password-stdin requires stdin to be a pipe, not a TTY.");
|
|
8476
|
+
process.stdin.setEncoding("utf8");
|
|
8477
|
+
let buf = "";
|
|
8478
|
+
for await (const chunk of process.stdin) buf += chunk;
|
|
8479
|
+
return buf;
|
|
8480
|
+
}
|
|
8481
|
+
function stripTrailingNewline(s) {
|
|
8482
|
+
return s.replace(/\r?\n$/, "");
|
|
8483
|
+
}
|
|
8484
|
+
function isPromptCancelled(err) {
|
|
8485
|
+
return err instanceof Error && err.name === "ExitPromptError";
|
|
7874
8486
|
}
|
|
7875
8487
|
async function waitForLanding(client, sessionId, timeoutMs) {
|
|
7876
8488
|
return await new Promise((resolve) => {
|
|
@@ -7934,18 +8546,25 @@ async function resolveUserWithRetry(cookies, opts = {}) {
|
|
|
7934
8546
|
const logoutCommand = defineCommand({
|
|
7935
8547
|
meta: {
|
|
7936
8548
|
name: "logout",
|
|
7937
|
-
description: "Delete the local session file."
|
|
8549
|
+
description: "Delete the local session file (and optionally the saved credentials)."
|
|
8550
|
+
},
|
|
8551
|
+
args: {
|
|
8552
|
+
json: {
|
|
8553
|
+
type: "boolean",
|
|
8554
|
+
description: "Emit machine-readable JSON to stdout.",
|
|
8555
|
+
default: false
|
|
8556
|
+
},
|
|
8557
|
+
purge: {
|
|
8558
|
+
type: "boolean",
|
|
8559
|
+
description: "Also delete saved keychain credentials and the auth-state pointer.",
|
|
8560
|
+
default: false
|
|
8561
|
+
}
|
|
7938
8562
|
},
|
|
7939
|
-
args: { json: {
|
|
7940
|
-
type: "boolean",
|
|
7941
|
-
description: "Emit machine-readable JSON to stdout.",
|
|
7942
|
-
default: false
|
|
7943
|
-
} },
|
|
7944
8563
|
async run({ args }) {
|
|
7945
8564
|
const path = sessionPathForDiagnostics();
|
|
7946
|
-
let
|
|
8565
|
+
let sessionRemoved;
|
|
7947
8566
|
try {
|
|
7948
|
-
|
|
8567
|
+
sessionRemoved = (await clearSession()).existed;
|
|
7949
8568
|
} catch (err) {
|
|
7950
8569
|
const message = err.message;
|
|
7951
8570
|
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
@@ -7957,13 +8576,29 @@ const logoutCommand = defineCommand({
|
|
|
7957
8576
|
process.stderr.write(`Failed to remove session file at ${path}: ${message}\n`);
|
|
7958
8577
|
return exitAfterFlush(ExitCode.Generic);
|
|
7959
8578
|
}
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
})
|
|
7965
|
-
|
|
7966
|
-
|
|
8579
|
+
let credentialsPurged = false;
|
|
8580
|
+
let purgeError = null;
|
|
8581
|
+
if (args.purge) try {
|
|
8582
|
+
credentialsPurged = (await deleteCredentials()).existed;
|
|
8583
|
+
} catch (err) {
|
|
8584
|
+
purgeError = err.message;
|
|
8585
|
+
}
|
|
8586
|
+
if (args.json) {
|
|
8587
|
+
const payload = {
|
|
8588
|
+
ok: true,
|
|
8589
|
+
sessionRemoved,
|
|
8590
|
+
credentialsPurged,
|
|
8591
|
+
path
|
|
8592
|
+
};
|
|
8593
|
+
if (purgeError !== null) payload.purgeError = purgeError;
|
|
8594
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
8595
|
+
} else {
|
|
8596
|
+
if (sessionRemoved) process.stdout.write(`Logged out. Session removed from ${path}\n`);
|
|
8597
|
+
else process.stdout.write(`No active session at ${path}.\n`);
|
|
8598
|
+
if (args.purge) if (purgeError !== null) process.stderr.write(`Could not delete saved credentials: ${purgeError}\n`);
|
|
8599
|
+
else if (credentialsPurged) process.stdout.write("Saved credentials deleted from the OS keychain.\n");
|
|
8600
|
+
else process.stdout.write("No saved credentials to delete.\n");
|
|
8601
|
+
}
|
|
7967
8602
|
return exitAfterFlush(ExitCode.Ok);
|
|
7968
8603
|
}
|
|
7969
8604
|
});
|
|
@@ -8498,7 +9133,7 @@ function resolveVersion() {
|
|
|
8498
9133
|
if (typeof injected === "string" && injected.length > 0) return injected;
|
|
8499
9134
|
} catch {}
|
|
8500
9135
|
try {
|
|
8501
|
-
return "0.1.
|
|
9136
|
+
return "0.1.25";
|
|
8502
9137
|
} catch {}
|
|
8503
9138
|
return "0.0.0-dev";
|
|
8504
9139
|
}
|
|
@@ -8870,6 +9505,22 @@ function maybeEmitNotice(entry, env) {
|
|
|
8870
9505
|
}
|
|
8871
9506
|
//#endregion
|
|
8872
9507
|
//#region src/commands/whoami.ts
|
|
9508
|
+
async function describeCredentialSource() {
|
|
9509
|
+
const active = await getActiveCredentialEmail().catch(() => null);
|
|
9510
|
+
if (!active) return {
|
|
9511
|
+
source: "none",
|
|
9512
|
+
email: null
|
|
9513
|
+
};
|
|
9514
|
+
return {
|
|
9515
|
+
source: active.kind,
|
|
9516
|
+
email: active.email
|
|
9517
|
+
};
|
|
9518
|
+
}
|
|
9519
|
+
function formatCredentials(cred) {
|
|
9520
|
+
if (cred.source === "none") return "none (run `aitcc login` to save)";
|
|
9521
|
+
if (cred.source === "env") return `env (AITCC_EMAIL${cred.email ? ` = ${cred.email}` : ""})`;
|
|
9522
|
+
return `keychain${cred.email ? ` (${cred.email})` : ""}`;
|
|
9523
|
+
}
|
|
8873
9524
|
async function runBackgroundUpdateCheck(json) {
|
|
8874
9525
|
if (json) return;
|
|
8875
9526
|
const timeoutMs = 500;
|
|
@@ -8897,14 +9548,18 @@ const whoamiCommand = defineCommand({
|
|
|
8897
9548
|
},
|
|
8898
9549
|
async run({ args }) {
|
|
8899
9550
|
const session = await readSession();
|
|
9551
|
+
const cred = await describeCredentialSource();
|
|
8900
9552
|
if (!session) {
|
|
8901
9553
|
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
8902
9554
|
ok: true,
|
|
8903
|
-
authenticated: false
|
|
9555
|
+
authenticated: false,
|
|
9556
|
+
credentialSource: cred.source,
|
|
9557
|
+
...cred.email ? { credentialEmail: cred.email } : {}
|
|
8904
9558
|
})}\n`);
|
|
8905
9559
|
else {
|
|
8906
9560
|
process.stderr.write("Not logged in. Run `aitcc login` to start a session.\n");
|
|
8907
9561
|
process.stderr.write(`Session file checked: ${sessionPathForDiagnostics()}\n`);
|
|
9562
|
+
process.stderr.write(`Credentials: ${formatCredentials(cred)}\n`);
|
|
8908
9563
|
}
|
|
8909
9564
|
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
8910
9565
|
}
|
|
@@ -8915,12 +9570,15 @@ const whoamiCommand = defineCommand({
|
|
|
8915
9570
|
authenticated: true,
|
|
8916
9571
|
source: "cache",
|
|
8917
9572
|
user: session.user,
|
|
8918
|
-
capturedAt: session.capturedAt
|
|
9573
|
+
capturedAt: session.capturedAt,
|
|
9574
|
+
credentialSource: cred.source,
|
|
9575
|
+
...cred.email ? { credentialEmail: cred.email } : {}
|
|
8919
9576
|
})}\n`);
|
|
8920
9577
|
return exitAfterFlush(ExitCode.Ok);
|
|
8921
9578
|
}
|
|
8922
9579
|
const label = session.user.displayName ? `${session.user.displayName} <${session.user.email}>` : session.user.email;
|
|
8923
9580
|
process.stdout.write(`Logged in as ${label} (cached)\n`);
|
|
9581
|
+
process.stdout.write(`Credentials: ${formatCredentials(cred)}\n`);
|
|
8924
9582
|
process.stdout.write(`Session captured: ${session.capturedAt}\n`);
|
|
8925
9583
|
await runBackgroundUpdateCheck(args.json);
|
|
8926
9584
|
return exitAfterFlush(ExitCode.Ok);
|
|
@@ -8944,11 +9602,14 @@ const whoamiCommand = defineCommand({
|
|
|
8944
9602
|
workspaceName: w.workspaceName,
|
|
8945
9603
|
role: w.role
|
|
8946
9604
|
})),
|
|
8947
|
-
capturedAt: session.capturedAt
|
|
9605
|
+
capturedAt: session.capturedAt,
|
|
9606
|
+
credentialSource: cred.source,
|
|
9607
|
+
...cred.email ? { credentialEmail: cred.email } : {}
|
|
8948
9608
|
})}\n`);
|
|
8949
9609
|
return exitAfterFlush(ExitCode.Ok);
|
|
8950
9610
|
}
|
|
8951
9611
|
process.stdout.write(`Logged in as ${info.name} <${info.email}> (${info.role})\n`);
|
|
9612
|
+
process.stdout.write(`Credentials: ${formatCredentials(cred)}\n`);
|
|
8952
9613
|
if (info.workspaces.length > 0) {
|
|
8953
9614
|
process.stdout.write("Workspaces:\n");
|
|
8954
9615
|
for (const w of info.workspaces) process.stdout.write(` - ${w.workspaceName} (id ${w.workspaceId}, ${w.role})\n`);
|
|
@@ -8961,9 +9622,14 @@ const whoamiCommand = defineCommand({
|
|
|
8961
9622
|
ok: true,
|
|
8962
9623
|
authenticated: false,
|
|
8963
9624
|
reason: "session-expired",
|
|
8964
|
-
errorCode: err.errorCode
|
|
9625
|
+
errorCode: err.errorCode,
|
|
9626
|
+
credentialSource: cred.source,
|
|
9627
|
+
...cred.email ? { credentialEmail: cred.email } : {}
|
|
8965
9628
|
})}\n`);
|
|
8966
|
-
else
|
|
9629
|
+
else {
|
|
9630
|
+
process.stderr.write("Session is no longer valid. Run `aitcc login` again.\n");
|
|
9631
|
+
process.stderr.write(`Credentials: ${formatCredentials(cred)}\n`);
|
|
9632
|
+
}
|
|
8967
9633
|
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
8968
9634
|
}
|
|
8969
9635
|
if (err instanceof NetworkError) {
|
|
@@ -8986,120 +9652,6 @@ const whoamiCommand = defineCommand({
|
|
|
8986
9652
|
}
|
|
8987
9653
|
});
|
|
8988
9654
|
//#endregion
|
|
8989
|
-
//#region src/api/workspaces.ts
|
|
8990
|
-
const WORKSPACES_BASE = "https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole";
|
|
8991
|
-
async function fetchWorkspaceDetail(workspaceId, cookies, opts = {}) {
|
|
8992
|
-
const raw = await requestConsoleApi({
|
|
8993
|
-
url: `${WORKSPACES_BASE}/workspaces/${workspaceId}`,
|
|
8994
|
-
cookies,
|
|
8995
|
-
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
8996
|
-
});
|
|
8997
|
-
const id = raw.id;
|
|
8998
|
-
const name = raw.name;
|
|
8999
|
-
if (typeof id !== "number" || !Number.isInteger(id) || id <= 0 || typeof name !== "string") throw new Error(`Unexpected workspace detail shape for id=${workspaceId}`);
|
|
9000
|
-
const { id: _id, name: _name, ...extra } = raw;
|
|
9001
|
-
return {
|
|
9002
|
-
workspaceId: id,
|
|
9003
|
-
workspaceName: name,
|
|
9004
|
-
extra
|
|
9005
|
-
};
|
|
9006
|
-
}
|
|
9007
|
-
async function fetchWorkspacePartner(workspaceId, cookies, opts = {}) {
|
|
9008
|
-
const raw = await requestConsoleApi({
|
|
9009
|
-
url: `${WORKSPACES_BASE}/workspaces/${workspaceId}/partner`,
|
|
9010
|
-
cookies,
|
|
9011
|
-
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
9012
|
-
});
|
|
9013
|
-
const registered = raw.registered;
|
|
9014
|
-
if (typeof registered !== "boolean") throw new Error(`Unexpected workspace partner shape for id=${workspaceId}`);
|
|
9015
|
-
return {
|
|
9016
|
-
registered,
|
|
9017
|
-
approvalType: typeof raw.approvalType === "string" ? raw.approvalType : null,
|
|
9018
|
-
rejectMessage: typeof raw.rejectMessage === "string" ? raw.rejectMessage : null,
|
|
9019
|
-
partner: raw.partner && typeof raw.partner === "object" ? raw.partner : null
|
|
9020
|
-
};
|
|
9021
|
-
}
|
|
9022
|
-
const WORKSPACE_TERM_TYPES = [
|
|
9023
|
-
"TOSS_LOGIN",
|
|
9024
|
-
"BIZ_WORKSPACE",
|
|
9025
|
-
"TOSS_PROMOTION_MONEY",
|
|
9026
|
-
"IAA",
|
|
9027
|
-
"IAP"
|
|
9028
|
-
];
|
|
9029
|
-
const DEFAULT_SEGMENT_CATEGORY = "생성된 세그먼트";
|
|
9030
|
-
async function fetchWorkspaceSegments(params, cookies, opts = {}) {
|
|
9031
|
-
const page = params.page ?? 0;
|
|
9032
|
-
const qs = new URLSearchParams();
|
|
9033
|
-
qs.set("category", params.category ?? DEFAULT_SEGMENT_CATEGORY);
|
|
9034
|
-
qs.set("search", params.search ?? "");
|
|
9035
|
-
qs.set("page", String(page));
|
|
9036
|
-
const raw = await requestConsoleApi({
|
|
9037
|
-
url: `${WORKSPACES_BASE}/workspaces/${params.workspaceId}/segments/list?${qs.toString()}`,
|
|
9038
|
-
cookies,
|
|
9039
|
-
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
9040
|
-
});
|
|
9041
|
-
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Unexpected segments shape for workspace=${params.workspaceId}`);
|
|
9042
|
-
const data = raw;
|
|
9043
|
-
return {
|
|
9044
|
-
contents: (Array.isArray(data.contents) ? data.contents : []).map((c) => c && typeof c === "object" ? c : {}),
|
|
9045
|
-
totalPage: typeof data.totalPage === "number" ? data.totalPage : 0,
|
|
9046
|
-
currentPage: typeof data.currentPage === "number" ? data.currentPage : page
|
|
9047
|
-
};
|
|
9048
|
-
}
|
|
9049
|
-
async function fetchWorkspaceTerms(workspaceId, type, cookies, opts = {}) {
|
|
9050
|
-
const raw = await requestConsoleApi({
|
|
9051
|
-
url: `${WORKSPACES_BASE}/workspaces/${workspaceId}/console-workspace-terms/${type}/skip-permission`,
|
|
9052
|
-
cookies,
|
|
9053
|
-
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
9054
|
-
});
|
|
9055
|
-
if (!Array.isArray(raw)) throw new Error(`Unexpected workspace terms shape for type=${type}`);
|
|
9056
|
-
return raw.map((entry, i) => {
|
|
9057
|
-
if (!entry || typeof entry !== "object") throw new Error(`Unexpected workspace terms entry at index ${i} for type=${type}`);
|
|
9058
|
-
const e = entry;
|
|
9059
|
-
return {
|
|
9060
|
-
required: Boolean(e.required),
|
|
9061
|
-
termsId: typeof e.termsId === "number" ? e.termsId : 0,
|
|
9062
|
-
revisionId: typeof e.revisionId === "number" ? e.revisionId : 0,
|
|
9063
|
-
title: typeof e.title === "string" ? e.title : "",
|
|
9064
|
-
contentsUrl: typeof e.contentsUrl === "string" ? e.contentsUrl : "",
|
|
9065
|
-
actionType: typeof e.actionType === "string" ? e.actionType : "",
|
|
9066
|
-
isAgreed: Boolean(e.isAgreed),
|
|
9067
|
-
isOneTimeConsent: Boolean(e.isOneTimeConsent)
|
|
9068
|
-
};
|
|
9069
|
-
});
|
|
9070
|
-
}
|
|
9071
|
-
/**
|
|
9072
|
-
* Persist agreement for one-or-more workspace terms. The endpoint takes a
|
|
9073
|
-
* single `agreedList` regardless of which bucket the terms came from — the
|
|
9074
|
-
* type tag is implicit in the (termsId, revisionId) pairs.
|
|
9075
|
-
*
|
|
9076
|
-
* Captured behaviour (2026-05-08, ws=36577):
|
|
9077
|
-
* - `POST /workspaces/<wid>/console-workspace-terms` with body
|
|
9078
|
-
* `{"agreedList":[{"termsId": <int>, "revisionId": <int>}, ...]}`
|
|
9079
|
-
* - Response on success: `{"resultType":"SUCCESS","success":{}}` — no
|
|
9080
|
-
* useful payload. We resolve `void`.
|
|
9081
|
-
* - Re-submitting an already-agreed term returns `errorCode: 500`
|
|
9082
|
-
* (Internal Server Error). The server is NOT idempotent, so callers
|
|
9083
|
-
* must filter to `isAgreed === false` before invoking.
|
|
9084
|
-
* - Empty `agreedList` returns SUCCESS (no-op), but we throw client-side
|
|
9085
|
-
* before sending — round-tripping a no-op request is wasted.
|
|
9086
|
-
*
|
|
9087
|
-
* Failure surfaces through `TossApiError` like every other write helper.
|
|
9088
|
-
*/
|
|
9089
|
-
async function agreeWorkspaceTerms(workspaceId, terms, cookies, opts = {}) {
|
|
9090
|
-
if (terms.length === 0) throw new Error("agreeWorkspaceTerms requires at least one term");
|
|
9091
|
-
await requestConsoleApi({
|
|
9092
|
-
method: "POST",
|
|
9093
|
-
url: `${WORKSPACES_BASE}/workspaces/${workspaceId}/console-workspace-terms`,
|
|
9094
|
-
cookies,
|
|
9095
|
-
body: { agreedList: terms.map(({ termsId, revisionId }) => ({
|
|
9096
|
-
termsId,
|
|
9097
|
-
revisionId
|
|
9098
|
-
})) },
|
|
9099
|
-
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
9100
|
-
});
|
|
9101
|
-
}
|
|
9102
|
-
//#endregion
|
|
9103
9655
|
//#region src/commands/workspace.ts
|
|
9104
9656
|
function formatScalar(v) {
|
|
9105
9657
|
if (v === null) return "null";
|