comber 0.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 (158) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +732 -0
  3. package/action.yml +279 -0
  4. package/comber.config.example.json +64 -0
  5. package/dist/api/classify.js +99 -0
  6. package/dist/api/classify.js.map +1 -0
  7. package/dist/api/driver.js +235 -0
  8. package/dist/api/driver.js.map +1 -0
  9. package/dist/api/graphql-driver.js +150 -0
  10. package/dist/api/graphql-driver.js.map +1 -0
  11. package/dist/api/graphql.js +115 -0
  12. package/dist/api/graphql.js.map +1 -0
  13. package/dist/api/har-driver.js +167 -0
  14. package/dist/api/har-driver.js.map +1 -0
  15. package/dist/api/har.js +100 -0
  16. package/dist/api/har.js.map +1 -0
  17. package/dist/api/load.js +83 -0
  18. package/dist/api/load.js.map +1 -0
  19. package/dist/api/openapi.js +159 -0
  20. package/dist/api/openapi.js.map +1 -0
  21. package/dist/api/request.js +141 -0
  22. package/dist/api/request.js.map +1 -0
  23. package/dist/api/schema.js +126 -0
  24. package/dist/api/schema.js.map +1 -0
  25. package/dist/cli.js +352 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/comment.js +164 -0
  28. package/dist/comment.js.map +1 -0
  29. package/dist/config.js +664 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/connectors/builtin/discord.js +55 -0
  32. package/dist/connectors/builtin/github-checks.js +53 -0
  33. package/dist/connectors/builtin/junit.js +70 -0
  34. package/dist/connectors/builtin/linear.js +58 -0
  35. package/dist/connectors/builtin/slack.js +60 -0
  36. package/dist/connectors/builtin/webhook.js +39 -0
  37. package/dist/connectors/index.js +3 -0
  38. package/dist/connectors/loader.js +72 -0
  39. package/dist/connectors/registry.js +28 -0
  40. package/dist/connectors/types.js +23 -0
  41. package/dist/core/action-gate.js +48 -0
  42. package/dist/core/baseline.js +85 -0
  43. package/dist/core/baseline.js.map +1 -0
  44. package/dist/core/blind-spots.js +22 -0
  45. package/dist/core/blind-spots.js.map +1 -0
  46. package/dist/core/contract-extras.js +1 -0
  47. package/dist/core/contract-extras.js.map +1 -0
  48. package/dist/core/contract.js +1 -0
  49. package/dist/core/contract.js.map +1 -0
  50. package/dist/core/coverage.js +70 -0
  51. package/dist/core/coverage.js.map +1 -0
  52. package/dist/core/crawl.js +229 -0
  53. package/dist/core/crawl.js.map +1 -0
  54. package/dist/core/egress.js +128 -0
  55. package/dist/core/egress.js.map +1 -0
  56. package/dist/core/ledger.js +41 -0
  57. package/dist/core/ledger.js.map +1 -0
  58. package/dist/core/net.js +39 -0
  59. package/dist/core/pipeline.js +191 -0
  60. package/dist/core/pipeline.js.map +1 -0
  61. package/dist/core/plan.js +121 -0
  62. package/dist/core/plan.js.map +1 -0
  63. package/dist/core/redact.js +240 -0
  64. package/dist/core/redact.js.map +1 -0
  65. package/dist/core/replay.js +263 -0
  66. package/dist/core/replay.js.map +1 -0
  67. package/dist/core/report.js +253 -0
  68. package/dist/core/report.js.map +1 -0
  69. package/dist/core/safety-judge.js +116 -0
  70. package/dist/core/safety.js +92 -0
  71. package/dist/core/safety.js.map +1 -0
  72. package/dist/core/signal-normalize.js +190 -0
  73. package/dist/core/signal-normalize.js.map +1 -0
  74. package/dist/core/signals.js +5 -0
  75. package/dist/core/signals.js.map +1 -0
  76. package/dist/core/triage.js +104 -0
  77. package/dist/core/triage.js.map +1 -0
  78. package/dist/core/types.js +1 -0
  79. package/dist/core/types.js.map +1 -0
  80. package/dist/desktop/desktop-profile.js +6 -0
  81. package/dist/desktop/desktop-profile.js.map +1 -0
  82. package/dist/desktop/driver.js +433 -0
  83. package/dist/desktop/driver.js.map +1 -0
  84. package/dist/desktop/evidence.js +206 -0
  85. package/dist/desktop/evidence.js.map +1 -0
  86. package/dist/desktop/macos/accessibility-runner.js +118 -0
  87. package/dist/desktop/macos/accessibility-runner.js.map +1 -0
  88. package/dist/desktop/macos/types.js +1 -0
  89. package/dist/desktop/macos/types.js.map +1 -0
  90. package/dist/desktop/preflight.js +107 -0
  91. package/dist/desktop/preflight.js.map +1 -0
  92. package/dist/desktop/proxy-proof.js +185 -0
  93. package/dist/desktop/proxy-proof.js.map +1 -0
  94. package/dist/desktop/view.js +348 -0
  95. package/dist/desktop/view.js.map +1 -0
  96. package/dist/desktop/windows/types.js +1 -0
  97. package/dist/desktop/windows/types.js.map +1 -0
  98. package/dist/desktop/windows/uia-runner.js +127 -0
  99. package/dist/desktop/windows/uia-runner.js.map +1 -0
  100. package/dist/native/apple.js +14 -0
  101. package/dist/native/apple.js.map +1 -0
  102. package/dist/native/device-profile.js +6 -0
  103. package/dist/native/device-profile.js.map +1 -0
  104. package/dist/native/driver.js +416 -0
  105. package/dist/native/driver.js.map +1 -0
  106. package/dist/native/evidence.js +164 -0
  107. package/dist/native/evidence.js.map +1 -0
  108. package/dist/native/load.js +9 -0
  109. package/dist/native/load.js.map +1 -0
  110. package/dist/native/maestro.js +153 -0
  111. package/dist/native/maestro.js.map +1 -0
  112. package/dist/native/preflight.js +72 -0
  113. package/dist/native/preflight.js.map +1 -0
  114. package/dist/native/proxy-proof.js +182 -0
  115. package/dist/native/proxy-proof.js.map +1 -0
  116. package/dist/native/samsung.js +34 -0
  117. package/dist/native/samsung.js.map +1 -0
  118. package/dist/native/signals.js +50 -0
  119. package/dist/native/signals.js.map +1 -0
  120. package/dist/native/view.js +139 -0
  121. package/dist/native/view.js.map +1 -0
  122. package/dist/profile.js +237 -0
  123. package/dist/profile.js.map +1 -0
  124. package/dist/sink.js +134 -0
  125. package/dist/sink.js.map +1 -0
  126. package/dist/sinks/file.js +49 -0
  127. package/dist/sinks/file.js.map +1 -0
  128. package/dist/sinks/http.js +38 -0
  129. package/dist/sinks/http.js.map +1 -0
  130. package/dist/sinks/index.js +5 -0
  131. package/dist/sinks/index.js.map +1 -0
  132. package/dist/sinks/multi.js +40 -0
  133. package/dist/sinks/multi.js.map +1 -0
  134. package/dist/sinks/paths.js +32 -0
  135. package/dist/sinks/paths.js.map +1 -0
  136. package/dist/sinks/record.js +58 -0
  137. package/dist/sinks/record.js.map +1 -0
  138. package/dist/sinks/types.js +1 -0
  139. package/dist/sinks/types.js.map +1 -0
  140. package/dist/web/breakage.js +210 -0
  141. package/dist/web/breakage.js.map +1 -0
  142. package/dist/web/controls.js +102 -0
  143. package/dist/web/controls.js.map +1 -0
  144. package/dist/web/driver.js +803 -0
  145. package/dist/web/driver.js.map +1 -0
  146. package/dist/web/elements.js +151 -0
  147. package/dist/web/elements.js.map +1 -0
  148. package/dist/web/fingerprint.js +13 -0
  149. package/dist/web/fingerprint.js.map +1 -0
  150. package/dist/web/forms.js +74 -0
  151. package/dist/web/forms.js.map +1 -0
  152. package/dist/web/links.js +194 -0
  153. package/dist/web/links.js.map +1 -0
  154. package/dist/web/types.js +1 -0
  155. package/dist/web/types.js.map +1 -0
  156. package/package.json +80 -0
  157. package/runtime/macos-accessibility-runner.js +275 -0
  158. package/runtime/windows-uia-runner.ps1 +539 -0
@@ -0,0 +1,275 @@
1
+ ObjC.import("Foundation");
2
+ ObjC.import("stdlib");
3
+
4
+ const ERROR_LIMIT = 240;
5
+ const MAX_DEPTH = 8;
6
+ const MAX_NODES = 300;
7
+ const MAX_CHILDREN_PER_NODE = 80;
8
+ let visitedNodes = 0;
9
+
10
+ function unwrap(value) {
11
+ return ObjC.unwrap(value);
12
+ }
13
+
14
+ function readStdin() {
15
+ const data = $.NSFileHandle.fileHandleWithStandardInput.readDataToEndOfFile;
16
+ const text = $.NSString.alloc.initWithDataEncoding(data, $.NSUTF8StringEncoding);
17
+ return text ? String(unwrap(text)) : "{}";
18
+ }
19
+
20
+ function commandName() {
21
+ const args = $.NSProcessInfo.processInfo.arguments;
22
+ const count = Number(args.count);
23
+ if (count < 1) return "";
24
+ return String(unwrap(args.objectAtIndex(count - 1)));
25
+ }
26
+
27
+ function ok(payload) {
28
+ writeJson({ ok: true, ...payload });
29
+ }
30
+
31
+ function fail(error) {
32
+ const message = String(error instanceof Error ? error.message : error).slice(0, ERROR_LIMIT);
33
+ writeJson({ ok: false, error: message });
34
+ }
35
+
36
+ function writeJson(payload) {
37
+ const text = $.NSString.alloc.initWithUTF8String(`${JSON.stringify(payload)}\n`);
38
+ const data = text.dataUsingEncoding($.NSUTF8StringEncoding);
39
+ $.NSFileHandle.fileHandleWithStandardOutput.writeData(data);
40
+ }
41
+
42
+ function text(value) {
43
+ if (value === undefined || value === null) return undefined;
44
+ const out = String(value);
45
+ return out.length ? out : undefined;
46
+ }
47
+
48
+ function bool(value) {
49
+ if (value === true || value === false) return value;
50
+ return undefined;
51
+ }
52
+
53
+ function read(fn) {
54
+ try {
55
+ return fn();
56
+ } catch (_) {
57
+ return undefined;
58
+ }
59
+ }
60
+
61
+ function attr(element, name) {
62
+ return read(() => element.attributes.byName(name).value());
63
+ }
64
+
65
+ function actionNames(element) {
66
+ return read(() => element.actions().map((action) => String(action.name()))) || [];
67
+ }
68
+
69
+ function runTask(launchPath, args) {
70
+ const task = $.NSTask.launchedTaskWithLaunchPathArguments(launchPath, args);
71
+ task.waitUntilExit;
72
+ if (task.terminationStatus !== 0) {
73
+ throw new Error(`${launchPath} exited ${task.terminationStatus}`);
74
+ }
75
+ }
76
+
77
+ function targetProcessName(target) {
78
+ if (target.processName) return target.processName;
79
+ if (target.appPath) {
80
+ const leaf = String(target.appPath).replace(/\/+$/, "").split("/").pop() || "";
81
+ return leaf.endsWith(".app") ? leaf.slice(0, -4) : leaf;
82
+ }
83
+ if (target.appId) return target.appId;
84
+ return "";
85
+ }
86
+
87
+ function launch(target) {
88
+ if (target.appPath) {
89
+ runTask("/usr/bin/open", ["-n", target.appPath]);
90
+ waitForProcess(target);
91
+ return;
92
+ }
93
+ if (target.appId) {
94
+ runTask("/usr/bin/open", ["-nb", target.appId]);
95
+ waitForProcess(target);
96
+ return;
97
+ }
98
+ if (target.processName) {
99
+ runTask("/usr/bin/open", ["-na", target.processName]);
100
+ waitForProcess(target);
101
+ return;
102
+ }
103
+ throw new Error("macOS Accessibility launch requires appPath, appId, or processName");
104
+ }
105
+
106
+ function waitForProcess(target) {
107
+ const processName = targetProcessName(target);
108
+ if (!processName) return;
109
+ const systemEvents = Application("System Events");
110
+ for (let attempt = 0; attempt < 25; attempt++) {
111
+ const exists = read(() => systemEvents.processes.byName(processName).exists()) === true;
112
+ const windows = exists ? read(() => systemEvents.processes.byName(processName).windows()) || [] : [];
113
+ if (exists && windows.length > 0) return;
114
+ delay(0.2);
115
+ }
116
+ }
117
+
118
+ function convert(element, path, depth) {
119
+ visitedNodes++;
120
+ const actions = actionNames(element);
121
+ const role = text(read(() => element.role())) || text(attr(element, "AXRole"));
122
+ const subrole = text(attr(element, "AXSubrole"));
123
+ const title = text(read(() => element.title())) || text(read(() => element.name())) || text(attr(element, "AXTitle"));
124
+ const value = text(read(() => element.value())) || text(attr(element, "AXValue"));
125
+ const description = text(read(() => element.description())) || text(attr(element, "AXDescription"));
126
+ const identifier = text(attr(element, "AXIdentifier")) || text(attr(element, "AXDOMIdentifier"));
127
+ const enabled = bool(read(() => element.enabled())) ?? bool(attr(element, "AXEnabled"));
128
+ const out = { path };
129
+ if (identifier) out.identifier = identifier;
130
+ if (title) out.title = title;
131
+ if (value) out.value = value;
132
+ if (role) out.role = role;
133
+ if (subrole) out.subrole = subrole;
134
+ if (description) out.description = description;
135
+ if (enabled !== undefined) out.enabled = enabled;
136
+ if (actions.length > 0) out.actions = actions;
137
+ if (visitedNodes >= MAX_NODES) {
138
+ out.isOpaque = true;
139
+ out.description = out.description || "Comber node budget reached";
140
+ return out;
141
+ }
142
+ const children = [];
143
+ if (depth < MAX_DEPTH) {
144
+ const rawChildren = read(() => element.uiElements()) || [];
145
+ const childCount = Math.min(rawChildren.length, MAX_CHILDREN_PER_NODE);
146
+ for (let index = 0; index < childCount && visitedNodes < MAX_NODES; index++) {
147
+ children.push(convert(rawChildren[index], `${path}/${index}`, depth + 1));
148
+ }
149
+ if (rawChildren.length > childCount || visitedNodes >= MAX_NODES) {
150
+ out.isOpaque = true;
151
+ out.description = out.description || "Comber child or node budget reached";
152
+ }
153
+ }
154
+ if (children.length > 0) out.children = children;
155
+ return out;
156
+ }
157
+
158
+ function snapshot(target) {
159
+ const processName = targetProcessName(target);
160
+ if (!processName) throw new Error("macOS Accessibility snapshot requires processName, appPath, or appId");
161
+ const systemEvents = Application("System Events");
162
+ const proc = systemEvents.processes.byName(processName);
163
+ const exists = read(() => proc.exists()) === true;
164
+ if (!exists) throw new Error(`macOS Accessibility process not found: ${processName}`);
165
+ const windows = read(() => proc.windows()) || [];
166
+ if (windows.length === 0) throw new Error(`macOS Accessibility process has no windows: ${processName}`);
167
+ const children = [];
168
+ for (let index = 0; index < windows.length; index++) {
169
+ children.push(convert(windows[index], `0/${index}`, 0));
170
+ }
171
+ const windowTitle = children[0] && (children[0].title || children[0].value) || processName;
172
+ return {
173
+ ...(target.appId ? { appId: target.appId } : {}),
174
+ ...(target.appPath ? { appPath: target.appPath } : {}),
175
+ processName,
176
+ windowTitle,
177
+ root: {
178
+ role: "AXApplication",
179
+ title: processName,
180
+ path: "0",
181
+ children,
182
+ },
183
+ };
184
+ }
185
+
186
+ function matches(element, selector, path) {
187
+ const identifier = text(attr(element, "AXIdentifier")) || text(attr(element, "AXDOMIdentifier"));
188
+ const title = text(read(() => element.title())) || text(read(() => element.name())) || text(attr(element, "AXTitle"));
189
+ const role = text(read(() => element.role())) || text(attr(element, "AXRole"));
190
+ if (selector.path && selector.path === path) return true;
191
+ if (selector.identifier && selector.identifier === identifier) return true;
192
+ if (selector.title && selector.title === title) return true;
193
+ if (selector.role && selector.role === role && selector.path === path) return true;
194
+ return false;
195
+ }
196
+
197
+ function findElement(element, selector, path) {
198
+ if (matches(element, selector, path)) return element;
199
+ const children = read(() => element.uiElements()) || [];
200
+ for (let index = 0; index < children.length; index++) {
201
+ const hit = findElement(children[index], selector, `${path}/${index}`);
202
+ if (hit) return hit;
203
+ }
204
+ return undefined;
205
+ }
206
+
207
+ function selectedElement(target, selector) {
208
+ const processName = targetProcessName(target);
209
+ const systemEvents = Application("System Events");
210
+ const windows = read(() => systemEvents.processes.byName(processName).windows()) || [];
211
+ for (let index = 0; index < windows.length; index++) {
212
+ const hit = findElement(windows[index], selector, `0/${index}`);
213
+ if (hit) return hit;
214
+ }
215
+ throw new Error("macOS Accessibility selector not found");
216
+ }
217
+
218
+ function perform(target, action) {
219
+ const element = selectedElement(target, action.selector);
220
+ const names = actionNames(element);
221
+ if (action.kind === "set-value") {
222
+ element.value = action.value || "";
223
+ return;
224
+ }
225
+ if (action.kind === "increment" && names.includes("AXIncrement")) {
226
+ element.actions.byName("AXIncrement").perform();
227
+ return;
228
+ }
229
+ if (action.kind === "decrement" && names.includes("AXDecrement")) {
230
+ element.actions.byName("AXDecrement").perform();
231
+ return;
232
+ }
233
+ if (names.includes("AXPress")) {
234
+ element.actions.byName("AXPress").perform();
235
+ return;
236
+ }
237
+ element.click();
238
+ }
239
+
240
+ function foreground(target) {
241
+ const processName = targetProcessName(target);
242
+ if (!processName) return "";
243
+ const systemEvents = Application("System Events");
244
+ const hit = read(() => systemEvents.processes.byName(processName).exists()) === true;
245
+ return hit ? processName : "";
246
+ }
247
+
248
+ try {
249
+ const command = commandName();
250
+ const payload = JSON.parse(readStdin());
251
+ if (command === "launch") {
252
+ launch(payload.target || {});
253
+ ok({});
254
+ } else if (command === "snapshot") {
255
+ ok({ tree: snapshot(payload.target || {}) });
256
+ } else if (command === "foreground") {
257
+ ok({ processName: foreground(payload.target || {}) });
258
+ } else if (command === "close") {
259
+ ok({});
260
+ } else if (
261
+ command === "press" ||
262
+ command === "toggle" ||
263
+ command === "set-value" ||
264
+ command === "select" ||
265
+ command === "increment" ||
266
+ command === "decrement"
267
+ ) {
268
+ perform(payload.target || {}, payload.action || {});
269
+ ok({});
270
+ } else {
271
+ throw new Error(`unsupported macOS Accessibility command: ${command}`);
272
+ }
273
+ } catch (error) {
274
+ fail(error);
275
+ }