@commandgarden/cli 2.14.0 → 2.14.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.
Files changed (69) hide show
  1. package/dist/main.js +248 -278
  2. package/node_modules/@commandgarden/app/dist/client/assets/{Slides-DWQmJfkp.js → Slides-DMIm_HgI.js} +5 -5
  3. package/node_modules/@commandgarden/app/dist/client/assets/{index-C8yqRQk-.js → index-CupEN1w-.js} +53 -53
  4. package/node_modules/@commandgarden/app/dist/client/index.html +18 -18
  5. package/node_modules/@commandgarden/app/dist/server/daemon-client.test.js +47 -0
  6. package/node_modules/@commandgarden/app/dist/server/main.js +1 -1
  7. package/node_modules/@commandgarden/app/dist/server/routes/audit.js +16 -27
  8. package/node_modules/@commandgarden/app/dist/server/routes/config.js +2 -8
  9. package/node_modules/@commandgarden/app/dist/server/routes/connectors.js +26 -37
  10. package/node_modules/@commandgarden/app/dist/server/routes/routes.test.js +137 -0
  11. package/node_modules/@commandgarden/app/dist/server/routes/run.js +4 -3
  12. package/node_modules/@commandgarden/app/dist/server/routes/status.js +1 -2
  13. package/node_modules/@commandgarden/app/dist/server/store.js +53 -53
  14. package/node_modules/@commandgarden/app/dist/server/store.test.js +59 -0
  15. package/node_modules/@commandgarden/app/package.json +42 -42
  16. package/node_modules/@commandgarden/chrome/dist/content-script.js.map +1 -1
  17. package/node_modules/@commandgarden/chrome/dist/fetch-interceptor.js +44 -0
  18. package/node_modules/@commandgarden/chrome/dist/manifest.json +26 -26
  19. package/node_modules/@commandgarden/chrome/dist/popup.html +218 -218
  20. package/node_modules/@commandgarden/chrome/dist/popup.js.map +1 -1
  21. package/node_modules/@commandgarden/chrome/dist/service-worker.js +40 -40
  22. package/node_modules/@commandgarden/chrome/dist/service-worker.js.map +2 -2
  23. package/node_modules/@commandgarden/chrome/package.json +26 -26
  24. package/node_modules/@commandgarden/daemon/connectors/ado-git-commits.eval.js +100 -100
  25. package/node_modules/@commandgarden/daemon/connectors/ado-git-commits.yaml +71 -71
  26. package/node_modules/@commandgarden/daemon/connectors/gcs-kb-content.yaml +61 -61
  27. package/node_modules/@commandgarden/daemon/connectors/gcs-kb-pages.yaml +54 -54
  28. package/node_modules/@commandgarden/daemon/connectors/jira-my-tickets.eval.js +115 -115
  29. package/node_modules/@commandgarden/daemon/connectors/jira-my-tickets.yaml +54 -54
  30. package/node_modules/@commandgarden/daemon/connectors/lib/msal-token.js +20 -20
  31. package/node_modules/@commandgarden/daemon/connectors/outlook-my-meetings.eval.js +204 -204
  32. package/node_modules/@commandgarden/daemon/connectors/outlook-my-meetings.yaml +42 -42
  33. package/node_modules/@commandgarden/daemon/connectors/saba-pending-training.eval.js +94 -94
  34. package/node_modules/@commandgarden/daemon/connectors/saba-pending-training.yaml +34 -34
  35. package/node_modules/@commandgarden/daemon/connectors/socket-security-news.yaml +63 -63
  36. package/node_modules/@commandgarden/daemon/connectors/teams-room-availability.eval.js +437 -437
  37. package/node_modules/@commandgarden/daemon/connectors/teams-room-availability.yaml +53 -53
  38. package/node_modules/@commandgarden/daemon/connectors/teams-rooms-availability.eval.js +547 -547
  39. package/node_modules/@commandgarden/daemon/connectors/teams-rooms-availability.yaml +48 -48
  40. package/node_modules/@commandgarden/daemon/connectors/timetracking-projects.eval.js +52 -52
  41. package/node_modules/@commandgarden/daemon/connectors/timetracking-projects.yaml +42 -42
  42. package/node_modules/@commandgarden/daemon/connectors/timetracking-report.eval.js +81 -81
  43. package/node_modules/@commandgarden/daemon/connectors/timetracking-report.yaml +52 -52
  44. package/node_modules/@commandgarden/daemon/connectors/tldrsec-newsletter.eval.js +27 -27
  45. package/node_modules/@commandgarden/daemon/connectors/tldrsec-newsletter.yaml +38 -38
  46. package/node_modules/@commandgarden/daemon/connectors/tokenmaster-clients-list.yaml +55 -55
  47. package/node_modules/@commandgarden/daemon/dist/audit-store.js +8 -8
  48. package/node_modules/@commandgarden/daemon/dist/auth.d.ts +0 -1
  49. package/node_modules/@commandgarden/daemon/dist/auth.d.ts.map +1 -1
  50. package/node_modules/@commandgarden/daemon/dist/auth.js +0 -10
  51. package/node_modules/@commandgarden/daemon/dist/auth.js.map +1 -1
  52. package/node_modules/@commandgarden/daemon/dist/cdp-client.d.ts +24 -0
  53. package/node_modules/@commandgarden/daemon/dist/cdp-client.d.ts.map +1 -0
  54. package/node_modules/@commandgarden/daemon/dist/cdp-client.js +150 -0
  55. package/node_modules/@commandgarden/daemon/dist/cdp-client.js.map +1 -0
  56. package/node_modules/@commandgarden/daemon/dist/main.js +5 -12
  57. package/node_modules/@commandgarden/daemon/dist/main.js.map +1 -1
  58. package/node_modules/@commandgarden/daemon/dist/server.d.ts.map +1 -1
  59. package/node_modules/@commandgarden/daemon/dist/server.js +0 -10
  60. package/node_modules/@commandgarden/daemon/dist/server.js.map +1 -1
  61. package/node_modules/@commandgarden/daemon/package.json +32 -32
  62. package/node_modules/@commandgarden/shared/dist/daemon-client.d.ts +1 -5
  63. package/node_modules/@commandgarden/shared/dist/daemon-client.d.ts.map +1 -1
  64. package/node_modules/@commandgarden/shared/dist/daemon-client.js +14 -32
  65. package/node_modules/@commandgarden/shared/dist/daemon-client.js.map +1 -1
  66. package/node_modules/@commandgarden/shared/package.json +32 -32
  67. package/package.json +58 -58
  68. package/node_modules/@commandgarden/app/dist/server/routes/utils.d.ts +0 -2
  69. package/node_modules/@commandgarden/app/dist/server/routes/utils.js +0 -4
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/messages.ts", "../src/content/dom-executor.ts", "../src/content/content-script.ts"],
4
- "sourcesContent": ["// src/messages.ts\r\nconst randomUUID = () => crypto.randomUUID();\r\n\r\nexport type DomAction = 'wait' | 'extract' | 'extract_tree' | 'extract_html' | 'click' | 'click_all' | 'type' | 'fetch';\r\n\r\nexport interface DomRequest {\r\n id: string;\r\n action: DomAction;\r\n params: Record<string, unknown>;\r\n}\r\n\r\nexport interface DomResponse {\r\n requestId: string;\r\n ok: boolean;\r\n data?: unknown;\r\n error?: string;\r\n}\r\n\r\nexport function createDomRequest(action: DomAction, params: Record<string, unknown>): DomRequest {\r\n return { id: randomUUID(), action, params };\r\n}\r\n\r\nexport function createDomResponse(\r\n requestId: string, ok: boolean, data?: unknown, error?: string,\r\n): DomResponse {\r\n return { requestId, ok, data, error };\r\n}\r\n\r\nexport function isDomRequest(value: unknown): value is DomRequest {\r\n if (value == null || typeof value !== 'object') return false;\r\n const obj = value as Record<string, unknown>;\r\n return typeof obj.id === 'string' && typeof obj.action === 'string' && typeof obj.params === 'object';\r\n}\r\n\r\nexport function isDomResponse(value: unknown): value is DomResponse {\r\n if (value == null || typeof value !== 'object') return false;\r\n const obj = value as Record<string, unknown>;\r\n return typeof obj.requestId === 'string' && typeof obj.ok === 'boolean';\r\n}\r\n", "// src/content/dom-executor.ts\r\n\r\nexport function waitForSelector(selector: string, timeout: number): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n if (document.querySelector(selector)) { resolve(); return; }\r\n const interval = 100;\r\n let elapsed = 0;\r\n const timer = setInterval(() => {\r\n if (document.querySelector(selector)) { clearInterval(timer); resolve(); return; }\r\n elapsed += interval;\r\n if (elapsed >= timeout) {\r\n clearInterval(timer);\r\n reject(new Error(`Selector \"${selector}\" timed out after ${timeout}ms`));\r\n }\r\n }, interval);\r\n });\r\n}\r\n\r\nexport function extractData(\r\n rowSelector: string,\r\n fields: Record<string, string>,\r\n): Record<string, string>[] {\r\n const rows = document.querySelectorAll(rowSelector);\r\n return Array.from(rows).map(row => {\r\n const record: Record<string, string> = {};\r\n for (const [name, selector] of Object.entries(fields)) {\r\n record[name] = row.querySelector(selector)?.textContent?.trim() ?? '';\r\n }\r\n return record;\r\n });\r\n}\r\n\r\nexport function extractHtml(selector: string): string {\r\n const el = document.querySelector(selector);\r\n if (!el) throw new Error(`Element \"${selector}\" not found`);\r\n return el.innerHTML;\r\n}\r\n\r\nexport function extractTree(\r\n rootSelector: string,\r\n group: { match: string; title: string; children: string },\r\n leaf: { match: string; fields: Record<string, string> },\r\n pathSeparator: string,\r\n): Record<string, unknown>[] {\r\n const root = document.querySelector(rootSelector);\r\n if (!root) throw new Error(`Root \"${rootSelector}\" not found`);\r\n\r\n function extractField(el: Element, spec: string): string {\r\n if (spec === 'textContent') return el.textContent?.trim().replace(/\\s+/g, ' ') ?? '';\r\n if (spec === 'href') return el.getAttribute('href') ?? '';\r\n if (spec.startsWith('attr:')) return el.getAttribute(spec.slice(5)) ?? '';\r\n return el.textContent?.trim() ?? '';\r\n }\r\n\r\n function walk(container: Element, ancestors: string[], depth: number): Record<string, unknown>[] {\r\n const results: Record<string, unknown>[] = [];\r\n for (const child of Array.from(container.children)) {\r\n if (child.matches(leaf.match)) {\r\n const row: Record<string, unknown> = {};\r\n for (const [name, spec] of Object.entries(leaf.fields)) {\r\n row[name] = extractField(child, spec);\r\n }\r\n const title = (row.title as string) || child.textContent?.trim() || '';\r\n const section = ancestors.length > 0 ? ancestors[0] : title;\r\n const pathParts = [...ancestors, title];\r\n row.section = section;\r\n row.path = pathParts.join(pathSeparator);\r\n row.depth = depth;\r\n results.push(row);\r\n } else if (child.matches(group.match)) {\r\n const titleEl = child.querySelector(group.title);\r\n const groupTitle = titleEl?.textContent?.trim().replace(/\\s+/g, ' ') ?? '';\r\n const childContainer = child.querySelector(group.children);\r\n if (childContainer) {\r\n results.push(...walk(childContainer, [...ancestors, groupTitle], depth + 1));\r\n }\r\n } else {\r\n results.push(...walk(child, ancestors, depth));\r\n }\r\n }\r\n return results;\r\n }\r\n\r\n return walk(root, [], 0);\r\n}\r\n\r\nexport async function clickAll(\r\n selector: string, pause: number, maxRounds: number, settle: number,\r\n): Promise<void> {\r\n for (let round = 0; round < maxRounds; round++) {\r\n const elements = document.querySelectorAll(selector);\r\n if (elements.length === 0) break;\r\n for (const el of Array.from(elements)) {\r\n (el as HTMLElement).click();\r\n if (pause > 0) await new Promise(r => setTimeout(r, pause));\r\n }\r\n }\r\n if (settle > 0) await new Promise(r => setTimeout(r, settle));\r\n}\r\n\r\nexport async function clickElement(selector: string): Promise<void> {\r\n const el = document.querySelector(selector);\r\n if (!el) throw new Error(`Element \"${selector}\" not found`);\r\n (el as HTMLElement).click();\r\n}\r\n\r\nexport async function typeIntoElement(selector: string, value: string): Promise<void> {\r\n const el = document.querySelector(selector) as HTMLInputElement | null;\r\n if (!el) throw new Error(`Element \"${selector}\" not found`);\r\n el.focus();\r\n el.value = value;\r\n el.dispatchEvent(new Event('input', { bubbles: true }));\r\n el.dispatchEvent(new Event('change', { bubbles: true }));\r\n}\r\n\r\nexport async function fetchFromPage(\r\n url: string, method = 'GET', headers?: Record<string, string>, body?: string,\r\n): Promise<unknown> {\r\n const resp = await fetch(url, {\r\n method, headers, body, credentials: 'include',\r\n });\r\n const contentType = resp.headers.get('content-type') ?? '';\r\n if (contentType.includes('application/json')) return resp.json();\r\n return resp.text();\r\n}\r\n", "// src/content/content-script.ts\r\nimport { isDomRequest, createDomResponse, type DomRequest } from '../messages.js';\r\nimport { waitForSelector, extractData, extractHtml, extractTree, clickElement, clickAll, typeIntoElement, fetchFromPage } from './dom-executor.js';\r\n\r\nchrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {\r\n if (!isDomRequest(message)) return false;\r\n const req = message as DomRequest;\r\n handleRequest(req).then(\r\n (data) => sendResponse(createDomResponse(req.id, true, data)),\r\n (err) => sendResponse(createDomResponse(req.id, false, undefined, err.message)),\r\n );\r\n return true; // Keep channel open for async response\r\n});\r\n\r\nasync function handleRequest(req: DomRequest): Promise<unknown> {\r\n const p = req.params;\r\n switch (req.action) {\r\n case 'wait':\r\n await waitForSelector(p.selector as string, (p.timeout as number) ?? 10000);\r\n return undefined;\r\n case 'extract':\r\n return extractData(p.selector as string, p.fields as Record<string, string>);\r\n case 'extract_html':\r\n return extractHtml(p.selector as string);\r\n case 'extract_tree':\r\n return extractTree(\r\n p.root as string,\r\n p.group as { match: string; title: string; children: string },\r\n p.leaf as { match: string; fields: Record<string, string> },\r\n (p.pathSeparator as string) ?? ' / ',\r\n );\r\n case 'click':\r\n await clickElement(p.selector as string);\r\n return undefined;\r\n case 'click_all':\r\n await clickAll(\r\n p.selector as string,\r\n (p.pause as number) ?? 200,\r\n (p.maxRounds as number) ?? 10,\r\n (p.settle as number) ?? 300,\r\n );\r\n return undefined;\r\n case 'type':\r\n await typeIntoElement(p.selector as string, p.value as string);\r\n return undefined;\r\n case 'fetch': {\r\n const result = await fetchFromPage(\r\n p.url as string, p.method as string,\r\n p.headers as Record<string, string>, p.body as string,\r\n );\r\n if (p.dataPath && result != null && typeof result === 'object' && !Array.isArray(result)) {\r\n let nested: unknown = result;\r\n for (const key of (p.dataPath as string).split('.')) {\r\n if (nested == null || typeof nested !== 'object') return [];\r\n nested = (nested as Record<string, unknown>)[key];\r\n }\r\n return Array.isArray(nested) ? nested : [];\r\n }\r\n return result;\r\n }\r\n default:\r\n throw new Error(`Unknown action: ${req.action}`);\r\n }\r\n}\r\n"],
4
+ "sourcesContent": ["// src/messages.ts\nconst randomUUID = () => crypto.randomUUID();\n\nexport type DomAction = 'wait' | 'extract' | 'extract_tree' | 'extract_html' | 'click' | 'click_all' | '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 function extractHtml(selector: string): string {\n const el = document.querySelector(selector);\n if (!el) throw new Error(`Element \"${selector}\" not found`);\n return el.innerHTML;\n}\n\nexport function extractTree(\n rootSelector: string,\n group: { match: string; title: string; children: string },\n leaf: { match: string; fields: Record<string, string> },\n pathSeparator: string,\n): Record<string, unknown>[] {\n const root = document.querySelector(rootSelector);\n if (!root) throw new Error(`Root \"${rootSelector}\" not found`);\n\n function extractField(el: Element, spec: string): string {\n if (spec === 'textContent') return el.textContent?.trim().replace(/\\s+/g, ' ') ?? '';\n if (spec === 'href') return el.getAttribute('href') ?? '';\n if (spec.startsWith('attr:')) return el.getAttribute(spec.slice(5)) ?? '';\n return el.textContent?.trim() ?? '';\n }\n\n function walk(container: Element, ancestors: string[], depth: number): Record<string, unknown>[] {\n const results: Record<string, unknown>[] = [];\n for (const child of Array.from(container.children)) {\n if (child.matches(leaf.match)) {\n const row: Record<string, unknown> = {};\n for (const [name, spec] of Object.entries(leaf.fields)) {\n row[name] = extractField(child, spec);\n }\n const title = (row.title as string) || child.textContent?.trim() || '';\n const section = ancestors.length > 0 ? ancestors[0] : title;\n const pathParts = [...ancestors, title];\n row.section = section;\n row.path = pathParts.join(pathSeparator);\n row.depth = depth;\n results.push(row);\n } else if (child.matches(group.match)) {\n const titleEl = child.querySelector(group.title);\n const groupTitle = titleEl?.textContent?.trim().replace(/\\s+/g, ' ') ?? '';\n const childContainer = child.querySelector(group.children);\n if (childContainer) {\n results.push(...walk(childContainer, [...ancestors, groupTitle], depth + 1));\n }\n } else {\n results.push(...walk(child, ancestors, depth));\n }\n }\n return results;\n }\n\n return walk(root, [], 0);\n}\n\nexport async function clickAll(\n selector: string, pause: number, maxRounds: number, settle: number,\n): Promise<void> {\n for (let round = 0; round < maxRounds; round++) {\n const elements = document.querySelectorAll(selector);\n if (elements.length === 0) break;\n for (const el of Array.from(elements)) {\n (el as HTMLElement).click();\n if (pause > 0) await new Promise(r => setTimeout(r, pause));\n }\n }\n if (settle > 0) await new Promise(r => setTimeout(r, settle));\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, extractHtml, extractTree, clickElement, clickAll, 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 'extract_html':\n return extractHtml(p.selector as string);\n case 'extract_tree':\n return extractTree(\n p.root as string,\n p.group as { match: string; title: string; children: string },\n p.leaf as { match: string; fields: Record<string, string> },\n (p.pathSeparator as string) ?? ' / ',\n );\n case 'click':\n await clickElement(p.selector as string);\n return undefined;\n case 'click_all':\n await clickAll(\n p.selector as string,\n (p.pause as number) ?? 200,\n (p.maxRounds as number) ?? 10,\n (p.settle as number) ?? 300,\n );\n return undefined;\n case 'type':\n await typeIntoElement(p.selector as string, p.value as string);\n return undefined;\n case 'fetch': {\n const result = await fetchFromPage(\n p.url as string, p.method as string,\n p.headers as Record<string, string>, p.body as string,\n );\n if (p.dataPath && result != null && typeof result === 'object' && !Array.isArray(result)) {\n let nested: unknown = result;\n for (const key of (p.dataPath as string).split('.')) {\n if (nested == null || typeof nested !== 'object') return [];\n nested = (nested as Record<string, unknown>)[key];\n }\n return Array.isArray(nested) ? nested : [];\n }\n return result;\n }\n default:\n throw new Error(`Unknown action: ${req.action}`);\n }\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
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
+ })();
@@ -1,26 +1,26 @@
1
- {
2
- "manifest_version": 3,
3
- "name": "commandGarden",
4
- "version": "1.0.1",
5
- "description": "Enterprise browser automation — secure, auditable CLI commands for websites",
6
- "minimum_chrome_version": "116",
7
- "icons": {
8
- "16": "icons/icon-16.png",
9
- "32": "icons/icon-32.png",
10
- "48": "icons/icon-48.png",
11
- "128": "icons/icon-128.png"
12
- },
13
- "permissions": ["alarms", "cookies", "debugger", "idle", "tabs", "scripting", "webRequest", "storage"],
14
- "host_permissions": ["<all_urls>"],
15
- "background": {
16
- "service_worker": "service-worker.js",
17
- "type": "module"
18
- },
19
- "action": {
20
- "default_popup": "popup.html",
21
- "default_icon": {
22
- "16": "icons/icon-16.png",
23
- "32": "icons/icon-32.png"
24
- }
25
- }
26
- }
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "commandGarden",
4
+ "version": "1.0.1",
5
+ "description": "Enterprise browser automation — secure, auditable CLI commands for websites",
6
+ "minimum_chrome_version": "116",
7
+ "icons": {
8
+ "16": "icons/icon-16.png",
9
+ "32": "icons/icon-32.png",
10
+ "48": "icons/icon-48.png",
11
+ "128": "icons/icon-128.png"
12
+ },
13
+ "permissions": ["alarms", "cookies", "debugger", "idle", "tabs", "scripting", "webRequest", "storage"],
14
+ "host_permissions": ["<all_urls>"],
15
+ "background": {
16
+ "service_worker": "service-worker.js",
17
+ "type": "module"
18
+ },
19
+ "action": {
20
+ "default_popup": "popup.html",
21
+ "default_icon": {
22
+ "16": "icons/icon-16.png",
23
+ "32": "icons/icon-32.png"
24
+ }
25
+ }
26
+ }
@@ -1,218 +1,218 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <link rel="preconnect" href="https://fonts.googleapis.com" />
6
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
7
- <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
8
- <style>
9
- * { margin: 0; padding: 0; box-sizing: border-box; border-radius: 0 !important; }
10
- body {
11
- width: 320px;
12
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
13
- font-size: 13px;
14
- background: #0a0a0a;
15
- color: #e5e5e5;
16
- }
17
- .header {
18
- padding: 12px 16px;
19
- border-bottom: 1px solid #262626;
20
- }
21
- .header-title {
22
- font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, sans-serif;
23
- font-size: 14px;
24
- font-weight: 700;
25
- letter-spacing: -0.02em;
26
- color: #e5e5e5;
27
- }
28
- .header-title span {
29
- font-weight: 400;
30
- opacity: 0.6;
31
- }
32
- .header-sub {
33
- font-family: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace;
34
- font-size: 0.6rem;
35
- font-weight: 500;
36
- text-transform: uppercase;
37
- letter-spacing: 0.12em;
38
- opacity: 0.4;
39
- margin-top: 2px;
40
- }
41
- .status-row {
42
- display: flex;
43
- align-items: center;
44
- gap: 8px;
45
- padding: 10px 16px;
46
- border-bottom: 1px solid #262626;
47
- }
48
- .dot {
49
- width: 8px;
50
- height: 8px;
51
- flex-shrink: 0;
52
- }
53
- .dot.connected { background: #4ade80; }
54
- .dot.disconnected { background: #f87171; }
55
- .dot.disabled { background: #737373; }
56
- .status-text { flex: 1; }
57
- .toggle {
58
- position: relative;
59
- width: 36px;
60
- height: 20px;
61
- flex-shrink: 0;
62
- cursor: pointer;
63
- }
64
- .toggle input {
65
- opacity: 0;
66
- width: 0;
67
- height: 0;
68
- }
69
- .toggle-track {
70
- position: absolute;
71
- inset: 0;
72
- background: #262626;
73
- transition: background 0.15s;
74
- }
75
- .toggle input:checked + .toggle-track {
76
- background: #4ade80;
77
- }
78
- .toggle-knob {
79
- position: absolute;
80
- top: 2px;
81
- left: 2px;
82
- width: 16px;
83
- height: 16px;
84
- background: #e5e5e5;
85
- transition: transform 0.15s;
86
- pointer-events: none;
87
- }
88
- .toggle input:checked ~ .toggle-knob {
89
- transform: translateX(16px);
90
- }
91
- .activity-list {
92
- max-height: 240px;
93
- overflow-y: auto;
94
- }
95
- .activity-item {
96
- display: flex;
97
- align-items: center;
98
- gap: 8px;
99
- padding: 8px 16px;
100
- border-bottom: 1px solid #171717;
101
- transition: background 0.1s;
102
- }
103
- .activity-item:hover {
104
- background: #171717;
105
- }
106
- .activity-icon { font-size: 12px; flex-shrink: 0; }
107
- .activity-icon.ok { color: #4ade80; }
108
- .activity-icon.fail { color: #f87171; }
109
- .activity-name { flex: 1; font-family: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace; font-size: 12px; }
110
- .activity-time { color: #a3a3a3; font-size: 11px; flex-shrink: 0; }
111
- .empty {
112
- padding: 16px;
113
- text-align: center;
114
- color: #737373;
115
- font-style: italic;
116
- }
117
- /* Approval section */
118
- .approval-section {
119
- border-bottom: 1px solid #262626;
120
- background: #171717;
121
- }
122
- .approval-header {
123
- padding: 10px 16px 4px;
124
- font-size: 12px;
125
- font-weight: 600;
126
- color: #e5e5e5;
127
- }
128
- .approval-count {
129
- display: inline-block;
130
- font-size: 10px;
131
- min-width: 16px;
132
- text-align: center;
133
- padding: 0 5px;
134
- line-height: 16px;
135
- background: rgba(251, 191, 36, 0.15);
136
- color: #fbbf24;
137
- font-weight: 700;
138
- margin-left: 6px;
139
- vertical-align: 1px;
140
- }
141
- .approval-card {
142
- padding: 10px 16px;
143
- display: flex;
144
- flex-direction: column;
145
- gap: 6px;
146
- }
147
- .approval-card + .approval-card {
148
- border-top: 1px solid #262626;
149
- }
150
- .approval-connector {
151
- font-family: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace;
152
- font-size: 12px;
153
- color: #e5e5e5;
154
- }
155
- .approval-detail {
156
- font-size: 11px;
157
- color: #a3a3a3;
158
- }
159
- .approval-cap {
160
- display: inline-block;
161
- font-size: 10px;
162
- padding: 1px 6px;
163
- background: rgba(251, 191, 36, 0.12);
164
- color: #fbbf24;
165
- font-family: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace;
166
- }
167
- .approval-actions {
168
- display: flex;
169
- gap: 8px;
170
- margin-top: 2px;
171
- }
172
- .btn-approve, .btn-reject {
173
- font-size: 11px;
174
- padding: 4px 14px;
175
- border: none;
176
- cursor: pointer;
177
- font-weight: 600;
178
- transition: background 0.15s, color 0.15s;
179
- }
180
- .btn-approve {
181
- background: #4ade80;
182
- color: #0a0a0a;
183
- }
184
- .btn-approve:hover { background: #22c55e; }
185
- .btn-approve:active { background: #16a34a; }
186
- .btn-reject {
187
- background: transparent;
188
- color: #a3a3a3;
189
- border: 1px solid #262626;
190
- }
191
- .btn-reject:hover { background: #171717; color: #e5e5e5; }
192
- .btn-reject:active { background: #262626; }
193
- /* Scrollbar */
194
- ::-webkit-scrollbar { width: 6px; height: 6px; }
195
- ::-webkit-scrollbar-track { background: transparent; }
196
- ::-webkit-scrollbar-thumb { background: rgba(229, 229, 229, 0.2); }
197
- ::-webkit-scrollbar-thumb:hover { background: rgba(229, 229, 229, 0.35); }
198
- </style>
199
- </head>
200
- <body>
201
- <div class="header">
202
- <div class="header-title"><span>command</span>Garden</div>
203
- <div class="header-sub">browser automation</div>
204
- </div>
205
- <div class="status-row">
206
- <div id="dot" class="dot disabled"></div>
207
- <span id="status-text" class="status-text">Checking…</span>
208
- <label class="toggle">
209
- <input type="checkbox" id="toggle-input">
210
- <div class="toggle-track"></div>
211
- <div class="toggle-knob"></div>
212
- </label>
213
- </div>
214
- <div id="approvals"></div>
215
- <div id="activity" class="activity-list"></div>
216
- <script src="popup.js"></script>
217
- </body>
218
- </html>
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
6
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
7
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
8
+ <style>
9
+ * { margin: 0; padding: 0; box-sizing: border-box; border-radius: 0 !important; }
10
+ body {
11
+ width: 320px;
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
13
+ font-size: 13px;
14
+ background: #0a0a0a;
15
+ color: #e5e5e5;
16
+ }
17
+ .header {
18
+ padding: 12px 16px;
19
+ border-bottom: 1px solid #262626;
20
+ }
21
+ .header-title {
22
+ font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, sans-serif;
23
+ font-size: 14px;
24
+ font-weight: 700;
25
+ letter-spacing: -0.02em;
26
+ color: #e5e5e5;
27
+ }
28
+ .header-title span {
29
+ font-weight: 400;
30
+ opacity: 0.6;
31
+ }
32
+ .header-sub {
33
+ font-family: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace;
34
+ font-size: 0.6rem;
35
+ font-weight: 500;
36
+ text-transform: uppercase;
37
+ letter-spacing: 0.12em;
38
+ opacity: 0.4;
39
+ margin-top: 2px;
40
+ }
41
+ .status-row {
42
+ display: flex;
43
+ align-items: center;
44
+ gap: 8px;
45
+ padding: 10px 16px;
46
+ border-bottom: 1px solid #262626;
47
+ }
48
+ .dot {
49
+ width: 8px;
50
+ height: 8px;
51
+ flex-shrink: 0;
52
+ }
53
+ .dot.connected { background: #4ade80; }
54
+ .dot.disconnected { background: #f87171; }
55
+ .dot.disabled { background: #737373; }
56
+ .status-text { flex: 1; }
57
+ .toggle {
58
+ position: relative;
59
+ width: 36px;
60
+ height: 20px;
61
+ flex-shrink: 0;
62
+ cursor: pointer;
63
+ }
64
+ .toggle input {
65
+ opacity: 0;
66
+ width: 0;
67
+ height: 0;
68
+ }
69
+ .toggle-track {
70
+ position: absolute;
71
+ inset: 0;
72
+ background: #262626;
73
+ transition: background 0.15s;
74
+ }
75
+ .toggle input:checked + .toggle-track {
76
+ background: #4ade80;
77
+ }
78
+ .toggle-knob {
79
+ position: absolute;
80
+ top: 2px;
81
+ left: 2px;
82
+ width: 16px;
83
+ height: 16px;
84
+ background: #e5e5e5;
85
+ transition: transform 0.15s;
86
+ pointer-events: none;
87
+ }
88
+ .toggle input:checked ~ .toggle-knob {
89
+ transform: translateX(16px);
90
+ }
91
+ .activity-list {
92
+ max-height: 240px;
93
+ overflow-y: auto;
94
+ }
95
+ .activity-item {
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 8px;
99
+ padding: 8px 16px;
100
+ border-bottom: 1px solid #171717;
101
+ transition: background 0.1s;
102
+ }
103
+ .activity-item:hover {
104
+ background: #171717;
105
+ }
106
+ .activity-icon { font-size: 12px; flex-shrink: 0; }
107
+ .activity-icon.ok { color: #4ade80; }
108
+ .activity-icon.fail { color: #f87171; }
109
+ .activity-name { flex: 1; font-family: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace; font-size: 12px; }
110
+ .activity-time { color: #a3a3a3; font-size: 11px; flex-shrink: 0; }
111
+ .empty {
112
+ padding: 16px;
113
+ text-align: center;
114
+ color: #737373;
115
+ font-style: italic;
116
+ }
117
+ /* Approval section */
118
+ .approval-section {
119
+ border-bottom: 1px solid #262626;
120
+ background: #171717;
121
+ }
122
+ .approval-header {
123
+ padding: 10px 16px 4px;
124
+ font-size: 12px;
125
+ font-weight: 600;
126
+ color: #e5e5e5;
127
+ }
128
+ .approval-count {
129
+ display: inline-block;
130
+ font-size: 10px;
131
+ min-width: 16px;
132
+ text-align: center;
133
+ padding: 0 5px;
134
+ line-height: 16px;
135
+ background: rgba(251, 191, 36, 0.15);
136
+ color: #fbbf24;
137
+ font-weight: 700;
138
+ margin-left: 6px;
139
+ vertical-align: 1px;
140
+ }
141
+ .approval-card {
142
+ padding: 10px 16px;
143
+ display: flex;
144
+ flex-direction: column;
145
+ gap: 6px;
146
+ }
147
+ .approval-card + .approval-card {
148
+ border-top: 1px solid #262626;
149
+ }
150
+ .approval-connector {
151
+ font-family: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace;
152
+ font-size: 12px;
153
+ color: #e5e5e5;
154
+ }
155
+ .approval-detail {
156
+ font-size: 11px;
157
+ color: #a3a3a3;
158
+ }
159
+ .approval-cap {
160
+ display: inline-block;
161
+ font-size: 10px;
162
+ padding: 1px 6px;
163
+ background: rgba(251, 191, 36, 0.12);
164
+ color: #fbbf24;
165
+ font-family: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace;
166
+ }
167
+ .approval-actions {
168
+ display: flex;
169
+ gap: 8px;
170
+ margin-top: 2px;
171
+ }
172
+ .btn-approve, .btn-reject {
173
+ font-size: 11px;
174
+ padding: 4px 14px;
175
+ border: none;
176
+ cursor: pointer;
177
+ font-weight: 600;
178
+ transition: background 0.15s, color 0.15s;
179
+ }
180
+ .btn-approve {
181
+ background: #4ade80;
182
+ color: #0a0a0a;
183
+ }
184
+ .btn-approve:hover { background: #22c55e; }
185
+ .btn-approve:active { background: #16a34a; }
186
+ .btn-reject {
187
+ background: transparent;
188
+ color: #a3a3a3;
189
+ border: 1px solid #262626;
190
+ }
191
+ .btn-reject:hover { background: #171717; color: #e5e5e5; }
192
+ .btn-reject:active { background: #262626; }
193
+ /* Scrollbar */
194
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
195
+ ::-webkit-scrollbar-track { background: transparent; }
196
+ ::-webkit-scrollbar-thumb { background: rgba(229, 229, 229, 0.2); }
197
+ ::-webkit-scrollbar-thumb:hover { background: rgba(229, 229, 229, 0.35); }
198
+ </style>
199
+ </head>
200
+ <body>
201
+ <div class="header">
202
+ <div class="header-title"><span>command</span>Garden</div>
203
+ <div class="header-sub">browser automation</div>
204
+ </div>
205
+ <div class="status-row">
206
+ <div id="dot" class="dot disabled"></div>
207
+ <span id="status-text" class="status-text">Checking…</span>
208
+ <label class="toggle">
209
+ <input type="checkbox" id="toggle-input">
210
+ <div class="toggle-track"></div>
211
+ <div class="toggle-knob"></div>
212
+ </label>
213
+ </div>
214
+ <div id="approvals"></div>
215
+ <div id="activity" class="activity-list"></div>
216
+ <script src="popup.js"></script>
217
+ </body>
218
+ </html>
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/popup/time-ago.ts", "../src/popup/popup.ts"],
4
- "sourcesContent": ["// src/popup/time-ago.ts\r\nexport function timeAgo(timestamp: number, now: number = Date.now()): string {\r\n const seconds = Math.floor((now - timestamp) / 1000);\r\n if (seconds < 60) return 'just now';\r\n const minutes = Math.floor(seconds / 60);\r\n if (minutes < 60) return `${minutes}m ago`;\r\n const hours = Math.floor(minutes / 60);\r\n if (hours < 24) return `${hours}h ago`;\r\n const days = Math.floor(hours / 24);\r\n return `${days}d ago`;\r\n}\r\n", "// src/popup/popup.ts\r\n/// <reference lib=\"dom\" />\r\nimport type { PopupStatusResponse, ApprovalInfo } from './popup-types.js';\r\nimport { timeAgo } from './time-ago.js';\r\n\r\nconst dot = document.getElementById('dot')!;\r\nconst statusText = document.getElementById('status-text')!;\r\nconst toggleInput = document.getElementById('toggle-input') as HTMLInputElement;\r\nconst activityEl = document.getElementById('activity')!;\r\nconst approvalsEl = document.getElementById('approvals')!;\r\n\r\nlet currentEnabled = true;\r\n\r\nfunction escapeHtml(text: string): string {\r\n const el = document.createElement('span');\r\n el.textContent = text;\r\n return el.innerHTML;\r\n}\r\n\r\nfunction renderApprovals(approvals: ApprovalInfo[]): void {\r\n if (approvals.length === 0) {\r\n approvalsEl.innerHTML = '';\r\n return;\r\n }\r\n\r\n const cards = approvals.map(a =>\r\n `<div class=\"approval-card\" data-id=\"${escapeHtml(a.approvalId)}\">\r\n <div class=\"approval-connector\">${escapeHtml(a.connectorKey)}</div>\r\n <div class=\"approval-detail\">Step ${a.stepIndex + 1} [${escapeHtml(a.stepType)}] <span class=\"approval-cap\">${escapeHtml(a.capability)}</span></div>\r\n <div class=\"approval-actions\">\r\n <button class=\"btn-approve\" data-approval-id=\"${escapeHtml(a.approvalId)}\" data-approved=\"true\">Approve</button>\r\n <button class=\"btn-reject\" data-approval-id=\"${escapeHtml(a.approvalId)}\" data-approved=\"false\">Reject</button>\r\n </div>\r\n </div>`\r\n ).join('');\r\n\r\n approvalsEl.innerHTML =\r\n `<div class=\"approval-section\">\r\n <div class=\"approval-header\">Pending approvals <span class=\"approval-count\">${approvals.length}</span></div>\r\n ${cards}\r\n </div>`;\r\n\r\n approvalsEl.querySelectorAll('[data-approval-id]').forEach(btn => {\r\n btn.addEventListener('click', () => {\r\n const approvalId = (btn as HTMLElement).dataset.approvalId!;\r\n const approved = (btn as HTMLElement).dataset.approved === 'true';\r\n (btn as HTMLButtonElement).disabled = true;\r\n chrome.runtime.sendMessage(\r\n { type: 'approvalDecision', approvalId, approved },\r\n () => { fetchStatus(); },\r\n );\r\n });\r\n });\r\n}\r\n\r\nfunction render(status: PopupStatusResponse): void {\r\n currentEnabled = status.enabled;\r\n toggleInput.checked = status.enabled;\r\n\r\n if (!status.enabled) {\r\n dot.className = 'dot disabled';\r\n statusText.textContent = 'Disabled';\r\n } else if (status.connected) {\r\n dot.className = 'dot connected';\r\n statusText.textContent = 'Connected';\r\n } else {\r\n dot.className = 'dot disconnected';\r\n statusText.textContent = 'Disconnected';\r\n }\r\n\r\n renderApprovals(status.pendingApprovals ?? []);\r\n\r\n if (status.recentActivity.length === 0) {\r\n activityEl.innerHTML = '<div class=\"empty\">No recent activity</div>';\r\n return;\r\n }\r\n\r\n const now = Date.now();\r\n activityEl.innerHTML = status.recentActivity\r\n .map(entry => {\r\n const icon = entry.ok\r\n ? '<span class=\"activity-icon ok\">\u2713</span>'\r\n : '<span class=\"activity-icon fail\">\u2717</span>';\r\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>`;\r\n })\r\n .join('');\r\n}\r\n\r\nfunction fetchStatus(): void {\r\n chrome.runtime.sendMessage({ type: 'getStatus' }, (response: PopupStatusResponse) => {\r\n if (chrome.runtime.lastError) {\r\n statusText.textContent = 'Error';\r\n return;\r\n }\r\n render(response);\r\n });\r\n}\r\n\r\ntoggleInput.addEventListener('change', () => {\r\n const newEnabled = toggleInput.checked;\r\n toggleInput.disabled = true;\r\n chrome.runtime.sendMessage({ type: 'setEnabled', enabled: newEnabled }, (response: PopupStatusResponse) => {\r\n toggleInput.disabled = false;\r\n if (chrome.runtime.lastError) {\r\n toggleInput.checked = currentEnabled;\r\n return;\r\n }\r\n render(response);\r\n });\r\n});\r\n\r\n// Listen for push updates from the service worker (approval state changes)\r\nchrome.runtime.onMessage.addListener((msg) => {\r\n if (msg.type === 'approvalUpdate') {\r\n renderApprovals(msg.pendingApprovals ?? []);\r\n }\r\n});\r\n\r\nfetchStatus();\r\n"],
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
5
  "mappings": "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",
6
6
  "names": []
7
7
  }