@chendpoc/pi-memory 0.2.4 → 0.3.2

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 (164) hide show
  1. package/README.md +92 -43
  2. package/dist/adapters/llm/standalone.js +1 -1
  3. package/dist/cli/init.js +20 -3
  4. package/dist/cli/parseArgs.d.ts +5 -2
  5. package/dist/cli/parseArgs.js +13 -0
  6. package/dist/cli/schedulerSync.d.ts +2 -0
  7. package/dist/cli/schedulerSync.js +26 -0
  8. package/dist/cli/status.d.ts +4 -40
  9. package/dist/cli/status.js +6 -230
  10. package/dist/cli/theme.d.ts +2 -0
  11. package/dist/cli/theme.js +8 -0
  12. package/dist/cli.js +5 -0
  13. package/dist/commands/status.js +2 -2
  14. package/dist/compact/parseMemoryExport.js +2 -1
  15. package/dist/compact/register.js +1 -1
  16. package/dist/compact/runSummary.js +1 -16
  17. package/dist/compact/subagentDelta.js +3 -5
  18. package/dist/consolidate/index.d.ts +1 -0
  19. package/dist/consolidate/index.js +1 -0
  20. package/dist/consolidate/mergeEntries.js +3 -3
  21. package/dist/consolidate/mergeMemoryEntries.d.ts +8 -0
  22. package/dist/consolidate/mergeMemoryEntries.js +23 -0
  23. package/dist/consolidate/mergePrompt.js +2 -2
  24. package/dist/consolidate/mergeWithLlm.js +2 -2
  25. package/dist/consolidate/runJob.d.ts +2 -2
  26. package/dist/consolidate/runJob.js +6 -12
  27. package/dist/consolidate/scheduler.d.ts +2 -2
  28. package/dist/consolidate/scheduler.js +1 -1
  29. package/dist/constants/env.d.ts +1 -0
  30. package/dist/constants/env.js +1 -0
  31. package/dist/constants/paths.d.ts +3 -1
  32. package/dist/constants/paths.js +6 -1
  33. package/dist/extension/createMemoryRuntime.d.ts +3 -0
  34. package/dist/extension/createMemoryRuntime.js +203 -0
  35. package/dist/extension/index.d.ts +2 -0
  36. package/dist/extension/index.js +1 -0
  37. package/dist/extension/lifecycle.d.ts +28 -0
  38. package/dist/extension/lifecycle.js +52 -0
  39. package/dist/extension/messageUtils.d.ts +4 -0
  40. package/dist/extension/messageUtils.js +18 -0
  41. package/dist/extension/types.d.ts +35 -0
  42. package/dist/extension/types.js +1 -0
  43. package/dist/index.d.ts +7 -9
  44. package/dist/index.js +7 -9
  45. package/dist/pi-extension.js +26 -236
  46. package/dist/preflight/episodic.js +13 -30
  47. package/dist/preflight/queryIntent.js +1 -1
  48. package/dist/preflight/render.js +1 -1
  49. package/dist/preflight/strip.d.ts +0 -1
  50. package/dist/preflight/strip.js +0 -24
  51. package/dist/redaction/index.d.ts +4 -0
  52. package/dist/redaction/index.js +3 -0
  53. package/dist/redaction/patterns/constants.d.ts +6 -0
  54. package/dist/redaction/patterns/constants.js +6 -0
  55. package/dist/redaction/patterns/crypto.d.ts +3 -0
  56. package/dist/redaction/patterns/crypto.js +17 -0
  57. package/dist/redaction/patterns/generic.d.ts +3 -0
  58. package/dist/redaction/patterns/generic.js +38 -0
  59. package/dist/redaction/patterns/index.d.ts +16 -0
  60. package/dist/redaction/patterns/index.js +23 -0
  61. package/dist/redaction/patterns/llm.d.ts +3 -0
  62. package/dist/redaction/patterns/llm.js +144 -0
  63. package/dist/redaction/patterns/platform.d.ts +3 -0
  64. package/dist/redaction/patterns/platform.js +87 -0
  65. package/dist/redaction/patterns/types.d.ts +18 -0
  66. package/dist/redaction/patterns/types.js +1 -0
  67. package/dist/redaction/redactText.d.ts +9 -0
  68. package/dist/redaction/redactText.js +31 -0
  69. package/dist/redaction/types.d.ts +19 -0
  70. package/dist/redaction/types.js +1 -0
  71. package/dist/redaction/utils.d.ts +28 -0
  72. package/dist/redaction/utils.js +106 -0
  73. package/dist/scheduler/index.d.ts +3 -0
  74. package/dist/scheduler/index.js +3 -0
  75. package/dist/scheduler/launchd.d.ts +14 -0
  76. package/dist/scheduler/launchd.js +69 -0
  77. package/dist/scheduler/launchdPlist.d.ts +14 -0
  78. package/dist/scheduler/launchdPlist.js +62 -0
  79. package/dist/scheduler/sync.d.ts +36 -0
  80. package/dist/scheduler/sync.js +79 -0
  81. package/dist/shutdown/enqueue.d.ts +1 -1
  82. package/dist/shutdown/enqueue.js +2 -5
  83. package/dist/shutdown/readQueue.js +1 -1
  84. package/dist/shutdown/runDrainJob.js +8 -37
  85. package/dist/shutdown/sessionReader.js +1 -14
  86. package/dist/sidecar/client.d.ts +6 -2
  87. package/dist/sidecar/client.js +49 -14
  88. package/dist/{preflight → sidecar}/queryCache.d.ts +1 -1
  89. package/dist/sidecar/reindexBridge.js +2 -2
  90. package/dist/sidecar/server/server.js +1 -1
  91. package/dist/sidecar/server/vec/chunkQuery.d.ts +4 -0
  92. package/dist/sidecar/server/vec/chunkQuery.js +46 -0
  93. package/dist/sidecar/server/vec/chunkReindex.d.ts +5 -0
  94. package/dist/sidecar/server/vec/chunkReindex.js +40 -0
  95. package/dist/sidecar/server/vec/embeddingCodec.d.ts +2 -0
  96. package/dist/sidecar/server/vec/embeddingCodec.js +6 -0
  97. package/dist/sidecar/server/vec/schema.d.ts +20 -0
  98. package/dist/sidecar/server/vec/schema.js +61 -0
  99. package/dist/sidecar/server/vec/store.d.ts +2 -13
  100. package/dist/sidecar/server/vec/store.js +12 -139
  101. package/dist/sidecar/sidecarManager.js +4 -58
  102. package/dist/sidecar/spawnLock.d.ts +2 -0
  103. package/dist/sidecar/spawnLock.js +57 -0
  104. package/dist/sidecar/syncIndex.d.ts +3 -0
  105. package/dist/sidecar/syncIndex.js +12 -0
  106. package/dist/sidecar/warmup.js +1 -1
  107. package/dist/status/copy.d.ts +2 -0
  108. package/dist/status/copy.js +2 -0
  109. package/dist/status/format.d.ts +7 -0
  110. package/dist/status/format.js +133 -0
  111. package/dist/status/gather.d.ts +2 -0
  112. package/dist/status/gather.js +88 -0
  113. package/dist/status/index.d.ts +4 -0
  114. package/dist/status/index.js +3 -0
  115. package/dist/status/types.d.ts +33 -0
  116. package/dist/status/types.js +1 -0
  117. package/dist/store/consolidatePort.d.ts +11 -0
  118. package/dist/store/consolidatePort.js +1 -0
  119. package/dist/store/index.d.ts +2 -0
  120. package/dist/store/index.js +1 -0
  121. package/dist/store/ingestEntries.d.ts +16 -0
  122. package/dist/store/ingestEntries.js +22 -0
  123. package/dist/{init/workspace.d.ts → store/initWorkspace.d.ts} +1 -1
  124. package/dist/{init/workspace.js → store/initWorkspace.js} +7 -5
  125. package/dist/store/listeners.d.ts +11 -0
  126. package/dist/store/listeners.js +27 -0
  127. package/dist/store/markdown/insert.d.ts +3 -0
  128. package/dist/store/markdown/insert.js +23 -0
  129. package/dist/store/memoryStore.d.ts +9 -22
  130. package/dist/store/memoryStore.js +71 -205
  131. package/dist/store/resolveEntries.d.ts +11 -0
  132. package/dist/store/resolveEntries.js +23 -0
  133. package/dist/store/types.d.ts +0 -1
  134. package/dist/store/writePath.d.ts +20 -0
  135. package/dist/store/writePath.js +123 -0
  136. package/dist/ui/memoryStatusWidget.d.ts +4 -8
  137. package/dist/ui/memoryStatusWidget.js +5 -17
  138. package/dist/utils/async.d.ts +11 -0
  139. package/dist/utils/async.js +24 -0
  140. package/dist/utils/index.d.ts +5 -1
  141. package/dist/utils/index.js +5 -1
  142. package/dist/{ipc/jsonlFramer.d.ts → utils/jsonl.d.ts} +1 -1
  143. package/dist/{ipc/jsonlFramer.js → utils/jsonl.js} +1 -1
  144. package/dist/utils/memory/index.d.ts +10 -0
  145. package/dist/utils/memory/index.js +43 -0
  146. package/dist/utils/paths.d.ts +4 -0
  147. package/dist/utils/paths.js +13 -3
  148. package/dist/utils/scheduler.d.ts +1 -1
  149. package/dist/utils/scheduler.js +6 -6
  150. package/dist/{preflight/session.d.ts → utils/session/index.d.ts} +1 -0
  151. package/dist/{preflight/session.js → utils/session/index.js} +5 -2
  152. package/doc/LAUNCH-KIT.md +229 -0
  153. package/doc/README-zh.md +445 -0
  154. package/doc/ROADMAP-zh.md +114 -0
  155. package/doc/ROADMAP.md +114 -0
  156. package/package.json +16 -4
  157. package/scripts/postinstall.mjs +11 -1
  158. package/templates/com.pi.memory.consolidate.plist.example +41 -0
  159. package/templates/consolidate.cmd.example +15 -0
  160. package/templates/crontab.example +14 -0
  161. package/templates/schtasks.example.txt +34 -0
  162. package/dist/consolidate/entryKey.d.ts +0 -5
  163. package/dist/consolidate/entryKey.js +0 -4
  164. /package/dist/{preflight → sidecar}/queryCache.js +0 -0
@@ -0,0 +1,144 @@
1
+ import { distanceBetweenRanges } from "../utils.js";
2
+ /** osv-scalibr/veles/secrets/mistralapikey — 32-char key within 200 chars of Mistral context. */
3
+ const MISTRAL_CONTEXT_RE = /\bmistral(?:ai)?(?:[_-]?(?:api[_-]?)?key)?\b|api\.mistral\.ai/gi;
4
+ const MISTRAL_KEY_RE = /\b([A-Za-z0-9]{32})\b/g;
5
+ const MISTRAL_MAX_DISTANCE = 200;
6
+ /**
7
+ * Mistral keys are 32-char alphanumeric with no prefix — match only when a Mistral
8
+ * context marker (e.g. mistral_api_key, api.mistral.ai) is within 200 chars.
9
+ */
10
+ function matchMistralApiKey(text) {
11
+ const contexts = [];
12
+ MISTRAL_CONTEXT_RE.lastIndex = 0;
13
+ let contextMatch;
14
+ while ((contextMatch = MISTRAL_CONTEXT_RE.exec(text)) !== null) {
15
+ contexts.push({
16
+ start: contextMatch.index,
17
+ end: contextMatch.index + contextMatch[0].length,
18
+ });
19
+ }
20
+ if (contexts.length === 0) {
21
+ return [];
22
+ }
23
+ const spans = [];
24
+ MISTRAL_KEY_RE.lastIndex = 0;
25
+ let keyMatch;
26
+ while ((keyMatch = MISTRAL_KEY_RE.exec(text)) !== null) {
27
+ const captured = keyMatch[1];
28
+ const start = keyMatch.index;
29
+ const end = start + captured.length;
30
+ const paired = contexts.some((ctx) => distanceBetweenRanges(ctx.start, ctx.end, start, end) <= MISTRAL_MAX_DISTANCE);
31
+ if (paired) {
32
+ spans.push({ start, end });
33
+ }
34
+ }
35
+ return spans;
36
+ }
37
+ /** LLM / AI inference provider keys. */
38
+ export const llmPatterns = [
39
+ // openai/openai.go
40
+ {
41
+ id: "openai-api-key",
42
+ source: "trufflehog/pkg/detectors/openai",
43
+ keywords: ["T3BlbkFJ"],
44
+ match: /\b(sk-(?:(?:proj|svcacct|service)-[A-Za-z0-9_-]+|[a-zA-Z0-9]+)T3BlbkFJ[A-Za-z0-9_-]+)\b/g,
45
+ },
46
+ // anthropic/anthropic.go
47
+ {
48
+ id: "anthropic-api-key",
49
+ source: "trufflehog/pkg/detectors/anthropic",
50
+ keywords: ["sk-ant-api03", "sk-ant-admin01", "sk-ant-"],
51
+ match: /\b(sk-ant-(?:admin01|api03)-[\w-]{93}AA)\b/g,
52
+ },
53
+ // groq/groq.go
54
+ {
55
+ id: "groq-api-key",
56
+ source: "trufflehog/pkg/detectors/groq",
57
+ keywords: ["gsk_"],
58
+ match: /\b(gsk_[a-zA-Z0-9]{52})\b/g,
59
+ },
60
+ // Google Gemini / Maps / GCP consumer API keys (AIzaSy…)
61
+ {
62
+ id: "google-api-key",
63
+ source: "pi-memory",
64
+ keywords: ["AIzaSy"],
65
+ match: /\b(AIzaSy[A-Za-z0-9\-_]{33})\b/g,
66
+ },
67
+ // openrouter/openrouter.go
68
+ {
69
+ id: "openrouter-api-key",
70
+ source: "trufflehog/pkg/detectors/openrouter",
71
+ keywords: ["sk-or-v1-"],
72
+ match: /\b(sk-or-v1-[0-9a-f]{64})\b/g,
73
+ },
74
+ // deepseek/deepseek.go (keyword-gated: bare sk- hex is too ambiguous)
75
+ {
76
+ id: "deepseek-api-key",
77
+ source: "trufflehog/pkg/detectors/deepseek",
78
+ keywords: ["deepseek", "DEEPSEEK"],
79
+ match: /\b(sk-[a-f0-9]{32})\b/g,
80
+ },
81
+ // xai/xai.go
82
+ {
83
+ id: "xai-api-key",
84
+ source: "trufflehog/pkg/detectors/xai",
85
+ keywords: ["xai-"],
86
+ match: /\b(xai-[0-9a-zA-Z_]{80})\b/g,
87
+ },
88
+ // huggingface/huggingface.go
89
+ {
90
+ id: "huggingface-token",
91
+ source: "trufflehog/pkg/detectors/huggingface",
92
+ keywords: ["hf_", "api_org_"],
93
+ match: /\b((?:hf_|api_org_)[a-zA-Z0-9]{34})\b/g,
94
+ },
95
+ // replicate/replicate.go
96
+ {
97
+ id: "replicate-token",
98
+ source: "trufflehog/pkg/detectors/replicate",
99
+ keywords: ["r8_", "replicate"],
100
+ match: /\b(r8_[0-9A-Za-z\-_]{37})\b/g,
101
+ },
102
+ // Perplexity (pplx-…)
103
+ {
104
+ id: "perplexity-api-key",
105
+ source: "pi-memory",
106
+ keywords: ["pplx-"],
107
+ match: /\b(pplx-[A-Za-z0-9_\-]{20,})\b/g,
108
+ },
109
+ // Fireworks AI (fw_…)
110
+ {
111
+ id: "fireworks-api-key",
112
+ source: "pi-memory",
113
+ keywords: ["fw_"],
114
+ match: /\b(fw_[A-Za-z0-9_\-]{20,})\b/g,
115
+ },
116
+ // Voyage AI embeddings (pa-…)
117
+ {
118
+ id: "voyage-api-key",
119
+ source: "pi-memory",
120
+ keywords: ["pa-"],
121
+ match: /\b(pa-[A-Za-z0-9_\-]{20,})\b/g,
122
+ },
123
+ // azure_openai/azure_openai.go
124
+ {
125
+ id: "azure-openai-api-key",
126
+ source: "trufflehog/pkg/detectors/azure_openai",
127
+ keywords: [".openai.azure.com"],
128
+ match: /(?:api[_.-]?key|openai[_.-]?key)(?:.|[\n\r]){0,40}?([a-f0-9]{32})\b/gi,
129
+ },
130
+ // gcp/gcp.go — flat service-account JSON block (provider_x509 marker)
131
+ {
132
+ id: "gcp-service-account-json",
133
+ source: "trufflehog/pkg/detectors/gcp",
134
+ keywords: ["provider_x509", "auth_provider_x509_cert_url"],
135
+ match: /\{[^{]+auth_provider_x509_cert_url[^}]+\}/gi,
136
+ },
137
+ // osv-scalibr/veles/secrets/mistralapikey — pairing via match()
138
+ {
139
+ id: "mistral-api-key",
140
+ source: "osv-scalibr/veles/secrets/mistralapikey",
141
+ keywords: ["mistral", "mistralai", "api.mistral.ai"],
142
+ match: matchMistralApiKey,
143
+ },
144
+ ];
@@ -0,0 +1,3 @@
1
+ import type { SecretPattern } from "./types.js";
2
+ /** Dev / SaaS / cloud platform tokens (non-LLM-specific). */
3
+ export declare const platformPatterns: readonly SecretPattern[];
@@ -0,0 +1,87 @@
1
+ /** Dev / SaaS / cloud platform tokens (non-LLM-specific). */
2
+ export const platformPatterns = [
3
+ // github/v2/github.go
4
+ {
5
+ id: "github-token",
6
+ source: "trufflehog/pkg/detectors/github/v2",
7
+ keywords: ["ghp_", "gho_", "ghu_", "ghs_", "ghr_", "github_pat_"],
8
+ match: /\b((?:ghp|gho|ghu|ghs|ghr|github_pat)_[a-zA-Z0-9_]{36,255})\b/g,
9
+ },
10
+ // pinecone/pinecone.go
11
+ {
12
+ id: "pinecone-api-key",
13
+ source: "trufflehog/pkg/detectors/pinecone",
14
+ keywords: ["pcsk_"],
15
+ match: /\b(pcsk_[A-Za-z0-9]{5,6}_[A-Za-z0-9]{63})\b/g,
16
+ },
17
+ // langsmith/langsmith.go
18
+ {
19
+ id: "langsmith-api-key",
20
+ source: "trufflehog/pkg/detectors/langsmith",
21
+ keywords: ["lsv2_pt_", "lsv2_sk_"],
22
+ match: /\b(lsv2_(?:pt|sk)_[a-f0-9]{32}_[a-f0-9]{10})\b/g,
23
+ },
24
+ // aws/access_keys/accesskey.go (access key id only; secret key needs entropy pairing)
25
+ {
26
+ id: "aws-access-key-id",
27
+ source: "trufflehog/pkg/detectors/aws/access_keys",
28
+ keywords: ["AKIA", "ABIA", "ACCA"],
29
+ match: /\b((?:AKIA|ABIA|ACCA)[A-Z0-9]{16})\b/g,
30
+ },
31
+ // slack/slack.go (four token types merged)
32
+ {
33
+ id: "slack-token",
34
+ source: "trufflehog/pkg/detectors/slack",
35
+ keywords: ["xoxb-", "xoxp-", "xoxa-", "xoxr-"],
36
+ match: /xox[bpar]-[0-9]{10,13}-[0-9]{10,13}[a-zA-Z0-9-]*/g,
37
+ },
38
+ // stripe/stripe.go (live keys only; test keys intentionally excluded)
39
+ {
40
+ id: "stripe-live-key",
41
+ source: "trufflehog/pkg/detectors/stripe",
42
+ keywords: ["k_live"],
43
+ match: /[rs]k_live_[a-zA-Z0-9]{20,247}/g,
44
+ },
45
+ // gitlab/v2/gitlab_v2.go
46
+ {
47
+ id: "gitlab-pat",
48
+ source: "trufflehog/pkg/detectors/gitlab/v2",
49
+ keywords: ["glpat-"],
50
+ match: /\b(glpat-[a-zA-Z0-9\-=_]{20,22})\b/g,
51
+ },
52
+ // npmtokenv2/npmtokenv2.go
53
+ {
54
+ id: "npm-token",
55
+ source: "trufflehog/pkg/detectors/npmtokenv2",
56
+ keywords: ["npm_"],
57
+ match: /(npm_[0-9a-zA-Z]{36})/g,
58
+ },
59
+ // supabasetoken/supabasetoken.go
60
+ {
61
+ id: "supabase-token",
62
+ source: "trufflehog/pkg/detectors/supabasetoken",
63
+ keywords: ["sbp_"],
64
+ match: /\b(sbp_[a-z0-9]{40})\b/g,
65
+ },
66
+ // linearapi/linearapi.go
67
+ {
68
+ id: "linear-api-key",
69
+ source: "trufflehog/pkg/detectors/linearapi",
70
+ keywords: ["lin_api_"],
71
+ match: /\b(lin_api_[0-9A-Za-z]{40})\b/g,
72
+ },
73
+ // sendgrid/sendgrid.go
74
+ {
75
+ id: "sendgrid-api-key",
76
+ source: "trufflehog/pkg/detectors/sendgrid",
77
+ keywords: ["SG."],
78
+ match: /\bSG\.[\w\-]{20,24}\.[\w\-]{39,50}\b/g,
79
+ },
80
+ // postman/postman.go
81
+ {
82
+ id: "postman-api-key",
83
+ source: "trufflehog/pkg/detectors/postman",
84
+ keywords: ["PMAK-"],
85
+ match: /\b(PMAK-[a-zA-Z0-9\-]{59})\b/g,
86
+ },
87
+ ];
@@ -0,0 +1,18 @@
1
+ export type SecretMatchSpan = {
2
+ start: number;
3
+ end: number;
4
+ };
5
+ /**
6
+ * Per-rule detector: RegExp for prefix/block rules; function when proximity pairing is required.
7
+ * Custom matchers return spans without patternId — collectSpans adds the rule id.
8
+ */
9
+ export type SecretPatternMatcher = RegExp | ((text: string) => readonly SecretMatchSpan[]);
10
+ export type SecretPattern = {
11
+ /** Stable id for debug metrics (no secret material). */
12
+ id: string;
13
+ /** TruffleHog detector path, or "pi-memory" for local supplements. */
14
+ source: string;
15
+ /** Optional keyword pre-filter (mirrors TruffleHog Keywords()). */
16
+ keywords?: string[];
17
+ match: SecretPatternMatcher;
18
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { RedactTextResult } from "./types.js";
2
+ export type { RedactTextResult, RedactionSpan } from "./types.js";
3
+ /** Run all secret detectors, merge overlaps, return redacted text and debug metadata. */
4
+ export declare function redactText(text: string): RedactTextResult;
5
+ /**
6
+ * True when nothing meaningful remains after redaction.
7
+ * MemoryStore skips the write (fail-closed) instead of persisting a lone placeholder.
8
+ */
9
+ export declare function isEmptyAfterRedaction(content: string): boolean;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Path A redaction entry: scan text against SECRET_PATTERNS, replace hits with [REDACTED].
3
+ * Called from MemoryStore.prepareEntryForWrite before Ground Truth is persisted.
4
+ */
5
+ import { REDACTED_PLACEHOLDER, SECRET_PATTERNS, SECRET_POLICY_VERSION, } from "./patterns/index.js";
6
+ import { applySpans, collectSpans, mergeSpans } from "./utils.js";
7
+ /** Run all secret detectors, merge overlaps, return redacted text and debug metadata. */
8
+ export function redactText(text) {
9
+ const spans = mergeSpans(collectSpans(text, SECRET_PATTERNS));
10
+ const hitCount = spans.length;
11
+ return {
12
+ text: applySpans(text, spans, REDACTED_PLACEHOLDER),
13
+ hitCount,
14
+ secretHits: hitCount,
15
+ piiHits: 0,
16
+ mutated: hitCount > 0,
17
+ spans,
18
+ policyVersion: SECRET_POLICY_VERSION,
19
+ };
20
+ }
21
+ /**
22
+ * True when nothing meaningful remains after redaction.
23
+ * MemoryStore skips the write (fail-closed) instead of persisting a lone placeholder.
24
+ */
25
+ export function isEmptyAfterRedaction(content) {
26
+ const trimmed = content.trim();
27
+ if (trimmed.length === 0) {
28
+ return true;
29
+ }
30
+ return trimmed === REDACTED_PLACEHOLDER;
31
+ }
@@ -0,0 +1,19 @@
1
+ /** Byte range of a secret match in the original string (before replacement). */
2
+ export type RedactionSpan = {
3
+ start: number;
4
+ end: number;
5
+ /** Rule id(s) that matched; merged spans join ids with "+". */
6
+ patternId: string;
7
+ };
8
+ export type RedactTextResult = {
9
+ text: string;
10
+ /** Number of merged spans (not raw regex hit count). */
11
+ hitCount: number;
12
+ secretHits: number;
13
+ /** Always 0 — pi-memory Path A covers secrets only, not PII. */
14
+ piiHits: number;
15
+ mutated: boolean;
16
+ spans: RedactionSpan[];
17
+ /** SECRET_POLICY_VERSION at time of scan; for debug / policy drift audits. */
18
+ policyVersion: string;
19
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Redaction pipeline helpers.
3
+ *
4
+ * Flow: hasKeyword (gate) → runPatternMatch (per rule) → collectSpans → mergeSpans → applySpans
5
+ */
6
+ import type { SecretMatchSpan, SecretPattern, SecretPatternMatcher } from "./patterns/types.js";
7
+ import type { RedactionSpan } from "./types.js";
8
+ /** Gap between two ranges; 0 when overlapping or adjacent. Used by proximity matchers. */
9
+ export declare function distanceBetweenRanges(aStart: number, aEnd: number, bStart: number, bEnd: number): number;
10
+ /**
11
+ * TruffleHog-style keyword gate: skip expensive regex / pairing when no keyword appears.
12
+ * Empty keywords → always run (used by rules with strong standalone prefixes).
13
+ */
14
+ export declare function hasKeyword(text: string, keywords: readonly string[] | undefined): boolean;
15
+ /**
16
+ * Execute one rule's `match` field: either a custom fn or a global RegExp scan.
17
+ * Prefers capture group 1 when present so only the secret token is redacted, not surrounding syntax.
18
+ */
19
+ export declare function runPatternMatch(text: string, matcher: SecretPatternMatcher): SecretMatchSpan[];
20
+ /** Run every pattern that passes keyword gate; attach rule id to each raw span. */
21
+ export declare function collectSpans(text: string, patterns: readonly SecretPattern[]): RedactionSpan[];
22
+ /**
23
+ * Collapse overlapping / adjacent hits into one span so we emit a single [REDACTED].
24
+ * Example: `Bearer ghp_xxx` matched by both bearer-token and github-token → one placeholder.
25
+ */
26
+ export declare function mergeSpans(spans: RedactionSpan[]): RedactionSpan[];
27
+ /** Replace matched ranges right-to-left so earlier span indices stay valid while slicing. */
28
+ export declare function applySpans(text: string, spans: RedactionSpan[], placeholder: string): string;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Redaction pipeline helpers.
3
+ *
4
+ * Flow: hasKeyword (gate) → runPatternMatch (per rule) → collectSpans → mergeSpans → applySpans
5
+ */
6
+ /** Gap between two ranges; 0 when overlapping or adjacent. Used by proximity matchers. */
7
+ export function distanceBetweenRanges(aStart, aEnd, bStart, bEnd) {
8
+ if (aEnd < bStart) {
9
+ return bStart - aEnd;
10
+ }
11
+ if (bEnd < aStart) {
12
+ return aStart - bEnd;
13
+ }
14
+ return 0;
15
+ }
16
+ /**
17
+ * TruffleHog-style keyword gate: skip expensive regex / pairing when no keyword appears.
18
+ * Empty keywords → always run (used by rules with strong standalone prefixes).
19
+ */
20
+ export function hasKeyword(text, keywords) {
21
+ if (!keywords || keywords.length === 0) {
22
+ return true;
23
+ }
24
+ const lower = text.toLowerCase();
25
+ for (const keyword of keywords) {
26
+ if (text.includes(keyword) || lower.includes(keyword.toLowerCase())) {
27
+ return true;
28
+ }
29
+ }
30
+ return false;
31
+ }
32
+ /**
33
+ * Execute one rule's `match` field: either a custom fn or a global RegExp scan.
34
+ * Prefers capture group 1 when present so only the secret token is redacted, not surrounding syntax.
35
+ */
36
+ export function runPatternMatch(text, matcher) {
37
+ if (typeof matcher === "function") {
38
+ return [...matcher(text)];
39
+ }
40
+ const spans = [];
41
+ matcher.lastIndex = 0;
42
+ let result;
43
+ while ((result = matcher.exec(text)) !== null) {
44
+ const captured = result[1] ?? result[0];
45
+ const start = result[1] !== undefined ? result.index + result[0].indexOf(result[1]) : result.index;
46
+ const end = start + captured.length;
47
+ if (end > start) {
48
+ spans.push({ start, end });
49
+ }
50
+ // Avoid infinite loop on zero-width matches (e.g. lookahead-only patterns).
51
+ if (result[0].length === 0) {
52
+ matcher.lastIndex += 1;
53
+ }
54
+ }
55
+ return spans;
56
+ }
57
+ /** Run every pattern that passes keyword gate; attach rule id to each raw span. */
58
+ export function collectSpans(text, patterns) {
59
+ const spans = [];
60
+ for (const pattern of patterns) {
61
+ if (!hasKeyword(text, pattern.keywords)) {
62
+ continue;
63
+ }
64
+ for (const span of runPatternMatch(text, pattern.match)) {
65
+ if (span.end > span.start) {
66
+ spans.push({ start: span.start, end: span.end, patternId: pattern.id });
67
+ }
68
+ }
69
+ }
70
+ return spans;
71
+ }
72
+ /**
73
+ * Collapse overlapping / adjacent hits into one span so we emit a single [REDACTED].
74
+ * Example: `Bearer ghp_xxx` matched by both bearer-token and github-token → one placeholder.
75
+ */
76
+ export function mergeSpans(spans) {
77
+ if (spans.length === 0) {
78
+ return [];
79
+ }
80
+ const sorted = [...spans].sort((a, b) => a.start - b.start || a.end - b.end);
81
+ const merged = [sorted[0]];
82
+ for (let i = 1; i < sorted.length; i += 1) {
83
+ const current = sorted[i];
84
+ const last = merged[merged.length - 1];
85
+ if (current.start <= last.end) {
86
+ last.end = Math.max(last.end, current.end);
87
+ last.patternId = `${last.patternId}+${current.patternId}`;
88
+ }
89
+ else {
90
+ merged.push({ ...current });
91
+ }
92
+ }
93
+ return merged;
94
+ }
95
+ /** Replace matched ranges right-to-left so earlier span indices stay valid while slicing. */
96
+ export function applySpans(text, spans, placeholder) {
97
+ if (spans.length === 0) {
98
+ return text;
99
+ }
100
+ let result = text;
101
+ for (let i = spans.length - 1; i >= 0; i -= 1) {
102
+ const span = spans[i];
103
+ result = result.slice(0, span.start) + placeholder + result.slice(span.end);
104
+ }
105
+ return result;
106
+ }
@@ -0,0 +1,3 @@
1
+ export { buildLaunchdMaintenancePlist, buildLaunchdMaintenanceShellCommand, shellSingleQuote, type LaunchdMaintenancePlistInput, } from "./launchdPlist.js";
2
+ export { launchAgentPlistPath, isLaunchAgentLoaded, syncLaunchdMaintenanceJob, type LaunchdSyncResult } from "./launchd.js";
3
+ export { canSyncLaunchdInProcess, isSchedulerSyncDisabled, resolvePackageCliPath, syncMaintenanceScheduler, type SchedulerSyncResult, type SyncMaintenanceSchedulerOptions, } from "./sync.js";
@@ -0,0 +1,3 @@
1
+ export { buildLaunchdMaintenancePlist, buildLaunchdMaintenanceShellCommand, shellSingleQuote, } from "./launchdPlist.js";
2
+ export { launchAgentPlistPath, isLaunchAgentLoaded, syncLaunchdMaintenanceJob } from "./launchd.js";
3
+ export { canSyncLaunchdInProcess, isSchedulerSyncDisabled, resolvePackageCliPath, syncMaintenanceScheduler, } from "./sync.js";
@@ -0,0 +1,14 @@
1
+ import { type LaunchdMaintenancePlistInput } from "./launchdPlist.js";
2
+ export declare function launchAgentPlistPath(label?: string): string;
3
+ /** True when the user LaunchAgent is registered in launchd (not merely when the plist file exists). */
4
+ export declare function isLaunchAgentLoaded(label: string): Promise<boolean>;
5
+ export type LaunchdSyncResult = {
6
+ label: string;
7
+ plistPath: string;
8
+ /** Plist bytes changed on disk. */
9
+ changed: boolean;
10
+ /** launchctl bootstrap ran because the job was missing (e.g. prior bootstrap failed). */
11
+ bootstrapped: boolean;
12
+ removedLegacy: string[];
13
+ };
14
+ export declare function syncLaunchdMaintenanceJob(input: LaunchdMaintenancePlistInput): Promise<LaunchdSyncResult>;
@@ -0,0 +1,69 @@
1
+ import { execa } from "execa";
2
+ import { existsSync } from "node:fs";
3
+ import { unlink, writeFile } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { LAUNCHD_LABEL, LEGACY_LAUNCHD_LABELS } from "../constants/paths.js";
7
+ import { buildLaunchdMaintenancePlist } from "./launchdPlist.js";
8
+ export function launchAgentPlistPath(label = LAUNCHD_LABEL) {
9
+ return join(homedir(), "Library", "LaunchAgents", `${label}.plist`);
10
+ }
11
+ function launchctlDomain() {
12
+ const uid = process.getuid?.();
13
+ return uid === undefined ? null : `gui/${uid}`;
14
+ }
15
+ async function launchctlBootout(label) {
16
+ const domain = launchctlDomain();
17
+ const plistPath = launchAgentPlistPath(label);
18
+ if (domain) {
19
+ await execa("launchctl", ["bootout", `${domain}/${label}`], { reject: false });
20
+ }
21
+ if (existsSync(plistPath)) {
22
+ await execa("launchctl", ["unload", plistPath], { reject: false });
23
+ }
24
+ }
25
+ async function launchctlBootstrap(plistPath) {
26
+ const domain = launchctlDomain();
27
+ if (!domain) {
28
+ throw new Error("launchctl bootstrap requires a user id");
29
+ }
30
+ await execa("launchctl", ["bootstrap", domain, plistPath]);
31
+ }
32
+ /** True when the user LaunchAgent is registered in launchd (not merely when the plist file exists). */
33
+ export async function isLaunchAgentLoaded(label) {
34
+ const domain = launchctlDomain();
35
+ if (!domain)
36
+ return false;
37
+ const result = await execa("launchctl", ["print", `${domain}/${label}`], { reject: false });
38
+ return result.exitCode === 0;
39
+ }
40
+ export async function syncLaunchdMaintenanceJob(input) {
41
+ const label = input.label;
42
+ const plistPath = launchAgentPlistPath(label);
43
+ const plist = buildLaunchdMaintenancePlist(input);
44
+ let previous = null;
45
+ if (existsSync(plistPath)) {
46
+ const { readFile } = await import("node:fs/promises");
47
+ previous = await readFile(plistPath, "utf8");
48
+ }
49
+ const removedLegacy = [];
50
+ for (const legacyLabel of LEGACY_LAUNCHD_LABELS) {
51
+ const legacyPath = launchAgentPlistPath(legacyLabel);
52
+ if (!existsSync(legacyPath))
53
+ continue;
54
+ await launchctlBootout(legacyLabel);
55
+ await unlink(legacyPath).catch(() => { });
56
+ removedLegacy.push(legacyLabel);
57
+ }
58
+ if (previous === plist) {
59
+ if (await isLaunchAgentLoaded(label)) {
60
+ return { label, plistPath, changed: false, bootstrapped: false, removedLegacy };
61
+ }
62
+ await launchctlBootstrap(plistPath);
63
+ return { label, plistPath, changed: false, bootstrapped: true, removedLegacy };
64
+ }
65
+ await writeFile(plistPath, plist, "utf8");
66
+ await launchctlBootout(label);
67
+ await launchctlBootstrap(plistPath);
68
+ return { label, plistPath, changed: true, bootstrapped: true, removedLegacy };
69
+ }
@@ -0,0 +1,14 @@
1
+ export type LaunchdMaintenancePlistInput = {
2
+ label: string;
3
+ nodePath: string;
4
+ cliPath: string;
5
+ envFile: string;
6
+ agentDir: string;
7
+ stdoutLog: string;
8
+ stderrLog: string;
9
+ logsDir: string;
10
+ };
11
+ /** Escape a string for safe inclusion inside single-quoted sh(1) arguments. */
12
+ export declare function shellSingleQuote(value: string): string;
13
+ export declare function buildLaunchdMaintenanceShellCommand(input: LaunchdMaintenancePlistInput): string;
14
+ export declare function buildLaunchdMaintenancePlist(input: LaunchdMaintenancePlistInput): string;
@@ -0,0 +1,62 @@
1
+ import { CONSOLIDATE_CRON_HOUR, CONSOLIDATE_CRON_MINUTE } from "../constants/timing.js";
2
+ /** Escape a string for safe inclusion inside single-quoted sh(1) arguments. */
3
+ export function shellSingleQuote(value) {
4
+ return `'${value.replace(/'/g, `'\"'\"'`)}'`;
5
+ }
6
+ export function buildLaunchdMaintenanceShellCommand(input) {
7
+ const mkdir = `mkdir -p ${shellSingleQuote(input.logsDir)}`;
8
+ const exec = [
9
+ "exec",
10
+ shellSingleQuote(input.nodePath),
11
+ shellSingleQuote(input.cliPath),
12
+ "maintenance",
13
+ "--cron",
14
+ "--verbose",
15
+ ].join(" ");
16
+ const redirect = `>> ${shellSingleQuote(input.stdoutLog)} 2>> ${shellSingleQuote(input.stderrLog)}`;
17
+ return `${mkdir} && ${exec} ${redirect}`;
18
+ }
19
+ export function buildLaunchdMaintenancePlist(input) {
20
+ const shellCommand = buildLaunchdMaintenanceShellCommand(input);
21
+ return `<?xml version="1.0" encoding="UTF-8"?>
22
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
23
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
24
+ <plist version="1.0">
25
+ <dict>
26
+ <key>Label</key>
27
+ <string>${escapeXml(input.label)}</string>
28
+
29
+ <key>ProgramArguments</key>
30
+ <array>
31
+ <string>/bin/sh</string>
32
+ <string>-c</string>
33
+ <string>${escapeXml(shellCommand)}</string>
34
+ </array>
35
+
36
+ <key>StartCalendarInterval</key>
37
+ <dict>
38
+ <key>Hour</key>
39
+ <integer>${CONSOLIDATE_CRON_HOUR}</integer>
40
+ <key>Minute</key>
41
+ <integer>${CONSOLIDATE_CRON_MINUTE}</integer>
42
+ </dict>
43
+
44
+ <key>EnvironmentVariables</key>
45
+ <dict>
46
+ <key>PI_MEMORY_ENV_FILE</key>
47
+ <string>${escapeXml(input.envFile)}</string>
48
+ <key>PI_MEMORY_AGENT_DIR</key>
49
+ <string>${escapeXml(input.agentDir)}</string>
50
+ </dict>
51
+ </dict>
52
+ </plist>
53
+ `;
54
+ }
55
+ function escapeXml(value) {
56
+ return value
57
+ .replace(/&/g, "&amp;")
58
+ .replace(/</g, "&lt;")
59
+ .replace(/>/g, "&gt;")
60
+ .replace(/"/g, "&quot;")
61
+ .replace(/'/g, "&apos;");
62
+ }
@@ -0,0 +1,36 @@
1
+ export type SchedulerSyncResult = {
2
+ status: "skipped";
3
+ reason: string;
4
+ } | {
5
+ status: "synced";
6
+ platform: "launchd";
7
+ label: string;
8
+ plistPath: string;
9
+ changed: boolean;
10
+ bootstrapped: boolean;
11
+ removedLegacy: string[];
12
+ } | {
13
+ status: "failed";
14
+ reason: string;
15
+ message: string;
16
+ };
17
+ export declare function isSchedulerSyncDisabled(env?: NodeJS.ProcessEnv): boolean;
18
+ /** Whether this process can attempt a user LaunchAgent sync (macOS + uid + home). */
19
+ export declare function canSyncLaunchdInProcess(env?: NodeJS.ProcessEnv): {
20
+ ok: true;
21
+ } | {
22
+ ok: false;
23
+ reason: string;
24
+ };
25
+ export declare function resolvePackageCliPath(moduleUrl?: string): string;
26
+ export type SyncMaintenanceSchedulerOptions = {
27
+ agentDir?: string;
28
+ cliPath?: string;
29
+ nodePath?: string;
30
+ env?: NodeJS.ProcessEnv;
31
+ };
32
+ /**
33
+ * Install or refresh the OS maintenance scheduler (macOS launchd today).
34
+ * Never throws — automatic callers (postinstall, session_start, init) treat failures as best-effort.
35
+ */
36
+ export declare function syncMaintenanceScheduler(opts?: SyncMaintenanceSchedulerOptions): Promise<SchedulerSyncResult>;