sitelooper 0.3.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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +625 -0
  3. package/bin/sitelooper.js +6 -0
  4. package/dist/agent/llm.js +460 -0
  5. package/dist/agent/llm.js.map +1 -0
  6. package/dist/agent/loop.js +870 -0
  7. package/dist/agent/loop.js.map +1 -0
  8. package/dist/agent/prompt.js +40 -0
  9. package/dist/agent/prompt.js.map +1 -0
  10. package/dist/agent/report.js +545 -0
  11. package/dist/agent/report.js.map +1 -0
  12. package/dist/agent/tools.js +1147 -0
  13. package/dist/agent/tools.js.map +1 -0
  14. package/dist/cli.js +1692 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/daemon/browser.js +218 -0
  17. package/dist/daemon/browser.js.map +1 -0
  18. package/dist/daemon/codegen.js +241 -0
  19. package/dist/daemon/codegen.js.map +1 -0
  20. package/dist/daemon/dialogs.js +57 -0
  21. package/dist/daemon/dialogs.js.map +1 -0
  22. package/dist/daemon/diff.js +198 -0
  23. package/dist/daemon/diff.js.map +1 -0
  24. package/dist/daemon/fingerprint.js +98 -0
  25. package/dist/daemon/fingerprint.js.map +1 -0
  26. package/dist/daemon/inputs.js +134 -0
  27. package/dist/daemon/inputs.js.map +1 -0
  28. package/dist/daemon/recorder.js +1232 -0
  29. package/dist/daemon/recorder.js.map +1 -0
  30. package/dist/daemon/refs.js +194 -0
  31. package/dist/daemon/refs.js.map +1 -0
  32. package/dist/daemon/server.js +1724 -0
  33. package/dist/daemon/server.js.map +1 -0
  34. package/dist/daemon/state.js +239 -0
  35. package/dist/daemon/state.js.map +1 -0
  36. package/dist/doctor.js +90 -0
  37. package/dist/doctor.js.map +1 -0
  38. package/dist/shared/paths.js +80 -0
  39. package/dist/shared/paths.js.map +1 -0
  40. package/dist/shared/protocol.js +28 -0
  41. package/dist/shared/protocol.js.map +1 -0
  42. package/dist/shared/secrets.js +92 -0
  43. package/dist/shared/secrets.js.map +1 -0
  44. package/dist/shared/text.js +39 -0
  45. package/dist/shared/text.js.map +1 -0
  46. package/dist/skills/compile.js +1420 -0
  47. package/dist/skills/compile.js.map +1 -0
  48. package/dist/skills/components.js +456 -0
  49. package/dist/skills/components.js.map +1 -0
  50. package/dist/skills/flow.js +1041 -0
  51. package/dist/skills/flow.js.map +1 -0
  52. package/dist/skills/learn.js +406 -0
  53. package/dist/skills/learn.js.map +1 -0
  54. package/dist/skills/ledger.js +304 -0
  55. package/dist/skills/ledger.js.map +1 -0
  56. package/dist/skills/relabel.js +206 -0
  57. package/dist/skills/relabel.js.map +1 -0
  58. package/dist/skills/repair.js +570 -0
  59. package/dist/skills/repair.js.map +1 -0
  60. package/dist/skills/replay.js +1281 -0
  61. package/dist/skills/replay.js.map +1 -0
  62. package/dist/skills/store.js +147 -0
  63. package/dist/skills/store.js.map +1 -0
  64. package/dist/spec/check.js +428 -0
  65. package/dist/spec/check.js.map +1 -0
  66. package/dist/spec/diagnostics.js +58 -0
  67. package/dist/spec/diagnostics.js.map +1 -0
  68. package/dist/spec/emit.js +2084 -0
  69. package/dist/spec/emit.js.map +1 -0
  70. package/dist/spec/index.js +62 -0
  71. package/dist/spec/index.js.map +1 -0
  72. package/dist/spec/ir.js +216 -0
  73. package/dist/spec/ir.js.map +1 -0
  74. package/dist/spec/lift.js +162 -0
  75. package/dist/spec/lift.js.map +1 -0
  76. package/dist/spec/locators.js +270 -0
  77. package/dist/spec/locators.js.map +1 -0
  78. package/dist/spec/lower.js +124 -0
  79. package/dist/spec/lower.js.map +1 -0
  80. package/dist/spec/repair.js +657 -0
  81. package/dist/spec/repair.js.map +1 -0
  82. package/dist/spec/rerecord.js +169 -0
  83. package/dist/spec/rerecord.js.map +1 -0
  84. package/dist/spec/rethread.js +120 -0
  85. package/dist/spec/rethread.js.map +1 -0
  86. package/package.json +50 -0
  87. package/skills/sitelooper/SKILL.md +228 -0
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Secret interpolation: `{{env:NAME}}` markers resolve to environment
3
+ * variables at the moment a tool executes, and ONLY there.
4
+ *
5
+ * The marker — not the value — is what travels everywhere else: the
6
+ * instruction the model reads, the recorded steps, compiled skills, flows,
7
+ * transcripts, and every artifact the bench publishes. The inner model
8
+ * copies the marker into its fill/type call verbatim and never sees the
9
+ * secret at all, so nothing upstream of the browser can leak it — the
10
+ * provider included. Skills recorded this way also survive credential
11
+ * rotation for free: replay re-resolves the marker each run.
12
+ *
13
+ * Scrubbing is the backstop for the one remaining channel: the PAGE. A form
14
+ * that echoes a typed value (a visible username, a confirmation banner)
15
+ * would put it into snapshots, reads, and diffs — so every tool result and
16
+ * diff is passed back through `scrubSecrets`, which replaces any value the
17
+ * session has resolved with its marker.
18
+ *
19
+ * The ledger is process-wide, which is session-wide by construction: one
20
+ * daemon process per session.
21
+ */
22
+ const SECRET_RE = /\{\{env:([A-Za-z_][A-Za-z0-9_]*)\}\}/g;
23
+ /** Values resolved this session, marker name → value. */
24
+ const ledger = new Map();
25
+ /** Minimum value length the scrubber will replace: shorter values would
26
+ * false-positive on ordinary page text ("1234" in a price). A secret this
27
+ * short is unsafe for unrelated reasons; the resolver still resolves it. */
28
+ const MIN_SCRUB_LEN = 4;
29
+ export function hasSecretMarker(text) {
30
+ SECRET_RE.lastIndex = 0;
31
+ return SECRET_RE.test(text);
32
+ }
33
+ /**
34
+ * Replace every `{{env:NAME}}` in `text` with the environment's value.
35
+ * Throws on an unset variable — a silently-unresolved marker would be typed
36
+ * into the page literally, which is never what the caller meant.
37
+ */
38
+ export function resolveSecrets(text) {
39
+ return text.replace(SECRET_RE, (_m, name) => {
40
+ const value = process.env[name];
41
+ if (value === undefined || value === '') {
42
+ throw new Error(`secret {{env:${name}}} cannot be resolved — the environment variable ${name} is not set where the daemon runs. ` +
43
+ `Set it and restart the session (sitelooper stop, then re-run with ${name} exported).`);
44
+ }
45
+ ledger.set(name, value);
46
+ return value;
47
+ });
48
+ }
49
+ /** resolveSecrets over every string field of a tool's args (non-mutating). */
50
+ export function resolveSecretsDeep(value) {
51
+ if (typeof value === 'string')
52
+ return (hasSecretMarker(value) ? resolveSecrets(value) : value);
53
+ if (Array.isArray(value))
54
+ return value.map((v) => resolveSecretsDeep(v));
55
+ if (value && typeof value === 'object') {
56
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, resolveSecretsDeep(v)]));
57
+ }
58
+ return value;
59
+ }
60
+ /**
61
+ * Replace every session-resolved secret VALUE in `text` with its marker.
62
+ * Applied to tool results and page diffs — anything the page echoed back.
63
+ */
64
+ export function scrubSecrets(text) {
65
+ let out = text;
66
+ // Longest first: a shorter secret that is a substring of a longer one
67
+ // (USER=james, PASS=james2024!) would otherwise split the longer value and
68
+ // let its tail through.
69
+ for (const [name, value] of [...ledger].sort((a, b) => b[1].length - a[1].length)) {
70
+ if (value.length < MIN_SCRUB_LEN)
71
+ continue;
72
+ if (out.includes(value))
73
+ out = out.split(value).join(`{{env:${name}}}`);
74
+ }
75
+ return out;
76
+ }
77
+ /** scrubSecrets over every string in a structure (non-mutating). */
78
+ export function scrubSecretsDeep(value) {
79
+ if (typeof value === 'string')
80
+ return scrubSecrets(value);
81
+ if (Array.isArray(value))
82
+ return value.map((v) => scrubSecretsDeep(v));
83
+ if (value && typeof value === 'object') {
84
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, scrubSecretsDeep(v)]));
85
+ }
86
+ return value;
87
+ }
88
+ /** Test seam: forget resolved values (a daemon process never needs this). */
89
+ export function clearSecretLedger() {
90
+ ledger.clear();
91
+ }
92
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../../src/shared/secrets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /** `text` cut to `max` characters with an ellipsis, or unchanged when it fits. */
2
+ export function clip(text, max) {
3
+ return text.length <= max ? text : text.slice(0, max) + '…';
4
+ }
5
+ /**
6
+ * Clock and calendar tokens are the RECORDING's moment, not part of what a
7
+ * page element IS: kanboard names its due-date textbox after the current
8
+ * minute ("09/03/2026 07:22") and labels a summary row "Due date:
9
+ * 12/31/2026 07:40". Recorded verbatim, such a name matches nothing nine
10
+ * minutes later. maskVolatile() turns each token into the `{{*}}` wildcard
11
+ * for stored expectations; volatileMatcher() turns a recorded locator name
12
+ * into a RegExp that treats the same tokens as wildcards.
13
+ */
14
+ export const WILDCARD = '{{*}}';
15
+ const VOLATILE_TOKEN = /\b\d{1,2}:\d{2}(?::\d{2})?\b|\b\d{1,4}[/.-]\d{1,2}[/.-]\d{1,4}\b/g;
16
+ export function maskVolatile(line) {
17
+ return line.replace(VOLATILE_TOKEN, WILDCARD);
18
+ }
19
+ const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
20
+ /**
21
+ * The recorded string itself when it carries no volatile token, else an
22
+ * anchored RegExp in which each token matches any token of the same shape —
23
+ * another time, another date — and nothing else, so the rest of the name is
24
+ * still matched exactly.
25
+ */
26
+ /**
27
+ * What one masked token stood for, as regex SOURCE. Exported because a
28
+ * compiled spec has to rebuild the same matcher from a line the store already
29
+ * masked (see spec/locators.ts, maskedMatcherSource) and a second copy of this
30
+ * shape would be free to drift away from this one.
31
+ */
32
+ export const VOLATILE_TOKEN_SHAPE = '(?:\\d{1,2}:\\d{2}(?::\\d{2})?|\\d{1,4}[/.-]\\d{1,2}[/.-]\\d{1,4})';
33
+ export function volatileMatcher(text) {
34
+ const masked = maskVolatile(text);
35
+ if (masked === text)
36
+ return text;
37
+ return new RegExp(`^${masked.split(WILDCARD).map(escapeRe).join(VOLATILE_TOKEN_SHAPE)}$`);
38
+ }
39
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/shared/text.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,UAAU,IAAI,CAAC,IAAY,EAAE,GAAW;IAC5C,OAAO,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,GAAG,CAAC;AAC9D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,OAAO,CAAC;AAChC,MAAM,cAAc,GAAG,mEAAmE,CAAC;AAE3F,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AAEzE;;;;;GAKG;AACH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,oEAAoE,CAAC;AACzG,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,OAAO,IAAI,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC;AAC5F,CAAC"}