@mafdet/cli 0.4.3 → 0.4.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/CHANGELOG.md +64 -0
- package/README.md +15 -5
- package/README.zh-CN.md +9 -2
- package/dist/agent/context-budget.d.ts +32 -0
- package/dist/agent/context-budget.d.ts.map +1 -1
- package/dist/agent/context-budget.js +64 -0
- package/dist/agent/loop.d.ts +8 -1
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +25 -13
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +7 -1
- package/dist/api/http.d.ts +15 -1
- package/dist/api/http.d.ts.map +1 -1
- package/dist/api/http.js +26 -5
- package/dist/api/sse-parser.d.ts +26 -1
- package/dist/api/sse-parser.d.ts.map +1 -1
- package/dist/api/sse-parser.js +62 -21
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +76 -0
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +21 -4
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +0 -2
- package/dist/commands/runtime.d.ts +6 -0
- package/dist/commands/runtime.d.ts.map +1 -1
- package/dist/commands/sessions.d.ts.map +1 -1
- package/dist/commands/sessions.js +129 -18
- package/dist/config/config.d.ts +0 -12
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +31 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -1
- package/dist/output/format.d.ts.map +1 -1
- package/dist/output/format.js +10 -5
- package/dist/output/terminal-safe.d.ts +12 -0
- package/dist/output/terminal-safe.d.ts.map +1 -0
- package/dist/output/terminal-safe.js +38 -0
- package/dist/sessions/open.d.ts.map +1 -1
- package/dist/sessions/open.js +26 -2
- package/dist/sessions/recorder.d.ts.map +1 -1
- package/dist/sessions/recorder.js +6 -1
- package/dist/sessions/session-id.d.ts +16 -0
- package/dist/sessions/session-id.d.ts.map +1 -0
- package/dist/sessions/session-id.js +93 -0
- package/dist/sessions/store.d.ts +41 -2
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +48 -24
- package/dist/sessions/usage.d.ts +6 -0
- package/dist/sessions/usage.d.ts.map +1 -1
- package/dist/sessions/usage.js +12 -1
- package/dist/tui/agent-surface.d.ts.map +1 -1
- package/dist/tui/agent-surface.js +6 -2
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +43 -10
- package/dist/tui/start-agent.d.ts +31 -0
- package/dist/tui/start-agent.d.ts.map +1 -1
- package/dist/tui/start-agent.js +147 -45
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"AAKA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/commands/agent.ts"],"names":[],"mappings":"AAKA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAYvE,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAGnD,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAatB,eAAO,MAAM,gBAAgB,0XAK+C,CAAC;AAE7E,eAAO,MAAM,YAAY,mgBAOmB,CAAC;AAE7C,MAAM,WAAW,OAAQ,SAAQ,SAAS;IACxC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,qDAAqD;IACrD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,uBAAuB,EAAE,QAAQ,CAAC;CACrD;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,OAAO,GACV,OAAO,CAAC,QAAQ,CAAC,CAsHnB;AAyJD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,kBAAkB,EAAE,OAAO,0JAI3E"}
|
package/dist/commands/agent.js
CHANGED
|
@@ -2,6 +2,11 @@ import { fetchAccessibleModels, fetchCatalog, intersectModels, } from "../api/mo
|
|
|
2
2
|
import { approverFor } from "../agent/approvals.js";
|
|
3
3
|
import { runAgent } from "../agent/loop.js";
|
|
4
4
|
import { runSession } from "../agent/session.js";
|
|
5
|
+
import { sessionsDir } from "../config/paths.js";
|
|
6
|
+
import { keyDigest } from "../security/redact.js";
|
|
7
|
+
import { decideRecording } from "../sessions/policy.js";
|
|
8
|
+
import { SessionRecorder } from "../sessions/recorder.js";
|
|
9
|
+
import { SessionWriter } from "../sessions/store.js";
|
|
5
10
|
import { READ_ONLY_TOOLS } from "../agent/tools/read-only.js";
|
|
6
11
|
import { COMMAND_TOOLS } from "../agent/tools/run-command.js";
|
|
7
12
|
import { WRITE_TOOLS } from "../agent/tools/write.js";
|
|
@@ -74,8 +79,14 @@ export async function agentCommand(argv, io) {
|
|
|
74
79
|
io.stderr(`mafdet agent: no goal\n${USAGE}`);
|
|
75
80
|
return ExitCode.Usage;
|
|
76
81
|
}
|
|
82
|
+
// F-007 · `decideRecording` already called this surface "interactive",
|
|
83
|
+
// so the policy claimed these runs were saved while nothing saved them.
|
|
84
|
+
const recorder = openRecorder(parsed, io, model, workspace.root, mode);
|
|
77
85
|
const summary = await runSession({
|
|
78
86
|
client: runtime.dataPlane,
|
|
87
|
+
...(recorder === undefined
|
|
88
|
+
? {}
|
|
89
|
+
: { onEvent: (event) => recorder.observe(event) }),
|
|
79
90
|
model,
|
|
80
91
|
pricing: resolved,
|
|
81
92
|
catalog,
|
|
@@ -89,6 +100,7 @@ export async function agentCommand(argv, io) {
|
|
|
89
100
|
stderr: io.stderr,
|
|
90
101
|
signal: io.signal,
|
|
91
102
|
});
|
|
103
|
+
recorder?.close();
|
|
92
104
|
io.stderr("\n" +
|
|
93
105
|
renderRecord([
|
|
94
106
|
["goals", `${summary.goals}`],
|
|
@@ -130,6 +142,45 @@ export async function agentCommand(argv, io) {
|
|
|
130
142
|
return reportError(error, io);
|
|
131
143
|
}
|
|
132
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* F-007 · A recorder for an interactive `mafdet agent`, or nothing.
|
|
147
|
+
*
|
|
148
|
+
* The same store, policy and failure behaviour as the TUI: `--no-history`
|
|
149
|
+
* writes nothing at all, and a store that cannot be opened costs the
|
|
150
|
+
* transcript rather than the session.
|
|
151
|
+
*/
|
|
152
|
+
function openRecorder(parsed, io, modelId, workspaceRoot, mode) {
|
|
153
|
+
const decision = decideRecording({
|
|
154
|
+
surface: "interactive",
|
|
155
|
+
noHistory: parsed.noHistory,
|
|
156
|
+
save: false,
|
|
157
|
+
});
|
|
158
|
+
if (!decision.record)
|
|
159
|
+
return undefined;
|
|
160
|
+
const env = io.env ?? process.env;
|
|
161
|
+
const root = sessionsDir(env);
|
|
162
|
+
try {
|
|
163
|
+
const writer = SessionWriter.create({
|
|
164
|
+
root,
|
|
165
|
+
title: parsed.title ??
|
|
166
|
+
workspaceRoot.split(/[\\/]/).filter(Boolean).at(-1) ??
|
|
167
|
+
"Conversation",
|
|
168
|
+
modelId,
|
|
169
|
+
workspace: workspaceRoot,
|
|
170
|
+
mode,
|
|
171
|
+
});
|
|
172
|
+
return new SessionRecorder({
|
|
173
|
+
writer,
|
|
174
|
+
root,
|
|
175
|
+
apiKeyRef: keyDigest(env.MAFDET_API_KEY ?? ""),
|
|
176
|
+
onProblem: (message) => io.stderr(`${message}\n`),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
io.stderr(`not recording this conversation: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
133
184
|
function parseArgs(argv, io) {
|
|
134
185
|
const positional = [];
|
|
135
186
|
let model;
|
|
@@ -137,6 +188,8 @@ function parseArgs(argv, io) {
|
|
|
137
188
|
let maxCostMicroUsd;
|
|
138
189
|
let json = false;
|
|
139
190
|
let allowWrite = false;
|
|
191
|
+
let noHistory = false;
|
|
192
|
+
let title;
|
|
140
193
|
const allowedCommands = [];
|
|
141
194
|
for (let i = 0; i < argv.length; i += 1) {
|
|
142
195
|
const arg = argv[i];
|
|
@@ -157,6 +210,25 @@ function parseArgs(argv, io) {
|
|
|
157
210
|
}
|
|
158
211
|
else if (arg === "--read-only")
|
|
159
212
|
allowWrite = false;
|
|
213
|
+
else if (arg === "--no-history")
|
|
214
|
+
noHistory = true;
|
|
215
|
+
else if (arg === "--title") {
|
|
216
|
+
const entry = next();
|
|
217
|
+
if (!entry) {
|
|
218
|
+
io.stderr("mafdet agent: --title needs a value\n");
|
|
219
|
+
return ExitCode.Usage;
|
|
220
|
+
}
|
|
221
|
+
title = entry;
|
|
222
|
+
}
|
|
223
|
+
else if (arg === "--resume") {
|
|
224
|
+
// F-007 · Deliberately not implemented here, and said rather than
|
|
225
|
+
// silently ignored. Resuming has to reconcile the session's model and
|
|
226
|
+
// workspace against the current key, which the TUI has a place to do
|
|
227
|
+
// and this surface does not - and a second, weaker resume path is
|
|
228
|
+
// exactly how the two drift apart. F-003 was that drift.
|
|
229
|
+
io.stderr("mafdet agent: --resume is not available here; run 'mafdet --resume <id>'\n");
|
|
230
|
+
return ExitCode.Usage;
|
|
231
|
+
}
|
|
160
232
|
else if (arg === "--max-turns") {
|
|
161
233
|
const raw = next();
|
|
162
234
|
maxTurns = Number(raw);
|
|
@@ -198,6 +270,8 @@ function parseArgs(argv, io) {
|
|
|
198
270
|
maxTurns,
|
|
199
271
|
maxCostMicroUsd,
|
|
200
272
|
json,
|
|
273
|
+
noHistory,
|
|
274
|
+
...(title === undefined ? {} : { title }),
|
|
201
275
|
};
|
|
202
276
|
}
|
|
203
277
|
/**
|
|
@@ -258,6 +332,8 @@ function finish(report, json, io) {
|
|
|
258
332
|
case "max_turns":
|
|
259
333
|
case "timeout":
|
|
260
334
|
case "max_cost":
|
|
335
|
+
// F-008 · A ceiling too: the window ran out, and the report says which.
|
|
336
|
+
case "context_limit":
|
|
261
337
|
// Stopped short on purpose; the report says so rather than pretending.
|
|
262
338
|
return ExitCode.LimitReached;
|
|
263
339
|
case "rate_limited":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAWxE,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEnD,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAatB,MAAM,WAAW,MAAO,SAAQ,SAAS;IACvC,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAClC,gDAAgD;IAChD,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAcD,wBAAsB,WAAW,CAC/B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,QAAQ,CAAC,CA8BnB;AA8SD,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,YAAY,GAAG,SAAS,EAC/B,KAAK,EAAE,WAAW,GACjB,MAAM,GAAG,SAAS,CASpB"}
|
package/dist/commands/chat.js
CHANGED
|
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { fetchCatalog } from "../api/models.js";
|
|
3
3
|
import { readChatStream } from "../api/sse-parser.js";
|
|
4
4
|
import { ApiError } from "../api/errors.js";
|
|
5
|
+
import { safeForTerminal, terminalSafeWriter, } from "../output/terminal-safe.js";
|
|
5
6
|
import { sessionsDir } from "../config/paths.js";
|
|
6
7
|
import { keyDigest } from "../security/redact.js";
|
|
7
8
|
import { upsertIndexEntry } from "../sessions/index-file.js";
|
|
@@ -196,7 +197,11 @@ function saveChat(args, io, answer, usage, requestId, model) {
|
|
|
196
197
|
model_id: args.model,
|
|
197
198
|
prompt_tokens: usage.prompt_tokens,
|
|
198
199
|
completion_tokens: usage.completion_tokens,
|
|
199
|
-
|
|
200
|
+
// F-012 · Omitted rather than zeroed when the catalogue could not
|
|
201
|
+
// price it: "we do not know" must not be recorded as "it was free".
|
|
202
|
+
...(micros === undefined
|
|
203
|
+
? {}
|
|
204
|
+
: { estimated_charge_micro_usd: micros.toString() }),
|
|
200
205
|
settlement_status: "pending",
|
|
201
206
|
api_key_ref: keyDigest(readKey(io)),
|
|
202
207
|
});
|
|
@@ -217,6 +222,10 @@ function chatTitle(prompt) {
|
|
|
217
222
|
const trimmed = line.trim();
|
|
218
223
|
return trimmed.length > 60 ? `${trimmed.slice(0, 57)}...` : trimmed;
|
|
219
224
|
}
|
|
225
|
+
/** Whether a terminal is on the other end of stdout. Injectable for tests. */
|
|
226
|
+
function isTty(io) {
|
|
227
|
+
return io.isTty ?? process.stdout.isTTY === true;
|
|
228
|
+
}
|
|
220
229
|
function readKey(io) {
|
|
221
230
|
return (io.env ?? process.env).MAFDET_API_KEY ?? "";
|
|
222
231
|
}
|
|
@@ -238,12 +247,20 @@ async function priceModel(runtime, modelId, needed) {
|
|
|
238
247
|
}
|
|
239
248
|
}
|
|
240
249
|
async function streamChat(runtime, body, args, io) {
|
|
241
|
-
const { response, requestId } = await runtime.dataPlane.postStream("/chat/completions", body, {
|
|
250
|
+
const { response, requestId, abort, limits } = await runtime.dataPlane.postStream("/chat/completions", body, {
|
|
242
251
|
signal: io.signal,
|
|
243
252
|
});
|
|
244
253
|
// Text goes to stdout as it arrives; everything else is stderr, so
|
|
245
254
|
// `mafdet chat ... > answer.txt` captures only the answer.
|
|
246
|
-
|
|
255
|
+
// F-009 · The limits travel with the stream: without them a gateway that
|
|
256
|
+
// sent headers and then went quiet would hang here indefinitely.
|
|
257
|
+
// F-010 · Sanitised only when a terminal is reading: `mafdet chat >
|
|
258
|
+
// answer.txt` and `| jq` must get the bytes the model sent.
|
|
259
|
+
const write = terminalSafeWriter(io.stdout, isTty(io));
|
|
260
|
+
const result = await readChatStream(response, { onText: write }, io.signal, {
|
|
261
|
+
...limits,
|
|
262
|
+
abort,
|
|
263
|
+
});
|
|
247
264
|
io.stdout("\n");
|
|
248
265
|
const model = await priceModel(runtime, args.model, !args.quiet || args.save);
|
|
249
266
|
if (io.signal?.aborted) {
|
|
@@ -260,7 +277,7 @@ async function streamChat(runtime, body, args, io) {
|
|
|
260
277
|
async function completeChat(runtime, body, args, io) {
|
|
261
278
|
const { data, requestId } = await runtime.dataPlane.postJson("/chat/completions", body, { signal: io.signal });
|
|
262
279
|
const answer = data.choices?.[0]?.message?.content ?? "";
|
|
263
|
-
io.stdout(`${answer}\n`);
|
|
280
|
+
io.stdout(`${safeForTerminal(answer, isTty(io))}\n`);
|
|
264
281
|
const model = await priceModel(runtime, args.model, !args.quiet || args.save);
|
|
265
282
|
summarise(args, data.usage, io, model);
|
|
266
283
|
saveChat(args, io, answer, data.usage, requestId, model);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAOnD,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,SAAS,GACZ,QAAQ,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAOnD,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,SAAS,GACZ,QAAQ,CAsEV"}
|
package/dist/commands/config.js
CHANGED
|
@@ -44,7 +44,6 @@ export function configCommand(argv, io) {
|
|
|
44
44
|
model_source: config.modelSource,
|
|
45
45
|
base_url: config.baseUrl,
|
|
46
46
|
backend_url: config.backendUrl,
|
|
47
|
-
approval_mode: config.approvalMode,
|
|
48
47
|
api_key: config.apiKey ? keyFingerprint(config.apiKey) : null,
|
|
49
48
|
user_config: config.sources.userConfigFound
|
|
50
49
|
? config.sources.userConfigPath
|
|
@@ -61,7 +60,6 @@ export function configCommand(argv, io) {
|
|
|
61
60
|
["model", view.model ? `${view.model} (${view.model_source})` : "<unset>"],
|
|
62
61
|
["base url", view.base_url],
|
|
63
62
|
["backend url", view.backend_url],
|
|
64
|
-
["approval mode", view.approval_mode],
|
|
65
63
|
["api key", view.api_key ?? "<unset> (set MAFDET_API_KEY)"],
|
|
66
64
|
["user config", view.user_config ?? "<none>"],
|
|
67
65
|
["project config", view.project_config ?? "<none>"],
|
|
@@ -10,6 +10,12 @@ export interface CommandIo {
|
|
|
10
10
|
/** Injected by tests; production uses global fetch. */
|
|
11
11
|
fetchImpl?: typeof fetch;
|
|
12
12
|
version?: string;
|
|
13
|
+
/**
|
|
14
|
+
* F-010 · Whether stdout is a terminal. Untrusted text is stripped of
|
|
15
|
+
* control sequences only then: a pipe or a file must receive exact bytes.
|
|
16
|
+
* Defaults to `process.stdout.isTTY`; tests set it explicitly.
|
|
17
|
+
*/
|
|
18
|
+
isTty?: boolean;
|
|
13
19
|
}
|
|
14
20
|
export interface Runtime {
|
|
15
21
|
config: LoadedConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/commands/runtime.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEnD,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/commands/runtime.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEnD,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,MAAM,EAAE,YAAY,CAAC;IACrB,sCAAsC;IACtC,SAAS,EAAE,UAAU,CAAC;IACtB,yCAAyC;IACzC,YAAY,EAAE,UAAU,CAAC;CAC1B;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAgBnD;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CASxD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,GAAG,QAAQ,CAkBnE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/commands/sessions.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"sessions.d.ts","sourceRoot":"","sources":["../../src/commands/sessions.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AA4BnD,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AAgBtB,wBAAsB,eAAe,CACnC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,SAAS,GACZ,OAAO,CAAC,QAAQ,CAAC,CAUnB;AA8PD,eAAO,MAAM,eAAe,GAAI,IAAI,MAAM,KAAG,MAUX,CAAC"}
|
|
@@ -11,14 +11,15 @@ import { sessionsDir } from "../config/paths.js";
|
|
|
11
11
|
import { formatUsd } from "../output/money.js";
|
|
12
12
|
import { ExitCode } from "../output/exit-codes.js";
|
|
13
13
|
import { parseFormatFlags, renderJson, renderRecord, renderTable, } from "../output/format.js";
|
|
14
|
+
import { safeForTerminal } from "../output/terminal-safe.js";
|
|
14
15
|
import { keyDigest } from "../security/redact.js";
|
|
15
16
|
import { loadIndex, removeIndexEntry } from "../sessions/index-file.js";
|
|
16
17
|
import { acquireSessionLock, SessionLockedError } from "../sessions/lock.js";
|
|
17
18
|
import { daysToCover, matchSettlements, planReconciliation, } from "../sessions/reconcile.js";
|
|
19
|
+
import { InvalidSessionIdError, resolveSessionDirectory, } from "../sessions/session-id.js";
|
|
18
20
|
import { deleteSession, readSession, SessionWriter, } from "../sessions/store.js";
|
|
19
21
|
import { settledCaveat, summariseUsage } from "../sessions/usage.js";
|
|
20
22
|
import { buildRuntime, reportError, requireApiKey, } from "./runtime.js";
|
|
21
|
-
import { join } from "node:path";
|
|
22
23
|
const USAGE = `Usage:
|
|
23
24
|
mafdet sessions list [--json|--plain]
|
|
24
25
|
mafdet sessions show <session-id> [--json]
|
|
@@ -32,6 +33,18 @@ calls a model and is never charged.
|
|
|
32
33
|
cost. It sends request IDs and nothing else - no messages, no titles.
|
|
33
34
|
`;
|
|
34
35
|
export async function sessionsCommand(argv, io) {
|
|
36
|
+
// F-013 · Nothing here may reach the top level as a raw Error. `--json
|
|
37
|
+
// --plain` used to print an ESM stack trace and exit 1 instead of a usage
|
|
38
|
+
// message, and every other command surface already funnels through
|
|
39
|
+
// reportError.
|
|
40
|
+
try {
|
|
41
|
+
return await run(argv, io);
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
return reportError(error, io);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function run(argv, io) {
|
|
35
48
|
const [sub, ...rest] = argv;
|
|
36
49
|
const root = sessionsDir(io.env ?? process.env);
|
|
37
50
|
if (sub === undefined || sub === "help" || sub === "--help") {
|
|
@@ -67,9 +80,12 @@ function list(argv, io, root) {
|
|
|
67
80
|
return ExitCode.Ok;
|
|
68
81
|
}
|
|
69
82
|
io.stdout(renderTable(sessions, [
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
// F-010 · Every one of these came off disk. A transcript is a file, and
|
|
84
|
+
// a title carrying an OSC sequence would retitle the window of anyone
|
|
85
|
+
// who ran `sessions list`.
|
|
86
|
+
{ header: "SESSION", value: (s) => safe(s.session_id, io) },
|
|
87
|
+
{ header: "TITLE", value: (s) => safe(s.title, io) },
|
|
88
|
+
{ header: "MODEL", value: (s) => safe(s.model_id, io) },
|
|
73
89
|
{ header: "REQUESTS", value: (s) => `${s.model_requests}` },
|
|
74
90
|
{
|
|
75
91
|
header: "EST. COST",
|
|
@@ -80,13 +96,17 @@ function list(argv, io, root) {
|
|
|
80
96
|
return ExitCode.Ok;
|
|
81
97
|
}
|
|
82
98
|
function show(argv, io, root) {
|
|
83
|
-
const { format } = parseFormatFlags(argv);
|
|
84
|
-
const id =
|
|
99
|
+
const { format, rest } = parseFormatFlags(argv);
|
|
100
|
+
const id = rest[0];
|
|
85
101
|
if (id === undefined) {
|
|
86
102
|
io.stderr(`mafdet sessions show: no session id\n${USAGE}`);
|
|
87
103
|
return ExitCode.Usage;
|
|
88
104
|
}
|
|
89
|
-
|
|
105
|
+
if (!checkExtra("show", rest, io))
|
|
106
|
+
return ExitCode.Usage;
|
|
107
|
+
const directory = locate(root, id, io);
|
|
108
|
+
if (directory === undefined)
|
|
109
|
+
return ExitCode.Usage;
|
|
90
110
|
if (!existsSync(directory)) {
|
|
91
111
|
io.stderr(`mafdet sessions: no session '${id}' on this computer\n`);
|
|
92
112
|
return ExitCode.General;
|
|
@@ -104,16 +124,17 @@ function show(argv, io, root) {
|
|
|
104
124
|
metadata: session.metadata,
|
|
105
125
|
events: session.events,
|
|
106
126
|
truncated_tail: session.truncatedTail,
|
|
127
|
+
corrupt_lines: session.corruptLines,
|
|
107
128
|
}));
|
|
108
129
|
return ExitCode.Ok;
|
|
109
130
|
}
|
|
110
131
|
const m = session.metadata;
|
|
111
132
|
io.stdout(renderRecord([
|
|
112
|
-
["session", m.session_id],
|
|
113
|
-
["title", m.title],
|
|
114
|
-
["model", m.model_id],
|
|
115
|
-
["workspace", m.workspace],
|
|
116
|
-
["mode", m.mode],
|
|
133
|
+
["session", safe(m.session_id, io)],
|
|
134
|
+
["title", safe(m.title, io)],
|
|
135
|
+
["model", safe(m.model_id, io)],
|
|
136
|
+
["workspace", safe(m.workspace, io)],
|
|
137
|
+
["mode", safe(m.mode, io)],
|
|
117
138
|
["created", m.created_at],
|
|
118
139
|
["updated", m.updated_at],
|
|
119
140
|
["messages", `${m.message_count}`],
|
|
@@ -125,16 +146,29 @@ function show(argv, io, root) {
|
|
|
125
146
|
// Worth saying plainly: the user should know a crash cost them the tail.
|
|
126
147
|
io.stderr("\nnote: the last event was incomplete, most likely an interrupted run.\n");
|
|
127
148
|
}
|
|
149
|
+
if (session.corruptLines.length > 0) {
|
|
150
|
+
// F-016 · A hole in the middle is not an interrupted run - it is
|
|
151
|
+
// corruption or interleaved writes, and part of the conversation is
|
|
152
|
+
// simply gone. Presenting the rest as complete would be a lie.
|
|
153
|
+
io.stderr(`\nwarning: ${session.corruptLines.length} unreadable line(s) in this ` +
|
|
154
|
+
`transcript (line ${session.corruptLines.join(", ")}); ` +
|
|
155
|
+
`some of the conversation is missing.\n`);
|
|
156
|
+
}
|
|
128
157
|
return ExitCode.Ok;
|
|
129
158
|
}
|
|
130
159
|
function remove(argv, io, root) {
|
|
131
|
-
const
|
|
160
|
+
const { rest } = parseFormatFlags(argv);
|
|
161
|
+
const id = rest[0];
|
|
132
162
|
const confirmed = argv.includes("--yes");
|
|
133
163
|
if (id === undefined) {
|
|
134
164
|
io.stderr(`mafdet sessions delete: no session id\n${USAGE}`);
|
|
135
165
|
return ExitCode.Usage;
|
|
136
166
|
}
|
|
137
|
-
|
|
167
|
+
if (!checkExtra("delete", rest, io, ["--yes"]))
|
|
168
|
+
return ExitCode.Usage;
|
|
169
|
+
const directory = locate(root, id, io);
|
|
170
|
+
if (directory === undefined)
|
|
171
|
+
return ExitCode.Usage;
|
|
138
172
|
if (!existsSync(directory)) {
|
|
139
173
|
io.stderr(`mafdet sessions: no session '${id}' on this computer\n`);
|
|
140
174
|
return ExitCode.General;
|
|
@@ -146,11 +180,84 @@ function remove(argv, io, root) {
|
|
|
146
180
|
io.stderr(DELETION_NOTICE(id));
|
|
147
181
|
return ExitCode.Usage;
|
|
148
182
|
}
|
|
149
|
-
|
|
183
|
+
// F-002 · Under the lock: a session open in another window is still writing
|
|
184
|
+
// to this directory, and deleting underneath it leaves the live process to
|
|
185
|
+
// recreate half a session.
|
|
186
|
+
let lock;
|
|
187
|
+
try {
|
|
188
|
+
lock = acquireSessionLock(directory);
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
// Not only SessionLockedError: an unwritable directory throws EACCES here,
|
|
192
|
+
// and letting that escape would print a Node stack trace at the user.
|
|
193
|
+
io.stderr(`mafdet sessions: cannot delete ${id}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
194
|
+
return ExitCode.General;
|
|
195
|
+
}
|
|
196
|
+
let result;
|
|
197
|
+
try {
|
|
198
|
+
result = deleteSession(directory);
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
// The lock file lives inside the directory, so a successful delete already
|
|
202
|
+
// took it with it; releasing is for the failure path.
|
|
203
|
+
try {
|
|
204
|
+
lock.release();
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// Gone with the directory.
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (!result.removed) {
|
|
211
|
+
// F-002 · Never print "deleted" for something still on disk.
|
|
212
|
+
io.stderr(`mafdet sessions: ${id} was not fully deleted\n`);
|
|
213
|
+
for (const failure of result.failures) {
|
|
214
|
+
io.stderr(` ${failure.path}: ${failure.reason}\n`);
|
|
215
|
+
}
|
|
216
|
+
// The index is rebuilt from the directories, so leaving the entry keeps
|
|
217
|
+
// the listing honest about what is still there.
|
|
218
|
+
return ExitCode.General;
|
|
219
|
+
}
|
|
150
220
|
removeIndexEntry(root, id);
|
|
151
221
|
io.stdout(`deleted ${id}\n`);
|
|
152
222
|
return ExitCode.Ok;
|
|
153
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* F-001 · Turn an id into a directory, or say why not.
|
|
226
|
+
*
|
|
227
|
+
* The single boundary: `show`, `usage` and `delete` all come through here, so
|
|
228
|
+
* there is no subcommand left that can build the path its own way. Returns
|
|
229
|
+
* undefined after reporting, because every caller's response is the same.
|
|
230
|
+
*/
|
|
231
|
+
/**
|
|
232
|
+
* F-014 · Refuse what we were not asked to do.
|
|
233
|
+
*
|
|
234
|
+
* A mistyped flag or a stray second argument used to be dropped silently, so
|
|
235
|
+
* `sessions delete a b --yes` deleted `a` and said nothing about `b`, and
|
|
236
|
+
* `--dry-run` looked like it worked. Anything not recognised is an error.
|
|
237
|
+
*/
|
|
238
|
+
/** F-010 · Terminal-safe when a terminal is reading, exact bytes otherwise. */
|
|
239
|
+
function safe(text, io) {
|
|
240
|
+
return safeForTerminal(text, io.isTty ?? process.stdout.isTTY === true);
|
|
241
|
+
}
|
|
242
|
+
function checkExtra(name, rest, io, known = []) {
|
|
243
|
+
const leftover = rest.slice(1).filter((arg) => !known.includes(arg));
|
|
244
|
+
if (leftover.length === 0)
|
|
245
|
+
return true;
|
|
246
|
+
io.stderr(`mafdet sessions ${name}: unexpected argument '${leftover[0]}'\n${USAGE}`);
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
function locate(root, id, io) {
|
|
250
|
+
try {
|
|
251
|
+
return resolveSessionDirectory(root, id);
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
if (error instanceof InvalidSessionIdError) {
|
|
255
|
+
io.stderr(`mafdet sessions: ${error.message}\n`);
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
154
261
|
export const DELETION_NOTICE = (id) => `Deleting ${id} removes local messages and its per-session usage mapping\n` +
|
|
155
262
|
`from this computer.\n\n` +
|
|
156
263
|
`It does not remove API usage, billing records, or charges.\n\n` +
|
|
@@ -170,13 +277,17 @@ export const DELETION_NOTICE = (id) => `Deleting ${id} removes local messages an
|
|
|
170
277
|
* would be a smaller number than the truth.
|
|
171
278
|
*/
|
|
172
279
|
async function usage(argv, io, root) {
|
|
173
|
-
const { format } = parseFormatFlags(argv);
|
|
174
|
-
const id =
|
|
280
|
+
const { format, rest } = parseFormatFlags(argv);
|
|
281
|
+
const id = rest[0];
|
|
175
282
|
if (id === undefined) {
|
|
176
283
|
io.stderr(`mafdet sessions usage: no session id\n${USAGE}`);
|
|
177
284
|
return ExitCode.Usage;
|
|
178
285
|
}
|
|
179
|
-
|
|
286
|
+
if (!checkExtra("usage", rest, io, ["--refresh"]))
|
|
287
|
+
return ExitCode.Usage;
|
|
288
|
+
const directory = locate(root, id, io);
|
|
289
|
+
if (directory === undefined)
|
|
290
|
+
return ExitCode.Usage;
|
|
180
291
|
if (!existsSync(directory)) {
|
|
181
292
|
io.stderr(`mafdet sessions: no session '${id}' on this computer\n`);
|
|
182
293
|
return ExitCode.General;
|
package/dist/config/config.d.ts
CHANGED
|
@@ -6,20 +6,9 @@ export declare class ConfigError extends Error {
|
|
|
6
6
|
}
|
|
7
7
|
declare const projectConfigSchema: z.ZodObject<{
|
|
8
8
|
model: z.ZodOptional<z.ZodString>;
|
|
9
|
-
approvalMode: z.ZodOptional<z.ZodEnum<{
|
|
10
|
-
"read-only": "read-only";
|
|
11
|
-
suggest: "suggest";
|
|
12
|
-
"auto-edit": "auto-edit";
|
|
13
|
-
}>>;
|
|
14
|
-
testCommands: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
15
9
|
}, z.core.$strict>;
|
|
16
10
|
declare const userConfigSchema: z.ZodObject<{
|
|
17
11
|
model: z.ZodOptional<z.ZodString>;
|
|
18
|
-
approvalMode: z.ZodOptional<z.ZodEnum<{
|
|
19
|
-
"read-only": "read-only";
|
|
20
|
-
suggest: "suggest";
|
|
21
|
-
"auto-edit": "auto-edit";
|
|
22
|
-
}>>;
|
|
23
12
|
baseUrl: z.ZodOptional<z.ZodString>;
|
|
24
13
|
backendUrl: z.ZodOptional<z.ZodString>;
|
|
25
14
|
}, z.core.$strict>;
|
|
@@ -48,7 +37,6 @@ export interface ResolvedConfig {
|
|
|
48
37
|
modelSource: "flag" | "env" | "project" | "user" | "none";
|
|
49
38
|
baseUrl: string;
|
|
50
39
|
backendUrl: string;
|
|
51
|
-
approvalMode: "read-only" | "suggest" | "auto-edit";
|
|
52
40
|
apiKey?: string;
|
|
53
41
|
}
|
|
54
42
|
/** Plan §5.1: flag > env > project > user > default. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,gBAAgB,6BAA6B,CAAC;AAC3D,eAAO,MAAM,mBAAmB,8BAA8B,CAAC;AAE/D,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM;CAK5B;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,gBAAgB,6BAA6B,CAAC;AAC3D,eAAO,MAAM,mBAAmB,8BAA8B,CAAC;AAE/D,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM;CAK5B;AAoCD,QAAA,MAAM,mBAAmB;;kBAId,CAAC;AAEZ,QAAA,MAAM,gBAAgB;;;;kBAMX,CAAC;AAUZ,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAChE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,GAAG,CAczE;AAOD,wBAAgB,eAAe,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAWxE;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,MAAM,GACb,aAAa,CAef;AAED,MAAM,WAAW,YAAY;IAC3B,4CAA4C;IAC5C,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,WAAW,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wDAAwD;AACxD,wBAAgB,aAAa,CAAC,KAAK,GAAE,YAAiB,GAAG,cAAc,CA8BtE"}
|
package/dist/config/config.js
CHANGED
|
@@ -16,22 +16,48 @@ export class ConfigError extends Error {
|
|
|
16
16
|
* silently dropping them would hide an attempt to redirect traffic.
|
|
17
17
|
*/
|
|
18
18
|
const FORBIDDEN_IN_PROJECT = ["apiKey", "baseUrl", "backendUrl"];
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* F-006 · Accepted, displayed, and never read by anything.
|
|
21
|
+
*
|
|
22
|
+
* `approvalMode` and `testCommands` were in the schema, in `config list` and
|
|
23
|
+
* in the README, and no code consulted either - so a user could set
|
|
24
|
+
* `approvalMode: "auto-edit"` and believe approval had been turned off.
|
|
25
|
+
*
|
|
26
|
+
* They are removed rather than wired up, and the reason is not effort. Both
|
|
27
|
+
* are permission-granting settings, and in a project file that means a cloned
|
|
28
|
+
* repository raising its own permission level: `.mafdet.json` asking for
|
|
29
|
+
* `auto-edit`, or listing its own `testCommands` to run. §8.3 already refuses
|
|
30
|
+
* a global approval flag, and CLI-003 already treats a repository as
|
|
31
|
+
* untrusted input - honouring these would undo both.
|
|
32
|
+
*
|
|
33
|
+
* Rejected with an explanation rather than ignored, so anyone who set one
|
|
34
|
+
* finds out it never did what they thought.
|
|
35
|
+
*/
|
|
36
|
+
const REMOVED_KEYS = {
|
|
37
|
+
approvalMode: "it was never implemented, and a config file may not raise the approval " +
|
|
38
|
+
"level - use --allow-write for this run",
|
|
39
|
+
testCommands: "it was never implemented, and a config file may not grant command " +
|
|
40
|
+
"execution - use --allow-command for this run",
|
|
41
|
+
};
|
|
20
42
|
const projectConfigSchema = z
|
|
21
43
|
.object({
|
|
22
44
|
model: z.string().min(1).optional(),
|
|
23
|
-
approvalMode: approvalMode.optional(),
|
|
24
|
-
testCommands: z.array(z.string().min(1)).optional(),
|
|
25
45
|
})
|
|
26
46
|
.strict();
|
|
27
47
|
const userConfigSchema = z
|
|
28
48
|
.object({
|
|
29
49
|
model: z.string().min(1).optional(),
|
|
30
|
-
approvalMode: approvalMode.optional(),
|
|
31
50
|
baseUrl: z.string().url().optional(),
|
|
32
51
|
backendUrl: z.string().url().optional(),
|
|
33
52
|
})
|
|
34
53
|
.strict();
|
|
54
|
+
function rejectRemoved(raw, source) {
|
|
55
|
+
for (const [key, why] of Object.entries(REMOVED_KEYS)) {
|
|
56
|
+
if (hasKey(raw, key)) {
|
|
57
|
+
throw new ConfigError(`${source} sets '${key}': ${why}.`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
35
61
|
/**
|
|
36
62
|
* Remote endpoints must be HTTPS. Plain HTTP is allowed only against the local
|
|
37
63
|
* machine, where there is no network to intercept — that exception exists so a
|
|
@@ -72,6 +98,7 @@ export function parseProjectConfig(raw, source) {
|
|
|
72
98
|
`Credentials and endpoints come from the environment or your user config only.`);
|
|
73
99
|
}
|
|
74
100
|
}
|
|
101
|
+
rejectRemoved(raw, source);
|
|
75
102
|
const parsed = projectConfigSchema.safeParse(raw);
|
|
76
103
|
if (!parsed.success) {
|
|
77
104
|
throw new ConfigError(`${source} is invalid: ${issueText(parsed.error)}`);
|
|
@@ -99,8 +126,6 @@ export function resolveConfig(input = {}) {
|
|
|
99
126
|
modelSource,
|
|
100
127
|
baseUrl,
|
|
101
128
|
backendUrl,
|
|
102
|
-
// read-only is the safe default; a caller raises it explicitly.
|
|
103
|
-
approvalMode: input.project?.approvalMode ?? input.user?.approvalMode ?? "read-only",
|
|
104
129
|
// The key is environment-only, by design (plan §5.4): a flag would put it
|
|
105
130
|
// in shell history and the process list, a file would outlive its use.
|
|
106
131
|
apiKey: trimmed(env.MAFDET_API_KEY),
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAeA,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAeA,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAmElD,wBAAsB,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyGpE"}
|
package/dist/index.js
CHANGED
|
@@ -27,8 +27,10 @@ const HELP = `mafdet - Mafdet AI command line
|
|
|
27
27
|
Usage:
|
|
28
28
|
mafdet <command> [options]
|
|
29
29
|
|
|
30
|
+
Run with no arguments in a terminal to open an interactive session.
|
|
31
|
+
|
|
30
32
|
Commands:
|
|
31
|
-
agent
|
|
33
|
+
agent Work in this directory with a model
|
|
32
34
|
chat Send a prompt to a model
|
|
33
35
|
embed Turn text into a vector
|
|
34
36
|
speech Turn text into an audio file
|
|
@@ -39,6 +41,7 @@ Commands:
|
|
|
39
41
|
account subscription Current plan and Playground allowance
|
|
40
42
|
sessions list Conversations saved on this computer
|
|
41
43
|
sessions show One saved conversation in detail
|
|
44
|
+
sessions usage What one conversation used, and what it was charged
|
|
42
45
|
sessions delete Remove a saved conversation from this computer
|
|
43
46
|
config list Show the resolved settings and where each came from
|
|
44
47
|
doctor Check key, endpoints, model, balance and workspace
|
|
@@ -49,6 +52,19 @@ Options:
|
|
|
49
52
|
-h, --help Show this help
|
|
50
53
|
-v, --version Print the version
|
|
51
54
|
|
|
55
|
+
Interactive session (bare 'mafdet'):
|
|
56
|
+
--allow-write Offer file changes, each shown as a diff and approved
|
|
57
|
+
--allow-command <prefix> Allow one command prefix, approved per run
|
|
58
|
+
--read-only Cancel an earlier --allow-write
|
|
59
|
+
--resume <id> Continue a saved conversation
|
|
60
|
+
--title <text> Name this conversation in the listing
|
|
61
|
+
--no-history Do not record this run at all
|
|
62
|
+
--no-tui Do not open the interactive session
|
|
63
|
+
|
|
64
|
+
The agent is read-only unless told otherwise, every file change is shown as a
|
|
65
|
+
diff and approved one at a time, and there is no flag that approves
|
|
66
|
+
everything in advance.
|
|
67
|
+
|
|
52
68
|
Docs: https://docs.mafdet.ai
|
|
53
69
|
`;
|
|
54
70
|
async function readStdin() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/output/format.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/output/format.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAEtD,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,YAAY,CAAC;IACrB,+DAA+D;IAC/D,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,WAAW,CAwBrE;AAED,sDAAsD;AACtD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEjD;AAED,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC;IAC1B,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,wBAAgB,WAAW,CAAC,CAAC,EAC3B,IAAI,EAAE,SAAS,CAAC,EAAE,EAClB,OAAO,EAAE,SAAS,MAAM,CAAC,CAAC,CAAC,EAAE,GAC5B,MAAM,CAoBR;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAC3B,IAAI,EAAE,SAAS,CAAC,EAAE,EAClB,OAAO,EAAE,SAAS,MAAM,CAAC,CAAC,CAAC,EAAE,GAC5B,MAAM,CAOR;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,GAC9C,MAAM,CAOR"}
|