@lazyingart/agintiflow 0.13.0 → 0.14.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
@@ -83,6 +83,16 @@ For current docs, install errors, package/toolchain setup, and source discovery,
83
83
 
84
84
  For raster image work, AgInTiFlow has an optional `image_generation` skill backed by the `generate_image` tool and a local `GRSAI` key. The skill tells DeepSeek when image generation is appropriate; the tool calls GRS AI Nano Banana, saves manifests/images under `artifacts/images`, and sends the result to the canvas. See [docs/auxiliary-image-generation.md](docs/auxiliary-image-generation.md).
85
85
 
86
+ AgInTiFlow now ships a Markdown skill library in `skills/<id>/SKILL.md`. Skills are prompt playbooks, while tools are deterministic actions such as `apply_patch`, `run_command`, `web_search`, `generate_image`, and `send_to_canvas`. Built-in skills cover code, websites/apps, LaTeX manuscripts, books, Word documents, image generation, GitHub, system maintenance, Android, R/Stan, Python, C/C++, shell, AAPS, and novel writing. See [docs/skills-and-tools.md](docs/skills-and-tools.md).
87
+
88
+ ```bash
89
+ aginti skills
90
+ aginti skills website
91
+ aginti --list-skills latex
92
+ # in chat:
93
+ /skills github commit
94
+ ```
95
+
86
96
  Launch the local web UI from an installed package:
87
97
 
88
98
  ```bash
@@ -0,0 +1,58 @@
1
+ # Skills And Tools
2
+
3
+ AgInTiFlow separates **skills** from **tools** so the agent can stay general while still improving on specialized work.
4
+
5
+ ## Definitions
6
+
7
+ **Skill**: Markdown guidance stored at `skills/<id>/SKILL.md`. A skill describes when to use a workflow, what to inspect first, which outputs matter, and which tools are usually useful. Skills are prompt context, not executable code.
8
+
9
+ **Tool**: A deterministic callable capability exposed to the model, such as `inspect_project`, `read_file`, `apply_patch`, `run_command`, `web_search`, `generate_image`, `preview_workspace`, or `send_to_canvas`.
10
+
11
+ **Profile**: A broad runtime mode such as `auto`, `code`, `latex`, or `maintenance`. Profiles tune routing, max steps, and general behavior. Skills can combine across profiles.
12
+
13
+ ## Built-In Skills
14
+
15
+ The package ships built-in skills for code engineering, website/app building, LaTeX manuscripts, books, Microsoft Word documents, image generation, GitHub maintenance, system maintenance, Android, R/Stan, Python, C/C++, shell scripting, AAPS, and novel writing.
16
+
17
+ List them from a project:
18
+
19
+ ```bash
20
+ aginti skills
21
+ aginti skills website
22
+ aginti --list-skills latex
23
+ ```
24
+
25
+ Inside interactive chat:
26
+
27
+ ```text
28
+ /skills
29
+ /skills github commit
30
+ ```
31
+
32
+ ## Selection Flow
33
+
34
+ For every run, AgInTiFlow scores the user goal and active profile against skill frontmatter:
35
+
36
+ ```yaml
37
+ ---
38
+ id: latex-manuscript
39
+ label: LaTeX Manuscript
40
+ description: Write, compile, and package LaTeX papers, reports, figures, bibliographies, and PDFs.
41
+ triggers:
42
+ - latex
43
+ - tex
44
+ - manuscript
45
+ tools:
46
+ - write_file
47
+ - apply_patch
48
+ - run_command
49
+ ---
50
+ ```
51
+
52
+ Selected skills are injected into the plan and execution prompts. The LLM still decides what to do; skills only provide domain playbooks and guardrails.
53
+
54
+ ## Adding A Skill
55
+
56
+ Create `skills/<id>/SKILL.md` with valid YAML frontmatter and a short Markdown body. Keep descriptions strings, not YAML arrays, because loaders expect `id`, `label`, and `description` as scalar strings.
57
+
58
+ Good skills are small, actionable, and tool-aware. They should say what to inspect, what to create or verify, and what to avoid. They should not hard-code one exact task.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.13.0",
3
+ "version": "0.14.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",
@@ -46,10 +46,12 @@
46
46
  "scripts/smoke-coding-tools.js",
47
47
  "scripts/smoke-capabilities.js",
48
48
  "scripts/smoke-inbox.js",
49
+ "scripts/smoke-skills.js",
49
50
  "scripts/smoke-toolchain-docker.js",
50
51
  "scripts/smoke-web-api.js",
51
52
  "src/",
52
53
  "run.js",
54
+ "skills/",
53
55
  "web.js"
54
56
  ],
55
57
  "bin": {
@@ -64,11 +66,12 @@
64
66
  "smoke:coding-tools": "node scripts/smoke-coding-tools.js",
65
67
  "smoke:auxiliary-tools": "node scripts/smoke-auxiliary-tools.js",
66
68
  "smoke:cli-chat": "node scripts/smoke-cli-chat.js",
69
+ "smoke:skills": "node scripts/smoke-skills.js",
67
70
  "smoke:toolchain-docker": "node scripts/smoke-toolchain-docker.js",
68
71
  "smoke:inbox": "node scripts/smoke-inbox.js",
69
72
  "smoke:web-api": "node scripts/smoke-web-api.js",
70
73
  "real:deepseek": "node scripts/real-deepseek-capabilities.js",
71
- "test": "npm run check && npm run smoke:web-api && npm run smoke:coding-tools && npm run smoke:auxiliary-tools && npm run smoke:capabilities && npm run smoke:cli-chat && npm run smoke:inbox",
74
+ "test": "npm run check && npm run smoke:web-api && npm run smoke:coding-tools && npm run smoke:auxiliary-tools && npm run smoke:capabilities && npm run smoke:skills && npm run smoke:cli-chat && npm run smoke:inbox",
72
75
  "pack:dry-run": "npm pack --dry-run",
73
76
  "smoke:capabilities": "node scripts/smoke-capabilities.js"
74
77
  },
@@ -81,6 +81,14 @@ try {
81
81
  capabilities.trustedDockerPolicy.some((check) => check.command.startsWith("chmod") && check.allowed),
82
82
  "trusted Docker policy did not allow chmod"
83
83
  );
84
+ assert(
85
+ capabilities.tools?.skills?.some((skill) => skill.id === "website-app"),
86
+ "capabilities did not report built-in website skill"
87
+ );
88
+ assert(
89
+ capabilities.tools?.skills?.some((skill) => skill.id === "latex-manuscript"),
90
+ "capabilities did not report built-in LaTeX skill"
91
+ );
84
92
 
85
93
  const doctor = JSON.parse(await runCli(["doctor", "--capabilities", "--json"]));
86
94
  assert(doctor.project.root === tempRoot, "doctor --capabilities used the wrong project root");
@@ -91,7 +99,7 @@ try {
91
99
  {
92
100
  ok: true,
93
101
  projectRoot: tempRoot,
94
- checks: ["aginti-md-init", "capabilities-cli", "doctor-capabilities", "maintenance-policy", "trusted-docker-policy", "git-policy"],
102
+ checks: ["aginti-md-init", "capabilities-cli", "doctor-capabilities", "maintenance-policy", "trusted-docker-policy", "git-policy", "skills-capability"],
95
103
  },
96
104
  null,
97
105
  2
@@ -122,6 +122,10 @@ try {
122
122
  if (!instructionsResult.stdout.includes("AGINTI.md") || !instructionsResult.stdout.includes("Project instructions")) {
123
123
  throw new Error("interactive /instructions did not show AGINTI.md");
124
124
  }
125
+ const skillsResult = await runChat("/skills website\n/exit\n");
126
+ if (!skillsResult.stdout.includes("website-app") || !skillsResult.stdout.includes("Website And App Builder")) {
127
+ throw new Error("interactive /skills did not show matching built-in skills");
128
+ }
125
129
  await runChat("remember that this project prefers pytest smoke tests in AGINTI.md\n/exit\n");
126
130
  const updatedInstructions = await fs.readFile(path.join(tempRoot, "AGINTI.md"), "utf8");
127
131
  if (!updatedInstructions.includes("pytest smoke tests")) {
@@ -159,7 +163,7 @@ try {
159
163
  {
160
164
  ok: true,
161
165
  projectRoot: tempRoot,
162
- checks: ["markdown-render", "markdown-table-no-duplicate", "patch-diff-render", "prompt-layout", "live-input-status-layout", "agent-response-gutter", "aginti-md", "instructions-command", "instructions-chat-edit", "interactive-chat", "mock-file-write", "run-status", "resume-latest", "resume-history-full"],
166
+ checks: ["markdown-render", "markdown-table-no-duplicate", "patch-diff-render", "prompt-layout", "live-input-status-layout", "agent-response-gutter", "aginti-md", "instructions-command", "skills-command", "instructions-chat-edit", "interactive-chat", "mock-file-write", "run-status", "resume-latest", "resume-history-full"],
163
167
  },
164
168
  null,
165
169
  2
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ import { execFile } from "node:child_process";
3
+ import path from "node:path";
4
+ import { promisify } from "node:util";
5
+ import { fileURLToPath } from "node:url";
6
+ import { formatSkillsForPrompt, listSkills, selectSkillsForGoal } from "../src/skill-library.js";
7
+
8
+ const execFileAsync = promisify(execFile);
9
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
10
+
11
+ function assert(condition, message) {
12
+ if (!condition) throw new Error(message);
13
+ }
14
+
15
+ function selectedIds(goal, taskProfile = "auto") {
16
+ return selectSkillsForGoal(goal, { taskProfile, limit: 8 }).map((skill) => skill.id);
17
+ }
18
+
19
+ const skills = listSkills({ includeBody: true });
20
+ const ids = new Set(skills.map((skill) => skill.id));
21
+ assert(skills.length >= 14, "expected built-in skills to load");
22
+ for (const required of [
23
+ "aaps",
24
+ "code",
25
+ "github-maintenance",
26
+ "image-generation",
27
+ "latex-manuscript",
28
+ "system-maintenance",
29
+ "website-app",
30
+ "word-documents",
31
+ ]) {
32
+ assert(ids.has(required), `missing required skill ${required}`);
33
+ }
34
+
35
+ assert(selectedIds("write a beautiful React website and preview it").includes("website-app"), "website prompt did not select website-app");
36
+ assert(selectedIds("write a LaTeX paper and compile a PDF").includes("latex-manuscript"), "latex prompt did not select latex-manuscript");
37
+ assert(selectedIds("edit a Microsoft Word docx and preserve the original").includes("word-documents"), "docx prompt did not select word-documents");
38
+ assert(selectedIds("generate a logo image with grsai nanobanana").includes("image-generation"), "image prompt did not select image-generation");
39
+ assert(selectedIds("git status commit push with gh").includes("github-maintenance"), "git prompt did not select github-maintenance");
40
+ assert(selectedIds("create an .aaps example for @lazyingart/aaps").includes("aaps"), "AAPS prompt did not select aaps");
41
+ assert(selectedIds("debug a C++ CMake build").includes("c-cpp"), "C++ prompt did not select c-cpp");
42
+ assert(selectedIds("set up Stan and CmdStanR reproducibly").includes("r-stan"), "Stan prompt did not select r-stan");
43
+
44
+ const prompt = formatSkillsForPrompt(selectSkillsForGoal("write latex manuscript with figures", { taskProfile: "latex", limit: 3 }));
45
+ assert(prompt.includes("A skill is Markdown guidance"), "skill prompt does not explain skill semantics");
46
+ assert(prompt.includes("latex-manuscript"), "skill prompt omitted selected skill");
47
+ assert(prompt.length < 5400, "skill prompt is too large for normal runs");
48
+
49
+ const cli = await execFileAsync(process.execPath, [path.join(repoRoot, "bin/aginti-cli.js"), "skills", "website"], {
50
+ cwd: repoRoot,
51
+ timeout: 10000,
52
+ maxBuffer: 512 * 1024,
53
+ env: {
54
+ ...process.env,
55
+ AGINTIFLOW_RUNTIME_DIR: "",
56
+ },
57
+ });
58
+ assert(cli.stdout.includes("website-app"), "aginti skills website did not print website-app");
59
+
60
+ console.log(
61
+ JSON.stringify(
62
+ {
63
+ ok: true,
64
+ skills: skills.length,
65
+ checks: ["load-built-ins", "select-by-goal", "prompt-format", "cli-skills"],
66
+ },
67
+ null,
68
+ 2
69
+ )
70
+ );
@@ -81,6 +81,9 @@ try {
81
81
  if (!Array.isArray(config.taskProfiles) || !config.taskProfiles.some((profile) => profile.id === "latex")) {
82
82
  throw new Error("task profiles are not advertised by /api/config");
83
83
  }
84
+ if (!Array.isArray(config.skills) || !config.skills.some((skill) => skill.id === "website-app")) {
85
+ throw new Error("built-in skills are not advertised by /api/config");
86
+ }
84
87
 
85
88
  const keyStatus = await fetchJson("/api/keys/status");
86
89
  if (typeof keyStatus.keyStatus?.deepseek !== "boolean") throw new Error("key status endpoint is invalid");
@@ -0,0 +1,21 @@
1
+ ---
2
+ id: aaps
3
+ label: AAPS Workflows
4
+ description: Work with ../AAPS, .aaps files, @lazyingart/aaps, automation plans, and related project conventions.
5
+ triggers:
6
+ - aaps
7
+ - .aaps
8
+ - @lazyingart/aaps
9
+ - automation plan
10
+ tools:
11
+ - inspect_project
12
+ - read_file
13
+ - write_file
14
+ - run_command
15
+ - web_search
16
+ ---
17
+ # AAPS Workflows
18
+
19
+ Inspect nearby `.aaps` files, package docs, and project notes before generating workflows. Keep assumptions explicit and outputs project-local.
20
+
21
+ Do not publish, upload, or expose credentials unless explicitly requested and safe.
@@ -0,0 +1,23 @@
1
+ ---
2
+ id: android
3
+ label: Android Development
4
+ description: Build and debug Android, Gradle, Kotlin, Java, emulator, and mobile app projects.
5
+ triggers:
6
+ - android
7
+ - gradle
8
+ - kotlin
9
+ - java
10
+ - apk
11
+ - emulator
12
+ tools:
13
+ - inspect_project
14
+ - search_files
15
+ - apply_patch
16
+ - run_command
17
+ - web_search
18
+ ---
19
+ # Android Development
20
+
21
+ Inspect Gradle files, manifests, package names, modules, and existing build scripts before editing. Prefer narrow Gradle tasks and source-set-aware patches.
22
+
23
+ If SDK/emulator tooling is missing, produce a setup report or project-local script rather than guessing.
@@ -0,0 +1,23 @@
1
+ ---
2
+ id: book-writing
3
+ label: Book And Long-Form Writing
4
+ description: Plan, draft, revise, and structure books, chapters, scripts, tutorials, and long documents.
5
+ triggers:
6
+ - book
7
+ - chapter
8
+ - script
9
+ - tutorial
10
+ - long form
11
+ - outline
12
+ - manuscript
13
+ tools:
14
+ - write_file
15
+ - read_file
16
+ - web_search
17
+ - send_to_canvas
18
+ ---
19
+ # Book And Long-Form Writing
20
+
21
+ Start with structure: audience, promise, outline, chapter flow, voice, and revision plan. Save durable drafts as Markdown unless the user requests another format.
22
+
23
+ Use web search for current facts and keep source notes when claims matter. For long work, produce incremental chapters and revision notes instead of one huge brittle output.
@@ -0,0 +1,23 @@
1
+ ---
2
+ id: c-cpp
3
+ label: C And C++ Development
4
+ description: Work with C, C++, CMake, Make, native builds, tests, and low-level debugging.
5
+ triggers:
6
+ - c++
7
+ - cpp
8
+ - c language
9
+ - cmake
10
+ - makefile
11
+ - gcc
12
+ - clang
13
+ tools:
14
+ - inspect_project
15
+ - search_files
16
+ - apply_patch
17
+ - run_command
18
+ ---
19
+ # C And C++ Development
20
+
21
+ Inspect build files before source edits. Keep patches narrow, respect formatting style, and run the smallest compile/test target available.
22
+
23
+ If native dependencies are missing, prefer Docker/project-local setup notes and do not silently change host toolchains.
@@ -0,0 +1,25 @@
1
+ ---
2
+ id: code
3
+ label: Code Engineering
4
+ description: Build, edit, debug, test, and maintain software projects across languages.
5
+ triggers:
6
+ - code
7
+ - app
8
+ - bug
9
+ - test
10
+ - refactor
11
+ - package
12
+ - project
13
+ tools:
14
+ - inspect_project
15
+ - search_files
16
+ - read_file
17
+ - apply_patch
18
+ - run_command
19
+ - web_search
20
+ ---
21
+ # Code Engineering
22
+
23
+ Orient before editing: inspect the project map, read instructions/manifests, search exact symbols or errors, then patch the smallest coherent set of files.
24
+
25
+ Prefer deterministic `apply_patch` for source edits. Run focused checks first, repair failures, then broaden checks only when useful. Summarize changed files, commands run, and residual risk.
@@ -0,0 +1,23 @@
1
+ ---
2
+ id: github-maintenance
3
+ label: GitHub And Release Maintenance
4
+ description: Use git and gh safely for status, commits, pull requests, releases, and repository maintenance.
5
+ triggers:
6
+ - git
7
+ - github
8
+ - gh
9
+ - commit
10
+ - push
11
+ - pull request
12
+ - release
13
+ tools:
14
+ - run_command
15
+ - read_file
16
+ - apply_patch
17
+ - web_search
18
+ ---
19
+ # GitHub And Release Maintenance
20
+
21
+ Always inspect `git status --short` and relevant diffs before committing or pushing. Keep commits scoped and stop on conflicts, divergence, or unrelated dirty work.
22
+
23
+ Use `gh` for PR/release/status workflows when authenticated. Fold long command output but preserve key errors, URLs, branch names, and commit hashes.
@@ -0,0 +1,23 @@
1
+ ---
2
+ id: image-generation
3
+ label: Image Generation
4
+ description: Generate or edit raster images, posters, covers, logos, illustrations, and visual assets.
5
+ triggers:
6
+ - image
7
+ - logo
8
+ - poster
9
+ - cover
10
+ - illustration
11
+ - nanobanana
12
+ - grsai
13
+ - gpt image
14
+ tools:
15
+ - generate_image
16
+ - write_file
17
+ - send_to_canvas
18
+ ---
19
+ # Image Generation
20
+
21
+ Use image generation when the user asks for a raster visual asset or prompt. Write a concise prompt with subject, style, composition, color, lighting, and output constraints.
22
+
23
+ Prefer `generate_image` when a GRS AI key is available. Save selected outputs under artifacts and send the chosen image to canvas.
@@ -0,0 +1,25 @@
1
+ ---
2
+ id: latex-manuscript
3
+ label: LaTeX Manuscript
4
+ description: Write, compile, and package LaTeX papers, reports, figures, bibliographies, and PDFs.
5
+ triggers:
6
+ - latex
7
+ - tex
8
+ - manuscript
9
+ - paper
10
+ - pdf
11
+ - figure
12
+ - overleaf
13
+ tools:
14
+ - write_file
15
+ - apply_patch
16
+ - run_command
17
+ - open_workspace_file
18
+ - send_to_canvas
19
+ - web_search
20
+ ---
21
+ # LaTeX Manuscript
22
+
23
+ Use a project subfolder with `main.tex`, figures, bibliography, and generated PDFs. Compile from the correct directory with `latexmk` or `pdflatex` when available.
24
+
25
+ If TeX is missing, produce an honest setup note or Docker-local setup plan instead of faking success. Send the final PDF or source to canvas when useful.
@@ -0,0 +1,21 @@
1
+ ---
2
+ id: novel-writing
3
+ label: Novel Writing
4
+ description: Plan, draft, revise, and manage novels, character arcs, scenes, chapters, and long fiction.
5
+ triggers:
6
+ - novel
7
+ - fiction
8
+ - character
9
+ - scene
10
+ - plot
11
+ - chapter
12
+ tools:
13
+ - write_file
14
+ - read_file
15
+ - send_to_canvas
16
+ ---
17
+ # Novel Writing
18
+
19
+ Track premise, characters, setting, conflict, tone, and continuity. Save outlines, chapter drafts, and revision notes as durable files.
20
+
21
+ For long works, write in scenes or chapters and maintain a compact continuity bible instead of relying on chat history alone.
@@ -0,0 +1,24 @@
1
+ ---
2
+ id: python
3
+ label: Python Development
4
+ description: Build Python packages, CLIs, scripts, tests, notebooks, plotting, and data workflows.
5
+ triggers:
6
+ - python
7
+ - pytest
8
+ - pyproject
9
+ - pip
10
+ - uv
11
+ - matplotlib
12
+ - pandas
13
+ tools:
14
+ - inspect_project
15
+ - search_files
16
+ - apply_patch
17
+ - run_command
18
+ - send_to_canvas
19
+ ---
20
+ # Python Development
21
+
22
+ Inspect `pyproject.toml`, requirements, package layout, and tests. Prefer stdlib tests when dependency installs are unnecessary; otherwise use project-local venv/conda/uv or Docker.
23
+
24
+ For plots and artifacts, save files with clear names and send useful outputs to canvas.
@@ -0,0 +1,24 @@
1
+ ---
2
+ id: r-stan
3
+ label: R And Stan
4
+ description: Work with R, Stan, CmdStan, CmdStanR, PyStan, statistics, and reproducible analysis projects.
5
+ triggers:
6
+ - r
7
+ - stan
8
+ - cmdstan
9
+ - cmdstanr
10
+ - pystan
11
+ - statistics
12
+ - bayesian
13
+ tools:
14
+ - inspect_project
15
+ - write_file
16
+ - run_command
17
+ - web_search
18
+ - send_to_canvas
19
+ ---
20
+ # R And Stan
21
+
22
+ Keep analysis reproducible: scripts, data paths, package notes, seed handling, and output folders. Prefer Docker or project-local toolchains for installation.
23
+
24
+ For Stan, validate model syntax and compile/run only when toolchains exist; otherwise write honest setup scripts and checks.
@@ -0,0 +1,21 @@
1
+ ---
2
+ id: shell
3
+ label: Shell Scripting
4
+ description: Write, audit, and run shell scripts for automation, setup, diagnostics, and maintenance.
5
+ triggers:
6
+ - shell
7
+ - bash
8
+ - script
9
+ - terminal
10
+ - command
11
+ - cli
12
+ tools:
13
+ - write_file
14
+ - run_command
15
+ - web_search
16
+ ---
17
+ # Shell Scripting
18
+
19
+ Prefer idempotent scripts with `set -euo pipefail` when appropriate, clear variables, dry-run or validation modes, and `bash -n` checks.
20
+
21
+ Separate diagnosis from mutation. Keep dangerous host operations explicit and reversible.
@@ -0,0 +1,24 @@
1
+ ---
2
+ id: system-maintenance
3
+ label: System Maintenance
4
+ description: Diagnose and repair local system, shell, package, Docker, conda, Python, R, and toolchain problems.
5
+ triggers:
6
+ - system
7
+ - install
8
+ - package
9
+ - docker
10
+ - conda
11
+ - environment
12
+ - fix computer
13
+ - error
14
+ tools:
15
+ - run_command
16
+ - write_file
17
+ - web_search
18
+ - send_to_canvas
19
+ ---
20
+ # System Maintenance
21
+
22
+ Diagnose first with read-only commands. Prefer Docker/project-local scripts and `/aginti-env` for broad toolchain setup. Host-level sudo or destructive operations require explicit user approval outside normal automation.
23
+
24
+ For complicated setup, write idempotent scripts, run syntax checks, and report exact next safe command.
@@ -0,0 +1,26 @@
1
+ ---
2
+ id: website-app
3
+ label: Website And App Builder
4
+ description: Build polished websites, web apps, landing pages, dashboards, and local previews.
5
+ triggers:
6
+ - website
7
+ - web app
8
+ - landing page
9
+ - frontend
10
+ - dashboard
11
+ - react
12
+ - html
13
+ tools:
14
+ - inspect_project
15
+ - write_file
16
+ - apply_patch
17
+ - run_command
18
+ - preview_workspace
19
+ - open_workspace_file
20
+ - send_to_canvas
21
+ ---
22
+ # Website And App Builder
23
+
24
+ Create real files, not only advice. For new UI, choose a clear visual direction, responsive layout, purposeful typography, and meaningful content. For existing apps, preserve conventions.
25
+
26
+ Use package scripts when available. Preview with `preview_workspace` or `open_workspace_file`; avoid transient Docker localhost loops.
@@ -0,0 +1,22 @@
1
+ ---
2
+ id: word-documents
3
+ label: Microsoft Word Documents
4
+ description: Edit, convert, inspect, or generate Word-style documents using available local tools.
5
+ triggers:
6
+ - word
7
+ - docx
8
+ - microsoft word
9
+ - office
10
+ - pandoc
11
+ - libreoffice
12
+ tools:
13
+ - read_file
14
+ - write_file
15
+ - run_command
16
+ - send_to_canvas
17
+ ---
18
+ # Microsoft Word Documents
19
+
20
+ Prefer safe conversions through available tools such as `pandoc`, `libreoffice`, or Python libraries when installed. Preserve originals; write converted or edited outputs to a new file unless overwrite is explicit.
21
+
22
+ If binary `.docx` content cannot be inspected directly, explain the needed converter and create a project-local script or setup note.
@@ -22,6 +22,7 @@ import { engineeringGuidanceForTask } from "./engineering-guidance.js";
22
22
  import { searchWeb } from "./web-search.js";
23
23
  import { runParallelScouts, shouldRunParallelScouts } from "./parallel-scouts.js";
24
24
  import { readProjectInstructions } from "./project.js";
25
+ import { formatSkillsForPrompt, selectSkillsForGoal } from "./skill-library.js";
25
26
 
26
27
  const exec = promisify(execCallback);
27
28
  const BROWSER_TOOLS = new Set(["open_url", "open_workspace_file", "preview_workspace", "click", "type", "scroll", "press", "back"]);
@@ -252,6 +253,8 @@ async function createInitialState(config, sessionId) {
252
253
  const now = new Date().toISOString();
253
254
  const taskProfile = getTaskProfile(config.taskProfile);
254
255
  const engineeringGuidance = engineeringGuidanceForTask(config.goal, config.taskProfile);
256
+ const selectedSkills = selectSkillsForGoal(config.goal, { taskProfile: config.taskProfile, limit: 6 });
257
+ const skillContext = formatSkillsForPrompt(selectedSkills);
255
258
  const projectInstructions = await readProjectInstructions(config.baseDir || config.commandCwd || process.cwd());
256
259
  const projectInstructionContext = formatProjectInstructions(projectInstructions);
257
260
  return {
@@ -272,6 +275,7 @@ async function createInitialState(config, sessionId) {
272
275
  truncated: projectInstructions.truncated,
273
276
  loadedAt: now,
274
277
  },
278
+ selectedSkills: selectedSkills.map((skill) => skill.id),
275
279
  },
276
280
  chat: [
277
281
  {
@@ -318,6 +322,7 @@ async function createInitialState(config, sessionId) {
318
322
  ? `Parallel DeepSeek scouts may run before complex execution. Scout count: ${config.parallelScoutCount}.`
319
323
  : "Parallel scouts are disabled.",
320
324
  `Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
325
+ skillContext,
321
326
  engineeringGuidance,
322
327
  "A frontend canvas/artifacts tunnel exists. Use send_to_canvas when important markdown, diffs, screenshots, images, or workspace files should be highlighted in the UI. It is optional and ordinary final text can still go directly to finish.",
323
328
  "For visual-output requests such as draw, plot, graph, chart, diagram, figure, image, or visualization, proactively publish a canvas artifact even when the user does not mention canvas. If workspace file tools are enabled, prefer creating a small SVG or markdown artifact and call send_to_canvas with selected=true.",
@@ -360,6 +365,7 @@ async function createInitialState(config, sessionId) {
360
365
  config.allowWebSearch ? "Web search tool: enabled." : "Web search tool: disabled.",
361
366
  config.allowParallelScouts ? `Parallel scouts: enabled count=${config.parallelScoutCount}.` : "Parallel scouts: disabled.",
362
367
  `Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
368
+ skillContext,
363
369
  engineeringGuidance,
364
370
  "Canvas/artifacts tunnel: available through send_to_canvas for optional frontend rendering.",
365
371
  "Visual-output requests should produce a canvas artifact without requiring the user to ask for canvas explicitly.",
@@ -451,6 +457,8 @@ async function applyContinuationPrompt(state, config, observers) {
451
457
 
452
458
  const taskProfile = getTaskProfile(config.taskProfile);
453
459
  const engineeringGuidance = engineeringGuidanceForTask(config.goal, config.taskProfile);
460
+ const selectedSkills = selectSkillsForGoal(config.goal, { taskProfile: config.taskProfile, limit: 6 });
461
+ const skillContext = formatSkillsForPrompt(selectedSkills);
454
462
  const projectInstructions = await readProjectInstructions(config.baseDir || config.commandCwd || process.cwd());
455
463
  state.meta = state.meta || {};
456
464
  state.meta.projectInstructions = {
@@ -459,6 +467,7 @@ async function applyContinuationPrompt(state, config, observers) {
459
467
  truncated: projectInstructions.truncated,
460
468
  loadedAt: new Date().toISOString(),
461
469
  };
470
+ state.meta.selectedSkills = selectedSkills.map((skill) => skill.id);
462
471
  ensureChatState(state);
463
472
  state.goal = config.goal;
464
473
  state.provider = config.provider;
@@ -485,6 +494,7 @@ async function applyContinuationPrompt(state, config, observers) {
485
494
  ? `Agent wrappers: selected=${normalizeWrapperName(config.preferredWrapper)}; ${wrapperStatusText()}`
486
495
  : "",
487
496
  `Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
497
+ skillContext,
488
498
  engineeringGuidance,
489
499
  formatProjectInstructions(projectInstructions),
490
500
  "AGINTI.md is editable project memory. If the user asks to remember a preference or update instructions, patch AGINTI.md rather than hiding that preference in session-only chat.",
@@ -9,6 +9,7 @@ import { listTaskProfiles } from "./task-profiles.js";
9
9
  import { listAgentWrappers } from "./tool-wrappers.js";
10
10
  import { listAuxiliarySkills } from "./auxiliary-tools.js";
11
11
  import { readCodebaseMap } from "./codebase-map.js";
12
+ import { listSkills } from "./skill-library.js";
12
13
 
13
14
  const execFileAsync = promisify(execFile);
14
15
 
@@ -218,6 +219,13 @@ export async function buildCapabilityReport(projectRoot, packageVersion, config)
218
219
  label: profile.label,
219
220
  tools: profile.tools,
220
221
  })),
222
+ skills: listSkills().map((skill) => ({
223
+ id: skill.id,
224
+ label: skill.label,
225
+ description: skill.description,
226
+ triggers: skill.triggers,
227
+ tools: skill.tools,
228
+ })),
221
229
  auxiliarySkills: listAuxiliarySkills().map((skill) => ({
222
230
  id: skill.id,
223
231
  label: skill.label,
package/src/cli.js CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  import { listTaskProfiles } from "./task-profiles.js";
18
18
  import { recommendedMaxStepsForTask } from "./engineering-guidance.js";
19
19
  import { promptAndSaveDeepSeekKey, promptHidden, shouldPromptForDeepSeek } from "./auth-onboarding.js";
20
+ import { listSkills, selectSkillsForGoal } from "./skill-library.js";
20
21
  import fs from "node:fs/promises";
21
22
  import path from "node:path";
22
23
  import { fileURLToPath } from "node:url";
@@ -64,6 +65,7 @@ export function parseArgs(argv) {
64
65
  port: "",
65
66
  host: "",
66
67
  listProfiles: false,
68
+ listSkills: false,
67
69
  latex: false,
68
70
  image: false,
69
71
  };
@@ -242,6 +244,10 @@ export function parseArgs(argv) {
242
244
  result.listProfiles = true;
243
245
  continue;
244
246
  }
247
+ if (arg === "--list-skills") {
248
+ result.listSkills = true;
249
+ continue;
250
+ }
245
251
  if (arg === "--sandbox-status") {
246
252
  result.sandboxStatus = true;
247
253
  continue;
@@ -261,7 +267,7 @@ export function parseArgs(argv) {
261
267
 
262
268
  function printUsage() {
263
269
  console.log(
264
- 'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti login deepseek|openai|grsai OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--image] [--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-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex] [--sandbox-status|--sandbox-preflight] "your task"'
270
+ 'Usage: aginti [chat] OR aginti web [--port 3210] OR aginti skills [query] OR aginti login deepseek|openai|grsai OR aginti resume [latest|<session-id>] ["prompt"] OR aginti queue <session-id> "message" OR aginti [--image] [--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-file-tools|--no-file-tools] [--web-search|--no-web-search] [--parallel-scouts|--no-parallel-scouts --scout-count 1..10] [--allow-auxiliary-tools|--no-auxiliary-tools] [--allow-wrappers --wrapper codex] [--list-skills] [--sandbox-status|--sandbox-preflight] "your task"'
265
271
  );
266
272
  }
267
273
 
@@ -320,6 +326,17 @@ function printProfiles() {
320
326
  }
321
327
  }
322
328
 
329
+ function printSkills(query = "") {
330
+ const skills = query
331
+ ? selectSkillsForGoal(query, { taskProfile: "auto", limit: 40, includeBody: false })
332
+ : listSkills({ includeBody: false });
333
+ for (const skill of skills) {
334
+ const triggers = skill.triggers?.length ? ` triggers=${skill.triggers.join(",")}` : "";
335
+ const tools = skill.tools?.length ? ` tools=${skill.tools.join(",")}` : "";
336
+ console.log(`${skill.id}: ${skill.label} - ${skill.description}${triggers}${tools}`);
337
+ }
338
+ }
339
+
323
340
  function printInitResult(result) {
324
341
  console.log(`AgInTiFlow project initialized: ${result.projectRoot}`);
325
342
  console.log(`instructions=${result.instructionsPath}`);
@@ -534,6 +551,11 @@ export async function main(argv = process.argv.slice(2)) {
534
551
  return;
535
552
  }
536
553
 
554
+ if (argv[0] === "skills" || argv[0] === "skill") {
555
+ printSkills(argv.slice(1).join(" ").trim());
556
+ return;
557
+ }
558
+
537
559
  if (argv[0] === "queue") {
538
560
  await handleQueueCommand(argv.slice(1));
539
561
  return;
@@ -592,6 +614,11 @@ export async function main(argv = process.argv.slice(2)) {
592
614
  return;
593
615
  }
594
616
 
617
+ if (args.listSkills) {
618
+ printSkills(args.goal);
619
+ return;
620
+ }
621
+
595
622
  if (args.sandboxStatus || args.sandboxPreflight) {
596
623
  const config = loadConfig({ ...args, goal: args.goal || "sandbox preflight" }, { packageDir });
597
624
  const result = args.sandboxPreflight
@@ -9,6 +9,7 @@ import { defaultMaxStepsForProfile, normalizeTaskProfile } from "./task-profiles
9
9
  import { recommendedMaxStepsForTask } from "./engineering-guidance.js";
10
10
  import { promptAndSaveDeepSeekKey, promptHidden, shouldPromptForDeepSeek } from "./auth-onboarding.js";
11
11
  import { SessionStore } from "./session-store.js";
12
+ import { listSkills, selectSkillsForGoal } from "./skill-library.js";
12
13
 
13
14
  const useColor = Boolean(input.isTTY && output.isTTY && process.env.AGINTIFLOW_NO_COLOR !== "1");
14
15
  const ansi = {
@@ -44,6 +45,8 @@ const SLASH_COMMANDS = [
44
45
  "/new",
45
46
  "/resume",
46
47
  "/sessions",
48
+ "/skills",
49
+ "/skill",
47
50
  "/profile",
48
51
  "/web-search",
49
52
  "/scouts",
@@ -480,6 +483,7 @@ function printHelp() {
480
483
  " /new Start a fresh session on the next message.",
481
484
  " /resume <session-id> Continue a saved session.",
482
485
  " /sessions List recent sessions in this project.",
486
+ " /skills [query] List Markdown skills selected for a topic.",
483
487
  " /profile <name> Set task profile, e.g. code, website, latex, maintenance.",
484
488
  " /web-search on|off Enable or disable the web_search tool.",
485
489
  " /scouts on|off|<1-10> Enable parallel DeepSeek scouts and set scout count.",
@@ -1589,6 +1593,30 @@ async function handleCommand(line, state, packageDir) {
1589
1593
  }
1590
1594
  return true;
1591
1595
  }
1596
+ if (command === "skills" || command === "skill") {
1597
+ const skills = value
1598
+ ? selectSkillsForGoal(value, { taskProfile: state.taskProfile, limit: 12, includeBody: false })
1599
+ : listSkills({ includeBody: false });
1600
+ if (skills.length === 0) {
1601
+ printAgentMessage("No matching skills found.");
1602
+ return true;
1603
+ }
1604
+ printAgentMessage(
1605
+ skills
1606
+ .map((skill) =>
1607
+ [
1608
+ `${skill.id}: ${skill.label}`,
1609
+ ` ${skill.description}`,
1610
+ skill.triggers?.length ? ` triggers: ${skill.triggers.join(", ")}` : "",
1611
+ skill.tools?.length ? ` tools: ${skill.tools.join(", ")}` : "",
1612
+ ]
1613
+ .filter(Boolean)
1614
+ .join("\n")
1615
+ )
1616
+ .join("\n\n")
1617
+ );
1618
+ return true;
1619
+ }
1592
1620
  if (command === "profile") {
1593
1621
  state.taskProfile = normalizeTaskProfile(value || "auto");
1594
1622
  state.maxSteps = Math.max(state.maxSteps, defaultMaxStepsForProfile(state.taskProfile));
@@ -3,6 +3,7 @@ import { normalizeWrapperName, wrapperStatusText } from "./tool-wrappers.js";
3
3
  import { getTaskProfile } from "./task-profiles.js";
4
4
  import { listAuxiliarySkills } from "./auxiliary-tools.js";
5
5
  import { engineeringGuidanceForTask } from "./engineering-guidance.js";
6
+ import { formatSkillsForPrompt, selectSkillsForGoal } from "./skill-library.js";
6
7
 
7
8
  export function createClient(config) {
8
9
  if (config.provider === "mock") {
@@ -193,6 +194,8 @@ function mockChatResponse(content, toolCalls = []) {
193
194
  export async function createPlan(client, config, state) {
194
195
  const taskProfile = getTaskProfile(config.taskProfile);
195
196
  const engineeringGuidance = engineeringGuidanceForTask(state.goal, config.taskProfile);
197
+ const selectedSkills = selectSkillsForGoal(state.goal, { taskProfile: config.taskProfile, limit: 5 });
198
+ const skillContext = formatSkillsForPrompt(selectedSkills);
196
199
  const projectInstructions = state.meta?.projectInstructions;
197
200
  if (client.mock) {
198
201
  return [
@@ -242,6 +245,7 @@ export async function createPlan(client, config, state) {
242
245
  ? `Parallel scout notes may be injected before execution for complex tasks. Scout count: ${config.parallelScoutCount}.`
243
246
  : "Parallel scouts are disabled.",
244
247
  `Task profile: ${taskProfile.label}. ${taskProfile.prompt}`,
248
+ skillContext,
245
249
  engineeringGuidance,
246
250
  "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.",
247
251
  "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.",
@@ -0,0 +1,153 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ const DEFAULT_SKILLS_DIR = path.resolve(__dirname, "..", "skills");
7
+ const DEFAULT_PROMPT_CHARS = 5200;
8
+
9
+ function parseScalar(value = "") {
10
+ const trimmed = String(value || "").trim();
11
+ if (
12
+ (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
13
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))
14
+ ) {
15
+ return trimmed.slice(1, -1);
16
+ }
17
+ return trimmed;
18
+ }
19
+
20
+ function parseFrontmatter(text, filePath) {
21
+ const match = String(text || "").match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/);
22
+ if (!match) throw new Error(`${filePath}: missing YAML frontmatter`);
23
+ const meta = {};
24
+ const lines = match[1].split(/\r?\n/);
25
+ for (let index = 0; index < lines.length; index += 1) {
26
+ const line = lines[index];
27
+ if (!line.trim() || line.trim().startsWith("#")) continue;
28
+ const scalar = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
29
+ if (!scalar) throw new Error(`${filePath}: invalid YAML line: ${line}`);
30
+ const key = scalar[1];
31
+ const value = scalar[2];
32
+ if (value.trim()) {
33
+ meta[key] = parseScalar(value);
34
+ continue;
35
+ }
36
+ const items = [];
37
+ while (index + 1 < lines.length && /^\s+-\s+/.test(lines[index + 1])) {
38
+ index += 1;
39
+ items.push(parseScalar(lines[index].replace(/^\s+-\s+/, "")));
40
+ }
41
+ meta[key] = items;
42
+ }
43
+ return {
44
+ meta,
45
+ body: String(text || "").slice(match[0].length).trim(),
46
+ };
47
+ }
48
+
49
+ function normalizeList(value) {
50
+ if (Array.isArray(value)) return value.map((item) => String(item || "").trim()).filter(Boolean);
51
+ if (!value) return [];
52
+ return String(value)
53
+ .split(",")
54
+ .map((item) => item.trim())
55
+ .filter(Boolean);
56
+ }
57
+
58
+ function loadSkillFile(filePath) {
59
+ const text = fs.readFileSync(filePath, "utf8");
60
+ const { meta, body } = parseFrontmatter(text, filePath);
61
+ for (const field of ["id", "label", "description"]) {
62
+ if (typeof meta[field] !== "string" || !meta[field].trim()) {
63
+ throw new Error(`${filePath}: ${field} must be a non-empty string`);
64
+ }
65
+ }
66
+ return {
67
+ id: meta.id.trim(),
68
+ label: meta.label.trim(),
69
+ description: meta.description.trim(),
70
+ triggers: normalizeList(meta.triggers),
71
+ tools: normalizeList(meta.tools),
72
+ body,
73
+ path: filePath,
74
+ };
75
+ }
76
+
77
+ export function listSkills({ includeBody = false, skillsDir = DEFAULT_SKILLS_DIR } = {}) {
78
+ let dirEntries = [];
79
+ try {
80
+ dirEntries = fs.readdirSync(skillsDir, { withFileTypes: true });
81
+ } catch {
82
+ return [];
83
+ }
84
+ const skills = [];
85
+ for (const entry of dirEntries) {
86
+ if (!entry.isDirectory()) continue;
87
+ const skillPath = path.join(skillsDir, entry.name, "SKILL.md");
88
+ try {
89
+ const skill = loadSkillFile(skillPath);
90
+ if (!includeBody) delete skill.body;
91
+ skills.push(skill);
92
+ } catch {
93
+ // Invalid local skill files are skipped so one bad skill does not break the agent.
94
+ }
95
+ }
96
+ return skills.sort((a, b) => a.id.localeCompare(b.id));
97
+ }
98
+
99
+ function scoreSkill(skill, text, taskProfile) {
100
+ let score = 0;
101
+ if (skill.id === taskProfile) score += 10;
102
+ if (skill.triggers.includes(taskProfile)) score += 6;
103
+ for (const trigger of skill.triggers) {
104
+ const needle = trigger.toLowerCase();
105
+ if (needle && text.includes(needle)) score += Math.max(2, Math.min(6, Math.ceil(needle.length / 6)));
106
+ }
107
+ for (const token of skill.description.toLowerCase().split(/[^a-z0-9+#.-]+/).filter((item) => item.length > 3)) {
108
+ if (text.includes(token)) score += 0.25;
109
+ }
110
+ return score;
111
+ }
112
+
113
+ export function selectSkillsForGoal(goal = "", { taskProfile = "auto", limit = 6, includeBody = true } = {}) {
114
+ const text = `${goal} ${taskProfile}`.toLowerCase();
115
+ const skills = listSkills({ includeBody });
116
+ const scored = skills
117
+ .map((skill) => ({ skill, score: scoreSkill(skill, text, taskProfile) }))
118
+ .filter((item) => item.score > 0)
119
+ .sort((a, b) => b.score - a.score || a.skill.id.localeCompare(b.skill.id))
120
+ .map((item) => item.skill);
121
+ return scored.slice(0, Math.max(1, limit));
122
+ }
123
+
124
+ function compactBody(body = "", limit = 620) {
125
+ const text = String(body || "")
126
+ .replace(/^# .+$/gm, "")
127
+ .replace(/\n{3,}/g, "\n\n")
128
+ .trim();
129
+ if (text.length <= limit) return text;
130
+ return `${text.slice(0, limit - 20).trim()}\n...`;
131
+ }
132
+
133
+ export function formatSkillsForPrompt(skills = [], { maxChars = DEFAULT_PROMPT_CHARS } = {}) {
134
+ if (!Array.isArray(skills) || skills.length === 0) return "";
135
+ const chunks = [
136
+ "Selected AgInTiFlow skills. A skill is Markdown guidance for when and how to use tools; it is not itself a tool. Follow relevant skill guidance without becoming constrained by it.",
137
+ ];
138
+ for (const skill of skills) {
139
+ chunks.push(
140
+ [
141
+ `## ${skill.id}: ${skill.label}`,
142
+ `Description: ${skill.description}`,
143
+ skill.tools?.length ? `Preferred tools: ${skill.tools.join(", ")}` : "",
144
+ skill.body ? compactBody(skill.body) : "",
145
+ ]
146
+ .filter(Boolean)
147
+ .join("\n")
148
+ );
149
+ }
150
+ const output = chunks.join("\n\n");
151
+ if (output.length <= maxChars) return output;
152
+ return `${output.slice(0, Math.max(maxChars - 80, 1)).trim()}\n... [skills truncated]`;
153
+ }
package/web.js CHANGED
@@ -14,6 +14,7 @@ import { summarizeWorkspaceTools, WORKSPACE_TOOL_NAMES } from "./src/workspace-t
14
14
  import { listTaskProfiles, normalizeTaskProfile } from "./src/task-profiles.js";
15
15
  import { loadProjectEnv, projectPaths, providerKeyStatus, setProviderKey } from "./src/project.js";
16
16
  import { buildCapabilityReport } from "./src/capabilities.js";
17
+ import { listSkills } from "./src/skill-library.js";
17
18
  import {
18
19
  buildArtifacts,
19
20
  countUnreadArtifacts,
@@ -574,6 +575,7 @@ app.get("/api/config", async (_req, res) => {
574
575
  maxSteps: 24,
575
576
  },
576
577
  taskProfiles: listTaskProfiles(),
578
+ skills: listSkills(),
577
579
  routing: {
578
580
  modes: ["smart", "fast", "complex", "manual"],
579
581
  presets: getModelPresets(),