@meetless/mla 0.1.4

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 (202) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +81 -0
  3. package/dist/build-info.json +9 -0
  4. package/dist/bundles/ask-core.js +396 -0
  5. package/dist/bundles/mcp.js +16592 -0
  6. package/dist/bundles/trace-core.js +263 -0
  7. package/dist/cli.js +828 -0
  8. package/dist/commands/activate.js +781 -0
  9. package/dist/commands/adoption.js +130 -0
  10. package/dist/commands/ask.js +290 -0
  11. package/dist/commands/context.js +114 -0
  12. package/dist/commands/debug.js +313 -0
  13. package/dist/commands/doctor.js +1021 -0
  14. package/dist/commands/enrich.js +427 -0
  15. package/dist/commands/evidence.js +229 -0
  16. package/dist/commands/flush.js +184 -0
  17. package/dist/commands/graph.js +104 -0
  18. package/dist/commands/init.js +272 -0
  19. package/dist/commands/internal-active-review.js +322 -0
  20. package/dist/commands/internal-auto-index.js +188 -0
  21. package/dist/commands/internal-capture-decisions.js +320 -0
  22. package/dist/commands/internal-evidence-correlate.js +239 -0
  23. package/dist/commands/internal-evidence-hooks.js +240 -0
  24. package/dist/commands/internal-evidence-inject.js +231 -0
  25. package/dist/commands/internal-finalize.js +221 -0
  26. package/dist/commands/internal-pretool-observe.js +225 -0
  27. package/dist/commands/internal-refresh.js +136 -0
  28. package/dist/commands/internal-session-nudge.js +120 -0
  29. package/dist/commands/internal-steer-sync.js +117 -0
  30. package/dist/commands/internal-turn-recap.js +140 -0
  31. package/dist/commands/kb.js +375 -0
  32. package/dist/commands/kb_add.js +681 -0
  33. package/dist/commands/kb_forget.js +283 -0
  34. package/dist/commands/kb_move.js +45 -0
  35. package/dist/commands/kb_pending.js +410 -0
  36. package/dist/commands/kb_personal.js +149 -0
  37. package/dist/commands/kb_promote.js +188 -0
  38. package/dist/commands/kb_purge.js +168 -0
  39. package/dist/commands/kb_reingest.js +335 -0
  40. package/dist/commands/kb_retime.js +170 -0
  41. package/dist/commands/kb_review.js +391 -0
  42. package/dist/commands/kb_revision.js +179 -0
  43. package/dist/commands/kb_show.js +385 -0
  44. package/dist/commands/label.js +226 -0
  45. package/dist/commands/login.js +295 -0
  46. package/dist/commands/logout.js +108 -0
  47. package/dist/commands/mcp-supervisor.js +93 -0
  48. package/dist/commands/mcp.js +227 -0
  49. package/dist/commands/queue-prune.js +98 -0
  50. package/dist/commands/review.js +358 -0
  51. package/dist/commands/rewire.js +124 -0
  52. package/dist/commands/rules.js +728 -0
  53. package/dist/commands/scan-context.js +67 -0
  54. package/dist/commands/session.js +347 -0
  55. package/dist/commands/stats.js +479 -0
  56. package/dist/commands/status.js +61 -0
  57. package/dist/commands/summary.js +250 -0
  58. package/dist/commands/turn.js +114 -0
  59. package/dist/commands/uninstall.js +222 -0
  60. package/dist/commands/whoami.js +102 -0
  61. package/dist/commands/workspace.js +130 -0
  62. package/dist/hooks-template/ce0-post-tool-use.sh +34 -0
  63. package/dist/hooks-template/ce0-session-start.sh +49 -0
  64. package/dist/hooks-template/ce0-stop.sh +29 -0
  65. package/dist/hooks-template/ce0-user-prompt-submit.sh +38 -0
  66. package/dist/hooks-template/common.sh +934 -0
  67. package/dist/hooks-template/event-batch-filter.jq +67 -0
  68. package/dist/hooks-template/flush.sh +503 -0
  69. package/dist/hooks-template/post-tool-use.sh +423 -0
  70. package/dist/hooks-template/pre-tool-use.sh +69 -0
  71. package/dist/hooks-template/session-start.sh +140 -0
  72. package/dist/hooks-template/stop.sh +308 -0
  73. package/dist/hooks-template/user-prompt-submit.sh +1162 -0
  74. package/dist/lib/activation.js +79 -0
  75. package/dist/lib/active-conflict-cache.js +141 -0
  76. package/dist/lib/active-memory.js +59 -0
  77. package/dist/lib/active-review-runner.js +26 -0
  78. package/dist/lib/agent-decision/index.js +25 -0
  79. package/dist/lib/agent-decision/keys.js +49 -0
  80. package/dist/lib/agent-decision/normalize-claude.js +183 -0
  81. package/dist/lib/agent-decision/types.js +21 -0
  82. package/dist/lib/agent-decision/validate.js +216 -0
  83. package/dist/lib/analytics/capture.js +96 -0
  84. package/dist/lib/analytics/command-event.js +267 -0
  85. package/dist/lib/analytics/consent.js +58 -0
  86. package/dist/lib/analytics/coverage-gap.js +96 -0
  87. package/dist/lib/analytics/envelope.js +236 -0
  88. package/dist/lib/analytics/event-id.js +86 -0
  89. package/dist/lib/analytics/evidence.js +150 -0
  90. package/dist/lib/analytics/followthrough.js +194 -0
  91. package/dist/lib/analytics/forwarder.js +109 -0
  92. package/dist/lib/analytics/logs.js +78 -0
  93. package/dist/lib/analytics/metrics.js +78 -0
  94. package/dist/lib/analytics/recorder.js +92 -0
  95. package/dist/lib/analytics/review-analytics.js +75 -0
  96. package/dist/lib/analytics/sequence.js +77 -0
  97. package/dist/lib/analytics/store.js +131 -0
  98. package/dist/lib/analytics/turn-recap.js +279 -0
  99. package/dist/lib/artifact_id.js +108 -0
  100. package/dist/lib/auth-breaker.js +161 -0
  101. package/dist/lib/auto-index.js +112 -0
  102. package/dist/lib/classifier.js +88 -0
  103. package/dist/lib/config.js +298 -0
  104. package/dist/lib/conflict-advisory.js +64 -0
  105. package/dist/lib/debug-bundle.js +520 -0
  106. package/dist/lib/enrichment/ingest.js +301 -0
  107. package/dist/lib/enrichment/plan.js +253 -0
  108. package/dist/lib/enrichment/protocol.js +359 -0
  109. package/dist/lib/enrichment/scout-brief.js +176 -0
  110. package/dist/lib/failure-telemetry.js +444 -0
  111. package/dist/lib/git.js +200 -0
  112. package/dist/lib/governance-cache.js +77 -0
  113. package/dist/lib/governed-path-cache.js +76 -0
  114. package/dist/lib/http.js +677 -0
  115. package/dist/lib/identity-envelope.js +23 -0
  116. package/dist/lib/kb-candidate.js +65 -0
  117. package/dist/lib/kb_acl.js +98 -0
  118. package/dist/lib/login.js +353 -0
  119. package/dist/lib/mcp-fetchers.js +130 -0
  120. package/dist/lib/mcp-restart.js +47 -0
  121. package/dist/lib/observability.js +805 -0
  122. package/dist/lib/open-url.js +33 -0
  123. package/dist/lib/orphan-guard.js +70 -0
  124. package/dist/lib/packaged.js +21 -0
  125. package/dist/lib/reconcile-sessions.js +171 -0
  126. package/dist/lib/redactor.js +89 -0
  127. package/dist/lib/relationship-candidate-query.js +27 -0
  128. package/dist/lib/render.js +611 -0
  129. package/dist/lib/rules/applicability.js +64 -0
  130. package/dist/lib/rules/attest-code-rule-version.js +47 -0
  131. package/dist/lib/rules/attest-notes-location.js +217 -0
  132. package/dist/lib/rules/attest-rule-version.js +69 -0
  133. package/dist/lib/rules/canonical-json.js +97 -0
  134. package/dist/lib/rules/ce0-emit.js +64 -0
  135. package/dist/lib/rules/ce0-evidence.js +281 -0
  136. package/dist/lib/rules/ce0-recall-sample.js +82 -0
  137. package/dist/lib/rules/ce0-rule.js +55 -0
  138. package/dist/lib/rules/ce0-sampling-bucket.js +15 -0
  139. package/dist/lib/rules/ce0-store.js +683 -0
  140. package/dist/lib/rules/ce0-telemetry-project.js +93 -0
  141. package/dist/lib/rules/ce0-telemetry.js +158 -0
  142. package/dist/lib/rules/code-rule-registry.js +17 -0
  143. package/dist/lib/rules/command-match.js +185 -0
  144. package/dist/lib/rules/consult-evidence-binding.js +27 -0
  145. package/dist/lib/rules/consultation-capture-adapter.js +193 -0
  146. package/dist/lib/rules/content-match.js +56 -0
  147. package/dist/lib/rules/deny-admission.js +99 -0
  148. package/dist/lib/rules/durable-observation.js +190 -0
  149. package/dist/lib/rules/enforce-notes-version.js +421 -0
  150. package/dist/lib/rules/evaluation-input-hash.js +126 -0
  151. package/dist/lib/rules/evaluator.js +108 -0
  152. package/dist/lib/rules/inert-rule-families.js +51 -0
  153. package/dist/lib/rules/input-authority-resolver.js +241 -0
  154. package/dist/lib/rules/interception-schema.js +170 -0
  155. package/dist/lib/rules/interception-store.js +267 -0
  156. package/dist/lib/rules/live-input-authority.js +66 -0
  157. package/dist/lib/rules/local-matcher.js +108 -0
  158. package/dist/lib/rules/local-observe.js +79 -0
  159. package/dist/lib/rules/local-rule-version-repo.js +214 -0
  160. package/dist/lib/rules/memory-requirement.js +109 -0
  161. package/dist/lib/rules/notes-observe.js +39 -0
  162. package/dist/lib/rules/notes-path.js +261 -0
  163. package/dist/lib/rules/notes-rule.js +75 -0
  164. package/dist/lib/rules/observe-adapter.js +114 -0
  165. package/dist/lib/rules/observed-rule-hash.js +119 -0
  166. package/dist/lib/rules/prompt-submit-adapter.js +132 -0
  167. package/dist/lib/rules/requirement-subject.js +240 -0
  168. package/dist/lib/rules/rule-activity.js +67 -0
  169. package/dist/lib/rules/rule-version-hash.js +151 -0
  170. package/dist/lib/rules/runtime-scope.js +55 -0
  171. package/dist/lib/rules/stop-adapter.js +116 -0
  172. package/dist/lib/rules/stop-response-snapshot.js +174 -0
  173. package/dist/lib/rules/types.js +10 -0
  174. package/dist/lib/rules/ulid.js +46 -0
  175. package/dist/lib/rules/version-evaluation.js +156 -0
  176. package/dist/lib/scanner/agent-memory.js +99 -0
  177. package/dist/lib/scanner/bootstrap-summary.js +87 -0
  178. package/dist/lib/scanner/cache.js +59 -0
  179. package/dist/lib/scanner/frontmatter.js +42 -0
  180. package/dist/lib/scanner/parse-directives.js +69 -0
  181. package/dist/lib/scanner/parse-structured.js +72 -0
  182. package/dist/lib/scanner/render.js +73 -0
  183. package/dist/lib/scanner/scan.js +132 -0
  184. package/dist/lib/scanner/score.js +38 -0
  185. package/dist/lib/scanner/scout-mission.js +126 -0
  186. package/dist/lib/scanner/types.js +7 -0
  187. package/dist/lib/session-scope.js +195 -0
  188. package/dist/lib/spool.js +355 -0
  189. package/dist/lib/staleness.js +100 -0
  190. package/dist/lib/steer-cache.js +87 -0
  191. package/dist/lib/tagged-reference.js +20 -0
  192. package/dist/lib/temporal.js +109 -0
  193. package/dist/lib/turn-recap-emit.js +67 -0
  194. package/dist/lib/unwire.js +253 -0
  195. package/dist/lib/update-check.js +469 -0
  196. package/dist/lib/update-notifier.js +217 -0
  197. package/dist/lib/upgrade-apply.js +643 -0
  198. package/dist/lib/wire.js +1087 -0
  199. package/dist/lib/workspace.js +96 -0
  200. package/dist/lib/zip.js +154 -0
  201. package/dist/pretool-entry.js +37 -0
  202. package/package.json +75 -0
@@ -0,0 +1,469 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.RELEASE_TRIPLES = exports.DEFAULT_MANIFEST_URL = exports.DEFAULT_UPDATE_URL = exports.UPDATE_STATE_FILE = exports.UPDATE_CHECK_INTERVAL_MS = void 0;
37
+ exports.parseManifest = parseManifest;
38
+ exports.verifyManifestSignature = verifyManifestSignature;
39
+ exports.currentTriple = currentTriple;
40
+ exports.selectArtifact = selectArtifact;
41
+ exports.isBelowMinVersion = isBelowMinVersion;
42
+ exports.parseVersion = parseVersion;
43
+ exports.isNewerVersion = isNewerVersion;
44
+ exports.isCI = isCI;
45
+ exports.notifierDisabled = notifierDisabled;
46
+ exports.upgradeKillSwitch = upgradeKillSwitch;
47
+ exports.autoUpgradeDisabled = autoUpgradeDisabled;
48
+ exports.resolveAutoApply = resolveAutoApply;
49
+ exports.shouldRunCheck = shouldRunCheck;
50
+ exports.shouldShowNag = shouldShowNag;
51
+ exports.detectInstallMethod = detectInstallMethod;
52
+ exports.upgradeCommandFor = upgradeCommandFor;
53
+ exports.formatUpdateNag = formatUpdateNag;
54
+ exports.planUpgrade = planUpgrade;
55
+ exports.parseState = parseState;
56
+ exports.serializeState = serializeState;
57
+ // Update notifier, copied from gh's model: a background version check on a 24h
58
+ // throttle plus an install-method-aware upgrade nag. Two rules, both deliberate:
59
+ //
60
+ // 1. NEVER auto-apply. We detect how `mla` was installed and print the right
61
+ // upgrade command; we never rename a binary over a brew/npm-managed path
62
+ // (that corrupts their metadata). Detect-and-redirect, not self-replace.
63
+ // 2. NEVER block or spam. The fetch runs in a detached child so the parent
64
+ // exits instantly; the nag shows only on a real TTY, off CI, and honors
65
+ // MLA_NO_UPDATE_NOTIFIER.
66
+ //
67
+ // This file is the PURE core (throttle, version compare, install-method
68
+ // detection, nag text). The IO wrappers (spawn the child, fetch GitHub, read/
69
+ // write the cache) live in update-notifier.ts so this stays unit-testable.
70
+ const path = __importStar(require("path"));
71
+ const os = __importStar(require("os"));
72
+ const crypto = __importStar(require("crypto"));
73
+ exports.UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24h, gh's cadence
74
+ exports.UPDATE_STATE_FILE = "update-check.json";
75
+ // Where the update check learns the latest published version: a plaintext file
76
+ // on the public meetless-public bucket holding the bare version (e.g. "0.4.2"),
77
+ // written by release.yml next to the binaries. We are not open-sourced yet, so
78
+ // there is no public GitHub Releases feed; this keeps the nag and the installer
79
+ // agreeing on the same release host. Override with MLA_UPDATE_URL.
80
+ exports.DEFAULT_UPDATE_URL = "https://storage.googleapis.com/meetless-public/cli/releases/latest/VERSION";
81
+ // --- signed release manifest -------------------------------------------------
82
+ // The signed manifest is the source of truth the upgrade path reads: the latest
83
+ // version, the floor below which a client is forced to upgrade, and a per-triple
84
+ // {url, sha256} so a client can download and verify the exact bytes for its own
85
+ // platform. It is fetched alongside a detached Ed25519 signature (manifest.json
86
+ // .sig) so a tampered manifest is rejected before any byte is trusted.
87
+ //
88
+ // Hosted on the same public bucket as the binaries so the installer, the cask,
89
+ // and the upgrade path all agree on one release host. The bucket can later sit
90
+ // behind the meetless.ai load balancer with zero code change (override the URL).
91
+ exports.DEFAULT_MANIFEST_URL = "https://storage.googleapis.com/meetless-public/cli/releases/latest/manifest.json";
92
+ // The three triples we publish. Hard invariants: install.sh, the Homebrew cask,
93
+ // and this list MUST stay byte-identical or a platform silently loses upgrades.
94
+ exports.RELEASE_TRIPLES = [
95
+ "aarch64-apple-darwin",
96
+ "x86_64-apple-darwin",
97
+ "x86_64-unknown-linux-gnu",
98
+ ];
99
+ const SHA256_RE = /^[0-9a-f]{64}$/;
100
+ const BARE_SEMVER_RE = /^\d+\.\d+\.\d+([.-][0-9A-Za-z.-]+)?$/;
101
+ // Allow https anywhere; allow http ONLY for loopback hosts. Loopback can't be
102
+ // MITM'd, which is exactly what the local eval server needs, and a real release
103
+ // URL is always https so production never downgrades the transport.
104
+ function isAllowedArtifactUrl(u) {
105
+ try {
106
+ const url = new URL(u);
107
+ if (url.protocol === "https:")
108
+ return true;
109
+ if (url.protocol === "http:" &&
110
+ (url.hostname === "127.0.0.1" || url.hostname === "::1" || url.hostname === "localhost")) {
111
+ return true;
112
+ }
113
+ return false;
114
+ }
115
+ catch {
116
+ return false;
117
+ }
118
+ }
119
+ // Parse + validate a manifest's JSON text. Returns null on ANY shape violation
120
+ // (wrong schemaVersion, bad semver, non-hex sha, disallowed url, empty artifact
121
+ // set) so a malformed or truncated manifest is treated as "no update", never
122
+ // acted on. Pure: callers verify the signature over the raw bytes first.
123
+ function parseManifest(raw) {
124
+ if (!raw)
125
+ return null;
126
+ let o;
127
+ try {
128
+ o = JSON.parse(raw);
129
+ }
130
+ catch {
131
+ return null;
132
+ }
133
+ if (!o || typeof o !== "object")
134
+ return null;
135
+ if (o.schemaVersion !== 1)
136
+ return null;
137
+ if (typeof o.channel !== "string" || !o.channel)
138
+ return null;
139
+ if (typeof o.version !== "string" || !BARE_SEMVER_RE.test(o.version))
140
+ return null;
141
+ if (typeof o.minVersion !== "string" || !BARE_SEMVER_RE.test(o.minVersion))
142
+ return null;
143
+ if (typeof o.releasedAt !== "string" || !o.releasedAt)
144
+ return null;
145
+ if (!o.artifacts || typeof o.artifacts !== "object")
146
+ return null;
147
+ const artifacts = {};
148
+ for (const [triple, a] of Object.entries(o.artifacts)) {
149
+ if (!a || typeof a !== "object")
150
+ return null;
151
+ const url = a.url;
152
+ const sha256 = a.sha256;
153
+ if (typeof url !== "string" || !isAllowedArtifactUrl(url))
154
+ return null;
155
+ if (typeof sha256 !== "string" || !SHA256_RE.test(sha256))
156
+ return null;
157
+ artifacts[triple] = { url, sha256 };
158
+ }
159
+ if (Object.keys(artifacts).length === 0)
160
+ return null;
161
+ const m = {
162
+ schemaVersion: 1,
163
+ channel: o.channel,
164
+ version: o.version,
165
+ minVersion: o.minVersion,
166
+ releasedAt: o.releasedAt,
167
+ artifacts,
168
+ };
169
+ if (typeof o.notes === "string")
170
+ m.notes = o.notes;
171
+ return m;
172
+ }
173
+ // Verify a detached Ed25519 signature over the EXACT manifest bytes against a
174
+ // trust list of SPKI PEM public keys. True iff any key verifies (so the prod key
175
+ // and a rotation key can be trusted at once). Ed25519 takes a null algorithm in
176
+ // Node's crypto (the hash is intrinsic). A malformed key or signature returns
177
+ // false, never throws: an unverifiable manifest is simply not trusted.
178
+ function verifyManifestSignature(manifestBytes, signatureB64, publicKeysPem) {
179
+ if (!signatureB64 || publicKeysPem.length === 0)
180
+ return false;
181
+ let sig;
182
+ try {
183
+ sig = Buffer.from(signatureB64.trim(), "base64");
184
+ }
185
+ catch {
186
+ return false;
187
+ }
188
+ if (sig.length === 0)
189
+ return false;
190
+ for (const pem of publicKeysPem) {
191
+ if (!pem || !pem.trim())
192
+ continue;
193
+ try {
194
+ const key = crypto.createPublicKey(pem);
195
+ if (crypto.verify(null, manifestBytes, key, sig))
196
+ return true;
197
+ }
198
+ catch {
199
+ // a bad key in the trust list is skipped, not fatal
200
+ }
201
+ }
202
+ return false;
203
+ }
204
+ // Map this process's platform/arch to a release triple, or null if unsupported
205
+ // (e.g. win32, or linux-arm64 which we do not publish). Mirrors install.sh's
206
+ // detect_target so the upgrade path picks the same artifact the installer would.
207
+ function currentTriple(platform, arch) {
208
+ if (platform === "darwin") {
209
+ if (arch === "arm64")
210
+ return "aarch64-apple-darwin";
211
+ if (arch === "x64")
212
+ return "x86_64-apple-darwin";
213
+ return null;
214
+ }
215
+ if (platform === "linux") {
216
+ if (arch === "x64")
217
+ return "x86_64-unknown-linux-gnu";
218
+ return null;
219
+ }
220
+ return null;
221
+ }
222
+ function selectArtifact(manifest, triple) {
223
+ if (!triple)
224
+ return null;
225
+ return manifest.artifacts[triple] ?? null;
226
+ }
227
+ // True iff `current` is strictly below `minVersion` (the forced-upgrade floor).
228
+ // Reuses the dev-build-safe comparison: an unparseable current is never "below".
229
+ function isBelowMinVersion(current, minVersion) {
230
+ return isNewerVersion(minVersion, current);
231
+ }
232
+ // --- version comparison ------------------------------------------------------
233
+ // Parse "1.2.3" / "v1.2.3" / "1.2.3-dirty" / "abc123-dirty" into [major,minor,
234
+ // patch], or null if it has no clean numeric core. Build suffixes (a git sha,
235
+ // "-dirty", "+meta") are ignored: a dev build like "b6a81f7a-dirty" has no
236
+ // semver and must never be compared as if it were behind or ahead.
237
+ function parseVersion(v) {
238
+ if (!v)
239
+ return null;
240
+ const m = /^v?(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
241
+ if (!m)
242
+ return null;
243
+ return [Number(m[1]), Number(m[2]), Number(m[3])];
244
+ }
245
+ // True iff `latest` is strictly newer than `current`. If either side has no
246
+ // parseable semver (e.g. a dev `<sha>-dirty` build), return false: we never nag
247
+ // someone whose version we can't reason about.
248
+ function isNewerVersion(latest, current) {
249
+ const a = parseVersion(latest);
250
+ const b = parseVersion(current);
251
+ if (!a || !b)
252
+ return false;
253
+ for (let i = 0; i < 3; i++) {
254
+ if (a[i] > b[i])
255
+ return true;
256
+ if (a[i] < b[i])
257
+ return false;
258
+ }
259
+ return false;
260
+ }
261
+ // --- environment gating ------------------------------------------------------
262
+ // Standard CI markers. The notifier is pointless (and noisy in logs) on CI.
263
+ function isCI(env) {
264
+ return Boolean(env.CI ||
265
+ env.CONTINUOUS_INTEGRATION ||
266
+ env.GITHUB_ACTIONS ||
267
+ env.GITLAB_CI ||
268
+ env.BUILDKITE ||
269
+ env.CIRCLECI ||
270
+ env.TEAMCITY_VERSION);
271
+ }
272
+ // Shared env-flag truthiness: unset / "" / "0" / "false" is off, anything else
273
+ // (1, yes, true, ...) is on. One definition so every opt-out reads the same way.
274
+ function envTruthy(v) {
275
+ const s = (v || "").trim().toLowerCase();
276
+ return s !== "" && s !== "0" && s !== "false";
277
+ }
278
+ // The user-facing opt-out (gh uses GH_NO_UPDATE_NOTIFIER; we mirror it).
279
+ function notifierDisabled(env) {
280
+ return envTruthy(env.MLA_NO_UPDATE_NOTIFIER);
281
+ }
282
+ // --- upgrade opt-out precedence ----------------------------------------------
283
+ // The total kill switch. When set, the binary does NO self-management at all:
284
+ // no background check, no nag, no staging, no apply-on-launch, and even an
285
+ // explicit `mla upgrade` refuses. For locked-down / managed environments that
286
+ // want the binary to never modify itself or phone home about versions.
287
+ function upgradeKillSwitch(env) {
288
+ return envTruthy(env.MLA_DISABLE_UPGRADE);
289
+ }
290
+ // Disable only the automatic self-replace (background staging + apply-on-launch).
291
+ // The check and the nag still run, and explicit `mla upgrade` still works; the
292
+ // binary just never swaps itself without the user typing the command.
293
+ function autoUpgradeDisabled(env) {
294
+ return envTruthy(env.MLA_DISABLE_AUTO_UPGRADE);
295
+ }
296
+ // Whether the binary may replace itself unattended (stage in the background and
297
+ // promote on launch). Precedence, highest first:
298
+ // MLA_DISABLE_UPGRADE > MLA_DISABLE_AUTO_UPGRADE > MLA_NO_UPDATE_NOTIFIER
299
+ // > config.update.autoApply (default false: nag-only).
300
+ function resolveAutoApply(opts) {
301
+ const { env, configAutoApply } = opts;
302
+ if (upgradeKillSwitch(env))
303
+ return false;
304
+ if (autoUpgradeDisabled(env))
305
+ return false;
306
+ if (notifierDisabled(env))
307
+ return false;
308
+ return configAutoApply === true;
309
+ }
310
+ // --- throttle ----------------------------------------------------------------
311
+ // Should the background fetch run? Only when notifications could ever be shown
312
+ // (not disabled, not CI) AND the throttle window has elapsed. Gating the fetch
313
+ // here means a disabled notifier makes zero network calls.
314
+ function shouldRunCheck(opts) {
315
+ const { state, now, env } = opts;
316
+ if (upgradeKillSwitch(env) || notifierDisabled(env) || isCI(env))
317
+ return false;
318
+ return now - state.lastCheckedAt >= exports.UPDATE_CHECK_INTERVAL_MS;
319
+ }
320
+ // --- nag display gating ------------------------------------------------------
321
+ // Should we print the upgrade nag now? Requires a cached newer version, an
322
+ // interactive session (both stdout and stderr are TTYs, so piping `mla ... |`
323
+ // stays clean), off CI, and not opted out.
324
+ function shouldShowNag(opts) {
325
+ const { state, currentVersion, env, stdoutTTY, stderrTTY } = opts;
326
+ if (notifierDisabled(env) || isCI(env))
327
+ return false;
328
+ if (!stdoutTTY || !stderrTTY)
329
+ return false;
330
+ return isNewerVersion(state.latestVersion, currentVersion);
331
+ }
332
+ // --- install-method detection ------------------------------------------------
333
+ // Detect how this `mla` was installed so the nag prints the command that
334
+ // actually works. We check both the running binary path (pkg build) and the
335
+ // script path (node + cli.js for source/npm), because which one is meaningful
336
+ // depends on the install:
337
+ // - curl|sh -> binary lives under ~/.meetless/bin
338
+ // - homebrew -> path under a brew prefix or a Caskroom dir
339
+ // - npm -> path under a node_modules tree
340
+ // MLA_INSTALL_METHOD overrides everything (an explicit escape hatch + the test seam).
341
+ function detectInstallMethod(opts) {
342
+ const { execPath, scriptPath, env } = opts;
343
+ const override = (env.MLA_INSTALL_METHOD || "").trim().toLowerCase();
344
+ if (override === "homebrew" || override === "curl" || override === "npm" || override === "unknown") {
345
+ return override;
346
+ }
347
+ const home = opts.home ?? os.homedir();
348
+ const brewPrefixes = opts.brewPrefixes ?? ["/opt/homebrew", "/usr/local", "/home/linuxbrew/.linuxbrew"];
349
+ const candidates = [execPath, scriptPath].filter((p) => Boolean(p));
350
+ const within = (child, parent) => {
351
+ const rel = path.relative(parent, child);
352
+ return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
353
+ };
354
+ // curl|sh install dir wins first: it is the most specific and unambiguous.
355
+ const curlDir = path.join(home, ".meetless", "bin");
356
+ if (candidates.some((c) => within(c, curlDir)))
357
+ return "curl";
358
+ // Homebrew: a Caskroom path, or anything under a brew prefix.
359
+ if (candidates.some((c) => c.includes(`${path.sep}Caskroom${path.sep}`)))
360
+ return "homebrew";
361
+ if (candidates.some((c) => brewPrefixes.some((p) => within(c, p))))
362
+ return "homebrew";
363
+ // npm global / npx: served out of a node_modules tree.
364
+ if (candidates.some((c) => c.includes(`${path.sep}node_modules${path.sep}`)))
365
+ return "npm";
366
+ return "unknown";
367
+ }
368
+ // The upgrade command to print for each install method.
369
+ function upgradeCommandFor(method) {
370
+ switch (method) {
371
+ case "homebrew":
372
+ return "brew upgrade --cask mla";
373
+ case "curl":
374
+ return "curl -fsSL https://meetless.ai/install.sh | sh";
375
+ case "npm":
376
+ return "npm i -g @meetless/mla@latest";
377
+ case "unknown":
378
+ default:
379
+ // We genuinely don't know how it was installed; send them to the page
380
+ // rather than guess a command that might corrupt a managed install.
381
+ return "see https://meetless.ai/install";
382
+ }
383
+ }
384
+ // --- nag text ----------------------------------------------------------------
385
+ // The two-line nag, gh-style. Kept here so it is asserted in tests verbatim.
386
+ function formatUpdateNag(opts) {
387
+ const { current, latest, method, required } = opts;
388
+ const from = current ? `${current} ` : "";
389
+ const lead = required
390
+ ? `\nmla ${from}is below the minimum supported version. Please upgrade to ${latest}.\n`
391
+ : `\nA new release of mla is available: ${from}-> ${latest}\n`;
392
+ return lead + `To upgrade, run: ${upgradeCommandFor(method)}\n`;
393
+ }
394
+ // Decide what `mla upgrade` should do given the current version, the verified
395
+ // manifest, this machine's triple, and whether --force was passed. Encapsulates
396
+ // the downgrade guard: we never silently move a user to an OLDER build (a stale
397
+ // "latest" pointer, a rolled-back release) unless they explicitly force it.
398
+ function planUpgrade(opts) {
399
+ const { current, manifest, triple, force } = opts;
400
+ const to = manifest.version;
401
+ if (!triple || !selectArtifact(manifest, triple)) {
402
+ return { action: "no-artifact", from: current, to, triple };
403
+ }
404
+ if (isNewerVersion(to, current)) {
405
+ return { action: "upgrade", from: current, to, triple };
406
+ }
407
+ // Not newer: same version, older, or a current we can't parse.
408
+ if (force) {
409
+ return { action: "upgrade", from: current, to, triple };
410
+ }
411
+ const cur = parseVersion(current);
412
+ const tgt = parseVersion(to); // always parses; manifest.version is validated
413
+ if (!cur) {
414
+ return { action: "unparseable-current", from: current, to, triple };
415
+ }
416
+ if (tgt && cur[0] === tgt[0] && cur[1] === tgt[1] && cur[2] === tgt[2]) {
417
+ return { action: "up-to-date", from: current, to, triple };
418
+ }
419
+ return { action: "downgrade-blocked", from: current, to, triple };
420
+ }
421
+ // --- state (de)serialization -------------------------------------------------
422
+ const EMPTY_STATE = { lastCheckedAt: 0, latestVersion: null };
423
+ // Validate a staged-upgrade pointer read back from disk; null if any field is
424
+ // missing or the wrong type, so a corrupt staged record is ignored, never acted
425
+ // on (apply-on-launch re-verifies the file's sha before promoting regardless).
426
+ function parseStaged(v) {
427
+ if (!v || typeof v !== "object")
428
+ return null;
429
+ const o = v;
430
+ if (typeof o.version !== "string" || typeof o.triple !== "string")
431
+ return null;
432
+ if (typeof o.sha256 !== "string" || typeof o.path !== "string")
433
+ return null;
434
+ if (typeof o.stagedAt !== "number")
435
+ return null;
436
+ return {
437
+ version: o.version,
438
+ triple: o.triple,
439
+ sha256: o.sha256,
440
+ path: o.path,
441
+ stagedAt: o.stagedAt,
442
+ };
443
+ }
444
+ function parseState(raw) {
445
+ if (!raw)
446
+ return { ...EMPTY_STATE };
447
+ try {
448
+ const o = JSON.parse(raw);
449
+ // Base shape only, so a pre-manifest cache deserializes to exactly the two
450
+ // original fields. Optional fields are attached ONLY when present and valid.
451
+ const state = {
452
+ lastCheckedAt: typeof o.lastCheckedAt === "number" ? o.lastCheckedAt : 0,
453
+ latestVersion: typeof o.latestVersion === "string" ? o.latestVersion : null,
454
+ };
455
+ if (typeof o.minVersion === "string")
456
+ state.minVersion = o.minVersion;
457
+ const staged = parseStaged(o.staged);
458
+ if (staged)
459
+ state.staged = staged;
460
+ return state;
461
+ }
462
+ catch {
463
+ // A corrupt cache is treated as "never checked", never fatal.
464
+ return { ...EMPTY_STATE };
465
+ }
466
+ }
467
+ function serializeState(state) {
468
+ return JSON.stringify(state);
469
+ }
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.stateFilePath = exports.writeUpdateState = exports.readUpdateState = void 0;
37
+ exports.maybeSpawnBackgroundCheck = maybeSpawnBackgroundCheck;
38
+ exports.maybeShowUpdateNag = maybeShowUpdateNag;
39
+ exports.fetchLatestVersion = fetchLatestVersion;
40
+ exports.runInternalUpdateCheck = runInternalUpdateCheck;
41
+ // IO layer for the update notifier. The decision logic is the pure core in
42
+ // update-check.ts; the heavier upgrade IO (cache read/write, manifest fetch +
43
+ // verify, download, swap, stage) lives in upgrade-apply.ts. This file wires the
44
+ // two together for the two background concerns: spawn the detached check child,
45
+ // and print the nag. Every function here is best-effort and swallows its own
46
+ // errors: the update notifier must never change an exit code or break a command.
47
+ const child_process_1 = require("child_process");
48
+ const update_check_1 = require("./update-check");
49
+ const observability_1 = require("./observability");
50
+ const config_1 = require("./config");
51
+ const upgrade_apply_1 = require("./upgrade-apply");
52
+ const fs = __importStar(require("fs"));
53
+ // Re-export the cache IO from its new home so existing importers keep working.
54
+ var upgrade_apply_2 = require("./upgrade-apply");
55
+ Object.defineProperty(exports, "readUpdateState", { enumerable: true, get: function () { return upgrade_apply_2.readUpdateState; } });
56
+ Object.defineProperty(exports, "writeUpdateState", { enumerable: true, get: function () { return upgrade_apply_2.writeUpdateState; } });
57
+ Object.defineProperty(exports, "stateFilePath", { enumerable: true, get: function () { return upgrade_apply_2.stateFilePath; } });
58
+ // Spawn a detached `mla _internal update-check` that fetches the signed manifest
59
+ // (and stages a binary when auto-apply is on), writes the cache, then returns
60
+ // immediately. The parent never waits on it. Skipped entirely for `_internal`
61
+ // commands (so the check child can't recurse) and whenever the throttle/gating
62
+ // says no (shouldRunCheck).
63
+ function maybeSpawnBackgroundCheck(opts) {
64
+ try {
65
+ const { command, env, now } = opts;
66
+ if (command === "_internal")
67
+ return; // never recurse from the check child
68
+ const entry = process.argv[1];
69
+ if (!entry)
70
+ return; // no re-invokable entry; skip rather than guess
71
+ if (!(0, update_check_1.shouldRunCheck)({ state: (0, upgrade_apply_1.readUpdateState)(), now, env }))
72
+ return;
73
+ const child = (0, child_process_1.spawn)(process.execPath, [entry, "_internal", "update-check"], {
74
+ detached: true,
75
+ stdio: "ignore",
76
+ });
77
+ child.on("error", () => { }); // swallow ENOENT etc; never throw from here
78
+ child.unref();
79
+ }
80
+ catch {
81
+ // never let the notifier break the real command
82
+ }
83
+ }
84
+ // Print the upgrade nag to stderr if a newer version is cached and the session
85
+ // is interactive (TTY) and not opted out / not CI. stderr keeps it off stdout
86
+ // so `mla ... | jq` stays clean. When the current version is below the manifest
87
+ // floor (minVersion), the stronger "required" wording is shown.
88
+ function maybeShowUpdateNag(opts) {
89
+ try {
90
+ const { currentVersion, env } = opts;
91
+ const state = (0, upgrade_apply_1.readUpdateState)();
92
+ const show = (0, update_check_1.shouldShowNag)({
93
+ state,
94
+ currentVersion,
95
+ env,
96
+ stdoutTTY: Boolean(process.stdout.isTTY),
97
+ stderrTTY: Boolean(process.stderr.isTTY),
98
+ });
99
+ if (!show || !state.latestVersion)
100
+ return;
101
+ const method = (0, update_check_1.detectInstallMethod)({
102
+ execPath: process.execPath,
103
+ scriptPath: process.argv[1],
104
+ env,
105
+ });
106
+ const required = (0, update_check_1.isBelowMinVersion)(currentVersion, state.minVersion ?? null);
107
+ process.stderr.write((0, update_check_1.formatUpdateNag)({ current: currentVersion, latest: state.latestVersion, method, required }));
108
+ }
109
+ catch {
110
+ // never let the notifier break the real command
111
+ }
112
+ }
113
+ // Query the plaintext release feed for the latest version (bare, no leading v).
114
+ // This is the FALLBACK path used only when the signed manifest is unreachable
115
+ // (e.g. during the manifest rollout window): it keeps the nag working but cannot
116
+ // drive a self-upgrade (no per-artifact sha to verify). Returns null on any
117
+ // failure so the caller just leaves the cache untouched.
118
+ async function fetchLatestVersion(opts) {
119
+ const url = opts?.url ?? process.env.MLA_UPDATE_URL ?? update_check_1.DEFAULT_UPDATE_URL;
120
+ const timeoutMs = opts?.timeoutMs ?? 4000;
121
+ const controller = new AbortController();
122
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
123
+ try {
124
+ const res = await fetch(url, {
125
+ headers: { "User-Agent": "mla-update-check" },
126
+ signal: controller.signal,
127
+ });
128
+ if (!res.ok)
129
+ return null;
130
+ // The VERSION file holds a single bare version line (e.g. "0.4.2"). Trim
131
+ // whitespace, take the first line, and drop an optional leading "v". Guard
132
+ // against an unexpected large/HTML body so a misrouted URL can't poison the
133
+ // cache with junk.
134
+ const text = (await res.text()).trim();
135
+ const first = text.split(/\r?\n/, 1)[0]?.trim() ?? "";
136
+ const ver = first.replace(/^v/, "");
137
+ if (!/^\d+\.\d+\.\d+([.-][0-9A-Za-z.-]+)?$/.test(ver))
138
+ return null;
139
+ return ver;
140
+ }
141
+ catch {
142
+ return null;
143
+ }
144
+ finally {
145
+ clearTimeout(timer);
146
+ }
147
+ }
148
+ // `mla _internal update-check`: the detached child. Fetch + verify the signed
149
+ // manifest, stamp latestVersion + minVersion, and (when auto-apply is enabled on
150
+ // a curl install) download + verify + stage a newer binary so apply-on-launch is
151
+ // a cheap local swap. Always exits 0 (best-effort); stamps lastCheckedAt even on
152
+ // a failed fetch so a flaky network doesn't hammer the feed every command.
153
+ async function runInternalUpdateCheck() {
154
+ const now = Date.now();
155
+ const env = process.env;
156
+ const buildInfo = (0, observability_1.loadBuildInfo)();
157
+ const prev = (0, upgrade_apply_1.readUpdateState)();
158
+ const verified = await (0, upgrade_apply_1.fetchManifest)({ env, buildInfo });
159
+ let latestVersion = prev.latestVersion;
160
+ let minVersion = prev.minVersion ?? null;
161
+ if (verified) {
162
+ latestVersion = verified.manifest.version;
163
+ minVersion = verified.manifest.minVersion;
164
+ }
165
+ else {
166
+ // Manifest unreachable: fall back to the plaintext VERSION feed so the nag
167
+ // keeps working. Cannot stage from this path (no verified artifact sha).
168
+ const plain = await fetchLatestVersion();
169
+ if (plain)
170
+ latestVersion = plain;
171
+ }
172
+ let staged = prev.staged ?? null;
173
+ if (verified &&
174
+ (0, update_check_1.resolveAutoApply)({ env, configAutoApply: (0, config_1.readUpdateConfig)().autoApply })) {
175
+ const method = (0, update_check_1.detectInstallMethod)({
176
+ execPath: process.execPath,
177
+ scriptPath: process.argv[1],
178
+ env,
179
+ });
180
+ if (method === "curl") {
181
+ const triple = (0, update_check_1.currentTriple)(process.platform, process.arch);
182
+ const plan = (0, update_check_1.planUpgrade)({
183
+ current: buildInfo.version,
184
+ manifest: verified.manifest,
185
+ triple,
186
+ force: false,
187
+ });
188
+ if (plan.action === "upgrade" && triple) {
189
+ const already = staged &&
190
+ staged.version === plan.to &&
191
+ staged.triple === triple &&
192
+ fs.existsSync(staged.path);
193
+ if (!already) {
194
+ const artifact = (0, update_check_1.selectArtifact)(verified.manifest, triple);
195
+ if (artifact) {
196
+ const extracted = await (0, upgrade_apply_1.downloadVerifyExtract)({ artifact });
197
+ if (extracted) {
198
+ try {
199
+ staged = (0, upgrade_apply_1.stageBinary)({
200
+ binaryPath: extracted.binaryPath,
201
+ version: plan.to,
202
+ triple,
203
+ now,
204
+ });
205
+ }
206
+ finally {
207
+ (0, upgrade_apply_1.cleanupDir)(extracted.dir);
208
+ }
209
+ }
210
+ }
211
+ }
212
+ }
213
+ }
214
+ }
215
+ (0, upgrade_apply_1.writeUpdateState)({ lastCheckedAt: now, latestVersion, minVersion, staged });
216
+ return 0;
217
+ }