@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.
- package/LICENSE +201 -0
- package/README.md +81 -0
- package/dist/build-info.json +9 -0
- package/dist/bundles/ask-core.js +396 -0
- package/dist/bundles/mcp.js +16592 -0
- package/dist/bundles/trace-core.js +263 -0
- package/dist/cli.js +828 -0
- package/dist/commands/activate.js +781 -0
- package/dist/commands/adoption.js +130 -0
- package/dist/commands/ask.js +290 -0
- package/dist/commands/context.js +114 -0
- package/dist/commands/debug.js +313 -0
- package/dist/commands/doctor.js +1021 -0
- package/dist/commands/enrich.js +427 -0
- package/dist/commands/evidence.js +229 -0
- package/dist/commands/flush.js +184 -0
- package/dist/commands/graph.js +104 -0
- package/dist/commands/init.js +272 -0
- package/dist/commands/internal-active-review.js +322 -0
- package/dist/commands/internal-auto-index.js +188 -0
- package/dist/commands/internal-capture-decisions.js +320 -0
- package/dist/commands/internal-evidence-correlate.js +239 -0
- package/dist/commands/internal-evidence-hooks.js +240 -0
- package/dist/commands/internal-evidence-inject.js +231 -0
- package/dist/commands/internal-finalize.js +221 -0
- package/dist/commands/internal-pretool-observe.js +225 -0
- package/dist/commands/internal-refresh.js +136 -0
- package/dist/commands/internal-session-nudge.js +120 -0
- package/dist/commands/internal-steer-sync.js +117 -0
- package/dist/commands/internal-turn-recap.js +140 -0
- package/dist/commands/kb.js +375 -0
- package/dist/commands/kb_add.js +681 -0
- package/dist/commands/kb_forget.js +283 -0
- package/dist/commands/kb_move.js +45 -0
- package/dist/commands/kb_pending.js +410 -0
- package/dist/commands/kb_personal.js +149 -0
- package/dist/commands/kb_promote.js +188 -0
- package/dist/commands/kb_purge.js +168 -0
- package/dist/commands/kb_reingest.js +335 -0
- package/dist/commands/kb_retime.js +170 -0
- package/dist/commands/kb_review.js +391 -0
- package/dist/commands/kb_revision.js +179 -0
- package/dist/commands/kb_show.js +385 -0
- package/dist/commands/label.js +226 -0
- package/dist/commands/login.js +295 -0
- package/dist/commands/logout.js +108 -0
- package/dist/commands/mcp-supervisor.js +93 -0
- package/dist/commands/mcp.js +227 -0
- package/dist/commands/queue-prune.js +98 -0
- package/dist/commands/review.js +358 -0
- package/dist/commands/rewire.js +124 -0
- package/dist/commands/rules.js +728 -0
- package/dist/commands/scan-context.js +67 -0
- package/dist/commands/session.js +347 -0
- package/dist/commands/stats.js +479 -0
- package/dist/commands/status.js +61 -0
- package/dist/commands/summary.js +250 -0
- package/dist/commands/turn.js +114 -0
- package/dist/commands/uninstall.js +222 -0
- package/dist/commands/whoami.js +102 -0
- package/dist/commands/workspace.js +130 -0
- package/dist/hooks-template/ce0-post-tool-use.sh +34 -0
- package/dist/hooks-template/ce0-session-start.sh +49 -0
- package/dist/hooks-template/ce0-stop.sh +29 -0
- package/dist/hooks-template/ce0-user-prompt-submit.sh +38 -0
- package/dist/hooks-template/common.sh +934 -0
- package/dist/hooks-template/event-batch-filter.jq +67 -0
- package/dist/hooks-template/flush.sh +503 -0
- package/dist/hooks-template/post-tool-use.sh +423 -0
- package/dist/hooks-template/pre-tool-use.sh +69 -0
- package/dist/hooks-template/session-start.sh +140 -0
- package/dist/hooks-template/stop.sh +308 -0
- package/dist/hooks-template/user-prompt-submit.sh +1162 -0
- package/dist/lib/activation.js +79 -0
- package/dist/lib/active-conflict-cache.js +141 -0
- package/dist/lib/active-memory.js +59 -0
- package/dist/lib/active-review-runner.js +26 -0
- package/dist/lib/agent-decision/index.js +25 -0
- package/dist/lib/agent-decision/keys.js +49 -0
- package/dist/lib/agent-decision/normalize-claude.js +183 -0
- package/dist/lib/agent-decision/types.js +21 -0
- package/dist/lib/agent-decision/validate.js +216 -0
- package/dist/lib/analytics/capture.js +96 -0
- package/dist/lib/analytics/command-event.js +267 -0
- package/dist/lib/analytics/consent.js +58 -0
- package/dist/lib/analytics/coverage-gap.js +96 -0
- package/dist/lib/analytics/envelope.js +236 -0
- package/dist/lib/analytics/event-id.js +86 -0
- package/dist/lib/analytics/evidence.js +150 -0
- package/dist/lib/analytics/followthrough.js +194 -0
- package/dist/lib/analytics/forwarder.js +109 -0
- package/dist/lib/analytics/logs.js +78 -0
- package/dist/lib/analytics/metrics.js +78 -0
- package/dist/lib/analytics/recorder.js +92 -0
- package/dist/lib/analytics/review-analytics.js +75 -0
- package/dist/lib/analytics/sequence.js +77 -0
- package/dist/lib/analytics/store.js +131 -0
- package/dist/lib/analytics/turn-recap.js +279 -0
- package/dist/lib/artifact_id.js +108 -0
- package/dist/lib/auth-breaker.js +161 -0
- package/dist/lib/auto-index.js +112 -0
- package/dist/lib/classifier.js +88 -0
- package/dist/lib/config.js +298 -0
- package/dist/lib/conflict-advisory.js +64 -0
- package/dist/lib/debug-bundle.js +520 -0
- package/dist/lib/enrichment/ingest.js +301 -0
- package/dist/lib/enrichment/plan.js +253 -0
- package/dist/lib/enrichment/protocol.js +359 -0
- package/dist/lib/enrichment/scout-brief.js +176 -0
- package/dist/lib/failure-telemetry.js +444 -0
- package/dist/lib/git.js +200 -0
- package/dist/lib/governance-cache.js +77 -0
- package/dist/lib/governed-path-cache.js +76 -0
- package/dist/lib/http.js +677 -0
- package/dist/lib/identity-envelope.js +23 -0
- package/dist/lib/kb-candidate.js +65 -0
- package/dist/lib/kb_acl.js +98 -0
- package/dist/lib/login.js +353 -0
- package/dist/lib/mcp-fetchers.js +130 -0
- package/dist/lib/mcp-restart.js +47 -0
- package/dist/lib/observability.js +805 -0
- package/dist/lib/open-url.js +33 -0
- package/dist/lib/orphan-guard.js +70 -0
- package/dist/lib/packaged.js +21 -0
- package/dist/lib/reconcile-sessions.js +171 -0
- package/dist/lib/redactor.js +89 -0
- package/dist/lib/relationship-candidate-query.js +27 -0
- package/dist/lib/render.js +611 -0
- package/dist/lib/rules/applicability.js +64 -0
- package/dist/lib/rules/attest-code-rule-version.js +47 -0
- package/dist/lib/rules/attest-notes-location.js +217 -0
- package/dist/lib/rules/attest-rule-version.js +69 -0
- package/dist/lib/rules/canonical-json.js +97 -0
- package/dist/lib/rules/ce0-emit.js +64 -0
- package/dist/lib/rules/ce0-evidence.js +281 -0
- package/dist/lib/rules/ce0-recall-sample.js +82 -0
- package/dist/lib/rules/ce0-rule.js +55 -0
- package/dist/lib/rules/ce0-sampling-bucket.js +15 -0
- package/dist/lib/rules/ce0-store.js +683 -0
- package/dist/lib/rules/ce0-telemetry-project.js +93 -0
- package/dist/lib/rules/ce0-telemetry.js +158 -0
- package/dist/lib/rules/code-rule-registry.js +17 -0
- package/dist/lib/rules/command-match.js +185 -0
- package/dist/lib/rules/consult-evidence-binding.js +27 -0
- package/dist/lib/rules/consultation-capture-adapter.js +193 -0
- package/dist/lib/rules/content-match.js +56 -0
- package/dist/lib/rules/deny-admission.js +99 -0
- package/dist/lib/rules/durable-observation.js +190 -0
- package/dist/lib/rules/enforce-notes-version.js +421 -0
- package/dist/lib/rules/evaluation-input-hash.js +126 -0
- package/dist/lib/rules/evaluator.js +108 -0
- package/dist/lib/rules/inert-rule-families.js +51 -0
- package/dist/lib/rules/input-authority-resolver.js +241 -0
- package/dist/lib/rules/interception-schema.js +170 -0
- package/dist/lib/rules/interception-store.js +267 -0
- package/dist/lib/rules/live-input-authority.js +66 -0
- package/dist/lib/rules/local-matcher.js +108 -0
- package/dist/lib/rules/local-observe.js +79 -0
- package/dist/lib/rules/local-rule-version-repo.js +214 -0
- package/dist/lib/rules/memory-requirement.js +109 -0
- package/dist/lib/rules/notes-observe.js +39 -0
- package/dist/lib/rules/notes-path.js +261 -0
- package/dist/lib/rules/notes-rule.js +75 -0
- package/dist/lib/rules/observe-adapter.js +114 -0
- package/dist/lib/rules/observed-rule-hash.js +119 -0
- package/dist/lib/rules/prompt-submit-adapter.js +132 -0
- package/dist/lib/rules/requirement-subject.js +240 -0
- package/dist/lib/rules/rule-activity.js +67 -0
- package/dist/lib/rules/rule-version-hash.js +151 -0
- package/dist/lib/rules/runtime-scope.js +55 -0
- package/dist/lib/rules/stop-adapter.js +116 -0
- package/dist/lib/rules/stop-response-snapshot.js +174 -0
- package/dist/lib/rules/types.js +10 -0
- package/dist/lib/rules/ulid.js +46 -0
- package/dist/lib/rules/version-evaluation.js +156 -0
- package/dist/lib/scanner/agent-memory.js +99 -0
- package/dist/lib/scanner/bootstrap-summary.js +87 -0
- package/dist/lib/scanner/cache.js +59 -0
- package/dist/lib/scanner/frontmatter.js +42 -0
- package/dist/lib/scanner/parse-directives.js +69 -0
- package/dist/lib/scanner/parse-structured.js +72 -0
- package/dist/lib/scanner/render.js +73 -0
- package/dist/lib/scanner/scan.js +132 -0
- package/dist/lib/scanner/score.js +38 -0
- package/dist/lib/scanner/scout-mission.js +126 -0
- package/dist/lib/scanner/types.js +7 -0
- package/dist/lib/session-scope.js +195 -0
- package/dist/lib/spool.js +355 -0
- package/dist/lib/staleness.js +100 -0
- package/dist/lib/steer-cache.js +87 -0
- package/dist/lib/tagged-reference.js +20 -0
- package/dist/lib/temporal.js +109 -0
- package/dist/lib/turn-recap-emit.js +67 -0
- package/dist/lib/unwire.js +253 -0
- package/dist/lib/update-check.js +469 -0
- package/dist/lib/update-notifier.js +217 -0
- package/dist/lib/upgrade-apply.js +643 -0
- package/dist/lib/wire.js +1087 -0
- package/dist/lib/workspace.js +96 -0
- package/dist/lib/zip.js +154 -0
- package/dist/pretool-entry.js +37 -0
- package/package.json +75 -0
|
@@ -0,0 +1,250 @@
|
|
|
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.parseSummaryArgs = parseSummaryArgs;
|
|
37
|
+
exports.runSummary = runSummary;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
// `mla summary` -- aggregate view over the hook's enrichment trace JSONL
|
|
42
|
+
// (T18b, §6.9). Counts/latency/cost/labels across the most recent N prompts.
|
|
43
|
+
//
|
|
44
|
+
// mla summary [--last N] [--json] [--all]
|
|
45
|
+
//
|
|
46
|
+
// By default it auto-scopes to the CURRENT session via CLAUDE_CODE_SESSION_ID
|
|
47
|
+
// (the env var the parent Claude Code shell exports, same as `mla review`), so
|
|
48
|
+
// there is deliberately NO `--session` flag: the shell already told us which
|
|
49
|
+
// session this is. `--all` opts back out to the cross-session aggregate; when
|
|
50
|
+
// run outside a session (var unset) it defaults to global.
|
|
51
|
+
//
|
|
52
|
+
// Was `mla traces summarize`; the `traces show`/`traces label` subcommands were
|
|
53
|
+
// removed 2026-05-31. Per-trace inspection is unnecessary from the CLI: every
|
|
54
|
+
// enrichment writes its trace_id to ~/.meetless/logs/ask-traces.jsonl AND the
|
|
55
|
+
// same id is printed live on each prompt's `<meetless-context trace="...">`
|
|
56
|
+
// block, so the operator pins a run via the prompt or `jq`/`tail` over the
|
|
57
|
+
// JSONL and opens it in the Langfuse dashboard. This command stays as the only
|
|
58
|
+
// thing the JSONL can't trivially give you by hand: the rolled-up aggregates.
|
|
59
|
+
//
|
|
60
|
+
// The trace line schema is produced by user-prompt-submit.sh write_trace(); we
|
|
61
|
+
// read only the fields we tally and treat everything as optional/best-effort so
|
|
62
|
+
// a partially-written or future-extended line never crashes the summary.
|
|
63
|
+
// Paths are resolved lazily from MEETLESS_HOME (same fallback as lib/config's
|
|
64
|
+
// HOME) so the short-lived CLI process picks up the operator's env, and tests
|
|
65
|
+
// can point at a temp dir without module-cache tricks.
|
|
66
|
+
function logDir() {
|
|
67
|
+
return path.join(process.env.MEETLESS_HOME || path.join(os.homedir(), ".meetless"), "logs");
|
|
68
|
+
}
|
|
69
|
+
function tracesFile() {
|
|
70
|
+
return path.join(logDir(), "ask-traces.jsonl");
|
|
71
|
+
}
|
|
72
|
+
function readLines() {
|
|
73
|
+
if (!fs.existsSync(tracesFile()))
|
|
74
|
+
return [];
|
|
75
|
+
return fs
|
|
76
|
+
.readFileSync(tracesFile(), "utf8")
|
|
77
|
+
.split("\n")
|
|
78
|
+
.filter((l) => l.trim().length > 0);
|
|
79
|
+
}
|
|
80
|
+
function parse(line) {
|
|
81
|
+
try {
|
|
82
|
+
const o = JSON.parse(line);
|
|
83
|
+
return o && typeof o === "object" ? o : null;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function num(v) {
|
|
90
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
91
|
+
}
|
|
92
|
+
function percentile(sortedAsc, p) {
|
|
93
|
+
if (sortedAsc.length === 0)
|
|
94
|
+
return 0;
|
|
95
|
+
const idx = Math.ceil(p * sortedAsc.length) - 1;
|
|
96
|
+
return sortedAsc[Math.min(sortedAsc.length - 1, Math.max(0, idx))];
|
|
97
|
+
}
|
|
98
|
+
function isLabeled(l) {
|
|
99
|
+
if (!l)
|
|
100
|
+
return false;
|
|
101
|
+
return (l.useful === true ||
|
|
102
|
+
l.noisy === true ||
|
|
103
|
+
l.harmful === true ||
|
|
104
|
+
l.prevented_mistake === true ||
|
|
105
|
+
(typeof l.notes === "string" && l.notes.trim().length > 0));
|
|
106
|
+
}
|
|
107
|
+
function parseSummaryArgs(argv) {
|
|
108
|
+
const out = { last: 20, json: false, all: false };
|
|
109
|
+
for (let i = 0; i < argv.length; i++) {
|
|
110
|
+
const a = argv[i];
|
|
111
|
+
if (a === "--json")
|
|
112
|
+
out.json = true;
|
|
113
|
+
else if (a === "--all")
|
|
114
|
+
out.all = true;
|
|
115
|
+
else if (a === "--last") {
|
|
116
|
+
const v = argv[++i];
|
|
117
|
+
const parsed = Number(v);
|
|
118
|
+
if (!v || !Number.isInteger(parsed) || parsed <= 0) {
|
|
119
|
+
throw new Error(`--last requires a positive integer (got: ${v ?? "(none)"})`);
|
|
120
|
+
}
|
|
121
|
+
out.last = parsed;
|
|
122
|
+
}
|
|
123
|
+
else
|
|
124
|
+
throw new Error(`Unknown flag for \`mla summary\`: ${a}`);
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
function buildSummary(traces) {
|
|
129
|
+
let injected = 0;
|
|
130
|
+
let discarded = 0;
|
|
131
|
+
let failOpen = 0;
|
|
132
|
+
let timeouts = 0;
|
|
133
|
+
let totalCost = 0;
|
|
134
|
+
const latencies = [];
|
|
135
|
+
const injectedChars = [];
|
|
136
|
+
const strategies = {};
|
|
137
|
+
let useful = 0;
|
|
138
|
+
let noisy = 0;
|
|
139
|
+
let harmful = 0;
|
|
140
|
+
let labeled = 0;
|
|
141
|
+
for (const t of traces) {
|
|
142
|
+
const decision = t.arbitration?.decision ?? "";
|
|
143
|
+
if (decision === "injected")
|
|
144
|
+
injected++;
|
|
145
|
+
if (t.arbitration?.discarded_after_compute === true)
|
|
146
|
+
discarded++;
|
|
147
|
+
if (decision === "fail_open")
|
|
148
|
+
failOpen++;
|
|
149
|
+
const failReason = t.hook?.fail_open_reason ?? "";
|
|
150
|
+
if (failReason === "timeout" || t.enrichment?.status === "timeout")
|
|
151
|
+
timeouts++;
|
|
152
|
+
const lat = num(t.enrichment?.latency_ms);
|
|
153
|
+
if (lat !== null)
|
|
154
|
+
latencies.push(lat);
|
|
155
|
+
const cost = num(t.enrichment?.cost_usd);
|
|
156
|
+
if (cost !== null)
|
|
157
|
+
totalCost += cost;
|
|
158
|
+
if (decision === "injected") {
|
|
159
|
+
const chars = num(t.hook?.injected_chars);
|
|
160
|
+
if (chars !== null)
|
|
161
|
+
injectedChars.push(chars);
|
|
162
|
+
}
|
|
163
|
+
const strat = t.experiment?.variant || t.enrichment?.strategy || "unknown";
|
|
164
|
+
strategies[strat] = (strategies[strat] ?? 0) + 1;
|
|
165
|
+
const ol = t.operator_label;
|
|
166
|
+
if (ol?.useful === true)
|
|
167
|
+
useful++;
|
|
168
|
+
if (ol?.noisy === true)
|
|
169
|
+
noisy++;
|
|
170
|
+
if (ol?.harmful === true)
|
|
171
|
+
harmful++;
|
|
172
|
+
if (isLabeled(ol))
|
|
173
|
+
labeled++;
|
|
174
|
+
}
|
|
175
|
+
latencies.sort((a, b) => a - b);
|
|
176
|
+
const avgLat = latencies.length ? latencies.reduce((s, v) => s + v, 0) / latencies.length : 0;
|
|
177
|
+
const p95Lat = percentile(latencies, 0.95);
|
|
178
|
+
const avgChars = injectedChars.length
|
|
179
|
+
? Math.round(injectedChars.reduce((s, v) => s + v, 0) / injectedChars.length)
|
|
180
|
+
: 0;
|
|
181
|
+
const timeoutRate = traces.length ? timeouts / traces.length : 0;
|
|
182
|
+
return {
|
|
183
|
+
prompt_count: traces.length,
|
|
184
|
+
injected,
|
|
185
|
+
discarded_after_compute: discarded,
|
|
186
|
+
fail_open: failOpen,
|
|
187
|
+
avg_enrichment_latency_ms: Math.round(avgLat),
|
|
188
|
+
p95_enrichment_latency_ms: p95Lat,
|
|
189
|
+
timeout_rate: timeoutRate,
|
|
190
|
+
total_cost_usd: totalCost,
|
|
191
|
+
avg_injected_chars: avgChars,
|
|
192
|
+
strategies,
|
|
193
|
+
operator_labels: { useful, noisy, harmful, unlabeled: traces.length - labeled },
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
function renderSummary(s) {
|
|
197
|
+
const secs = (ms) => (ms / 1000).toFixed(1) + "s";
|
|
198
|
+
const pct = (r) => (r * 100).toFixed(0) + "%";
|
|
199
|
+
const strat = Object.entries(s.strategies)
|
|
200
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
201
|
+
.join(" ");
|
|
202
|
+
return [
|
|
203
|
+
`Prompt count: ${s.prompt_count}`,
|
|
204
|
+
`Injected: ${s.injected} Discarded after compute: ${s.discarded_after_compute} Fail-open: ${s.fail_open}`,
|
|
205
|
+
`Avg enrichment latency: ${secs(s.avg_enrichment_latency_ms)} P95: ${secs(s.p95_enrichment_latency_ms)} Timeout rate: ${pct(s.timeout_rate)}`,
|
|
206
|
+
`Total cost: $${s.total_cost_usd.toFixed(2)} Avg injected chars: ${s.avg_injected_chars} Strategies: ${strat || "(none)"}`,
|
|
207
|
+
`Operator labels: ${s.operator_labels.useful} useful / ${s.operator_labels.noisy} noisy / ${s.operator_labels.harmful} harmful / ${s.operator_labels.unlabeled} unlabeled`,
|
|
208
|
+
].join("\n");
|
|
209
|
+
}
|
|
210
|
+
function runSummary(argv) {
|
|
211
|
+
let args;
|
|
212
|
+
try {
|
|
213
|
+
args = parseSummaryArgs(argv);
|
|
214
|
+
}
|
|
215
|
+
catch (e) {
|
|
216
|
+
console.error(e.message);
|
|
217
|
+
return 2;
|
|
218
|
+
}
|
|
219
|
+
const lines = readLines();
|
|
220
|
+
if (lines.length === 0) {
|
|
221
|
+
console.error(`No traces found at ${tracesFile()}.`);
|
|
222
|
+
return 1;
|
|
223
|
+
}
|
|
224
|
+
let traces = lines.map(parse).filter((t) => t !== null);
|
|
225
|
+
// Auto-scope to the current live session. The parent shell (Claude Code)
|
|
226
|
+
// exports CLAUDE_CODE_SESSION_ID, the same var `mla review` binds to, so the
|
|
227
|
+
// operator never passes a session id by hand. `--all` opts back out to the
|
|
228
|
+
// cross-session aggregate; outside a session (var unset) we default to global
|
|
229
|
+
// since there is no current session to scope to. Scope BEFORE --last so the
|
|
230
|
+
// window is "last N of this session", not "last N overall then filtered".
|
|
231
|
+
const session = (process.env.CLAUDE_CODE_SESSION_ID || "").trim();
|
|
232
|
+
const scoped = !args.all && session.length > 0;
|
|
233
|
+
if (scoped)
|
|
234
|
+
traces = traces.filter((t) => t.session_id === session);
|
|
235
|
+
if (traces.length === 0) {
|
|
236
|
+
console.error(scoped
|
|
237
|
+
? `No traces for the current session (${session}) at ${tracesFile()}. Use --all for every session.`
|
|
238
|
+
: `No traces found at ${tracesFile()}.`);
|
|
239
|
+
return 1;
|
|
240
|
+
}
|
|
241
|
+
traces = traces.slice(-args.last);
|
|
242
|
+
const summary = buildSummary(traces);
|
|
243
|
+
if (args.json) {
|
|
244
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
console.log(renderSummary(summary));
|
|
248
|
+
}
|
|
249
|
+
return 0;
|
|
250
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// `mla turn [N]` -- the operator-facing per-turn assist recap (Layer B of
|
|
3
|
+
// notes/20260609-mla-per-turn-assist-recap-plan.md). The cheap, always-available,
|
|
4
|
+
// zero-model-cost answer to "how did mla do?" for a single turn:
|
|
5
|
+
//
|
|
6
|
+
// mla turn recap the latest completed turn of the current session
|
|
7
|
+
// mla turn 5 recap turn 5
|
|
8
|
+
// mla turn --session X target another session
|
|
9
|
+
// mla turn 5 --json machine output (the full TurnRecap)
|
|
10
|
+
//
|
|
11
|
+
// It reads the same three local spool files as `mla stats` and computes the recap
|
|
12
|
+
// via Layer A computeTurnRecap. `mla stats --turn [N]` is wired to this same
|
|
13
|
+
// handler (commands/stats.ts) so the feature is discoverable from the stats surface
|
|
14
|
+
// An framed it against. Unlike `mla _internal turn-recap` (fail-soft for the hook),
|
|
15
|
+
// this is a human read: a strict argv error exits 2, a missing session exits 1.
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.parseTurnArgs = parseTurnArgs;
|
|
18
|
+
exports.latestTurnIndex = latestTurnIndex;
|
|
19
|
+
exports.runTurn = runTurn;
|
|
20
|
+
const followthrough_1 = require("../lib/analytics/followthrough");
|
|
21
|
+
const logs_1 = require("../lib/analytics/logs");
|
|
22
|
+
const turn_recap_1 = require("../lib/analytics/turn-recap");
|
|
23
|
+
function parseTurnArgs(argv) {
|
|
24
|
+
const out = { session: null, turn: null, json: false };
|
|
25
|
+
for (let i = 0; i < argv.length; i++) {
|
|
26
|
+
const a = argv[i];
|
|
27
|
+
switch (a) {
|
|
28
|
+
case "--session":
|
|
29
|
+
out.session = argv[++i] ?? "";
|
|
30
|
+
if (!out.session)
|
|
31
|
+
throw new Error("--session requires a value");
|
|
32
|
+
break;
|
|
33
|
+
case "--json":
|
|
34
|
+
out.json = true;
|
|
35
|
+
break;
|
|
36
|
+
default: {
|
|
37
|
+
// A number-shaped token (including a leading '-' or a decimal) is a
|
|
38
|
+
// turn-index candidate, so `mla turn -1` / `mla turn 1.5` report a clear
|
|
39
|
+
// turn error rather than the misleading "unknown flag". A non-numeric
|
|
40
|
+
// leading-dash token is a genuine unknown flag.
|
|
41
|
+
const numeric = /^-?\d*\.?\d+$/.test(a);
|
|
42
|
+
if (!numeric && a.startsWith("-"))
|
|
43
|
+
throw new Error(`Unknown flag for \`mla turn\`: ${a}`);
|
|
44
|
+
if (out.turn !== null)
|
|
45
|
+
throw new Error("`mla turn` takes one turn index at most");
|
|
46
|
+
if (!/^[0-9]+$/.test(a) || Number(a) < 1) {
|
|
47
|
+
throw new Error(`turn index must be a positive integer: ${a}`);
|
|
48
|
+
}
|
|
49
|
+
out.turn = Number(a);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
// The latest turn that left ANY trace on disk for this session: the max turn_index
|
|
56
|
+
// across the three spool files (the inject side, the pull side, the cite side).
|
|
57
|
+
// Reading all three (not just ask-traces) means a turn that only produced a pull or
|
|
58
|
+
// a citation still counts as the latest, and a NOT_RUN turn whose minimal trace
|
|
59
|
+
// line landed is found too. Returns null when the session has no turns yet.
|
|
60
|
+
function latestTurnIndex(sessionId, readLog) {
|
|
61
|
+
let max = null;
|
|
62
|
+
const bump = (t) => {
|
|
63
|
+
if (max === null || t > max)
|
|
64
|
+
max = t;
|
|
65
|
+
};
|
|
66
|
+
for (const raw of readLog("ask-traces.jsonl")) {
|
|
67
|
+
const t = (0, turn_recap_1.parseAskTrace)(raw);
|
|
68
|
+
if (t && t.session_id === sessionId)
|
|
69
|
+
bump(t.turn_index);
|
|
70
|
+
}
|
|
71
|
+
for (const c of (0, followthrough_1.parseMcpCalls)(readLog("mcp-calls.jsonl"))) {
|
|
72
|
+
if (c.session_id === sessionId)
|
|
73
|
+
bump(c.turn_index);
|
|
74
|
+
}
|
|
75
|
+
for (const r of (0, followthrough_1.parseReportCitations)(readLog("report-citations.jsonl"))) {
|
|
76
|
+
if (r.session_id === sessionId)
|
|
77
|
+
bump(r.turn_index);
|
|
78
|
+
}
|
|
79
|
+
return max;
|
|
80
|
+
}
|
|
81
|
+
async function runTurn(argv, deps = {}) {
|
|
82
|
+
const log = deps.log ?? ((l) => console.log(l));
|
|
83
|
+
const err = deps.err ?? ((l) => console.error(l));
|
|
84
|
+
let args;
|
|
85
|
+
try {
|
|
86
|
+
args = parseTurnArgs(argv);
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
err(e.message);
|
|
90
|
+
return 2;
|
|
91
|
+
}
|
|
92
|
+
const env = deps.env ?? process.env;
|
|
93
|
+
const session = (args.session ?? env.CLAUDE_CODE_SESSION_ID ?? "").trim();
|
|
94
|
+
if (!session) {
|
|
95
|
+
err("No session id provided and no $CLAUDE_CODE_SESSION_ID env. " +
|
|
96
|
+
"Run `mla turn` inside a Claude Code session, or pass --session <sid>.");
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
const readLog = deps.readLog ?? logs_1.readLogJsonl;
|
|
100
|
+
const turn = args.turn ?? latestTurnIndex(session, readLog);
|
|
101
|
+
if (turn === null) {
|
|
102
|
+
log(`No turns recorded for session ${session} yet.`);
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
const compute = deps.compute ?? ((s, t) => (0, turn_recap_1.computeTurnRecap)(s, t, { readLog }));
|
|
106
|
+
const recap = compute(session, turn);
|
|
107
|
+
if (args.json) {
|
|
108
|
+
log(JSON.stringify(recap, null, 2));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
log((0, turn_recap_1.renderBlock)(recap));
|
|
112
|
+
}
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
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.runUninstall = runUninstall;
|
|
37
|
+
const os = __importStar(require("os"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const readline = __importStar(require("readline"));
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const config_1 = require("../lib/config");
|
|
42
|
+
const unwire_1 = require("../lib/unwire");
|
|
43
|
+
const flush_1 = require("./flush");
|
|
44
|
+
function defaultConfirm(prompt) {
|
|
45
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
rl.question(prompt, (answer) => {
|
|
48
|
+
rl.close();
|
|
49
|
+
const n = answer.trim().toLowerCase();
|
|
50
|
+
resolve(n === "y" || n === "yes");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function defaultChoose(prompt) {
|
|
55
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
rl.question(prompt, (answer) => {
|
|
58
|
+
rl.close();
|
|
59
|
+
const n = answer.trim().toLowerCase();
|
|
60
|
+
if (n === "f" || n === "flush")
|
|
61
|
+
resolve("flush");
|
|
62
|
+
else if (n === "d" || n === "delete")
|
|
63
|
+
resolve("delete");
|
|
64
|
+
else
|
|
65
|
+
resolve("cancel");
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async function runUninstall(argv, deps = {}) {
|
|
70
|
+
const log = deps.log ?? ((m) => console.log(m));
|
|
71
|
+
const errlog = deps.errlog ?? ((m) => console.error(m));
|
|
72
|
+
let dryRun = false;
|
|
73
|
+
let yes = false;
|
|
74
|
+
for (const a of argv) {
|
|
75
|
+
if (a === "--dry-run")
|
|
76
|
+
dryRun = true;
|
|
77
|
+
else if (a === "--yes" || a === "-y")
|
|
78
|
+
yes = true;
|
|
79
|
+
else {
|
|
80
|
+
errlog(`Unknown flag for \`mla uninstall\`: ${a}. Usage: mla uninstall [--dry-run] [--yes]`);
|
|
81
|
+
return 2;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const home = deps.home ?? config_1.HOME;
|
|
85
|
+
const settingsPath = deps.settingsPath ?? path.join(os.homedir(), ".claude", "settings.json");
|
|
86
|
+
const claudeJsonPath = deps.claudeJsonPath ?? path.join(os.homedir(), ".claude.json");
|
|
87
|
+
const skillDir = deps.skillDir ?? path.join(os.homedir(), ".claude", "skills", "mla");
|
|
88
|
+
const queueDir = deps.queueDir ?? config_1.QUEUE_DIR;
|
|
89
|
+
const isTTY = deps.isTTY ?? Boolean(process.stdin.isTTY);
|
|
90
|
+
const env = deps.env ?? process.env;
|
|
91
|
+
const countQueued = deps.countQueued ?? unwire_1.countQueuedSessions;
|
|
92
|
+
const countEvents = deps.countEvents ?? unwire_1.countQueuedEvents;
|
|
93
|
+
const homeExists = deps.homeExists ?? ((p) => fs.existsSync(p));
|
|
94
|
+
const skillExists = deps.skillExists ?? ((p) => fs.existsSync(p));
|
|
95
|
+
const resolveBinary = deps.resolveBinary ?? unwire_1.resolveMlaBinary;
|
|
96
|
+
const removeHooks = deps.removeHooks ?? unwire_1.removeMeetlessHooks;
|
|
97
|
+
const removeMcp = deps.removeMcp ?? unwire_1.removeMeetlessMcp;
|
|
98
|
+
const removeDir = deps.removeDir ?? unwire_1.removeDir;
|
|
99
|
+
const confirm = deps.confirm ?? defaultConfirm;
|
|
100
|
+
const choose = deps.choose ?? defaultChoose;
|
|
101
|
+
const flush = deps.flush ?? flush_1.runFlush;
|
|
102
|
+
const queued = countQueued(queueDir);
|
|
103
|
+
const events = countEvents(queueDir);
|
|
104
|
+
const { binPath, realPath } = resolveBinary(env);
|
|
105
|
+
// 1. Render the plan.
|
|
106
|
+
log("mla uninstall will remove the local Meetless footprint:");
|
|
107
|
+
log(` - ${home} (config, credentials, queue, hooks, logs, telemetry)`);
|
|
108
|
+
log(` - the Meetless hook entries in ${settingsPath}`);
|
|
109
|
+
log(` - the "meetless" MCP server in ${claudeJsonPath}`);
|
|
110
|
+
if (skillExists(skillDir))
|
|
111
|
+
log(` - ${skillDir} (the /mla skill)`);
|
|
112
|
+
log("");
|
|
113
|
+
log("Left in place on purpose:");
|
|
114
|
+
log(" - any of your own hooks or MCP servers (only Meetless's entries are removed)");
|
|
115
|
+
log(" - .meetless.json + the CLAUDE.md rule block in other repos (non-secret).");
|
|
116
|
+
log(" Remove those per repo with: rm .meetless.json (and delete the");
|
|
117
|
+
log(" `BEGIN MEETLESS RULES` ... `END MEETLESS RULES` block from CLAUDE.md)");
|
|
118
|
+
log("");
|
|
119
|
+
if (dryRun) {
|
|
120
|
+
// Disclose un-flushed captured events the real run would put at risk, so the
|
|
121
|
+
// preview names what is lost by its honest magnitude (event count, not file
|
|
122
|
+
// count). Stay silent when no un-flushed events remain: a counted session
|
|
123
|
+
// file can be fully drained and hold nothing to lose.
|
|
124
|
+
if (events > 0) {
|
|
125
|
+
log(`Note: the local queue holds ${events} captured event(s) across ${queued} session(s) not yet flushed to the server.`);
|
|
126
|
+
log("The real run will offer to flush, delete, or cancel before anything is removed.");
|
|
127
|
+
log("");
|
|
128
|
+
}
|
|
129
|
+
// Preview the one manual step uninstall leaves behind, so --dry-run shows
|
|
130
|
+
// the complete footprint and never undersells what stays on disk.
|
|
131
|
+
log("After removal, one manual step remains: remove the `mla` binary yourself:");
|
|
132
|
+
for (const line of (0, unwire_1.detectBinaryRemovalHint)(binPath, realPath))
|
|
133
|
+
log(line);
|
|
134
|
+
log("");
|
|
135
|
+
log("(dry run: nothing was changed.)");
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
// 2. Gate. Non-interactive requires --yes.
|
|
139
|
+
if (!yes && !isTTY) {
|
|
140
|
+
errlog("Refusing to uninstall non-interactively. Re-run with --yes to proceed (or --dry-run to preview).");
|
|
141
|
+
return 2;
|
|
142
|
+
}
|
|
143
|
+
// 3. Un-flushed-event safety (interactive only; --yes warns and proceeds).
|
|
144
|
+
// Gate on the event count, not the file count: drained-but-lingering session
|
|
145
|
+
// files carry nothing to lose, so they must not trigger a data-loss prompt.
|
|
146
|
+
if (events > 0) {
|
|
147
|
+
log(`Warning: the local queue holds ${events} captured event(s) across ${queued} session(s) not yet flushed to the server.`);
|
|
148
|
+
if (yes) {
|
|
149
|
+
log("Proceeding under --yes; this unflushed data will be deleted.");
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
const choice = await choose("[f]lush now, [d]elete anyway, or [c]ancel? ");
|
|
153
|
+
if (choice === "cancel") {
|
|
154
|
+
log("Cancelled. Nothing was changed.");
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
if (choice === "flush") {
|
|
158
|
+
log("Flushing queued sessions...");
|
|
159
|
+
const rc = await flush([]);
|
|
160
|
+
if (rc !== 0) {
|
|
161
|
+
errlog("Flush did not complete cleanly. Cancelling uninstall so no data is lost. Resolve the flush, then re-run.");
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// 4. Final confirmation (skipped under --yes).
|
|
168
|
+
if (!yes) {
|
|
169
|
+
const ok = await confirm("Remove the Meetless local install? [y/N] ");
|
|
170
|
+
if (!ok) {
|
|
171
|
+
log("Cancelled. Nothing was changed.");
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// 5. Execute. Strip wiring BEFORE deleting HOME. Each step best-effort.
|
|
176
|
+
let hadError = false;
|
|
177
|
+
const hooksRes = removeHooks(settingsPath);
|
|
178
|
+
if (hooksRes.changed) {
|
|
179
|
+
log(`Removed Meetless hooks (${hooksRes.removed.join(", ")}) from ${settingsPath}` +
|
|
180
|
+
(hooksRes.backupPath ? ` (backup: ${hooksRes.backupPath})` : ""));
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
log(`No Meetless hooks found in ${settingsPath}.`);
|
|
184
|
+
}
|
|
185
|
+
const mcpRes = removeMcp(claudeJsonPath);
|
|
186
|
+
if (mcpRes.changed) {
|
|
187
|
+
log(`Removed the "meetless" MCP server from ${claudeJsonPath}` +
|
|
188
|
+
(mcpRes.backupPath ? ` (backup: ${mcpRes.backupPath})` : ""));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
log(`No "meetless" MCP server found in ${claudeJsonPath}.`);
|
|
192
|
+
}
|
|
193
|
+
if (skillExists(skillDir)) {
|
|
194
|
+
const r = removeDir(skillDir);
|
|
195
|
+
if (r.error) {
|
|
196
|
+
hadError = true;
|
|
197
|
+
errlog(`Could not remove ${skillDir}: ${r.error}`);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
log(`Removed ${skillDir}.`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (homeExists(home)) {
|
|
204
|
+
const r = removeDir(home);
|
|
205
|
+
if (r.error) {
|
|
206
|
+
hadError = true;
|
|
207
|
+
errlog(`Could not remove ${home}: ${r.error}`);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
log(`Removed ${home}.`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
log(`${home} was already absent.`);
|
|
215
|
+
}
|
|
216
|
+
// 6. Binary: print, never self-delete.
|
|
217
|
+
log("");
|
|
218
|
+
log("Local state is gone. One step left, to remove the `mla` binary itself:");
|
|
219
|
+
for (const line of (0, unwire_1.detectBinaryRemovalHint)(binPath, realPath))
|
|
220
|
+
log(line);
|
|
221
|
+
return hadError ? 1 : 0;
|
|
222
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runWhoami = runWhoami;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const http_1 = require("../lib/http");
|
|
6
|
+
// Best-effort humanizer for an ISO expiry. Mirrors login.ts formatRemaining:
|
|
7
|
+
// null for an unparseable/absent timestamp so callers degrade gracefully.
|
|
8
|
+
function formatExpiry(iso) {
|
|
9
|
+
if (!iso)
|
|
10
|
+
return "unknown";
|
|
11
|
+
const ms = Date.parse(iso) - Date.now();
|
|
12
|
+
if (Number.isNaN(ms))
|
|
13
|
+
return "unknown";
|
|
14
|
+
if (ms <= 0)
|
|
15
|
+
return "expired";
|
|
16
|
+
const hours = Math.floor(ms / (60 * 60 * 1000));
|
|
17
|
+
if (hours < 48)
|
|
18
|
+
return `in ~${hours}h`;
|
|
19
|
+
return `in ~${Math.floor(hours / 24)}d`;
|
|
20
|
+
}
|
|
21
|
+
async function runWhoami(argv, deps = {}) {
|
|
22
|
+
if (argv.length > 0) {
|
|
23
|
+
console.error(`\`mla whoami\` takes no arguments (got: ${argv.join(" ")}).`);
|
|
24
|
+
return 2;
|
|
25
|
+
}
|
|
26
|
+
const log = deps.log ?? ((m) => console.log(m));
|
|
27
|
+
const getMeFn = deps.getMeFn ?? ((cfg) => (0, http_1.get)(cfg, "/internal/v1/auth/me"));
|
|
28
|
+
// `auth.mode: none` is also the shape of a box that never ran `mla init`.
|
|
29
|
+
if (!(0, config_1.configExists)()) {
|
|
30
|
+
log("Not configured (no cli-config.json).");
|
|
31
|
+
log("Run `mla init --control-token <T>` or `mla login` to get started.");
|
|
32
|
+
return 1;
|
|
33
|
+
}
|
|
34
|
+
let cfg;
|
|
35
|
+
try {
|
|
36
|
+
cfg = (0, config_1.readConfig)();
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
console.error(e.message);
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
if (cfg.auth.mode === "none") {
|
|
43
|
+
log("Not configured (auth.mode: none).");
|
|
44
|
+
log("Run `mla init --control-token <T>` or `mla login` to log in.");
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
if (cfg.auth.mode === "shared-key") {
|
|
48
|
+
// A shared key authenticates AS the workspace's internal key; there is no
|
|
49
|
+
// user behind it. Print the mode + the folder/config workspace without a
|
|
50
|
+
// network call (the §6.6 contract: "does NOT call /auth/me").
|
|
51
|
+
log("auth.mode: shared-key (no user identity)");
|
|
52
|
+
log(` Control: ${cfg.controlUrl}`);
|
|
53
|
+
if (cfg.workspaceId) {
|
|
54
|
+
log(` Workspace: ${cfg.workspaceId} (from cli-config.json)`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
log(" Workspace: resolved per-folder from .meetless.json (run `mla workspace`)");
|
|
58
|
+
}
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
// user-token: resolve live identity from control.
|
|
62
|
+
let me;
|
|
63
|
+
try {
|
|
64
|
+
me = await getMeFn(cfg);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
const err = e;
|
|
68
|
+
if (err.status === 401) {
|
|
69
|
+
// After T27's auto-refresh has tried and failed, a 401 means the session
|
|
70
|
+
// is gone (expired or revoked). Point the operator at re-login.
|
|
71
|
+
console.error("Your CLI session has expired or was revoked. Run `mla login`.");
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
// Network failure, control down, or unexpected status. The cached identity
|
|
75
|
+
// is still useful, so print it with a clear "could not reach control" note
|
|
76
|
+
// rather than failing blind.
|
|
77
|
+
console.error(`Could not reach control to verify the session (${err.message}).`);
|
|
78
|
+
const who = cfg.auth.user.displayName || cfg.auth.user.id;
|
|
79
|
+
const email = cfg.auth.user.email ? ` <${cfg.auth.user.email}>` : "";
|
|
80
|
+
log(`Cached identity: ${who}${email} (role ${cfg.auth.user.role}, unverified).`);
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
if (me.mode === "shared-key" || !me.user) {
|
|
84
|
+
// Should not happen for a user-token bearer, but stay honest if control
|
|
85
|
+
// reports shared-key (e.g. the token happened to equal INTERNAL_API_KEY).
|
|
86
|
+
log("auth.mode: shared-key (no user identity)");
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
const email = me.user.email ? ` <${me.user.email}>` : "";
|
|
90
|
+
log(`Logged in as ${me.user.displayName}${email}.`);
|
|
91
|
+
log(` User: ${me.user.id}`);
|
|
92
|
+
log(` Role: ${me.user.role}`);
|
|
93
|
+
if (me.workspace) {
|
|
94
|
+
log(` Workspace: ${me.workspace.name} (${me.workspace.slug})`);
|
|
95
|
+
}
|
|
96
|
+
if (me.sessionId) {
|
|
97
|
+
log(` Session: ${me.sessionId}`);
|
|
98
|
+
}
|
|
99
|
+
log(` Access token expires ${formatExpiry(me.accessExpiresAt)}.`);
|
|
100
|
+
log(` Refresh token expires ${formatExpiry(me.refreshExpiresAt)}.`);
|
|
101
|
+
return 0;
|
|
102
|
+
}
|