Mads
Multi-Agent Distributed System
Loading...
Searching...
No Matches
plugin_skill.hpp
Go to the documentation of this file.
1/*
2 ____ _ _ ____ _ _ _ _
3 | _ \| |_ _ __ _(_)_ __ / ___|| | _(_) | |
4 | |_) | | | | |/ _` | | '_ \ \___ \| |/ / | | |
5 | __/| | |_| | (_| | | | | | ___) | <| | | |
6 |_| |_|\__,_|\__, |_|_| |_| |____/|_|\_\_|_|_|
7 |___/
8
9 Installs the `mads-plugin` agent skill into a plugin project, so that an AI
10 coding assistant working on the plugin has the runtime context it cannot infer
11 from the plugin API alone (call order, per-host effect of every return_type,
12 settings injection, topics/blobs, deployment, migration).
13
14 The source of truth is <prefix>/share/skills/mads-plugin: SKILL.md.tpl is
15 rendered with the same inja data `mads plugin` uses for the other templates
16 (so version and protocol pins are stamped), while the reference/ markdown files are copied
17 verbatim -- they contain code samples whose braces must not be parsed as inja
18 tags.
19
20 Used by both `mads plugin <name>` (scaffolding) and `mads plugin --update`
21 (refreshing an existing project after a protocol migration).
22
23 Author(s): Paolo Bosetti
24*/
25#ifndef PLUGIN_SKILL_HPP
26#define PLUGIN_SKILL_HPP
27
28#include <algorithm>
29#include <filesystem>
30#include <fstream>
31#include <inja/inja.hpp>
32#include <iostream>
33#include <nlohmann/json.hpp>
34#include <rang.hpp>
35#include <string>
36#include <vector>
37
38namespace Mads {
39namespace PluginSkill {
40
41namespace fs = std::filesystem;
42using json = nlohmann::json;
43
45inline const char *skill_rel_dir() { return ".claude/skills/mads-plugin"; }
46
57inline int install(const fs::path &skills_dir, const fs::path &project_dir,
58 const json &data, bool overwrite, bool verbose = true) {
59 using namespace rang;
60 if (!fs::is_directory(skills_dir)) {
61 std::cerr << fg::yellow << "Warning: skill sources not found in "
62 << skills_dir << ", skipping agent documentation" << fg::reset
63 << std::endl;
64 return 0;
65 }
66
67 fs::path dest = project_dir / skill_rel_dir();
68 std::error_code ec;
69 fs::create_directories(dest / "reference", ec);
70 if (ec) {
71 std::cerr << fg::red << "Error: cannot create " << dest << ": "
72 << ec.message() << fg::reset << std::endl;
73 return 0;
74 }
75
76 int written = 0;
77 auto report = [&](const fs::path &p, bool skipped) {
78 if (!verbose)
79 return;
80 std::cout << "==> " << style::bold << p.string() << style::reset << ": ";
81 if (skipped)
82 std::cout << fg::red << "already exists, skipped; use -o to overwrite"
83 << fg::reset << std::endl;
84 else
85 std::cout << fg::green << "created" << fg::reset << std::endl;
86 };
87
88 // SKILL.md is rendered: it carries the MADS version and protocol numbers.
89 fs::path tpl = skills_dir / "SKILL.md.tpl";
90 fs::path skill_md = dest / "SKILL.md";
91 if (fs::exists(tpl)) {
92 if (!overwrite && fs::exists(skill_md)) {
93 report(skill_md, true);
94 } else {
95 try {
96 inja::Environment env;
97 // inja's default line statement is "##", which would swallow every
98 // markdown heading in the skill; the same "%%" convention the README
99 // template uses keeps headings intact.
100 env.set_line_statement("%%");
101 std::string rendered = env.render_file(tpl.string(), data);
102 std::ofstream ofs(skill_md);
103 ofs << rendered;
104 ofs.close();
105 written++;
106 report(skill_md, false);
107 } catch (const std::exception &e) {
108 std::cerr << fg::red << "Error rendering " << tpl << ": " << e.what()
109 << fg::reset << std::endl;
110 }
111 }
112 }
113
114 // reference/*.md are copied verbatim.
115 fs::path ref_src = skills_dir / "reference";
116 if (fs::is_directory(ref_src)) {
117 std::vector<fs::path> refs;
118 for (const auto &e : fs::directory_iterator(ref_src))
119 if (e.is_regular_file() && e.path().extension() == ".md")
120 refs.push_back(e.path());
121 std::sort(refs.begin(), refs.end());
122 for (const auto &src : refs) {
123 fs::path dst = dest / "reference" / src.filename();
124 if (!overwrite && fs::exists(dst)) {
125 report(dst, true);
126 continue;
127 }
128 fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec);
129 if (ec) {
130 std::cerr << fg::red << "Error: cannot copy " << src << ": "
131 << ec.message() << fg::reset << std::endl;
132 ec.clear();
133 continue;
134 }
135 written++;
136 report(dst, false);
137 }
138 }
139
140 return written;
141}
142
143} // namespace PluginSkill
144} // namespace Mads
145
146#endif // PLUGIN_SKILL_HPP
const char * skill_rel_dir()
Where the skill lands inside a plugin project.
int install(const fs::path &skills_dir, const fs::path &project_dir, const json &data, bool overwrite, bool verbose=true)
Renders/copies the mads-plugin skill into a plugin project.
nlohmann::json json
Definition agent.hpp:67