@aligndottech/cli 0.37.1 → 0.38.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 (175) hide show
  1. package/README.md +16 -10
  2. package/dist/cli.d.ts +28 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +98 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/check.d.ts +1 -1
  7. package/dist/commands/check.d.ts.map +1 -1
  8. package/dist/commands/check.js +44 -2
  9. package/dist/commands/check.js.map +1 -1
  10. package/dist/commands/connect-agents.d.ts +1 -0
  11. package/dist/commands/connect-agents.d.ts.map +1 -1
  12. package/dist/commands/connect-agents.js +10 -6
  13. package/dist/commands/connect-agents.js.map +1 -1
  14. package/dist/commands/connect.d.ts +31 -0
  15. package/dist/commands/connect.d.ts.map +1 -0
  16. package/dist/commands/connect.js +92 -0
  17. package/dist/commands/connect.js.map +1 -0
  18. package/dist/commands/context.d.ts.map +1 -1
  19. package/dist/commands/context.js +52 -10
  20. package/dist/commands/context.js.map +1 -1
  21. package/dist/commands/decisions/index.d.ts.map +1 -1
  22. package/dist/commands/decisions/index.js +5 -0
  23. package/dist/commands/decisions/index.js.map +1 -1
  24. package/dist/commands/default-action.d.ts +3 -1
  25. package/dist/commands/default-action.d.ts.map +1 -1
  26. package/dist/commands/default-action.js +29 -27
  27. package/dist/commands/default-action.js.map +1 -1
  28. package/dist/commands/export.d.ts.map +1 -1
  29. package/dist/commands/export.js +3 -0
  30. package/dist/commands/export.js.map +1 -1
  31. package/dist/commands/import/sessions.d.ts +10 -2
  32. package/dist/commands/import/sessions.d.ts.map +1 -1
  33. package/dist/commands/import/sessions.js +98 -3
  34. package/dist/commands/import/sessions.js.map +1 -1
  35. package/dist/commands/import.d.ts +7 -0
  36. package/dist/commands/import.d.ts.map +1 -1
  37. package/dist/commands/import.js +42 -2
  38. package/dist/commands/import.js.map +1 -1
  39. package/dist/commands/invite.d.ts +25 -0
  40. package/dist/commands/invite.d.ts.map +1 -0
  41. package/dist/commands/invite.js +68 -0
  42. package/dist/commands/invite.js.map +1 -0
  43. package/dist/commands/links.d.ts.map +1 -1
  44. package/dist/commands/links.js +5 -0
  45. package/dist/commands/links.js.map +1 -1
  46. package/dist/commands/mcp.d.ts +64 -35
  47. package/dist/commands/mcp.d.ts.map +1 -1
  48. package/dist/commands/mcp.js +85 -40
  49. package/dist/commands/mcp.js.map +1 -1
  50. package/dist/commands/ratify.d.ts.map +1 -1
  51. package/dist/commands/ratify.js +21 -0
  52. package/dist/commands/ratify.js.map +1 -1
  53. package/dist/commands/registry.d.ts +30 -0
  54. package/dist/commands/registry.d.ts.map +1 -0
  55. package/dist/commands/registry.js +70 -0
  56. package/dist/commands/registry.js.map +1 -0
  57. package/dist/commands/setup.d.ts +45 -1
  58. package/dist/commands/setup.d.ts.map +1 -1
  59. package/dist/commands/setup.js +197 -88
  60. package/dist/commands/setup.js.map +1 -1
  61. package/dist/commands/status.d.ts.map +1 -1
  62. package/dist/commands/status.js +10 -22
  63. package/dist/commands/status.js.map +1 -1
  64. package/dist/commands/telemetry.d.ts +7 -3
  65. package/dist/commands/telemetry.d.ts.map +1 -1
  66. package/dist/commands/telemetry.js +12 -7
  67. package/dist/commands/telemetry.js.map +1 -1
  68. package/dist/commands/why.d.ts.map +1 -1
  69. package/dist/commands/why.js +52 -10
  70. package/dist/commands/why.js.map +1 -1
  71. package/dist/index.js +4 -90
  72. package/dist/index.js.map +1 -1
  73. package/dist/lib/agent-rules.d.ts +1 -0
  74. package/dist/lib/agent-rules.d.ts.map +1 -1
  75. package/dist/lib/agent-rules.js +60 -0
  76. package/dist/lib/agent-rules.js.map +1 -1
  77. package/dist/lib/commands-doc.d.ts +15 -0
  78. package/dist/lib/commands-doc.d.ts.map +1 -0
  79. package/dist/lib/commands-doc.js +85 -0
  80. package/dist/lib/commands-doc.js.map +1 -0
  81. package/dist/lib/config.d.ts +7 -2
  82. package/dist/lib/config.d.ts.map +1 -1
  83. package/dist/lib/config.js.map +1 -1
  84. package/dist/lib/connect-prompt.d.ts +1 -1
  85. package/dist/lib/connect-prompt.js +2 -2
  86. package/dist/lib/connect-prompt.js.map +1 -1
  87. package/dist/lib/decision-links.d.ts +39 -0
  88. package/dist/lib/decision-links.d.ts.map +1 -1
  89. package/dist/lib/decision-links.js +51 -0
  90. package/dist/lib/decision-links.js.map +1 -1
  91. package/dist/lib/first-decision.d.ts +22 -0
  92. package/dist/lib/first-decision.d.ts.map +1 -0
  93. package/dist/lib/first-decision.js +22 -0
  94. package/dist/lib/first-decision.js.map +1 -0
  95. package/dist/lib/gateway-client.d.ts +13 -0
  96. package/dist/lib/gateway-client.d.ts.map +1 -1
  97. package/dist/lib/gateway-client.js +14 -0
  98. package/dist/lib/gateway-client.js.map +1 -1
  99. package/dist/lib/git.d.ts +28 -0
  100. package/dist/lib/git.d.ts.map +1 -1
  101. package/dist/lib/git.js +42 -0
  102. package/dist/lib/git.js.map +1 -1
  103. package/dist/lib/hook-context.d.ts +7 -0
  104. package/dist/lib/hook-context.d.ts.map +1 -0
  105. package/dist/lib/hook-context.js +37 -0
  106. package/dist/lib/hook-context.js.map +1 -0
  107. package/dist/lib/hook-payload.d.ts +7 -0
  108. package/dist/lib/hook-payload.d.ts.map +1 -1
  109. package/dist/lib/hook-payload.js +56 -8
  110. package/dist/lib/hook-payload.js.map +1 -1
  111. package/dist/lib/invite-prompt.d.ts +46 -0
  112. package/dist/lib/invite-prompt.d.ts.map +1 -0
  113. package/dist/lib/invite-prompt.js +63 -0
  114. package/dist/lib/invite-prompt.js.map +1 -0
  115. package/dist/lib/mcp-instructions.shared.d.ts +40 -0
  116. package/dist/lib/mcp-instructions.shared.d.ts.map +1 -0
  117. package/dist/lib/mcp-instructions.shared.js +56 -0
  118. package/dist/lib/mcp-instructions.shared.js.map +1 -0
  119. package/dist/lib/mcp-setup.d.ts +32 -2
  120. package/dist/lib/mcp-setup.d.ts.map +1 -1
  121. package/dist/lib/mcp-setup.js +92 -6
  122. package/dist/lib/mcp-setup.js.map +1 -1
  123. package/dist/lib/next-step.d.ts +92 -0
  124. package/dist/lib/next-step.d.ts.map +1 -0
  125. package/dist/lib/next-step.js +97 -0
  126. package/dist/lib/next-step.js.map +1 -0
  127. package/dist/lib/personal-email-domains.d.ts +8 -0
  128. package/dist/lib/personal-email-domains.d.ts.map +1 -0
  129. package/dist/lib/personal-email-domains.js +39 -0
  130. package/dist/lib/personal-email-domains.js.map +1 -0
  131. package/dist/lib/personal-import.d.ts +2 -0
  132. package/dist/lib/personal-import.d.ts.map +1 -1
  133. package/dist/lib/personal-import.js +4 -1
  134. package/dist/lib/personal-import.js.map +1 -1
  135. package/dist/lib/read-value-rollup.d.ts +18 -0
  136. package/dist/lib/read-value-rollup.d.ts.map +1 -0
  137. package/dist/lib/read-value-rollup.js +33 -0
  138. package/dist/lib/read-value-rollup.js.map +1 -0
  139. package/dist/lib/sessions/import-summary.d.ts +39 -0
  140. package/dist/lib/sessions/import-summary.d.ts.map +1 -0
  141. package/dist/lib/sessions/import-summary.js +41 -0
  142. package/dist/lib/sessions/import-summary.js.map +1 -0
  143. package/dist/lib/sessions/memory-files.d.ts +85 -0
  144. package/dist/lib/sessions/memory-files.d.ts.map +1 -0
  145. package/dist/lib/sessions/memory-files.js +235 -0
  146. package/dist/lib/sessions/memory-files.js.map +1 -0
  147. package/dist/lib/sessions/source-url.d.ts +11 -0
  148. package/dist/lib/sessions/source-url.d.ts.map +1 -1
  149. package/dist/lib/sessions/source-url.js +15 -0
  150. package/dist/lib/sessions/source-url.js.map +1 -1
  151. package/dist/lib/setup-funnel.d.ts +41 -0
  152. package/dist/lib/setup-funnel.d.ts.map +1 -0
  153. package/dist/lib/setup-funnel.js +28 -0
  154. package/dist/lib/setup-funnel.js.map +1 -0
  155. package/dist/lib/telemetry-consent.d.ts +17 -8
  156. package/dist/lib/telemetry-consent.d.ts.map +1 -1
  157. package/dist/lib/telemetry-consent.js +21 -6
  158. package/dist/lib/telemetry-consent.js.map +1 -1
  159. package/dist/lib/telemetry-env.d.ts +19 -0
  160. package/dist/lib/telemetry-env.d.ts.map +1 -0
  161. package/dist/lib/telemetry-env.js +13 -0
  162. package/dist/lib/telemetry-env.js.map +1 -0
  163. package/dist/lib/usage-telemetry.d.ts +108 -13
  164. package/dist/lib/usage-telemetry.d.ts.map +1 -1
  165. package/dist/lib/usage-telemetry.js +227 -55
  166. package/dist/lib/usage-telemetry.js.map +1 -1
  167. package/dist/lib/user-hooks.d.ts +15 -0
  168. package/dist/lib/user-hooks.d.ts.map +1 -0
  169. package/dist/lib/user-hooks.js +144 -0
  170. package/dist/lib/user-hooks.js.map +1 -0
  171. package/dist/lib/value-rollup.d.ts +10 -3
  172. package/dist/lib/value-rollup.d.ts.map +1 -1
  173. package/dist/lib/value-rollup.js +11 -4
  174. package/dist/lib/value-rollup.js.map +1 -1
  175. package/package.json +2 -1
@@ -0,0 +1,235 @@
1
+ /**
2
+ * ALI-810: Claude Code's own auto-memory as a second, already-curated decision source.
3
+ *
4
+ * ALI-808 mines raw transcripts. This reads the notes the harness already decided were worth
5
+ * keeping: `~/.claude/projects/<project>/memory/*.md`, one fact per file, with a `type` in
6
+ * frontmatter. Higher precision per file and far cheaper to parse - no noise filtering, no
7
+ * repeat-loop exclusion, no plan-file resolution.
8
+ *
9
+ * ## What is verified, and what is not
10
+ *
11
+ * There is no captured memory-file fixture, because this environment has no memory directory
12
+ * at all (checked: `find ~/.claude -type d -name memory` is empty while `~/.claude/projects`
13
+ * exists). Under ALI-808's own gate - "the gate is the fixture, not the agent name" - that
14
+ * would normally mean shipping `locate` and refusing to parse, the way `gemini-cli` and
15
+ * `cursor` do.
16
+ *
17
+ * The difference here is that the format is DOCUMENTED by its producer rather than reverse
18
+ * engineered, so the fixtures are built from Anthropic's own published description rather
19
+ * than from a guess (https://code.claude.com/docs/en/memory, read 2026-09-09). Everything
20
+ * this module relies on is quoted from that page:
21
+ *
22
+ * - the directory is `~/.claude/projects/<project>/memory/`, `<project>` derived from the
23
+ * git repository, so all worktrees and subdirectories of one repo share it;
24
+ * - it holds a `MEMORY.md` index plus one topic file per memory, with arbitrary names;
25
+ * - Claude records the kind in a `type` frontmatter field with exactly four values:
26
+ * `user`, `feedback`, `project`, `reference`;
27
+ * - a `modified` frontmatter field carries an ISO-8601 write time;
28
+ * - "Claude Code never adds frontmatter to a file that has none", so a memory file may
29
+ * legitimately carry no frontmatter at all;
30
+ * - `autoMemoryDirectory` in settings.json relocates the directory.
31
+ *
32
+ * **The ALI-810 ticket also names `description` and `originSessionId` frontmatter fields.
33
+ * The documentation describes neither.** They may well exist - the ticket was written from a
34
+ * real machine and this one has no memory directory to check - but nothing here depends on
35
+ * them, because a field this module cannot confirm is a field it must not require. If they
36
+ * turn out to be real, `parseMemoryFile` already returns them under `extra` and a follow-up
37
+ * can promote them without touching any caller.
38
+ */
39
+ import { readFileSync } from 'node:fs';
40
+ import { homedir } from 'node:os';
41
+ import { basename, dirname, join } from 'node:path';
42
+ import { findFilesRecursive, sortByMtimeDesc } from './fs-scan.js';
43
+ import { claudeCodeAdapter } from './adapters/claude-code.js';
44
+ /** The four kinds Claude Code records, verbatim from the documentation. */
45
+ export const MEMORY_TYPES = ['user', 'feedback', 'project', 'reference'];
46
+ /**
47
+ * The two kinds that are decision-shaped.
48
+ *
49
+ * `project` is "ongoing work, deadlines, and decisions that Claude can't derive from the code
50
+ * or git history" and `feedback` is "corrections you give Claude and approaches you confirm" -
51
+ * both are things somebody decided. `user` is a person's role and working preferences, and
52
+ * `reference` is where to find a dashboard. Neither is a decision, and importing them would
53
+ * put personal notes into a decision graph.
54
+ */
55
+ export const DECISION_MEMORY_TYPES = ['project', 'feedback'];
56
+ /** The index file, never a memory in its own right. */
57
+ export const MEMORY_INDEX_FILENAME = 'MEMORY.md';
58
+ /**
59
+ * Where this project's memory directory is, if anywhere.
60
+ *
61
+ * Resolution order, and the reason for it:
62
+ *
63
+ * 1. `autoMemoryDirectory` from `~/.claude/settings.json`. Reading it matters because the
64
+ * failure direction without it is silent: a user who relocated the directory would get an
65
+ * empty list, which is indistinguishable from "no memories yet".
66
+ * 2. Otherwise, the `memory/` sibling of this project's Claude Code session files. The docs
67
+ * say `<project>` is derived from the git repository, and the exact slug encoding is not
68
+ * documented - so rather than guess at it, this reuses the cwd matching ALI-808 already
69
+ * proved (each session file records its own `cwd`) and takes the directory those files
70
+ * sit in. That is correct for worktrees and subdirectories for free, because they share
71
+ * one project directory.
72
+ *
73
+ * Returns [] when the agent has never run here, which is the normal case and not an error.
74
+ */
75
+ export function locateMemoryDirs(cwd) {
76
+ const configured = readAutoMemoryDirectory();
77
+ if (configured)
78
+ return [configured];
79
+ const sessionFiles = claudeCodeAdapter.locateSessionFiles(cwd);
80
+ const dirs = new Set();
81
+ for (const file of sessionFiles)
82
+ dirs.add(join(dirname(file), 'memory'));
83
+ return [...dirs];
84
+ }
85
+ /** Memory topic files for this project, newest first. `MEMORY.md` is excluded: it is an index
86
+ * of the others, so importing it would duplicate every memory as one more candidate. */
87
+ export function locateMemoryFiles(cwd) {
88
+ const found = [];
89
+ for (const dir of locateMemoryDirs(cwd)) {
90
+ for (const file of findFilesRecursive(dir, /\.md$/)) {
91
+ if (basename(file) !== MEMORY_INDEX_FILENAME)
92
+ found.push(file);
93
+ }
94
+ }
95
+ return sortByMtimeDesc(found);
96
+ }
97
+ function readAutoMemoryDirectory() {
98
+ let raw;
99
+ try {
100
+ raw = readFileSync(join(homedir(), '.claude', 'settings.json'), 'utf8');
101
+ }
102
+ catch {
103
+ return null;
104
+ }
105
+ let parsed;
106
+ try {
107
+ parsed = JSON.parse(raw);
108
+ }
109
+ catch {
110
+ // A settings file we cannot parse is not ours to repair, and guessing past it would be
111
+ // worse than falling back to the documented default location.
112
+ return null;
113
+ }
114
+ const value = parsed?.['autoMemoryDirectory'];
115
+ if (typeof value !== 'string' || value.length === 0)
116
+ return null;
117
+ // Documented constraint: "must be an absolute path or start with ~/".
118
+ if (value.startsWith('~/'))
119
+ return join(homedir(), value.slice(2));
120
+ return value;
121
+ }
122
+ /**
123
+ * Parses one memory file.
124
+ *
125
+ * The frontmatter reader deliberately handles only `key: value` scalars on one line, which is
126
+ * every field the documentation describes. Anything else - a nested map, a list, a block
127
+ * scalar - is left in `extra` as its raw text if it is a simple scalar and otherwise ignored,
128
+ * rather than half-understood. js-yaml is a devDependency here, not a runtime one, so pulling
129
+ * a YAML parser into shipped CLI code would be a new dependency for two scalar fields.
130
+ *
131
+ * Returns null only for an unreadable file.
132
+ */
133
+ export function parseMemoryFile(filePath) {
134
+ let text;
135
+ try {
136
+ text = readFileSync(filePath, 'utf8');
137
+ }
138
+ catch {
139
+ return null;
140
+ }
141
+ const { frontmatter, body } = splitFrontmatter(text);
142
+ const rawType = frontmatter['type'];
143
+ const type = isMemoryType(rawType) ? rawType : null;
144
+ const modified = frontmatter['modified'] ?? null;
145
+ const extra = {};
146
+ for (const [k, v] of Object.entries(frontmatter)) {
147
+ if (k !== 'type' && k !== 'modified')
148
+ extra[k] = v;
149
+ }
150
+ return { filePath, type, modified, extra, body };
151
+ }
152
+ function isMemoryType(value) {
153
+ return value !== undefined && MEMORY_TYPES.includes(value);
154
+ }
155
+ /**
156
+ * Splits `---\n...\n---\n` off the front. A file with no frontmatter is the documented normal
157
+ * case, not a malformed one, so it yields an empty map and the whole text as the body.
158
+ */
159
+ function splitFrontmatter(text) {
160
+ const normalized = text.replace(/\r\n/g, '\n');
161
+ if (!normalized.startsWith('---\n'))
162
+ return { frontmatter: {}, body: normalized.trim() };
163
+ const end = normalized.indexOf('\n---', 3);
164
+ if (end === -1) {
165
+ // An opening fence with no closing one is not frontmatter. Treating the rest of the file
166
+ // as key/value would silently swallow the content.
167
+ return { frontmatter: {}, body: normalized.trim() };
168
+ }
169
+ const block = normalized.slice(4, end);
170
+ const after = normalized.slice(end + 4);
171
+ const frontmatter = {};
172
+ for (const line of block.split('\n')) {
173
+ const colon = line.indexOf(':');
174
+ if (colon === -1)
175
+ continue;
176
+ const key = line.slice(0, colon).trim();
177
+ let value = line.slice(colon + 1).trim();
178
+ if (!key || !value)
179
+ continue;
180
+ // Strip one layer of matching quotes, which is how an ISO timestamp is often written.
181
+ if ((value.startsWith('"') && value.endsWith('"') && value.length > 1) ||
182
+ (value.startsWith("'") && value.endsWith("'") && value.length > 1)) {
183
+ value = value.slice(1, -1);
184
+ }
185
+ frontmatter[key] = value;
186
+ }
187
+ return { frontmatter, body: after.trim() };
188
+ }
189
+ /** First markdown heading, else the filename stem with `_`/`-` as spaces. */
190
+ function titleFor(file) {
191
+ const heading = file.body.split('\n').find(l => /^#{1,6}\s+\S/.test(l));
192
+ if (heading)
193
+ return heading.replace(/^#{1,6}\s+/, '').trim();
194
+ return basename(file.filePath).replace(/\.md$/, '').replace(/[_-]+/g, ' ').trim();
195
+ }
196
+ /**
197
+ * The decision-shaped memories among these files.
198
+ *
199
+ * Three separate reasons a file is dropped, all deliberate:
200
+ * - `type` is `user` or `reference`: classified, and not a decision.
201
+ * - `type` is absent or unrecognised: NOT classified. Importing it would be a guess about
202
+ * personal notes, so it is skipped rather than defaulted into a decision.
203
+ * - the body is empty: nothing to review.
204
+ */
205
+ export function extractMemoryDecisions(filePaths) {
206
+ const candidates = [];
207
+ for (const filePath of filePaths) {
208
+ const parsed = parseMemoryFile(filePath);
209
+ if (!parsed)
210
+ continue;
211
+ // This null check is RUNTIME-REDUNDANT and kept deliberately. Replacing it with a no-op
212
+ // leaves every test green, because DECISION_MEMORY_TYPES cannot contain null so the next
213
+ // line drops an unclassified file anyway. Deleting it does NOT leave the suite green:
214
+ // `tsc` then reports TS2345 and TS2322, because the narrowing to MemoryType is what lets
215
+ // the next line and the push below compile. The enforcing mechanism is the compiler
216
+ // rather than a test, which is the honest reason it survives a mutation run - so do not
217
+ // remove it as dead code.
218
+ if (parsed.type === null)
219
+ continue;
220
+ if (!DECISION_MEMORY_TYPES.includes(parsed.type))
221
+ continue;
222
+ if (parsed.body.length === 0)
223
+ continue;
224
+ candidates.push({
225
+ filePath,
226
+ type: parsed.type,
227
+ title: titleFor(parsed),
228
+ body: parsed.body,
229
+ timestamp: parsed.modified,
230
+ memoryDir: dirname(filePath),
231
+ });
232
+ }
233
+ return candidates;
234
+ }
235
+ //# sourceMappingURL=memory-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-files.js","sourceRoot":"","sources":["../../../src/lib/sessions/memory-files.ts"],"names":[],"mappings":"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"}
@@ -7,4 +7,15 @@ export declare function parseSessionSourceUrl(url: string): {
7
7
  sessionId: string;
8
8
  messageId: string;
9
9
  } | null;
10
+ /**
11
+ * ALI-810: memory files are not sessions, so they get their own scheme rather than being
12
+ * squeezed into `<agent>-session://<sessionId>/<messageId>` with invented ids.
13
+ *
14
+ * Built from the project directory name and the topic file's stem, both of which are stable
15
+ * for a given memory across rewrites - Claude edits a topic file in place rather than making
16
+ * a new one. That stability is the point: the gateway upserts on `(tenant_id, source_url)`,
17
+ * so re-running the import after Claude has revised a memory updates the decision instead of
18
+ * adding a second copy of it.
19
+ */
20
+ export declare function buildMemorySourceUrl(memoryDir: string, filePath: string): string;
10
21
  //# sourceMappingURL=source-url.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"source-url.d.ts","sourceRoot":"","sources":["../../../src/lib/sessions/source-url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI5C;6CAC6C;AAC7C,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpG;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAWpH"}
1
+ {"version":3,"file":"source-url.d.ts","sourceRoot":"","sources":["../../../src/lib/sessions/source-url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI5C;6CAC6C;AAC7C,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpG;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAWpH;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIhF"}
@@ -17,4 +17,19 @@ export function parseSessionSourceUrl(url) {
17
17
  }
18
18
  return null;
19
19
  }
20
+ /**
21
+ * ALI-810: memory files are not sessions, so they get their own scheme rather than being
22
+ * squeezed into `<agent>-session://<sessionId>/<messageId>` with invented ids.
23
+ *
24
+ * Built from the project directory name and the topic file's stem, both of which are stable
25
+ * for a given memory across rewrites - Claude edits a topic file in place rather than making
26
+ * a new one. That stability is the point: the gateway upserts on `(tenant_id, source_url)`,
27
+ * so re-running the import after Claude has revised a memory updates the decision instead of
28
+ * adding a second copy of it.
29
+ */
30
+ export function buildMemorySourceUrl(memoryDir, filePath) {
31
+ const project = memoryDir.split(/[\\/]/).filter(Boolean).at(-2) ?? 'unknown-project';
32
+ const topic = (filePath.split(/[\\/]/).pop() ?? 'unknown').replace(/\.md$/, '');
33
+ return `claude-code-memory://${project}/${topic}`;
34
+ }
20
35
  //# sourceMappingURL=source-url.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"source-url.js","sourceRoot":"","sources":["../../../src/lib/sessions/source-url.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAyB,CAAC,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;AAExG;6CAC6C;AAC7C,MAAM,UAAU,qBAAqB,CAAC,KAAgB,EAAE,SAAiB,EAAE,SAAiB;IAC1F,OAAO,GAAG,KAAK,cAAc,SAAS,IAAI,SAAS,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,GAAG,KAAK,aAAa,CAAC;QACrC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACtC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,KAAK,KAAK,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC9B,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;QACtF,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"source-url.js","sourceRoot":"","sources":["../../../src/lib/sessions/source-url.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAyB,CAAC,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;AAExG;6CAC6C;AAC7C,MAAM,UAAU,qBAAqB,CAAC,KAAgB,EAAE,SAAiB,EAAE,SAAiB;IAC1F,OAAO,GAAG,KAAK,cAAc,SAAS,IAAI,SAAS,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,GAAG,KAAK,aAAa,CAAC;QACrC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACtC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,KAAK,KAAK,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC9B,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;QACtF,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAiB,EAAE,QAAgB;IACtE,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,iBAAiB,CAAC;IACrF,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,SAAS,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAChF,OAAO,wBAAwB,OAAO,IAAI,KAAK,EAAE,CAAC;AACpD,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { EnvironmentConfig } from './config.js';
2
+ /**
3
+ * ALI-949: the setup wizard's two funnel stages, carried through `runSetup` so bare
4
+ * `align` and `align setup` share one emitter (both run runSetup's body).
5
+ *
6
+ * `started` BUFFERS rather than sends. When a mode branch begins nothing may be sendable
7
+ * on a fresh install: cloud has no token until login, and local mode asks for consent
8
+ * mid-wizard (ALI-794 puts the found-decisions moment before any question, and that order
9
+ * is not negotiable here). So the wizard offers the current env at each checkpoint - the
10
+ * top of the local value phase, after consent, the top of cloud setup, after login - and
11
+ * this object sends at the first offer the emitter reports as sent. Sendability is
12
+ * recordFunnelStage's decision, not repeated here.
13
+ *
14
+ * Within one wizard run, "once" is per IDENTITY rather than once in total. A local-mode
15
+ * ping is keyed on the machine's installId; a cloud ping on the tenant. Nothing joins the
16
+ * two, so a fresh-install user who consents locally and then chooses "sync to the cloud"
17
+ * would otherwise leave `setup_started` under one identity and `setup_completed` under the
18
+ * other (fresh-context review on #279). A later checkpoint on the OTHER transport therefore
19
+ * sends again under its own identity; the same transport never repeats in the run. There is
20
+ * no cross-run memory here: one object is created per `runSetup` call, and a re-run of the
21
+ * wizard is a new setup_started, which is what the funnel wants (the reader counts an
22
+ * install once per stage regardless - `cli_activation_funnel.py`'s countIf(> 0)).
23
+ *
24
+ * What that means for the funnel: in local mode `setup_started` says "started, and got as
25
+ * far as consenting" rather than "typed the command". A user who declines consent is never
26
+ * counted at any stage, which is the consent model, not a gap.
27
+ *
28
+ * Offers are serialised through one promise chain because call sites fire them without
29
+ * awaiting (`void funnel.started(env)` - a slow gateway must not delay the wizard), so two
30
+ * offers can overlap; the second waits for the first's verdict before deciding to send.
31
+ * `completed` queues behind the started chain too (Copilot on #279): a started send still
32
+ * in flight when the wizard finishes must not be overtaken, or the install's two events
33
+ * reach the gateway out of order. By the outro the started send has long settled, so this
34
+ * costs nothing in practice and is the ordering guarantee when it has not.
35
+ */
36
+ export interface SetupFunnel {
37
+ started(env: EnvironmentConfig): Promise<void>;
38
+ completed(env: EnvironmentConfig): Promise<void>;
39
+ }
40
+ export declare function createSetupFunnel(): SetupFunnel;
41
+ //# sourceMappingURL=setup-funnel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-funnel.d.ts","sourceRoot":"","sources":["../../src/lib/setup-funnel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,SAAS,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD;AAMD,wBAAgB,iBAAiB,IAAI,WAAW,CAuB/C"}
@@ -0,0 +1,28 @@
1
+ import { recordFunnelStage } from './usage-telemetry.js';
2
+ const transportOf = (env) => (env.mode === 'local-embedded' ? 'anonymous' : 'cloud');
3
+ export function createSetupFunnel() {
4
+ // The transports setup_started has been sent on so far.
5
+ let startedChain = Promise.resolve(new Set());
6
+ let completedChain = Promise.resolve(false);
7
+ return {
8
+ async started(env) {
9
+ startedChain = startedChain.then(async (sentOn) => {
10
+ const transport = transportOf(env);
11
+ if (sentOn.has(transport))
12
+ return sentOn;
13
+ const sent = await recordFunnelStage(env, 'setup_started', 'setup');
14
+ return sent ? new Set([...sentOn, transport]) : sentOn;
15
+ });
16
+ await startedChain;
17
+ },
18
+ async completed(env) {
19
+ // Capture the previous link BEFORE reassigning, or the new chain waits on itself.
20
+ const previous = completedChain;
21
+ completedChain = startedChain
22
+ .then(() => previous)
23
+ .then((sent) => (sent ? true : recordFunnelStage(env, 'setup_completed', 'setup')));
24
+ await completedChain;
25
+ },
26
+ };
27
+ }
28
+ //# sourceMappingURL=setup-funnel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-funnel.js","sourceRoot":"","sources":["../../src/lib/setup-funnel.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AA2CzD,MAAM,WAAW,GAAG,CAAC,GAAsB,EAAa,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAEnH,MAAM,UAAU,iBAAiB;IAC/B,wDAAwD;IACxD,IAAI,YAAY,GAAoC,OAAO,CAAC,OAAO,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IAC/E,IAAI,cAAc,GAAqB,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9D,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,GAAG;YACf,YAAY,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;gBAChD,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;gBACnC,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;oBAAE,OAAO,MAAM,CAAC;gBACzC,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;gBACpE,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YACzD,CAAC,CAAC,CAAC;YACH,MAAM,YAAY,CAAC;QACrB,CAAC;QACD,KAAK,CAAC,SAAS,CAAC,GAAG;YACjB,kFAAkF;YAClF,MAAM,QAAQ,GAAG,cAAc,CAAC;YAChC,cAAc,GAAG,YAAY;iBAC1B,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC;iBACpB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;YACtF,MAAM,cAAc,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -3,18 +3,27 @@
3
3
  * carry every unrelated accessor `createConfigStore()` exposes.
4
4
  */
5
5
  export interface TelemetryConsentStore {
6
- getTelemetryConsent(): 'granted' | 'declined' | undefined;
7
- setTelemetryConsent(value: 'granted' | 'declined'): void;
6
+ getTelemetryConsent(): 'granted' | 'declined' | 'off' | undefined;
7
+ setTelemetryConsent(value: 'granted' | 'declined' | 'off'): void;
8
8
  }
9
9
  /**
10
10
  * ALI-618 D3: the one-time local-mode consent prompt, shown from `runLocalSetup()`.
11
11
  *
12
- * Never asks twice - a decision already on disk (granted OR declined) is left alone. Never
13
- * prompts without a TTY on both streams: a piped `setup --local` run hangs on a prompt it
14
- * cannot answer, and a closed stdin crashes clack's raw-mode init AFTER the real setup work has
15
- * already succeeded (the align-cli#118 lesson, `setup-local-non-tty.test.ts`). A non-interactive
16
- * run leaves consent UNSET rather than implicitly declined - a scripted first run may be a CI
17
- * smoke test, not a real user's choice, and it can still be asked on a later interactive run.
12
+ * Never asks twice - a decision already on disk (granted, declined, or `align telemetry off`)
13
+ * is left alone. Never prompts without a TTY on both streams: a piped `setup --local` run
14
+ * hangs on a prompt it cannot answer, and a closed stdin crashes clack's raw-mode init AFTER
15
+ * the real setup work has already succeeded (the align-cli#118 lesson,
16
+ * `setup-local-non-tty.test.ts`). A non-interactive run leaves consent UNSET rather than
17
+ * implicitly declined - a scripted first run may be a CI smoke test, not a real user's choice,
18
+ * and it can still be asked on a later interactive run.
19
+ *
20
+ * ALI-954: an env var that already turns everything off (`DO_NOT_TRACK=1`, `ALIGN_TELEMETRY=0`)
21
+ * skips the question with a one-line note - asking would imply the answer matters - and
22
+ * leaves the decision unset for the same reason as the non-interactive case.
23
+ *
24
+ * The question is about USAGE (which commands you run). The two anonymous counts the docs
25
+ * describe (install, setup completed) are not what is being asked about here and send either
26
+ * way; `align telemetry off` is what stops those (docs/telemetry.md).
18
27
  *
19
28
  * Default is No: anything other than an explicit yes - the default answer, or Ctrl-C - leaves
20
29
  * telemetry off (D3).
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry-consent.d.ts","sourceRoot":"","sources":["../../src/lib/telemetry-consent.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,mBAAmB,IAAI,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;IAC1D,mBAAmB,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,GAAG,IAAI,CAAC;CAC1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,qBAAqB,EAC7B,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,IAAI,CAAC,CAYf"}
1
+ {"version":3,"file":"telemetry-consent.d.ts","sourceRoot":"","sources":["../../src/lib/telemetry-consent.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,mBAAmB,IAAI,SAAS,GAAG,UAAU,GAAG,KAAK,GAAG,SAAS,CAAC;IAClE,mBAAmB,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,GAAG,KAAK,GAAG,IAAI,CAAC;CAClE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,qBAAqB,EAC7B,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,IAAI,CAAC,CAkBf"}
@@ -1,13 +1,23 @@
1
1
  import * as p from '@clack/prompts';
2
+ import { telemetryDisabledByEnv } from './telemetry-env.js';
2
3
  /**
3
4
  * ALI-618 D3: the one-time local-mode consent prompt, shown from `runLocalSetup()`.
4
5
  *
5
- * Never asks twice - a decision already on disk (granted OR declined) is left alone. Never
6
- * prompts without a TTY on both streams: a piped `setup --local` run hangs on a prompt it
7
- * cannot answer, and a closed stdin crashes clack's raw-mode init AFTER the real setup work has
8
- * already succeeded (the align-cli#118 lesson, `setup-local-non-tty.test.ts`). A non-interactive
9
- * run leaves consent UNSET rather than implicitly declined - a scripted first run may be a CI
10
- * smoke test, not a real user's choice, and it can still be asked on a later interactive run.
6
+ * Never asks twice - a decision already on disk (granted, declined, or `align telemetry off`)
7
+ * is left alone. Never prompts without a TTY on both streams: a piped `setup --local` run
8
+ * hangs on a prompt it cannot answer, and a closed stdin crashes clack's raw-mode init AFTER
9
+ * the real setup work has already succeeded (the align-cli#118 lesson,
10
+ * `setup-local-non-tty.test.ts`). A non-interactive run leaves consent UNSET rather than
11
+ * implicitly declined - a scripted first run may be a CI smoke test, not a real user's choice,
12
+ * and it can still be asked on a later interactive run.
13
+ *
14
+ * ALI-954: an env var that already turns everything off (`DO_NOT_TRACK=1`, `ALIGN_TELEMETRY=0`)
15
+ * skips the question with a one-line note - asking would imply the answer matters - and
16
+ * leaves the decision unset for the same reason as the non-interactive case.
17
+ *
18
+ * The question is about USAGE (which commands you run). The two anonymous counts the docs
19
+ * describe (install, setup completed) are not what is being asked about here and send either
20
+ * way; `align telemetry off` is what stops those (docs/telemetry.md).
11
21
  *
12
22
  * Default is No: anything other than an explicit yes - the default answer, or Ctrl-C - leaves
13
23
  * telemetry off (D3).
@@ -17,6 +27,11 @@ export async function maybeRequestTelemetryConsent(config, interactive) {
17
27
  return;
18
28
  if (!interactive)
19
29
  return;
30
+ const envSwitch = telemetryDisabledByEnv();
31
+ if (envSwitch !== undefined) {
32
+ p.log.info(`Telemetry is off (${envSwitch} is set), so nothing is sent and you won't be asked.`);
33
+ return;
34
+ }
20
35
  const answer = await p.confirm({
21
36
  message: 'Help improve Align? Send an anonymous count of which commands you run - no code, no ' +
22
37
  'decisions, no file names, ever. You can change this any time with `align telemetry off`.',
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry-consent.js","sourceRoot":"","sources":["../../src/lib/telemetry-consent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AAWpC;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,MAA6B,EAC7B,WAAoB;IAEpB,IAAI,MAAM,CAAC,mBAAmB,EAAE,KAAK,SAAS;QAAE,OAAO;IACvD,IAAI,CAAC,WAAW;QAAE,OAAO;IAEzB,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,OAAO,CAAC;QAC7B,OAAO,EACL,sFAAsF;YACtF,0FAA0F;QAC5F,YAAY,EAAE,KAAK;KACpB,CAAC,CAAC;IAEH,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;AACrF,CAAC"}
1
+ {"version":3,"file":"telemetry-consent.js","sourceRoot":"","sources":["../../src/lib/telemetry-consent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AACpC,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAW5D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,MAA6B,EAC7B,WAAoB;IAEpB,IAAI,MAAM,CAAC,mBAAmB,EAAE,KAAK,SAAS;QAAE,OAAO;IACvD,IAAI,CAAC,WAAW;QAAE,OAAO;IAEzB,MAAM,SAAS,GAAG,sBAAsB,EAAE,CAAC;IAC3C,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,qBAAqB,SAAS,sDAAsD,CAAC,CAAC;QACjG,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,OAAO,CAAC;QAC7B,OAAO,EACL,sFAAsF;YACtF,0FAA0F;QAC5F,YAAY,EAAE,KAAK;KACpB,CAAC,CAAC;IAEH,MAAM,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;AACrF,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * ALI-954: the two environment variables that turn ALL telemetry off - both tiers, the
3
+ * default-on beacons included. Its own module so the consent prompt (telemetry-consent.ts)
4
+ * can ask the same question without importing the emitter, which setup.test.ts mocks down to
5
+ * one function.
6
+ *
7
+ * The two read in opposite directions, each erring toward sending nothing:
8
+ * - `ALIGN_TELEMETRY` (ours, ALI-618): set and not recognisably ON means OFF. Enumerating the
9
+ * falsy words instead guesses at what a user will type, and every miss is a live send by
10
+ * someone who believes they opted out.
11
+ * - `DO_NOT_TRACK` (consoledonottrack.com, the cross-tool convention): set and not
12
+ * recognisably OFF means "do not track". The convention says `1`; a user who exports `true`
13
+ * means the same thing, and `DO_NOT_TRACK=0` is the one spelling that means nothing.
14
+ * Both trimmed, because a trailing newline comes free from a `.env` file or a here-doc.
15
+ */
16
+ export type TelemetryEnvSwitch = 'DO_NOT_TRACK' | 'ALIGN_TELEMETRY';
17
+ /** The variable that disables telemetry, or undefined when neither does. */
18
+ export declare function telemetryDisabledByEnv(): TelemetryEnvSwitch | undefined;
19
+ //# sourceMappingURL=telemetry-env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-env.d.ts","sourceRoot":"","sources":["../../src/lib/telemetry-env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,kBAAkB,GAAG,cAAc,GAAG,iBAAiB,CAAC;AAKpE,4EAA4E;AAC5E,wBAAgB,sBAAsB,IAAI,kBAAkB,GAAG,SAAS,CAMvE"}
@@ -0,0 +1,13 @@
1
+ const ON_VALUES = new Set(['1', 'true', 'yes', 'on']);
2
+ const OFF_VALUES = new Set(['0', 'false', 'no', 'off']);
3
+ /** The variable that disables telemetry, or undefined when neither does. */
4
+ export function telemetryDisabledByEnv() {
5
+ const dnt = process.env['DO_NOT_TRACK']?.trim().toLowerCase();
6
+ if (dnt !== undefined && dnt !== '' && !OFF_VALUES.has(dnt))
7
+ return 'DO_NOT_TRACK';
8
+ const ours = process.env['ALIGN_TELEMETRY']?.trim().toLowerCase();
9
+ if (ours !== undefined && ours !== '' && !ON_VALUES.has(ours))
10
+ return 'ALIGN_TELEMETRY';
11
+ return undefined;
12
+ }
13
+ //# sourceMappingURL=telemetry-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-env.js","sourceRoot":"","sources":["../../src/lib/telemetry-env.ts"],"names":[],"mappings":"AAiBA,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;AACtD,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AAExD,4EAA4E;AAC5E,MAAM,UAAU,sBAAsB;IACpC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC9D,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,cAAc,CAAC;IACnF,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAClE,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,iBAAiB,CAAC;IACxF,OAAO,SAAS,CAAC;AACnB,CAAC"}