@lazyingart/agintiflow 0.20.38 → 0.20.39

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
@@ -50,7 +50,7 @@ aginti --list-profiles
50
50
  aginti --sandbox-status
51
51
  ```
52
52
 
53
- When AgInTiFlow is installed globally from npm, normal `aginti`, `aginti chat`, `aginti resume`, and `aginti web` startup checks npm for a newer `@lazyingart/agintiflow` release at a throttled interval. If a newer version is found, it runs `npm install -g @lazyingart/agintiflow@latest` and restarts the CLI once. Source checkouts and non-TTY automation skip this behavior. Force a check with `aginti update`, skip one run with `--no-auto-update`, or disable it with `AGINTIFLOW_NO_AUTO_UPDATE=1`.
53
+ When AgInTiFlow is installed globally from npm, normal `aginti`, `aginti chat`, `aginti resume`, and `aginti web` startup checks npm for a newer `@lazyingart/agintiflow` release at a throttled interval. If a newer version is found in an interactive terminal, AgInTiFlow shows an Up/Down selector with `Update now`, `Skip this time`, and `Skip this version`; updating runs `npm install -g @lazyingart/agintiflow@latest` and restarts the CLI once. Source checkouts and non-TTY automation skip this behavior. Force a check with `aginti update`, skip one run with `--no-auto-update`, or disable it with `AGINTIFLOW_NO_AUTO_UPDATE=1`.
54
54
 
55
55
  On first interactive use, if no main model key is detected, `aginti` opens an auth wizard. Use Up/Down to choose DeepSeek, OpenAI, Qwen, or Venice, paste the key, and press Enter to save it to the project-local ignored file `.aginti/.env` with `0600` permissions. The wizard points to DeepSeek keys at `https://platform.deepseek.com/api_keys`, OpenAI keys at `https://platform.openai.com/api-keys`, and Venice at `https://venice.ai`. It then offers the optional auxiliary image key; press Esc to skip. You can rerun it even when keys already exist:
56
56
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.38",
3
+ "version": "0.20.39",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a web-first coding agent and CLI with DeepSeek routing, sandboxed tools, model providers, canvas artifacts, and optional wrappers.",
6
6
  "license": "Apache-2.0",
@@ -6,6 +6,9 @@ import {
6
6
  maybeAutoUpdate,
7
7
  shouldAutoUpdateCommand,
8
8
  } from "../src/auto-update.js";
9
+ import fs from "node:fs/promises";
10
+ import os from "node:os";
11
+ import path from "node:path";
9
12
 
10
13
  function assert(condition, message) {
11
14
  if (!condition) throw new Error(message);
@@ -47,5 +50,47 @@ const skipped = await maybeAutoUpdate({
47
50
  });
48
51
  assert(skipped.skipped === "source-checkout", "source checkout update guard failed");
49
52
 
50
- console.log("auto-update smoke ok");
53
+ const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "agintiflow-auto-update-"));
54
+ const previousHome = process.env.AGINTIFLOW_HOME;
55
+ process.env.AGINTIFLOW_HOME = tempHome;
56
+ await fs.mkdir(tempHome, { recursive: true });
57
+ await fs.writeFile(
58
+ path.join(tempHome, "update-check.json"),
59
+ `${JSON.stringify({ checkedAt: Date.now(), latest: "0.20.99" })}\n`,
60
+ "utf8"
61
+ );
62
+ const writes = [];
63
+ const fakeStdout = {
64
+ isTTY: true,
65
+ write(value) {
66
+ writes.push(String(value));
67
+ },
68
+ };
69
+ const skipVersion = await maybeAutoUpdate({
70
+ argv: [],
71
+ packageDir: scopedGlobalPath,
72
+ packageName: "@lazyingart/agintiflow",
73
+ packageVersion: "0.20.38",
74
+ restart: false,
75
+ stdout: fakeStdout,
76
+ selectUpdateAction: async () => "skip-version",
77
+ });
78
+ assert(skipVersion.skipped === "skip-version", "skip-version selector choice was not honored");
79
+ const cacheAfterSkip = JSON.parse(await fs.readFile(path.join(tempHome, "update-check.json"), "utf8"));
80
+ assert(cacheAfterSkip.skippedVersion === "0.20.99", "skip-version did not persist skipped version");
81
+ const repeatedSkip = await maybeAutoUpdate({
82
+ argv: [],
83
+ packageDir: scopedGlobalPath,
84
+ packageName: "@lazyingart/agintiflow",
85
+ packageVersion: "0.20.38",
86
+ restart: false,
87
+ stdout: fakeStdout,
88
+ selectUpdateAction: async () => {
89
+ throw new Error("selector should not be called after skip-version");
90
+ },
91
+ });
92
+ assert(repeatedSkip.skipped === "skipped-version", "cached skipped version was not respected");
93
+ if (previousHome === undefined) delete process.env.AGINTIFLOW_HOME;
94
+ else process.env.AGINTIFLOW_HOME = previousHome;
51
95
 
96
+ console.log("auto-update smoke ok");
@@ -2,12 +2,30 @@ import { execFile, spawn } from "node:child_process";
2
2
  import fs from "node:fs/promises";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
+ import readline from "node:readline";
5
6
  import { promisify } from "node:util";
6
7
 
7
8
  const execFileAsync = promisify(execFile);
8
9
  const DEFAULT_PACKAGE_NAME = "@lazyingart/agintiflow";
9
10
  const DEFAULT_CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000;
10
11
  const DEFAULT_FAILURE_RETRY_MS = 6 * 60 * 60 * 1000;
12
+ const UPDATE_CHOICES = [
13
+ {
14
+ id: "update",
15
+ label: "Update now",
16
+ description: "Install latest globally, then restart AgInTiFlow.",
17
+ },
18
+ {
19
+ id: "skip-once",
20
+ label: "Skip this time",
21
+ description: "Continue with the current version for this run.",
22
+ },
23
+ {
24
+ id: "skip-version",
25
+ label: "Skip this version",
26
+ description: "Do not ask again until a newer release appears.",
27
+ },
28
+ ];
11
29
  const START_COMMANDS = new Set(["", "chat", "interactive", "resume", "web", "--web", "--chat", "--interactive"]);
12
30
  const SKIP_COMMANDS = new Set([
13
31
  "auth",
@@ -115,12 +133,16 @@ function nowMs() {
115
133
  }
116
134
 
117
135
  function checkIntervalMs() {
118
- const value = Number(process.env.AGINTIFLOW_AUTO_UPDATE_INTERVAL_MS || "");
136
+ const raw = process.env.AGINTIFLOW_AUTO_UPDATE_INTERVAL_MS;
137
+ if (raw === undefined || raw === "") return DEFAULT_CHECK_INTERVAL_MS;
138
+ const value = Number(raw);
119
139
  return Number.isFinite(value) && value >= 0 ? value : DEFAULT_CHECK_INTERVAL_MS;
120
140
  }
121
141
 
122
142
  function failureRetryMs() {
123
- const value = Number(process.env.AGINTIFLOW_AUTO_UPDATE_FAILURE_RETRY_MS || "");
143
+ const raw = process.env.AGINTIFLOW_AUTO_UPDATE_FAILURE_RETRY_MS;
144
+ if (raw === undefined || raw === "") return DEFAULT_FAILURE_RETRY_MS;
145
+ const value = Number(raw);
124
146
  return Number.isFinite(value) && value >= 0 ? value : DEFAULT_FAILURE_RETRY_MS;
125
147
  }
126
148
 
@@ -158,6 +180,91 @@ function shouldSkipFailedInstall(cache, currentMs, force) {
158
180
  return failedAt > 0 && currentMs - failedAt < failureRetryMs();
159
181
  }
160
182
 
183
+ function renderUpdateSelector(output, { current, latest, packageName, selectedIndex, renderedLines }) {
184
+ const rows = [
185
+ `AgInTiFlow update available: ${current} -> ${latest}`,
186
+ `Package: ${packageName}`,
187
+ "Use Up/Down to choose, Enter to confirm, Esc to skip.",
188
+ "",
189
+ ...UPDATE_CHOICES.map((choice, index) => {
190
+ const cursor = index === selectedIndex ? ">" : " ";
191
+ return `${cursor} ${choice.label.padEnd(17)} ${choice.description}`;
192
+ }),
193
+ ];
194
+ if (renderedLines > 0) output.write(`\x1b[${renderedLines}A\x1b[J`);
195
+ output.write(`${rows.join("\n")}\n`);
196
+ return rows.length;
197
+ }
198
+
199
+ export async function promptUpdateChoice({
200
+ current = "",
201
+ latest = "",
202
+ packageName = DEFAULT_PACKAGE_NAME,
203
+ input = process.stdin,
204
+ output = process.stdout,
205
+ } = {}) {
206
+ if (!input.isTTY || !output.isTTY || typeof input.setRawMode !== "function") return "skip-once";
207
+
208
+ readline.emitKeypressEvents(input);
209
+ const wasRaw = input.isRaw;
210
+ let renderedLines = 0;
211
+ let selectedIndex = 0;
212
+ let resolved = false;
213
+
214
+ return await new Promise((resolve) => {
215
+ function finish(choice) {
216
+ if (resolved) return;
217
+ resolved = true;
218
+ input.off("keypress", onKeypress);
219
+ input.setRawMode(Boolean(wasRaw));
220
+ if (!wasRaw) input.pause();
221
+ output.write("\n");
222
+ resolve(choice);
223
+ }
224
+
225
+ function render() {
226
+ renderedLines = renderUpdateSelector(output, {
227
+ current,
228
+ latest,
229
+ packageName,
230
+ selectedIndex,
231
+ renderedLines,
232
+ });
233
+ }
234
+
235
+ function onKeypress(_chunk, key = {}) {
236
+ if (key.ctrl && key.name === "c") {
237
+ input.setRawMode(Boolean(wasRaw));
238
+ if (!wasRaw) input.pause();
239
+ output.write("\n");
240
+ process.exit(130);
241
+ }
242
+ if (key.name === "up" || key.name === "left") {
243
+ selectedIndex = (selectedIndex - 1 + UPDATE_CHOICES.length) % UPDATE_CHOICES.length;
244
+ render();
245
+ return;
246
+ }
247
+ if (key.name === "down" || key.name === "right" || key.name === "tab") {
248
+ selectedIndex = (selectedIndex + 1) % UPDATE_CHOICES.length;
249
+ render();
250
+ return;
251
+ }
252
+ if (key.name === "return" || key.name === "enter") {
253
+ finish(UPDATE_CHOICES[selectedIndex].id);
254
+ return;
255
+ }
256
+ if (key.name === "escape") {
257
+ finish("skip-once");
258
+ }
259
+ }
260
+
261
+ input.setRawMode(true);
262
+ input.resume();
263
+ render();
264
+ input.on("keypress", onKeypress);
265
+ });
266
+ }
267
+
161
268
  export async function maybeAutoUpdate({
162
269
  argv = [],
163
270
  force = false,
@@ -166,6 +273,7 @@ export async function maybeAutoUpdate({
166
273
  packageName = DEFAULT_PACKAGE_NAME,
167
274
  packageVersion = "",
168
275
  restart = false,
276
+ selectUpdateAction = promptUpdateChoice,
169
277
  stdout = process.stdout,
170
278
  stderr = process.stderr,
171
279
  } = {}) {
@@ -214,6 +322,35 @@ export async function maybeAutoUpdate({
214
322
  return { checked: true, latest, current: packageVersion, updated: false, skipped: "recent-install-failure" };
215
323
  }
216
324
 
325
+ if (!manual && !force) {
326
+ if (cache.skippedVersion === latest) {
327
+ return { checked: true, latest, current: packageVersion, updated: false, skipped: "skipped-version" };
328
+ }
329
+ const choice = await selectUpdateAction({
330
+ current: packageVersion,
331
+ latest,
332
+ packageName,
333
+ input: process.stdin,
334
+ output: stdout,
335
+ });
336
+ if (choice === "skip-version") {
337
+ await writeCache({
338
+ ...cache,
339
+ checkedAt: currentMs,
340
+ latest,
341
+ skippedVersion: latest,
342
+ skippedAt: currentMs,
343
+ packageName,
344
+ });
345
+ stdout.write(`Skipped AgInTiFlow ${latest}. Run \`aginti update\` to install it later.\n`);
346
+ return { checked: true, latest, current: packageVersion, updated: false, skipped: "skip-version" };
347
+ }
348
+ if (choice !== "update") {
349
+ stdout.write(`Skipped update for this run. Run \`aginti update\` to install ${latest} later.\n`);
350
+ return { checked: true, latest, current: packageVersion, updated: false, skipped: "skip-once" };
351
+ }
352
+ }
353
+
217
354
  stdout.write(`AgInTiFlow update available: ${packageVersion} -> ${latest}\n`);
218
355
  stdout.write(`Running: npm install -g ${packageName}@latest\n`);
219
356
  const install = await installLatest(packageName);