@mafdet/cli 0.4.0 → 0.4.2
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 +50 -0
- package/README.md +64 -3
- package/README.zh-CN.md +53 -1
- package/dist/agent/loop.d.ts +14 -0
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +16 -3
- package/dist/agent/session.d.ts +5 -0
- package/dist/agent/session.d.ts.map +1 -1
- package/dist/agent/session.js +1 -1
- package/dist/agent/tools/read-only.d.ts.map +1 -1
- package/dist/agent/tools/read-only.js +6 -0
- package/dist/api/account.d.ts +43 -0
- package/dist/api/account.d.ts.map +1 -1
- package/dist/api/account.js +40 -0
- package/dist/commands/sessions.d.ts +5 -0
- package/dist/commands/sessions.d.ts.map +1 -0
- package/dist/commands/sessions.js +379 -0
- package/dist/config/paths.d.ts +7 -0
- package/dist/config/paths.d.ts.map +1 -1
- package/dist/config/paths.js +24 -0
- package/dist/filesystem/protected-dirs.d.ts +26 -0
- package/dist/filesystem/protected-dirs.d.ts.map +1 -0
- package/dist/filesystem/protected-dirs.js +78 -0
- package/dist/filesystem/workspace.d.ts +12 -1
- package/dist/filesystem/workspace.d.ts.map +1 -1
- package/dist/filesystem/workspace.js +24 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/security/redact.d.ts +26 -3
- package/dist/security/redact.d.ts.map +1 -1
- package/dist/security/redact.js +48 -3
- package/dist/sessions/index-file.d.ts +38 -0
- package/dist/sessions/index-file.d.ts.map +1 -0
- package/dist/sessions/index-file.js +174 -0
- package/dist/sessions/lock.d.ts +39 -0
- package/dist/sessions/lock.d.ts.map +1 -0
- package/dist/sessions/lock.js +134 -0
- package/dist/sessions/open.d.ts +45 -0
- package/dist/sessions/open.d.ts.map +1 -0
- package/dist/sessions/open.js +96 -0
- package/dist/sessions/policy.d.ts +32 -0
- package/dist/sessions/policy.d.ts.map +1 -0
- package/dist/sessions/policy.js +57 -0
- package/dist/sessions/reconcile.d.ts +49 -0
- package/dist/sessions/reconcile.d.ts.map +1 -0
- package/dist/sessions/reconcile.js +115 -0
- package/dist/sessions/recorder.d.ts +42 -0
- package/dist/sessions/recorder.d.ts.map +1 -0
- package/dist/sessions/recorder.js +147 -0
- package/dist/sessions/resume.d.ts +67 -0
- package/dist/sessions/resume.d.ts.map +1 -0
- package/dist/sessions/resume.js +100 -0
- package/dist/sessions/store.d.ts +141 -0
- package/dist/sessions/store.d.ts.map +1 -0
- package/dist/sessions/store.js +272 -0
- package/dist/sessions/usage.d.ts +35 -0
- package/dist/sessions/usage.d.ts.map +1 -0
- package/dist/sessions/usage.js +157 -0
- package/dist/tui/agent-surface.d.ts +10 -0
- package/dist/tui/agent-surface.d.ts.map +1 -1
- package/dist/tui/agent-surface.js +27 -3
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +61 -3
- package/dist/tui/input.d.ts +13 -1
- package/dist/tui/input.d.ts.map +1 -1
- package/dist/tui/input.js +9 -2
- package/dist/tui/route.d.ts.map +1 -1
- package/dist/tui/route.js +19 -2
- package/dist/tui/session-centre.d.ts +15 -0
- package/dist/tui/session-centre.d.ts.map +1 -0
- package/dist/tui/session-centre.js +59 -0
- package/dist/tui/start-agent.d.ts +16 -1
- package/dist/tui/start-agent.d.ts.map +1 -1
- package/dist/tui/start-agent.js +125 -1
- package/package.json +1 -1
package/dist/tui/start-agent.js
CHANGED
|
@@ -9,6 +9,13 @@ import { buildToolset, READ_ONLY_PROMPT, WRITE_PROMPT, } from "../commands/agent
|
|
|
9
9
|
import { buildRuntime } from "../commands/runtime.js";
|
|
10
10
|
import { Workspace } from "../filesystem/workspace.js";
|
|
11
11
|
import { ExitCode } from "../output/exit-codes.js";
|
|
12
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { sessionsDir } from "../config/paths.js";
|
|
14
|
+
import { keyDigest } from "../security/redact.js";
|
|
15
|
+
import { SessionRecorder } from "../sessions/recorder.js";
|
|
16
|
+
import { SessionWriter } from "../sessions/store.js";
|
|
17
|
+
import { NOTICE_FILE, decideRecording, firstSaveNotice, } from "../sessions/policy.js";
|
|
18
|
+
import { describeResume, openForResume } from "../sessions/open.js";
|
|
12
19
|
import { runAgentSurface } from "./agent-surface.js";
|
|
13
20
|
import { palette } from "./theme.js";
|
|
14
21
|
/**
|
|
@@ -17,6 +24,32 @@ import { palette } from "./theme.js";
|
|
|
17
24
|
* The same spellings the agent command uses, so there is one grammar for
|
|
18
25
|
* granting these and no second one to keep in step.
|
|
19
26
|
*/
|
|
27
|
+
/** `--no-history` and `--title`, read off the bare invocation. */
|
|
28
|
+
export function parseSessionOptions(argv) {
|
|
29
|
+
let title;
|
|
30
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
31
|
+
if (argv[i] !== "--title")
|
|
32
|
+
continue;
|
|
33
|
+
const value = argv[i + 1];
|
|
34
|
+
if (value !== undefined && !value.startsWith("-")) {
|
|
35
|
+
title = value;
|
|
36
|
+
i += 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
let resumeId;
|
|
40
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
41
|
+
if (argv[i] !== "--resume")
|
|
42
|
+
continue;
|
|
43
|
+
const value = argv[i + 1];
|
|
44
|
+
if (value !== undefined && !value.startsWith("-"))
|
|
45
|
+
resumeId = value;
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
noHistory: argv.includes("--no-history"),
|
|
49
|
+
...(title === undefined ? {} : { title }),
|
|
50
|
+
...(resumeId === undefined ? {} : { resumeId }),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
20
53
|
export function parsePermissions(argv) {
|
|
21
54
|
let allowWrite = false;
|
|
22
55
|
const allowedCommands = [];
|
|
@@ -58,10 +91,42 @@ export async function startAgent(options) {
|
|
|
58
91
|
? ` - commands: ${options.permissions.allowedCommands.join(", ")}`
|
|
59
92
|
: "") +
|
|
60
93
|
"\n type a goal, or /help for the session commands\n\n"));
|
|
94
|
+
const opened = options.session?.resumeId
|
|
95
|
+
? openForResume({
|
|
96
|
+
root: sessionsDir(options.commandIo.env ?? process.env),
|
|
97
|
+
sessionId: options.session.resumeId,
|
|
98
|
+
systemPrompt: mode === "read-only" ? READ_ONLY_PROMPT : WRITE_PROMPT,
|
|
99
|
+
callable: [options.model.modelId],
|
|
100
|
+
agentCapable: options.model.agentEligible
|
|
101
|
+
? [options.model.modelId]
|
|
102
|
+
: [],
|
|
103
|
+
allowedCommandsThisRun: options.permissions.allowedCommands,
|
|
104
|
+
})
|
|
105
|
+
: undefined;
|
|
106
|
+
if (opened !== undefined && opened.kind !== "opened") {
|
|
107
|
+
options.stdout.write(`\n ${bold("cannot resume that conversation")}: ${opened.kind === "missing"
|
|
108
|
+
? "no session with that id on this computer"
|
|
109
|
+
: opened.reason}\n\n`);
|
|
110
|
+
return ExitCode.General;
|
|
111
|
+
}
|
|
112
|
+
if (opened !== undefined)
|
|
113
|
+
options.stdout.write(dim(describeResume(opened)));
|
|
114
|
+
const recorder = opened !== undefined
|
|
115
|
+
? new SessionRecorder({
|
|
116
|
+
writer: opened.writer,
|
|
117
|
+
root: sessionsDir(options.commandIo.env ?? process.env),
|
|
118
|
+
apiKeyRef: keyDigest(options.apiKey),
|
|
119
|
+
onProblem: (message) => void options.stdout.write(dim(` ${message}\n`)),
|
|
120
|
+
})
|
|
121
|
+
: startRecording(options, mode);
|
|
61
122
|
await runAgentSurface({
|
|
62
123
|
io: options.io,
|
|
124
|
+
...(recorder === undefined ? {} : { recorder }),
|
|
63
125
|
capabilities: options.capabilities,
|
|
64
126
|
stdout: options.stdout,
|
|
127
|
+
...(options.getSignal === undefined
|
|
128
|
+
? {}
|
|
129
|
+
: { getSignal: options.getSignal }),
|
|
65
130
|
session: {
|
|
66
131
|
client: runtime.dataPlane,
|
|
67
132
|
model: options.model.modelId,
|
|
@@ -74,9 +139,68 @@ export async function startAgent(options) {
|
|
|
74
139
|
allowedCommands: options.permissions.allowedCommands,
|
|
75
140
|
},
|
|
76
141
|
mode,
|
|
142
|
+
// The rebuilt conversation, or nothing for a new session. runSession
|
|
143
|
+
// appends to this array in place, which is what continues the thread.
|
|
144
|
+
...(opened === undefined ? {} : { history: opened.messages }),
|
|
77
145
|
allowedCommands: options.permissions.allowedCommands,
|
|
78
|
-
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
79
146
|
},
|
|
80
147
|
});
|
|
81
148
|
return ExitCode.Ok;
|
|
82
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Open a recorder for this run, or not.
|
|
152
|
+
*
|
|
153
|
+
* Returns undefined for `--no-history`, and - importantly - reaches the
|
|
154
|
+
* filesystem only when it is going to record. A flag that still created an
|
|
155
|
+
* empty directory would be a flag the user was right not to trust.
|
|
156
|
+
*/
|
|
157
|
+
function startRecording(options, mode) {
|
|
158
|
+
const decision = decideRecording({
|
|
159
|
+
surface: "interactive",
|
|
160
|
+
noHistory: options.session?.noHistory ?? false,
|
|
161
|
+
save: false,
|
|
162
|
+
});
|
|
163
|
+
if (!decision.record)
|
|
164
|
+
return undefined;
|
|
165
|
+
const env = options.commandIo.env ?? process.env;
|
|
166
|
+
const root = sessionsDir(env);
|
|
167
|
+
try {
|
|
168
|
+
const writer = SessionWriter.create({
|
|
169
|
+
root,
|
|
170
|
+
title: options.session?.title ?? defaultTitle(options.workspace),
|
|
171
|
+
modelId: options.model.modelId,
|
|
172
|
+
workspace: options.workspace,
|
|
173
|
+
mode,
|
|
174
|
+
});
|
|
175
|
+
showNoticeOnce(root, options);
|
|
176
|
+
return new SessionRecorder({
|
|
177
|
+
writer,
|
|
178
|
+
root,
|
|
179
|
+
apiKeyRef: keyDigest(options.apiKey),
|
|
180
|
+
onProblem: (message) => void options.stdout.write(palette(options.capabilities).dim(` ${message}\n`)),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
// A store that cannot be opened is worth one line, not a dead session.
|
|
185
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
186
|
+
options.stdout.write(palette(options.capabilities).dim(` not recording this conversation: ${message}\n`));
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** The directory name is the most useful thing we know before anyone speaks. */
|
|
191
|
+
function defaultTitle(workspace) {
|
|
192
|
+
const name = workspace.split(/[\\/]/).filter(Boolean).at(-1);
|
|
193
|
+
return name === undefined || name.length === 0 ? "Conversation" : name;
|
|
194
|
+
}
|
|
195
|
+
function showNoticeOnce(root, options) {
|
|
196
|
+
const marker = `${root}/${NOTICE_FILE}`;
|
|
197
|
+
try {
|
|
198
|
+
if (existsSync(marker))
|
|
199
|
+
return;
|
|
200
|
+
options.stdout.write(firstSaveNotice(root));
|
|
201
|
+
writeFileSync(marker, `${new Date().toISOString()}\n`, { mode: 0o600 });
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Showing it twice is a much smaller problem than failing to start.
|
|
205
|
+
}
|
|
206
|
+
}
|