@extension.dev/mcp 6.6.0 → 7.0.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +35 -0
- package/README.md +5 -5
- package/dist/module.js +58 -280
- package/dist/src/lib/credentials.d.ts +1 -1
- package/dist/src/lib/login-flow.d.ts +0 -10
- package/dist/src/tools/preview-web.d.ts +0 -12
- package/extensions/live-preview/chromium/action/index.css +1 -1
- package/extensions/live-preview/chromium/action/index.js +1 -1
- package/extensions/live-preview/chromium/background/service_worker.js +4 -4
- package/extensions/live-preview/chromium/manifest.json +0 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/dist/src/lib/github-device.d.ts +0 -31
package/dist/module.js
CHANGED
|
@@ -629,7 +629,7 @@ __webpack_require__.d(whoami_namespaceObject, {
|
|
|
629
629
|
handler: ()=>whoami_handler,
|
|
630
630
|
schema: ()=>whoami_schema
|
|
631
631
|
});
|
|
632
|
-
var package_namespaceObject = JSON.parse('{"rE":"
|
|
632
|
+
var package_namespaceObject = JSON.parse('{"rE":"7.0.0","El":{"OP":"4.0.16-canary.1784889479.74e12044"}}');
|
|
633
633
|
function credentialsPath() {
|
|
634
634
|
if ("win32" === process.platform) {
|
|
635
635
|
const base = process.env.APPDATA || process.env.LOCALAPPDATA || node_path.join(node_os.homedir(), "AppData", "Roaming");
|
|
@@ -647,7 +647,7 @@ function readCredentials() {
|
|
|
647
647
|
if (1 !== data.version) return null;
|
|
648
648
|
const token = String(data.token || "").trim();
|
|
649
649
|
if (!token) return null;
|
|
650
|
-
const provider = "extensiondev" === data.provider
|
|
650
|
+
const provider = "extensiondev" === data.provider ? data.provider : void 0;
|
|
651
651
|
return {
|
|
652
652
|
version: 1,
|
|
653
653
|
token,
|
|
@@ -735,15 +735,6 @@ function safeApiBase(raw) {
|
|
|
735
735
|
};
|
|
736
736
|
}
|
|
737
737
|
async function fetchLoginConfig(apiBase, fetchImpl = fetch) {
|
|
738
|
-
const override = String(process.env.EXTENSION_DEV_GITHUB_CLIENT_ID || "").trim();
|
|
739
|
-
if (override) return {
|
|
740
|
-
provider: "github",
|
|
741
|
-
clientId: override,
|
|
742
|
-
scope: "read:user",
|
|
743
|
-
deviceCodeUrl: "/api/cli/device/code",
|
|
744
|
-
deviceTokenUrl: "/api/cli/device/token",
|
|
745
|
-
verificationUri: "https://github.com/login/device"
|
|
746
|
-
};
|
|
747
738
|
const res = await fetchImpl(`${apiBase}/api/cli/login/config`, {
|
|
748
739
|
headers: {
|
|
749
740
|
accept: "application/json"
|
|
@@ -751,16 +742,10 @@ async function fetchLoginConfig(apiBase, fetchImpl = fetch) {
|
|
|
751
742
|
});
|
|
752
743
|
if (!res.ok) throw new Error(`Could not fetch login config from ${apiBase} (${res.status}).`);
|
|
753
744
|
const data = await res.json().catch(()=>({}));
|
|
754
|
-
const provider = "extensiondev" === data.provider ? "extensiondev" : "github";
|
|
755
|
-
const clientId = String(data.githubClientId || "").trim();
|
|
756
|
-
if ("github" === provider && !clientId) throw new Error("Login is not configured on the server (no GitHub client id). Set EXTENSION_DEV_GITHUB_CLIENT_ID to override.");
|
|
757
745
|
return {
|
|
758
|
-
provider,
|
|
759
|
-
clientId,
|
|
760
|
-
scope: String(data.scope || "read:user"),
|
|
761
746
|
deviceCodeUrl: String(data.deviceCodeUrl || "/api/cli/device/code"),
|
|
762
747
|
deviceTokenUrl: String(data.deviceTokenUrl || "/api/cli/device/token"),
|
|
763
|
-
verificationUri: String(data.verificationUri || "
|
|
748
|
+
verificationUri: String(data.verificationUri || `${apiBase.replace(/\/+$/, "")}/device`)
|
|
764
749
|
};
|
|
765
750
|
}
|
|
766
751
|
function persistTokenResponse(args) {
|
|
@@ -773,40 +758,11 @@ function persistTokenResponse(args) {
|
|
|
773
758
|
projectSlug: String(args.data.projectSlug || ""),
|
|
774
759
|
expiresAt: Number(args.data.expiresAt || 0),
|
|
775
760
|
api: args.apiBase,
|
|
776
|
-
provider:
|
|
761
|
+
provider: "extensiondev"
|
|
777
762
|
};
|
|
778
763
|
writeCredentials(creds);
|
|
779
764
|
return creds;
|
|
780
765
|
}
|
|
781
|
-
async function exchangeAndPersist(args) {
|
|
782
|
-
const doFetch = args.fetchImpl ?? fetch;
|
|
783
|
-
const res = await doFetch(`${args.apiBase}/api/cli/login/exchange`, {
|
|
784
|
-
method: "POST",
|
|
785
|
-
headers: {
|
|
786
|
-
"content-type": "application/json",
|
|
787
|
-
accept: "application/json"
|
|
788
|
-
},
|
|
789
|
-
body: JSON.stringify({
|
|
790
|
-
githubToken: args.githubToken,
|
|
791
|
-
project: args.project
|
|
792
|
-
})
|
|
793
|
-
});
|
|
794
|
-
const text = await res.text();
|
|
795
|
-
let data;
|
|
796
|
-
try {
|
|
797
|
-
data = JSON.parse(text);
|
|
798
|
-
} catch {
|
|
799
|
-
data = {
|
|
800
|
-
message: text
|
|
801
|
-
};
|
|
802
|
-
}
|
|
803
|
-
if (!res.ok) throw new Error(`Login exchange failed (${res.status}): ${data.message || "unknown error"}`);
|
|
804
|
-
return persistTokenResponse({
|
|
805
|
-
apiBase: args.apiBase,
|
|
806
|
-
data,
|
|
807
|
-
provider: "github"
|
|
808
|
-
});
|
|
809
|
-
}
|
|
810
766
|
function _define_property(obj, key, value) {
|
|
811
767
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
812
768
|
value: value,
|
|
@@ -2006,7 +1962,7 @@ function materializeCarrier(projectPath, browser) {
|
|
|
2006
1962
|
...ignored ? {
|
|
2007
1963
|
gitignored: ignored
|
|
2008
1964
|
} : {},
|
|
2009
|
-
note: "Live-preview carrier placed in ./extensions; Extension.js loads it as a companion beside your extension. Open https://
|
|
1965
|
+
note: "Live-preview carrier placed in ./extensions; Extension.js loads it as a companion beside your extension. Open https://preview.extension.dev/?session=live in the dev browser (any http://localhost origin works too) to watch the session's real-lane chrome.* trace on the Trace tab. It is a debug companion, never part of a release: extension_stop and extension_build remove it again" + (ignored ? `, and ${ignored} was added to .gitignore.` : "."),
|
|
2010
1966
|
limitations: [
|
|
2011
1967
|
"The trace shows calls a PAGE bridges to the carrier. Your extension's own chrome.* calls run directly in its contexts and never cross the carrier, so they do not appear.",
|
|
2012
1968
|
"Bridged calls run under the CARRIER's identity, not your extension's. The preview assumes a single active guest and does not namespace per-extension state, so storage, action/badge state, messaging delivery, offscreen documents and relative script paths belong to the carrier. Rows affected are badged carrier-scoped in the Trace tab.",
|
|
@@ -2016,7 +1972,7 @@ function materializeCarrier(projectPath, browser) {
|
|
|
2016
1972
|
...carrierId ? {
|
|
2017
1973
|
bridgeProtocol: {
|
|
2018
1974
|
carrierExtensionId: carrierId,
|
|
2019
|
-
allowedOrigins: "https://
|
|
1975
|
+
allowedOrigins: "https://preview.extension.dev, https://intelligence.extension.dev, https://themes.extension.dev, http://localhost/*, http://127.0.0.1/*",
|
|
2020
1976
|
howTo: "From a page on an allowed origin, register your guest once with a 'session' message (it declares the permissions the carrier enforces), then send 'bridge' messages to run chrome.* for real; each one streams into the Trace tab. Use the EXACT dotted wire names the bridge dispatcher accepts: storage is storage.get/set/remove/clear with the AREA AS AN ARGUMENT, NOT storage.local.get.",
|
|
2021
1977
|
example: [
|
|
2022
1978
|
`const id = '${carrierId}'`,
|
|
@@ -2693,7 +2649,7 @@ const dev_schema = {
|
|
|
2693
2649
|
carrier: {
|
|
2694
2650
|
type: "boolean",
|
|
2695
2651
|
default: false,
|
|
2696
|
-
description: "Load the bundled Extension.dev Live Preview carrier beside your extension (Chromium-family browsers only). It is placed in the project's ./extensions folder, which Extension.js auto-loads; allowlisted pages (
|
|
2652
|
+
description: "Load the bundled Extension.dev Live Preview carrier beside your extension (Chromium-family browsers only). It is placed in the project's ./extensions folder, which Extension.js auto-loads; allowlisted pages (preview.extension.dev, localhost) can then pair with the session and stream its real-lane chrome.* trace. Writes extensions/extension-dev-live-preview/ into the project, gitignores it, and takes it back out on extension_stop or extension_build: it is a debug companion, never part of a release."
|
|
2697
2653
|
}
|
|
2698
2654
|
},
|
|
2699
2655
|
required: [
|
|
@@ -4573,23 +4529,13 @@ function recordSharedPreview(projectPath, entry) {
|
|
|
4573
4529
|
} : {}
|
|
4574
4530
|
};
|
|
4575
4531
|
}
|
|
4576
|
-
const DEFAULT_INSPECT_URL = "http://localhost:3106";
|
|
4577
4532
|
const DEFAULT_PREVIEW_DEV_URL = "http://localhost:3110";
|
|
4578
|
-
const
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
label: "preview.extension.dev"
|
|
4585
|
-
},
|
|
4586
|
-
inspect: {
|
|
4587
|
-
defaultOrigin: DEFAULT_INSPECT_URL,
|
|
4588
|
-
scheme: (encoded)=>`inspect://path/${encoded}`,
|
|
4589
|
-
fetchPath: "/__inspect/fetch",
|
|
4590
|
-
devCommand: "pnpm --filter inspect.extension.dev dev",
|
|
4591
|
-
label: "inspect.extension.dev"
|
|
4592
|
-
}
|
|
4533
|
+
const SURFACE = {
|
|
4534
|
+
defaultOrigin: DEFAULT_PREVIEW_DEV_URL,
|
|
4535
|
+
scheme: (encoded)=>`preview://build/${encoded}`,
|
|
4536
|
+
fetchPath: "/__preview/fetch",
|
|
4537
|
+
devCommand: "pnpm --filter preview.extension.dev dev",
|
|
4538
|
+
label: "preview.extension.dev"
|
|
4593
4539
|
};
|
|
4594
4540
|
async function buildShare(projectPath, distDir, manifest, browser) {
|
|
4595
4541
|
const result = await uploadPreview({
|
|
@@ -4674,7 +4620,7 @@ function detectSurfaces(manifest) {
|
|
|
4674
4620
|
}
|
|
4675
4621
|
const preview_web_schema = {
|
|
4676
4622
|
name: "extension_preview_web",
|
|
4677
|
-
description: "Preview an in-progress extension in the web emulator (no real browser). Builds the project (unless build:false), points preview.extension.dev at the dist directory over the dev-only preview://build scheme, and returns a deep link plus a loadability check against its dev server. preview is the author's
|
|
4623
|
+
description: "Preview an in-progress extension in the web emulator (no real browser). Builds the project (unless build:false), points preview.extension.dev at the dist directory over the dev-only preview://build scheme, and returns a deep link plus a loadability check against its dev server. preview.extension.dev is the author's door for a local build: it renders YOUR build and carries the Emulated/Real lane toggle and the Trace tab. Pass share:true to UPLOAD the dist you just built and get back a public link (share.previewUrl) that renders those exact bytes for anyone who opens it, with no install and no dev server, and that also serves the whole build as a downloadable zip. Sharing needs a token scoped to an existing extension.dev workspace and project (extension_login or EXTENSION_DEV_TOKEN, valid up to 7 days), so a local folder with no project on the platform cannot share. Use share:true whenever the build has to reach someone who is not at this machine, since the plain deepLink only resolves locally. Shared links do not disappear when this response scrolls away: extension_shares lists every link this token has shared and revokes any of them.",
|
|
4678
4624
|
inputSchema: {
|
|
4679
4625
|
type: "object",
|
|
4680
4626
|
properties: {
|
|
@@ -4698,7 +4644,7 @@ const preview_web_schema = {
|
|
|
4698
4644
|
"safari"
|
|
4699
4645
|
],
|
|
4700
4646
|
default: "chrome",
|
|
4701
|
-
description: "Which dist/<browser> output to preview.
|
|
4647
|
+
description: "Which dist/<browser> output to preview. The emulator renders it as mocked Chrome regardless."
|
|
4702
4648
|
},
|
|
4703
4649
|
build: {
|
|
4704
4650
|
type: "boolean",
|
|
@@ -4709,22 +4655,9 @@ const preview_web_schema = {
|
|
|
4709
4655
|
type: "string",
|
|
4710
4656
|
description: "Preview this built directory directly instead of resolving dist/<browser> under projectPath. Implies build:false."
|
|
4711
4657
|
},
|
|
4712
|
-
surface: {
|
|
4713
|
-
type: "string",
|
|
4714
|
-
enum: [
|
|
4715
|
-
"preview",
|
|
4716
|
-
"inspect"
|
|
4717
|
-
],
|
|
4718
|
-
default: "preview",
|
|
4719
|
-
description: "Which front door renders the build. \"preview\" (default) is preview.extension.dev, the author's door: your own in-progress build, with the Emulated/Real lane toggle and the Trace tab. \"inspect\" is inspect.extension.dev, the evaluator's door."
|
|
4720
|
-
},
|
|
4721
4658
|
hostUrl: {
|
|
4722
4659
|
type: "string",
|
|
4723
|
-
description: "Origin of the running dev server
|
|
4724
|
-
},
|
|
4725
|
-
inspectUrl: {
|
|
4726
|
-
type: "string",
|
|
4727
|
-
description: "Deprecated alias for hostUrl, kept for callers written before the preview surface existed. Only consulted when surface is \"inspect\"."
|
|
4660
|
+
description: "Origin of the running preview.extension.dev dev server (default http://localhost:3110)."
|
|
4728
4661
|
},
|
|
4729
4662
|
probe: {
|
|
4730
4663
|
type: "boolean",
|
|
@@ -4766,9 +4699,7 @@ const preview_web_schema = {
|
|
|
4766
4699
|
};
|
|
4767
4700
|
async function preview_web_handler(args) {
|
|
4768
4701
|
const browser = args.browser ?? "chrome";
|
|
4769
|
-
const
|
|
4770
|
-
const surface = SURFACES[surfaceKey];
|
|
4771
|
-
const hostBase = (args.hostUrl ?? ("inspect" === surfaceKey ? args.inspectUrl : void 0) ?? surface.defaultOrigin).replace(/\/+$/, "");
|
|
4702
|
+
const hostBase = (args.hostUrl ?? SURFACE.defaultOrigin).replace(/\/+$/, "");
|
|
4772
4703
|
const shouldBuild = args.distPath ? false : false !== args.build;
|
|
4773
4704
|
let buildResult = null;
|
|
4774
4705
|
if (shouldBuild) {
|
|
@@ -4811,12 +4742,11 @@ async function preview_web_handler(args) {
|
|
|
4811
4742
|
});
|
|
4812
4743
|
}
|
|
4813
4744
|
const encoded = Buffer.from(distDir).toString("base64url");
|
|
4814
|
-
const internalUrl =
|
|
4745
|
+
const internalUrl = SURFACE.scheme(encoded);
|
|
4815
4746
|
const deepLink = `${hostBase}/?url=${encodeURIComponent(internalUrl)}`;
|
|
4816
4747
|
const result = {
|
|
4817
4748
|
ok: true,
|
|
4818
4749
|
deepLink,
|
|
4819
|
-
surface: surfaceKey,
|
|
4820
4750
|
distDir,
|
|
4821
4751
|
manifest: {
|
|
4822
4752
|
name: manifest.name ?? node_path.basename(distDir),
|
|
@@ -4829,7 +4759,7 @@ async function preview_web_handler(args) {
|
|
|
4829
4759
|
} : {
|
|
4830
4760
|
built: false
|
|
4831
4761
|
},
|
|
4832
|
-
hint: `Open deepLink in a browser to see the extension render in ${
|
|
4762
|
+
hint: `Open deepLink in a browser to see the extension render in ${SURFACE.label}'s emulator. It must be running (${SURFACE.devCommand}). Once it renders, the Trace tab shows every chrome.* call it makes, and the lane toggle switches between the emulated backend and a real carrier-equipped browser.`
|
|
4833
4763
|
};
|
|
4834
4764
|
if (args.open) {
|
|
4835
4765
|
const sessionBrowser = args.openIn ?? browser;
|
|
@@ -4849,7 +4779,7 @@ async function preview_web_handler(args) {
|
|
|
4849
4779
|
}
|
|
4850
4780
|
if (args.share) result.share = await buildShare(args.projectPath, distDir, manifest, browser);
|
|
4851
4781
|
if (false === args.probe) return JSON.stringify(result);
|
|
4852
|
-
const probeUrl = `${hostBase}${
|
|
4782
|
+
const probeUrl = `${hostBase}${SURFACE.fetchPath}?url=${encodeURIComponent(internalUrl)}`;
|
|
4853
4783
|
try {
|
|
4854
4784
|
const res = await fetch(probeUrl, {
|
|
4855
4785
|
headers: {
|
|
@@ -4864,7 +4794,7 @@ async function preview_web_handler(args) {
|
|
|
4864
4794
|
probe: {
|
|
4865
4795
|
status: res.status,
|
|
4866
4796
|
contentType,
|
|
4867
|
-
note: `${
|
|
4797
|
+
note: `${SURFACE.label} answered but not with a preview payload. On the deployed host ${SURFACE.fetchPath} does not exist (dev-only); run a local dev server (${SURFACE.devCommand}) to use web preview.`
|
|
4868
4798
|
}
|
|
4869
4799
|
});
|
|
4870
4800
|
const payload = await res.json();
|
|
@@ -4886,7 +4816,7 @@ async function preview_web_handler(args) {
|
|
|
4886
4816
|
previewLoadable: false,
|
|
4887
4817
|
probe: {
|
|
4888
4818
|
error: err instanceof Error ? err.message : String(err),
|
|
4889
|
-
note: `Could not reach ${
|
|
4819
|
+
note: `Could not reach ${SURFACE.label} at ${hostBase}. Start it with '${SURFACE.devCommand}', then open deepLink.`
|
|
4890
4820
|
}
|
|
4891
4821
|
});
|
|
4892
4822
|
}
|
|
@@ -10066,78 +9996,6 @@ async function add_feature_handler(args) {
|
|
|
10066
9996
|
hint: conflicts.length ? `Warning: ${conflicts.length} file(s) already exist and would be overwritten.` : "No conflicts detected. Safe to create all files."
|
|
10067
9997
|
});
|
|
10068
9998
|
}
|
|
10069
|
-
const GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
10070
|
-
const GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
10071
|
-
const DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
10072
|
-
const defaultSleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
10073
|
-
async function startDeviceCode(args) {
|
|
10074
|
-
const doFetch = args.fetchImpl ?? fetch;
|
|
10075
|
-
const res = await doFetch(GITHUB_DEVICE_CODE_URL, {
|
|
10076
|
-
method: "POST",
|
|
10077
|
-
headers: {
|
|
10078
|
-
accept: "application/json",
|
|
10079
|
-
"content-type": "application/json"
|
|
10080
|
-
},
|
|
10081
|
-
body: JSON.stringify({
|
|
10082
|
-
client_id: args.clientId,
|
|
10083
|
-
scope: args.scope || "read:user"
|
|
10084
|
-
})
|
|
10085
|
-
});
|
|
10086
|
-
const data = await res.json().catch(()=>({}));
|
|
10087
|
-
if (!res.ok || data.error) throw new Error(`GitHub device-code request failed: ${data.error_description || data.error || res.status}`);
|
|
10088
|
-
return {
|
|
10089
|
-
deviceCode: String(data.device_code || ""),
|
|
10090
|
-
userCode: String(data.user_code || ""),
|
|
10091
|
-
verificationUri: String(data.verification_uri || "https://github.com/login/device"),
|
|
10092
|
-
interval: Number(data.interval || 5),
|
|
10093
|
-
expiresIn: Number(data.expires_in || 900)
|
|
10094
|
-
};
|
|
10095
|
-
}
|
|
10096
|
-
async function pollForToken(args) {
|
|
10097
|
-
const doFetch = args.fetchImpl ?? fetch;
|
|
10098
|
-
const sleep = args.sleepImpl ?? defaultSleep;
|
|
10099
|
-
let intervalMs = 1000 * Math.max(1, args.interval);
|
|
10100
|
-
const deadline = Date.now() + Math.max(0, args.budgetMs);
|
|
10101
|
-
while(Date.now() < deadline){
|
|
10102
|
-
await sleep(intervalMs);
|
|
10103
|
-
const res = await doFetch(GITHUB_TOKEN_URL, {
|
|
10104
|
-
method: "POST",
|
|
10105
|
-
headers: {
|
|
10106
|
-
accept: "application/json",
|
|
10107
|
-
"content-type": "application/json"
|
|
10108
|
-
},
|
|
10109
|
-
body: JSON.stringify({
|
|
10110
|
-
client_id: args.clientId,
|
|
10111
|
-
device_code: args.deviceCode,
|
|
10112
|
-
grant_type: DEVICE_GRANT_TYPE
|
|
10113
|
-
})
|
|
10114
|
-
});
|
|
10115
|
-
const data = await res.json().catch(()=>({}));
|
|
10116
|
-
const token = String(data.access_token || "").trim();
|
|
10117
|
-
if (token) return {
|
|
10118
|
-
ok: true,
|
|
10119
|
-
githubToken: token
|
|
10120
|
-
};
|
|
10121
|
-
const error = String(data.error || "");
|
|
10122
|
-
if ("authorization_pending" === error) continue;
|
|
10123
|
-
if ("slow_down" === error) {
|
|
10124
|
-
intervalMs = Math.max(intervalMs + 5000, 1000 * Number(data.interval || 0));
|
|
10125
|
-
continue;
|
|
10126
|
-
}
|
|
10127
|
-
if ("expired_token" === error) return {
|
|
10128
|
-
ok: false,
|
|
10129
|
-
reason: "expired"
|
|
10130
|
-
};
|
|
10131
|
-
if ("access_denied" === error) return {
|
|
10132
|
-
ok: false,
|
|
10133
|
-
reason: "denied"
|
|
10134
|
-
};
|
|
10135
|
-
}
|
|
10136
|
-
return {
|
|
10137
|
-
ok: false,
|
|
10138
|
-
reason: "pending"
|
|
10139
|
-
};
|
|
10140
|
-
}
|
|
10141
9999
|
async function requestDeviceCode(args) {
|
|
10142
10000
|
const doFetch = args.fetchImpl ?? fetch;
|
|
10143
10001
|
const res = await doFetch(`${args.apiBase}${args.path}`, {
|
|
@@ -10201,8 +10059,7 @@ async function pollDeviceToken(args) {
|
|
|
10201
10059
|
if (res.ok && data.token) {
|
|
10202
10060
|
const creds = persistTokenResponse({
|
|
10203
10061
|
apiBase: args.apiBase,
|
|
10204
|
-
data
|
|
10205
|
-
provider: "extensiondev"
|
|
10062
|
+
data
|
|
10206
10063
|
});
|
|
10207
10064
|
return {
|
|
10208
10065
|
ok: true,
|
|
@@ -10233,7 +10090,7 @@ async function pollDeviceToken(args) {
|
|
|
10233
10090
|
}
|
|
10234
10091
|
const login_schema = {
|
|
10235
10092
|
name: "extension_login",
|
|
10236
|
-
description: "Authenticate to extension.dev 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.
|
|
10093
|
+
description: "Authenticate to extension.dev 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. You authorize at extension.dev/device; GitHub federation happens server-side, so no GitHub token ever lands on this machine. Never returns the token. Minted tokens live at most 7 days (server-enforced), so CI pipelines must re-mint before expiry on the console's Access tokens page (project settings -> Access tokens). This is the only tool besides extension_publish that talks to the hosted platform.",
|
|
10237
10094
|
inputSchema: {
|
|
10238
10095
|
type: "object",
|
|
10239
10096
|
properties: {
|
|
@@ -10315,99 +10172,47 @@ async function login_handler(args) {
|
|
|
10315
10172
|
} catch (err) {
|
|
10316
10173
|
return login_fail("LoginConfigError", err?.message || "Could not load login config.");
|
|
10317
10174
|
}
|
|
10318
|
-
if (
|
|
10319
|
-
if (args.deviceCode) {
|
|
10320
|
-
const poll = await pollDeviceToken({
|
|
10321
|
-
apiBase,
|
|
10322
|
-
path: config.deviceTokenUrl,
|
|
10323
|
-
project,
|
|
10324
|
-
deviceCode: String(args.deviceCode),
|
|
10325
|
-
interval: 5,
|
|
10326
|
-
budgetMs: RESUME_BUDGET_MS
|
|
10327
|
-
});
|
|
10328
|
-
if (poll.ok) return success(poll.creds);
|
|
10329
|
-
if ("expired" === poll.reason) return login_fail("LoginExpired", "The device code expired. Run extension_login again to restart.");
|
|
10330
|
-
if ("denied" === poll.reason) return login_fail("LoginDenied", "Authorization was denied at extension.dev/device.");
|
|
10331
|
-
if ("error" === poll.reason) return login_fail("LoginError", poll.message || "Device login failed.");
|
|
10332
|
-
return resumePending(String(args.deviceCode), config.verificationUri);
|
|
10333
|
-
}
|
|
10334
|
-
let start;
|
|
10335
|
-
try {
|
|
10336
|
-
start = await requestDeviceCode({
|
|
10337
|
-
apiBase,
|
|
10338
|
-
path: config.deviceCodeUrl,
|
|
10339
|
-
project
|
|
10340
|
-
});
|
|
10341
|
-
} catch (err) {
|
|
10342
|
-
return login_fail("LoginStartError", err?.message || "Could not start the device flow.");
|
|
10343
|
-
}
|
|
10175
|
+
if (args.deviceCode) {
|
|
10344
10176
|
const poll = await pollDeviceToken({
|
|
10345
10177
|
apiBase,
|
|
10346
10178
|
path: config.deviceTokenUrl,
|
|
10347
10179
|
project,
|
|
10348
|
-
deviceCode: start.deviceCode,
|
|
10349
|
-
interval: start.interval,
|
|
10350
|
-
budgetMs: FIRST_CALL_BUDGET_MS
|
|
10351
|
-
});
|
|
10352
|
-
if (poll.ok) return success(poll.creds);
|
|
10353
|
-
if ("denied" === poll.reason) return login_fail("LoginDenied", "Authorization was denied at extension.dev/device.");
|
|
10354
|
-
return login_pending({
|
|
10355
|
-
deviceCode: start.deviceCode,
|
|
10356
|
-
userCode: start.userCode,
|
|
10357
|
-
verificationUri: start.verificationUri,
|
|
10358
|
-
verificationUriComplete: start.verificationUriComplete
|
|
10359
|
-
});
|
|
10360
|
-
}
|
|
10361
|
-
if (args.deviceCode) {
|
|
10362
|
-
const poll = await pollForToken({
|
|
10363
|
-
clientId: config.clientId,
|
|
10364
10180
|
deviceCode: String(args.deviceCode),
|
|
10365
10181
|
interval: 5,
|
|
10366
10182
|
budgetMs: RESUME_BUDGET_MS
|
|
10367
10183
|
});
|
|
10368
|
-
if (
|
|
10369
|
-
|
|
10370
|
-
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
try {
|
|
10374
|
-
const creds = await exchangeAndPersist({
|
|
10375
|
-
apiBase,
|
|
10376
|
-
githubToken: poll.githubToken,
|
|
10377
|
-
project
|
|
10378
|
-
});
|
|
10379
|
-
return success(creds);
|
|
10380
|
-
} catch (err) {
|
|
10381
|
-
return login_fail("LoginExchangeError", err?.message || "Token exchange failed.");
|
|
10382
|
-
}
|
|
10184
|
+
if (poll.ok) return success(poll.creds);
|
|
10185
|
+
if ("expired" === poll.reason) return login_fail("LoginExpired", "The device code expired. Run extension_login again to restart.");
|
|
10186
|
+
if ("denied" === poll.reason) return login_fail("LoginDenied", "Authorization was denied at extension.dev/device.");
|
|
10187
|
+
if ("error" === poll.reason) return login_fail("LoginError", poll.message || "Device login failed.");
|
|
10188
|
+
return resumePending(String(args.deviceCode), config.verificationUri);
|
|
10383
10189
|
}
|
|
10384
10190
|
let start;
|
|
10385
10191
|
try {
|
|
10386
|
-
start = await
|
|
10387
|
-
|
|
10388
|
-
|
|
10192
|
+
start = await requestDeviceCode({
|
|
10193
|
+
apiBase,
|
|
10194
|
+
path: config.deviceCodeUrl,
|
|
10195
|
+
project
|
|
10389
10196
|
});
|
|
10390
10197
|
} catch (err) {
|
|
10391
|
-
return login_fail("LoginStartError", err?.message || "Could not start device flow.");
|
|
10198
|
+
return login_fail("LoginStartError", err?.message || "Could not start the device flow.");
|
|
10392
10199
|
}
|
|
10393
|
-
const poll = await
|
|
10394
|
-
|
|
10200
|
+
const poll = await pollDeviceToken({
|
|
10201
|
+
apiBase,
|
|
10202
|
+
path: config.deviceTokenUrl,
|
|
10203
|
+
project,
|
|
10395
10204
|
deviceCode: start.deviceCode,
|
|
10396
10205
|
interval: start.interval,
|
|
10397
10206
|
budgetMs: FIRST_CALL_BUDGET_MS
|
|
10398
10207
|
});
|
|
10399
|
-
if (poll.ok)
|
|
10400
|
-
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
10406
|
-
}
|
|
10407
|
-
return login_fail("LoginExchangeError", err?.message || "Token exchange failed.");
|
|
10408
|
-
}
|
|
10409
|
-
if (!poll.ok && "denied" === poll.reason) return login_fail("LoginDenied", "Authorization was denied on GitHub.");
|
|
10410
|
-
return login_pending(start);
|
|
10208
|
+
if (poll.ok) return success(poll.creds);
|
|
10209
|
+
if ("denied" === poll.reason) return login_fail("LoginDenied", "Authorization was denied at extension.dev/device.");
|
|
10210
|
+
return login_pending({
|
|
10211
|
+
deviceCode: start.deviceCode,
|
|
10212
|
+
userCode: start.userCode,
|
|
10213
|
+
verificationUri: start.verificationUri,
|
|
10214
|
+
verificationUriComplete: start.verificationUriComplete
|
|
10215
|
+
});
|
|
10411
10216
|
}
|
|
10412
10217
|
const whoami_schema = {
|
|
10413
10218
|
name: "extension_whoami",
|
|
@@ -10443,7 +10248,7 @@ async function whoami_handler() {
|
|
|
10443
10248
|
apiRecordedAtLogin: recordedApi
|
|
10444
10249
|
} : {},
|
|
10445
10250
|
apiDefault: effectiveDefaultApi,
|
|
10446
|
-
provider: creds.provider ?? "
|
|
10251
|
+
provider: creds.provider ?? "extensiondev",
|
|
10447
10252
|
expiresAt: creds.expiresAt ? new Date(1000 * creds.expiresAt).toISOString() : null,
|
|
10448
10253
|
expiresInSeconds: creds.expiresAt ? creds.expiresAt - now : null,
|
|
10449
10254
|
expired,
|
|
@@ -11279,55 +11084,28 @@ async function runCli(cmd, args) {
|
|
|
11279
11084
|
const apiBase = resolveApiBase(flag("api"));
|
|
11280
11085
|
try {
|
|
11281
11086
|
const config = await fetchLoginConfig(apiBase);
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
|
|
11285
|
-
|
|
11286
|
-
project
|
|
11287
|
-
});
|
|
11288
|
-
log("");
|
|
11289
|
-
log(` Open ${start.verificationUri} and enter code: ${start.userCode}`);
|
|
11290
|
-
log("");
|
|
11291
|
-
log(" Waiting for authorization...");
|
|
11292
|
-
const poll = await pollDeviceToken({
|
|
11293
|
-
apiBase,
|
|
11294
|
-
path: config.deviceTokenUrl,
|
|
11295
|
-
project,
|
|
11296
|
-
deviceCode: start.deviceCode,
|
|
11297
|
-
interval: start.interval,
|
|
11298
|
-
budgetMs: 1000 * start.expiresIn
|
|
11299
|
-
});
|
|
11300
|
-
if (!poll.ok) {
|
|
11301
|
-
log("denied" === poll.reason ? "Authorization was denied at extension.dev/device." : "expired" === poll.reason ? "The device code expired. Run login again." : "Timed out waiting for authorization. Run login again.");
|
|
11302
|
-
return 1;
|
|
11303
|
-
}
|
|
11304
|
-
log(`Logged in to ${poll.creds.workspaceSlug}/${poll.creds.projectSlug}.`);
|
|
11305
|
-
return 0;
|
|
11306
|
-
}
|
|
11307
|
-
const start = await startDeviceCode({
|
|
11308
|
-
clientId: config.clientId,
|
|
11309
|
-
scope: config.scope
|
|
11087
|
+
const start = await requestDeviceCode({
|
|
11088
|
+
apiBase,
|
|
11089
|
+
path: config.deviceCodeUrl,
|
|
11090
|
+
project
|
|
11310
11091
|
});
|
|
11311
11092
|
log("");
|
|
11312
11093
|
log(` Open ${start.verificationUri} and enter code: ${start.userCode}`);
|
|
11313
11094
|
log("");
|
|
11314
11095
|
log(" Waiting for authorization...");
|
|
11315
|
-
const poll = await
|
|
11316
|
-
|
|
11096
|
+
const poll = await pollDeviceToken({
|
|
11097
|
+
apiBase,
|
|
11098
|
+
path: config.deviceTokenUrl,
|
|
11099
|
+
project,
|
|
11317
11100
|
deviceCode: start.deviceCode,
|
|
11318
11101
|
interval: start.interval,
|
|
11319
11102
|
budgetMs: 1000 * start.expiresIn
|
|
11320
11103
|
});
|
|
11321
11104
|
if (!poll.ok) {
|
|
11322
|
-
log("denied" === poll.reason ? "Authorization was denied
|
|
11105
|
+
log("denied" === poll.reason ? "Authorization was denied at extension.dev/device." : "expired" === poll.reason ? "The device code expired. Run login again." : "Timed out waiting for authorization. Run login again.");
|
|
11323
11106
|
return 1;
|
|
11324
11107
|
}
|
|
11325
|
-
|
|
11326
|
-
apiBase,
|
|
11327
|
-
githubToken: poll.githubToken,
|
|
11328
|
-
project
|
|
11329
|
-
});
|
|
11330
|
-
log(`Logged in to ${creds.workspaceSlug}/${creds.projectSlug}.`);
|
|
11108
|
+
log(`Logged in to ${poll.creds.workspaceSlug}/${poll.creds.projectSlug}.`);
|
|
11331
11109
|
return 0;
|
|
11332
11110
|
} catch (err) {
|
|
11333
11111
|
log(err instanceof Error ? err.message : String(err));
|
|
@@ -5,7 +5,7 @@ export interface StoredCredentials {
|
|
|
5
5
|
projectSlug: string;
|
|
6
6
|
expiresAt: number;
|
|
7
7
|
api: string;
|
|
8
|
-
provider?: "extensiondev"
|
|
8
|
+
provider?: "extensiondev";
|
|
9
9
|
}
|
|
10
10
|
export declare function credentialsPath(): string;
|
|
11
11
|
export declare function readCredentials(): StoredCredentials | null;
|
|
@@ -10,9 +10,6 @@ export declare function safeApiBase(raw: string): {
|
|
|
10
10
|
message: string;
|
|
11
11
|
};
|
|
12
12
|
export interface LoginConfig {
|
|
13
|
-
provider: "extensiondev" | "github";
|
|
14
|
-
clientId: string;
|
|
15
|
-
scope: string;
|
|
16
13
|
deviceCodeUrl: string;
|
|
17
14
|
deviceTokenUrl: string;
|
|
18
15
|
verificationUri: string;
|
|
@@ -21,12 +18,5 @@ export declare function fetchLoginConfig(apiBase: string, fetchImpl?: FetchImpl)
|
|
|
21
18
|
export declare function persistTokenResponse(args: {
|
|
22
19
|
apiBase: string;
|
|
23
20
|
data: Record<string, unknown>;
|
|
24
|
-
provider: "extensiondev" | "github";
|
|
25
21
|
}): StoredCredentials;
|
|
26
|
-
export declare function exchangeAndPersist(args: {
|
|
27
|
-
apiBase: string;
|
|
28
|
-
githubToken: string;
|
|
29
|
-
project: string;
|
|
30
|
-
fetchImpl?: FetchImpl;
|
|
31
|
-
}): Promise<StoredCredentials>;
|
|
32
22
|
export {};
|
|
@@ -23,20 +23,10 @@ export declare const schema: {
|
|
|
23
23
|
type: string;
|
|
24
24
|
description: string;
|
|
25
25
|
};
|
|
26
|
-
surface: {
|
|
27
|
-
type: string;
|
|
28
|
-
enum: string[];
|
|
29
|
-
default: string;
|
|
30
|
-
description: string;
|
|
31
|
-
};
|
|
32
26
|
hostUrl: {
|
|
33
27
|
type: string;
|
|
34
28
|
description: string;
|
|
35
29
|
};
|
|
36
|
-
inspectUrl: {
|
|
37
|
-
type: string;
|
|
38
|
-
description: string;
|
|
39
|
-
};
|
|
40
30
|
probe: {
|
|
41
31
|
type: string;
|
|
42
32
|
default: boolean;
|
|
@@ -66,9 +56,7 @@ export declare function handler(args: {
|
|
|
66
56
|
browser?: string;
|
|
67
57
|
build?: boolean;
|
|
68
58
|
distPath?: string;
|
|
69
|
-
surface?: string;
|
|
70
59
|
hostUrl?: string;
|
|
71
|
-
inspectUrl?: string;
|
|
72
60
|
probe?: boolean;
|
|
73
61
|
open?: boolean;
|
|
74
62
|
openIn?: string;
|