@commandgarden/cli 1.3.1 → 1.4.1
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/main.js +37 -2
- package/node_modules/@commandgarden/chrome/dist/content-script.js +107 -0
- package/node_modules/@commandgarden/chrome/dist/content-script.js.map +7 -0
- package/node_modules/@commandgarden/chrome/dist/fetch-interceptor.js +44 -0
- package/node_modules/@commandgarden/chrome/dist/manifest.json +16 -0
- package/node_modules/@commandgarden/chrome/dist/popup.html +197 -0
- package/node_modules/@commandgarden/chrome/dist/popup.js +112 -0
- package/node_modules/@commandgarden/chrome/dist/popup.js.map +7 -0
- package/node_modules/@commandgarden/chrome/dist/service-worker.js +7444 -0
- package/node_modules/@commandgarden/chrome/dist/service-worker.js.map +7 -0
- package/node_modules/@commandgarden/chrome/package.json +25 -0
- package/package.json +3 -1
package/dist/main.js
CHANGED
|
@@ -12201,7 +12201,7 @@ var {
|
|
|
12201
12201
|
} = import_index.default;
|
|
12202
12202
|
|
|
12203
12203
|
// src/main.ts
|
|
12204
|
-
import { join as join4, dirname as
|
|
12204
|
+
import { join as join4, dirname as dirname3, resolve } from "path";
|
|
12205
12205
|
import { readFileSync as readFileSync8 } from "fs";
|
|
12206
12206
|
import { homedir } from "os";
|
|
12207
12207
|
import { fileURLToPath } from "url";
|
|
@@ -17405,9 +17405,40 @@ async function executeDown(cgHome) {
|
|
|
17405
17405
|
return lines.join("\n");
|
|
17406
17406
|
}
|
|
17407
17407
|
|
|
17408
|
+
// src/commands/extension-cmd.ts
|
|
17409
|
+
import { dirname as dirname2 } from "path";
|
|
17410
|
+
import { spawn as spawn3 } from "child_process";
|
|
17411
|
+
function executeExtensionSetup(baseDir) {
|
|
17412
|
+
const manifestPath = resolveScript(
|
|
17413
|
+
baseDir,
|
|
17414
|
+
"../../chrome/dist/manifest.json",
|
|
17415
|
+
"@commandgarden/chrome",
|
|
17416
|
+
"dist/manifest.json"
|
|
17417
|
+
);
|
|
17418
|
+
const extensionDir = dirname2(manifestPath);
|
|
17419
|
+
const [command, args] = process.platform === "darwin" ? ["open", ["-a", "Google Chrome", "chrome://extensions"]] : process.platform === "win32" ? ["cmd", ["/c", "start", "chrome", "chrome://extensions"]] : ["google-chrome", ["chrome://extensions"]];
|
|
17420
|
+
const child = spawn3(command, args, { stdio: "ignore", detached: true });
|
|
17421
|
+
child.on("error", () => {
|
|
17422
|
+
});
|
|
17423
|
+
child.unref();
|
|
17424
|
+
return [
|
|
17425
|
+
"",
|
|
17426
|
+
"Chrome extension path:",
|
|
17427
|
+
` ${extensionDir}`,
|
|
17428
|
+
"",
|
|
17429
|
+
"To install:",
|
|
17430
|
+
' 1. Enable "Developer mode" (top-right toggle)',
|
|
17431
|
+
' 2. Click "Load unpacked"',
|
|
17432
|
+
" 3. Select the path above",
|
|
17433
|
+
"",
|
|
17434
|
+
"Opening chrome://extensions...",
|
|
17435
|
+
""
|
|
17436
|
+
].join("\n");
|
|
17437
|
+
}
|
|
17438
|
+
|
|
17408
17439
|
// src/main.ts
|
|
17409
17440
|
var __filename = fileURLToPath(import.meta.url);
|
|
17410
|
-
var __dirname =
|
|
17441
|
+
var __dirname = dirname3(__filename);
|
|
17411
17442
|
var CLI_VERSION = (() => {
|
|
17412
17443
|
try {
|
|
17413
17444
|
const pkg = JSON.parse(readFileSync8(resolve(__dirname, "..", "package.json"), "utf-8"));
|
|
@@ -17511,6 +17542,10 @@ gui.command("stop").description("Stop the GUI").action(() => {
|
|
|
17511
17542
|
gui.command("status").description("Check GUI status").action(() => {
|
|
17512
17543
|
console.log(executeGuiStatus(CG_HOME));
|
|
17513
17544
|
});
|
|
17545
|
+
var extension = program2.command("extension").description("Manage the Chrome extension");
|
|
17546
|
+
extension.command("setup").description("Show extension path and open Chrome for installation").action(() => {
|
|
17547
|
+
console.log(executeExtensionSetup(__dirname));
|
|
17548
|
+
});
|
|
17514
17549
|
program2.command("up").description("Start daemon + GUI, open browser (use --no-open to skip)").option("--no-open", "Do not open browser").action(async (opts) => {
|
|
17515
17550
|
console.log(await executeUp(BASE_URL, CG_HOME, getDaemonScript(), getAppScript(), CONFIG_PATH, {
|
|
17516
17551
|
noOpen: opts.open === false
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
// src/messages.ts
|
|
4
|
+
function createDomResponse(requestId, ok, data, error) {
|
|
5
|
+
return { requestId, ok, data, error };
|
|
6
|
+
}
|
|
7
|
+
function isDomRequest(value) {
|
|
8
|
+
if (value == null || typeof value !== "object") return false;
|
|
9
|
+
const obj = value;
|
|
10
|
+
return typeof obj.id === "string" && typeof obj.action === "string" && typeof obj.params === "object";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/content/dom-executor.ts
|
|
14
|
+
function waitForSelector(selector, timeout) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
if (document.querySelector(selector)) {
|
|
17
|
+
resolve();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const interval = 100;
|
|
21
|
+
let elapsed = 0;
|
|
22
|
+
const timer = setInterval(() => {
|
|
23
|
+
if (document.querySelector(selector)) {
|
|
24
|
+
clearInterval(timer);
|
|
25
|
+
resolve();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
elapsed += interval;
|
|
29
|
+
if (elapsed >= timeout) {
|
|
30
|
+
clearInterval(timer);
|
|
31
|
+
reject(new Error(`Selector "${selector}" timed out after ${timeout}ms`));
|
|
32
|
+
}
|
|
33
|
+
}, interval);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function extractData(rowSelector, fields) {
|
|
37
|
+
const rows = document.querySelectorAll(rowSelector);
|
|
38
|
+
return Array.from(rows).map((row) => {
|
|
39
|
+
const record = {};
|
|
40
|
+
for (const [name, selector] of Object.entries(fields)) {
|
|
41
|
+
record[name] = row.querySelector(selector)?.textContent?.trim() ?? "";
|
|
42
|
+
}
|
|
43
|
+
return record;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async function clickElement(selector) {
|
|
47
|
+
const el = document.querySelector(selector);
|
|
48
|
+
if (!el) throw new Error(`Element "${selector}" not found`);
|
|
49
|
+
el.click();
|
|
50
|
+
}
|
|
51
|
+
async function typeIntoElement(selector, value) {
|
|
52
|
+
const el = document.querySelector(selector);
|
|
53
|
+
if (!el) throw new Error(`Element "${selector}" not found`);
|
|
54
|
+
el.focus();
|
|
55
|
+
el.value = value;
|
|
56
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
57
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
58
|
+
}
|
|
59
|
+
async function fetchFromPage(url, method = "GET", headers, body) {
|
|
60
|
+
const resp = await fetch(url, {
|
|
61
|
+
method,
|
|
62
|
+
headers,
|
|
63
|
+
body,
|
|
64
|
+
credentials: "include"
|
|
65
|
+
});
|
|
66
|
+
const contentType = resp.headers.get("content-type") ?? "";
|
|
67
|
+
if (contentType.includes("application/json")) return resp.json();
|
|
68
|
+
return resp.text();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/content/content-script.ts
|
|
72
|
+
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|
73
|
+
if (!isDomRequest(message)) return false;
|
|
74
|
+
const req = message;
|
|
75
|
+
handleRequest(req).then(
|
|
76
|
+
(data) => sendResponse(createDomResponse(req.id, true, data)),
|
|
77
|
+
(err) => sendResponse(createDomResponse(req.id, false, void 0, err.message))
|
|
78
|
+
);
|
|
79
|
+
return true;
|
|
80
|
+
});
|
|
81
|
+
async function handleRequest(req) {
|
|
82
|
+
const p = req.params;
|
|
83
|
+
switch (req.action) {
|
|
84
|
+
case "wait":
|
|
85
|
+
await waitForSelector(p.selector, p.timeout ?? 1e4);
|
|
86
|
+
return void 0;
|
|
87
|
+
case "extract":
|
|
88
|
+
return extractData(p.selector, p.fields);
|
|
89
|
+
case "click":
|
|
90
|
+
await clickElement(p.selector);
|
|
91
|
+
return void 0;
|
|
92
|
+
case "type":
|
|
93
|
+
await typeIntoElement(p.selector, p.value);
|
|
94
|
+
return void 0;
|
|
95
|
+
case "fetch":
|
|
96
|
+
return fetchFromPage(
|
|
97
|
+
p.url,
|
|
98
|
+
p.method,
|
|
99
|
+
p.headers,
|
|
100
|
+
p.body
|
|
101
|
+
);
|
|
102
|
+
default:
|
|
103
|
+
throw new Error(`Unknown action: ${req.action}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
})();
|
|
107
|
+
//# sourceMappingURL=content-script.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/messages.ts", "../src/content/dom-executor.ts", "../src/content/content-script.ts"],
|
|
4
|
+
"sourcesContent": ["// src/messages.ts\nconst randomUUID = () => crypto.randomUUID();\n\nexport type DomAction = 'wait' | 'extract' | 'click' | 'type' | 'fetch';\n\nexport interface DomRequest {\n id: string;\n action: DomAction;\n params: Record<string, unknown>;\n}\n\nexport interface DomResponse {\n requestId: string;\n ok: boolean;\n data?: unknown;\n error?: string;\n}\n\nexport function createDomRequest(action: DomAction, params: Record<string, unknown>): DomRequest {\n return { id: randomUUID(), action, params };\n}\n\nexport function createDomResponse(\n requestId: string, ok: boolean, data?: unknown, error?: string,\n): DomResponse {\n return { requestId, ok, data, error };\n}\n\nexport function isDomRequest(value: unknown): value is DomRequest {\n if (value == null || typeof value !== 'object') return false;\n const obj = value as Record<string, unknown>;\n return typeof obj.id === 'string' && typeof obj.action === 'string' && typeof obj.params === 'object';\n}\n\nexport function isDomResponse(value: unknown): value is DomResponse {\n if (value == null || typeof value !== 'object') return false;\n const obj = value as Record<string, unknown>;\n return typeof obj.requestId === 'string' && typeof obj.ok === 'boolean';\n}\n", "// src/content/dom-executor.ts\n\nexport function waitForSelector(selector: string, timeout: number): Promise<void> {\n return new Promise((resolve, reject) => {\n if (document.querySelector(selector)) { resolve(); return; }\n const interval = 100;\n let elapsed = 0;\n const timer = setInterval(() => {\n if (document.querySelector(selector)) { clearInterval(timer); resolve(); return; }\n elapsed += interval;\n if (elapsed >= timeout) {\n clearInterval(timer);\n reject(new Error(`Selector \"${selector}\" timed out after ${timeout}ms`));\n }\n }, interval);\n });\n}\n\nexport function extractData(\n rowSelector: string,\n fields: Record<string, string>,\n): Record<string, string>[] {\n const rows = document.querySelectorAll(rowSelector);\n return Array.from(rows).map(row => {\n const record: Record<string, string> = {};\n for (const [name, selector] of Object.entries(fields)) {\n record[name] = row.querySelector(selector)?.textContent?.trim() ?? '';\n }\n return record;\n });\n}\n\nexport async function clickElement(selector: string): Promise<void> {\n const el = document.querySelector(selector);\n if (!el) throw new Error(`Element \"${selector}\" not found`);\n (el as HTMLElement).click();\n}\n\nexport async function typeIntoElement(selector: string, value: string): Promise<void> {\n const el = document.querySelector(selector) as HTMLInputElement | null;\n if (!el) throw new Error(`Element \"${selector}\" not found`);\n el.focus();\n el.value = value;\n el.dispatchEvent(new Event('input', { bubbles: true }));\n el.dispatchEvent(new Event('change', { bubbles: true }));\n}\n\nexport async function fetchFromPage(\n url: string, method = 'GET', headers?: Record<string, string>, body?: string,\n): Promise<unknown> {\n const resp = await fetch(url, {\n method, headers, body, credentials: 'include',\n });\n const contentType = resp.headers.get('content-type') ?? '';\n if (contentType.includes('application/json')) return resp.json();\n return resp.text();\n}\n", "// src/content/content-script.ts\nimport { isDomRequest, createDomResponse, type DomRequest } from '../messages.js';\nimport { waitForSelector, extractData, clickElement, typeIntoElement, fetchFromPage } from './dom-executor.js';\n\nchrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {\n if (!isDomRequest(message)) return false;\n const req = message as DomRequest;\n handleRequest(req).then(\n (data) => sendResponse(createDomResponse(req.id, true, data)),\n (err) => sendResponse(createDomResponse(req.id, false, undefined, err.message)),\n );\n return true; // Keep channel open for async response\n});\n\nasync function handleRequest(req: DomRequest): Promise<unknown> {\n const p = req.params;\n switch (req.action) {\n case 'wait':\n await waitForSelector(p.selector as string, (p.timeout as number) ?? 10000);\n return undefined;\n case 'extract':\n return extractData(p.selector as string, p.fields as Record<string, string>);\n case 'click':\n await clickElement(p.selector as string);\n return undefined;\n case 'type':\n await typeIntoElement(p.selector as string, p.value as string);\n return undefined;\n case 'fetch':\n return fetchFromPage(\n p.url as string, p.method as string,\n p.headers as Record<string, string>, p.body as string,\n );\n default:\n throw new Error(`Unknown action: ${req.action}`);\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// fetch-interceptor.js — injected at document_start in MAIN world.
|
|
2
|
+
// Patches window.fetch BEFORE OWA's modules capture a reference to it.
|
|
3
|
+
// Minimal — only the fetch patch, nothing else.
|
|
4
|
+
(function () {
|
|
5
|
+
if (window.__rfb) return;
|
|
6
|
+
window.__rfb = [];
|
|
7
|
+
window.__rfb_urls = [];
|
|
8
|
+
|
|
9
|
+
// Intercept Service Worker messages — OWA sends getSchedule data
|
|
10
|
+
// from the SW to the page via postMessage, bypassing window.fetch.
|
|
11
|
+
if (navigator.serviceWorker) {
|
|
12
|
+
navigator.serviceWorker.addEventListener('message', function (e) {
|
|
13
|
+
try {
|
|
14
|
+
var str = typeof e.data === 'string' ? e.data : JSON.stringify(e.data);
|
|
15
|
+
if (str.indexOf('getSchedule') > -1 && str.indexOf('availabilityView') > -1) {
|
|
16
|
+
window.__rfb.push({ req: '', body: str });
|
|
17
|
+
}
|
|
18
|
+
} catch (_e) { /* ignore */ }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
var origFetch = window.fetch;
|
|
23
|
+
window.fetch = function () {
|
|
24
|
+
var a = arguments;
|
|
25
|
+
var u = (a[0] && a[0].url) || a[0] || '';
|
|
26
|
+
var rb = (a[1] && a[1].body) || '';
|
|
27
|
+
var uStr = String(u);
|
|
28
|
+
if (window.__rfb_urls.length < 100) {
|
|
29
|
+
window.__rfb_urls.push(uStr.slice(0, 120));
|
|
30
|
+
}
|
|
31
|
+
return origFetch.apply(this, a).then(function (r) {
|
|
32
|
+
try {
|
|
33
|
+
if (uStr.indexOf('graphql') > -1) {
|
|
34
|
+
r.clone().text().then(function (t) {
|
|
35
|
+
if (t.indexOf('getSchedule') > -1 && t.indexOf('availabilityView') > -1) {
|
|
36
|
+
window.__rfb.push({ req: String(rb || ''), body: t });
|
|
37
|
+
}
|
|
38
|
+
}).catch(function () {});
|
|
39
|
+
}
|
|
40
|
+
} catch (_e) { /* ignore */ }
|
|
41
|
+
return r;
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "commandGarden",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Enterprise browser automation — secure, auditable CLI commands for websites",
|
|
6
|
+
"minimum_chrome_version": "116",
|
|
7
|
+
"permissions": ["alarms", "cookies", "debugger", "idle", "tabs", "scripting", "webRequest", "storage"],
|
|
8
|
+
"host_permissions": ["<all_urls>"],
|
|
9
|
+
"background": {
|
|
10
|
+
"service_worker": "service-worker.js",
|
|
11
|
+
"type": "module"
|
|
12
|
+
},
|
|
13
|
+
"action": {
|
|
14
|
+
"default_popup": "popup.html"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<style>
|
|
6
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
7
|
+
body {
|
|
8
|
+
width: 320px;
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
10
|
+
font-size: 13px;
|
|
11
|
+
background: #1e1e1e;
|
|
12
|
+
color: #ccc;
|
|
13
|
+
}
|
|
14
|
+
.header {
|
|
15
|
+
padding: 12px 16px;
|
|
16
|
+
font-family: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, Consolas, monospace;
|
|
17
|
+
font-size: 13px;
|
|
18
|
+
font-weight: 600;
|
|
19
|
+
color: #e0e0e0;
|
|
20
|
+
border-bottom: 1px solid #333;
|
|
21
|
+
}
|
|
22
|
+
.status-row {
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: 8px;
|
|
26
|
+
padding: 10px 16px;
|
|
27
|
+
border-bottom: 1px solid #333;
|
|
28
|
+
}
|
|
29
|
+
.dot {
|
|
30
|
+
width: 8px;
|
|
31
|
+
height: 8px;
|
|
32
|
+
border-radius: 50%;
|
|
33
|
+
flex-shrink: 0;
|
|
34
|
+
}
|
|
35
|
+
.dot.connected { background: #4caf50; }
|
|
36
|
+
.dot.disconnected { background: #f44336; }
|
|
37
|
+
.dot.disabled { background: #666; }
|
|
38
|
+
.status-text { flex: 1; }
|
|
39
|
+
.toggle {
|
|
40
|
+
position: relative;
|
|
41
|
+
width: 36px;
|
|
42
|
+
height: 20px;
|
|
43
|
+
flex-shrink: 0;
|
|
44
|
+
cursor: pointer;
|
|
45
|
+
}
|
|
46
|
+
.toggle input {
|
|
47
|
+
opacity: 0;
|
|
48
|
+
width: 0;
|
|
49
|
+
height: 0;
|
|
50
|
+
}
|
|
51
|
+
.toggle-track {
|
|
52
|
+
position: absolute;
|
|
53
|
+
inset: 0;
|
|
54
|
+
background: #555;
|
|
55
|
+
border-radius: 10px;
|
|
56
|
+
transition: background 0.2s;
|
|
57
|
+
}
|
|
58
|
+
.toggle input:checked + .toggle-track {
|
|
59
|
+
background: #4caf50;
|
|
60
|
+
}
|
|
61
|
+
.toggle-knob {
|
|
62
|
+
position: absolute;
|
|
63
|
+
top: 2px;
|
|
64
|
+
left: 2px;
|
|
65
|
+
width: 16px;
|
|
66
|
+
height: 16px;
|
|
67
|
+
background: #fff;
|
|
68
|
+
border-radius: 50%;
|
|
69
|
+
transition: transform 0.2s;
|
|
70
|
+
pointer-events: none;
|
|
71
|
+
}
|
|
72
|
+
.toggle input:checked ~ .toggle-knob {
|
|
73
|
+
transform: translateX(16px);
|
|
74
|
+
}
|
|
75
|
+
.activity-list {
|
|
76
|
+
max-height: 240px;
|
|
77
|
+
overflow-y: auto;
|
|
78
|
+
}
|
|
79
|
+
.activity-item {
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
gap: 8px;
|
|
83
|
+
padding: 8px 16px;
|
|
84
|
+
border-bottom: 1px solid #2a2a2a;
|
|
85
|
+
transition: background 0.1s;
|
|
86
|
+
}
|
|
87
|
+
.activity-item:hover {
|
|
88
|
+
background: #252525;
|
|
89
|
+
}
|
|
90
|
+
.activity-icon { font-size: 12px; flex-shrink: 0; }
|
|
91
|
+
.activity-icon.ok { color: #4caf50; }
|
|
92
|
+
.activity-icon.fail { color: #f44336; }
|
|
93
|
+
.activity-name { flex: 1; font-family: monospace; font-size: 12px; }
|
|
94
|
+
.activity-time { color: #888; font-size: 11px; flex-shrink: 0; }
|
|
95
|
+
.empty {
|
|
96
|
+
padding: 16px;
|
|
97
|
+
text-align: center;
|
|
98
|
+
color: #666;
|
|
99
|
+
font-style: italic;
|
|
100
|
+
}
|
|
101
|
+
/* Approval section */
|
|
102
|
+
.approval-section {
|
|
103
|
+
border-bottom: 1px solid #333;
|
|
104
|
+
background: #252525;
|
|
105
|
+
}
|
|
106
|
+
.approval-header {
|
|
107
|
+
padding: 10px 16px 4px;
|
|
108
|
+
font-size: 12px;
|
|
109
|
+
font-weight: 600;
|
|
110
|
+
color: #ccc;
|
|
111
|
+
}
|
|
112
|
+
.approval-count {
|
|
113
|
+
display: inline-block;
|
|
114
|
+
font-size: 10px;
|
|
115
|
+
min-width: 16px;
|
|
116
|
+
text-align: center;
|
|
117
|
+
padding: 0 5px;
|
|
118
|
+
line-height: 16px;
|
|
119
|
+
border-radius: 8px;
|
|
120
|
+
background: rgba(245, 158, 11, 0.18);
|
|
121
|
+
color: #F59E0B;
|
|
122
|
+
font-weight: 700;
|
|
123
|
+
margin-left: 6px;
|
|
124
|
+
vertical-align: 1px;
|
|
125
|
+
}
|
|
126
|
+
.approval-card {
|
|
127
|
+
padding: 10px 16px;
|
|
128
|
+
display: flex;
|
|
129
|
+
flex-direction: column;
|
|
130
|
+
gap: 6px;
|
|
131
|
+
}
|
|
132
|
+
.approval-card + .approval-card {
|
|
133
|
+
border-top: 1px solid #2a2a2a;
|
|
134
|
+
}
|
|
135
|
+
.approval-connector {
|
|
136
|
+
font-family: monospace;
|
|
137
|
+
font-size: 12px;
|
|
138
|
+
color: #e0e0e0;
|
|
139
|
+
}
|
|
140
|
+
.approval-detail {
|
|
141
|
+
font-size: 11px;
|
|
142
|
+
color: #999;
|
|
143
|
+
}
|
|
144
|
+
.approval-cap {
|
|
145
|
+
display: inline-block;
|
|
146
|
+
font-size: 10px;
|
|
147
|
+
padding: 1px 6px;
|
|
148
|
+
border-radius: 3px;
|
|
149
|
+
background: rgba(245, 158, 11, 0.15);
|
|
150
|
+
color: #F59E0B;
|
|
151
|
+
font-family: monospace;
|
|
152
|
+
}
|
|
153
|
+
.approval-actions {
|
|
154
|
+
display: flex;
|
|
155
|
+
gap: 8px;
|
|
156
|
+
margin-top: 2px;
|
|
157
|
+
}
|
|
158
|
+
.btn-approve, .btn-reject {
|
|
159
|
+
font-size: 11px;
|
|
160
|
+
padding: 4px 14px;
|
|
161
|
+
border-radius: 6px;
|
|
162
|
+
border: none;
|
|
163
|
+
cursor: pointer;
|
|
164
|
+
font-weight: 600;
|
|
165
|
+
transition: background 0.15s, color 0.15s;
|
|
166
|
+
}
|
|
167
|
+
.btn-approve {
|
|
168
|
+
background: #4caf50;
|
|
169
|
+
color: #fff;
|
|
170
|
+
}
|
|
171
|
+
.btn-approve:hover { background: #43a047; }
|
|
172
|
+
.btn-approve:active { background: #388e3c; }
|
|
173
|
+
.btn-reject {
|
|
174
|
+
background: transparent;
|
|
175
|
+
color: #999;
|
|
176
|
+
border: 1px solid #555;
|
|
177
|
+
}
|
|
178
|
+
.btn-reject:hover { background: #333; color: #ccc; }
|
|
179
|
+
.btn-reject:active { background: #2a2a2a; }
|
|
180
|
+
</style>
|
|
181
|
+
</head>
|
|
182
|
+
<body>
|
|
183
|
+
<div class="header">commandGarden</div>
|
|
184
|
+
<div class="status-row">
|
|
185
|
+
<div id="dot" class="dot disabled"></div>
|
|
186
|
+
<span id="status-text" class="status-text">Checking…</span>
|
|
187
|
+
<label class="toggle">
|
|
188
|
+
<input type="checkbox" id="toggle-input">
|
|
189
|
+
<div class="toggle-track"></div>
|
|
190
|
+
<div class="toggle-knob"></div>
|
|
191
|
+
</label>
|
|
192
|
+
</div>
|
|
193
|
+
<div id="approvals"></div>
|
|
194
|
+
<div id="activity" class="activity-list"></div>
|
|
195
|
+
<script src="popup.js"></script>
|
|
196
|
+
</body>
|
|
197
|
+
</html>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
// src/popup/time-ago.ts
|
|
4
|
+
function timeAgo(timestamp, now = Date.now()) {
|
|
5
|
+
const seconds = Math.floor((now - timestamp) / 1e3);
|
|
6
|
+
if (seconds < 60) return "just now";
|
|
7
|
+
const minutes = Math.floor(seconds / 60);
|
|
8
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
9
|
+
const hours = Math.floor(minutes / 60);
|
|
10
|
+
if (hours < 24) return `${hours}h ago`;
|
|
11
|
+
const days = Math.floor(hours / 24);
|
|
12
|
+
return `${days}d ago`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/popup/popup.ts
|
|
16
|
+
var dot = document.getElementById("dot");
|
|
17
|
+
var statusText = document.getElementById("status-text");
|
|
18
|
+
var toggleInput = document.getElementById("toggle-input");
|
|
19
|
+
var activityEl = document.getElementById("activity");
|
|
20
|
+
var approvalsEl = document.getElementById("approvals");
|
|
21
|
+
var currentEnabled = true;
|
|
22
|
+
function escapeHtml(text) {
|
|
23
|
+
const el = document.createElement("span");
|
|
24
|
+
el.textContent = text;
|
|
25
|
+
return el.innerHTML;
|
|
26
|
+
}
|
|
27
|
+
function renderApprovals(approvals) {
|
|
28
|
+
if (approvals.length === 0) {
|
|
29
|
+
approvalsEl.innerHTML = "";
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const cards = approvals.map(
|
|
33
|
+
(a) => `<div class="approval-card" data-id="${escapeHtml(a.approvalId)}">
|
|
34
|
+
<div class="approval-connector">${escapeHtml(a.connectorKey)}</div>
|
|
35
|
+
<div class="approval-detail">Step ${a.stepIndex + 1} [${escapeHtml(a.stepType)}] <span class="approval-cap">${escapeHtml(a.capability)}</span></div>
|
|
36
|
+
<div class="approval-actions">
|
|
37
|
+
<button class="btn-approve" data-approval-id="${escapeHtml(a.approvalId)}" data-approved="true">Approve</button>
|
|
38
|
+
<button class="btn-reject" data-approval-id="${escapeHtml(a.approvalId)}" data-approved="false">Reject</button>
|
|
39
|
+
</div>
|
|
40
|
+
</div>`
|
|
41
|
+
).join("");
|
|
42
|
+
approvalsEl.innerHTML = `<div class="approval-section">
|
|
43
|
+
<div class="approval-header">Pending approvals <span class="approval-count">${approvals.length}</span></div>
|
|
44
|
+
${cards}
|
|
45
|
+
</div>`;
|
|
46
|
+
approvalsEl.querySelectorAll("[data-approval-id]").forEach((btn) => {
|
|
47
|
+
btn.addEventListener("click", () => {
|
|
48
|
+
const approvalId = btn.dataset.approvalId;
|
|
49
|
+
const approved = btn.dataset.approved === "true";
|
|
50
|
+
btn.disabled = true;
|
|
51
|
+
chrome.runtime.sendMessage(
|
|
52
|
+
{ type: "approvalDecision", approvalId, approved },
|
|
53
|
+
() => {
|
|
54
|
+
fetchStatus();
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function render(status) {
|
|
61
|
+
currentEnabled = status.enabled;
|
|
62
|
+
toggleInput.checked = status.enabled;
|
|
63
|
+
if (!status.enabled) {
|
|
64
|
+
dot.className = "dot disabled";
|
|
65
|
+
statusText.textContent = "Disabled";
|
|
66
|
+
} else if (status.connected) {
|
|
67
|
+
dot.className = "dot connected";
|
|
68
|
+
statusText.textContent = "Connected";
|
|
69
|
+
} else {
|
|
70
|
+
dot.className = "dot disconnected";
|
|
71
|
+
statusText.textContent = "Disconnected";
|
|
72
|
+
}
|
|
73
|
+
renderApprovals(status.pendingApprovals ?? []);
|
|
74
|
+
if (status.recentActivity.length === 0) {
|
|
75
|
+
activityEl.innerHTML = '<div class="empty">No recent activity</div>';
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
activityEl.innerHTML = status.recentActivity.map((entry) => {
|
|
80
|
+
const icon = entry.ok ? '<span class="activity-icon ok">\u2713</span>' : '<span class="activity-icon fail">\u2717</span>';
|
|
81
|
+
return `<div class="activity-item">${icon}<span class="activity-name">${escapeHtml(entry.connector)}</span><span class="activity-time">${timeAgo(entry.timestamp, now)}</span></div>`;
|
|
82
|
+
}).join("");
|
|
83
|
+
}
|
|
84
|
+
function fetchStatus() {
|
|
85
|
+
chrome.runtime.sendMessage({ type: "getStatus" }, (response) => {
|
|
86
|
+
if (chrome.runtime.lastError) {
|
|
87
|
+
statusText.textContent = "Error";
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
render(response);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
toggleInput.addEventListener("change", () => {
|
|
94
|
+
const newEnabled = toggleInput.checked;
|
|
95
|
+
toggleInput.disabled = true;
|
|
96
|
+
chrome.runtime.sendMessage({ type: "setEnabled", enabled: newEnabled }, (response) => {
|
|
97
|
+
toggleInput.disabled = false;
|
|
98
|
+
if (chrome.runtime.lastError) {
|
|
99
|
+
toggleInput.checked = currentEnabled;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
render(response);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
chrome.runtime.onMessage.addListener((msg) => {
|
|
106
|
+
if (msg.type === "approvalUpdate") {
|
|
107
|
+
renderApprovals(msg.pendingApprovals ?? []);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
fetchStatus();
|
|
111
|
+
})();
|
|
112
|
+
//# sourceMappingURL=popup.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/popup/time-ago.ts", "../src/popup/popup.ts"],
|
|
4
|
+
"sourcesContent": ["// src/popup/time-ago.ts\nexport function timeAgo(timestamp: number, now: number = Date.now()): string {\n const seconds = Math.floor((now - timestamp) / 1000);\n if (seconds < 60) return 'just now';\n const minutes = Math.floor(seconds / 60);\n if (minutes < 60) return `${minutes}m ago`;\n const hours = Math.floor(minutes / 60);\n if (hours < 24) return `${hours}h ago`;\n const days = Math.floor(hours / 24);\n return `${days}d ago`;\n}\n", "// src/popup/popup.ts\n/// <reference lib=\"dom\" />\nimport type { PopupStatusResponse, ApprovalInfo } from './popup-types.js';\nimport { timeAgo } from './time-ago.js';\n\nconst dot = document.getElementById('dot')!;\nconst statusText = document.getElementById('status-text')!;\nconst toggleInput = document.getElementById('toggle-input') as HTMLInputElement;\nconst activityEl = document.getElementById('activity')!;\nconst approvalsEl = document.getElementById('approvals')!;\n\nlet currentEnabled = true;\n\nfunction escapeHtml(text: string): string {\n const el = document.createElement('span');\n el.textContent = text;\n return el.innerHTML;\n}\n\nfunction renderApprovals(approvals: ApprovalInfo[]): void {\n if (approvals.length === 0) {\n approvalsEl.innerHTML = '';\n return;\n }\n\n const cards = approvals.map(a =>\n `<div class=\"approval-card\" data-id=\"${escapeHtml(a.approvalId)}\">\n <div class=\"approval-connector\">${escapeHtml(a.connectorKey)}</div>\n <div class=\"approval-detail\">Step ${a.stepIndex + 1} [${escapeHtml(a.stepType)}] <span class=\"approval-cap\">${escapeHtml(a.capability)}</span></div>\n <div class=\"approval-actions\">\n <button class=\"btn-approve\" data-approval-id=\"${escapeHtml(a.approvalId)}\" data-approved=\"true\">Approve</button>\n <button class=\"btn-reject\" data-approval-id=\"${escapeHtml(a.approvalId)}\" data-approved=\"false\">Reject</button>\n </div>\n </div>`\n ).join('');\n\n approvalsEl.innerHTML =\n `<div class=\"approval-section\">\n <div class=\"approval-header\">Pending approvals <span class=\"approval-count\">${approvals.length}</span></div>\n ${cards}\n </div>`;\n\n approvalsEl.querySelectorAll('[data-approval-id]').forEach(btn => {\n btn.addEventListener('click', () => {\n const approvalId = (btn as HTMLElement).dataset.approvalId!;\n const approved = (btn as HTMLElement).dataset.approved === 'true';\n (btn as HTMLButtonElement).disabled = true;\n chrome.runtime.sendMessage(\n { type: 'approvalDecision', approvalId, approved },\n () => { fetchStatus(); },\n );\n });\n });\n}\n\nfunction render(status: PopupStatusResponse): void {\n currentEnabled = status.enabled;\n toggleInput.checked = status.enabled;\n\n if (!status.enabled) {\n dot.className = 'dot disabled';\n statusText.textContent = 'Disabled';\n } else if (status.connected) {\n dot.className = 'dot connected';\n statusText.textContent = 'Connected';\n } else {\n dot.className = 'dot disconnected';\n statusText.textContent = 'Disconnected';\n }\n\n renderApprovals(status.pendingApprovals ?? []);\n\n if (status.recentActivity.length === 0) {\n activityEl.innerHTML = '<div class=\"empty\">No recent activity</div>';\n return;\n }\n\n const now = Date.now();\n activityEl.innerHTML = status.recentActivity\n .map(entry => {\n const icon = entry.ok\n ? '<span class=\"activity-icon ok\">\u2713</span>'\n : '<span class=\"activity-icon fail\">\u2717</span>';\n return `<div class=\"activity-item\">${icon}<span class=\"activity-name\">${escapeHtml(entry.connector)}</span><span class=\"activity-time\">${timeAgo(entry.timestamp, now)}</span></div>`;\n })\n .join('');\n}\n\nfunction fetchStatus(): void {\n chrome.runtime.sendMessage({ type: 'getStatus' }, (response: PopupStatusResponse) => {\n if (chrome.runtime.lastError) {\n statusText.textContent = 'Error';\n return;\n }\n render(response);\n });\n}\n\ntoggleInput.addEventListener('change', () => {\n const newEnabled = toggleInput.checked;\n toggleInput.disabled = true;\n chrome.runtime.sendMessage({ type: 'setEnabled', enabled: newEnabled }, (response: PopupStatusResponse) => {\n toggleInput.disabled = false;\n if (chrome.runtime.lastError) {\n toggleInput.checked = currentEnabled;\n return;\n }\n render(response);\n });\n});\n\n// Listen for push updates from the service worker (approval state changes)\nchrome.runtime.onMessage.addListener((msg) => {\n if (msg.type === 'approvalUpdate') {\n renderApprovals(msg.pendingApprovals ?? []);\n }\n});\n\nfetchStatus();\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|