@commandgarden/cli 2.0.0 → 2.2.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.
Files changed (54) hide show
  1. package/dist/main.js +74 -10
  2. package/node_modules/@commandgarden/app/dist/client/assets/index-Cglxej9D.css +1 -0
  3. package/node_modules/@commandgarden/app/dist/client/assets/index-Cju0_G1Y.js +397 -0
  4. package/node_modules/@commandgarden/app/dist/client/index.html +19 -19
  5. package/node_modules/@commandgarden/app/dist/server/routes/connectors.js +3 -0
  6. package/node_modules/@commandgarden/app/dist/server/routes/index.js +4 -0
  7. package/node_modules/@commandgarden/app/dist/server/routes/security-news-cache.d.ts +3 -0
  8. package/node_modules/@commandgarden/app/dist/server/routes/security-news-cache.js +17 -0
  9. package/node_modules/@commandgarden/app/dist/server/routes/skills.d.ts +2 -0
  10. package/node_modules/@commandgarden/app/dist/server/routes/skills.js +65 -0
  11. package/node_modules/@commandgarden/app/dist/server/store.d.ts +5 -0
  12. package/node_modules/@commandgarden/app/dist/server/store.js +46 -27
  13. package/node_modules/@commandgarden/app/package.json +42 -42
  14. package/node_modules/@commandgarden/chrome/dist/content-script.js.map +1 -1
  15. package/node_modules/@commandgarden/chrome/dist/manifest.json +16 -16
  16. package/node_modules/@commandgarden/chrome/dist/popup.html +218 -197
  17. package/node_modules/@commandgarden/chrome/dist/popup.js.map +1 -1
  18. package/node_modules/@commandgarden/chrome/dist/service-worker.js.map +1 -1
  19. package/node_modules/@commandgarden/chrome/package.json +25 -25
  20. package/node_modules/@commandgarden/daemon/connectors/ado-git-commits.eval.js +143 -143
  21. package/node_modules/@commandgarden/daemon/connectors/ado-git-commits.yaml +70 -70
  22. package/node_modules/@commandgarden/daemon/connectors/gcs-kb-content.yaml +58 -58
  23. package/node_modules/@commandgarden/daemon/connectors/gcs-kb-pages.yaml +47 -47
  24. package/node_modules/@commandgarden/daemon/connectors/jira-my-tickets.eval.js +115 -115
  25. package/node_modules/@commandgarden/daemon/connectors/jira-my-tickets.yaml +54 -54
  26. package/node_modules/@commandgarden/daemon/connectors/lib/msal-token.js +20 -20
  27. package/node_modules/@commandgarden/daemon/connectors/outlook-my-meetings.eval.js +186 -186
  28. package/node_modules/@commandgarden/daemon/connectors/outlook-my-meetings.yaml +41 -41
  29. package/node_modules/@commandgarden/daemon/connectors/saba-pending-training.eval.js +94 -0
  30. package/node_modules/@commandgarden/daemon/connectors/saba-pending-training.yaml +34 -0
  31. package/node_modules/@commandgarden/daemon/connectors/socket-security-news.yaml +63 -63
  32. package/node_modules/@commandgarden/daemon/connectors/teams-room-availability.eval.js +341 -341
  33. package/node_modules/@commandgarden/daemon/connectors/teams-room-availability.yaml +46 -46
  34. package/node_modules/@commandgarden/daemon/connectors/timetracking-projects.eval.js +52 -52
  35. package/node_modules/@commandgarden/daemon/connectors/timetracking-projects.yaml +42 -42
  36. package/node_modules/@commandgarden/daemon/connectors/timetracking-report.eval.js +81 -81
  37. package/node_modules/@commandgarden/daemon/connectors/timetracking-report.yaml +52 -52
  38. package/node_modules/@commandgarden/daemon/connectors/tldrsec-newsletter.eval.js +27 -0
  39. package/node_modules/@commandgarden/daemon/connectors/tldrsec-newsletter.yaml +38 -0
  40. package/node_modules/@commandgarden/daemon/connectors/tokenmaster-clients-list.yaml +55 -55
  41. package/node_modules/@commandgarden/daemon/dist/audit-store.js +8 -8
  42. package/node_modules/@commandgarden/daemon/package.json +32 -32
  43. package/node_modules/@commandgarden/shared/package.json +32 -32
  44. package/package.json +58 -57
  45. package/node_modules/@commandgarden/app/dist/client/assets/index-CThI0UCl.css +0 -1
  46. package/node_modules/@commandgarden/app/dist/client/assets/index-WANip_um.js +0 -252
  47. package/node_modules/@commandgarden/app/dist/server/daemon-client.test.js +0 -47
  48. package/node_modules/@commandgarden/app/dist/server/routes/routes.test.js +0 -137
  49. package/node_modules/@commandgarden/app/dist/server/store.test.js +0 -59
  50. package/node_modules/@commandgarden/chrome/dist/fetch-interceptor.js +0 -44
  51. package/node_modules/@commandgarden/daemon/dist/cdp-client.d.ts +0 -24
  52. package/node_modules/@commandgarden/daemon/dist/cdp-client.d.ts.map +0 -1
  53. package/node_modules/@commandgarden/daemon/dist/cdp-client.js +0 -150
  54. package/node_modules/@commandgarden/daemon/dist/cdp-client.js.map +0 -1
@@ -1,19 +1,19 @@
1
- <!DOCTYPE html>
2
- <html lang="en" data-theme="dark">
3
- <head>
4
- <script>
5
- (function(){var t=localStorage.getItem('theme');if(t){document.documentElement.setAttribute('data-theme',t)}else if(matchMedia('(prefers-color-scheme:dark)').matches){document.documentElement.setAttribute('data-theme','dark')}})();
6
- </script>
7
- <meta charset="UTF-8" />
8
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
- <link rel="preconnect" href="https://fonts.googleapis.com" />
10
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
11
- <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" />
12
- <title>commandGarden</title>
13
- <script type="module" crossorigin src="/assets/index-WANip_um.js"></script>
14
- <link rel="stylesheet" crossorigin href="/assets/index-CThI0UCl.css">
15
- </head>
16
- <body>
17
- <div id="root"></div>
18
- </body>
19
- </html>
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="dark">
3
+ <head>
4
+ <script>
5
+ (function(){var t=localStorage.getItem('theme');if(t){document.documentElement.setAttribute('data-theme',t)}else if(matchMedia('(prefers-color-scheme:dark)').matches){document.documentElement.setAttribute('data-theme','dark')}})();
6
+ </script>
7
+ <meta charset="UTF-8" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
9
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
11
+ <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" />
12
+ <title>commandGarden</title>
13
+ <script type="module" crossorigin src="/assets/index-Cju0_G1Y.js"></script>
14
+ <link rel="stylesheet" crossorigin href="/assets/index-Cglxej9D.css">
15
+ </head>
16
+ <body>
17
+ <div id="root"></div>
18
+ </body>
19
+ </html>
@@ -2,6 +2,9 @@ import { HIGH_RISK_CAPABILITIES } from '@commandgarden/shared';
2
2
  const APP_ROUTES = {
3
3
  'timetracking/report': '/apps/timetracking',
4
4
  'teams/room-availability': '/apps/rooms',
5
+ 'saba/pending-training': '/apps/saba',
6
+ 'tokenmaster/clients-list': '/apps/trusted-peer-expiry',
7
+ 'tokenmaster/client-trustedby': '/apps/trusted-peer-expiry',
5
8
  };
6
9
  export function connectorRoutes(app, daemon) {
7
10
  app.get('/api/connectors', async () => {
@@ -7,6 +7,8 @@ import { preferencesRoutes } from './preferences.js';
7
7
  import { goalsRoutes } from './goals.js';
8
8
  import { timetrackingCacheRoutes } from './timetracking-cache.js';
9
9
  import { journalRoutes } from './journal.js';
10
+ import { securityNewsCacheRoutes } from './security-news-cache.js';
11
+ import { skillsRoutes } from './skills.js';
10
12
  export function registerRoutes(app, daemon, store) {
11
13
  statusRoutes(app, daemon);
12
14
  connectorRoutes(app, daemon);
@@ -17,4 +19,6 @@ export function registerRoutes(app, daemon, store) {
17
19
  goalsRoutes(app, store);
18
20
  timetrackingCacheRoutes(app, store);
19
21
  journalRoutes(app, daemon, store);
22
+ securityNewsCacheRoutes(app, store);
23
+ skillsRoutes(app);
20
24
  }
@@ -0,0 +1,3 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { AppStore } from '../store.js';
3
+ export declare function securityNewsCacheRoutes(app: FastifyInstance, store: AppStore): void;
@@ -0,0 +1,17 @@
1
+ export function securityNewsCacheRoutes(app, store) {
2
+ app.get('/api/security-news/cache', async () => {
3
+ const cached = store.getCachedSecurityNews();
4
+ if (!cached)
5
+ return { ok: true, data: null, fetchedAt: null };
6
+ return { ok: true, data: cached.data, fetchedAt: cached.fetchedAt };
7
+ });
8
+ app.post('/api/security-news/cache', async (req, reply) => {
9
+ const { data } = req.body;
10
+ if (!Array.isArray(data)) {
11
+ reply.code(400);
12
+ return { ok: false, error: 'Missing data array' };
13
+ }
14
+ store.cacheSecurityNews(data);
15
+ return { ok: true };
16
+ });
17
+ }
@@ -0,0 +1,2 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ export declare function skillsRoutes(app: FastifyInstance): void;
@@ -0,0 +1,65 @@
1
+ import { join, dirname } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ function findSkillsDir() {
6
+ const candidates = [
7
+ // From app/{dist,src}/server/routes/ → commandGarden/skills/
8
+ join(__dirname, '..', '..', '..', '..', 'skills'),
9
+ // From app/{dist,src}/server/routes/ → app/skills/ (bundled via prepare-bundle)
10
+ join(__dirname, '..', '..', '..', 'skills'),
11
+ ];
12
+ for (const p of candidates) {
13
+ if (existsSync(p) && statSync(p).isDirectory())
14
+ return p;
15
+ }
16
+ return null;
17
+ }
18
+ function parseFrontmatter(content) {
19
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
20
+ if (!match)
21
+ return { description: '', body: content };
22
+ const frontmatter = match[1];
23
+ const body = match[2];
24
+ const descMatch = frontmatter.match(/description:\s*"(.+?)"/);
25
+ return { description: descMatch?.[1] ?? '', body };
26
+ }
27
+ function loadSkills(dir) {
28
+ const entries = readdirSync(dir, { withFileTypes: true });
29
+ const skills = [];
30
+ for (const entry of entries) {
31
+ if (!entry.isDirectory())
32
+ continue;
33
+ const skillDir = join(dir, entry.name);
34
+ const skillMd = join(skillDir, 'SKILL.md');
35
+ if (!existsSync(skillMd))
36
+ continue;
37
+ const skillContent = readFileSync(skillMd, 'utf-8');
38
+ const { description } = parseFrontmatter(skillContent);
39
+ const files = [];
40
+ for (const f of readdirSync(skillDir)) {
41
+ const fp = join(skillDir, f);
42
+ if (statSync(fp).isFile() && f.endsWith('.md')) {
43
+ files.push({ name: f, content: readFileSync(fp, 'utf-8') });
44
+ }
45
+ }
46
+ skills.push({ name: entry.name, description, files });
47
+ }
48
+ return skills.sort((a, b) => a.name.localeCompare(b.name));
49
+ }
50
+ export function skillsRoutes(app) {
51
+ app.get('/api/skills', async (_req, reply) => {
52
+ const dir = findSkillsDir();
53
+ if (!dir) {
54
+ reply.code(404).send({ ok: false, error: 'Skills directory not found' });
55
+ return;
56
+ }
57
+ try {
58
+ const skills = loadSkills(dir);
59
+ return { ok: true, skills };
60
+ }
61
+ catch (err) {
62
+ reply.code(500).send({ ok: false, error: err instanceof Error ? err.message : 'Failed to load skills' });
63
+ }
64
+ });
65
+ }
@@ -43,5 +43,10 @@ export declare class AppStore {
43
43
  data: Record<string, unknown>[];
44
44
  fetchedAt: string;
45
45
  } | null;
46
+ cacheSecurityNews(data: Record<string, unknown>[]): void;
47
+ getCachedSecurityNews(): {
48
+ data: Record<string, unknown>[];
49
+ fetchedAt: string;
50
+ } | null;
46
51
  close(): void;
47
52
  }
@@ -49,16 +49,16 @@ export class AppStore {
49
49
  });
50
50
  }
51
51
  migrate() {
52
- this.db.run(`CREATE TABLE IF NOT EXISTS preferences (
53
- key TEXT PRIMARY KEY,
54
- value TEXT NOT NULL
52
+ this.db.run(`CREATE TABLE IF NOT EXISTS preferences (
53
+ key TEXT PRIMARY KEY,
54
+ value TEXT NOT NULL
55
55
  )`);
56
- this.db.run(`CREATE TABLE IF NOT EXISTS saved_views (
57
- id TEXT PRIMARY KEY,
58
- app TEXT NOT NULL,
59
- name TEXT NOT NULL,
60
- config TEXT NOT NULL,
61
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
56
+ this.db.run(`CREATE TABLE IF NOT EXISTS saved_views (
57
+ id TEXT PRIMARY KEY,
58
+ app TEXT NOT NULL,
59
+ name TEXT NOT NULL,
60
+ config TEXT NOT NULL,
61
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
62
62
  )`);
63
63
  // Drop legacy goals table (had category column) and recreate with activity
64
64
  try {
@@ -76,26 +76,31 @@ export class AppStore {
76
76
  }
77
77
  catch { /* ignore */ }
78
78
  }
79
- this.db.run(`CREATE TABLE IF NOT EXISTS goals (
80
- id INTEGER PRIMARY KEY AUTOINCREMENT,
81
- month TEXT NOT NULL,
82
- project_id TEXT NOT NULL,
83
- activity TEXT NOT NULL DEFAULT '',
84
- target_days REAL NOT NULL,
85
- target_hours REAL NOT NULL,
86
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
87
- updated_at TEXT NOT NULL DEFAULT (datetime('now')),
88
- UNIQUE(month, project_id, activity)
79
+ this.db.run(`CREATE TABLE IF NOT EXISTS goals (
80
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
81
+ month TEXT NOT NULL,
82
+ project_id TEXT NOT NULL,
83
+ activity TEXT NOT NULL DEFAULT '',
84
+ target_days REAL NOT NULL,
85
+ target_hours REAL NOT NULL,
86
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
87
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
88
+ UNIQUE(month, project_id, activity)
89
89
  )`);
90
- this.db.run(`CREATE TABLE IF NOT EXISTS report_cache (
91
- month TEXT PRIMARY KEY,
92
- data TEXT NOT NULL,
93
- fetched_at TEXT NOT NULL
90
+ this.db.run(`CREATE TABLE IF NOT EXISTS report_cache (
91
+ month TEXT PRIMARY KEY,
92
+ data TEXT NOT NULL,
93
+ fetched_at TEXT NOT NULL
94
94
  )`);
95
- this.db.run(`CREATE TABLE IF NOT EXISTS projects_cache (
96
- id INTEGER PRIMARY KEY CHECK (id = 1),
97
- data TEXT NOT NULL,
98
- fetched_at TEXT NOT NULL
95
+ this.db.run(`CREATE TABLE IF NOT EXISTS projects_cache (
96
+ id INTEGER PRIMARY KEY CHECK (id = 1),
97
+ data TEXT NOT NULL,
98
+ fetched_at TEXT NOT NULL
99
+ )`);
100
+ this.db.run(`CREATE TABLE IF NOT EXISTS security_news_cache (
101
+ id INTEGER PRIMARY KEY CHECK (id = 1),
102
+ data TEXT NOT NULL,
103
+ fetched_at TEXT NOT NULL
99
104
  )`);
100
105
  }
101
106
  getPreference(key) {
@@ -195,6 +200,20 @@ export class AppStore {
195
200
  fetchedAt: rows[0].fetched_at,
196
201
  };
197
202
  }
203
+ cacheSecurityNews(data) {
204
+ const now = new Date().toISOString();
205
+ this.db.run('INSERT OR REPLACE INTO security_news_cache (id, data, fetched_at) VALUES (1, ?, ?)', [JSON.stringify(data), now]);
206
+ this.persist();
207
+ }
208
+ getCachedSecurityNews() {
209
+ const rows = this.query('SELECT data, fetched_at FROM security_news_cache WHERE id = 1');
210
+ if (rows.length === 0)
211
+ return null;
212
+ return {
213
+ data: JSON.parse(rows[0].data),
214
+ fetchedAt: rows[0].fetched_at,
215
+ };
216
+ }
198
217
  close() {
199
218
  this.db.close();
200
219
  }
@@ -1,42 +1,42 @@
1
- {
2
- "name": "@commandgarden/app",
3
- "version": "0.1.0",
4
- "private": true,
5
- "type": "module",
6
- "files": [
7
- "dist"
8
- ],
9
- "scripts": {
10
- "build": "vite build && tsc -p tsconfig.server.json",
11
- "dev": "concurrently \"vite\" \"tsx watch src/server/main.ts\"",
12
- "start": "node dist/server/main.js",
13
- "test": "vitest run",
14
- "test:watch": "vitest"
15
- },
16
- "dependencies": {
17
- "@commandgarden/shared": "*",
18
- "@fastify/static": "^8.0.0",
19
- "fastify": "^5.0.0",
20
- "lucide-react": "^1.23.0",
21
- "react": "^18.3.0",
22
- "react-dom": "^18.3.0",
23
- "react-router-dom": "^6.23.0",
24
- "sql.js": "^1.11.0",
25
- "yaml": "^2.4.0"
26
- },
27
- "devDependencies": {
28
- "@types/node": "^22.0.0",
29
- "@types/react": "^18.3.0",
30
- "@types/react-dom": "^18.3.0",
31
- "@vitejs/plugin-react": "^4.3.0",
32
- "autoprefixer": "^10.4.0",
33
- "concurrently": "^9.0.0",
34
- "daisyui": "^4.12.0",
35
- "postcss": "^8.4.0",
36
- "tailwindcss": "^3.4.0",
37
- "tsx": "^4.0.0",
38
- "typescript": "^5.4.0",
39
- "vite": "^5.4.0",
40
- "vitest": "^2.0.0"
41
- }
42
- }
1
+ {
2
+ "name": "@commandgarden/app",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "scripts": {
10
+ "build": "vite build && tsc -p tsconfig.server.json",
11
+ "dev": "concurrently \"vite\" \"tsx watch src/server/main.ts\"",
12
+ "start": "node dist/server/main.js",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest"
15
+ },
16
+ "dependencies": {
17
+ "@commandgarden/shared": "*",
18
+ "@fastify/static": "^8.0.0",
19
+ "fastify": "^5.0.0",
20
+ "lucide-react": "^1.23.0",
21
+ "react": "^18.3.0",
22
+ "react-dom": "^18.3.0",
23
+ "react-router-dom": "^6.23.0",
24
+ "sql.js": "^1.11.0",
25
+ "yaml": "^2.4.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/node": "^22.0.0",
29
+ "@types/react": "^18.3.0",
30
+ "@types/react-dom": "^18.3.0",
31
+ "@vitejs/plugin-react": "^4.3.0",
32
+ "autoprefixer": "^10.4.0",
33
+ "concurrently": "^9.0.0",
34
+ "daisyui": "^4.12.0",
35
+ "postcss": "^8.4.0",
36
+ "tailwindcss": "^3.4.0",
37
+ "tsx": "^4.0.0",
38
+ "typescript": "^5.4.0",
39
+ "vite": "^5.4.0",
40
+ "vitest": "^2.0.0"
41
+ }
42
+ }
@@ -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\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"],
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"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,16 +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
- }
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
+ }