copillm 0.3.0-beta.1 → 0.3.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/auth/runAuth.js +102 -34
- package/dist/cli/packageInfo.js +1 -1
- package/package.json +1 -1
package/dist/cli/auth/runAuth.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
1
2
|
import { clearStoredCredential, loadStoredCredential, loadStoredCredentialForAccount, registerExistingCredentialAsDefault, saveStoredCredential } from "../../auth/credentials.js";
|
|
2
|
-
import { readAccountsIndex, assertValidAccountId, InvalidAccountIdError, UnknownAccountError } from "../../auth/accounts.js";
|
|
3
|
+
import { readAccountsIndex, findAccount, assertValidAccountId, InvalidAccountIdError, UnknownAccountError } from "../../auth/accounts.js";
|
|
3
4
|
import { addAccount, listAccountsDetailed, removeAccountAndCredential, removeAllAccounts, switchDefaultAccount } from "../../auth/accountManager.js";
|
|
4
5
|
import { loginViaDeviceFlow } from "../../auth/deviceFlow.js";
|
|
5
6
|
import { inspectGithubIdentity } from "../../auth/githubIdentity.js";
|
|
@@ -11,26 +12,37 @@ import { writeCommandOutput } from "../shared/output.js";
|
|
|
11
12
|
/**
|
|
12
13
|
* Derive a friendly, path-safe account id from the GitHub login behind a token.
|
|
13
14
|
* Returns null when the lookup fails or the login isn't a valid id.
|
|
15
|
+
*
|
|
16
|
+
* Multi-account routing now depends on this, so it retries a few times with a
|
|
17
|
+
* generous timeout: GitHub's `/user` can briefly throttle or lag right after a
|
|
18
|
+
* device-flow token exchange, and a single failed probe must not cause the
|
|
19
|
+
* caller to mis-identify (and overwrite) the wrong account.
|
|
14
20
|
*/
|
|
15
21
|
async function deriveAccountId(token) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
identity =
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
const attempts = 3;
|
|
23
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
24
|
+
let identity = null;
|
|
25
|
+
try {
|
|
26
|
+
identity = await inspectGithubIdentity({ token, timeoutMs: 8_000 });
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
identity = null;
|
|
30
|
+
}
|
|
31
|
+
const login = identity?.login;
|
|
32
|
+
if (login) {
|
|
33
|
+
try {
|
|
34
|
+
assertValidAccountId(login);
|
|
35
|
+
return login;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (attempt < attempts) {
|
|
42
|
+
await sleep(400 * attempt);
|
|
43
|
+
}
|
|
33
44
|
}
|
|
45
|
+
return null;
|
|
34
46
|
}
|
|
35
47
|
export async function runAuthLogin(opts, options) {
|
|
36
48
|
if (options.forceSession) {
|
|
@@ -53,9 +65,41 @@ export async function runAuthLogin(opts, options) {
|
|
|
53
65
|
const token = await loginViaDeviceFlow();
|
|
54
66
|
const saveMode = options.forceSession ? "session" : "auto";
|
|
55
67
|
const index = readAccountsIndex();
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
// ---- Explicit name (`--as`) -------------------------------------------
|
|
69
|
+
if (namedRequested) {
|
|
70
|
+
const accountId = opts.as.trim();
|
|
71
|
+
// First time we materialize the index via an explicit name: preserve any
|
|
72
|
+
// pre-existing single account as the default so its token isn't clobbered.
|
|
73
|
+
if (!index) {
|
|
74
|
+
const existing = await loadStoredCredential();
|
|
75
|
+
if (existing) {
|
|
76
|
+
const existingId = (await deriveAccountId(existing.token)) ?? "default";
|
|
77
|
+
if (existingId !== accountId) {
|
|
78
|
+
registerExistingCredentialAsDefault(existingId, existing.accountType);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const result = await addAccount({ id: accountId, accountType, token, mode: saveMode });
|
|
83
|
+
emitLoginResult(opts, result, false);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// ---- No name: auto-manage by the token's GitHub login -----------------
|
|
87
|
+
// Identify the account from its GitHub login so a second `auth login` for a
|
|
88
|
+
// DIFFERENT account is kept alongside the first instead of overwriting it.
|
|
89
|
+
// The cardinal rule: never replace an existing credential unless we have
|
|
90
|
+
// positively confirmed it's the SAME GitHub account.
|
|
91
|
+
const newLogin = await deriveAccountId(token);
|
|
92
|
+
const existing = await loadStoredCredential();
|
|
93
|
+
if (!existing) {
|
|
94
|
+
// Nothing stored yet.
|
|
95
|
+
if (index) {
|
|
96
|
+
// An index exists but its default account has no credential — restore it.
|
|
97
|
+
const targetId = newLogin ?? index.defaultAccount;
|
|
98
|
+
const result = await addAccount({ id: targetId, accountType, token, mode: saveMode });
|
|
99
|
+
emitLoginResult(opts, result, !result.isDefault);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Fresh single-account install: store without creating an index.
|
|
59
103
|
const backend = await saveStoredCredential(token, accountType, { mode: saveMode });
|
|
60
104
|
writeCommandOutput(opts, `Login succeeded. Credentials stored via ${describeBackend(backend)}.`, {
|
|
61
105
|
status: "ok",
|
|
@@ -64,21 +108,45 @@ export async function runAuthLogin(opts, options) {
|
|
|
64
108
|
});
|
|
65
109
|
return;
|
|
66
110
|
}
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
111
|
+
// A credential already exists. We must know which GitHub account just signed
|
|
112
|
+
// in before we touch anything, or we risk clobbering a different account.
|
|
113
|
+
if (!newLogin) {
|
|
114
|
+
writeCommandOutput(opts, "Login failed: couldn't verify which GitHub account you signed in as (the GitHub user lookup didn't succeed). " +
|
|
115
|
+
"Your existing credentials were left untouched. Re-run `copillm auth login`, or name this account explicitly with `copillm auth login --as <name>`.", { status: "error", action: "login", error: "github_identity_unresolved" });
|
|
116
|
+
process.exitCode = 1;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (index) {
|
|
120
|
+
// Add the new login as its own account, or refresh it in place if known.
|
|
121
|
+
const wasKnown = findAccount(newLogin) !== null;
|
|
122
|
+
const result = await addAccount({ id: newLogin, accountType, token, mode: saveMode });
|
|
123
|
+
emitLoginResult(opts, result, !wasKnown && !result.isDefault);
|
|
124
|
+
return;
|
|
78
125
|
}
|
|
79
|
-
|
|
126
|
+
// No index yet. Compare against the existing single account.
|
|
127
|
+
const existingLogin = await deriveAccountId(existing.token);
|
|
128
|
+
if (existingLogin === newLogin) {
|
|
129
|
+
// Confirmed the SAME account → refresh in place, no index created.
|
|
130
|
+
const backend = await saveStoredCredential(token, accountType, { mode: saveMode });
|
|
131
|
+
writeCommandOutput(opts, `Login succeeded. Credentials stored via ${describeBackend(backend)}.`, {
|
|
132
|
+
status: "ok",
|
|
133
|
+
action: "login",
|
|
134
|
+
credential_backend: backend
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// A different (or unverifiable) prior account → transition to multi-account,
|
|
139
|
+
// preserving the prior login as the default and adding the new one.
|
|
140
|
+
registerExistingCredentialAsDefault(existingLogin ?? "default", existing.accountType);
|
|
141
|
+
const result = await addAccount({ id: newLogin, accountType, token, mode: saveMode });
|
|
142
|
+
emitLoginResult(opts, result, !result.isDefault);
|
|
143
|
+
}
|
|
144
|
+
function emitLoginResult(opts, result, hintSwitch) {
|
|
80
145
|
const defaultSuffix = result.isDefault ? " (default)" : "";
|
|
81
|
-
|
|
146
|
+
const switchHint = hintSwitch
|
|
147
|
+
? ` It is not the default — run \`copillm auth switch ${result.id}\` to make it so.`
|
|
148
|
+
: "";
|
|
149
|
+
writeCommandOutput(opts, `Login succeeded for account "${result.id}"${defaultSuffix}. Credentials stored via ${describeBackend(result.backend)}.${switchHint}`, {
|
|
82
150
|
status: "ok",
|
|
83
151
|
action: "login",
|
|
84
152
|
account: result.id,
|
package/dist/cli/packageInfo.js
CHANGED