@manyoars/oars 0.2.0 → 0.4.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.
- package/README.md +147 -28
- package/dist/api.js +1 -1
- package/dist/bin.js +6 -29
- package/dist/cli.d.ts +10 -4
- package/dist/cli.js +440 -91
- package/dist/commands/keys.d.ts +31 -0
- package/dist/commands/keys.js +65 -0
- package/dist/commands/login.d.ts +1 -1
- package/dist/commands/login.js +3 -3
- package/dist/errors.d.ts +13 -13
- package/dist/errors.js +28 -11
- package/dist/gate/checks.d.ts +68 -0
- package/dist/gate/checks.js +584 -0
- package/dist/gate/command.d.ts +14 -0
- package/dist/gate/command.js +61 -0
- package/dist/gate/hook-fill-main.d.ts +1 -0
- package/dist/gate/hook-fill-main.js +16 -0
- package/dist/gate/hook-fill.d.ts +38 -0
- package/dist/gate/hook-fill.js +94 -0
- package/dist/gate/hook-main.d.ts +1 -0
- package/dist/gate/hook-main.js +47 -0
- package/dist/gate/hook-process.d.ts +24 -0
- package/dist/gate/hook-process.js +112 -0
- package/dist/gate/hook-record.d.ts +3 -0
- package/dist/gate/hook-record.js +8 -0
- package/dist/gate/hook.d.ts +48 -0
- package/dist/gate/hook.js +259 -0
- package/dist/gate/install.d.ts +25 -0
- package/dist/gate/install.js +126 -0
- package/dist/gate/local-bin.d.ts +8 -0
- package/dist/gate/local-bin.js +36 -0
- package/dist/gate/notes.d.ts +19 -0
- package/dist/gate/notes.js +27 -0
- package/dist/gate/novelty.d.ts +35 -0
- package/dist/gate/novelty.js +99 -0
- package/dist/gate/policy.d.ts +25 -0
- package/dist/gate/policy.js +73 -0
- package/dist/gate/record.d.ts +120 -0
- package/dist/gate/record.js +125 -0
- package/dist/gate/registry-state.d.ts +10 -0
- package/dist/gate/registry-state.js +30 -0
- package/dist/gate/registry.d.ts +56 -0
- package/dist/gate/registry.js +94 -0
- package/dist/gate/replay.d.ts +20 -0
- package/dist/gate/replay.js +254 -0
- package/dist/gate/report.d.ts +50 -0
- package/dist/gate/report.js +89 -0
- package/dist/gate/scope.d.ts +7 -0
- package/dist/gate/scope.js +27 -0
- package/dist/gate/transcripts.d.ts +68 -0
- package/dist/gate/transcripts.js +232 -0
- package/dist/hook-bin.d.ts +1 -0
- package/dist/hook-bin.js +50 -0
- package/dist/issuer.d.ts +10 -6
- package/dist/issuer.js +19 -24
- package/dist/main.d.ts +1 -0
- package/dist/main.js +67 -0
- package/dist/oidc.js +1 -1
- package/dist/sessions/actions.d.ts +35 -0
- package/dist/sessions/actions.js +429 -0
- package/dist/sessions/allow.d.ts +23 -0
- package/dist/sessions/allow.js +71 -0
- package/dist/sessions/archive.d.ts +11 -0
- package/dist/sessions/archive.js +38 -0
- package/dist/sessions/audit.d.ts +42 -0
- package/dist/sessions/audit.js +104 -0
- package/dist/sessions/baseline.d.ts +62 -0
- package/dist/sessions/baseline.js +190 -0
- package/dist/sessions/call-shape.d.ts +19 -0
- package/dist/sessions/call-shape.js +85 -0
- package/dist/sessions/capture-runtime.d.ts +10 -0
- package/dist/sessions/capture-runtime.js +12 -0
- package/dist/sessions/cell-increments.d.ts +47 -0
- package/dist/sessions/cell-increments.js +243 -0
- package/dist/sessions/cells.d.ts +109 -0
- package/dist/sessions/cells.js +261 -0
- package/dist/sessions/codex.d.ts +53 -0
- package/dist/sessions/codex.js +745 -0
- package/dist/sessions/command.d.ts +127 -0
- package/dist/sessions/command.js +419 -0
- package/dist/sessions/copilot.d.ts +40 -0
- package/dist/sessions/copilot.js +432 -0
- package/dist/sessions/coverage.d.ts +19 -0
- package/dist/sessions/coverage.js +56 -0
- package/dist/sessions/cursor.d.ts +24 -0
- package/dist/sessions/cursor.js +324 -0
- package/dist/sessions/derive.d.ts +57 -0
- package/dist/sessions/derive.js +386 -0
- package/dist/sessions/exclusion.d.ts +87 -0
- package/dist/sessions/exclusion.js +361 -0
- package/dist/sessions/extract.d.ts +181 -0
- package/dist/sessions/extract.js +1806 -0
- package/dist/sessions/fold.d.ts +17 -0
- package/dist/sessions/fold.js +71 -0
- package/dist/sessions/frame.d.ts +24 -0
- package/dist/sessions/frame.js +95 -0
- package/dist/sessions/gemini.d.ts +37 -0
- package/dist/sessions/gemini.js +613 -0
- package/dist/sessions/gold-arms.d.ts +67 -0
- package/dist/sessions/gold-arms.js +172 -0
- package/dist/sessions/gold.d.ts +197 -0
- package/dist/sessions/gold.js +463 -0
- package/dist/sessions/graph.d.ts +12 -0
- package/dist/sessions/graph.js +34 -0
- package/dist/sessions/grok.d.ts +36 -0
- package/dist/sessions/grok.js +421 -0
- package/dist/sessions/hook-calls.d.ts +3 -0
- package/dist/sessions/hook-calls.js +47 -0
- package/dist/sessions/hook-index.d.ts +66 -0
- package/dist/sessions/hook-index.js +164 -0
- package/dist/sessions/hooks.d.ts +17 -0
- package/dist/sessions/hooks.js +281 -0
- package/dist/sessions/identity.d.ts +27 -0
- package/dist/sessions/identity.js +131 -0
- package/dist/sessions/install.d.ts +70 -0
- package/dist/sessions/install.js +246 -0
- package/dist/sessions/lineage.d.ts +19 -0
- package/dist/sessions/lineage.js +118 -0
- package/dist/sessions/mcp.d.ts +7 -0
- package/dist/sessions/mcp.js +157 -0
- package/dist/sessions/merkle.d.ts +7 -0
- package/dist/sessions/merkle.js +28 -0
- package/dist/sessions/motif.d.ts +44 -0
- package/dist/sessions/motif.js +59 -0
- package/dist/sessions/opencode.d.ts +33 -0
- package/dist/sessions/opencode.js +319 -0
- package/dist/sessions/otel.d.ts +60 -0
- package/dist/sessions/otel.js +494 -0
- package/dist/sessions/package-books.d.ts +48 -0
- package/dist/sessions/package-books.js +120 -0
- package/dist/sessions/package-resolve.d.ts +32 -0
- package/dist/sessions/package-resolve.js +185 -0
- package/dist/sessions/pending.d.ts +34 -0
- package/dist/sessions/pending.js +142 -0
- package/dist/sessions/pi.d.ts +36 -0
- package/dist/sessions/pi.js +353 -0
- package/dist/sessions/pool-books.d.ts +21 -0
- package/dist/sessions/pool-books.js +75 -0
- package/dist/sessions/pool-choices.d.ts +5 -0
- package/dist/sessions/pool-choices.js +11 -0
- package/dist/sessions/pool-command.d.ts +115 -0
- package/dist/sessions/pool-command.js +669 -0
- package/dist/sessions/pool-delta.d.ts +54 -0
- package/dist/sessions/pool-delta.js +190 -0
- package/dist/sessions/pool-nightly-deps.d.ts +3 -0
- package/dist/sessions/pool-nightly-deps.js +11 -0
- package/dist/sessions/pool-nightly.d.ts +10 -0
- package/dist/sessions/pool-nightly.js +85 -0
- package/dist/sessions/pool-outbox.d.ts +76 -0
- package/dist/sessions/pool-outbox.js +313 -0
- package/dist/sessions/pool-print.d.ts +26 -0
- package/dist/sessions/pool-print.js +68 -0
- package/dist/sessions/pool-raw.d.ts +27 -0
- package/dist/sessions/pool-raw.js +68 -0
- package/dist/sessions/pool-replica.d.ts +35 -0
- package/dist/sessions/pool-replica.js +91 -0
- package/dist/sessions/pool-send-lock.d.ts +20 -0
- package/dist/sessions/pool-send-lock.js +87 -0
- package/dist/sessions/pool-sent.d.ts +4 -0
- package/dist/sessions/pool-sent.js +26 -0
- package/dist/sessions/pool-totals.d.ts +10 -0
- package/dist/sessions/pool-totals.js +21 -0
- package/dist/sessions/pool-wire.d.ts +435 -0
- package/dist/sessions/pool-wire.js +1100 -0
- package/dist/sessions/pool.d.ts +32 -0
- package/dist/sessions/pool.js +164 -0
- package/dist/sessions/projection.d.ts +75 -0
- package/dist/sessions/projection.js +165 -0
- package/dist/sessions/public-vocabulary.d.ts +72 -0
- package/dist/sessions/public-vocabulary.js +295 -0
- package/dist/sessions/query.d.ts +123 -0
- package/dist/sessions/query.js +268 -0
- package/dist/sessions/read-adoption.d.ts +2 -0
- package/dist/sessions/read-adoption.js +27 -0
- package/dist/sessions/read-call-shape.d.ts +2 -0
- package/dist/sessions/read-call-shape.js +25 -0
- package/dist/sessions/read-change.d.ts +2 -0
- package/dist/sessions/read-change.js +30 -0
- package/dist/sessions/read-cost.d.ts +4 -0
- package/dist/sessions/read-cost.js +48 -0
- package/dist/sessions/read-day-move.d.ts +3 -0
- package/dist/sessions/read-day-move.js +65 -0
- package/dist/sessions/read-fit.d.ts +7 -0
- package/dist/sessions/read-fit.js +41 -0
- package/dist/sessions/read-interaction.d.ts +5 -0
- package/dist/sessions/read-interaction.js +62 -0
- package/dist/sessions/read-lead-lag.d.ts +15 -0
- package/dist/sessions/read-lead-lag.js +81 -0
- package/dist/sessions/read-live.d.ts +6 -0
- package/dist/sessions/read-live.js +38 -0
- package/dist/sessions/read-method.d.ts +2 -0
- package/dist/sessions/read-method.js +29 -0
- package/dist/sessions/read-next-step.d.ts +2 -0
- package/dist/sessions/read-next-step.js +35 -0
- package/dist/sessions/read-order.d.ts +54 -0
- package/dist/sessions/read-order.js +62 -0
- package/dist/sessions/read-readable.d.ts +8 -0
- package/dist/sessions/read-readable.js +24 -0
- package/dist/sessions/read-recovery-edge.d.ts +2 -0
- package/dist/sessions/read-recovery-edge.js +26 -0
- package/dist/sessions/read-recovery.d.ts +20 -0
- package/dist/sessions/read-recovery.js +97 -0
- package/dist/sessions/read-rows.d.ts +13 -0
- package/dist/sessions/read-rows.js +47 -0
- package/dist/sessions/read-substitution.d.ts +5 -0
- package/dist/sessions/read-substitution.js +51 -0
- package/dist/sessions/read-support.d.ts +48 -0
- package/dist/sessions/read-support.js +73 -0
- package/dist/sessions/read-tool-day.d.ts +2 -0
- package/dist/sessions/read-tool-day.js +26 -0
- package/dist/sessions/read-version-boundary.d.ts +31 -0
- package/dist/sessions/read-version-boundary.js +95 -0
- package/dist/sessions/read.d.ts +86 -0
- package/dist/sessions/read.js +87 -0
- package/dist/sessions/recall-bundle.d.ts +12 -0
- package/dist/sessions/recall-bundle.js +33 -0
- package/dist/sessions/recall-consequence.d.ts +45 -0
- package/dist/sessions/recall-consequence.js +179 -0
- package/dist/sessions/recall-frames.d.ts +18 -0
- package/dist/sessions/recall-frames.js +44 -0
- package/dist/sessions/recall-next.d.ts +145 -0
- package/dist/sessions/recall-next.js +241 -0
- package/dist/sessions/recall-object.d.ts +139 -0
- package/dist/sessions/recall-object.js +78 -0
- package/dist/sessions/recall-pool.d.ts +19 -0
- package/dist/sessions/recall-pool.js +96 -0
- package/dist/sessions/recall-push.d.ts +26 -0
- package/dist/sessions/recall-push.js +47 -0
- package/dist/sessions/recall-reads.d.ts +124 -0
- package/dist/sessions/recall-reads.js +417 -0
- package/dist/sessions/recall-render.d.ts +19 -0
- package/dist/sessions/recall-render.js +168 -0
- package/dist/sessions/recovery-calls.d.ts +21 -0
- package/dist/sessions/recovery-calls.js +25 -0
- package/dist/sessions/redact.d.ts +17 -0
- package/dist/sessions/redact.js +113 -0
- package/dist/sessions/repo-root.d.ts +10 -0
- package/dist/sessions/repo-root.js +101 -0
- package/dist/sessions/schema.d.ts +694 -0
- package/dist/sessions/schema.js +167 -0
- package/dist/sessions/session-shape.d.ts +76 -0
- package/dist/sessions/session-shape.js +355 -0
- package/dist/sessions/sources.d.ts +50 -0
- package/dist/sessions/sources.js +233 -0
- package/dist/sessions/steps.d.ts +105 -0
- package/dist/sessions/steps.js +250 -0
- package/dist/sessions/store.d.ts +309 -0
- package/dist/sessions/store.js +1498 -0
- package/dist/sessions/strata.d.ts +26 -0
- package/dist/sessions/strata.js +77 -0
- package/dist/sessions/survival.d.ts +27 -0
- package/dist/sessions/survival.js +230 -0
- package/dist/sessions/transcript-tail.d.ts +14 -0
- package/dist/sessions/transcript-tail.js +64 -0
- package/dist/sessions/verify.d.ts +53 -0
- package/dist/sessions/verify.js +286 -0
- package/dist/sessions/vocab-load.d.ts +26 -0
- package/dist/sessions/vocab-load.js +61 -0
- package/dist/sessions/vscode-copilot.d.ts +38 -0
- package/dist/sessions/vscode-copilot.js +494 -0
- package/dist/telemetry/command.d.ts +8 -0
- package/dist/telemetry/command.js +85 -0
- package/dist/telemetry/consent.d.ts +17 -0
- package/dist/telemetry/consent.js +63 -0
- package/dist/telemetry/crash.d.ts +8 -0
- package/dist/telemetry/crash.js +52 -0
- package/dist/telemetry/emit.d.ts +31 -0
- package/dist/telemetry/emit.js +42 -0
- package/dist/telemetry/fields.d.ts +16 -0
- package/dist/telemetry/fields.js +26 -0
- package/dist/telemetry/health.d.ts +21 -0
- package/dist/telemetry/health.js +108 -0
- package/dist/telemetry/hook-emit.d.ts +4 -0
- package/dist/telemetry/hook-emit.js +18 -0
- package/dist/telemetry/measures.d.ts +32 -0
- package/dist/telemetry/measures.js +128 -0
- package/dist/telemetry/observe.d.ts +13 -0
- package/dist/telemetry/observe.js +51 -0
- package/dist/telemetry/read-events.d.ts +9 -0
- package/dist/telemetry/read-events.js +17 -0
- package/dist/telemetry/record.d.ts +17 -0
- package/dist/telemetry/record.js +26 -0
- package/dist/telemetry/report.d.ts +77 -0
- package/dist/telemetry/report.js +188 -0
- package/dist/telemetry/schema.d.ts +40 -0
- package/dist/telemetry/schema.js +187 -0
- package/dist/telemetry/send-main.d.ts +1 -0
- package/dist/telemetry/send-main.js +11 -0
- package/dist/telemetry/send.d.ts +36 -0
- package/dist/telemetry/send.js +172 -0
- package/dist/telemetry/spool.d.ts +17 -0
- package/dist/telemetry/spool.js +70 -0
- package/dist/telemetry/wire.d.ts +25 -0
- package/dist/telemetry/wire.js +76 -0
- package/package.json +4 -5
- package/vocab/crates.json +1 -0
- package/vocab/go.json +1 -0
- package/vocab/harness.json +1 -0
- package/vocab/homebrew.json +1 -0
- package/vocab/manifest.json +1 -0
- package/vocab/maven.json +1 -0
- package/vocab/models.json +1 -0
- package/vocab/npm.json +1 -0
- package/vocab/programs.json +1 -0
- package/vocab/pypi.json +1 -0
- package/vocab/rubygems.json +1 -0
- package/dist/commands/deploy-input.d.ts +0 -38
- package/dist/commands/deploy-input.js +0 -150
- package/dist/commands/deploy.d.ts +0 -22
- package/dist/commands/deploy.js +0 -124
- package/dist/commands/status.d.ts +0 -7
- package/dist/commands/status.js +0 -18
- package/dist/commands/templates.d.ts +0 -11
- package/dist/commands/templates.js +0 -32
- package/dist/commands/test-charge.d.ts +0 -11
- package/dist/commands/test-charge.js +0 -34
- package/dist/journal.d.ts +0 -26
- package/dist/journal.js +0 -58
- package/parity/deploy-plans/paid-api.json +0 -34
- package/parity/deploy-plans/per-task-agent.json +0 -34
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const PATTERNS: ReadonlyArray<readonly [RegExp, string]>;
|
|
2
|
+
/** Shannon entropy in bits per character. */
|
|
3
|
+
export declare const entropy: (text: string) => number;
|
|
4
|
+
export type Redactor = (text: string) => string;
|
|
5
|
+
/** `domains` are the organization domains to mask (`example.com` also masks `mail.example.com`). */
|
|
6
|
+
export declare const makeRedactor: (domains?: readonly string[]) => Redactor;
|
|
7
|
+
/** How many values of a redacted text were masked: each placeholder is one. */
|
|
8
|
+
export declare const placeholders: (text: string) => number;
|
|
9
|
+
/** The names this machine's person goes by in a transcript: the account's and the home folder's. A name under three characters would mask ordinary words, so it is left to the path rules. */
|
|
10
|
+
export declare const localNames: (home?: string, account?: () => string) => string[];
|
|
11
|
+
/** A whole transcript body redacted for upload, and how many values were masked (placeholders added, a count and nothing else). `names` are the local usernames to mask wherever they stand as a whole word. */
|
|
12
|
+
export declare const redactTranscript: (body: string, domains?: readonly string[], names?: readonly string[]) => {
|
|
13
|
+
text: string;
|
|
14
|
+
masked: number;
|
|
15
|
+
};
|
|
16
|
+
/** The version of the rules above, stated to the pool with every raw part; a change to any rule of `redactTranscript` raises it. */
|
|
17
|
+
export declare const REDACTION_VERSION = "1";
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { homedir, userInfo } from 'node:os';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
// Redaction before anything is written to the session store. Deterministic and conservative: it would rather over-mask a
|
|
4
|
+
// long random-looking token than let a secret through. Pattern rules first, then emails, the organization domains the
|
|
5
|
+
// operator names, and last an entropy rule for long mixed-case tokens no pattern knew.
|
|
6
|
+
export const PATTERNS = [
|
|
7
|
+
[/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g, '<secret>'],
|
|
8
|
+
[/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g, '<secret>'],
|
|
9
|
+
[/\bgh[pousr]_[A-Za-z0-9]{20,}\b/g, '<secret>'],
|
|
10
|
+
[/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, '<secret>'],
|
|
11
|
+
[/\bsk-(?:ant-)?[A-Za-z0-9_-]{16,}\b/g, '<secret>'],
|
|
12
|
+
[/\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{10,}\b/g, '<secret>'],
|
|
13
|
+
[/\bwhsec_[A-Za-z0-9]{10,}\b/g, '<secret>'],
|
|
14
|
+
[/\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g, '<secret>'],
|
|
15
|
+
[/\bAIza[0-9A-Za-z_-]{30,}\b/g, '<secret>'],
|
|
16
|
+
[/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, '<secret>'],
|
|
17
|
+
[/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]{12,}/gi, '$1 <secret>'],
|
|
18
|
+
// user:password@host inside a URL
|
|
19
|
+
[/(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/:@]+:[^\s/@]+@/gi, '$1<secret>@'],
|
|
20
|
+
// NAME=value and "name": "value" where the name says it is a secret
|
|
21
|
+
[/\b([A-Za-z0-9_-]*(?:password|passwd|secret|token|api[_-]?key|private[_-]?key|credential)[A-Za-z0-9_-]*)(["']?\s*[=:]\s*)(["']?)[^\s"'&,;]{4,}\3/gi, '$1$2$3<secret>$3'],
|
|
22
|
+
];
|
|
23
|
+
const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+/g;
|
|
24
|
+
// A pinned alias, `foo@npm:bar@1.0.0` or `jsr:@scope/bar@1.0.0`, ends in `bar@1.0.0`, which EMAIL reads as an address. Digits and dots after
|
|
25
|
+
// the `@` are a version, never a domain, so that one shape right after `npm:` or `jsr:` (and an optional scope) is kept. An all-numeric four-part tail is an
|
|
26
|
+
// IP address, a real host, so it stays masked (as in `extract.ts` `VERSIONED`); any other address stays masked too.
|
|
27
|
+
const ALIAS_PREFIX = /(?<![A-Za-z0-9])(?:npm|jsr):(?:@[A-Za-z0-9._~-]+\/)?$/;
|
|
28
|
+
const DOTTED_VERSION = /@(?!\d+\.\d+\.\d+\.\d+$)\d+(?:\.\d+)+$/;
|
|
29
|
+
const TOKEN = /[A-Za-z0-9+_=-]{24,}/g;
|
|
30
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
31
|
+
/** Shannon entropy in bits per character. */
|
|
32
|
+
export const entropy = (text) => {
|
|
33
|
+
const counts = new Map();
|
|
34
|
+
for (const char of text)
|
|
35
|
+
counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
36
|
+
let bits = 0;
|
|
37
|
+
for (const count of counts.values()) {
|
|
38
|
+
const p = count / text.length;
|
|
39
|
+
bits -= p * Math.log2(p);
|
|
40
|
+
}
|
|
41
|
+
return bits;
|
|
42
|
+
};
|
|
43
|
+
const looksRandom = (token) => /[a-z]/.test(token) && /[A-Z]/.test(token) && /\d/.test(token) && entropy(token) >= 4;
|
|
44
|
+
/** `domains` are the organization domains to mask (`example.com` also masks `mail.example.com`). */
|
|
45
|
+
export const makeRedactor = (domains = []) => {
|
|
46
|
+
const domainRule = domains.length === 0 ? undefined : new RegExp(`(?:[A-Za-z0-9-]+\\.)*(?:${domains.map((domain) => escapeRegExp(domain.toLowerCase())).join('|')})\\b`, 'gi');
|
|
47
|
+
return (text) => {
|
|
48
|
+
let out = text;
|
|
49
|
+
for (const [pattern, replacement] of PATTERNS)
|
|
50
|
+
out = out.replace(pattern, replacement);
|
|
51
|
+
out = out.replace(EMAIL, (match, offset, whole) => DOTTED_VERSION.test(match) && ALIAS_PREFIX.test(whole.slice(0, offset)) ? match : '<email>');
|
|
52
|
+
if (domainRule !== undefined)
|
|
53
|
+
out = out.replace(domainRule, '<org-domain>');
|
|
54
|
+
return out.replace(TOKEN, (token) => (looksRandom(token) ? '<secret>' : token));
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
// What a whole transcript holds beyond what a slice of one does (t884): home paths and usernames, environment values, and hosts. A raw copy that
|
|
58
|
+
// leaves the machine goes through these after `makeRedactor`'s rules. Best effort, not a proof: `docs/sessions-store.md` ("The raw copy") lists what it cannot catch.
|
|
59
|
+
const URL_HOST = /(\b[a-z][a-z0-9+.-]*:\/\/)(?:[^\s/@"'\\]*@)?[A-Za-z0-9.-]+(?::\d+)?/gi;
|
|
60
|
+
const IPV4 = /\b(?:\d{1,3}\.){3}\d{1,3}\b/g;
|
|
61
|
+
const INTERNAL_NAME = /\b(?:[a-z0-9-]+\.)+(?:internal|local|lan|corp|intranet|home|localdomain)\b/gi;
|
|
62
|
+
const UNIX_HOME = /(\/(?:home|Users)\/)(?![<.])[^/\s"'\\]+/g;
|
|
63
|
+
const WINDOWS_HOME = /([A-Za-z]:[\\/]+Users[\\/]+)(?![<.])[^\\/\s"']+/gi;
|
|
64
|
+
const ENVIRONMENT = /\b((?:export\s+)?[A-Z][A-Z0-9_]{2,}=)(?!<)(?:\\?["'][^"'\\]*\\?["']|[^\s"'\\]+)/g;
|
|
65
|
+
// The harness names a project folder after its path: `/Users/name/work` becomes `-Users-name-work`, and `C:\\Users\\name\\work` becomes `C--Users-name-work`. The name runs to the next hyphen, so a hyphenated name is masked whole earlier, by the local-name rule (which also matches each name in this encoded form).
|
|
66
|
+
const ENCODED_HOME = /(?<![A-Za-z0-9])((?:[A-Za-z]-)?-(?:Users|home)-)(?!<)[^-\s"'\\/]+/g;
|
|
67
|
+
// A shell prompt, `name@box:~/dir$`: a box name with no dot is not an address, so the email rule leaves it.
|
|
68
|
+
const PROMPT = /(?:<user>|\b[A-Za-z0-9._-]+)@[A-Za-z0-9-]+(?=:[~/.])/g;
|
|
69
|
+
// Full, compressed, and loopback forms. A clock time (`10:20:30`) has no `::`, so it is never one.
|
|
70
|
+
const IPV6 = /(?<![0-9A-Za-z:])(?:(?:[0-9a-f]{1,4}:){7}[0-9a-f]{1,4}|(?:[0-9a-f]{1,4}:){1,7}:(?:[0-9a-f]{1,4}(?::[0-9a-f]{1,4}){0,6})?|::[0-9a-f]{1,4}(?::[0-9a-f]{1,4}){0,6})(?![0-9A-Za-z:])/gi;
|
|
71
|
+
const MIN_NAME = 3;
|
|
72
|
+
const PLACEHOLDER = /<(?:secret|email|org-domain|host|user|env)>/g;
|
|
73
|
+
/** How many values of a redacted text were masked: each placeholder is one. */
|
|
74
|
+
export const placeholders = (text) => text.match(PLACEHOLDER)?.length ?? 0;
|
|
75
|
+
/** The account name; `userInfo()` throws when the uid has no passwd entry (a container run with `--user <uid>`), so the environment stands in. */
|
|
76
|
+
const accountName = () => userInfo().username;
|
|
77
|
+
/** The names this machine's person goes by in a transcript: the account's and the home folder's. A name under three characters would mask ordinary words, so it is left to the path rules. */
|
|
78
|
+
export const localNames = (home = homedir(), account = accountName) => {
|
|
79
|
+
const read = () => {
|
|
80
|
+
try {
|
|
81
|
+
return account();
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return process.env['USER'] || process.env['USERNAME'] || '';
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
return [...new Set([read(), basename(home)].filter((name) => name.length >= MIN_NAME))];
|
|
88
|
+
};
|
|
89
|
+
// Each name also stands in its encoded-folder form (`John Smith` is `John-Smith` in `C--Users-John-Smith-repo`), and the longest goes first so `mary-jane` is never cut to `mary`.
|
|
90
|
+
const nameRule = (names) => {
|
|
91
|
+
const usable = names.filter((name) => name.length >= MIN_NAME);
|
|
92
|
+
const forms = [...new Set(usable.flatMap((name) => [name, name.replace(/[^A-Za-z0-9]/g, '-')]))].sort((a, b) => b.length - a.length);
|
|
93
|
+
return forms.length === 0 ? undefined : new RegExp(`(?<![A-Za-z0-9<])(?:${forms.map(escapeRegExp).join('|')})(?![A-Za-z0-9>])`, 'gi');
|
|
94
|
+
};
|
|
95
|
+
/** A whole transcript body redacted for upload, and how many values were masked (placeholders added, a count and nothing else). `names` are the local usernames to mask wherever they stand as a whole word. */
|
|
96
|
+
export const redactTranscript = (body, domains = [], names = []) => {
|
|
97
|
+
const named = nameRule(names);
|
|
98
|
+
// The names go first: a hyphenated or spaced name must be masked whole before the path rules take its first word.
|
|
99
|
+
const known = makeRedactor(domains)(body);
|
|
100
|
+
const redacted = (named === undefined ? known : known.replace(named, '<user>'))
|
|
101
|
+
.replace(URL_HOST, '$1<host>')
|
|
102
|
+
.replace(INTERNAL_NAME, '<host>')
|
|
103
|
+
.replace(IPV4, '<host>')
|
|
104
|
+
.replace(IPV6, '<host>')
|
|
105
|
+
.replace(UNIX_HOME, '$1<user>')
|
|
106
|
+
.replace(WINDOWS_HOME, '$1<user>')
|
|
107
|
+
.replace(ENCODED_HOME, '$1<user>')
|
|
108
|
+
.replace(PROMPT, '<user>@<host>')
|
|
109
|
+
.replace(ENVIRONMENT, '$1<env>');
|
|
110
|
+
return { text: redacted, masked: Math.max(0, placeholders(redacted) - placeholders(body)) };
|
|
111
|
+
};
|
|
112
|
+
/** The version of the rules above, stated to the pool with every raw part; a change to any rule of `redactTranscript` raises it. */
|
|
113
|
+
export const REDACTION_VERSION = '1';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** A resolver for working directories; answers are cached, since a transcript repeats a handful of directories thousands of times. */
|
|
2
|
+
export declare const makeRepoRoot: (exists?: (path: string) => boolean) => ((cwd: string) => string | null);
|
|
3
|
+
/** `https://User:pw@GitHub.com/org/repo.git`, `git@github.com:org/repo.git`, and `ssh://git@github.com/org/repo/` all read `github.com/org/repo`. */
|
|
4
|
+
export declare const normalizeRemote: (url: string) => string;
|
|
5
|
+
/**
|
|
6
|
+
* A resolver from a repository root to its identity string: `origin:<normalized remote>` when the repository's `config` names an
|
|
7
|
+
* `origin`, else `path:<root>`. The `config` is `<root>/.git/config`, or for a linked worktree (`.git` is a file) the common
|
|
8
|
+
* directory's, reached through the file's `gitdir:` line and that directory's `commondir` file. Any step failing falls back to the path.
|
|
9
|
+
*/
|
|
10
|
+
export declare const makeRepoIdentity: (read?: (path: string) => string | null) => ((root: string) => string);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// The repository root a working directory sits in, for hashing file paths from the repository rather than from wherever the harness ran
|
|
2
|
+
// (spec Section 1.1, CAPTURE 4). A linked worktree under `<root>/.claude/worktrees/<name>` resolves to `<root>`, as A9 counted it; any
|
|
3
|
+
// other directory resolves to the nearest ancestor holding a `.git` entry (a folder, or the file a linked worktree keeps). The walk
|
|
4
|
+
// looks at the file system rather than running `git rev-parse`, so it costs no process per distinct directory and still answers for a
|
|
5
|
+
// directory that has since been deleted (it walks up to the nearest ancestor that exists). Absent a repository it answers null.
|
|
6
|
+
//
|
|
7
|
+
// `makeRepoIdentity` names the repository itself, so two clones of one repository, or one clone on two machines, share a `repo` value:
|
|
8
|
+
// the normalized `origin` remote URL read from the repository's `config` (no git process), else the absolute root path.
|
|
9
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
+
import { posix } from 'node:path';
|
|
11
|
+
const WORKTREE = /^(.+?)\/\.claude\/worktrees\/[^/]+(?:\/.*)?$/;
|
|
12
|
+
/** A resolver for working directories; answers are cached, since a transcript repeats a handful of directories thousands of times. */
|
|
13
|
+
export const makeRepoRoot = (exists = existsSync) => {
|
|
14
|
+
const cache = new Map();
|
|
15
|
+
return (cwd) => {
|
|
16
|
+
const start = cwd.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
17
|
+
const known = cache.get(start);
|
|
18
|
+
if (known !== undefined)
|
|
19
|
+
return known;
|
|
20
|
+
const linked = WORKTREE.exec(start);
|
|
21
|
+
let found = linked?.[1] ?? null;
|
|
22
|
+
// posix.dirname('C:') is '.', which would probe the deposit process's own directory: stop at a drive or filesystem root first.
|
|
23
|
+
for (let dir = start; found === null;) {
|
|
24
|
+
if (exists(`${dir}/.git`))
|
|
25
|
+
found = dir;
|
|
26
|
+
const parent = posix.dirname(dir);
|
|
27
|
+
if (found !== null || parent === '.' || parent === dir)
|
|
28
|
+
break;
|
|
29
|
+
dir = parent;
|
|
30
|
+
}
|
|
31
|
+
cache.set(start, found);
|
|
32
|
+
return found;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
/** `https://User:pw@GitHub.com/org/repo.git`, `git@github.com:org/repo.git`, and `ssh://git@github.com/org/repo/` all read `github.com/org/repo`. */
|
|
36
|
+
export const normalizeRemote = (url) => {
|
|
37
|
+
const bare = url
|
|
38
|
+
.trim()
|
|
39
|
+
.replace(/^[a-z][a-z0-9+.-]*:\/\//i, '')
|
|
40
|
+
.replace(/^[^@/]*@/, '')
|
|
41
|
+
.replace(/^([^/:]+):(?!\d+(?:\/|$))/, '$1/')
|
|
42
|
+
.replace(/\/+$/, '')
|
|
43
|
+
.replace(/\.git$/, '')
|
|
44
|
+
.replace(/\/+$/, '');
|
|
45
|
+
const slash = bare.indexOf('/');
|
|
46
|
+
return slash < 0 ? bare.toLowerCase() : `${bare.slice(0, slash).toLowerCase()}${bare.slice(slash)}`;
|
|
47
|
+
};
|
|
48
|
+
/** The `url` of `[remote "origin"]` in a git `config`, or null. */
|
|
49
|
+
const originUrl = (config) => {
|
|
50
|
+
let inOrigin = false;
|
|
51
|
+
for (const line of config.split(/\r?\n/)) {
|
|
52
|
+
const section = /^\s*\[(.*)\]\s*$/.exec(line);
|
|
53
|
+
if (section !== null)
|
|
54
|
+
inOrigin = /^remote\s+"origin"$/i.test(section[1] ?? '');
|
|
55
|
+
else if (inOrigin) {
|
|
56
|
+
const url = /^\s*url\s*=\s*(.+?)\s*$/i.exec(line);
|
|
57
|
+
if (url !== null)
|
|
58
|
+
return url[1] ?? null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
};
|
|
63
|
+
const isAbsolutePath = (path) => path.startsWith('/') || /^[A-Za-z]:\//.test(path);
|
|
64
|
+
const resolveFrom = (base, path) => (isAbsolutePath(path) ? path : posix.normalize(`${base}/${path}`));
|
|
65
|
+
/** The text of a file, or null when it cannot be read. */
|
|
66
|
+
const readOrNull = (path) => {
|
|
67
|
+
try {
|
|
68
|
+
return readFileSync(path, 'utf8');
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* A resolver from a repository root to its identity string: `origin:<normalized remote>` when the repository's `config` names an
|
|
76
|
+
* `origin`, else `path:<root>`. The `config` is `<root>/.git/config`, or for a linked worktree (`.git` is a file) the common
|
|
77
|
+
* directory's, reached through the file's `gitdir:` line and that directory's `commondir` file. Any step failing falls back to the path.
|
|
78
|
+
*/
|
|
79
|
+
export const makeRepoIdentity = (read = readOrNull) => {
|
|
80
|
+
const cache = new Map();
|
|
81
|
+
const configOf = (root) => {
|
|
82
|
+
const direct = read(`${root}/.git/config`);
|
|
83
|
+
if (direct !== null)
|
|
84
|
+
return direct;
|
|
85
|
+
const pointer = /^gitdir:\s*(.+?)\s*$/m.exec(read(`${root}/.git`) ?? '')?.[1];
|
|
86
|
+
if (pointer === undefined)
|
|
87
|
+
return null;
|
|
88
|
+
const gitdir = resolveFrom(root, pointer);
|
|
89
|
+
const common = read(`${gitdir}/commondir`)?.trim();
|
|
90
|
+
return read(`${common === undefined || common === '' ? gitdir : resolveFrom(gitdir, common)}/config`);
|
|
91
|
+
};
|
|
92
|
+
return (root) => {
|
|
93
|
+
const known = cache.get(root);
|
|
94
|
+
if (known !== undefined)
|
|
95
|
+
return known;
|
|
96
|
+
const url = originUrl(configOf(root) ?? '');
|
|
97
|
+
const identity = url === null ? `path:${root.toLowerCase()}` : `origin:${normalizeRemote(url)}`;
|
|
98
|
+
cache.set(root, identity);
|
|
99
|
+
return identity;
|
|
100
|
+
};
|
|
101
|
+
};
|