@lazyingart/agintiflow 0.6.0 → 0.7.0

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,16 @@ aginti --list-profiles
46
46
  aginti --sandbox-status --sandbox-mode docker-readonly
47
47
  ```
48
48
 
49
+ Start an interactive Codex-style CLI chat from any project folder:
50
+
51
+ ```bash
52
+ aginti
53
+ # or explicitly:
54
+ aginti chat
55
+ ```
56
+
57
+ Inside chat, type normal requests such as `write a small Python CLI app with tests`. Use `/help` for commands, `/docker on` to switch to Docker workspace mode with approved package installs, `/sessions` to list project runs, and `/resume <session-id>` to continue work.
58
+
49
59
  Launch the local web UI from an installed package:
50
60
 
51
61
  ```bash
@@ -71,8 +81,8 @@ aginti doctor --capabilities
71
81
  aginti sessions list
72
82
  aginti sessions show <session-id>
73
83
  aginti resume <session-id> "continue with a short follow-up"
74
- aginti --profile code --provider mock --routing manual "Create notes/hello.md"
75
- aginti --allow-shell --sandbox-mode docker-workspace --approve-package-installs "set up and test this project"
84
+ aginti --profile code "write a small Python CLI app with tests"
85
+ aginti --sandbox-mode docker-workspace --approve-package-installs "set up this project and run the tests"
76
86
  ```
77
87
 
78
88
  Run from a source checkout:
@@ -324,7 +334,7 @@ Package policy values:
324
334
  | `prompt` | Return a clear approval-required error; the UI can switch to approved. |
325
335
  | `allow` | Permit package/setup commands. Docker workspace mode also allows broader shell/network commands while keeping secrets and npm publishing blocked. |
326
336
 
327
- Toolchain commands such as `python3 plot.py`, `latexmk -pdf paper.tex`, and `pdflatex -interaction=nonstopmode -halt-on-error paper.tex` are allowlisted only when the shell tool is enabled. In Docker mode they require `docker-workspace` because they write outputs back to `/workspace`. File and canvas tools accept both normal relative paths and Docker virtual paths like `/workspace/report.pdf`, while other absolute host paths remain blocked.
337
+ Toolchain commands such as `python3 plot.py`, `latexmk -pdf paper.tex`, and `pdflatex -interaction=nonstopmode -halt-on-error paper.tex` are allowlisted only when the shell tool is enabled. In Docker mode the project folder is mounted as `/workspace`; any file written to `/workspace/report.pdf` appears on the host as `<your-project>/report.pdf`. CLI runs print both the host workspace and the Docker mapping before execution. File and canvas tools accept both normal relative paths and Docker virtual paths like `/workspace/report.pdf`, while other absolute host paths remain blocked.
328
338
 
329
339
  Safe preflight endpoints:
330
340
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
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",
@@ -41,6 +41,7 @@
41
41
  "scripts/install-docker-ubuntu.sh",
42
42
  "scripts/setup-agent-toolchain-docker.sh",
43
43
  "scripts/real-deepseek-capabilities.js",
44
+ "scripts/smoke-cli-chat.js",
44
45
  "scripts/smoke-coding-tools.js",
45
46
  "scripts/smoke-capabilities.js",
46
47
  "scripts/smoke-toolchain-docker.js",
@@ -59,10 +60,11 @@
59
60
  "check": "node --check run.js && node --check web.js && node --check bin/aginti-cli.js && node --check src/*.js",
60
61
  "setup:toolchain-docker": "scripts/setup-agent-toolchain-docker.sh",
61
62
  "smoke:coding-tools": "node scripts/smoke-coding-tools.js",
63
+ "smoke:cli-chat": "node scripts/smoke-cli-chat.js",
62
64
  "smoke:toolchain-docker": "node scripts/smoke-toolchain-docker.js",
63
65
  "smoke:web-api": "node scripts/smoke-web-api.js",
64
66
  "real:deepseek": "node scripts/real-deepseek-capabilities.js",
65
- "test": "npm run check && npm run smoke:web-api && npm run smoke:coding-tools && npm run smoke:capabilities",
67
+ "test": "npm run check && npm run smoke:web-api && npm run smoke:coding-tools && npm run smoke:capabilities && npm run smoke:cli-chat",
66
68
  "pack:dry-run": "npm pack --dry-run",
67
69
  "smoke:capabilities": "node scripts/smoke-capabilities.js"
68
70
  },
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "node:child_process";
3
+ import fs from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9
+ const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-cli-chat-"));
10
+ const binPath = path.join(repoRoot, "bin/aginti-cli.js");
11
+
12
+ function runChat(inputText) {
13
+ return new Promise((resolve, reject) => {
14
+ const child = spawn(
15
+ process.execPath,
16
+ [binPath, "chat", "--provider", "mock", "--routing", "manual", "--profile", "code"],
17
+ {
18
+ cwd: tempRoot,
19
+ stdio: ["pipe", "pipe", "pipe"],
20
+ env: {
21
+ ...process.env,
22
+ AGINTIFLOW_RUNTIME_DIR: "",
23
+ },
24
+ }
25
+ );
26
+
27
+ let stdout = "";
28
+ let stderr = "";
29
+ const timer = setTimeout(() => {
30
+ child.kill("SIGTERM");
31
+ reject(new Error("interactive chat smoke timed out"));
32
+ }, 25000);
33
+
34
+ child.stdout.on("data", (chunk) => {
35
+ stdout += String(chunk);
36
+ });
37
+ child.stderr.on("data", (chunk) => {
38
+ stderr += String(chunk);
39
+ });
40
+ child.on("error", (error) => {
41
+ clearTimeout(timer);
42
+ reject(error);
43
+ });
44
+ child.on("close", (code) => {
45
+ clearTimeout(timer);
46
+ if (code === 0) resolve({ stdout, stderr });
47
+ else reject(new Error(`interactive chat exited ${code}\n${stdout}\n${stderr}`));
48
+ });
49
+
50
+ child.stdin.end(inputText);
51
+ });
52
+ }
53
+
54
+ try {
55
+ const result = await runChat("Create notes/interactive.md with a short CLI chat smoke message\n/exit\n");
56
+ const written = await fs.readFile(path.join(tempRoot, "notes/interactive.md"), "utf8");
57
+ if (!written.includes("Created by AgInTiFlow mock mode.")) {
58
+ throw new Error("interactive chat did not create the expected file");
59
+ }
60
+ if (!result.stdout.includes("Interactive agent chat")) {
61
+ throw new Error("interactive chat did not print its banner");
62
+ }
63
+ console.log(
64
+ JSON.stringify(
65
+ {
66
+ ok: true,
67
+ projectRoot: tempRoot,
68
+ checks: ["interactive-chat", "mock-file-write"],
69
+ },
70
+ null,
71
+ 2
72
+ )
73
+ );
74
+ } finally {
75
+ await fs.rm(tempRoot, { recursive: true, force: true });
76
+ }
@@ -791,6 +791,16 @@ export async function runAgent(config) {
791
791
  console.log(`Provider: ${config.provider}`);
792
792
  console.log(`Model: ${config.model}`);
793
793
  console.log(`Routing: ${config.routingMode} (${config.routeReason})`);
794
+ console.log(`Workspace: ${config.commandCwd}`);
795
+ console.log(`Sessions: ${config.sessionsDir}`);
796
+ if (config.useDockerSandbox) {
797
+ console.log(
798
+ `Docker: image=${config.dockerSandboxImage} mode=${config.sandboxMode} packagePolicy=${config.packageInstallPolicy}`
799
+ );
800
+ console.log(`Docker workspace: /workspace -> ${config.commandCwd}`);
801
+ } else if (config.allowShellTool) {
802
+ console.log(`Shell: host policy=${config.packageInstallPolicy}`);
803
+ }
794
804
  if (state.plan) {
795
805
  console.log("\nPlan:");
796
806
  console.log(state.plan);
package/src/cli.js CHANGED
@@ -4,6 +4,7 @@ import { listAgentWrappers } from "./tool-wrappers.js";
4
4
  import { getModelPresets } from "./model-routing.js";
5
5
  import { getDockerSandboxStatus, runDockerPreflight } from "./docker-sandbox.js";
6
6
  import { buildCapabilityReport, printCapabilityReport } from "./capabilities.js";
7
+ import { startInteractiveCli } from "./interactive-cli.js";
7
8
  import {
8
9
  doctorReport,
9
10
  initProject,
@@ -52,6 +53,7 @@ export function parseArgs(argv) {
52
53
  sandboxStatus: false,
53
54
  sandboxPreflight: false,
54
55
  web: false,
56
+ interactive: false,
55
57
  port: "",
56
58
  host: "",
57
59
  listProfiles: false,
@@ -64,6 +66,10 @@ export function parseArgs(argv) {
64
66
  result.web = true;
65
67
  continue;
66
68
  }
69
+ if (arg === "chat" || arg === "interactive" || arg === "--chat" || arg === "--interactive") {
70
+ result.interactive = true;
71
+ continue;
72
+ }
67
73
  if (arg === "--port") {
68
74
  result.port = readOption(argv, i);
69
75
  i += 1;
@@ -133,6 +139,10 @@ export function parseArgs(argv) {
133
139
  result.allowShellTool = true;
134
140
  continue;
135
141
  }
142
+ if (arg === "--no-shell") {
143
+ result.allowShellTool = false;
144
+ continue;
145
+ }
136
146
  if (arg === "--allow-destructive" || arg === "--trusted-host-shell") {
137
147
  result.allowDestructive = true;
138
148
  continue;
@@ -197,6 +207,12 @@ export function parseArgs(argv) {
197
207
  return result;
198
208
  }
199
209
 
210
+ function printUsage() {
211
+ console.log(
212
+ 'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti [--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"'
213
+ );
214
+ }
215
+
200
216
  function printRoutes() {
201
217
  const presets = getModelPresets();
202
218
  for (const preset of Object.values(presets)) {
@@ -312,6 +328,11 @@ async function handleSessionsCommand(argv) {
312
328
  }
313
329
 
314
330
  export async function main(argv = process.argv.slice(2)) {
331
+ if (argv[0] === "--help" || argv[0] === "help" || argv[0] === "-h") {
332
+ printUsage();
333
+ return;
334
+ }
335
+
315
336
  if (argv[0] === "--version" || argv[0] === "version" || argv[0] === "-v") {
316
337
  console.log(packageJson.version);
317
338
  return;
@@ -393,13 +414,21 @@ export async function main(argv = process.argv.slice(2)) {
393
414
  console.error('Usage: aginti resume <session-id> "new prompt"');
394
415
  process.exit(1);
395
416
  }
396
- const config = loadConfig({ ...parseArgs([prompt]), resume: sessionId, goal: prompt }, { packageDir });
417
+ const config = loadConfig(
418
+ { ...parseArgs([prompt]), resume: sessionId, goal: prompt, allowShellTool: true, allowFileTools: true },
419
+ { packageDir }
420
+ );
397
421
  await runAgent(config);
398
422
  return;
399
423
  }
400
424
 
401
425
  const args = parseArgs(argv);
402
426
 
427
+ if (args.interactive || (!args.goal && !args.resume && process.stdin.isTTY)) {
428
+ await startInteractiveCli(args, { packageDir, packageVersion: packageJson.version });
429
+ return;
430
+ }
431
+
403
432
  if (args.web) {
404
433
  if (args.port) process.env.PORT = String(args.port);
405
434
  if (args.host) process.env.HOST = String(args.host);
@@ -433,12 +462,17 @@ export async function main(argv = process.argv.slice(2)) {
433
462
  }
434
463
 
435
464
  if (!args.goal && !args.resume) {
436
- console.error(
437
- 'Usage: aginti-cli web [--port 3210] OR aginti-cli [--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] [--allow-destructive] [--allow-file-tools|--no-file-tools] [--allow-wrappers --wrapper codex] [--sandbox-status|--sandbox-preflight] "your task"'
438
- );
465
+ printUsage();
439
466
  process.exit(1);
440
467
  }
441
468
 
442
- const config = loadConfig(args, { packageDir });
469
+ const config = loadConfig(
470
+ {
471
+ ...args,
472
+ allowShellTool: args.allowShellTool ?? true,
473
+ allowFileTools: args.allowFileTools ?? true,
474
+ },
475
+ { packageDir }
476
+ );
443
477
  await runAgent(config);
444
478
  }
@@ -0,0 +1,225 @@
1
+ import readline from "node:readline/promises";
2
+ import { stdin as input, stdout as output } from "node:process";
3
+ import { runAgent } from "./agent-runner.js";
4
+ import { loadConfig } from "./config.js";
5
+ import { initProject, listProjectSessions } from "./project.js";
6
+ import { normalizePackageInstallPolicy, normalizeSandboxMode } from "./command-policy.js";
7
+ import { normalizeTaskProfile } from "./task-profiles.js";
8
+
9
+ function printHelp() {
10
+ console.log(
11
+ [
12
+ "Commands:",
13
+ " /help Show this help.",
14
+ " /status Show active route, workspace, sandbox, and session.",
15
+ " /new Start a fresh session on the next message.",
16
+ " /resume <session-id> Continue a saved session.",
17
+ " /sessions List recent sessions in this project.",
18
+ " /profile <name> Set task profile, e.g. code, website, latex, maintenance.",
19
+ " /routing <mode> Set routing: smart, fast, complex, manual.",
20
+ " /provider <name> Set provider: deepseek, openai, mock.",
21
+ " /model <name> Set an explicit model, or /model auto.",
22
+ " /docker on Use docker-workspace with approved package installs.",
23
+ " /docker off Use host shell policy.",
24
+ " /installs block|prompt|allow",
25
+ " /cwd <path> Change command workspace.",
26
+ " /exit Quit.",
27
+ "",
28
+ "Type a normal request to run the agent. Example: write a Python CLI app with tests",
29
+ ].join("\n")
30
+ );
31
+ }
32
+
33
+ function printStatus(state) {
34
+ console.log(`project=${process.cwd()}`);
35
+ console.log(`cwd=${state.commandCwd || process.cwd()}`);
36
+ console.log(`session=${state.sessionId || "new"}`);
37
+ console.log(`provider=${state.provider || "auto"} routing=${state.routingMode} model=${state.model || "auto"}`);
38
+ console.log(`profile=${state.taskProfile} maxSteps=${state.maxSteps}`);
39
+ console.log(
40
+ `shell=${state.allowShellTool} files=${state.allowFileTools} sandbox=${state.sandboxMode} installs=${state.packageInstallPolicy}`
41
+ );
42
+ if (state.sandboxMode !== "host") {
43
+ console.log(`dockerWorkspace=/workspace -> ${state.commandCwd || process.cwd()}`);
44
+ }
45
+ }
46
+
47
+ function createState(args = {}) {
48
+ return {
49
+ provider: args.provider || "",
50
+ model: args.model || "",
51
+ routingMode: args.routingMode || "smart",
52
+ commandCwd: args.commandCwd || process.cwd(),
53
+ sandboxMode: normalizeSandboxMode(args.sandboxMode || "host"),
54
+ packageInstallPolicy: normalizePackageInstallPolicy(args.packageInstallPolicy || "prompt"),
55
+ allowShellTool: args.allowShellTool ?? true,
56
+ allowFileTools: args.allowFileTools ?? true,
57
+ allowWrapperTools: args.allowWrapperTools ?? false,
58
+ allowDestructive: args.allowDestructive ?? false,
59
+ preferredWrapper: args.preferredWrapper || "codex",
60
+ taskProfile: normalizeTaskProfile(args.taskProfile || "auto"),
61
+ headless: args.headless ?? false,
62
+ maxSteps: Number.isFinite(args.maxSteps) && args.maxSteps > 0 ? args.maxSteps : 15,
63
+ sessionId: args.resume || "",
64
+ };
65
+ }
66
+
67
+ async function handleCommand(line, state, packageDir) {
68
+ const [command, ...rest] = line.slice(1).trim().split(/\s+/);
69
+ const value = rest.join(" ").trim();
70
+
71
+ if (!command || command === "help" || command === "?") {
72
+ printHelp();
73
+ return true;
74
+ }
75
+ if (command === "exit" || command === "quit" || command === "q") return false;
76
+ if (command === "status") {
77
+ printStatus(state);
78
+ return true;
79
+ }
80
+ if (command === "new") {
81
+ state.sessionId = "";
82
+ console.log("Next message will start a new session.");
83
+ return true;
84
+ }
85
+ if (command === "resume") {
86
+ if (!value) console.log("Usage: /resume <session-id>");
87
+ else {
88
+ state.sessionId = value;
89
+ console.log(`Resuming ${state.sessionId}`);
90
+ }
91
+ return true;
92
+ }
93
+ if (command === "sessions") {
94
+ const sessions = await listProjectSessions(process.cwd(), 20);
95
+ if (sessions.length === 0) console.log("No project-local sessions found.");
96
+ else {
97
+ for (const session of sessions) {
98
+ const goal = session.goal ? ` ${session.goal.slice(0, 80)}` : "";
99
+ console.log(`${session.sessionId} ${session.provider}/${session.model} ${session.updatedAt}${goal}`);
100
+ }
101
+ }
102
+ return true;
103
+ }
104
+ if (command === "profile") {
105
+ state.taskProfile = normalizeTaskProfile(value || "auto");
106
+ console.log(`profile=${state.taskProfile}`);
107
+ return true;
108
+ }
109
+ if (command === "routing") {
110
+ state.routingMode = value || "smart";
111
+ console.log(`routing=${state.routingMode}`);
112
+ return true;
113
+ }
114
+ if (command === "provider") {
115
+ state.provider = value === "auto" ? "" : value;
116
+ console.log(`provider=${state.provider || "auto"}`);
117
+ return true;
118
+ }
119
+ if (command === "model") {
120
+ state.model = value === "auto" ? "" : value;
121
+ console.log(`model=${state.model || "auto"}`);
122
+ return true;
123
+ }
124
+ if (command === "installs") {
125
+ state.packageInstallPolicy = normalizePackageInstallPolicy(value || "prompt");
126
+ console.log(`installs=${state.packageInstallPolicy}`);
127
+ return true;
128
+ }
129
+ if (command === "docker") {
130
+ if (value === "on") {
131
+ state.sandboxMode = "docker-workspace";
132
+ state.packageInstallPolicy = "allow";
133
+ console.log(`docker=on /workspace -> ${state.commandCwd || process.cwd()} installs=allow`);
134
+ } else if (value === "off") {
135
+ state.sandboxMode = "host";
136
+ state.packageInstallPolicy = "prompt";
137
+ console.log("docker=off sandbox=host installs=prompt");
138
+ } else {
139
+ console.log("Usage: /docker on OR /docker off");
140
+ }
141
+ return true;
142
+ }
143
+ if (command === "cwd") {
144
+ state.commandCwd = value || process.cwd();
145
+ console.log(`cwd=${state.commandCwd}`);
146
+ return true;
147
+ }
148
+ if (command === "init") {
149
+ const result = await initProject(process.cwd());
150
+ console.log(`initialized project=${result.projectRoot}`);
151
+ return true;
152
+ }
153
+ if (command === "web") {
154
+ const port = value || "3220";
155
+ console.log(`Run in another terminal: aginti web --port ${port}`);
156
+ return true;
157
+ }
158
+
159
+ console.log(`Unknown command: /${command}. Use /help.`);
160
+ return true;
161
+ }
162
+
163
+ async function runPrompt(prompt, state, packageDir) {
164
+ const config = loadConfig(
165
+ {
166
+ provider: state.provider,
167
+ model: state.model,
168
+ routingMode: state.routingMode,
169
+ commandCwd: state.commandCwd,
170
+ sandboxMode: state.sandboxMode,
171
+ packageInstallPolicy: state.packageInstallPolicy,
172
+ allowShellTool: state.allowShellTool,
173
+ allowFileTools: state.allowFileTools,
174
+ allowWrapperTools: state.allowWrapperTools,
175
+ allowDestructive: state.allowDestructive,
176
+ preferredWrapper: state.preferredWrapper,
177
+ taskProfile: state.taskProfile,
178
+ maxSteps: state.maxSteps,
179
+ headless: state.headless,
180
+ resume: state.sessionId,
181
+ goal: prompt,
182
+ },
183
+ { packageDir, baseDir: process.cwd() }
184
+ );
185
+
186
+ const result = await runAgent(config);
187
+ state.sessionId = result.sessionId || state.sessionId;
188
+ }
189
+
190
+ export async function startInteractiveCli(args = {}, { packageDir, packageVersion } = {}) {
191
+ const state = createState(args);
192
+ const rl = readline.createInterface({ input, output, terminal: Boolean(input.isTTY && output.isTTY) });
193
+
194
+ console.log(`AgInTiFlow ${packageVersion || ""}`.trim());
195
+ console.log(`Project: ${process.cwd()}`);
196
+ console.log("Interactive agent chat. Type /help for commands, /exit to quit.");
197
+ printStatus(state);
198
+
199
+ try {
200
+ while (true) {
201
+ let answer = "";
202
+ try {
203
+ answer = await rl.question("\naginti> ");
204
+ } catch (error) {
205
+ if (error?.code === "ERR_USE_AFTER_CLOSE") break;
206
+ throw error;
207
+ }
208
+ const line = answer.trim();
209
+ if (!line) continue;
210
+ if (line.startsWith("/")) {
211
+ const keepGoing = await handleCommand(line, state, packageDir);
212
+ if (!keepGoing) break;
213
+ continue;
214
+ }
215
+
216
+ try {
217
+ await runPrompt(line, state, packageDir);
218
+ } catch (error) {
219
+ console.error(`error: ${error.message}`);
220
+ }
221
+ }
222
+ } finally {
223
+ rl.close();
224
+ }
225
+ }