@lazyingart/agintiflow 0.8.5 → 0.8.7

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/README.md CHANGED
@@ -46,6 +46,14 @@ aginti --list-profiles
46
46
  aginti --sandbox-status
47
47
  ```
48
48
 
49
+ On first interactive use, if no DeepSeek key is detected, `aginti` asks you to paste it and saves it to the project-local ignored file `.aginti/.env` with `0600` permissions. You can also set it explicitly:
50
+
51
+ ```bash
52
+ aginti login deepseek
53
+ # or non-interactively:
54
+ printf '%s' "$DEEPSEEK_API_KEY" | aginti keys set deepseek --stdin
55
+ ```
56
+
49
57
  Start an interactive Codex-style CLI chat from any project folder:
50
58
 
51
59
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a resumable Playwright website-control agent with OpenAI-compatible tool calling.",
6
6
  "license": "Apache-2.0",
@@ -329,6 +329,16 @@ function createObservers(config) {
329
329
  };
330
330
  }
331
331
 
332
+ function emitConsole(config, value = "", options = {}) {
333
+ if (typeof config.onConsole === "function") {
334
+ config.onConsole(String(value), options);
335
+ return;
336
+ }
337
+
338
+ if (options.error) console.error(value);
339
+ else console.log(value);
340
+ }
341
+
332
342
  function createBrowserState() {
333
343
  return {
334
344
  browser: null,
@@ -1033,25 +1043,27 @@ export async function runAgent(config) {
1033
1043
  startUrl: config.startUrl,
1034
1044
  });
1035
1045
 
1036
- console.log(`Session: ${sessionId}`);
1037
- console.log(`Provider: ${config.provider}`);
1038
- console.log(`Model: ${config.model}`);
1039
- console.log(`Routing: ${config.routingMode} (${config.routeReason})`);
1040
- console.log(`Workspace: ${config.commandCwd}`);
1041
- console.log(`Sessions: ${config.sessionsDir}`);
1046
+ emitConsole(config, `Session: ${sessionId}`, { kind: "meta" });
1047
+ emitConsole(config, `Provider: ${config.provider}`, { kind: "meta" });
1048
+ emitConsole(config, `Model: ${config.model}`, { kind: "meta" });
1049
+ emitConsole(config, `Routing: ${config.routingMode} (${config.routeReason})`, { kind: "meta" });
1050
+ emitConsole(config, `Workspace: ${config.commandCwd}`, { kind: "meta" });
1051
+ emitConsole(config, `Sessions: ${config.sessionsDir}`, { kind: "meta" });
1042
1052
  if (config.useDockerSandbox) {
1043
- console.log(
1044
- `Docker: image=${config.dockerSandboxImage} mode=${config.sandboxMode} packagePolicy=${config.packageInstallPolicy}`
1053
+ emitConsole(
1054
+ config,
1055
+ `Docker: image=${config.dockerSandboxImage} mode=${config.sandboxMode} packagePolicy=${config.packageInstallPolicy}`,
1056
+ { kind: "meta" }
1045
1057
  );
1046
- console.log(`Docker workspace: /workspace -> ${config.commandCwd}`);
1047
- console.log("Docker env: /aginti-env persistent toolchain; /aginti-cache persistent caches");
1058
+ emitConsole(config, `Docker workspace: /workspace -> ${config.commandCwd}`, { kind: "meta" });
1059
+ emitConsole(config, "Docker env: /aginti-env persistent toolchain; /aginti-cache persistent caches", { kind: "meta" });
1048
1060
  } else if (config.allowShellTool) {
1049
- console.log(`Shell: host policy=${config.packageInstallPolicy}`);
1061
+ emitConsole(config, `Shell: host policy=${config.packageInstallPolicy}`, { kind: "meta" });
1050
1062
  }
1051
1063
  if (state.plan) {
1052
- console.log("\nPlan:");
1053
- console.log(state.plan);
1054
- console.log("");
1064
+ emitConsole(config, "\nPlan:", { kind: "heading" });
1065
+ emitConsole(config, state.plan, { kind: "plan", markdown: true });
1066
+ emitConsole(config, "", { kind: "meta" });
1055
1067
  }
1056
1068
 
1057
1069
  for (let step = state.stepsCompleted + 1; step <= config.maxSteps; step += 1) {
@@ -1139,7 +1151,7 @@ export async function runAgent(config) {
1139
1151
  result: fallback,
1140
1152
  sessionId,
1141
1153
  });
1142
- console.log(fallback);
1154
+ emitConsole(config, fallback, { kind: "assistant", markdown: true });
1143
1155
  state.stepsCompleted = step;
1144
1156
  state.updatedAt = new Date().toISOString();
1145
1157
  await store.saveState(state);
@@ -1209,7 +1221,7 @@ export async function runAgent(config) {
1209
1221
  result: toolResult.result,
1210
1222
  sessionId,
1211
1223
  });
1212
- console.log(toolResult.result);
1224
+ emitConsole(config, toolResult.result, { kind: "assistant", markdown: true });
1213
1225
  return {
1214
1226
  sessionId,
1215
1227
  result: toolResult.result,
@@ -1232,7 +1244,7 @@ export async function runAgent(config) {
1232
1244
  reason: "max_steps_reached",
1233
1245
  sessionId,
1234
1246
  });
1235
- console.error(`Stopped after ${config.maxSteps} steps without finish().`);
1247
+ emitConsole(config, `Stopped after ${config.maxSteps} steps without finish().`, { kind: "error", error: true });
1236
1248
  return {
1237
1249
  sessionId,
1238
1250
  result: "",
@@ -0,0 +1,62 @@
1
+ import readline from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import { Writable } from "node:stream";
4
+ import { providerKeyStatus, setProviderKey } from "./project.js";
5
+
6
+ class MutedWritable extends Writable {
7
+ constructor(target) {
8
+ super();
9
+ this.target = target;
10
+ this.muted = false;
11
+ }
12
+
13
+ _write(chunk, encoding, callback) {
14
+ if (!this.muted) this.target.write(chunk, encoding);
15
+ callback();
16
+ }
17
+ }
18
+
19
+ export async function promptHidden(promptText) {
20
+ if (!input.isTTY || !output.isTTY) return "";
21
+
22
+ const mutedOutput = new MutedWritable(output);
23
+ const rl = readline.createInterface({
24
+ input,
25
+ output: mutedOutput,
26
+ terminal: true,
27
+ });
28
+
29
+ try {
30
+ output.write(promptText);
31
+ mutedOutput.muted = true;
32
+ const value = await rl.question("");
33
+ output.write("\n");
34
+ return String(value || "").trim();
35
+ } finally {
36
+ mutedOutput.muted = false;
37
+ rl.close();
38
+ }
39
+ }
40
+
41
+ export function shouldPromptForDeepSeek(args = {}, projectRoot = process.cwd()) {
42
+ const provider = String(args.provider || "").toLowerCase();
43
+ if (provider === "mock" || provider === "openai") return false;
44
+ if (process.env.AGINTIFLOW_NO_AUTH_PROMPT === "1") return false;
45
+ if (!input.isTTY || !output.isTTY) return false;
46
+ return !providerKeyStatus(projectRoot).deepseek;
47
+ }
48
+
49
+ export async function promptAndSaveDeepSeekKey(projectRoot = process.cwd(), options = {}) {
50
+ const key = await promptHidden(
51
+ options.promptText || "DeepSeek API key not found. Paste DEEPSEEK_API_KEY to save locally, or press Enter to skip: "
52
+ );
53
+ if (!key) return { saved: false, skipped: true };
54
+
55
+ const result = await setProviderKey(projectRoot, "deepseek", key);
56
+ return {
57
+ saved: true,
58
+ provider: result.provider,
59
+ keyName: result.keyName,
60
+ path: result.path,
61
+ };
62
+ }
package/src/cli.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  showProjectSession,
16
16
  } from "./project.js";
17
17
  import { listTaskProfiles } from "./task-profiles.js";
18
+ import { promptAndSaveDeepSeekKey, promptHidden, shouldPromptForDeepSeek } from "./auth-onboarding.js";
18
19
  import fs from "node:fs/promises";
19
20
  import path from "node:path";
20
21
  import { fileURLToPath } from "node:url";
@@ -219,7 +220,7 @@ export function parseArgs(argv) {
219
220
 
220
221
  function printUsage() {
221
222
  console.log(
222
- 'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|mock] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-destructive] [--allow-file-tools|--no-file-tools] [--allow-wrappers --wrapper codex] [--sandbox-status|--sandbox-preflight] "your task"'
223
+ 'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti login deepseek OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--latex] [--routing smart|fast|complex|manual] [--provider deepseek|openai|mock] [--sandbox-mode host|docker-readonly|docker-workspace] [--package-install-policy block|prompt|allow] [--approve-package-installs] [--allow-shell|--no-shell] [--allow-destructive] [--allow-file-tools|--no-file-tools] [--allow-wrappers --wrapper codex] [--sandbox-status|--sandbox-preflight] "your task"'
223
224
  );
224
225
  }
225
226
 
@@ -298,6 +299,21 @@ async function readStdin() {
298
299
  return input.trim();
299
300
  }
300
301
 
302
+ async function ensureDeepSeekKeyForOneShot(args) {
303
+ if (!shouldPromptForDeepSeek(args, process.cwd())) return true;
304
+ console.log("DeepSeek API key is not configured for this project.");
305
+ console.log("Paste it once to save it in `.aginti/.env` with 0600 permissions, or press Enter to cancel.");
306
+ const result = await promptAndSaveDeepSeekKey(process.cwd(), {
307
+ promptText: "DeepSeek API key: ",
308
+ });
309
+ if (result.saved) {
310
+ console.log(`saved ${result.keyName} to project-local ignored env`);
311
+ return true;
312
+ }
313
+ console.error("No DeepSeek key saved. Run `aginti login deepseek` later, or use `--provider mock` for local tests.");
314
+ return false;
315
+ }
316
+
301
317
  async function handleKeyCommand(argv) {
302
318
  const [verb = "status", provider = ""] = argv;
303
319
  if (verb === "status") {
@@ -313,17 +329,17 @@ async function handleKeyCommand(argv) {
313
329
 
314
330
  if (verb === "set") {
315
331
  const target = provider || "deepseek";
316
- if (!argv.includes("--stdin")) {
317
- console.error(`Usage: aginti keys set ${target} --stdin`);
332
+ const key = argv.includes("--stdin") ? await readStdin() : await promptHidden(`${target === "openai" ? "OpenAI" : "DeepSeek"} API key: `);
333
+ if (!key) {
334
+ console.error("No key saved.");
318
335
  process.exit(1);
319
336
  }
320
- const key = await readStdin();
321
337
  const result = await setProviderKey(process.cwd(), target, key);
322
338
  console.log(`saved ${result.provider} key to project-local ignored env (${result.keyName})`);
323
339
  return;
324
340
  }
325
341
 
326
- console.error("Usage: aginti keys status OR aginti keys set deepseek --stdin");
342
+ console.error("Usage: aginti keys status OR aginti keys set deepseek [--stdin]");
327
343
  process.exit(1);
328
344
  }
329
345
 
@@ -442,11 +458,13 @@ export async function main(argv = process.argv.slice(2)) {
442
458
 
443
459
  if (argv[0] === "login") {
444
460
  const provider = argv[1] || "deepseek";
445
- if (!argv.includes("--stdin") && process.stdin.isTTY) {
446
- console.error(`Usage: printf '%s' '<key>' | aginti login ${provider} --stdin`);
461
+ const key = argv.includes("--stdin") || !process.stdin.isTTY
462
+ ? await readStdin()
463
+ : await promptHidden(`${provider === "openai" ? "OpenAI" : "DeepSeek"} API key: `);
464
+ if (!key) {
465
+ console.error("No key saved.");
447
466
  process.exit(1);
448
467
  }
449
- const key = await readStdin();
450
468
  const result = await setProviderKey(process.cwd(), provider, key);
451
469
  console.log(`saved ${result.provider} key to project-local ignored env (${result.keyName})`);
452
470
  return;
@@ -478,7 +496,9 @@ export async function main(argv = process.argv.slice(2)) {
478
496
  });
479
497
  return;
480
498
  }
481
- const config = loadConfig(agentDefaults({ ...parseArgs([prompt]), resume: sessionId, goal: prompt }), { packageDir });
499
+ const resumeArgs = agentDefaults({ ...parseArgs([prompt]), resume: sessionId, goal: prompt });
500
+ if (!(await ensureDeepSeekKeyForOneShot(resumeArgs))) process.exit(1);
501
+ const config = loadConfig(resumeArgs, { packageDir });
482
502
  await runAgent(config);
483
503
  return;
484
504
  }
@@ -527,6 +547,8 @@ export async function main(argv = process.argv.slice(2)) {
527
547
  process.exit(1);
528
548
  }
529
549
 
530
- const config = loadConfig(agentDefaults(args), { packageDir });
550
+ const finalArgs = agentDefaults(args);
551
+ if (!(await ensureDeepSeekKeyForOneShot(finalArgs))) process.exit(1);
552
+ const config = loadConfig(finalArgs, { packageDir });
531
553
  await runAgent(config);
532
554
  }
@@ -6,9 +6,98 @@ import { loadConfig } from "./config.js";
6
6
  import { initProject, listProjectSessions } from "./project.js";
7
7
  import { normalizePackageInstallPolicy, normalizeSandboxMode } from "./command-policy.js";
8
8
  import { normalizeTaskProfile } from "./task-profiles.js";
9
+ import { promptAndSaveDeepSeekKey, shouldPromptForDeepSeek } from "./auth-onboarding.js";
10
+
11
+ const useColor = Boolean(input.isTTY && output.isTTY && !process.env.NO_COLOR);
12
+ const ansi = {
13
+ reset: "\x1b[0m",
14
+ bold: "\x1b[1m",
15
+ dim: "\x1b[2m",
16
+ cyan: "\x1b[36m",
17
+ green: "\x1b[32m",
18
+ yellow: "\x1b[33m",
19
+ red: "\x1b[31m",
20
+ userBg: "\x1b[48;5;24m\x1b[38;5;231m",
21
+ agentBg: "\x1b[48;5;29m\x1b[38;5;231m",
22
+ systemBg: "\x1b[48;5;236m\x1b[38;5;245m",
23
+ };
24
+
25
+ function color(value, ...codes) {
26
+ if (!useColor || codes.length === 0) return String(value);
27
+ return `${codes.join("")}${value}${ansi.reset}`;
28
+ }
29
+
30
+ function label(name, bgCode) {
31
+ return color(` ${name} `, bgCode, ansi.bold);
32
+ }
33
+
34
+ function userPrompt() {
35
+ return `\n${label("user>", ansi.userBg)} `;
36
+ }
37
+
38
+ function stripMarkdown(text) {
39
+ const lines = String(text || "").split(/\r?\n/);
40
+ let inFence = false;
41
+ const rendered = [];
42
+
43
+ for (const rawLine of lines) {
44
+ let line = rawLine;
45
+ if (/^\s*```/.test(line)) {
46
+ inFence = !inFence;
47
+ if (inFence) rendered.push(color("code", ansi.dim));
48
+ continue;
49
+ }
50
+
51
+ if (!inFence) {
52
+ if (/^\s*[-*_]{3,}\s*$/.test(line)) {
53
+ rendered.push("");
54
+ continue;
55
+ }
56
+ line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
57
+ line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1 ($2)");
58
+ line = line.replace(/\*\*([^*]+)\*\*/g, (_, value) => color(value, ansi.bold));
59
+ line = line.replace(/__([^_]+)__/g, (_, value) => color(value, ansi.bold));
60
+ line = line.replace(/(^|[^\w])\*([^*\n]+)\*/g, "$1$2");
61
+ line = line.replace(/(^|[^\w])_([^_\n]+)_/g, "$1$2");
62
+ line = line.replace(/`([^`]+)`/g, (_, value) => color(value, ansi.yellow));
63
+ line = line.replace(/^(\s*)[-*+]\s+/, "$1- ");
64
+ line = line.replace(/^\s*>\s?/, " ");
65
+ }
66
+
67
+ rendered.push(line);
68
+ }
69
+
70
+ return rendered.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
71
+ }
72
+
73
+ function printWrapped(prefix, text) {
74
+ const rendered = stripMarkdown(text);
75
+ const lines = rendered.split("\n");
76
+ const gutter = " ".repeat(useColor ? 9 : prefix.length);
77
+ console.log(`${prefix}${lines[0] || ""}`);
78
+ for (const line of lines.slice(1)) {
79
+ console.log(`${gutter}${line}`);
80
+ }
81
+ }
82
+
83
+ function printAgentMessage(text) {
84
+ printWrapped(`${label("aginti>", ansi.agentBg)} `, text);
85
+ }
86
+
87
+ function printSystemLine(text) {
88
+ if (!String(text || "").trim()) {
89
+ console.log("");
90
+ return;
91
+ }
92
+ console.log(`${label("state", ansi.systemBg)} ${color(text, ansi.dim)}`);
93
+ }
94
+
95
+ function printHeading(text) {
96
+ console.log(color(stripMarkdown(text), ansi.bold, ansi.cyan));
97
+ }
9
98
 
10
99
  function printHelp() {
11
- console.log(
100
+ printAgentMessage(
12
101
  [
13
102
  "Commands:",
14
103
  " /help Show this help.",
@@ -34,18 +123,18 @@ function printHelp() {
34
123
  }
35
124
 
36
125
  function printStatus(state) {
37
- console.log(`project=${process.cwd()}`);
38
- console.log(`cwd=${state.commandCwd || process.cwd()}`);
39
- console.log(`session=${state.sessionId || "new"}`);
40
- console.log(`status=${state.status || "idle"}${state.activeGoal ? ` workingOn=${state.activeGoal}` : ""}`);
41
- if (state.lastEvent) console.log(`last=${state.lastEvent}`);
42
- console.log(`provider=${state.provider || "auto"} routing=${state.routingMode} model=${state.model || "auto"}`);
43
- console.log(`profile=${state.taskProfile} maxSteps=${state.maxSteps}`);
44
- console.log(
126
+ printSystemLine(`project=${process.cwd()}`);
127
+ printSystemLine(`cwd=${state.commandCwd || process.cwd()}`);
128
+ printSystemLine(`session=${state.sessionId || "new"}`);
129
+ printSystemLine(`status=${state.status || "idle"}${state.activeGoal ? ` workingOn=${state.activeGoal}` : ""}`);
130
+ if (state.lastEvent) printSystemLine(`last=${state.lastEvent}`);
131
+ printSystemLine(`provider=${state.provider || "auto"} routing=${state.routingMode} model=${state.model || "auto"}`);
132
+ printSystemLine(`profile=${state.taskProfile} maxSteps=${state.maxSteps}`);
133
+ printSystemLine(
45
134
  `shell=${state.allowShellTool} files=${state.allowFileTools} sandbox=${state.sandboxMode} installs=${state.packageInstallPolicy}`
46
135
  );
47
136
  if (state.sandboxMode !== "host") {
48
- console.log(`dockerWorkspace=/workspace -> ${state.commandCwd || process.cwd()}`);
137
+ printSystemLine(`dockerWorkspace=/workspace -> ${state.commandCwd || process.cwd()}`);
49
138
  }
50
139
  }
51
140
 
@@ -65,7 +154,7 @@ async function latestSession() {
65
154
 
66
155
  function printStatusEvent(state, label, details = "") {
67
156
  state.lastEvent = details ? `${label}: ${details}` : label;
68
- console.log(`status=${state.status || "running"} ${state.lastEvent}`);
157
+ printSystemLine(`status=${state.status || "running"} ${state.lastEvent}`);
69
158
  }
70
159
 
71
160
  function attachRunInterrupts(controller) {
@@ -80,7 +169,7 @@ function attachRunInterrupts(controller) {
80
169
  if (!isEscape && !isCtrlC) return;
81
170
  if (controller.signal.aborted) return;
82
171
  const reason = isEscape ? "escape" : "ctrl-c";
83
- console.log(`\nstatus=stopping reason=${reason}`);
172
+ printSystemLine(`status=stopping reason=${reason}`);
84
173
  controller.abort(new Error(`Interrupted by ${reason}.`));
85
174
  };
86
175
  input.on("keypress", handler);
@@ -96,19 +185,21 @@ async function printResumeHint(state) {
96
185
  const sessionId = state.sessionId || "";
97
186
  console.log("");
98
187
  if (sessionId) {
99
- console.log("Interrupted. Session saved.");
100
- console.log(`Resume: aginti resume ${sessionId}`);
101
- console.log(`One-shot: aginti resume ${sessionId} "continue"`);
188
+ printAgentMessage(["Interrupted. Session saved.", `Resume: aginti resume ${sessionId}`, `One-shot: aginti resume ${sessionId} "continue"`].join("\n"));
102
189
  } else {
103
- console.log("Interrupted. No active session yet.");
190
+ printAgentMessage("Interrupted. No active session yet.");
104
191
  const sessions = await listProjectSessions(process.cwd(), 5).catch(() => []);
105
192
  if (sessions.length > 0) {
106
- console.log("Recent sessions:");
107
- for (const session of sessions) console.log(` ${formatSessionLine(session)}`);
108
- console.log(`Resume latest: aginti resume ${sessions[0].sessionId}`);
109
- console.log("List all: aginti sessions list");
193
+ printAgentMessage(
194
+ [
195
+ "Recent sessions:",
196
+ ...sessions.map((session) => ` ${formatSessionLine(session)}`),
197
+ `Resume latest: aginti resume ${sessions[0].sessionId}`,
198
+ "List all: aginti sessions list",
199
+ ].join("\n")
200
+ );
110
201
  } else {
111
- console.log("Restart: aginti");
202
+ printAgentMessage("Restart: aginti");
112
203
  }
113
204
  }
114
205
  }
@@ -133,6 +224,29 @@ function createState(args = {}) {
133
224
  };
134
225
  }
135
226
 
227
+ async function maybeOnboardDeepSeekKey(state) {
228
+ if (!shouldPromptForDeepSeek(state, process.cwd())) return;
229
+
230
+ printAgentMessage(
231
+ [
232
+ "DeepSeek API key is not configured for this project.",
233
+ "Paste it once to save it in `.aginti/.env` with 0600 permissions, or press Enter to continue in mock mode.",
234
+ ].join("\n")
235
+ );
236
+ const result = await promptAndSaveDeepSeekKey(process.cwd(), {
237
+ promptText: "DeepSeek API key: ",
238
+ });
239
+ if (result.saved) {
240
+ printAgentMessage(`Saved ${result.keyName} to project-local ignored env.`);
241
+ return;
242
+ }
243
+
244
+ state.provider = "mock";
245
+ state.routingMode = "manual";
246
+ state.model = "mock-agent";
247
+ printAgentMessage("No key saved. Continuing in local mock mode. Use `/provider deepseek` after running `aginti login deepseek`.");
248
+ }
249
+
136
250
  async function handleCommand(line, state, packageDir) {
137
251
  const [command, ...rest] = line.slice(1).trim().split(/\s+/);
138
252
  const value = rest.join(" ").trim();
@@ -148,71 +262,75 @@ async function handleCommand(line, state, packageDir) {
148
262
  }
149
263
  if (command === "new") {
150
264
  state.sessionId = "";
151
- console.log("Next message will start a new session.");
265
+ printAgentMessage("Next message will start a new session.");
152
266
  return true;
153
267
  }
154
268
  if (command === "resume") {
155
269
  if (!value || value === "latest") {
156
270
  const latest = await latestSession();
157
271
  if (!latest) {
158
- console.log("No project-local sessions found. Use /new or type a request to start one.");
272
+ printAgentMessage("No project-local sessions found. Use /new or type a request to start one.");
159
273
  } else {
160
274
  state.sessionId = latest.sessionId;
161
- console.log(`Resuming latest ${formatSessionLine(latest)}`);
275
+ printAgentMessage(`Resuming latest ${formatSessionLine(latest)}`);
162
276
  }
163
277
  } else {
164
278
  state.sessionId = value;
165
- console.log(`Resuming ${state.sessionId}`);
279
+ printAgentMessage(`Resuming ${state.sessionId}`);
166
280
  }
167
281
  return true;
168
282
  }
169
283
  if (command === "sessions") {
170
284
  const sessions = await listProjectSessions(process.cwd(), 20);
171
- if (sessions.length === 0) console.log("No project-local sessions found.");
285
+ if (sessions.length === 0) printAgentMessage("No project-local sessions found.");
172
286
  else {
173
- for (const session of sessions) {
174
- const goal = session.goal ? ` ${session.goal.slice(0, 80)}` : "";
175
- console.log(`${session.sessionId} ${session.provider}/${session.model} ${session.updatedAt}${goal}`);
176
- }
287
+ printAgentMessage(
288
+ sessions
289
+ .map((session) => {
290
+ const goal = session.goal ? ` ${session.goal.slice(0, 80)}` : "";
291
+ return `${session.sessionId} ${session.provider}/${session.model} ${session.updatedAt}${goal}`;
292
+ })
293
+ .join("\n")
294
+ );
177
295
  }
178
296
  return true;
179
297
  }
180
298
  if (command === "profile") {
181
299
  state.taskProfile = normalizeTaskProfile(value || "auto");
182
- console.log(`profile=${state.taskProfile}`);
300
+ printSystemLine(`profile=${state.taskProfile}`);
183
301
  return true;
184
302
  }
185
303
  if (command === "routing") {
186
304
  state.routingMode = value || "smart";
187
- console.log(`routing=${state.routingMode}`);
305
+ printSystemLine(`routing=${state.routingMode}`);
188
306
  return true;
189
307
  }
190
308
  if (command === "provider") {
191
309
  state.provider = value === "auto" ? "" : value;
192
- console.log(`provider=${state.provider || "auto"}`);
310
+ printSystemLine(`provider=${state.provider || "auto"}`);
193
311
  return true;
194
312
  }
195
313
  if (command === "model") {
196
314
  state.model = value === "auto" ? "" : value;
197
- console.log(`model=${state.model || "auto"}`);
315
+ printSystemLine(`model=${state.model || "auto"}`);
198
316
  return true;
199
317
  }
200
318
  if (command === "installs") {
201
319
  state.packageInstallPolicy = normalizePackageInstallPolicy(value || "prompt");
202
- console.log(`installs=${state.packageInstallPolicy}`);
320
+ printSystemLine(`installs=${state.packageInstallPolicy}`);
203
321
  return true;
204
322
  }
205
323
  if (command === "docker") {
206
324
  if (value === "on") {
207
325
  state.sandboxMode = "docker-workspace";
208
326
  state.packageInstallPolicy = "allow";
209
- console.log(`docker=on /workspace -> ${state.commandCwd || process.cwd()} installs=allow`);
327
+ printSystemLine(`docker=on /workspace -> ${state.commandCwd || process.cwd()} installs=allow`);
210
328
  } else if (value === "off") {
211
329
  state.sandboxMode = "host";
212
330
  state.packageInstallPolicy = "prompt";
213
- console.log("docker=off sandbox=host installs=prompt");
331
+ printSystemLine("docker=off sandbox=host installs=prompt");
214
332
  } else {
215
- console.log("Usage: /docker on OR /docker off");
333
+ printAgentMessage("Usage: /docker on OR /docker off");
216
334
  }
217
335
  return true;
218
336
  }
@@ -222,32 +340,32 @@ async function handleCommand(line, state, packageDir) {
222
340
  state.sandboxMode = "docker-workspace";
223
341
  state.packageInstallPolicy = "allow";
224
342
  state.maxSteps = Math.max(state.maxSteps, 30);
225
- console.log("latex=on profile=latex sandbox=docker-workspace installs=allow maxSteps=30");
343
+ printSystemLine("latex=on profile=latex sandbox=docker-workspace installs=allow maxSteps=30");
226
344
  } else if (value === "off") {
227
345
  state.taskProfile = "auto";
228
- console.log("latex=off profile=auto");
346
+ printSystemLine("latex=off profile=auto");
229
347
  } else {
230
- console.log("Usage: /latex on OR /latex off");
348
+ printAgentMessage("Usage: /latex on OR /latex off");
231
349
  }
232
350
  return true;
233
351
  }
234
352
  if (command === "cwd") {
235
353
  state.commandCwd = value || process.cwd();
236
- console.log(`cwd=${state.commandCwd}`);
354
+ printSystemLine(`cwd=${state.commandCwd}`);
237
355
  return true;
238
356
  }
239
357
  if (command === "init") {
240
358
  const result = await initProject(process.cwd());
241
- console.log(`initialized project=${result.projectRoot}`);
359
+ printAgentMessage(`initialized project=${result.projectRoot}`);
242
360
  return true;
243
361
  }
244
362
  if (command === "web") {
245
363
  const port = value || "3220";
246
- console.log(`Run in another terminal: aginti web --port ${port}`);
364
+ printAgentMessage(`Run in another terminal: aginti web --port ${port}`);
247
365
  return true;
248
366
  }
249
367
 
250
- console.log(`Unknown command: /${command}. Use /help.`);
368
+ printAgentMessage(`Unknown command: /${command}. Use /help.`);
251
369
  return true;
252
370
  }
253
371
 
@@ -279,8 +397,8 @@ async function runPrompt(prompt, state, packageDir) {
279
397
  state.status = "running";
280
398
  state.activeGoal = prompt.replace(/\s+/g, " ").slice(0, 120);
281
399
  state.lastEvent = "";
282
- console.log(`session=${state.sessionId}`);
283
- console.log(`status=running workingOn=${state.activeGoal}`);
400
+ printSystemLine(`session=${state.sessionId}`);
401
+ printSystemLine(`status=running workingOn=${state.activeGoal}`);
284
402
 
285
403
  const detachInterrupts = attachRunInterrupts(controller);
286
404
  let result;
@@ -288,6 +406,19 @@ async function runPrompt(prompt, state, packageDir) {
288
406
  result = await runAgent({
289
407
  ...config,
290
408
  abortSignal: controller.signal,
409
+ onConsole: (text, options = {}) => {
410
+ if (options.kind === "assistant") {
411
+ printAgentMessage(text);
412
+ } else if (options.kind === "plan") {
413
+ printWrapped(`${label("plan", ansi.systemBg)} `, text);
414
+ } else if (options.kind === "heading") {
415
+ printHeading(text);
416
+ } else if (options.error) {
417
+ console.error(`${label("error", ansi.systemBg)} ${stripMarkdown(text)}`);
418
+ } else {
419
+ printSystemLine(text);
420
+ }
421
+ },
291
422
  onEvent: (type, data = {}) => {
292
423
  if (type === "plan.created") {
293
424
  printStatusEvent(state, "planned");
@@ -316,7 +447,7 @@ async function runPrompt(prompt, state, packageDir) {
316
447
  state.sessionId = result.sessionId || state.sessionId;
317
448
  state.status = result.stopped ? "stopped" : "idle";
318
449
  state.activeGoal = "";
319
- console.log(`status=${state.status} session=${state.sessionId}`);
450
+ printSystemLine(`status=${state.status} session=${state.sessionId}`);
320
451
  if (result.stopped && result.reason === "user_interrupt") {
321
452
  await printResumeHint(state);
322
453
  }
@@ -324,18 +455,19 @@ async function runPrompt(prompt, state, packageDir) {
324
455
 
325
456
  export async function startInteractiveCli(args = {}, { packageDir, packageVersion } = {}) {
326
457
  const state = createState(args);
458
+ await maybeOnboardDeepSeekKey(state);
327
459
  const rl = readline.createInterface({ input, output, terminal: Boolean(input.isTTY && output.isTTY) });
328
460
 
329
- console.log(`AgInTiFlow ${packageVersion || ""}`.trim());
330
- console.log(`Project: ${process.cwd()}`);
331
- console.log("Interactive agent chat. Type /help for commands, /exit to quit.");
461
+ console.log(color(` AgInTiFlow ${packageVersion || ""} `, ansi.agentBg, ansi.bold).trimEnd());
462
+ printSystemLine(`Project: ${process.cwd()}`);
463
+ printAgentMessage("Interactive agent chat. Type /help for commands, /exit to quit.");
332
464
  printStatus(state);
333
465
 
334
466
  try {
335
467
  while (true) {
336
468
  let answer = "";
337
469
  try {
338
- answer = await rl.question("\naginti> ");
470
+ answer = await rl.question(userPrompt());
339
471
  } catch (error) {
340
472
  if (error?.code === "ERR_USE_AFTER_CLOSE") break;
341
473
  if (isAbortError(error)) {
@@ -359,7 +491,7 @@ export async function startInteractiveCli(args = {}, { packageDir, packageVersio
359
491
  await printResumeHint(state);
360
492
  break;
361
493
  }
362
- console.error(`error: ${error.message}`);
494
+ console.error(`${label("error", ansi.systemBg)} ${error.message}`);
363
495
  }
364
496
  }
365
497
  } finally {
package/src/project.js CHANGED
@@ -199,6 +199,11 @@ export async function setProviderKey(projectRoot, provider, value) {
199
199
 
200
200
  const paths = projectPaths(projectRoot);
201
201
  await fsp.mkdir(paths.controlDir, { recursive: true });
202
+ await ensureLine(paths.gitignorePath, [
203
+ ".aginti/.env",
204
+ ".aginti/.env.*",
205
+ "!.aginti/.env.example",
206
+ ]);
202
207
  let parsed = {};
203
208
  try {
204
209
  parsed = parseEnvText(await fsp.readFile(paths.envPath, "utf8"));