@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.
Files changed (75) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +64 -3
  3. package/README.zh-CN.md +53 -1
  4. package/dist/agent/loop.d.ts +14 -0
  5. package/dist/agent/loop.d.ts.map +1 -1
  6. package/dist/agent/loop.js +16 -3
  7. package/dist/agent/session.d.ts +5 -0
  8. package/dist/agent/session.d.ts.map +1 -1
  9. package/dist/agent/session.js +1 -1
  10. package/dist/agent/tools/read-only.d.ts.map +1 -1
  11. package/dist/agent/tools/read-only.js +6 -0
  12. package/dist/api/account.d.ts +43 -0
  13. package/dist/api/account.d.ts.map +1 -1
  14. package/dist/api/account.js +40 -0
  15. package/dist/commands/sessions.d.ts +5 -0
  16. package/dist/commands/sessions.d.ts.map +1 -0
  17. package/dist/commands/sessions.js +379 -0
  18. package/dist/config/paths.d.ts +7 -0
  19. package/dist/config/paths.d.ts.map +1 -1
  20. package/dist/config/paths.js +24 -0
  21. package/dist/filesystem/protected-dirs.d.ts +26 -0
  22. package/dist/filesystem/protected-dirs.d.ts.map +1 -0
  23. package/dist/filesystem/protected-dirs.js +78 -0
  24. package/dist/filesystem/workspace.d.ts +12 -1
  25. package/dist/filesystem/workspace.d.ts.map +1 -1
  26. package/dist/filesystem/workspace.js +24 -3
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +8 -0
  29. package/dist/security/redact.d.ts +26 -3
  30. package/dist/security/redact.d.ts.map +1 -1
  31. package/dist/security/redact.js +48 -3
  32. package/dist/sessions/index-file.d.ts +38 -0
  33. package/dist/sessions/index-file.d.ts.map +1 -0
  34. package/dist/sessions/index-file.js +174 -0
  35. package/dist/sessions/lock.d.ts +39 -0
  36. package/dist/sessions/lock.d.ts.map +1 -0
  37. package/dist/sessions/lock.js +134 -0
  38. package/dist/sessions/open.d.ts +45 -0
  39. package/dist/sessions/open.d.ts.map +1 -0
  40. package/dist/sessions/open.js +96 -0
  41. package/dist/sessions/policy.d.ts +32 -0
  42. package/dist/sessions/policy.d.ts.map +1 -0
  43. package/dist/sessions/policy.js +57 -0
  44. package/dist/sessions/reconcile.d.ts +49 -0
  45. package/dist/sessions/reconcile.d.ts.map +1 -0
  46. package/dist/sessions/reconcile.js +115 -0
  47. package/dist/sessions/recorder.d.ts +42 -0
  48. package/dist/sessions/recorder.d.ts.map +1 -0
  49. package/dist/sessions/recorder.js +147 -0
  50. package/dist/sessions/resume.d.ts +67 -0
  51. package/dist/sessions/resume.d.ts.map +1 -0
  52. package/dist/sessions/resume.js +100 -0
  53. package/dist/sessions/store.d.ts +141 -0
  54. package/dist/sessions/store.d.ts.map +1 -0
  55. package/dist/sessions/store.js +272 -0
  56. package/dist/sessions/usage.d.ts +35 -0
  57. package/dist/sessions/usage.d.ts.map +1 -0
  58. package/dist/sessions/usage.js +157 -0
  59. package/dist/tui/agent-surface.d.ts +10 -0
  60. package/dist/tui/agent-surface.d.ts.map +1 -1
  61. package/dist/tui/agent-surface.js +27 -3
  62. package/dist/tui/app.d.ts.map +1 -1
  63. package/dist/tui/app.js +61 -3
  64. package/dist/tui/input.d.ts +13 -1
  65. package/dist/tui/input.d.ts.map +1 -1
  66. package/dist/tui/input.js +9 -2
  67. package/dist/tui/route.d.ts.map +1 -1
  68. package/dist/tui/route.js +19 -2
  69. package/dist/tui/session-centre.d.ts +15 -0
  70. package/dist/tui/session-centre.d.ts.map +1 -0
  71. package/dist/tui/session-centre.js +59 -0
  72. package/dist/tui/start-agent.d.ts +16 -1
  73. package/dist/tui/start-agent.d.ts.map +1 -1
  74. package/dist/tui/start-agent.js +125 -1
  75. package/package.json +1 -1
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mafdet/cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Mafdet AI command line — model calls, account queries, and a permissioned local agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",