@askthew/mcp-plugin 0.4.2 → 0.4.4
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.d.ts +0 -3
- package/dist/cli.js +16 -50
- package/dist/cli.test.js +28 -93
- package/dist/free-tier-policy.test.js +12 -11
- package/dist/index.test.js +10 -12
- package/dist/lib/free-tier-policy.d.ts +1 -2
- package/dist/lib/free-tier-policy.js +3 -19
- package/dist/lib/paths.d.ts +0 -1
- package/dist/lib/paths.js +0 -3
- package/dist/lib/telemetry.js +14 -18
- package/package.json +1 -1
- package/dist/auth-pending.test.d.ts +0 -1
- package/dist/auth-pending.test.js +0 -56
- package/dist/lib/auth-magic-link.d.ts +0 -22
- package/dist/lib/auth-magic-link.js +0 -43
- package/dist/lib/auth-pending.d.ts +0 -23
- package/dist/lib/auth-pending.js +0 -36
package/dist/cli.d.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { requestMagicLinkCode as requestMagicLinkCodeDefault, verifyMagicLinkCode as verifyMagicLinkCodeDefault } from "./lib/auth-magic-link.js";
|
|
3
2
|
import { tryRegisterFreeInstall } from "./lib/free-install-registration.js";
|
|
4
3
|
type AuthCommandDeps = {
|
|
5
4
|
log?: (message: string) => void;
|
|
6
|
-
requestMagicLinkCode?: typeof requestMagicLinkCodeDefault;
|
|
7
|
-
verifyMagicLinkCode?: typeof verifyMagicLinkCodeDefault;
|
|
8
5
|
registerFreeInstall?: typeof tryRegisterFreeInstall;
|
|
9
6
|
};
|
|
10
7
|
export declare function runAuthCommand(argv: string[], deps?: AuthCommandDeps): Promise<void>;
|
package/dist/cli.js
CHANGED
|
@@ -5,9 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import { execFileSync } from "node:child_process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { createAskTheWMcpServer } from "./index.js";
|
|
8
|
-
import {
|
|
9
|
-
import { verifyMagicLinkCode as verifyMagicLinkCodeDefault, } from "./lib/auth-magic-link.js";
|
|
10
|
-
import { credentialsPath, ensureAskTheWDataDir } from "./lib/paths.js";
|
|
8
|
+
import { ensureAskTheWDataDir, identityPath } from "./lib/paths.js";
|
|
11
9
|
import { loadCliCredentials } from "./lib/free-tier-policy.js";
|
|
12
10
|
import { describeFreeIdentity, tryRegisterFreeInstall } from "./lib/free-install-registration.js";
|
|
13
11
|
import { ensureLocalIdentity, loadLocalIdentity, publicIdentity } from "./lib/local-identity.js";
|
|
@@ -28,13 +26,12 @@ function usage() {
|
|
|
28
26
|
" askthew-mcp identify --email <email> [--no-telemetry]",
|
|
29
27
|
" askthew-mcp identity status",
|
|
30
28
|
" askthew-mcp auth login --email <email> [--no-telemetry]",
|
|
31
|
-
" askthew-mcp auth verify --code <code> [--email <email>]",
|
|
32
29
|
" askthew-mcp auth logout | status",
|
|
33
30
|
" askthew-mcp telemetry status | opt-out | opt-in | preview",
|
|
34
31
|
" askthew-mcp local stats | reset --hard",
|
|
35
32
|
" askthew-mcp install-hook --pre-commit",
|
|
36
33
|
" askthew-mcp digest --weekly",
|
|
37
|
-
" askthew-mcp sync upload [--dry-run]",
|
|
34
|
+
" askthew-mcp sync upload [--token <workspace-install-token>] [--dry-run]",
|
|
38
35
|
" askthew-mcp print-config --host <claude_code|codex|cursor> --token <install-token> --api-url <url> --server-name <name> [--client-id <id>] [--client-label <label>]",
|
|
39
36
|
].join("\n");
|
|
40
37
|
}
|
|
@@ -340,7 +337,7 @@ async function runUninstallCommand(argv) {
|
|
|
340
337
|
fs.rmSync(ensureAskTheWDataDir(), { recursive: true, force: true });
|
|
341
338
|
}
|
|
342
339
|
if (!keepAuth && !dryRun) {
|
|
343
|
-
const file =
|
|
340
|
+
const file = identityPath();
|
|
344
341
|
if (fs.existsSync(file))
|
|
345
342
|
fs.rmSync(file, { force: true });
|
|
346
343
|
}
|
|
@@ -362,55 +359,27 @@ function argValue(argv, name) {
|
|
|
362
359
|
}
|
|
363
360
|
export async function runAuthCommand(argv, deps = {}) {
|
|
364
361
|
const log = deps.log ?? console.log;
|
|
365
|
-
const verifyCode = deps.verifyMagicLinkCode ?? verifyMagicLinkCodeDefault;
|
|
366
362
|
const registerInstall = deps.registerFreeInstall ?? tryRegisterFreeInstall;
|
|
367
363
|
const [subcommand] = argv;
|
|
368
364
|
if (subcommand === "status") {
|
|
369
365
|
const identity = loadLocalIdentity();
|
|
370
|
-
const credentials = loadCliCredentials();
|
|
371
366
|
log(identity
|
|
372
367
|
? `Identified local free install ${identity.installId}${identity.emailClaim ? ` with email claim ${identity.emailClaim}` : ""}. Email claim is unverified until upgrade.`
|
|
373
|
-
:
|
|
374
|
-
? `Logged in as ${credentials.email ?? credentials.userId}. Telemetry: ${credentials.telemetryOptOut ? "off" : "on"}`
|
|
375
|
-
: pendingAuth()
|
|
376
|
-
? `Not logged in. Pending code for ${pendingAuth()?.email}. Run \`askthew-mcp auth verify --code <6-digit-code>\`.`
|
|
377
|
-
: `No local identity yet. Run \`askthew-mcp identify --email <your-email>\`, or install with \`--free --email <your-email>\`.`);
|
|
368
|
+
: `No local identity yet. Run \`askthew-mcp identify --email <your-email>\`, or install with \`--free --email <your-email>\`.`);
|
|
378
369
|
return;
|
|
379
370
|
}
|
|
380
371
|
if (subcommand === "logout") {
|
|
381
|
-
const file =
|
|
372
|
+
const file = identityPath();
|
|
382
373
|
if (fs.existsSync(file))
|
|
383
374
|
fs.rmSync(file);
|
|
384
|
-
log("
|
|
375
|
+
log("Removed Ask The W local free install identity.");
|
|
385
376
|
return;
|
|
386
377
|
}
|
|
387
|
-
if (subcommand !== "login"
|
|
388
|
-
throw new Error("Usage: askthew-mcp auth login --email <email> [--no-telemetry] | askthew-mcp auth
|
|
378
|
+
if (subcommand !== "login") {
|
|
379
|
+
throw new Error("Usage: askthew-mcp auth login --email <email> [--no-telemetry] | askthew-mcp auth logout | status");
|
|
389
380
|
}
|
|
390
381
|
ensureAskTheWDataDir();
|
|
391
382
|
const email = argValue(argv, "--email")?.trim();
|
|
392
|
-
const code = argValue(argv, "--code")?.trim();
|
|
393
|
-
if (subcommand === "verify" || code) {
|
|
394
|
-
if (!code)
|
|
395
|
-
throw new Error("Missing --code.");
|
|
396
|
-
const pending = email ? pendingAuthForEmail(email) : pendingAuth();
|
|
397
|
-
if (!pending) {
|
|
398
|
-
throw new Error(email
|
|
399
|
-
? `No pending Ask The W login request for ${email}. Run \`askthew-mcp auth login --email ${email}\` first.`
|
|
400
|
-
: "No pending Ask The W login request. Run `askthew-mcp auth login --email <email>` first.");
|
|
401
|
-
}
|
|
402
|
-
if (subcommand === "login") {
|
|
403
|
-
log("Using the pending Ask The W login request. Next time, run `askthew-mcp auth verify --code <6-digit-code>`.");
|
|
404
|
-
}
|
|
405
|
-
const credentials = await verifyCode({
|
|
406
|
-
requestId: pending.requestId,
|
|
407
|
-
code,
|
|
408
|
-
telemetryOptOut: pending.telemetryOptOut,
|
|
409
|
-
});
|
|
410
|
-
clearPendingAuth();
|
|
411
|
-
log(`Logged in. Account status: ${credentials.accountStatus}. Credentials stored with mode 0600.`);
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
383
|
if (!email)
|
|
415
384
|
throw new Error("Missing --email.");
|
|
416
385
|
const noTelemetry = argv.includes("--no-telemetry");
|
|
@@ -464,15 +433,8 @@ async function runTelemetryCommand(argv) {
|
|
|
464
433
|
return;
|
|
465
434
|
}
|
|
466
435
|
if (subcommand === "opt-out" || subcommand === "opt-in") {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
console.log(`Telemetry: ${subcommand === "opt-out" ? "off" : "on"}`);
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
const next = { ...credentials, telemetryOptOut: subcommand === "opt-out" };
|
|
473
|
-
fs.writeFileSync(credentialsPath(), `${JSON.stringify(next, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
474
|
-
fs.chmodSync(credentialsPath(), 0o600);
|
|
475
|
-
console.log(`Telemetry: ${next.telemetryOptOut ? "off" : "on"}`);
|
|
436
|
+
ensureLocalIdentity({ telemetryOptOut: subcommand === "opt-out" });
|
|
437
|
+
console.log(`Telemetry: ${subcommand === "opt-out" ? "off" : "on"}`);
|
|
476
438
|
return;
|
|
477
439
|
}
|
|
478
440
|
if (subcommand === "preview") {
|
|
@@ -525,7 +487,7 @@ async function runDigestCommand(argv) {
|
|
|
525
487
|
}
|
|
526
488
|
async function runSyncCommand(argv) {
|
|
527
489
|
if (argv[0] !== "upload")
|
|
528
|
-
throw new Error("Usage: askthew-mcp sync upload [--dry-run]");
|
|
490
|
+
throw new Error("Usage: askthew-mcp sync upload [--token <workspace-install-token>] [--dry-run]");
|
|
529
491
|
const credentials = loadCliCredentials();
|
|
530
492
|
if (!credentials)
|
|
531
493
|
throw new Error("No local identity. Run `askthew-mcp identify --email <your-email>` first.");
|
|
@@ -534,7 +496,11 @@ async function runSyncCommand(argv) {
|
|
|
534
496
|
console.log(JSON.stringify(syncDryRun(store), null, 2));
|
|
535
497
|
return;
|
|
536
498
|
}
|
|
537
|
-
|
|
499
|
+
const syncToken = argValue(argv, "--token")?.trim() || process.env.ASKTHEW_INSTALL_TOKEN?.trim();
|
|
500
|
+
if (!syncToken) {
|
|
501
|
+
throw new Error("Missing workspace install token. Pass `--token <workspace-install-token>` after upgrading.");
|
|
502
|
+
}
|
|
503
|
+
console.log(JSON.stringify(await uploadLocalStore({ store, credentials, syncToken }), null, 2));
|
|
538
504
|
}
|
|
539
505
|
const isDirectCliExecution = Boolean(process.argv[1]) && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
540
506
|
if (isDirectCliExecution) {
|
package/dist/cli.test.js
CHANGED
|
@@ -6,7 +6,7 @@ import os from "node:os";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { runAuthCommand } from "./cli.js";
|
|
9
|
-
import {
|
|
9
|
+
import { identityPath } from "./lib/paths.js";
|
|
10
10
|
const cliPath = fileURLToPath(new URL("./cli.js", import.meta.url));
|
|
11
11
|
function makeFixture() {
|
|
12
12
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-cli-install-"));
|
|
@@ -38,14 +38,6 @@ function runCli(input) {
|
|
|
38
38
|
},
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
|
-
function writeCredentials(dataDir) {
|
|
42
|
-
fs.writeFileSync(path.join(dataDir, "credentials.json"), `${JSON.stringify({
|
|
43
|
-
email: "founder@example.com",
|
|
44
|
-
userId: "user_1",
|
|
45
|
-
cliToken: "cli_token",
|
|
46
|
-
cliTokenId: "cli_token_1",
|
|
47
|
-
}, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
48
|
-
}
|
|
49
41
|
async function withCliEnv(dataDir, fn) {
|
|
50
42
|
const previous = {
|
|
51
43
|
ASKTHEW_DATA_DIR: process.env.ASKTHEW_DATA_DIR,
|
|
@@ -120,10 +112,10 @@ test("free install dry-run stays non-mutating and does not create identity", ()
|
|
|
120
112
|
fs.rmSync(fixture.root, { recursive: true, force: true });
|
|
121
113
|
}
|
|
122
114
|
});
|
|
123
|
-
test("free install
|
|
115
|
+
test("free install ignores stale legacy credentials and writes local identity", () => {
|
|
124
116
|
const fixture = makeFixture();
|
|
125
117
|
try {
|
|
126
|
-
|
|
118
|
+
fs.writeFileSync(path.join(fixture.dataDir, "credentials.json"), "{\"legacy\":true}\n", "utf8");
|
|
127
119
|
const result = runCli({
|
|
128
120
|
args: ["install", "--host", "claude_code", "--free", "--api-url", "http://127.0.0.1:9"],
|
|
129
121
|
cwd: fixture.project,
|
|
@@ -140,21 +132,15 @@ test("free install with auth writes host config and agent instructions", () => {
|
|
|
140
132
|
assert.equal("ASKTHEW_CLI_TOKEN" in server.env, false);
|
|
141
133
|
assert.equal("ASKTHEW_INSTALL_TOKEN" in server.env, false);
|
|
142
134
|
assert.match(fs.readFileSync(path.join(fixture.project, "CLAUDE.md"), "utf8"), /capture_session_signal/);
|
|
135
|
+
assert.equal(fs.existsSync(path.join(fixture.dataDir, "identity.json")), true);
|
|
143
136
|
}
|
|
144
137
|
finally {
|
|
145
138
|
fs.rmSync(fixture.root, { recursive: true, force: true });
|
|
146
139
|
}
|
|
147
140
|
});
|
|
148
|
-
test("auth status reports
|
|
141
|
+
test("auth status reports missing local identity without pending-code guidance", () => {
|
|
149
142
|
const fixture = makeFixture();
|
|
150
143
|
try {
|
|
151
|
-
fs.writeFileSync(path.join(fixture.dataDir, "config.json"), `${JSON.stringify({
|
|
152
|
-
pendingAuth: {
|
|
153
|
-
email: "founder@example.com",
|
|
154
|
-
requestId: "request_1",
|
|
155
|
-
expiresAt: new Date(Date.now() + 60_000).toISOString(),
|
|
156
|
-
},
|
|
157
|
-
}, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
158
144
|
const result = runCli({
|
|
159
145
|
args: ["auth", "status"],
|
|
160
146
|
cwd: fixture.project,
|
|
@@ -162,31 +148,35 @@ test("auth status reports a pending verification code", () => {
|
|
|
162
148
|
dataDir: fixture.dataDir,
|
|
163
149
|
});
|
|
164
150
|
assert.equal(result.status, 0, result.stderr);
|
|
165
|
-
assert.match(result.stdout, /
|
|
166
|
-
assert.
|
|
151
|
+
assert.match(result.stdout, /No local identity yet/);
|
|
152
|
+
assert.doesNotMatch(result.stdout, /verify --code|Pending code/);
|
|
167
153
|
}
|
|
168
154
|
finally {
|
|
169
155
|
fs.rmSync(fixture.root, { recursive: true, force: true });
|
|
170
156
|
}
|
|
171
157
|
});
|
|
172
|
-
test("auth code commands
|
|
158
|
+
test("removed auth code commands do not issue or verify email codes", () => {
|
|
173
159
|
const fixture = makeFixture();
|
|
174
160
|
try {
|
|
175
|
-
|
|
176
|
-
["auth", "verify", "--code", "123456"],
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
161
|
+
const verify = runCli({
|
|
162
|
+
args: ["auth", "verify", "--code", "123456"],
|
|
163
|
+
cwd: fixture.project,
|
|
164
|
+
home: fixture.home,
|
|
165
|
+
dataDir: fixture.dataDir,
|
|
166
|
+
extraEnv: { ASKTHEW_API_URL: "http://127.0.0.1:9" },
|
|
167
|
+
});
|
|
168
|
+
assert.equal(verify.status, 1);
|
|
169
|
+
assert.match(verify.stderr, /Usage: askthew-mcp auth login/);
|
|
170
|
+
const loginWithCode = runCli({
|
|
171
|
+
args: ["auth", "login", "--email", "founder@example.com", "--code", "123456"],
|
|
172
|
+
cwd: fixture.project,
|
|
173
|
+
home: fixture.home,
|
|
174
|
+
dataDir: fixture.dataDir,
|
|
175
|
+
extraEnv: { ASKTHEW_API_URL: "http://127.0.0.1:9" },
|
|
176
|
+
});
|
|
177
|
+
assert.equal(loginWithCode.status, 0, loginWithCode.stderr);
|
|
178
|
+
assert.match(loginWithCode.stdout, /No email code is required/);
|
|
179
|
+
assert.doesNotMatch(loginWithCode.stdout, /Code sent|Logged in/);
|
|
190
180
|
}
|
|
191
181
|
finally {
|
|
192
182
|
fs.rmSync(fixture.root, { recursive: true, force: true });
|
|
@@ -200,14 +190,6 @@ test("auth login now identifies the local free install without requesting an ema
|
|
|
200
190
|
await withCliEnv(fixture.dataDir, async () => {
|
|
201
191
|
await runAuthCommand(["login", "--email", "ymtest89+test5@gmail.com"], {
|
|
202
192
|
log: (message) => logs.push(message),
|
|
203
|
-
requestMagicLinkCode: async () => {
|
|
204
|
-
calls.push({ type: "unexpected_request" });
|
|
205
|
-
throw new Error("auth login must not request a new code.");
|
|
206
|
-
},
|
|
207
|
-
verifyMagicLinkCode: async () => {
|
|
208
|
-
calls.push({ type: "unexpected_verify" });
|
|
209
|
-
throw new Error("auth login must not verify a code.");
|
|
210
|
-
},
|
|
211
193
|
registerFreeInstall: async ({ identity }) => {
|
|
212
194
|
calls.push({ type: "register", installId: identity.installId, emailClaim: identity.emailClaim });
|
|
213
195
|
return { ok: true, registeredAt: new Date().toISOString() };
|
|
@@ -218,57 +200,10 @@ test("auth login now identifies the local free install without requesting an ema
|
|
|
218
200
|
assert.equal(calls[0].type, "register");
|
|
219
201
|
assert.equal(calls[0].emailClaim, "ymtest89+test5@gmail.com");
|
|
220
202
|
assert.equal(fs.existsSync(identityPath({ ASKTHEW_DATA_DIR: fixture.dataDir })), true);
|
|
221
|
-
assert.equal(fs.existsSync(
|
|
203
|
+
assert.equal(fs.existsSync(path.join(fixture.dataDir, "credentials.json")), false);
|
|
222
204
|
assert.match(logs.join("\n"), /No email code is required/);
|
|
223
205
|
}
|
|
224
206
|
finally {
|
|
225
207
|
fs.rmSync(fixture.root, { recursive: true, force: true });
|
|
226
208
|
}
|
|
227
209
|
});
|
|
228
|
-
test("backwards-compatible auth login --code verifies pending state without requesting a new code", async () => {
|
|
229
|
-
const fixture = makeFixture();
|
|
230
|
-
const calls = [];
|
|
231
|
-
const logs = [];
|
|
232
|
-
try {
|
|
233
|
-
await withCliEnv(fixture.dataDir, async () => {
|
|
234
|
-
writePrivateJson(configPath(), {
|
|
235
|
-
pendingAuth: {
|
|
236
|
-
email: "ymtest89+test5@gmail.com",
|
|
237
|
-
requestId: "22222222-2222-4222-8222-222222222222",
|
|
238
|
-
expiresAt: new Date(Date.now() + 10 * 60_000).toISOString(),
|
|
239
|
-
},
|
|
240
|
-
});
|
|
241
|
-
await runAuthCommand(["login", "--email", "ymtest89+test5@gmail.com", "--code", "150259"], {
|
|
242
|
-
log: (message) => logs.push(message),
|
|
243
|
-
requestMagicLinkCode: async () => {
|
|
244
|
-
calls.push({ type: "unexpected_request" });
|
|
245
|
-
throw new Error("login --code must not request a new code.");
|
|
246
|
-
},
|
|
247
|
-
verifyMagicLinkCode: async (input) => {
|
|
248
|
-
calls.push({ type: "verify", requestId: input.requestId, code: input.code });
|
|
249
|
-
const credentials = {
|
|
250
|
-
email: "ymtest89+test5@gmail.com",
|
|
251
|
-
userId: "user_2",
|
|
252
|
-
cliToken: "cli_token_2",
|
|
253
|
-
cliTokenId: "cli_token_2",
|
|
254
|
-
accountStatus: "new_dormant",
|
|
255
|
-
};
|
|
256
|
-
writePrivateJson(credentialsPath(), credentials);
|
|
257
|
-
return credentials;
|
|
258
|
-
},
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
assert.deepEqual(calls, [
|
|
262
|
-
{
|
|
263
|
-
type: "verify",
|
|
264
|
-
requestId: "22222222-2222-4222-8222-222222222222",
|
|
265
|
-
code: "150259",
|
|
266
|
-
},
|
|
267
|
-
]);
|
|
268
|
-
assert.match(logs.join("\n"), /Using the pending Ask The W login request/);
|
|
269
|
-
assert.match(logs.join("\n"), /Logged in/);
|
|
270
|
-
}
|
|
271
|
-
finally {
|
|
272
|
-
fs.rmSync(fixture.root, { recursive: true, force: true });
|
|
273
|
-
}
|
|
274
|
-
});
|
|
@@ -4,6 +4,7 @@ import fs from "node:fs";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { resolveMcpMode } from "./lib/free-tier-policy.js";
|
|
7
|
+
import { ensureLocalIdentity } from "./lib/local-identity.js";
|
|
7
8
|
function withTempDataDir(fn) {
|
|
8
9
|
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-mode-"));
|
|
9
10
|
try {
|
|
@@ -21,15 +22,15 @@ test("mode resolution prefers paid install tokens", () => {
|
|
|
21
22
|
assert.equal(mode.mode, "paid");
|
|
22
23
|
assert.equal(mode.reason, "workspace_install_token");
|
|
23
24
|
});
|
|
24
|
-
test("mode resolution detects
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
test("mode resolution detects local free install identity", () => {
|
|
26
|
+
withTempDataDir((env) => {
|
|
27
|
+
ensureLocalIdentity({ emailClaim: "founder@example.com", env });
|
|
28
|
+
const mode = resolveMcpMode(env);
|
|
29
|
+
assert.equal(mode.mode, "free");
|
|
30
|
+
assert.equal(mode.reason, "local_install_identity");
|
|
31
|
+
assert.equal(mode.cliCredentials?.userId, mode.cliCredentials?.installId);
|
|
32
|
+
assert.equal(mode.cliCredentials?.email, "founder@example.com");
|
|
29
33
|
});
|
|
30
|
-
assert.equal(mode.mode, "free");
|
|
31
|
-
assert.equal(mode.reason, "cli_free_tier_credentials");
|
|
32
|
-
assert.equal(mode.cliCredentials?.userId, "user_1");
|
|
33
34
|
});
|
|
34
35
|
test("mode resolution distinguishes pending free auth from no identity", () => {
|
|
35
36
|
withTempDataDir((env) => {
|
|
@@ -39,12 +40,12 @@ test("mode resolution distinguishes pending free auth from no identity", () => {
|
|
|
39
40
|
});
|
|
40
41
|
const none = resolveMcpMode(env);
|
|
41
42
|
assert.equal(pending.mode, "free_pending_auth");
|
|
42
|
-
assert.equal(pending.reason, "
|
|
43
|
+
assert.equal(pending.reason, "free_mode_no_identity");
|
|
43
44
|
assert.equal(none.mode, "unauthenticated");
|
|
44
45
|
assert.equal(none.reason, "no_identity");
|
|
45
46
|
});
|
|
46
47
|
});
|
|
47
|
-
test("mode resolution
|
|
48
|
+
test("mode resolution ignores legacy credentials files", () => {
|
|
48
49
|
withTempDataDir((env, dataDir) => {
|
|
49
50
|
fs.writeFileSync(path.join(dataDir, "credentials.json"), "{\"not\":\"credentials\"}\n", "utf8");
|
|
50
51
|
const mode = resolveMcpMode({
|
|
@@ -52,6 +53,6 @@ test("mode resolution marks malformed free credentials as pending auth", () => {
|
|
|
52
53
|
ASKTHEW_FREE_MODE: "1",
|
|
53
54
|
});
|
|
54
55
|
assert.equal(mode.mode, "free_pending_auth");
|
|
55
|
-
assert.equal(mode.reason, "
|
|
56
|
+
assert.equal(mode.reason, "free_mode_no_identity");
|
|
56
57
|
});
|
|
57
58
|
});
|
package/dist/index.test.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from "node:os";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { codingSessionSignalSchema, createAskTheWMcpServer, normalizeInstallTokenInput, redactCodingSessionSignal, redactProvenanceSignal, } from "./index.js";
|
|
7
7
|
import { LocalStore } from "./lib/local-store.js";
|
|
8
|
-
import {
|
|
8
|
+
import { ensureLocalIdentity } from "./lib/local-identity.js";
|
|
9
9
|
function toolResultJson(result) {
|
|
10
10
|
return JSON.parse(result.content[0].text);
|
|
11
11
|
}
|
|
@@ -19,10 +19,11 @@ async function withFreeEnv(fn) {
|
|
|
19
19
|
ASKTHEW_FREE_MODE: process.env.ASKTHEW_FREE_MODE,
|
|
20
20
|
};
|
|
21
21
|
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-free-tools-"));
|
|
22
|
-
process.env.ASKTHEW_CLI_TOKEN = "cli_free_token";
|
|
23
|
-
process.env.ASKTHEW_USER_ID = "local-user";
|
|
24
|
-
process.env.ASKTHEW_CLI_TOKEN_ID = "cli-token-id";
|
|
25
22
|
process.env.ASKTHEW_DATA_DIR = dataDir;
|
|
23
|
+
ensureLocalIdentity({ emailClaim: "founder@example.com" });
|
|
24
|
+
delete process.env.ASKTHEW_CLI_TOKEN;
|
|
25
|
+
delete process.env.ASKTHEW_USER_ID;
|
|
26
|
+
delete process.env.ASKTHEW_CLI_TOKEN_ID;
|
|
26
27
|
delete process.env.ASKTHEW_INSTALL_TOKEN;
|
|
27
28
|
delete process.env.ASKTHEW_FREE_MODE;
|
|
28
29
|
try {
|
|
@@ -83,16 +84,11 @@ async function withInstalledFreeEnv(fn) {
|
|
|
83
84
|
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-installed-free-tools-"));
|
|
84
85
|
process.env.ASKTHEW_FREE_MODE = "1";
|
|
85
86
|
process.env.ASKTHEW_DATA_DIR = dataDir;
|
|
87
|
+
ensureLocalIdentity({ emailClaim: "ymtest89+test5@gmail.com" });
|
|
86
88
|
delete process.env.ASKTHEW_CLI_TOKEN;
|
|
87
89
|
delete process.env.ASKTHEW_USER_ID;
|
|
88
90
|
delete process.env.ASKTHEW_CLI_TOKEN_ID;
|
|
89
91
|
delete process.env.ASKTHEW_INSTALL_TOKEN;
|
|
90
|
-
writePrivateJson(credentialsPath(), {
|
|
91
|
-
email: "ymtest89+test5@gmail.com",
|
|
92
|
-
userId: "local-user",
|
|
93
|
-
cliToken: "cli_free_token",
|
|
94
|
-
cliTokenId: "cli-token-id",
|
|
95
|
-
});
|
|
96
92
|
try {
|
|
97
93
|
return await fn();
|
|
98
94
|
}
|
|
@@ -644,7 +640,8 @@ test("authenticated free mode keeps capture, decisions, and review local without
|
|
|
644
640
|
assert.equal(capture.ok, true);
|
|
645
641
|
assert.match(decision.id, /^d_/);
|
|
646
642
|
assert.equal(review.ok, true);
|
|
647
|
-
assert.equal(calls.length,
|
|
643
|
+
assert.equal(calls.length, 1);
|
|
644
|
+
assert.match(calls[0].url, /\/api\/cli\/v1\/free-installs\/register$/);
|
|
648
645
|
const store = LocalStore.open();
|
|
649
646
|
try {
|
|
650
647
|
const stats = store.stats();
|
|
@@ -686,7 +683,8 @@ test("installed free mode with credential file captures locally even if hosted a
|
|
|
686
683
|
assert.equal(capture.sessionId, "session-installed-free");
|
|
687
684
|
assert.equal("code" in capture, false);
|
|
688
685
|
assert.equal(JSON.stringify(capture).includes("local_only_free_feature"), false);
|
|
689
|
-
assert.equal(calls.length,
|
|
686
|
+
assert.equal(calls.length, 1);
|
|
687
|
+
assert.match(calls[0].url, /\/api\/cli\/v1\/free-installs\/register$/);
|
|
690
688
|
const store = LocalStore.open();
|
|
691
689
|
try {
|
|
692
690
|
const signals = store.listSignals({ sessionId: "session-installed-free", limit: 10 });
|
|
@@ -7,8 +7,7 @@ export interface CliCredentials {
|
|
|
7
7
|
cliTokenId: string;
|
|
8
8
|
apiUrl?: string;
|
|
9
9
|
telemetryOptOut?: boolean;
|
|
10
|
-
|
|
11
|
-
identityKind?: "legacy_token" | "local_install";
|
|
10
|
+
identityKind: "local_install";
|
|
12
11
|
installId?: string;
|
|
13
12
|
localIdentity?: LocalInstallIdentity;
|
|
14
13
|
}
|
|
@@ -1,20 +1,8 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import { credentialsPath, readJsonFile } from "./paths.js";
|
|
3
1
|
import { loadLocalIdentity } from "./local-identity.js";
|
|
4
2
|
function clean(value) {
|
|
5
3
|
return String(value ?? "").trim().replace(/^['"]/, "").replace(/['"]$/, "");
|
|
6
4
|
}
|
|
7
5
|
export function loadCliCredentials(env = process.env) {
|
|
8
|
-
const explicitToken = clean(env.ASKTHEW_CLI_TOKEN);
|
|
9
|
-
if (explicitToken) {
|
|
10
|
-
return {
|
|
11
|
-
userId: clean(env.ASKTHEW_USER_ID) || "local",
|
|
12
|
-
cliToken: explicitToken,
|
|
13
|
-
cliTokenId: clean(env.ASKTHEW_CLI_TOKEN_ID) || "env",
|
|
14
|
-
apiUrl: clean(env.ASKTHEW_API_URL) || undefined,
|
|
15
|
-
telemetryOptOut: env.ASKTHEW_TELEMETRY === "off",
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
6
|
const localIdentity = loadLocalIdentity(env);
|
|
19
7
|
if (localIdentity) {
|
|
20
8
|
return {
|
|
@@ -29,11 +17,7 @@ export function loadCliCredentials(env = process.env) {
|
|
|
29
17
|
localIdentity,
|
|
30
18
|
};
|
|
31
19
|
}
|
|
32
|
-
|
|
33
|
-
if (!creds?.cliToken || !creds.userId || !creds.cliTokenId) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
return creds;
|
|
20
|
+
return null;
|
|
37
21
|
}
|
|
38
22
|
export function resolveMcpMode(env = process.env) {
|
|
39
23
|
const installToken = clean(env.ASKTHEW_INSTALL_TOKEN);
|
|
@@ -49,13 +33,13 @@ export function resolveMcpMode(env = process.env) {
|
|
|
49
33
|
return {
|
|
50
34
|
mode: "free",
|
|
51
35
|
cliCredentials: credentials,
|
|
52
|
-
reason: "
|
|
36
|
+
reason: "local_install_identity",
|
|
53
37
|
};
|
|
54
38
|
}
|
|
55
39
|
if (clean(env.ASKTHEW_FREE_MODE) === "1" || clean(env.ASKTHEW_FREE_MODE).toLowerCase() === "true") {
|
|
56
40
|
return {
|
|
57
41
|
mode: "free_pending_auth",
|
|
58
|
-
reason:
|
|
42
|
+
reason: "free_mode_no_identity",
|
|
59
43
|
};
|
|
60
44
|
}
|
|
61
45
|
return {
|
package/dist/lib/paths.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export declare function askTheWDataDir(env?: NodeJS.ProcessEnv): string;
|
|
2
2
|
export declare function ensureAskTheWDataDir(env?: NodeJS.ProcessEnv): string;
|
|
3
3
|
export declare function localStorePath(env?: NodeJS.ProcessEnv): string;
|
|
4
|
-
export declare function credentialsPath(env?: NodeJS.ProcessEnv): string;
|
|
5
4
|
export declare function identityPath(env?: NodeJS.ProcessEnv): string;
|
|
6
5
|
export declare function configPath(env?: NodeJS.ProcessEnv): string;
|
|
7
6
|
export declare function jsonFallbackStorePath(env?: NodeJS.ProcessEnv): string;
|
package/dist/lib/paths.js
CHANGED
|
@@ -20,9 +20,6 @@ export function ensureAskTheWDataDir(env = process.env) {
|
|
|
20
20
|
export function localStorePath(env = process.env) {
|
|
21
21
|
return path.join(askTheWDataDir(env), "store.sqlite");
|
|
22
22
|
}
|
|
23
|
-
export function credentialsPath(env = process.env) {
|
|
24
|
-
return path.join(askTheWDataDir(env), "credentials.json");
|
|
25
|
-
}
|
|
26
23
|
export function identityPath(env = process.env) {
|
|
27
24
|
return path.join(askTheWDataDir(env), "identity.json");
|
|
28
25
|
}
|
package/dist/lib/telemetry.js
CHANGED
|
@@ -31,13 +31,11 @@ export function buildTelemetryPayload(input) {
|
|
|
31
31
|
platform: `${process.platform}-${process.arch}`,
|
|
32
32
|
node: process.version.replace(/^v/, ""),
|
|
33
33
|
},
|
|
34
|
-
identity:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
: { kind: "legacy_token" },
|
|
34
|
+
identity: {
|
|
35
|
+
kind: "local_install",
|
|
36
|
+
installId: input.credentials.installId,
|
|
37
|
+
emailClaimed: Boolean(input.credentials.email),
|
|
38
|
+
},
|
|
41
39
|
});
|
|
42
40
|
}
|
|
43
41
|
export async function flushTelemetryOutbox(input) {
|
|
@@ -46,7 +44,7 @@ export async function flushTelemetryOutbox(input) {
|
|
|
46
44
|
}
|
|
47
45
|
const fetcher = input.fetchImpl ?? fetch;
|
|
48
46
|
const apiUrl = (input.apiUrl ?? input.credentials.apiUrl ?? process.env.ASKTHEW_API_URL ?? "https://app.askthew.com").replace(/\/$/, "");
|
|
49
|
-
if (input.credentials.
|
|
47
|
+
if (input.credentials.localIdentity) {
|
|
50
48
|
await tryRegisterFreeInstall({
|
|
51
49
|
identity: input.credentials.localIdentity,
|
|
52
50
|
deviceLabel: "askthew-mcp",
|
|
@@ -56,20 +54,18 @@ export async function flushTelemetryOutbox(input) {
|
|
|
56
54
|
let sent = 0;
|
|
57
55
|
for (const row of input.store.listTelemetryOutbox({ undeliveredOnly: true, limit: 20 })) {
|
|
58
56
|
const body = JSON.stringify(row.payload);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
if (!input.credentials.localIdentity) {
|
|
58
|
+
input.store.markTelemetryAttempt(row.id, false);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const signed = signLocalIdentityPayload({ identity: input.credentials.localIdentity, body });
|
|
62
62
|
const response = await fetcher(`${apiUrl}/api/cli/v1/telemetry`, {
|
|
63
63
|
method: "POST",
|
|
64
64
|
headers: {
|
|
65
65
|
"Content-Type": "application/json",
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"X-AskTheW-Timestamp": signed.timestamp,
|
|
70
|
-
"X-AskTheW-Signature": signed.signature,
|
|
71
|
-
}
|
|
72
|
-
: { Authorization: `Bearer ${input.credentials.cliToken}` }),
|
|
66
|
+
"X-AskTheW-Install-Id": input.credentials.localIdentity.installId,
|
|
67
|
+
"X-AskTheW-Timestamp": signed.timestamp,
|
|
68
|
+
"X-AskTheW-Signature": signed.signature,
|
|
73
69
|
},
|
|
74
70
|
body,
|
|
75
71
|
}).catch(() => null);
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { clearPendingAuth, pendingAuth, pendingAuthForEmail, savePendingAuth } from "./lib/auth-pending.js";
|
|
7
|
-
import { configPath } from "./lib/paths.js";
|
|
8
|
-
function withTempEnv(fn) {
|
|
9
|
-
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "askthew-auth-pending-"));
|
|
10
|
-
try {
|
|
11
|
-
return fn({ ASKTHEW_DATA_DIR: dataDir });
|
|
12
|
-
}
|
|
13
|
-
finally {
|
|
14
|
-
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
test("pending auth stores and resolves the request id for the matching email", () => {
|
|
18
|
-
withTempEnv((env) => {
|
|
19
|
-
savePendingAuth({
|
|
20
|
-
email: "Founder@Example.com",
|
|
21
|
-
requestId: "request_1",
|
|
22
|
-
expiresAt: new Date(Date.now() + 60_000).toISOString(),
|
|
23
|
-
telemetryOptOut: true,
|
|
24
|
-
}, env);
|
|
25
|
-
const pending = pendingAuthForEmail("founder@example.com", env);
|
|
26
|
-
assert.equal(pending?.requestId, "request_1");
|
|
27
|
-
assert.equal(pending?.telemetryOptOut, true);
|
|
28
|
-
assert.equal(pendingAuth(env)?.email, "Founder@Example.com");
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
test("pending auth ignores other emails and clears expired requests", () => {
|
|
32
|
-
withTempEnv((env) => {
|
|
33
|
-
savePendingAuth({
|
|
34
|
-
email: "founder@example.com",
|
|
35
|
-
requestId: "request_1",
|
|
36
|
-
expiresAt: new Date(Date.now() - 1_000).toISOString(),
|
|
37
|
-
}, env);
|
|
38
|
-
assert.equal(pendingAuthForEmail("other@example.com", env), null);
|
|
39
|
-
assert.equal(pendingAuthForEmail("founder@example.com", env), null);
|
|
40
|
-
const config = JSON.parse(fs.readFileSync(configPath(env), "utf8"));
|
|
41
|
-
assert.equal("pendingAuth" in config, false);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
test("pending auth clear keeps the config file private and removes only pending auth", () => {
|
|
45
|
-
withTempEnv((env) => {
|
|
46
|
-
savePendingAuth({
|
|
47
|
-
email: "founder@example.com",
|
|
48
|
-
requestId: "request_1",
|
|
49
|
-
expiresAt: new Date(Date.now() + 60_000).toISOString(),
|
|
50
|
-
}, env);
|
|
51
|
-
clearPendingAuth(env);
|
|
52
|
-
const config = JSON.parse(fs.readFileSync(configPath(env), "utf8"));
|
|
53
|
-
assert.equal("pendingAuth" in config, false);
|
|
54
|
-
assert.equal((fs.statSync(configPath(env)).mode & 0o777), 0o600);
|
|
55
|
-
});
|
|
56
|
-
});
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { CliCredentials } from "./free-tier-policy.js";
|
|
2
|
-
export interface MagicLinkClientOptions {
|
|
3
|
-
apiUrl?: string;
|
|
4
|
-
fetchImpl?: typeof fetch;
|
|
5
|
-
}
|
|
6
|
-
export declare function requestMagicLinkCode(input: {
|
|
7
|
-
email: string;
|
|
8
|
-
deviceLabel?: string;
|
|
9
|
-
apiUrl?: string;
|
|
10
|
-
fetchImpl?: typeof fetch;
|
|
11
|
-
}): Promise<{
|
|
12
|
-
requestId: string;
|
|
13
|
-
expiresAt: string;
|
|
14
|
-
devCode?: string;
|
|
15
|
-
}>;
|
|
16
|
-
export declare function verifyMagicLinkCode(input: {
|
|
17
|
-
requestId: string;
|
|
18
|
-
code: string;
|
|
19
|
-
apiUrl?: string;
|
|
20
|
-
fetchImpl?: typeof fetch;
|
|
21
|
-
telemetryOptOut?: boolean;
|
|
22
|
-
}): Promise<CliCredentials>;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { credentialsPath, writePrivateJson } from "./paths.js";
|
|
2
|
-
function baseUrl(apiUrl) {
|
|
3
|
-
return (apiUrl?.trim() || process.env.ASKTHEW_API_URL?.trim() || "https://app.askthew.com").replace(/\/$/, "");
|
|
4
|
-
}
|
|
5
|
-
async function requestJson(route, body, options) {
|
|
6
|
-
const fetcher = options.fetchImpl ?? fetch;
|
|
7
|
-
const response = await fetcher(`${baseUrl(options.apiUrl)}${route}`, {
|
|
8
|
-
method: "POST",
|
|
9
|
-
headers: { "Content-Type": "application/json" },
|
|
10
|
-
body: JSON.stringify(body),
|
|
11
|
-
});
|
|
12
|
-
const payload = await response.json().catch(() => null);
|
|
13
|
-
if (!response.ok) {
|
|
14
|
-
const message = payload && typeof payload === "object" && "error" in payload
|
|
15
|
-
? String(payload.error)
|
|
16
|
-
: "Ask The W auth request failed.";
|
|
17
|
-
throw new Error(message);
|
|
18
|
-
}
|
|
19
|
-
return payload;
|
|
20
|
-
}
|
|
21
|
-
export async function requestMagicLinkCode(input) {
|
|
22
|
-
return requestJson("/api/cli/v1/magic-link/request", {
|
|
23
|
-
email: input.email,
|
|
24
|
-
deviceLabel: input.deviceLabel,
|
|
25
|
-
}, input);
|
|
26
|
-
}
|
|
27
|
-
export async function verifyMagicLinkCode(input) {
|
|
28
|
-
const verified = await requestJson("/api/cli/v1/magic-link/verify", {
|
|
29
|
-
requestId: input.requestId,
|
|
30
|
-
code: input.code,
|
|
31
|
-
}, input);
|
|
32
|
-
const credentials = {
|
|
33
|
-
email: verified.email,
|
|
34
|
-
userId: verified.userId,
|
|
35
|
-
cliToken: verified.cliToken,
|
|
36
|
-
cliTokenId: verified.cliTokenId,
|
|
37
|
-
accountStatus: verified.accountStatus,
|
|
38
|
-
apiUrl: input.apiUrl,
|
|
39
|
-
telemetryOptOut: input.telemetryOptOut,
|
|
40
|
-
};
|
|
41
|
-
writePrivateJson(credentialsPath(), credentials);
|
|
42
|
-
return credentials;
|
|
43
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
type CliConfig = {
|
|
2
|
-
pendingAuth?: {
|
|
3
|
-
email: string;
|
|
4
|
-
requestId: string;
|
|
5
|
-
expiresAt: string;
|
|
6
|
-
telemetryOptOut?: boolean;
|
|
7
|
-
};
|
|
8
|
-
};
|
|
9
|
-
export declare function savePendingAuth(input: NonNullable<CliConfig["pendingAuth"]>, env?: NodeJS.ProcessEnv): void;
|
|
10
|
-
export declare function clearPendingAuth(env?: NodeJS.ProcessEnv): void;
|
|
11
|
-
export declare function pendingAuthForEmail(email: string, env?: NodeJS.ProcessEnv): {
|
|
12
|
-
email: string;
|
|
13
|
-
requestId: string;
|
|
14
|
-
expiresAt: string;
|
|
15
|
-
telemetryOptOut?: boolean;
|
|
16
|
-
} | null;
|
|
17
|
-
export declare function pendingAuth(env?: NodeJS.ProcessEnv): {
|
|
18
|
-
email: string;
|
|
19
|
-
requestId: string;
|
|
20
|
-
expiresAt: string;
|
|
21
|
-
telemetryOptOut?: boolean;
|
|
22
|
-
} | null;
|
|
23
|
-
export {};
|
package/dist/lib/auth-pending.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { configPath, readJsonFile, writePrivateJson } from "./paths.js";
|
|
2
|
-
function loadCliConfig(env = process.env) {
|
|
3
|
-
return readJsonFile(configPath(env)) ?? {};
|
|
4
|
-
}
|
|
5
|
-
export function savePendingAuth(input, env = process.env) {
|
|
6
|
-
const config = loadCliConfig(env);
|
|
7
|
-
writePrivateJson(configPath(env), {
|
|
8
|
-
...config,
|
|
9
|
-
pendingAuth: input,
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
export function clearPendingAuth(env = process.env) {
|
|
13
|
-
const config = loadCliConfig(env);
|
|
14
|
-
if (!config.pendingAuth)
|
|
15
|
-
return;
|
|
16
|
-
const { pendingAuth: _pendingAuth, ...next } = config;
|
|
17
|
-
writePrivateJson(configPath(env), next);
|
|
18
|
-
}
|
|
19
|
-
export function pendingAuthForEmail(email, env = process.env) {
|
|
20
|
-
const pending = pendingAuth(env);
|
|
21
|
-
if (!pending || pending.email.toLowerCase() !== email.toLowerCase()) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
return pending;
|
|
25
|
-
}
|
|
26
|
-
export function pendingAuth(env = process.env) {
|
|
27
|
-
const pending = loadCliConfig(env).pendingAuth;
|
|
28
|
-
if (!pending) {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
if (Number.isFinite(Date.parse(pending.expiresAt)) && Date.parse(pending.expiresAt) <= Date.now()) {
|
|
32
|
-
clearPendingAuth(env);
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
return pending;
|
|
36
|
-
}
|