@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.
- package/README.md +16 -10
- package/dist/cli.d.ts +28 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +98 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/check.d.ts +1 -1
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +44 -2
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/connect-agents.d.ts +1 -0
- package/dist/commands/connect-agents.d.ts.map +1 -1
- package/dist/commands/connect-agents.js +10 -6
- package/dist/commands/connect-agents.js.map +1 -1
- package/dist/commands/connect.d.ts +31 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +92 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +52 -10
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/decisions/index.d.ts.map +1 -1
- package/dist/commands/decisions/index.js +5 -0
- package/dist/commands/decisions/index.js.map +1 -1
- package/dist/commands/default-action.d.ts +3 -1
- package/dist/commands/default-action.d.ts.map +1 -1
- package/dist/commands/default-action.js +29 -27
- package/dist/commands/default-action.js.map +1 -1
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +3 -0
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/import/sessions.d.ts +10 -2
- package/dist/commands/import/sessions.d.ts.map +1 -1
- package/dist/commands/import/sessions.js +98 -3
- package/dist/commands/import/sessions.js.map +1 -1
- package/dist/commands/import.d.ts +7 -0
- package/dist/commands/import.d.ts.map +1 -1
- package/dist/commands/import.js +42 -2
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/invite.d.ts +25 -0
- package/dist/commands/invite.d.ts.map +1 -0
- package/dist/commands/invite.js +68 -0
- package/dist/commands/invite.js.map +1 -0
- package/dist/commands/links.d.ts.map +1 -1
- package/dist/commands/links.js +5 -0
- package/dist/commands/links.js.map +1 -1
- package/dist/commands/mcp.d.ts +64 -35
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +85 -40
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/ratify.d.ts.map +1 -1
- package/dist/commands/ratify.js +21 -0
- package/dist/commands/ratify.js.map +1 -1
- package/dist/commands/registry.d.ts +30 -0
- package/dist/commands/registry.d.ts.map +1 -0
- package/dist/commands/registry.js +70 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/setup.d.ts +45 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +197 -88
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +10 -22
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/telemetry.d.ts +7 -3
- package/dist/commands/telemetry.d.ts.map +1 -1
- package/dist/commands/telemetry.js +12 -7
- package/dist/commands/telemetry.js.map +1 -1
- package/dist/commands/why.d.ts.map +1 -1
- package/dist/commands/why.js +52 -10
- package/dist/commands/why.js.map +1 -1
- package/dist/index.js +4 -90
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-rules.d.ts +1 -0
- package/dist/lib/agent-rules.d.ts.map +1 -1
- package/dist/lib/agent-rules.js +60 -0
- package/dist/lib/agent-rules.js.map +1 -1
- package/dist/lib/commands-doc.d.ts +15 -0
- package/dist/lib/commands-doc.d.ts.map +1 -0
- package/dist/lib/commands-doc.js +85 -0
- package/dist/lib/commands-doc.js.map +1 -0
- package/dist/lib/config.d.ts +7 -2
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/connect-prompt.d.ts +1 -1
- package/dist/lib/connect-prompt.js +2 -2
- package/dist/lib/connect-prompt.js.map +1 -1
- package/dist/lib/decision-links.d.ts +39 -0
- package/dist/lib/decision-links.d.ts.map +1 -1
- package/dist/lib/decision-links.js +51 -0
- package/dist/lib/decision-links.js.map +1 -1
- package/dist/lib/first-decision.d.ts +22 -0
- package/dist/lib/first-decision.d.ts.map +1 -0
- package/dist/lib/first-decision.js +22 -0
- package/dist/lib/first-decision.js.map +1 -0
- package/dist/lib/gateway-client.d.ts +13 -0
- package/dist/lib/gateway-client.d.ts.map +1 -1
- package/dist/lib/gateway-client.js +14 -0
- package/dist/lib/gateway-client.js.map +1 -1
- package/dist/lib/git.d.ts +28 -0
- package/dist/lib/git.d.ts.map +1 -1
- package/dist/lib/git.js +42 -0
- package/dist/lib/git.js.map +1 -1
- package/dist/lib/hook-context.d.ts +7 -0
- package/dist/lib/hook-context.d.ts.map +1 -0
- package/dist/lib/hook-context.js +37 -0
- package/dist/lib/hook-context.js.map +1 -0
- package/dist/lib/hook-payload.d.ts +7 -0
- package/dist/lib/hook-payload.d.ts.map +1 -1
- package/dist/lib/hook-payload.js +56 -8
- package/dist/lib/hook-payload.js.map +1 -1
- package/dist/lib/invite-prompt.d.ts +46 -0
- package/dist/lib/invite-prompt.d.ts.map +1 -0
- package/dist/lib/invite-prompt.js +63 -0
- package/dist/lib/invite-prompt.js.map +1 -0
- package/dist/lib/mcp-instructions.shared.d.ts +40 -0
- package/dist/lib/mcp-instructions.shared.d.ts.map +1 -0
- package/dist/lib/mcp-instructions.shared.js +56 -0
- package/dist/lib/mcp-instructions.shared.js.map +1 -0
- package/dist/lib/mcp-setup.d.ts +32 -2
- package/dist/lib/mcp-setup.d.ts.map +1 -1
- package/dist/lib/mcp-setup.js +92 -6
- package/dist/lib/mcp-setup.js.map +1 -1
- package/dist/lib/next-step.d.ts +92 -0
- package/dist/lib/next-step.d.ts.map +1 -0
- package/dist/lib/next-step.js +97 -0
- package/dist/lib/next-step.js.map +1 -0
- package/dist/lib/personal-email-domains.d.ts +8 -0
- package/dist/lib/personal-email-domains.d.ts.map +1 -0
- package/dist/lib/personal-email-domains.js +39 -0
- package/dist/lib/personal-email-domains.js.map +1 -0
- package/dist/lib/personal-import.d.ts +2 -0
- package/dist/lib/personal-import.d.ts.map +1 -1
- package/dist/lib/personal-import.js +4 -1
- package/dist/lib/personal-import.js.map +1 -1
- package/dist/lib/read-value-rollup.d.ts +18 -0
- package/dist/lib/read-value-rollup.d.ts.map +1 -0
- package/dist/lib/read-value-rollup.js +33 -0
- package/dist/lib/read-value-rollup.js.map +1 -0
- package/dist/lib/sessions/import-summary.d.ts +39 -0
- package/dist/lib/sessions/import-summary.d.ts.map +1 -0
- package/dist/lib/sessions/import-summary.js +41 -0
- package/dist/lib/sessions/import-summary.js.map +1 -0
- package/dist/lib/sessions/memory-files.d.ts +85 -0
- package/dist/lib/sessions/memory-files.d.ts.map +1 -0
- package/dist/lib/sessions/memory-files.js +235 -0
- package/dist/lib/sessions/memory-files.js.map +1 -0
- package/dist/lib/sessions/source-url.d.ts +11 -0
- package/dist/lib/sessions/source-url.d.ts.map +1 -1
- package/dist/lib/sessions/source-url.js +15 -0
- package/dist/lib/sessions/source-url.js.map +1 -1
- package/dist/lib/setup-funnel.d.ts +41 -0
- package/dist/lib/setup-funnel.d.ts.map +1 -0
- package/dist/lib/setup-funnel.js +28 -0
- package/dist/lib/setup-funnel.js.map +1 -0
- package/dist/lib/telemetry-consent.d.ts +17 -8
- package/dist/lib/telemetry-consent.d.ts.map +1 -1
- package/dist/lib/telemetry-consent.js +21 -6
- package/dist/lib/telemetry-consent.js.map +1 -1
- package/dist/lib/telemetry-env.d.ts +19 -0
- package/dist/lib/telemetry-env.d.ts.map +1 -0
- package/dist/lib/telemetry-env.js +13 -0
- package/dist/lib/telemetry-env.js.map +1 -0
- package/dist/lib/usage-telemetry.d.ts +108 -13
- package/dist/lib/usage-telemetry.d.ts.map +1 -1
- package/dist/lib/usage-telemetry.js +227 -55
- package/dist/lib/usage-telemetry.js.map +1 -1
- package/dist/lib/user-hooks.d.ts +15 -0
- package/dist/lib/user-hooks.d.ts.map +1 -0
- package/dist/lib/user-hooks.js +144 -0
- package/dist/lib/user-hooks.js.map +1 -0
- package/dist/lib/value-rollup.d.ts +10 -3
- package/dist/lib/value-rollup.d.ts.map +1 -1
- package/dist/lib/value-rollup.js +11 -4
- package/dist/lib/value-rollup.js.map +1 -1
- 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
|
|
13
|
-
* prompts without a TTY on both streams: a piped `setup --local` run
|
|
14
|
-
* cannot answer, and a closed stdin crashes clack's raw-mode init AFTER
|
|
15
|
-
* already succeeded (the align-cli#118 lesson,
|
|
16
|
-
* run leaves consent UNSET rather than
|
|
17
|
-
*
|
|
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":"
|
|
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
|
|
6
|
-
* prompts without a TTY on both streams: a piped `setup --local` run
|
|
7
|
-
* cannot answer, and a closed stdin crashes clack's raw-mode init AFTER
|
|
8
|
-
* already succeeded (the align-cli#118 lesson,
|
|
9
|
-
* run leaves consent UNSET rather than
|
|
10
|
-
*
|
|
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;
|
|
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"}
|