@lazyingart/agintiflow 0.8.6 → 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.6",
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",
@@ -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,6 +6,7 @@ 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";
9
10
 
10
11
  const useColor = Boolean(input.isTTY && output.isTTY && !process.env.NO_COLOR);
11
12
  const ansi = {
@@ -223,6 +224,29 @@ function createState(args = {}) {
223
224
  };
224
225
  }
225
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
+
226
250
  async function handleCommand(line, state, packageDir) {
227
251
  const [command, ...rest] = line.slice(1).trim().split(/\s+/);
228
252
  const value = rest.join(" ").trim();
@@ -431,6 +455,7 @@ async function runPrompt(prompt, state, packageDir) {
431
455
 
432
456
  export async function startInteractiveCli(args = {}, { packageDir, packageVersion } = {}) {
433
457
  const state = createState(args);
458
+ await maybeOnboardDeepSeekKey(state);
434
459
  const rl = readline.createInterface({ input, output, terminal: Boolean(input.isTTY && output.isTTY) });
435
460
 
436
461
  console.log(color(` AgInTiFlow ${packageVersion || ""} `, ansi.agentBg, ansi.bold).trimEnd());
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"));