@lazyingart/agintiflow 0.8.12 → 0.9.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.
@@ -1,6 +1,7 @@
1
1
  import OpenAI from "openai";
2
2
  import { normalizeWrapperName, wrapperStatusText } from "./tool-wrappers.js";
3
3
  import { getTaskProfile } from "./task-profiles.js";
4
+ import { listAuxiliarySkills } from "./auxiliary-tools.js";
4
5
 
5
6
  export function createClient(config) {
6
7
  if (config.provider === "mock") {
@@ -137,6 +138,18 @@ function mockCanvasToolForGoal(goal = "") {
137
138
  });
138
139
  }
139
140
 
141
+ function mockAuxiliaryToolForGoal(goal = "") {
142
+ const text = String(goal || "").toLowerCase();
143
+ if (!/\b(image|picture|photo|illustration|cover|poster|logo|generate art|draw a)\b/.test(text)) return null;
144
+ return mockToolCall("generate_image", {
145
+ prompt: `Mock image generation request: ${goal}`,
146
+ outputDir: "artifacts/images/mock-image",
147
+ outputStem: "mock-image",
148
+ aspectRatio: "1:1",
149
+ dryRun: true,
150
+ });
151
+ }
152
+
140
153
  function mockChatResponse(content, toolCalls = []) {
141
154
  return {
142
155
  choices: [
@@ -186,6 +199,11 @@ export async function createPlan(client, config, state) {
186
199
  config.allowWrapperTools
187
200
  ? `Agent wrappers are enabled. Use the selected wrapper only: ${normalizeWrapperName(config.preferredWrapper)}. Status: ${wrapperStatusText()}.`
188
201
  : "",
202
+ config.allowAuxiliaryTools
203
+ ? `Auxiliary skills are enabled: ${listAuxiliarySkills()
204
+ .map((skill) => `${skill.id} via ${skill.toolName} (${skill.available ? "key available" : `needs ${skill.keyName}`})`)
205
+ .join(", ")}. For raster image generation requests, plan to use generate_image when a GRSAI key is available; otherwise ask the user to run /auxilliary grsai or aginti login grsai.`
206
+ : "Auxiliary skills are disabled for this run.",
189
207
  `Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
190
208
  "A canvas/artifacts tunnel is available through send_to_canvas. Use it when an output should be highlighted visually, such as screenshots, image files, important markdown, diffs, or generated artifact paths. It is optional for ordinary text answers.",
191
209
  "Work like a practical coding agent: inspect when useful, edit with file tools, run safe checks when they add confidence, and keep outputs inside the workspace.",
@@ -514,6 +532,35 @@ export async function requestNextStep(client, config, messages) {
514
532
  });
515
533
  }
516
534
 
535
+ if (config.allowAuxiliaryTools) {
536
+ tools.splice(-1, 0, {
537
+ type: "function",
538
+ function: {
539
+ name: "generate_image",
540
+ description:
541
+ "Generate a raster image artifact through the optional GRS AI Nano Banana image-generation skill. Use for user requests that explicitly need a real image/photo/illustration/cover/poster/logo concept rather than SVG/code-native graphics. Saves prompt, redacted payload, manifest, and downloaded images in the workspace. If the GRSAI key is missing, ask the user to run /auxilliary grsai or aginti login grsai.",
542
+ parameters: {
543
+ type: "object",
544
+ properties: {
545
+ prompt: { type: "string", description: "Detailed image-generation prompt." },
546
+ outputDir: { type: "string", description: "Workspace-relative output directory. Defaults to artifacts/images/<timestamp>." },
547
+ outputStem: { type: "string", description: "Filename stem for downloaded images." },
548
+ aspectRatio: { type: "string", description: "Aspect ratio such as 1:1, 16:9, 2:3, or 3:2." },
549
+ imageSize: { type: "string", description: "Image size such as 1K, 2K, or 4K." },
550
+ model: { type: "string", description: "Image model. Defaults to nano-banana-2." },
551
+ referenceImages: {
552
+ type: "array",
553
+ items: { type: "string" },
554
+ description: "Optional workspace-relative image paths, HTTPS URLs, or data URLs.",
555
+ },
556
+ },
557
+ required: ["prompt"],
558
+ additionalProperties: false,
559
+ },
560
+ },
561
+ });
562
+ }
563
+
517
564
  tools.splice(-1, 0, {
518
565
  type: "function",
519
566
  function: {
@@ -590,6 +637,13 @@ export async function requestNextStep(client, config, messages) {
590
637
  }
591
638
  }
592
639
 
640
+ if (config.allowAuxiliaryTools) {
641
+ const auxiliaryTool = mockAuxiliaryToolForGoal(config.goal);
642
+ if (auxiliaryTool) {
643
+ return mockChatResponse("Mock mode will exercise an auxiliary skill tool in dry-run mode.", [auxiliaryTool]);
644
+ }
645
+ }
646
+
593
647
  const canvasTool = mockCanvasToolForGoal(config.goal);
594
648
  if (canvasTool) {
595
649
  return mockChatResponse("Mock mode will publish a canvas artifact for the UI tunnel.", [canvasTool]);
package/src/project.js CHANGED
@@ -15,6 +15,8 @@ const LOCAL_ENV_KEYS = new Set([
15
15
  "DEEPSEEK_FAST_MODEL",
16
16
  "DEEPSEEK_PRO_MODEL",
17
17
  "OPENAI_DEFAULT_MODEL",
18
+ "GRSAI",
19
+ "GRSAI_API_KEY",
18
20
  ]);
19
21
 
20
22
  export function resolveProjectRoot(input = process.cwd()) {
@@ -27,6 +29,7 @@ export function projectPaths(projectRoot = process.cwd()) {
27
29
  root,
28
30
  controlDir: path.join(root, ".aginti"),
29
31
  envPath: path.join(root, ".aginti", ".env"),
32
+ rootEnvPath: path.join(root, ".env"),
30
33
  envExamplePath: path.join(root, ".aginti", ".env.example"),
31
34
  controlReadmePath: path.join(root, ".aginti", "README.md"),
32
35
  notesDir: path.join(root, "notes"),
@@ -59,19 +62,24 @@ export function parseEnvText(text = "") {
59
62
 
60
63
  export function loadProjectEnv(projectRoot = process.cwd(), { override = false } = {}) {
61
64
  const paths = projectPaths(projectRoot);
62
- let loaded = false;
63
- try {
64
- const parsed = parseEnvText(fs.readFileSync(paths.envPath, "utf8"));
65
- for (const [key, value] of Object.entries(parsed)) {
66
- if (override || !process.env[key]) process.env[key] = value;
65
+ const envPaths = [paths.rootEnvPath, paths.envPath];
66
+ const loadedPaths = [];
67
+ for (const envPath of envPaths) {
68
+ try {
69
+ const parsed = parseEnvText(fs.readFileSync(envPath, "utf8"));
70
+ if (Object.keys(parsed).length === 0) continue;
71
+ for (const [key, value] of Object.entries(parsed)) {
72
+ if (override || !process.env[key]) process.env[key] = value;
73
+ }
74
+ loadedPaths.push(envPath);
75
+ } catch {
76
+ // Ignore missing or unreadable optional local env files.
67
77
  }
68
- loaded = true;
69
- } catch {
70
- loaded = false;
71
78
  }
72
79
  return {
73
- loaded,
80
+ loaded: loadedPaths.length > 0,
74
81
  path: paths.envPath,
82
+ paths: loadedPaths,
75
83
  };
76
84
  }
77
85
 
@@ -137,6 +145,7 @@ export async function initProject(projectRoot = process.cwd()) {
137
145
  "# Copy values into .aginti/.env. Never commit real secrets.",
138
146
  "DEEPSEEK_API_KEY=",
139
147
  "OPENAI_API_KEY=",
148
+ "GRSAI=",
140
149
  "DEEPSEEK_FAST_MODEL=deepseek-v4-flash",
141
150
  "DEEPSEEK_PRO_MODEL=deepseek-v4-pro",
142
151
  "",
@@ -153,6 +162,8 @@ export async function initProject(projectRoot = process.cwd()) {
153
162
  );
154
163
 
155
164
  const gitignore = await ensureLine(paths.gitignorePath, [
165
+ ".env",
166
+ ".env.*",
156
167
  ".aginti/.env",
157
168
  ".aginti/.env.*",
158
169
  "!.aginti/.env.example",
@@ -177,21 +188,31 @@ export function providerKeyStatus(projectRoot = process.cwd()) {
177
188
  return {
178
189
  openai: Boolean(process.env.OPENAI_API_KEY || process.env.LLM_API_KEY),
179
190
  deepseek: Boolean(process.env.DEEPSEEK_API_KEY || process.env.LLM_API_KEY),
191
+ grsai: Boolean(process.env.GRSAI || process.env.GRSAI_API_KEY),
180
192
  mock: true,
181
193
  localEnv: env.loaded,
182
194
  localEnvPath: env.path,
183
195
  envVars: {
184
196
  openai: ["OPENAI_API_KEY", "LLM_API_KEY"],
185
197
  deepseek: ["DEEPSEEK_API_KEY", "LLM_API_KEY"],
198
+ grsai: ["GRSAI", "GRSAI_API_KEY"],
186
199
  },
187
200
  };
188
201
  }
189
202
 
190
203
  export async function setProviderKey(projectRoot, provider, value) {
191
204
  const normalizedProvider = String(provider || "").toLowerCase();
192
- const keyName = normalizedProvider === "openai" ? "OPENAI_API_KEY" : "DEEPSEEK_API_KEY";
193
- if (!["deepseek", "openai"].includes(normalizedProvider)) {
194
- throw new Error("Provider must be deepseek or openai.");
205
+ const aliases = {
206
+ auxiliary: "grsai",
207
+ auxilliary: "grsai",
208
+ image: "grsai",
209
+ imagegen: "grsai",
210
+ };
211
+ const canonicalProvider = aliases[normalizedProvider] || normalizedProvider;
212
+ const keyName =
213
+ canonicalProvider === "openai" ? "OPENAI_API_KEY" : canonicalProvider === "grsai" ? "GRSAI" : "DEEPSEEK_API_KEY";
214
+ if (!["deepseek", "openai", "grsai"].includes(canonicalProvider)) {
215
+ throw new Error("Provider must be deepseek, openai, or grsai.");
195
216
  }
196
217
 
197
218
  const keyValue = String(value || "").trim();
@@ -220,7 +241,7 @@ export async function setProviderKey(projectRoot, provider, value) {
220
241
  loadProjectEnv(projectRoot, { override: true });
221
242
  return {
222
243
  ok: true,
223
- provider: normalizedProvider,
244
+ provider: canonicalProvider,
224
245
  keyName,
225
246
  path: paths.envPath,
226
247
  };
@@ -324,6 +345,7 @@ export async function doctorReport(projectRoot, packageVersion, config) {
324
345
  keys: {
325
346
  openai: keyStatus.openai,
326
347
  deepseek: keyStatus.deepseek,
348
+ grsai: keyStatus.grsai,
327
349
  mock: true,
328
350
  },
329
351
  sandbox: dockerStatus,
package/src/redaction.js CHANGED
@@ -2,7 +2,7 @@ const SECRET_PATTERNS = [
2
2
  /\bsk-[A-Za-z0-9_-]{16,}\b/g,
3
3
  /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g,
4
4
  /\bgh[pousr]_[A-Za-z0-9_]{20,}\b/g,
5
- /((?:api[_-]?key|token|secret|password|passwd|npm_token|_authToken)\s*[:=]\s*)[^\s"'`]+/gi,
5
+ /((?:api[_-]?key|token|secret|password|passwd|npm_token|_authToken|grsai)\s*[:=]\s*)[^\s"'`]+/gi,
6
6
  /(\/\/registry\.npmjs\.org\/:_authToken=)[^\s"'`]+/gi,
7
7
  /(Authorization:\s*Bearer\s+)[^\s"'`]+/gi,
8
8
  ];
@@ -55,6 +55,13 @@ export const TASK_PROFILES = {
55
55
  "For website/app tasks, create or inspect real site files, preview with workspace preview tools, add local checks when useful, and use the configured sandbox/package policy for dependencies.",
56
56
  tools: ["files", "shell", "canvas", "sandbox"],
57
57
  },
58
+ image: {
59
+ id: "image",
60
+ label: "Image generation",
61
+ prompt:
62
+ "For raster image, cover, poster, illustration, photo, and logo-concept tasks, write a clear visual prompt, use generate_image when the optional GRS AI key is available, save outputs under artifacts/images, and send the selected image to the canvas. If the key is missing, ask the user to run /auxilliary grsai or aginti login grsai.",
63
+ tools: ["auxiliary:image_generation", "files", "canvas"],
64
+ },
58
65
  aaps: {
59
66
  id: "aaps",
60
67
  label: "AAPS",
@@ -110,8 +110,10 @@ function isSensitivePath(relativePath) {
110
110
  const segments = pathSegments(relativePath);
111
111
  const basename = segments.at(-1) || "";
112
112
  const lowerBase = basename.toLowerCase();
113
+ const lowerSegments = segments.map((segment) => segment.toLowerCase());
113
114
  const lowerPath = normalizeRelative(relativePath).toLowerCase();
114
115
  if (segments.includes(".git")) return true;
116
+ if (lowerSegments.some((segment) => segment === ".env" || segment.startsWith(".env."))) return true;
115
117
  if (lowerBase === ".env" || lowerBase.startsWith(".env.")) return true;
116
118
  if (SENSITIVE_BASENAMES.has(lowerBase)) return true;
117
119
  if (SENSITIVE_EXTENSIONS.has(path.extname(lowerBase))) return true;
package/web.js CHANGED
@@ -141,6 +141,8 @@ function normalizePreferencePayload(body = {}, current = db.getPreferences()) {
141
141
  taskProfile: normalizeTaskProfile(body.taskProfile || current.taskProfile || "auto"),
142
142
  allowShellTool: typeof body.allowShellTool === "boolean" ? body.allowShellTool : Boolean(current.allowShellTool),
143
143
  allowFileTools: typeof body.allowFileTools === "boolean" ? body.allowFileTools : current.allowFileTools !== false,
144
+ allowAuxiliaryTools:
145
+ typeof body.allowAuxiliaryTools === "boolean" ? body.allowAuxiliaryTools : current.allowAuxiliaryTools !== false,
144
146
  allowWrapperTools:
145
147
  typeof body.allowWrapperTools === "boolean" ? body.allowWrapperTools : Boolean(current.allowWrapperTools),
146
148
  preferredWrapper: normalizeWrapperName(body.preferredWrapper || current.preferredWrapper || "codex"),
@@ -183,6 +185,7 @@ function publicKeyStatus(projectRoot = baseDir) {
183
185
  return {
184
186
  openai: status.openai,
185
187
  deepseek: status.deepseek,
188
+ grsai: status.grsai,
186
189
  mock: true,
187
190
  localEnv: status.localEnv,
188
191
  envVars: status.envVars,
@@ -217,6 +220,7 @@ function buildRunConfig(body, overrides = {}) {
217
220
  allowDestructive: merged.allowDestructive,
218
221
  allowShellTool: merged.allowShellTool,
219
222
  allowFileTools: merged.allowFileTools,
223
+ allowAuxiliaryTools: merged.allowAuxiliaryTools,
220
224
  allowWrapperTools: merged.allowWrapperTools,
221
225
  preferredWrapper: merged.preferredWrapper,
222
226
  wrapperTimeoutMs: merged.wrapperTimeoutMs,