@commandgarden/cli 1.4.2 → 2.1.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 (91) hide show
  1. package/dist/main.js +67 -8
  2. package/node_modules/@commandgarden/app/dist/client/assets/index-GpK1-Tyq.js +255 -0
  3. package/node_modules/@commandgarden/app/dist/client/assets/index-JB_Cdl_F.css +1 -0
  4. package/node_modules/@commandgarden/app/dist/client/index.html +2 -2
  5. package/node_modules/@commandgarden/app/dist/server/journal/journal.config.d.ts +25 -0
  6. package/node_modules/@commandgarden/app/dist/server/journal/journal.config.js +31 -0
  7. package/node_modules/@commandgarden/app/dist/server/journal/journal.service.d.ts +37 -0
  8. package/node_modules/@commandgarden/app/dist/server/journal/journal.service.js +142 -0
  9. package/node_modules/@commandgarden/app/dist/server/journal/journal.types.d.ts +91 -0
  10. package/node_modules/@commandgarden/app/dist/server/journal/journal.types.js +4 -0
  11. package/node_modules/@commandgarden/app/dist/server/journal/sources/git.source.d.ts +21 -0
  12. package/node_modules/@commandgarden/app/dist/server/journal/sources/git.source.js +72 -0
  13. package/node_modules/@commandgarden/app/dist/server/journal/sources/jira.source.d.ts +15 -0
  14. package/node_modules/@commandgarden/app/dist/server/journal/sources/jira.source.js +42 -0
  15. package/node_modules/@commandgarden/app/dist/server/journal/sources/meetings.source.d.ts +21 -0
  16. package/node_modules/@commandgarden/app/dist/server/journal/sources/meetings.source.js +25 -0
  17. package/node_modules/@commandgarden/app/dist/server/journal/sources/timetracking.source.d.ts +17 -0
  18. package/node_modules/@commandgarden/app/dist/server/journal/sources/timetracking.source.js +83 -0
  19. package/node_modules/@commandgarden/app/dist/server/routes/connectors.js +2 -0
  20. package/node_modules/@commandgarden/app/dist/server/routes/index.js +6 -0
  21. package/node_modules/@commandgarden/app/dist/server/routes/journal.d.ts +9 -0
  22. package/node_modules/@commandgarden/app/dist/server/routes/journal.js +56 -0
  23. package/node_modules/@commandgarden/app/dist/server/routes/security-news-cache.d.ts +3 -0
  24. package/node_modules/@commandgarden/app/dist/server/routes/security-news-cache.js +17 -0
  25. package/node_modules/@commandgarden/app/dist/server/routes/skills.d.ts +2 -0
  26. package/node_modules/@commandgarden/app/dist/server/routes/skills.js +65 -0
  27. package/node_modules/@commandgarden/app/dist/server/store.d.ts +5 -0
  28. package/node_modules/@commandgarden/app/dist/server/store.js +19 -0
  29. package/node_modules/@commandgarden/chrome/dist/content-script.js +84 -2
  30. package/node_modules/@commandgarden/chrome/dist/content-script.js.map +2 -2
  31. package/node_modules/@commandgarden/chrome/dist/popup.html +74 -53
  32. package/node_modules/@commandgarden/chrome/dist/service-worker.js +73 -8
  33. package/node_modules/@commandgarden/chrome/dist/service-worker.js.map +3 -3
  34. package/node_modules/@commandgarden/daemon/connectors/ado-git-commits.eval.js +143 -0
  35. package/node_modules/@commandgarden/daemon/connectors/ado-git-commits.yaml +70 -0
  36. package/node_modules/@commandgarden/daemon/connectors/gcs-kb-content.yaml +58 -0
  37. package/node_modules/@commandgarden/daemon/connectors/gcs-kb-pages.yaml +47 -0
  38. package/node_modules/@commandgarden/daemon/connectors/jira-my-tickets.eval.js +115 -0
  39. package/node_modules/@commandgarden/daemon/connectors/jira-my-tickets.yaml +54 -0
  40. package/node_modules/@commandgarden/daemon/connectors/outlook-my-meetings.eval.js +186 -0
  41. package/node_modules/@commandgarden/daemon/connectors/outlook-my-meetings.yaml +41 -0
  42. package/node_modules/@commandgarden/daemon/connectors/socket-security-news.yaml +63 -0
  43. package/node_modules/@commandgarden/daemon/connectors/tldrsec-newsletter.eval.js +27 -0
  44. package/node_modules/@commandgarden/daemon/connectors/tldrsec-newsletter.yaml +38 -0
  45. package/node_modules/@commandgarden/daemon/connectors/tokenmaster-client-trustedby.yaml +62 -0
  46. package/node_modules/@commandgarden/daemon/connectors/tokenmaster-clients-list.yaml +55 -0
  47. package/node_modules/@commandgarden/daemon/connectors/wiz-blog-security.eval.js +29 -0
  48. package/node_modules/@commandgarden/daemon/connectors/wiz-blog-security.yaml +44 -0
  49. package/node_modules/@commandgarden/daemon/dist/daemon-runner.d.ts +12 -0
  50. package/node_modules/@commandgarden/daemon/dist/daemon-runner.d.ts.map +1 -0
  51. package/node_modules/@commandgarden/daemon/dist/daemon-runner.js +65 -0
  52. package/node_modules/@commandgarden/daemon/dist/daemon-runner.js.map +1 -0
  53. package/node_modules/@commandgarden/daemon/dist/server.d.ts.map +1 -1
  54. package/node_modules/@commandgarden/daemon/dist/server.js +87 -13
  55. package/node_modules/@commandgarden/daemon/dist/server.js.map +1 -1
  56. package/node_modules/@commandgarden/daemon/dist/transforms.d.ts +3 -0
  57. package/node_modules/@commandgarden/daemon/dist/transforms.d.ts.map +1 -0
  58. package/node_modules/@commandgarden/daemon/dist/transforms.js +36 -0
  59. package/node_modules/@commandgarden/daemon/dist/transforms.js.map +1 -0
  60. package/node_modules/@commandgarden/daemon/package.json +4 -2
  61. package/node_modules/@commandgarden/shared/dist/connector.d.ts +175 -0
  62. package/node_modules/@commandgarden/shared/dist/connector.d.ts.map +1 -1
  63. package/node_modules/@commandgarden/shared/dist/connector.js +2 -0
  64. package/node_modules/@commandgarden/shared/dist/connector.js.map +1 -1
  65. package/node_modules/@commandgarden/shared/dist/expression/evaluator.d.ts +2 -0
  66. package/node_modules/@commandgarden/shared/dist/expression/evaluator.d.ts.map +1 -1
  67. package/node_modules/@commandgarden/shared/dist/expression/evaluator.js.map +1 -1
  68. package/node_modules/@commandgarden/shared/dist/expression/filters.d.ts.map +1 -1
  69. package/node_modules/@commandgarden/shared/dist/expression/filters.js +5 -0
  70. package/node_modules/@commandgarden/shared/dist/expression/filters.js.map +1 -1
  71. package/node_modules/@commandgarden/shared/dist/fan-out.d.ts +28 -0
  72. package/node_modules/@commandgarden/shared/dist/fan-out.d.ts.map +1 -0
  73. package/node_modules/@commandgarden/shared/dist/fan-out.js +55 -0
  74. package/node_modules/@commandgarden/shared/dist/fan-out.js.map +1 -0
  75. package/node_modules/@commandgarden/shared/dist/index.d.ts +2 -1
  76. package/node_modules/@commandgarden/shared/dist/index.d.ts.map +1 -1
  77. package/node_modules/@commandgarden/shared/dist/index.js +3 -1
  78. package/node_modules/@commandgarden/shared/dist/index.js.map +1 -1
  79. package/node_modules/@commandgarden/shared/dist/loader.d.ts.map +1 -1
  80. package/node_modules/@commandgarden/shared/dist/loader.js +17 -0
  81. package/node_modules/@commandgarden/shared/dist/loader.js.map +1 -1
  82. package/node_modules/@commandgarden/shared/dist/pipeline.d.ts +223 -1
  83. package/node_modules/@commandgarden/shared/dist/pipeline.d.ts.map +1 -1
  84. package/node_modules/@commandgarden/shared/dist/pipeline.js +58 -2
  85. package/node_modules/@commandgarden/shared/dist/pipeline.js.map +1 -1
  86. package/node_modules/@commandgarden/shared/dist/protocol.d.ts +1 -0
  87. package/node_modules/@commandgarden/shared/dist/protocol.d.ts.map +1 -1
  88. package/node_modules/@commandgarden/shared/dist/protocol.js.map +1 -1
  89. package/package.json +1 -1
  90. package/node_modules/@commandgarden/app/dist/client/assets/index-B-_w54e9.css +0 -1
  91. package/node_modules/@commandgarden/app/dist/client/assets/index-Bb1BOK1E.js +0 -252
@@ -0,0 +1,41 @@
1
+ site: outlook
2
+ name: my-meetings
3
+ version: "1.0"
4
+ description: "User's own meeting schedule from Outlook Calendar for a given day"
5
+ access: read
6
+
7
+ domains:
8
+ - "outlook.cloud.microsoft.mcas.ms"
9
+ capabilities:
10
+ - navigate
11
+ - js_evaluate
12
+
13
+ args:
14
+ - name: date
15
+ type: string
16
+ required: true
17
+ help: "Day to fetch in YYYY-MM-DD format"
18
+ pattern: "^\\d{4}-\\d{2}-\\d{2}$"
19
+
20
+ columns:
21
+ - name: date
22
+ type: string
23
+ - name: subject
24
+ type: string
25
+ - name: start
26
+ type: string
27
+ - name: end
28
+ type: string
29
+ - name: durationMin
30
+ type: number
31
+ - name: state
32
+ type: string
33
+
34
+ pipeline:
35
+ - step: navigate
36
+ url: "https://outlook.cloud.microsoft.mcas.ms/calendar/deeplink/compose"
37
+ - step: wait
38
+ selector: "body"
39
+ timeout: 30000
40
+ - step: js_evaluate
41
+ file: outlook-my-meetings.eval.js
@@ -0,0 +1,63 @@
1
+ site: socket
2
+ name: security-news
3
+ version: "1.0"
4
+ description: "Latest security news from Socket.dev blog"
5
+ access: read
6
+
7
+ domains:
8
+ - "socket.dev"
9
+ capabilities:
10
+ - navigate
11
+ - cookie_read
12
+
13
+ args:
14
+ - name: tag
15
+ type: string
16
+ required: false
17
+ default: ""
18
+ help: "Filter by tag (e.g. 'Research', 'Security News')"
19
+
20
+ columns:
21
+ - name: title
22
+ type: string
23
+ - name: summary
24
+ type: string
25
+ - name: url
26
+ type: string
27
+ - name: date
28
+ type: string
29
+ - name: author
30
+ type: string
31
+ - name: tags
32
+ type: string
33
+
34
+ pipeline:
35
+ - step: navigate
36
+ url: "https://socket.dev/"
37
+ - step: wait
38
+ selector: "body"
39
+ timeout: 30000
40
+ - step: fetch
41
+ url: "https://socket.dev/api/blog/feed.json"
42
+ method: "GET"
43
+ headers:
44
+ Accept: "application/json"
45
+ as: "feed"
46
+ - step: transform
47
+ type: json_unwrap
48
+ input: feed
49
+ as: _data
50
+ options:
51
+ path: "items"
52
+ - step: map
53
+ fields:
54
+ title: "${{ row.title }}"
55
+ summary: "${{ row.summary }}"
56
+ url: "${{ row.url }}"
57
+ date: "${{ row.date_published }}"
58
+ author: "${{ row.author.name }}"
59
+ tags: "${{ row.tags }}"
60
+ - step: filter # empty tag = show all (contains '' is always true)
61
+ field: tags
62
+ operator: contains
63
+ value: "${{ args.tag | default('') }}"
@@ -0,0 +1,27 @@
1
+ // tldrsec-newsletter.eval.js
2
+ // Extracts newsletter posts from Remix's __remixContext embedded JSON.
3
+ // Returns an array of flattened post objects for the pipeline.
4
+ // NOTE: No IIFE — evaluateInPage wraps this in AsyncFunction already.
5
+
6
+ const ctx = window.__remixContext;
7
+ if (!ctx) throw new Error('__remixContext not found');
8
+
9
+ const loaderData = ctx.state?.loaderData;
10
+ if (!loaderData) throw new Error('loaderData not found in __remixContext');
11
+
12
+ // The route key for the tag/category page
13
+ const categoryData = loaderData['routes/t/$category'];
14
+ if (!categoryData) throw new Error('routes/t/$category loader data not found');
15
+
16
+ const posts = categoryData.paginatedPosts?.posts;
17
+ if (!Array.isArray(posts)) throw new Error('paginatedPosts.posts is not an array');
18
+
19
+ return posts.map((p) => ({
20
+ title: p.web_title || p.meta_default_title || '',
21
+ slug: p.slug || '',
22
+ url: 'https://tldrsec.com/p/' + (p.slug || ''),
23
+ publishedAt: p.override_scheduled_at || p.created_at || '',
24
+ excerpt: p.web_subtitle || p.meta_default_description || '',
25
+ authors: (p.authors || []).map((a) => a.name).join(', '),
26
+ tags: (p.content_tags || []).map((t) => t.display).join(', '),
27
+ }));
@@ -0,0 +1,38 @@
1
+ site: tldrsec
2
+ name: newsletter
3
+ version: "1.0"
4
+ description: "Latest newsletter issues from tl;dr sec — titles, summaries, authors, tags, and publish dates"
5
+ access: read
6
+
7
+ domains:
8
+ - "tldrsec.com"
9
+ capabilities:
10
+ - navigate
11
+ - js_evaluate
12
+
13
+ args: []
14
+
15
+ columns:
16
+ - name: title
17
+ type: string
18
+ - name: slug
19
+ type: string
20
+ - name: url
21
+ type: string
22
+ - name: publishedAt
23
+ type: string
24
+ - name: excerpt
25
+ type: string
26
+ - name: authors
27
+ type: string
28
+ - name: tags
29
+ type: string
30
+
31
+ pipeline:
32
+ - step: navigate
33
+ url: "https://tldrsec.com/t/Newsletter"
34
+ - step: wait
35
+ selector: "body"
36
+ timeout: 15000
37
+ - step: js_evaluate
38
+ file: tldrsec-newsletter.eval.js
@@ -0,0 +1,62 @@
1
+ site: tokenmaster
2
+ name: client-trustedby
3
+ version: "2.0"
4
+ description: "List the clients that trust a given client in TokenMaster"
5
+ access: read
6
+
7
+ vars:
8
+ domains:
9
+ emea: tma.query.api.dvb.corpinter.net
10
+ amap: tma.query.api.am.dvb.corpinter.net
11
+ cn: tma.query.api.dvb.corpinter.net.cn
12
+
13
+ args:
14
+ - name: clientid
15
+ type: string
16
+ required: true
17
+ help: "The client ID to look up trustedby relationships for"
18
+ - name: region
19
+ type: string
20
+ required: false
21
+ default: "emea"
22
+ enum: [emea, amap, cn]
23
+ help: "Region to query (emea, amap, cn, or 'all' for fan-out). Default: emea"
24
+
25
+ domains:
26
+ - "tma.query.api.dvb.corpinter.net"
27
+ - "tma.query.api.am.dvb.corpinter.net"
28
+ - "tma.query.api.dvb.corpinter.net.cn"
29
+ capabilities:
30
+ - navigate
31
+ - cookie_read
32
+
33
+ columns:
34
+ - name: id
35
+ type: string
36
+ - name: name
37
+ type: string
38
+ - name: is_onboard_client
39
+ type: boolean
40
+ - name: idDisplay
41
+ type: string
42
+ - name: expiry_date
43
+ type: string
44
+
45
+ pipeline:
46
+ - step: navigate
47
+ url: "https://${{ args.region | lookup(vars.domains) }}/v2/clients/${{ args.clientid }}/trustedby"
48
+ - step: wait
49
+ selector: "body"
50
+ timeout: 30000
51
+ - step: fetch
52
+ url: "https://${{ args.region | lookup(vars.domains) }}/v2/clients/${{ args.clientid }}/trustedby"
53
+ method: "GET"
54
+ headers:
55
+ Accept: "application/json"
56
+ - step: map
57
+ fields:
58
+ id: "${{ row.id }}"
59
+ name: "${{ row.name }}"
60
+ is_onboard_client: "${{ row.is_onboard_client }}"
61
+ idDisplay: "${{ row.idDisplay }}"
62
+ expiry_date: "${{ row.expiry_date }}"
@@ -0,0 +1,55 @@
1
+ site: tokenmaster
2
+ name: clients-list
3
+ version: "2.0"
4
+ description: "List the clients managed by the current user in TokenMaster"
5
+ access: read
6
+
7
+ vars:
8
+ domains:
9
+ emea: tma.query.api.dvb.corpinter.net
10
+ amap: tma.query.api.am.dvb.corpinter.net
11
+ cn: tma.query.api.dvb.corpinter.net.cn
12
+
13
+ args:
14
+ - name: region
15
+ type: string
16
+ required: false
17
+ default: "all"
18
+ enum: [emea, amap, cn]
19
+ help: "Region to query (emea, amap, cn, or 'all' for fan-out). Default: all"
20
+
21
+ domains:
22
+ - "tma.query.api.dvb.corpinter.net"
23
+ - "tma.query.api.am.dvb.corpinter.net"
24
+ - "tma.query.api.dvb.corpinter.net.cn"
25
+ capabilities:
26
+ - navigate
27
+ - cookie_read
28
+
29
+ columns:
30
+ - name: id
31
+ type: string
32
+ - name: name
33
+ type: string
34
+ - name: status
35
+ type: string
36
+ - name: admins
37
+ type: string
38
+
39
+ pipeline:
40
+ - step: navigate
41
+ url: "https://${{ args.region | lookup(vars.domains) }}/v1/clients"
42
+ - step: wait
43
+ selector: "body"
44
+ timeout: 30000
45
+ - step: fetch
46
+ url: "https://${{ args.region | lookup(vars.domains) }}/v1/clients"
47
+ method: "GET"
48
+ headers:
49
+ Accept: "application/json"
50
+ - step: map
51
+ fields:
52
+ id: "${{ row.id }}"
53
+ name: "${{ row.name }}"
54
+ status: "${{ row.status }}"
55
+ admins: "${{ row.admins }}"
@@ -0,0 +1,29 @@
1
+ // wiz-blog-security.eval.js
2
+ // Extracts blog posts from Next.js __NEXT_DATA__ embedded JSON.
3
+ // Returns an array of flattened post objects for the pipeline.
4
+ // NOTE: No IIFE — evaluateInPage wraps this in AsyncFunction already.
5
+
6
+ const script = document.getElementById('__NEXT_DATA__');
7
+ if (!script) throw new Error('__NEXT_DATA__ script tag not found');
8
+
9
+ const raw = script.textContent;
10
+ if (!raw) throw new Error('__NEXT_DATA__ script tag is empty');
11
+
12
+ const nextData = JSON.parse(raw);
13
+ const pageProps = nextData?.props?.pageProps;
14
+ if (!pageProps) throw new Error('pageProps not found in __NEXT_DATA__');
15
+
16
+ const posts = pageProps.initialPosts;
17
+ if (!Array.isArray(posts)) throw new Error('initialPosts is not an array');
18
+
19
+ return posts
20
+ .filter((p) => !p.hiddenFromArchive)
21
+ .map((p) => ({
22
+ title: p.title || '',
23
+ slug: p.slug || '',
24
+ url: 'https://www.wiz.io/blog/' + (p.slug || ''),
25
+ publishedAt: p._firstPublishedAt || '',
26
+ excerpt: p.excerpt || '',
27
+ authors: (p.authors || []).map((a) => a.fullName).join(', '),
28
+ tags: (p.tags || []).map((t) => t.name).join(', '),
29
+ }));
@@ -0,0 +1,44 @@
1
+ site: wiz
2
+ name: blog-security
3
+ version: "1.0"
4
+ description: "Latest security blog posts from Wiz — titles, excerpts, authors, tags, and publish dates"
5
+ access: read
6
+
7
+ domains:
8
+ - "www.wiz.io"
9
+ capabilities:
10
+ - navigate
11
+ - js_evaluate
12
+
13
+ args:
14
+ - name: tag
15
+ type: string
16
+ required: false
17
+ default: "security"
18
+ pattern: "^[a-z0-9-]+$"
19
+ help: "Blog tag slug to filter by (e.g. 'security', 'research', 'ai'). Default: security"
20
+
21
+ columns:
22
+ - name: title
23
+ type: string
24
+ - name: slug
25
+ type: string
26
+ - name: url
27
+ type: string
28
+ - name: publishedAt
29
+ type: string
30
+ - name: excerpt
31
+ type: string
32
+ - name: authors
33
+ type: string
34
+ - name: tags
35
+ type: string
36
+
37
+ pipeline:
38
+ - step: navigate
39
+ url: "https://www.wiz.io/blog/tag/${{ args.tag }}"
40
+ - step: wait
41
+ selector: "#__NEXT_DATA__"
42
+ timeout: 15000
43
+ - step: js_evaluate
44
+ file: wiz-blog-security.eval.js
@@ -0,0 +1,12 @@
1
+ import type { PipelineStep } from '@commandgarden/shared';
2
+ export interface DaemonRunnerInput {
3
+ steps: PipelineStep[];
4
+ data: Record<string, unknown>[];
5
+ vars: Record<string, unknown>;
6
+ args?: Record<string, string | number | boolean>;
7
+ }
8
+ export declare function runDaemonSteps(input: DaemonRunnerInput): {
9
+ data: Record<string, unknown>[];
10
+ vars: Record<string, unknown>;
11
+ };
12
+ //# sourceMappingURL=daemon-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-runner.d.ts","sourceRoot":"","sources":["../src/daemon-runner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAsC,MAAM,uBAAuB,CAAC;AAI9F,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;CAClD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,iBAAiB,GAAG;IACxD,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,CAwDA"}
@@ -0,0 +1,65 @@
1
+ import { interpolate } from '@commandgarden/shared';
2
+ import { getTransform } from './transforms.js';
3
+ export function runDaemonSteps(input) {
4
+ const vars = { ...input.vars };
5
+ const args = input.args ?? {};
6
+ let data = input.data;
7
+ for (const step of input.steps) {
8
+ switch (step.step) {
9
+ case 'transform': {
10
+ const ts = step;
11
+ const inputVal = vars[ts.input] ?? '';
12
+ const fn = getTransform(ts.type);
13
+ const result = fn(inputVal, ts.options);
14
+ if (Array.isArray(result)) {
15
+ data = result;
16
+ }
17
+ else if (typeof result === 'object') {
18
+ Object.assign(vars, result);
19
+ }
20
+ else {
21
+ vars[ts.as] = result;
22
+ }
23
+ break;
24
+ }
25
+ // map and filter are dual-mode — they run here when they appear after a transform
26
+ case 'map': {
27
+ const ms = step;
28
+ data = data.map(row => {
29
+ const mapped = {};
30
+ const ctx = { args, vars: { ...vars, row }, cookies: {}, row };
31
+ for (const [key, expr] of Object.entries(ms.fields)) {
32
+ mapped[key] = interpolate(expr, ctx);
33
+ }
34
+ return mapped;
35
+ });
36
+ break;
37
+ }
38
+ case 'filter': {
39
+ const fs = step;
40
+ const filterCtx = { args, vars, cookies: {} };
41
+ const filterValue = interpolate(fs.value, filterCtx);
42
+ data = data.filter(row => {
43
+ const actual = row[fs.field];
44
+ switch (fs.operator) {
45
+ case 'eq': return actual === filterValue;
46
+ case 'ne': return actual !== filterValue;
47
+ case 'contains': return String(actual).includes(String(filterValue));
48
+ case 'matches': try {
49
+ return new RegExp(String(filterValue)).test(String(actual));
50
+ }
51
+ catch {
52
+ return false;
53
+ }
54
+ default: return true;
55
+ }
56
+ });
57
+ break;
58
+ }
59
+ default:
60
+ throw new Error(`DaemonRunner does not support step type: ${step.step}`);
61
+ }
62
+ }
63
+ return { data, vars };
64
+ }
65
+ //# sourceMappingURL=daemon-runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-runner.js","sourceRoot":"","sources":["../src/daemon-runner.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,SAAS,CAAC;AAM9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAKxC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,sSA+TlD"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,SAAS,CAAC;AAM9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAKxC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,UAAU,EAAE,UAAU,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,UAAU,sSA6YlD"}
@@ -2,8 +2,9 @@
2
2
  import Fastify from 'fastify';
3
3
  import websocket from '@fastify/websocket';
4
4
  import { randomUUID, createHash } from 'node:crypto';
5
- import { isRunCommandRequest, createAuditEvent, STEP_CAPABILITY_MAP } from '@commandgarden/shared';
5
+ import { isRunCommandRequest, createAuditEvent, STEP_CAPABILITY_MAP, expandFanOut, validateEnumArgs, splitPipeline } from '@commandgarden/shared';
6
6
  import { userInfo } from 'node:os';
7
+ import { runDaemonSteps } from './daemon-runner.js';
7
8
  import { validateCommand } from './validator.js';
8
9
  import { validateToken } from './auth.js';
9
10
  import { SseManager } from './sse-manager.js';
@@ -182,11 +183,16 @@ export async function createServer(deps) {
182
183
  reply.code(404).send({ ok: false, error: validation.denialReason });
183
184
  return;
184
185
  }
186
+ const connector = validation.connector;
187
+ const enumError = validateEnumArgs(body.args, connector.args ?? []);
188
+ if (enumError) {
189
+ reply.code(400).send({ ok: false, error: enumError });
190
+ return;
191
+ }
185
192
  if (!deps.wsRelay.connected) {
186
193
  reply.code(503).send({ ok: false, error: 'Extension not connected' });
187
194
  return;
188
195
  }
189
- const connector = validation.connector;
190
196
  const requiresApproval = connectorNeedsApproval(body.connector, connector);
191
197
  const approvalConfig = requiresApproval ? buildApprovalConfig() : undefined;
192
198
  const correlationId = randomUUID();
@@ -195,10 +201,20 @@ export async function createServer(deps) {
195
201
  ? createHash('sha256').update(meta.yamlContent).digest('hex').slice(0, 16)
196
202
  : undefined;
197
203
  const startTime = Date.now();
204
+ // Fan-out: expand "all" on enum args into sequential runs
205
+ const fanOut = expandFanOut(body.args, connector.args ?? []);
206
+ if (fanOut.isFanOut && requiresApproval) {
207
+ reply.code(400).send({
208
+ ok: false,
209
+ error: 'Fan-out (--region all) is not supported with approval-required connectors. Specify a single region.',
210
+ });
211
+ return;
212
+ }
213
+ const resolvedArgs = fanOut.argSets[0];
198
214
  try {
199
215
  deps.auditStore.insert(createAuditEvent({
200
216
  type: 'command.start', connector: body.connector, user,
201
- args: body.args,
217
+ args: fanOut.isFanOut ? body.args : resolvedArgs,
202
218
  domains: connector.domains, capabilities: [...connector.capabilities],
203
219
  correlationId, connectorHash,
204
220
  }));
@@ -207,24 +223,48 @@ export async function createServer(deps) {
207
223
  reply.code(500).send({ ok: false, error: 'Audit system unavailable \u2014 command blocked' });
208
224
  return;
209
225
  }
210
- const runPipeline = async (requestId) => {
226
+ const runPipeline = async (requestId, runArgs) => {
211
227
  try {
212
- const resp = await deps.wsRelay.send(connector, body.args, approvalConfig, undefined, requestId || undefined);
228
+ // Split pipeline: extension steps run in browser, daemon steps run here
229
+ const { extensionSteps, daemonSteps } = splitPipeline(connector.pipeline);
230
+ const extConnector = { ...connector, pipeline: extensionSteps };
231
+ const resp = await deps.wsRelay.send(extConnector, runArgs, approvalConfig, undefined, requestId || undefined);
232
+ // Run daemon-side steps (e.g. transform) if extension succeeded
233
+ let finalData = resp.data;
234
+ let finalVars = resp.vars ?? {};
235
+ if (resp.ok && daemonSteps.length > 0) {
236
+ // Also include data row fields as vars for daemon transforms
237
+ const dataVars = finalData.length > 0 ? finalData[0] : {};
238
+ const mergedVars = { ...dataVars, ...finalVars };
239
+ const daemonResult = runDaemonSteps({ steps: daemonSteps, data: finalData, vars: mergedVars, args: runArgs });
240
+ finalData = daemonResult.data;
241
+ finalVars = daemonResult.vars;
242
+ // Merge daemon vars back into data rows
243
+ if (finalData.length > 0) {
244
+ for (const row of finalData) {
245
+ for (const [k, v] of Object.entries(finalVars)) {
246
+ if (connector.columns?.some(c => c.name === k) && !(k in row)) {
247
+ row[k] = v;
248
+ }
249
+ }
250
+ }
251
+ }
252
+ }
213
253
  const durationMs = Date.now() - startTime;
214
254
  deps.auditStore.insert(createAuditEvent({
215
255
  type: resp.ok ? 'command.success' : 'command.error',
216
256
  connector: body.connector, user,
217
- args: body.args,
257
+ args: runArgs,
218
258
  domains: connector.domains, capabilities: [...connector.capabilities],
219
- rowCount: resp.data.length,
259
+ rowCount: finalData.length,
220
260
  columns: connector.columns?.map(c => c.name),
221
261
  durationMs, error: resp.error,
222
262
  correlationId, connectorHash, steps: resp.steps,
223
263
  }));
224
264
  return {
225
- ok: resp.ok, connector: body.connector, rowCount: resp.data.length,
265
+ ok: resp.ok, connector: body.connector, rowCount: finalData.length,
226
266
  columns: connector.columns?.map(c => c.name) ?? [],
227
- data: resp.data, error: resp.error, durationMs,
267
+ data: finalData, error: resp.error, durationMs,
228
268
  requestId, requiresApproval,
229
269
  };
230
270
  }
@@ -233,16 +273,50 @@ export async function createServer(deps) {
233
273
  const error = err instanceof Error ? err.message : 'Unknown error';
234
274
  deps.auditStore.insert(createAuditEvent({
235
275
  type: 'command.error', connector: body.connector, user,
236
- args: body.args, durationMs, error,
276
+ args: runArgs, durationMs, error,
237
277
  correlationId, connectorHash,
238
278
  }));
239
279
  return { ok: false, data: [], error, durationMs, requestId, requiresApproval };
240
280
  }
241
281
  };
282
+ if (fanOut.isFanOut) {
283
+ const { fanOutArgName } = fanOut;
284
+ const allData = [];
285
+ const errors = [];
286
+ for (const argSet of fanOut.argSets) {
287
+ const result = await runPipeline('', argSet);
288
+ if (result.ok) {
289
+ for (const row of result.data) {
290
+ row[fanOutArgName] = argSet[fanOutArgName];
291
+ allData.push(row);
292
+ }
293
+ }
294
+ else if (result.error) {
295
+ errors.push(`${argSet[fanOutArgName]}: ${result.error}`);
296
+ }
297
+ }
298
+ const columns = connector.columns?.map(c => c.name) ?? [];
299
+ if (!columns.includes(fanOutArgName)) {
300
+ columns.unshift(fanOutArgName);
301
+ }
302
+ const durationMs = Date.now() - startTime;
303
+ const hasErrors = errors.length > 0;
304
+ return {
305
+ ok: !hasErrors || allData.length > 0,
306
+ connector: body.connector,
307
+ rowCount: allData.length,
308
+ columns,
309
+ data: allData,
310
+ error: hasErrors ? errors.join('; ') : undefined,
311
+ durationMs,
312
+ requestId: '',
313
+ requiresApproval: false,
314
+ };
315
+ }
242
316
  if (requiresApproval) {
243
317
  const requestId = randomUUID();
244
318
  sse.waitForConnection(requestId, SSE_CONNECT_TIMEOUT_MS)
245
- .then(() => runPipeline(requestId))
319
+ .then(() => runPipeline(requestId, resolvedArgs))
246
320
  .then(result => {
247
321
  sse.send(requestId, 'result', result);
248
322
  sse.remove(requestId);
@@ -251,7 +325,7 @@ export async function createServer(deps) {
251
325
  const durationMs = Date.now() - startTime;
252
326
  deps.auditStore.insert(createAuditEvent({
253
327
  type: 'command.error', connector: body.connector, user,
254
- args: body.args, durationMs,
328
+ args: resolvedArgs, durationMs,
255
329
  error: err instanceof Error ? err.message : 'SSE timeout',
256
330
  }));
257
331
  });
@@ -261,7 +335,7 @@ export async function createServer(deps) {
261
335
  });
262
336
  }
263
337
  else {
264
- const result = await runPipeline('');
338
+ const result = await runPipeline('', resolvedArgs);
265
339
  if (!result.ok) {
266
340
  reply.code(500).send(result);
267
341
  return;