@ait-co/console-cli 0.1.24 → 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 +365 -102
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -2593,7 +2593,7 @@ async function runAppInit(args) {
|
|
|
2593
2593
|
workspaceId = await pickWorkspace(session.cookies);
|
|
2594
2594
|
categoryIds = await pickCategories(session.cookies);
|
|
2595
2595
|
} catch (err) {
|
|
2596
|
-
if (isPromptCancelled$
|
|
2596
|
+
if (isPromptCancelled$2(err)) {
|
|
2597
2597
|
process.stderr.write("Aborted.\n");
|
|
2598
2598
|
return exitAfterFlush(ExitCode.Usage);
|
|
2599
2599
|
}
|
|
@@ -2636,7 +2636,7 @@ async function runAppInit(args) {
|
|
|
2636
2636
|
categoryIds
|
|
2637
2637
|
};
|
|
2638
2638
|
} catch (err) {
|
|
2639
|
-
if (isPromptCancelled$
|
|
2639
|
+
if (isPromptCancelled$2(err)) {
|
|
2640
2640
|
process.stderr.write("Aborted.\n");
|
|
2641
2641
|
return exitAfterFlush(ExitCode.Usage);
|
|
2642
2642
|
}
|
|
@@ -2695,7 +2695,7 @@ async function fileExists(path) {
|
|
|
2695
2695
|
return false;
|
|
2696
2696
|
}
|
|
2697
2697
|
}
|
|
2698
|
-
function isPromptCancelled$
|
|
2698
|
+
function isPromptCancelled$2(err) {
|
|
2699
2699
|
return err instanceof Error && err.name === "ExitPromptError";
|
|
2700
2700
|
}
|
|
2701
2701
|
async function pickWorkspace(cookies) {
|
|
@@ -5856,7 +5856,7 @@ async function runCommand(command, opts) {
|
|
|
5856
5856
|
function isCommandNotFound(err) {
|
|
5857
5857
|
return err.code === "ENOENT";
|
|
5858
5858
|
}
|
|
5859
|
-
function stripTrailingNewline(s) {
|
|
5859
|
+
function stripTrailingNewline$1(s) {
|
|
5860
5860
|
return s.replace(/\r?\n$/, "");
|
|
5861
5861
|
}
|
|
5862
5862
|
function redactStderr(stderr) {
|
|
@@ -5890,7 +5890,7 @@ const LINUX_BACKEND = {
|
|
|
5890
5890
|
throw new CredentialBackendCommandError("secret-tool lookup", result.exitCode, redactStderr(result.stderr));
|
|
5891
5891
|
}
|
|
5892
5892
|
if (result.stdout.length === 0) return null;
|
|
5893
|
-
const password = stripTrailingNewline(result.stdout);
|
|
5893
|
+
const password = stripTrailingNewline$1(result.stdout);
|
|
5894
5894
|
return password.length > 0 ? password : null;
|
|
5895
5895
|
},
|
|
5896
5896
|
async set(account, password) {
|
|
@@ -5954,7 +5954,7 @@ const MACOS_BACKEND = {
|
|
|
5954
5954
|
}
|
|
5955
5955
|
if (result.exitCode === 44) return null;
|
|
5956
5956
|
if (result.exitCode !== 0) return null;
|
|
5957
|
-
const password = stripTrailingNewline(result.stdout);
|
|
5957
|
+
const password = stripTrailingNewline$1(result.stdout);
|
|
5958
5958
|
return password.length > 0 ? password : null;
|
|
5959
5959
|
},
|
|
5960
5960
|
async set(account, password) {
|
|
@@ -6489,13 +6489,17 @@ const authImportCommand = defineCommand({
|
|
|
6489
6489
|
});
|
|
6490
6490
|
//#endregion
|
|
6491
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
|
+
}
|
|
6492
6495
|
async function runAuthSet(args, deps = {}) {
|
|
6496
|
+
emitDeprecation("use `aitcc login` (interactive prompt offers a save option).");
|
|
6493
6497
|
const env = deps.env ?? process.env;
|
|
6494
6498
|
let email = args.email?.trim();
|
|
6495
|
-
let password$
|
|
6496
|
-
const argvPasswordUsed = password$
|
|
6499
|
+
let password$1 = args.password;
|
|
6500
|
+
const argvPasswordUsed = password$1 !== void 0;
|
|
6497
6501
|
if (!email && env.AITCC_EMAIL) email = env.AITCC_EMAIL;
|
|
6498
|
-
if (password$
|
|
6502
|
+
if (password$1 === void 0 && env.AITCC_PASSWORD) password$1 = env.AITCC_PASSWORD;
|
|
6499
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");
|
|
6500
6504
|
const interactive = process.stdout.isTTY && process.stdin.isTTY && !args.json;
|
|
6501
6505
|
if (!email) {
|
|
@@ -6509,7 +6513,7 @@ async function runAuthSet(args, deps = {}) {
|
|
|
6509
6513
|
validate: (raw) => raw.trim().length > 0 ? true : "email is required"
|
|
6510
6514
|
})).trim();
|
|
6511
6515
|
} catch (err) {
|
|
6512
|
-
if (isPromptCancelled(err)) {
|
|
6516
|
+
if (isPromptCancelled$1(err)) {
|
|
6513
6517
|
process.stderr.write("Aborted.\n");
|
|
6514
6518
|
return exitAfterFlush(ExitCode.Usage);
|
|
6515
6519
|
}
|
|
@@ -6525,26 +6529,26 @@ async function runAuthSet(args, deps = {}) {
|
|
|
6525
6529
|
else process.stderr.write(`Invalid email: ${email}\n`);
|
|
6526
6530
|
return exitAfterFlush(ExitCode.Usage);
|
|
6527
6531
|
}
|
|
6528
|
-
if (password$
|
|
6532
|
+
if (password$1 === void 0) {
|
|
6529
6533
|
if (!interactive) {
|
|
6530
6534
|
emitInteractiveRequired(args.json, "password");
|
|
6531
6535
|
return exitAfterFlush(ExitCode.Usage);
|
|
6532
6536
|
}
|
|
6533
6537
|
try {
|
|
6534
|
-
password$
|
|
6538
|
+
password$1 = await password({
|
|
6535
6539
|
message: "Password:",
|
|
6536
6540
|
mask: true,
|
|
6537
6541
|
validate: (raw) => raw.length > 0 ? true : "password is required"
|
|
6538
6542
|
});
|
|
6539
6543
|
} catch (err) {
|
|
6540
|
-
if (isPromptCancelled(err)) {
|
|
6544
|
+
if (isPromptCancelled$1(err)) {
|
|
6541
6545
|
process.stderr.write("Aborted.\n");
|
|
6542
6546
|
return exitAfterFlush(ExitCode.Usage);
|
|
6543
6547
|
}
|
|
6544
6548
|
throw err;
|
|
6545
6549
|
}
|
|
6546
6550
|
}
|
|
6547
|
-
if (password$
|
|
6551
|
+
if (password$1.length === 0) {
|
|
6548
6552
|
if (args.json) emitJson({
|
|
6549
6553
|
ok: false,
|
|
6550
6554
|
reason: "invalid-password",
|
|
@@ -6555,7 +6559,7 @@ async function runAuthSet(args, deps = {}) {
|
|
|
6555
6559
|
}
|
|
6556
6560
|
let result;
|
|
6557
6561
|
try {
|
|
6558
|
-
result = await saveCredentials(email, password$
|
|
6562
|
+
result = await saveCredentials(email, password$1, deps.backend ? { override: deps.backend } : {});
|
|
6559
6563
|
} catch (err) {
|
|
6560
6564
|
const message = err.message;
|
|
6561
6565
|
if (args.json) emitJson({
|
|
@@ -6576,6 +6580,7 @@ async function runAuthSet(args, deps = {}) {
|
|
|
6576
6580
|
return exitAfterFlush(ExitCode.Ok);
|
|
6577
6581
|
}
|
|
6578
6582
|
async function runAuthClear(args, deps = {}) {
|
|
6583
|
+
emitDeprecation("use `aitcc logout --purge` to remove session and saved credentials together.");
|
|
6579
6584
|
const interactive = process.stdout.isTTY && process.stdin.isTTY && !args.json;
|
|
6580
6585
|
const active = await getActiveCredentialEmail(deps.env ? { env: deps.env } : {}).catch(() => null);
|
|
6581
6586
|
if (!args.yes) {
|
|
@@ -6596,7 +6601,7 @@ async function runAuthClear(args, deps = {}) {
|
|
|
6596
6601
|
default: false
|
|
6597
6602
|
});
|
|
6598
6603
|
} catch (err) {
|
|
6599
|
-
if (isPromptCancelled(err)) {
|
|
6604
|
+
if (isPromptCancelled$1(err)) {
|
|
6600
6605
|
process.stderr.write("Aborted.\n");
|
|
6601
6606
|
return exitAfterFlush(ExitCode.Usage);
|
|
6602
6607
|
}
|
|
@@ -6634,6 +6639,7 @@ async function runAuthClear(args, deps = {}) {
|
|
|
6634
6639
|
return exitAfterFlush(ExitCode.Ok);
|
|
6635
6640
|
}
|
|
6636
6641
|
async function runAuthStatus(args, deps = {}) {
|
|
6642
|
+
emitDeprecation("use `aitcc whoami` (now reports credential source).");
|
|
6637
6643
|
const active = await getActiveCredentialEmail(deps.env ? { env: deps.env } : {}).catch(() => null);
|
|
6638
6644
|
const session = await readSession();
|
|
6639
6645
|
if (args.json) {
|
|
@@ -6672,7 +6678,7 @@ function emitInteractiveRequired(json, missing) {
|
|
|
6672
6678
|
});
|
|
6673
6679
|
else process.stderr.write(`Cannot prompt for ${missing} in non-interactive mode. Use --${missing} or set AITCC_${missing.toUpperCase()}.\n`);
|
|
6674
6680
|
}
|
|
6675
|
-
function isPromptCancelled(err) {
|
|
6681
|
+
function isPromptCancelled$1(err) {
|
|
6676
6682
|
return err instanceof Error && err.name === "ExitPromptError";
|
|
6677
6683
|
}
|
|
6678
6684
|
const authCommand = defineCommand({
|
|
@@ -6684,7 +6690,7 @@ const authCommand = defineCommand({
|
|
|
6684
6690
|
set: defineCommand({
|
|
6685
6691
|
meta: {
|
|
6686
6692
|
name: "set",
|
|
6687
|
-
description: "
|
|
6693
|
+
description: "[deprecated] Use `aitcc login` instead — the prompt now offers a save option."
|
|
6688
6694
|
},
|
|
6689
6695
|
args: {
|
|
6690
6696
|
json: {
|
|
@@ -6712,7 +6718,7 @@ const authCommand = defineCommand({
|
|
|
6712
6718
|
clear: defineCommand({
|
|
6713
6719
|
meta: {
|
|
6714
6720
|
name: "clear",
|
|
6715
|
-
description: "
|
|
6721
|
+
description: "[deprecated] Use `aitcc logout --purge` instead."
|
|
6716
6722
|
},
|
|
6717
6723
|
args: {
|
|
6718
6724
|
json: {
|
|
@@ -6737,7 +6743,7 @@ const authCommand = defineCommand({
|
|
|
6737
6743
|
status: defineCommand({
|
|
6738
6744
|
meta: {
|
|
6739
6745
|
name: "status",
|
|
6740
|
-
description: "
|
|
6746
|
+
description: "[deprecated] Use `aitcc whoami` (now reports credential source)."
|
|
6741
6747
|
},
|
|
6742
6748
|
args: { json: {
|
|
6743
6749
|
type: "boolean",
|
|
@@ -7897,10 +7903,38 @@ function chooseLoginMode(input) {
|
|
|
7897
7903
|
if (input.interactiveFlag) return "interactive";
|
|
7898
7904
|
return input.hasCredentials ? "headless" : "interactive";
|
|
7899
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
|
+
};
|
|
7900
7934
|
const loginCommand = defineCommand({
|
|
7901
7935
|
meta: {
|
|
7902
7936
|
name: "login",
|
|
7903
|
-
description: "
|
|
7937
|
+
description: "Sign in to the Apps in Toss console and capture the session cookies."
|
|
7904
7938
|
},
|
|
7905
7939
|
args: {
|
|
7906
7940
|
json: {
|
|
@@ -7918,9 +7952,26 @@ const loginCommand = defineCommand({
|
|
|
7918
7952
|
description: "Force the visible-browser flow even if credentials are configured.",
|
|
7919
7953
|
default: false
|
|
7920
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
|
+
},
|
|
7921
7972
|
"skip-onboarding": {
|
|
7922
7973
|
type: "boolean",
|
|
7923
|
-
description: "
|
|
7974
|
+
description: "Deprecated no-op; kept so existing scripts do not break.",
|
|
7924
7975
|
default: false
|
|
7925
7976
|
}
|
|
7926
7977
|
},
|
|
@@ -7929,7 +7980,10 @@ const loginCommand = defineCommand({
|
|
|
7929
7980
|
json: args.json,
|
|
7930
7981
|
timeout: args.timeout,
|
|
7931
7982
|
interactive: args.interactive,
|
|
7932
|
-
|
|
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
|
|
7933
7987
|
}, {
|
|
7934
7988
|
getCredentials: loadCredentials,
|
|
7935
7989
|
saveCredentials
|
|
@@ -7973,17 +8027,41 @@ async function runLoginCommand(args, deps) {
|
|
|
7973
8027
|
}
|
|
7974
8028
|
process.stderr.write(`Using custom authorize URL from AITCC_OAUTH_URL: ${authorizeUrl}\n`);
|
|
7975
8029
|
}
|
|
7976
|
-
|
|
7977
|
-
if (
|
|
7978
|
-
|
|
7979
|
-
|
|
7980
|
-
|
|
7981
|
-
|
|
7982
|
-
|
|
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
|
+
}
|
|
7983
8061
|
}
|
|
7984
8062
|
const initialMode = chooseLoginMode({
|
|
7985
8063
|
interactiveFlag: args.interactive,
|
|
7986
|
-
hasCredentials: credentials !== null
|
|
8064
|
+
hasCredentials: resolved.credentials !== null
|
|
7987
8065
|
});
|
|
7988
8066
|
const endpointTimeoutMs = Math.min(6e4, Math.max(3e4, Math.floor(timeoutMs / 2)));
|
|
7989
8067
|
const firstAttemptStart = Date.now();
|
|
@@ -7993,9 +8071,9 @@ async function runLoginCommand(args, deps) {
|
|
|
7993
8071
|
endpointTimeoutMs,
|
|
7994
8072
|
authorizeUrl,
|
|
7995
8073
|
mode: initialMode,
|
|
7996
|
-
credentials,
|
|
7997
|
-
|
|
7998
|
-
|
|
8074
|
+
credentials: resolved.credentials,
|
|
8075
|
+
saved,
|
|
8076
|
+
emitError
|
|
7999
8077
|
});
|
|
8000
8078
|
if (result.status === "fallback-to-interactive") {
|
|
8001
8079
|
process.stderr.write(`${result.message}\n`);
|
|
@@ -8006,8 +8084,8 @@ async function runLoginCommand(args, deps) {
|
|
|
8006
8084
|
authorizeUrl,
|
|
8007
8085
|
mode: "interactive",
|
|
8008
8086
|
credentials: null,
|
|
8009
|
-
|
|
8010
|
-
|
|
8087
|
+
saved,
|
|
8088
|
+
emitError
|
|
8011
8089
|
});
|
|
8012
8090
|
if (second.status === "exit") return exitAfterFlush(second.code);
|
|
8013
8091
|
second.status;
|
|
@@ -8015,8 +8093,167 @@ async function runLoginCommand(args, deps) {
|
|
|
8015
8093
|
}
|
|
8016
8094
|
return exitAfterFlush(result.code);
|
|
8017
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
|
+
}
|
|
8018
8255
|
async function attemptLogin(opts) {
|
|
8019
|
-
const { args, timeoutMs, endpointTimeoutMs, authorizeUrl, mode, credentials,
|
|
8256
|
+
const { args, timeoutMs, endpointTimeoutMs, authorizeUrl, mode, credentials, saved, emitError } = opts;
|
|
8020
8257
|
const launched = await launchChrome({
|
|
8021
8258
|
initialUrl: authorizeUrl,
|
|
8022
8259
|
endpointTimeoutMs,
|
|
@@ -8055,8 +8292,8 @@ async function attemptLogin(opts) {
|
|
|
8055
8292
|
}
|
|
8056
8293
|
if (mode === "interactive") process.stderr.write("Opened a browser window — complete the sign-in there. The CLI will capture the session automatically.\n");
|
|
8057
8294
|
else {
|
|
8058
|
-
const
|
|
8059
|
-
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`);
|
|
8060
8297
|
}
|
|
8061
8298
|
let client = null;
|
|
8062
8299
|
const disposeAll = async () => {
|
|
@@ -8223,57 +8460,29 @@ async function attemptLogin(opts) {
|
|
|
8223
8460
|
capturedAt: session.capturedAt,
|
|
8224
8461
|
cookieCount: cookies.length,
|
|
8225
8462
|
mode,
|
|
8463
|
+
credentialSource: credentials?.source ?? "browser",
|
|
8464
|
+
saved,
|
|
8226
8465
|
stepUp
|
|
8227
8466
|
})}\n`);
|
|
8228
8467
|
else process.stdout.write(`Logged in as ${user.name} <${user.email}>\n`);
|
|
8229
8468
|
await disposeAll();
|
|
8230
|
-
if (mode === "interactive" && credentials === null && !args.json && !args.skipOnboarding && process.stdout.isTTY && process.stdin.isTTY && deps.saveCredentials) await runOnboardingPrompt(user.email, deps.saveCredentials);
|
|
8231
8469
|
return {
|
|
8232
8470
|
status: "exit",
|
|
8233
8471
|
code: ExitCode.Ok
|
|
8234
8472
|
};
|
|
8235
8473
|
}
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
agreed = await confirm({
|
|
8249
|
-
message: "Save credentials?",
|
|
8250
|
-
default: true
|
|
8251
|
-
});
|
|
8252
|
-
} catch (err) {
|
|
8253
|
-
if (err instanceof Error && err.name === "ExitPromptError") return;
|
|
8254
|
-
process.stderr.write(`Onboarding prompt failed: ${err.message}\n`);
|
|
8255
|
-
return;
|
|
8256
|
-
}
|
|
8257
|
-
if (!agreed) return;
|
|
8258
|
-
let password$1;
|
|
8259
|
-
try {
|
|
8260
|
-
password$1 = await password({
|
|
8261
|
-
message: "Password:",
|
|
8262
|
-
mask: true,
|
|
8263
|
-
validate: (raw) => raw.length > 0 ? true : "password is required"
|
|
8264
|
-
});
|
|
8265
|
-
} catch (err) {
|
|
8266
|
-
if (err instanceof Error && err.name === "ExitPromptError") return;
|
|
8267
|
-
process.stderr.write(`Could not read password: ${err.message}\n`);
|
|
8268
|
-
return;
|
|
8269
|
-
}
|
|
8270
|
-
try {
|
|
8271
|
-
await save(email, password$1);
|
|
8272
|
-
process.stdout.write("Saved. Next `aitcc login` will run headlessly.\n");
|
|
8273
|
-
} catch (err) {
|
|
8274
|
-
process.stderr.write(`Could not save credentials: ${err.message}. You can retry later with \`aitcc auth set\`.
|
|
8275
|
-
`);
|
|
8276
|
-
}
|
|
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";
|
|
8277
8486
|
}
|
|
8278
8487
|
async function waitForLanding(client, sessionId, timeoutMs) {
|
|
8279
8488
|
return await new Promise((resolve) => {
|
|
@@ -8337,18 +8546,25 @@ async function resolveUserWithRetry(cookies, opts = {}) {
|
|
|
8337
8546
|
const logoutCommand = defineCommand({
|
|
8338
8547
|
meta: {
|
|
8339
8548
|
name: "logout",
|
|
8340
|
-
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
|
+
}
|
|
8341
8562
|
},
|
|
8342
|
-
args: { json: {
|
|
8343
|
-
type: "boolean",
|
|
8344
|
-
description: "Emit machine-readable JSON to stdout.",
|
|
8345
|
-
default: false
|
|
8346
|
-
} },
|
|
8347
8563
|
async run({ args }) {
|
|
8348
8564
|
const path = sessionPathForDiagnostics();
|
|
8349
|
-
let
|
|
8565
|
+
let sessionRemoved;
|
|
8350
8566
|
try {
|
|
8351
|
-
|
|
8567
|
+
sessionRemoved = (await clearSession()).existed;
|
|
8352
8568
|
} catch (err) {
|
|
8353
8569
|
const message = err.message;
|
|
8354
8570
|
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
@@ -8360,13 +8576,29 @@ const logoutCommand = defineCommand({
|
|
|
8360
8576
|
process.stderr.write(`Failed to remove session file at ${path}: ${message}\n`);
|
|
8361
8577
|
return exitAfterFlush(ExitCode.Generic);
|
|
8362
8578
|
}
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
})
|
|
8368
|
-
|
|
8369
|
-
|
|
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
|
+
}
|
|
8370
8602
|
return exitAfterFlush(ExitCode.Ok);
|
|
8371
8603
|
}
|
|
8372
8604
|
});
|
|
@@ -8901,7 +9133,7 @@ function resolveVersion() {
|
|
|
8901
9133
|
if (typeof injected === "string" && injected.length > 0) return injected;
|
|
8902
9134
|
} catch {}
|
|
8903
9135
|
try {
|
|
8904
|
-
return "0.1.
|
|
9136
|
+
return "0.1.25";
|
|
8905
9137
|
} catch {}
|
|
8906
9138
|
return "0.0.0-dev";
|
|
8907
9139
|
}
|
|
@@ -9273,6 +9505,22 @@ function maybeEmitNotice(entry, env) {
|
|
|
9273
9505
|
}
|
|
9274
9506
|
//#endregion
|
|
9275
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
|
+
}
|
|
9276
9524
|
async function runBackgroundUpdateCheck(json) {
|
|
9277
9525
|
if (json) return;
|
|
9278
9526
|
const timeoutMs = 500;
|
|
@@ -9300,14 +9548,18 @@ const whoamiCommand = defineCommand({
|
|
|
9300
9548
|
},
|
|
9301
9549
|
async run({ args }) {
|
|
9302
9550
|
const session = await readSession();
|
|
9551
|
+
const cred = await describeCredentialSource();
|
|
9303
9552
|
if (!session) {
|
|
9304
9553
|
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
9305
9554
|
ok: true,
|
|
9306
|
-
authenticated: false
|
|
9555
|
+
authenticated: false,
|
|
9556
|
+
credentialSource: cred.source,
|
|
9557
|
+
...cred.email ? { credentialEmail: cred.email } : {}
|
|
9307
9558
|
})}\n`);
|
|
9308
9559
|
else {
|
|
9309
9560
|
process.stderr.write("Not logged in. Run `aitcc login` to start a session.\n");
|
|
9310
9561
|
process.stderr.write(`Session file checked: ${sessionPathForDiagnostics()}\n`);
|
|
9562
|
+
process.stderr.write(`Credentials: ${formatCredentials(cred)}\n`);
|
|
9311
9563
|
}
|
|
9312
9564
|
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
9313
9565
|
}
|
|
@@ -9318,12 +9570,15 @@ const whoamiCommand = defineCommand({
|
|
|
9318
9570
|
authenticated: true,
|
|
9319
9571
|
source: "cache",
|
|
9320
9572
|
user: session.user,
|
|
9321
|
-
capturedAt: session.capturedAt
|
|
9573
|
+
capturedAt: session.capturedAt,
|
|
9574
|
+
credentialSource: cred.source,
|
|
9575
|
+
...cred.email ? { credentialEmail: cred.email } : {}
|
|
9322
9576
|
})}\n`);
|
|
9323
9577
|
return exitAfterFlush(ExitCode.Ok);
|
|
9324
9578
|
}
|
|
9325
9579
|
const label = session.user.displayName ? `${session.user.displayName} <${session.user.email}>` : session.user.email;
|
|
9326
9580
|
process.stdout.write(`Logged in as ${label} (cached)\n`);
|
|
9581
|
+
process.stdout.write(`Credentials: ${formatCredentials(cred)}\n`);
|
|
9327
9582
|
process.stdout.write(`Session captured: ${session.capturedAt}\n`);
|
|
9328
9583
|
await runBackgroundUpdateCheck(args.json);
|
|
9329
9584
|
return exitAfterFlush(ExitCode.Ok);
|
|
@@ -9347,11 +9602,14 @@ const whoamiCommand = defineCommand({
|
|
|
9347
9602
|
workspaceName: w.workspaceName,
|
|
9348
9603
|
role: w.role
|
|
9349
9604
|
})),
|
|
9350
|
-
capturedAt: session.capturedAt
|
|
9605
|
+
capturedAt: session.capturedAt,
|
|
9606
|
+
credentialSource: cred.source,
|
|
9607
|
+
...cred.email ? { credentialEmail: cred.email } : {}
|
|
9351
9608
|
})}\n`);
|
|
9352
9609
|
return exitAfterFlush(ExitCode.Ok);
|
|
9353
9610
|
}
|
|
9354
9611
|
process.stdout.write(`Logged in as ${info.name} <${info.email}> (${info.role})\n`);
|
|
9612
|
+
process.stdout.write(`Credentials: ${formatCredentials(cred)}\n`);
|
|
9355
9613
|
if (info.workspaces.length > 0) {
|
|
9356
9614
|
process.stdout.write("Workspaces:\n");
|
|
9357
9615
|
for (const w of info.workspaces) process.stdout.write(` - ${w.workspaceName} (id ${w.workspaceId}, ${w.role})\n`);
|
|
@@ -9364,9 +9622,14 @@ const whoamiCommand = defineCommand({
|
|
|
9364
9622
|
ok: true,
|
|
9365
9623
|
authenticated: false,
|
|
9366
9624
|
reason: "session-expired",
|
|
9367
|
-
errorCode: err.errorCode
|
|
9625
|
+
errorCode: err.errorCode,
|
|
9626
|
+
credentialSource: cred.source,
|
|
9627
|
+
...cred.email ? { credentialEmail: cred.email } : {}
|
|
9368
9628
|
})}\n`);
|
|
9369
|
-
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
|
+
}
|
|
9370
9633
|
return exitAfterFlush(ExitCode.NotAuthenticated);
|
|
9371
9634
|
}
|
|
9372
9635
|
if (err instanceof NetworkError) {
|