@extension.dev/mcp 4.0.8 → 4.1.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.1.0
4
+
5
+ The MCP now consumes `@extension.dev/core` for all platform-auth logic
6
+ (core MIGRATION.md phase 2). No tool schema changes, no behavior changes:
7
+ the JSON-string envelopes are byte-compatible and pinned by tests.
8
+
9
+ - New dependency `@extension.dev/core` ^0.2.0: device-code login, credential
10
+ store, and publish client now live there, shared with every other surface.
11
+ - Deleted `src/lib/credentials.ts`, `src/lib/github-device.ts`,
12
+ `src/lib/login-flow.ts` and their migrated tests; `login`, `whoami`,
13
+ `logout`, and `release-promote` import from core.
14
+ - `tools/publish.ts` is a thin adapter over core's `publish()`; the frozen
15
+ PublishAuthError / PublishConfigError / PublishNetworkError / PublishError
16
+ envelopes and the success passthrough are pinned by a new
17
+ `publish-envelope` test.
18
+ - New `core-boundary` regression test: no file under `src/` may redefine the
19
+ credential store or import auth primitives from anywhere but
20
+ `@extension.dev/core`.
21
+ - CI and Release workflows pass `NPM_TOKEN` to the install step (core is
22
+ npm-restricted until the public flip).
23
+
3
24
  ## 4.0.8
4
25
 
5
26
  Tracks the `extension` 4.0.8 suite (the versioning convention: this package's
package/dist/module.js CHANGED
@@ -11,6 +11,7 @@ import cross_spawn from "cross-spawn";
11
11
  import { filterKeysForThisBrowser } from "browser-extension-manifest-fields";
12
12
  import node_net from "node:net";
13
13
  import ws from "ws";
14
+ import { clearCredentials, exchangeAndPersist, fetchLoginConfig, pollForToken, publish, readCredentials, resolveApiBase, resolveToken, safeApiBase, startDeviceCode } from "@extension.dev/core";
14
15
  import { extensionInstall, getManagedBrowsersCacheRoot } from "extension-install";
15
16
  import { execFile } from "node:child_process";
16
17
  import { promisify } from "node:util";
@@ -132,8 +133,6 @@ var publish_namespaceObject = {};
132
133
  __webpack_require__.r(publish_namespaceObject);
133
134
  __webpack_require__.d(publish_namespaceObject, {
134
135
  handler: ()=>publish_handler,
135
- resolveToken: ()=>resolveToken,
136
- safeApiBase: ()=>safeApiBase,
137
136
  schema: ()=>publish_schema
138
137
  });
139
138
  var release_promote_namespaceObject = {};
@@ -179,7 +178,7 @@ __webpack_require__.d(whoami_namespaceObject, {
179
178
  schema: ()=>whoami_schema
180
179
  });
181
180
  var package_namespaceObject = {
182
- rE: "4.0.8"
181
+ rE: "4.1.0"
183
182
  };
184
183
  const schema = {
185
184
  name: "extension_create",
@@ -2341,70 +2340,6 @@ async function dom_inspect_handler(args) {
2341
2340
  if (null != args.timeout) cli.push("--timeout", String(args.timeout));
2342
2341
  return runActVerb(cli, args.projectPath, args.timeout);
2343
2342
  }
2344
- function credentialsPath() {
2345
- if ("win32" === process.platform) {
2346
- const base = process.env.APPDATA || process.env.LOCALAPPDATA || node_path.join(node_os.homedir(), "AppData", "Roaming");
2347
- return node_path.join(base, "extension-dev", "auth.json");
2348
- }
2349
- const xdg = String(process.env.XDG_CONFIG_HOME || "").trim();
2350
- const base = xdg || node_path.join(node_os.homedir(), ".config");
2351
- return node_path.join(base, "extension-dev", "auth.json");
2352
- }
2353
- function readCredentials() {
2354
- try {
2355
- const raw = node_fs.readFileSync(credentialsPath(), "utf8");
2356
- const data = JSON.parse(raw);
2357
- if (!data || "object" != typeof data) return null;
2358
- if (1 !== data.version) return null;
2359
- const token = String(data.token || "").trim();
2360
- if (!token) return null;
2361
- return {
2362
- version: 1,
2363
- token,
2364
- workspaceSlug: String(data.workspaceSlug || ""),
2365
- projectSlug: String(data.projectSlug || ""),
2366
- expiresAt: Number(data.expiresAt || 0),
2367
- api: String(data.api || "")
2368
- };
2369
- } catch {
2370
- return null;
2371
- }
2372
- }
2373
- function writeCredentials(creds) {
2374
- const file = credentialsPath();
2375
- node_fs.mkdirSync(node_path.dirname(file), {
2376
- recursive: true
2377
- });
2378
- node_fs.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", {
2379
- mode: 384
2380
- });
2381
- try {
2382
- node_fs.chmodSync(file, 384);
2383
- } catch {}
2384
- return file;
2385
- }
2386
- function clearCredentials() {
2387
- const file = credentialsPath();
2388
- try {
2389
- node_fs.unlinkSync(file);
2390
- return {
2391
- cleared: true,
2392
- path: file
2393
- };
2394
- } catch {
2395
- return {
2396
- cleared: false,
2397
- path: file
2398
- };
2399
- }
2400
- }
2401
- function readValidCredentials(nowSeconds = Math.floor(Date.now() / 1000)) {
2402
- const creds = readCredentials();
2403
- if (!creds) return null;
2404
- if (creds.expiresAt && creds.expiresAt <= nowSeconds) return null;
2405
- return creds;
2406
- }
2407
- const DEFAULT_API = "https://www.extension.dev";
2408
2343
  const publish_schema = {
2409
2344
  name: "extension_publish",
2410
2345
  description: "Publish a project to extension.dev and return a shareable URL. Auth-gated: requires EXTENSION_DEV_TOKEN (a workspace/project access token) in the environment. Posts to the platform's CLI publish endpoint directly. This is the only tool that talks to the hosted platform rather than the local browser.",
@@ -2433,76 +2368,24 @@ const publish_schema = {
2433
2368
  ]
2434
2369
  }
2435
2370
  };
2436
- function resolveToken() {
2437
- const fromEnv = String(process.env.EXTENSION_DEV_TOKEN || "").trim();
2438
- if (fromEnv) return fromEnv;
2439
- const creds = readValidCredentials();
2440
- return creds?.token ? String(creds.token).trim() : "";
2441
- }
2442
- function safeApiBase(raw) {
2443
- let parsed;
2444
- try {
2445
- parsed = new URL(raw);
2446
- } catch {
2447
- return {
2448
- ok: false,
2449
- message: `Invalid platform URL: ${raw}`
2450
- };
2451
- }
2452
- const isLocalhost = "localhost" === parsed.hostname || "127.0.0.1" === parsed.hostname || "[::1]" === parsed.hostname || "::1" === parsed.hostname;
2453
- if ("https:" !== parsed.protocol && !("http:" === parsed.protocol && isLocalhost)) return {
2454
- ok: false,
2455
- message: `Refusing to send the access token to ${raw}: use https (http is allowed only for localhost).`
2456
- };
2457
- return {
2458
- ok: true,
2459
- base: `${parsed.origin}${parsed.pathname}`.replace(/\/+$/, "")
2460
- };
2461
- }
2462
- function fail(name, message) {
2463
- return JSON.stringify({
2371
+ async function publish_handler(args) {
2372
+ const token = resolveToken();
2373
+ if (!token) return JSON.stringify({
2464
2374
  ok: false,
2465
2375
  error: {
2466
- name,
2467
- message
2376
+ name: "PublishAuthError",
2377
+ message: "No token. Run extension_login, or set EXTENSION_DEV_TOKEN (create one in the extension.dev dashboard)."
2468
2378
  }
2469
2379
  });
2380
+ const result = await publish({
2381
+ ttlHours: args.ttlHours,
2382
+ buildSha: args.buildSha,
2383
+ api: args.api,
2384
+ token
2385
+ });
2386
+ return result.ok ? JSON.stringify(result.data) : JSON.stringify(result);
2470
2387
  }
2471
- async function publish_handler(args) {
2472
- const token = resolveToken();
2473
- if (!token) return fail("PublishAuthError", "No token. Run extension_login, or set EXTENSION_DEV_TOKEN (create one in the extension.dev dashboard).");
2474
- const apiCheck = safeApiBase(String(args.api || process.env.EXTENSION_DEV_API_URL || DEFAULT_API));
2475
- if (!apiCheck.ok) return fail("PublishConfigError", apiCheck.message);
2476
- const url = `${apiCheck.base}/api/cli/publish`;
2477
- const body = {};
2478
- if (null != args.ttlHours) body.ttlHours = Number(args.ttlHours);
2479
- if (args.buildSha) body.buildSha = args.buildSha;
2480
- let res;
2481
- try {
2482
- res = await fetch(url, {
2483
- method: "POST",
2484
- headers: {
2485
- authorization: `Bearer ${token}`,
2486
- "content-type": "application/json"
2487
- },
2488
- body: JSON.stringify(body)
2489
- });
2490
- } catch (err) {
2491
- return fail("PublishNetworkError", `Could not reach ${url}: ${err?.message || err}`);
2492
- }
2493
- const text = await res.text();
2494
- let data;
2495
- try {
2496
- data = JSON.parse(text);
2497
- } catch {
2498
- data = {
2499
- message: text
2500
- };
2501
- }
2502
- if (!res.ok) return fail("PublishError", `publish failed (${res.status}): ${data?.message || text || "unknown error"}`);
2503
- return JSON.stringify(data);
2504
- }
2505
- const release_promote_DEFAULT_API = "https://www.extension.dev";
2388
+ const DEFAULT_API = "https://www.extension.dev";
2506
2389
  const release_promote_schema = {
2507
2390
  name: "extension_release_promote",
2508
2391
  description: "Promote a built extension to a release channel (e.g. stable, preview, beta) on extension.dev, headless. Auth-gated: requires a release token in EXTENSION_DEV_TOKEN (mint and revoke it in the dashboard under project settings -> Access tokens). Posts to the platform's CLI release endpoint; the project is identified by the token. Cutting a version-bump PR is not available headlessly (it writes to your source repo and needs an interactive login).",
@@ -2547,7 +2430,7 @@ const release_promote_schema = {
2547
2430
  ]
2548
2431
  }
2549
2432
  };
2550
- function release_promote_fail(name, message) {
2433
+ function fail(name, message) {
2551
2434
  return JSON.stringify({
2552
2435
  ok: false,
2553
2436
  error: {
@@ -2558,12 +2441,12 @@ function release_promote_fail(name, message) {
2558
2441
  }
2559
2442
  async function release_promote_handler(args) {
2560
2443
  const token = resolveToken();
2561
- if (!token) return release_promote_fail("ReleaseAuthError", "No token. Set EXTENSION_DEV_TOKEN to a release token (create one in the extension.dev dashboard under project settings -> Access tokens), or run extension_login.");
2444
+ if (!token) return fail("ReleaseAuthError", "No token. Set EXTENSION_DEV_TOKEN to a release token (create one in the extension.dev dashboard under project settings -> Access tokens), or run extension_login.");
2562
2445
  const buildId = String(args.buildId || "").trim();
2563
2446
  const channel = String(args.channel || "").trim();
2564
- if (!buildId || !channel) return release_promote_fail("ReleaseInputError", "buildId and channel are required.");
2565
- const apiCheck = safeApiBase(String(args.api || process.env.EXTENSION_DEV_API_URL || release_promote_DEFAULT_API));
2566
- if (!apiCheck.ok) return release_promote_fail("ReleaseConfigError", apiCheck.message);
2447
+ if (!buildId || !channel) return fail("ReleaseInputError", "buildId and channel are required.");
2448
+ const apiCheck = safeApiBase(String(args.api || process.env.EXTENSION_DEV_API_URL || DEFAULT_API));
2449
+ if (!apiCheck.ok) return fail("ReleaseConfigError", apiCheck.message);
2567
2450
  const url = `${apiCheck.base}/api/cli/release/promote`;
2568
2451
  const body = {
2569
2452
  buildId,
@@ -2584,7 +2467,7 @@ async function release_promote_handler(args) {
2584
2467
  body: JSON.stringify(body)
2585
2468
  });
2586
2469
  } catch (err) {
2587
- return release_promote_fail("ReleaseNetworkError", `Could not reach ${url}: ${err?.message || err}`);
2470
+ return fail("ReleaseNetworkError", `Could not reach ${url}: ${err?.message || err}`);
2588
2471
  }
2589
2472
  const text = await res.text();
2590
2473
  let data;
@@ -2595,7 +2478,7 @@ async function release_promote_handler(args) {
2595
2478
  message: text
2596
2479
  };
2597
2480
  }
2598
- if (!res.ok) return release_promote_fail("ReleaseError", `promote failed (${res.status}): ${data?.message || text || "unknown error"}`);
2481
+ if (!res.ok) return fail("ReleaseError", `promote failed (${res.status}): ${data?.message || text || "unknown error"}`);
2599
2482
  return JSON.stringify(data);
2600
2483
  }
2601
2484
  const wait_schema = {
@@ -2893,138 +2776,6 @@ async function add_feature_handler(args) {
2893
2776
  hint: conflicts.length ? `Warning: ${conflicts.length} file(s) already exist and would be overwritten.` : "No conflicts detected. Safe to create all files."
2894
2777
  });
2895
2778
  }
2896
- const GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
2897
- const GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token";
2898
- const DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
2899
- const defaultSleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
2900
- async function startDeviceCode(args) {
2901
- const doFetch = args.fetchImpl ?? fetch;
2902
- const res = await doFetch(GITHUB_DEVICE_CODE_URL, {
2903
- method: "POST",
2904
- headers: {
2905
- accept: "application/json",
2906
- "content-type": "application/json"
2907
- },
2908
- body: JSON.stringify({
2909
- client_id: args.clientId,
2910
- scope: args.scope || "read:user"
2911
- })
2912
- });
2913
- const data = await res.json().catch(()=>({}));
2914
- if (!res.ok || data.error) throw new Error(`GitHub device-code request failed: ${data.error_description || data.error || res.status}`);
2915
- return {
2916
- deviceCode: String(data.device_code || ""),
2917
- userCode: String(data.user_code || ""),
2918
- verificationUri: String(data.verification_uri || "https://github.com/login/device"),
2919
- interval: Number(data.interval || 5),
2920
- expiresIn: Number(data.expires_in || 900)
2921
- };
2922
- }
2923
- async function pollForToken(args) {
2924
- const doFetch = args.fetchImpl ?? fetch;
2925
- const sleep = args.sleepImpl ?? defaultSleep;
2926
- let intervalMs = 1000 * Math.max(1, args.interval);
2927
- const deadline = Date.now() + Math.max(0, args.budgetMs);
2928
- while(Date.now() < deadline){
2929
- await sleep(intervalMs);
2930
- const res = await doFetch(GITHUB_TOKEN_URL, {
2931
- method: "POST",
2932
- headers: {
2933
- accept: "application/json",
2934
- "content-type": "application/json"
2935
- },
2936
- body: JSON.stringify({
2937
- client_id: args.clientId,
2938
- device_code: args.deviceCode,
2939
- grant_type: DEVICE_GRANT_TYPE
2940
- })
2941
- });
2942
- const data = await res.json().catch(()=>({}));
2943
- const token = String(data.access_token || "").trim();
2944
- if (token) return {
2945
- ok: true,
2946
- githubToken: token
2947
- };
2948
- const error = String(data.error || "");
2949
- if ("authorization_pending" === error) continue;
2950
- if ("slow_down" === error) {
2951
- intervalMs = Math.max(intervalMs + 5000, 1000 * Number(data.interval || 0));
2952
- continue;
2953
- }
2954
- if ("expired_token" === error) return {
2955
- ok: false,
2956
- reason: "expired"
2957
- };
2958
- if ("access_denied" === error) return {
2959
- ok: false,
2960
- reason: "denied"
2961
- };
2962
- }
2963
- return {
2964
- ok: false,
2965
- reason: "pending"
2966
- };
2967
- }
2968
- const login_flow_DEFAULT_API = "https://www.extension.dev";
2969
- function resolveApiBase(api) {
2970
- return String(api || process.env.EXTENSION_DEV_API_URL || login_flow_DEFAULT_API).replace(/\/+$/, "");
2971
- }
2972
- async function fetchLoginConfig(apiBase, fetchImpl = fetch) {
2973
- const override = String(process.env.EXTENSION_DEV_GITHUB_CLIENT_ID || "").trim();
2974
- if (override) return {
2975
- clientId: override,
2976
- scope: "read:user"
2977
- };
2978
- const res = await fetchImpl(`${apiBase}/api/cli/login/config`, {
2979
- headers: {
2980
- accept: "application/json"
2981
- }
2982
- });
2983
- if (!res.ok) throw new Error(`Could not fetch login config from ${apiBase} (${res.status}).`);
2984
- const data = await res.json().catch(()=>({}));
2985
- const clientId = String(data.githubClientId || "").trim();
2986
- if (!clientId) throw new Error("Login is not configured on the server (no GitHub client id). Set EXTENSION_DEV_GITHUB_CLIENT_ID to override.");
2987
- return {
2988
- clientId,
2989
- scope: String(data.scope || "read:user")
2990
- };
2991
- }
2992
- async function exchangeAndPersist(args) {
2993
- const doFetch = args.fetchImpl ?? fetch;
2994
- const res = await doFetch(`${args.apiBase}/api/cli/login/exchange`, {
2995
- method: "POST",
2996
- headers: {
2997
- "content-type": "application/json",
2998
- accept: "application/json"
2999
- },
3000
- body: JSON.stringify({
3001
- githubToken: args.githubToken,
3002
- project: args.project
3003
- })
3004
- });
3005
- const text = await res.text();
3006
- let data;
3007
- try {
3008
- data = JSON.parse(text);
3009
- } catch {
3010
- data = {
3011
- message: text
3012
- };
3013
- }
3014
- if (!res.ok) throw new Error(`Login exchange failed (${res.status}): ${data.message || "unknown error"}`);
3015
- const token = String(data.token || "").trim();
3016
- if (!token) throw new Error("Login exchange returned no token.");
3017
- const creds = {
3018
- version: 1,
3019
- token,
3020
- workspaceSlug: String(data.workspaceSlug || ""),
3021
- projectSlug: String(data.projectSlug || ""),
3022
- expiresAt: Number(data.expiresAt || 0),
3023
- api: args.apiBase
3024
- };
3025
- writeCredentials(creds);
3026
- return creds;
3027
- }
3028
2779
  const login_schema = {
3029
2780
  name: "extension_login",
3030
2781
  description: "Authenticate to extension.dev via GitHub device-code and store a project-scoped access token locally so extension_publish can use it. Two-phase: call with `project` to get a code + URL for the user to authorize, then call again with the returned `deviceCode` to finish. Never returns the token. This is the only tool besides extension_publish that talks to the hosted platform.",
@@ -24,23 +24,6 @@ export declare const schema: {
24
24
  required: string[];
25
25
  };
26
26
  };
27
- /** Resolve the access token: EXTENSION_DEV_TOKEN env first, then the login creds file. */
28
- export declare function resolveToken(): string;
29
- /**
30
- * Validate the platform base URL BEFORE we attach a bearer token to a request to
31
- * it. SECURITY: the `api` arg / EXTENSION_DEV_API_URL is operator-supplied, but a
32
- * hostile value (e.g. via prompt-injection in the client) could redirect the
33
- * token to an attacker. The access token must never leave over plaintext or go to
34
- * an arbitrary scheme, so we require https -- allowing http only for localhost
35
- * dev. Returns the normalized base (no trailing slash) or an error message.
36
- */
37
- export declare function safeApiBase(raw: string): {
38
- ok: true;
39
- base: string;
40
- } | {
41
- ok: false;
42
- message: string;
43
- };
44
27
  export declare function handler(args: {
45
28
  projectPath: string;
46
29
  ttlHours?: number;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@extension.dev/mcp",
3
3
  "type": "module",
4
- "version": "4.0.8",
4
+ "version": "4.1.0",
5
5
  "description": "MCP server that lets AI agents build, run, inspect, and publish browser extensions. 26 tools for scaffolding, live DOM inspection, log streaming, and store-ready builds across Chrome, Edge, and Firefox.",
6
6
  "license": "MIT",
7
7
  "author": {
@@ -62,6 +62,7 @@
62
62
  "extension.dev"
63
63
  ],
64
64
  "dependencies": {
65
+ "@extension.dev/core": "^0.2.0",
65
66
  "@modelcontextprotocol/sdk": "^1.12.1",
66
67
  "browser-extension-manifest-fields": "^2.2.9",
67
68
  "cross-spawn": "^7.0.6",
@@ -1,24 +0,0 @@
1
- export interface StoredCredentials {
2
- version: 1;
3
- /** The HMAC access token the publish path sends as `Bearer`. */
4
- token: string;
5
- workspaceSlug: string;
6
- projectSlug: string;
7
- /** Token expiry as unix epoch seconds (0 if unknown). */
8
- expiresAt: number;
9
- /** Platform base URL the token was minted against. */
10
- api: string;
11
- }
12
- export declare function credentialsPath(): string;
13
- export declare function readCredentials(): StoredCredentials | null;
14
- export declare function writeCredentials(creds: StoredCredentials): string;
15
- export declare function clearCredentials(): {
16
- cleared: boolean;
17
- path: string;
18
- };
19
- /**
20
- * Return stored credentials only if the token has not expired. Used by the
21
- * publish token resolution so an expired local token falls through cleanly to
22
- * "no token" instead of producing a 401 from the platform.
23
- */
24
- export declare function readValidCredentials(nowSeconds?: number): StoredCredentials | null;
@@ -1,40 +0,0 @@
1
- export interface DeviceCodeStart {
2
- deviceCode: string;
3
- userCode: string;
4
- verificationUri: string;
5
- /** Minimum seconds to wait between polls. */
6
- interval: number;
7
- /** Seconds until the device code expires. */
8
- expiresIn: number;
9
- }
10
- export type PollResult = {
11
- ok: true;
12
- githubToken: string;
13
- } | {
14
- ok: false;
15
- reason: "pending" | "expired" | "denied";
16
- error?: string;
17
- };
18
- type FetchImpl = typeof fetch;
19
- type SleepImpl = (ms: number) => Promise<void>;
20
- export declare function startDeviceCode(args: {
21
- clientId: string;
22
- scope?: string;
23
- fetchImpl?: FetchImpl;
24
- }): Promise<DeviceCodeStart>;
25
- /**
26
- * Poll for the access token until the budget elapses. A budget shorter than
27
- * the device-code lifetime lets a caller (e.g. an MCP tool that must return
28
- * promptly) poll in bounded slices and report "pending" between calls; a long
29
- * budget (a blocking CLI) waits the whole time. Returns `pending` only on
30
- * budget exhaustion; `expired`/`denied` are terminal.
31
- */
32
- export declare function pollForToken(args: {
33
- clientId: string;
34
- deviceCode: string;
35
- interval: number;
36
- budgetMs: number;
37
- fetchImpl?: FetchImpl;
38
- sleepImpl?: SleepImpl;
39
- }): Promise<PollResult>;
40
- export {};
@@ -1,25 +0,0 @@
1
- import { type StoredCredentials } from "./credentials";
2
- type FetchImpl = typeof fetch;
3
- export declare function resolveApiBase(api?: string): string;
4
- export interface LoginConfig {
5
- clientId: string;
6
- scope: string;
7
- }
8
- /**
9
- * Resolve the GitHub OAuth client id the device flow authenticates against.
10
- * EXTENSION_DEV_GITHUB_CLIENT_ID overrides (useful when pointing at a self-
11
- * hosted platform); otherwise it comes from the platform's public config.
12
- */
13
- export declare function fetchLoginConfig(apiBase: string, fetchImpl?: FetchImpl): Promise<LoginConfig>;
14
- /**
15
- * Trade a verified GitHub user token for a project-scoped access token and
16
- * write it to the local credentials file. Returns the stored credentials
17
- * (token included for the caller's in-memory use; never logged).
18
- */
19
- export declare function exchangeAndPersist(args: {
20
- apiBase: string;
21
- githubToken: string;
22
- project: string;
23
- fetchImpl?: FetchImpl;
24
- }): Promise<StoredCredentials>;
25
- export {};