@buygent/cli 0.3.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/LICENSE +3 -0
- package/README.md +23 -0
- package/dist/cli.js +327 -0
- package/dist/extension/background.js +1008 -0
- package/dist/extension/content/coupang.js +914 -0
- package/dist/extension/manifest.json +36 -0
- package/dist/extension/popup.html +132 -0
- package/dist/extension/popup.js +119 -0
- package/dist/native-host.js +4 -0
- package/package.json +32 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "Buygent",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Buygent BYOK bridge for Coupang session probing and native host connectivity.",
|
|
6
|
+
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz9QWmSM8oHI1SIgpWOs2Qd+svxqlch1W0ctBFtbHK7oUw5y0RFfu5hmB1khciag0ii4jjvitdCMt2yj3Oug3Kc+NZ9wijP5oz+bwW8y+7u9hXjZu+eAZjHuclUvl+yBnL0Ny4altosfWA3x0Lih2S7ZCCrZR/Jx6Gk483XgZE9VBPz5Banu7hqF8qleWX0LvrJXwUp3RWFywWXdq1LswQLcQtx9niygpGMvGr1DKlJ8+iDw4Cn8XUCzp1+n74DBHAz6eKbl+5iOoFCLYxzOeEfs0ABXGlrL0ShF2mgwt/3+ucL2wTWe1atXOJHmmmhU3bK4anyFFUMzTsTA+Io96+wIDAQAB",
|
|
7
|
+
"permissions": [
|
|
8
|
+
"activeTab",
|
|
9
|
+
"nativeMessaging",
|
|
10
|
+
"storage",
|
|
11
|
+
"tabs"
|
|
12
|
+
],
|
|
13
|
+
"host_permissions": [
|
|
14
|
+
"https://*.coupang.com/*"
|
|
15
|
+
],
|
|
16
|
+
"background": {
|
|
17
|
+
"service_worker": "background.js",
|
|
18
|
+
"type": "module"
|
|
19
|
+
},
|
|
20
|
+
"content_scripts": [
|
|
21
|
+
{
|
|
22
|
+
"matches": [
|
|
23
|
+
"https://*.coupang.com/*"
|
|
24
|
+
],
|
|
25
|
+
"js": [
|
|
26
|
+
"content/coupang.js"
|
|
27
|
+
],
|
|
28
|
+
"run_at": "document_idle"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"action": {
|
|
32
|
+
"default_title": "Buygent",
|
|
33
|
+
"default_popup": "popup.html"
|
|
34
|
+
},
|
|
35
|
+
"minimum_chrome_version": "120"
|
|
36
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Buygent</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: light dark;
|
|
10
|
+
font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
body {
|
|
14
|
+
margin: 0;
|
|
15
|
+
min-width: 360px;
|
|
16
|
+
background: #111827;
|
|
17
|
+
color: #f9fafb;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
main {
|
|
21
|
+
padding: 16px;
|
|
22
|
+
display: grid;
|
|
23
|
+
gap: 12px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
h1 {
|
|
27
|
+
margin: 0;
|
|
28
|
+
font-size: 18px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
p {
|
|
32
|
+
margin: 0;
|
|
33
|
+
color: #cbd5e1;
|
|
34
|
+
font-size: 13px;
|
|
35
|
+
line-height: 1.5;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.actions {
|
|
39
|
+
display: grid;
|
|
40
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
41
|
+
gap: 8px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
button {
|
|
45
|
+
border: 0;
|
|
46
|
+
border-radius: 10px;
|
|
47
|
+
background: #2563eb;
|
|
48
|
+
color: #fff;
|
|
49
|
+
padding: 10px 12px;
|
|
50
|
+
font: inherit;
|
|
51
|
+
cursor: pointer;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
button.secondary {
|
|
55
|
+
background: #334155;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
dl {
|
|
59
|
+
margin: 0;
|
|
60
|
+
display: grid;
|
|
61
|
+
gap: 8px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.row {
|
|
65
|
+
display: grid;
|
|
66
|
+
gap: 2px;
|
|
67
|
+
padding: 10px 12px;
|
|
68
|
+
border-radius: 10px;
|
|
69
|
+
background: #1f2937;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
dt {
|
|
73
|
+
font-size: 11px;
|
|
74
|
+
letter-spacing: 0.04em;
|
|
75
|
+
text-transform: uppercase;
|
|
76
|
+
color: #94a3b8;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
dd {
|
|
80
|
+
margin: 0;
|
|
81
|
+
font-size: 13px;
|
|
82
|
+
word-break: break-word;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#status-banner[data-state="error"] {
|
|
86
|
+
color: #fecaca;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#status-banner[data-state="ok"] {
|
|
90
|
+
color: #bfdbfe;
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
93
|
+
</head>
|
|
94
|
+
<body>
|
|
95
|
+
<main>
|
|
96
|
+
<header>
|
|
97
|
+
<h1>Buygent</h1>
|
|
98
|
+
<p id="status-banner" data-state="ok">Ready.</p>
|
|
99
|
+
</header>
|
|
100
|
+
|
|
101
|
+
<div class="actions">
|
|
102
|
+
<button id="probe-button" type="button">Probe active tab</button>
|
|
103
|
+
<button id="ping-button" class="secondary" type="button">Ping native host</button>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<dl>
|
|
107
|
+
<div class="row">
|
|
108
|
+
<dt>Active tab</dt>
|
|
109
|
+
<dd id="active-tab">Unknown</dd>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="row">
|
|
112
|
+
<dt>Session</dt>
|
|
113
|
+
<dd id="session-state">Unknown</dd>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="row">
|
|
116
|
+
<dt>Page context</dt>
|
|
117
|
+
<dd id="page-context">No probe yet</dd>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="row">
|
|
120
|
+
<dt>Native host</dt>
|
|
121
|
+
<dd id="native-host-state">Not checked</dd>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="row">
|
|
124
|
+
<dt>Last updated</dt>
|
|
125
|
+
<dd id="last-updated">Never</dd>
|
|
126
|
+
</div>
|
|
127
|
+
</dl>
|
|
128
|
+
</main>
|
|
129
|
+
|
|
130
|
+
<script type="module" src="./popup.js"></script>
|
|
131
|
+
</body>
|
|
132
|
+
</html>
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// extension/protocol.ts
|
|
2
|
+
var BUYGENT_PROTOCOL_VERSION = 1;
|
|
3
|
+
var BUYGENT_NATIVE_HOST_NAME = "com.buygent.host";
|
|
4
|
+
var NATIVE_HOST_SOCKET_FILENAME = `${BUYGENT_NATIVE_HOST_NAME}.sock`;
|
|
5
|
+
var createEnvelope = (type, payload, options) => ({
|
|
6
|
+
protocolVersion: BUYGENT_PROTOCOL_VERSION,
|
|
7
|
+
type,
|
|
8
|
+
messageId: options.messageId,
|
|
9
|
+
...options.requestId ? { requestId: options.requestId } : {},
|
|
10
|
+
sentAt: options.sentAt ?? new Date().toISOString(),
|
|
11
|
+
source: options.source,
|
|
12
|
+
payload
|
|
13
|
+
});
|
|
14
|
+
var TERMINAL_ORDER_STATES = new Set(["aborted", "completed_dry_run"]);
|
|
15
|
+
|
|
16
|
+
// extension/popup.ts
|
|
17
|
+
var statusBanner = document.querySelector("#status-banner");
|
|
18
|
+
var activeTabElement = document.querySelector("#active-tab");
|
|
19
|
+
var sessionStateElement = document.querySelector("#session-state");
|
|
20
|
+
var pageContextElement = document.querySelector("#page-context");
|
|
21
|
+
var nativeHostStateElement = document.querySelector("#native-host-state");
|
|
22
|
+
var lastUpdatedElement = document.querySelector("#last-updated");
|
|
23
|
+
var probeButton = document.querySelector("#probe-button");
|
|
24
|
+
var pingButton = document.querySelector("#ping-button");
|
|
25
|
+
var createRequestId = () => globalThis.crypto?.randomUUID?.() ?? `req_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
26
|
+
var setBanner = (message, state = "ok") => {
|
|
27
|
+
if (!statusBanner) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
statusBanner.textContent = message;
|
|
31
|
+
statusBanner.dataset.state = state;
|
|
32
|
+
};
|
|
33
|
+
var requestBackground = async (type) => await chrome.runtime.sendMessage(createEnvelope(type, {}, {
|
|
34
|
+
source: "popup",
|
|
35
|
+
messageId: createRequestId()
|
|
36
|
+
}));
|
|
37
|
+
var formatProbe = (probe) => {
|
|
38
|
+
if (!probe) {
|
|
39
|
+
return "No probe yet";
|
|
40
|
+
}
|
|
41
|
+
const summary = [`${probe.pageKind}`, probe.loggedIn ? "logged in" : probe.sessionState];
|
|
42
|
+
if (probe.product?.title) {
|
|
43
|
+
summary.push(probe.product.title);
|
|
44
|
+
}
|
|
45
|
+
if (probe.checkout?.totalPriceText) {
|
|
46
|
+
summary.push(`checkout ${probe.checkout.totalPriceText}`);
|
|
47
|
+
} else if (probe.product?.priceText) {
|
|
48
|
+
summary.push(probe.product.priceText);
|
|
49
|
+
}
|
|
50
|
+
return summary.join(" · ");
|
|
51
|
+
};
|
|
52
|
+
var formatNativeHost = (response) => {
|
|
53
|
+
if (!response) {
|
|
54
|
+
return "Not checked";
|
|
55
|
+
}
|
|
56
|
+
if (response.type === "native:pong") {
|
|
57
|
+
return `Connected · ${response.payload.hostVersion} · ${response.payload.receivedAt}`;
|
|
58
|
+
}
|
|
59
|
+
return `Error · ${response.payload.code} · ${response.payload.message}`;
|
|
60
|
+
};
|
|
61
|
+
var renderState = (state) => {
|
|
62
|
+
if (activeTabElement) {
|
|
63
|
+
activeTabElement.textContent = state.activeTabUrl ?? "Unknown";
|
|
64
|
+
}
|
|
65
|
+
if (sessionStateElement) {
|
|
66
|
+
const probe = state.lastProbe;
|
|
67
|
+
const orderState = state.lastOrderStatus?.state ?? state.lastOrderCheckpoint?.state;
|
|
68
|
+
sessionStateElement.textContent = probe ? `${probe.sessionState} (${probe.selectorsVersion})${orderState ? ` · ${orderState}` : ""}` : state.lastError ?? "Unknown";
|
|
69
|
+
}
|
|
70
|
+
if (pageContextElement) {
|
|
71
|
+
pageContextElement.textContent = state.lastOrderCheckpoint?.detail ?? state.lastOrderStatus?.detail ?? formatProbe(state.lastProbe);
|
|
72
|
+
}
|
|
73
|
+
if (nativeHostStateElement) {
|
|
74
|
+
nativeHostStateElement.textContent = formatNativeHost(state.lastNativeHostResponse);
|
|
75
|
+
}
|
|
76
|
+
if (lastUpdatedElement) {
|
|
77
|
+
lastUpdatedElement.textContent = state.lastUpdatedAt ?? "Never";
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
var syncState = async () => {
|
|
81
|
+
const response = await requestBackground("popup:get-state");
|
|
82
|
+
if (response.type === "error") {
|
|
83
|
+
setBanner(response.payload.message, "error");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
renderState(response.payload.state);
|
|
87
|
+
setBanner(response.payload.state.lastError ?? "Ready.", response.payload.state.lastError ? "error" : "ok");
|
|
88
|
+
};
|
|
89
|
+
var runAction = async (button, type, successMessage) => {
|
|
90
|
+
if (button) {
|
|
91
|
+
button.disabled = true;
|
|
92
|
+
}
|
|
93
|
+
setBanner("Working…");
|
|
94
|
+
try {
|
|
95
|
+
const response = await requestBackground(type);
|
|
96
|
+
if (response.type === "error") {
|
|
97
|
+
setBanner(response.payload.message, "error");
|
|
98
|
+
await syncState();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (response.type === "popup:probe-result" || response.type === "popup:native-host-response") {
|
|
102
|
+
renderState(response.payload.state);
|
|
103
|
+
}
|
|
104
|
+
setBanner(successMessage);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
setBanner(error instanceof Error ? error.message : "Extension request failed", "error");
|
|
107
|
+
} finally {
|
|
108
|
+
if (button) {
|
|
109
|
+
button.disabled = false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
probeButton?.addEventListener("click", () => {
|
|
114
|
+
runAction(probeButton, "popup:probe-active-tab", "Probe updated.");
|
|
115
|
+
});
|
|
116
|
+
pingButton?.addEventListener("click", () => {
|
|
117
|
+
runAction(pingButton, "popup:ping-native-host", "Native host responded.");
|
|
118
|
+
});
|
|
119
|
+
syncState();
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @bun
|
|
3
|
+
import{createServer as q}from"net";import{existsSync as f,mkdirSync as w,readFileSync as b,rmSync as m}from"fs";import{homedir as R}from"os";import{dirname as g,join as h}from"path";import{fileURLToPath as V}from"url";var x="0.0.0-dev",E="0.3.0",A=()=>{let e=g(V(import.meta.url));while(!0){let t=h(e,"package.json");if(f(t)){let o=JSON.parse(b(t,"utf8"));if(typeof o.version==="string"&&o.version.trim().length>0)return o.version}let r=g(e);if(r===e)break;e=r}return E==="0.3.0"?x:E},B=A(),T=1,c=h(R(),".buygent","chrome-extension","com.buygent.host.sock"),n=Buffer.alloc(0),s=new Map,p=new Map,l=new WeakMap,i=()=>globalThis.crypto?.randomUUID?.()??`req_${Date.now()}_${Math.random().toString(16).slice(2)}`,_=(e,t,r)=>({protocolVersion:T,type:e,messageId:r.messageId,...r.requestId?{requestId:r.requestId}:{},sentAt:r.sentAt??new Date().toISOString(),source:r.source,payload:t}),d=(e,t)=>_("error",e,t),O=(e)=>{if(!e||typeof e!=="object"||Array.isArray(e))return{ok:!1,error:d({code:"invalid_envelope",message:"Expected a JSON object envelope."},{source:"native-host",messageId:i()})};let t=e;if(t.protocolVersion!==T)return{ok:!1,error:d({code:"unsupported_protocol_version",message:`Unsupported protocolVersion: ${String(t.protocolVersion??"missing")}`},{source:"native-host",messageId:i()})};if(typeof t.type!=="string"||typeof t.messageId!=="string"||typeof t.sentAt!=="string"||typeof t.source!=="string")return{ok:!1,error:d({code:"invalid_envelope",message:"Envelope must include type, messageId, sentAt, and source fields."},{source:"native-host",messageId:i()})};return{ok:!0,envelope:e}},D=(e)=>JSON.stringify(e),u=(e)=>{let t=Buffer.from(JSON.stringify(e),"utf8"),r=Buffer.alloc(4);r.writeUInt32LE(t.length,0),process.stdout.write(r),process.stdout.write(t)},a=(e,t)=>{e.write(`${D(t)}
|
|
4
|
+
`)},P=(e,t)=>{let r=p.get(e)??new Set;r.add(t),p.set(e,r)},S=(e)=>{for(let[t,r]of p.entries())if(r.delete(e),r.size===0)p.delete(t);for(let[t,r]of s.entries())if(r===e)s.delete(t)},k=(e,t)=>{let r=p.get(e);if(!r)return;for(let o of r)a(o,t)},U=(e)=>{if(e.type==="native:ping"){u(_("native:pong",{hostVersion:B,receivedAt:new Date().toISOString()},{source:"native-host",messageId:i(),requestId:e.messageId}));return}if(e.type==="order:ack"){if(e.requestId){let t=s.get(e.requestId);if(t)P(e.payload.orderId,t),a(t,e)}return}if(e.type==="runtime:state"){if(e.requestId){let t=s.get(e.requestId);if(t)s.delete(e.requestId),a(t,e)}return}if(e.type==="order:status"||e.type==="order:checkpoint"){k(e.payload.orderId,e);return}if(e.type==="error"){let t=typeof e.payload.details==="object"&&e.payload.details&&!Array.isArray(e.payload.details)?e.payload.details.orderId:void 0;if(typeof t==="string"){k(t,e);return}if(e.requestId){let r=s.get(e.requestId);if(r)s.delete(e.requestId),a(r,e)}}},L=(e)=>{let t=O(e);if(!t.ok){u(t.error);return}U(t.envelope)},H=(e,t)=>{let r=O(JSON.parse(t));if(!r.ok){a(e,r.error);return}let o=r.envelope;if(o.type==="order:start"||o.type==="runtime:get-state"){s.set(o.messageId,e),u(o);return}a(e,d({code:"unsupported_message",message:`Unsupported CLI/native-host message: ${o.type}`},{source:"native-host",messageId:i(),requestId:o.messageId}))},J=()=>{if(w(g(c),{recursive:!0,mode:448}),f(c))m(c,{force:!0})};J();var M=q((e)=>{e.setEncoding("utf8"),l.set(e,""),e.on("data",(t)=>{let o=`${l.get(e)??""}${t}`.split(/\r?\n/u);l.set(e,o.pop()??"");for(let N of o){let y=N.trim();if(!y)continue;try{H(e,y)}catch(I){a(e,d({code:"invalid_message",message:I instanceof Error?I.message:"Invalid CLI/native-host payload."},{source:"native-host",messageId:i()}))}}}),e.on("close",()=>{S(e)}),e.on("error",()=>{S(e)})});M.listen(c);process.stdin.on("data",(e)=>{n=Buffer.concat([n,e]);while(n.length>=4){let t=n.readUInt32LE(0);if(n.length<4+t)return;let r=n.subarray(4,4+t);n=n.subarray(4+t);try{L(JSON.parse(r.toString("utf8")))}catch(o){u(d({code:"invalid_message",message:o instanceof Error?o.message:"Invalid JSON payload."},{source:"native-host",messageId:i()}))}}});var v=()=>{if(M.close(),f(c))m(c,{force:!0})};process.stdin.on("end",()=>{v(),process.exit(0)});process.on("SIGTERM",()=>{v(),process.exit(0)});process.on("SIGINT",()=>{v(),process.exit(0)});process.stdin.resume();
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@buygent/cli",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Buygent CLI for one-command installation and extension setup.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"buygent": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/cli.js",
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=20",
|
|
13
|
+
"bun": ">=1.0"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public",
|
|
22
|
+
"registry": "https://registry.npmjs.org/"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://buygent.io"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://buygent.io",
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://buygent.io"
|
|
31
|
+
}
|
|
32
|
+
}
|