@agentprojectcontext/apx 1.58.0 → 1.60.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/package.json +1 -1
- package/src/core/agent/skills/index.js +9 -0
- package/src/core/agent/skills/inspector.js +7 -2
- package/src/core/agent/skills/policy.js +128 -0
- package/src/core/agent/super-agent.js +8 -1
- package/src/core/agent/tools/handlers/_asana.js +34 -0
- package/src/core/agent/tools/handlers/asana-create-task.js +38 -0
- package/src/core/agent/tools/handlers/asana-list-projects.js +19 -0
- package/src/core/agent/tools/handlers/asana-list-tasks.js +27 -0
- package/src/core/agent/tools/handlers/asana-update-task.js +32 -0
- package/src/core/agent/tools/handlers/list-skills.js +6 -3
- package/src/core/agent/tools/handlers/load-skill.js +9 -2
- package/src/core/agent/tools/names.js +10 -0
- package/src/core/agent/tools/registry.js +11 -0
- package/src/core/integrations/catalog.js +66 -0
- package/src/core/integrations/index.js +10 -0
- package/src/core/integrations/plugins/asana.js +231 -0
- package/src/core/integrations/sources.js +56 -0
- package/src/core/integrations/store.js +118 -0
- package/src/host/daemon/api/integrations.js +191 -0
- package/src/host/daemon/api/skills.js +301 -17
- package/src/host/daemon/api.js +2 -0
- package/src/interfaces/cli/commands/skills.js +3 -2
- package/src/interfaces/web/dist/assets/index-DFNV6BWh.js +761 -0
- package/src/interfaces/web/dist/assets/{index-DPAuXATr.js.map → index-DFNV6BWh.js.map} +1 -1
- package/src/interfaces/web/dist/assets/index-HU-Wt2l9.css +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/src/components/integrations/AsanaPlugin.tsx +275 -0
- package/src/interfaces/web/src/components/integrations/ComingSoonPlugin.tsx +44 -0
- package/src/interfaces/web/src/components/integrations/PluginCard.tsx +61 -0
- package/src/interfaces/web/src/components/integrations/PluginToolsSection.tsx +39 -0
- package/src/interfaces/web/src/components/settings/SkillsManager.tsx +465 -0
- package/src/interfaces/web/src/components/settings/SkillsSettings.tsx +49 -0
- package/src/interfaces/web/src/i18n/en.ts +67 -0
- package/src/interfaces/web/src/i18n/es.ts +67 -0
- package/src/interfaces/web/src/lib/api/integrations.ts +106 -0
- package/src/interfaces/web/src/lib/api/skills.ts +79 -8
- package/src/interfaces/web/src/lib/api.ts +1 -0
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +12 -4
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +3 -3
- package/src/interfaces/web/src/screens/project/IntegrationsTab.tsx +146 -0
- package/src/interfaces/web/src/screens/project/SkillsTab.tsx +13 -0
- package/src/interfaces/web/dist/assets/index-Cl0WXtxF.css +0 -1
- package/src/interfaces/web/dist/assets/index-DPAuXATr.js +0 -705
|
@@ -1,16 +1,36 @@
|
|
|
1
|
-
// `/skills` listing + Skill Inspector control surface
|
|
1
|
+
// `/skills` listing + enable/disable + Skill Inspector control surface.
|
|
2
2
|
//
|
|
3
|
-
// GET
|
|
4
|
-
// GET
|
|
5
|
-
// PUT
|
|
6
|
-
// POST
|
|
7
|
-
// POST
|
|
3
|
+
// GET /skills catalog annotated with enabled/private per scope
|
|
4
|
+
// GET /skills/:slug/detail full body + frontmatter for the viewer
|
|
5
|
+
// PUT /skills/enabled toggle a skill on/off (or clear) for a scope
|
|
6
|
+
// POST /skills create a user skill (online editor)
|
|
7
|
+
// POST /skills/import/zip import a skill from an uploaded .zip
|
|
8
|
+
// POST /skills/import/repo import a skill by cloning a git repo
|
|
9
|
+
// DELETE /skills/:slug delete a user skill
|
|
10
|
+
// GET /skills/inspector inspector config + index status
|
|
11
|
+
// PUT /skills/inspector toggle / tune inspector config
|
|
12
|
+
// POST /skills/index (re)build the inspector vector index
|
|
13
|
+
// POST /skills/inspect dry-run the inspector for a prompt
|
|
8
14
|
//
|
|
9
|
-
//
|
|
10
|
-
// project
|
|
11
|
-
//
|
|
12
|
-
|
|
15
|
+
// A "scope" is either "default" (the super-agent / no-project baseline) or a
|
|
16
|
+
// project's absolute path. Creating/importing with a project_path targets that
|
|
17
|
+
// project's <project>/.apc/skills/ (source "project"); without it, skills land
|
|
18
|
+
// in ~/.apx/skills/ (source "global"). Built-in skills are private: always
|
|
19
|
+
// active, never disableable or deletable. The inspector routes mirror
|
|
20
|
+
// /embeddings/* so the web admin configures the skill RAG like the memory RAG.
|
|
21
|
+
import fs from "node:fs";
|
|
22
|
+
import os from "node:os";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import { spawnSync } from "node:child_process";
|
|
25
|
+
import { listSkills, loadSkill, SKILL_LOCATIONS } from "#core/agent/skills/loader.js";
|
|
13
26
|
import { condenseSkillDescription } from "#core/agent/skills/catalog.js";
|
|
27
|
+
import { apcSkillsDir } from "#core/apc/paths.js";
|
|
28
|
+
import {
|
|
29
|
+
annotateSkills,
|
|
30
|
+
setSkillEnabled,
|
|
31
|
+
resolveScopeKey,
|
|
32
|
+
isPrivateSkill,
|
|
33
|
+
} from "#core/agent/skills/policy.js";
|
|
14
34
|
import {
|
|
15
35
|
inspectPromptForSkills,
|
|
16
36
|
INSPECTOR_DEFAULTS,
|
|
@@ -22,8 +42,63 @@ import {
|
|
|
22
42
|
} from "#core/agent/skills/index-store.js";
|
|
23
43
|
import { readConfig, writeConfig } from "#core/config/index.js";
|
|
24
44
|
|
|
45
|
+
const SLUG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
46
|
+
// Only http(s)/ssh/git git remotes — never a local path or shell metachar.
|
|
47
|
+
const REPO_URL_RE = /^(https?:\/\/|git@|ssh:\/\/|git:\/\/)[\w.@:/\-~]+$/;
|
|
48
|
+
|
|
25
49
|
const KNOWN_KEYS = Object.keys(INSPECTOR_DEFAULTS);
|
|
26
50
|
|
|
51
|
+
// Where a newly created/imported skill lands, given a scope. A project_path
|
|
52
|
+
// targets that project's .apc/skills/ (source "project"); otherwise the global
|
|
53
|
+
// ~/.apx/skills/ (source "global").
|
|
54
|
+
function targetSkillsDir(projectPath) {
|
|
55
|
+
return projectPath ? apcSkillsDir(projectPath) : SKILL_LOCATIONS.global;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function skillExists(slug, projectPath) {
|
|
59
|
+
return listSkills({ projectPath }).some((s) => s.slug === slug);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function writeSkillFile(dir, slug, description, body) {
|
|
63
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
64
|
+
const fmDesc = String(description || "").replace(/\r?\n/g, " ").trim();
|
|
65
|
+
const content =
|
|
66
|
+
`---\nname: ${slug}\ndescription: ${fmDesc}\n---\n\n${String(body || "").trim()}\n`;
|
|
67
|
+
fs.writeFileSync(path.join(dir, "SKILL.md"), content, "utf8");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Find the skill root inside an extracted/cloned tree: the dir that directly
|
|
71
|
+
// contains a SKILL.md (the tree itself, or its single top-level subdir).
|
|
72
|
+
function findSkillRoot(root) {
|
|
73
|
+
if (fs.existsSync(path.join(root, "SKILL.md"))) return root;
|
|
74
|
+
let entries;
|
|
75
|
+
try { entries = fs.readdirSync(root, { withFileTypes: true }); } catch { return null; }
|
|
76
|
+
const dirs = entries.filter((e) => e.isDirectory() && e.name !== "__MACOSX");
|
|
77
|
+
for (const d of dirs) {
|
|
78
|
+
const sub = path.join(root, d.name);
|
|
79
|
+
if (fs.existsSync(path.join(sub, "SKILL.md"))) return sub;
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function readSlugFromSkill(dir, fallback) {
|
|
85
|
+
try {
|
|
86
|
+
const raw = fs.readFileSync(path.join(dir, "SKILL.md"), "utf8");
|
|
87
|
+
const m = raw.match(/^---[\s\S]*?\bname\s*:\s*(.+?)\s*$/m);
|
|
88
|
+
if (m && SLUG_RE.test(m[1].trim())) return m[1].trim();
|
|
89
|
+
} catch { /* ignore */ }
|
|
90
|
+
return fallback;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Copy an extracted skill dir into the target skills location under <slug>/.
|
|
94
|
+
function installSkillDir(srcDir, slug, projectPath) {
|
|
95
|
+
const destBase = targetSkillsDir(projectPath);
|
|
96
|
+
const dest = path.join(destBase, slug);
|
|
97
|
+
fs.mkdirSync(destBase, { recursive: true });
|
|
98
|
+
fs.cpSync(srcDir, dest, { recursive: true });
|
|
99
|
+
return dest;
|
|
100
|
+
}
|
|
101
|
+
|
|
27
102
|
function mergedInspectorConfig(cfg) {
|
|
28
103
|
return { ...INSPECTOR_DEFAULTS, ...(cfg?.skills?.inspector || {}) };
|
|
29
104
|
}
|
|
@@ -40,17 +115,31 @@ function indexStatus() {
|
|
|
40
115
|
|
|
41
116
|
export function register(app /*, ctx */) {
|
|
42
117
|
app.get("/skills", (req, res) => {
|
|
43
|
-
const projectPath = typeof req.query?.project_path === "string"
|
|
118
|
+
const projectPath = typeof req.query?.project_path === "string" && req.query.project_path
|
|
44
119
|
? req.query.project_path
|
|
45
120
|
: undefined;
|
|
121
|
+
// A caller may ask about the "default" (super-agent) scope while still
|
|
122
|
+
// wanting project-scoped skills scanned — keep the two concerns separate.
|
|
123
|
+
const scope = typeof req.query?.scope === "string" && req.query.scope
|
|
124
|
+
? req.query.scope
|
|
125
|
+
: undefined;
|
|
46
126
|
try {
|
|
47
|
-
const
|
|
127
|
+
const cfg = readConfig();
|
|
128
|
+
const scopeKey = resolveScopeKey(scope || projectPath);
|
|
129
|
+
const annotated = annotateSkills(listSkills({ projectPath }), {
|
|
130
|
+
config: cfg,
|
|
131
|
+
projectPath: scopeKey === "default" ? undefined : scopeKey,
|
|
132
|
+
});
|
|
48
133
|
res.json({
|
|
49
|
-
count:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
134
|
+
count: annotated.length,
|
|
135
|
+
scope: scopeKey,
|
|
136
|
+
skills: annotated.map((s) => ({
|
|
137
|
+
slug: s.slug,
|
|
138
|
+
source: s.source,
|
|
139
|
+
description: condenseSkillDescription(s.description),
|
|
140
|
+
enabled: s.enabled,
|
|
141
|
+
private: s.private,
|
|
142
|
+
overridden: s.overridden,
|
|
54
143
|
})),
|
|
55
144
|
});
|
|
56
145
|
} catch (e) {
|
|
@@ -58,6 +147,201 @@ export function register(app /*, ctx */) {
|
|
|
58
147
|
}
|
|
59
148
|
});
|
|
60
149
|
|
|
150
|
+
// ---- Full detail (viewer) ----------------------------------------------
|
|
151
|
+
|
|
152
|
+
app.get("/skills/:slug/detail", (req, res) => {
|
|
153
|
+
try {
|
|
154
|
+
const projectPath = typeof req.query?.project_path === "string" && req.query.project_path
|
|
155
|
+
? req.query.project_path
|
|
156
|
+
: undefined;
|
|
157
|
+
const skill = loadSkill(req.params.slug, { projectPath });
|
|
158
|
+
const cfg = readConfig();
|
|
159
|
+
const [annotated] = annotateSkills([skill], { config: cfg, projectPath });
|
|
160
|
+
res.json({
|
|
161
|
+
slug: skill.slug,
|
|
162
|
+
source: skill.source,
|
|
163
|
+
description: skill.description,
|
|
164
|
+
frontmatter: skill.frontmatter,
|
|
165
|
+
body: skill.body,
|
|
166
|
+
file: skill.file,
|
|
167
|
+
enabled: annotated?.enabled ?? true,
|
|
168
|
+
private: annotated?.private ?? false,
|
|
169
|
+
overridden: annotated?.overridden ?? false,
|
|
170
|
+
});
|
|
171
|
+
} catch (e) {
|
|
172
|
+
res.status(404).json({ error: e.message });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// ---- Enable / disable per scope ----------------------------------------
|
|
177
|
+
|
|
178
|
+
app.put("/skills/enabled", (req, res) => {
|
|
179
|
+
try {
|
|
180
|
+
const { slug, enabled, scope, project_path } = req.body || {};
|
|
181
|
+
if (!slug || typeof slug !== "string") {
|
|
182
|
+
return res.status(400).json({ error: "slug required" });
|
|
183
|
+
}
|
|
184
|
+
const cfg = readConfig();
|
|
185
|
+
const all = listSkills({ projectPath: project_path });
|
|
186
|
+
const target = all.find((s) => s.slug === slug);
|
|
187
|
+
if (!target) return res.status(404).json({ error: `skill "${slug}" not found` });
|
|
188
|
+
if (isPrivateSkill(target)) {
|
|
189
|
+
return res.status(403).json({ error: `skill "${slug}" is private (built-in) and always active` });
|
|
190
|
+
}
|
|
191
|
+
// enabled: boolean sets an override; null/undefined clears it (inherit).
|
|
192
|
+
const value = enabled === null || enabled === undefined ? null : !!enabled;
|
|
193
|
+
setSkillEnabled(cfg, { slug, enabled: value, scope, projectPath: project_path });
|
|
194
|
+
writeConfig(cfg);
|
|
195
|
+
const scopeKey = resolveScopeKey(scope || project_path);
|
|
196
|
+
res.json({ ok: true, slug, scope: scopeKey, enabled: value });
|
|
197
|
+
} catch (e) {
|
|
198
|
+
res.status(500).json({ error: e.message });
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// ---- Create / import user skills ---------------------------------------
|
|
203
|
+
|
|
204
|
+
// Online editor: write a SKILL.md from slug + description + body.
|
|
205
|
+
app.post("/skills", (req, res) => {
|
|
206
|
+
try {
|
|
207
|
+
const { slug, description, body, project_path } = req.body || {};
|
|
208
|
+
if (!slug || typeof slug !== "string" || !SLUG_RE.test(slug)) {
|
|
209
|
+
return res.status(400).json({ error: "slug required (lowercase letters, digits, dashes)" });
|
|
210
|
+
}
|
|
211
|
+
if (skillExists(slug, project_path)) {
|
|
212
|
+
return res.status(409).json({ error: `a skill named "${slug}" already exists in this scope` });
|
|
213
|
+
}
|
|
214
|
+
const dir = path.join(targetSkillsDir(project_path), slug);
|
|
215
|
+
writeSkillFile(dir, slug, description, body);
|
|
216
|
+
res.status(201).json({ ok: true, slug, source: project_path ? "project" : "global" });
|
|
217
|
+
} catch (e) {
|
|
218
|
+
res.status(500).json({ error: e.message });
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Import from an uploaded .zip (sent as base64 in JSON — skills are tiny).
|
|
223
|
+
app.post("/skills/import/zip", (req, res) => {
|
|
224
|
+
let tmp;
|
|
225
|
+
try {
|
|
226
|
+
const { data, project_path } = req.body || {};
|
|
227
|
+
if (!data || typeof data !== "string") {
|
|
228
|
+
return res.status(400).json({ error: "zip data (base64) required" });
|
|
229
|
+
}
|
|
230
|
+
const buf = Buffer.from(data.replace(/^data:.*;base64,/, ""), "base64");
|
|
231
|
+
if (!buf.length) return res.status(400).json({ error: "empty zip" });
|
|
232
|
+
|
|
233
|
+
tmp = fs.mkdtempSync(path.join(os.tmpdir(), "apx-skill-zip-"));
|
|
234
|
+
const zipPath = path.join(tmp, "skill.zip");
|
|
235
|
+
fs.writeFileSync(zipPath, buf);
|
|
236
|
+
const out = path.join(tmp, "out");
|
|
237
|
+
fs.mkdirSync(out);
|
|
238
|
+
const unzip = spawnSync("unzip", ["-qq", "-o", zipPath, "-d", out], { encoding: "utf8" });
|
|
239
|
+
if (unzip.status !== 0) {
|
|
240
|
+
return res.status(400).json({ error: `unzip failed: ${(unzip.stderr || "bad archive").trim()}` });
|
|
241
|
+
}
|
|
242
|
+
const root = findSkillRoot(out);
|
|
243
|
+
if (!root) return res.status(400).json({ error: "no SKILL.md found in the zip" });
|
|
244
|
+
const slug = readSlugFromSkill(root, path.basename(root));
|
|
245
|
+
if (!SLUG_RE.test(slug)) return res.status(400).json({ error: `invalid skill name "${slug}"` });
|
|
246
|
+
if (skillExists(slug, project_path)) {
|
|
247
|
+
return res.status(409).json({ error: `a skill named "${slug}" already exists in this scope` });
|
|
248
|
+
}
|
|
249
|
+
installSkillDir(root, slug, project_path);
|
|
250
|
+
res.status(201).json({ ok: true, slug, source: project_path ? "project" : "global" });
|
|
251
|
+
} catch (e) {
|
|
252
|
+
res.status(500).json({ error: e.message });
|
|
253
|
+
} finally {
|
|
254
|
+
if (tmp) fs.rmSync(tmp, { recursive: true, force: true });
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Import by cloning a git repo. The repo root (or its single skill subdir)
|
|
259
|
+
// must contain a SKILL.md.
|
|
260
|
+
app.post("/skills/import/repo", (req, res) => {
|
|
261
|
+
let tmp;
|
|
262
|
+
try {
|
|
263
|
+
const { url, project_path } = req.body || {};
|
|
264
|
+
if (!url || typeof url !== "string" || !REPO_URL_RE.test(url.trim())) {
|
|
265
|
+
return res.status(400).json({ error: "a valid git URL (https/ssh/git) is required" });
|
|
266
|
+
}
|
|
267
|
+
tmp = fs.mkdtempSync(path.join(os.tmpdir(), "apx-skill-repo-"));
|
|
268
|
+
const clone = path.join(tmp, "repo");
|
|
269
|
+
// Array args (no shell) — url is regex-validated above.
|
|
270
|
+
const git = spawnSync("git", ["clone", "--depth", "1", url.trim(), clone], {
|
|
271
|
+
encoding: "utf8",
|
|
272
|
+
timeout: 60_000,
|
|
273
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
274
|
+
});
|
|
275
|
+
if (git.status !== 0) {
|
|
276
|
+
return res.status(400).json({ error: `git clone failed: ${(git.stderr || "").trim().slice(0, 300)}` });
|
|
277
|
+
}
|
|
278
|
+
const root = findSkillRoot(clone);
|
|
279
|
+
if (!root) return res.status(400).json({ error: "no SKILL.md found in the repo" });
|
|
280
|
+
const slug = readSlugFromSkill(root, path.basename(root));
|
|
281
|
+
if (!SLUG_RE.test(slug)) return res.status(400).json({ error: `invalid skill name "${slug}"` });
|
|
282
|
+
if (skillExists(slug, project_path)) {
|
|
283
|
+
return res.status(409).json({ error: `a skill named "${slug}" already exists in this scope` });
|
|
284
|
+
}
|
|
285
|
+
// Strip the repo's .git before installing so we don't nest a git dir.
|
|
286
|
+
fs.rmSync(path.join(root, ".git"), { recursive: true, force: true });
|
|
287
|
+
installSkillDir(root, slug, project_path);
|
|
288
|
+
res.status(201).json({ ok: true, slug, source: project_path ? "project" : "global" });
|
|
289
|
+
} catch (e) {
|
|
290
|
+
res.status(500).json({ error: e.message });
|
|
291
|
+
} finally {
|
|
292
|
+
if (tmp) fs.rmSync(tmp, { recursive: true, force: true });
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
app.delete("/skills/:slug", (req, res) => {
|
|
297
|
+
try {
|
|
298
|
+
const slug = req.params.slug;
|
|
299
|
+
if (!slug || !SLUG_RE.test(slug)) {
|
|
300
|
+
return res.status(400).json({ error: "invalid slug" });
|
|
301
|
+
}
|
|
302
|
+
const projectPath = typeof req.query?.project_path === "string" && req.query.project_path
|
|
303
|
+
? req.query.project_path
|
|
304
|
+
: undefined;
|
|
305
|
+
let entry;
|
|
306
|
+
try { entry = loadSkill(slug, { projectPath }); } catch { entry = null; }
|
|
307
|
+
if (!entry) return res.status(404).json({ error: `skill "${slug}" not found` });
|
|
308
|
+
// Only user-managed skills (global ~/.apx/skills or project .apc/skills)
|
|
309
|
+
// can be deleted; built-in ones ship with apx.
|
|
310
|
+
if (entry.source !== "global" && entry.source !== "project") {
|
|
311
|
+
return res.status(403).json({ error: `built-in skill "${slug}" cannot be deleted (it ships with apx)` });
|
|
312
|
+
}
|
|
313
|
+
// The skill dir is the parent of its SKILL.md (dir-style) — never the
|
|
314
|
+
// shared skills root itself.
|
|
315
|
+
const dir = path.dirname(entry.file);
|
|
316
|
+
const roots = [SKILL_LOCATIONS.global, projectPath ? apcSkillsDir(projectPath) : null]
|
|
317
|
+
.filter(Boolean)
|
|
318
|
+
.map((p) => path.resolve(p));
|
|
319
|
+
if (roots.includes(path.resolve(dir))) {
|
|
320
|
+
// Flat-style <slug>.md — remove just the file.
|
|
321
|
+
fs.rmSync(entry.file, { force: true });
|
|
322
|
+
} else {
|
|
323
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
324
|
+
}
|
|
325
|
+
// Drop any dangling enable/disable overrides for this slug.
|
|
326
|
+
const cfg = readConfig();
|
|
327
|
+
const pol = cfg?.skills?.policy;
|
|
328
|
+
if (pol && typeof pol === "object") {
|
|
329
|
+
let touched = false;
|
|
330
|
+
for (const scopeKey of Object.keys(pol)) {
|
|
331
|
+
if (pol[scopeKey] && slug in pol[scopeKey]) {
|
|
332
|
+
delete pol[scopeKey][slug];
|
|
333
|
+
if (Object.keys(pol[scopeKey]).length === 0) delete pol[scopeKey];
|
|
334
|
+
touched = true;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (touched) writeConfig(cfg);
|
|
338
|
+
}
|
|
339
|
+
res.json({ ok: true, slug });
|
|
340
|
+
} catch (e) {
|
|
341
|
+
res.status(500).json({ error: e.message });
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
61
345
|
// ---- Inspector config + status -----------------------------------------
|
|
62
346
|
|
|
63
347
|
app.get("/skills/inspector", (_req, res) => {
|
package/src/host/daemon/api.js
CHANGED
|
@@ -18,6 +18,7 @@ import { register as registerProjects } from "./api/projects.js";
|
|
|
18
18
|
import { register as registerAgents } from "./api/agents.js";
|
|
19
19
|
import { register as registerSessions } from "./api/sessions.js";
|
|
20
20
|
import { register as registerMcps } from "./api/mcps.js";
|
|
21
|
+
import { register as registerIntegrations } from "./api/integrations.js";
|
|
21
22
|
import { register as registerVars } from "./api/vars.js";
|
|
22
23
|
import { register as registerMessages } from "./api/messages.js";
|
|
23
24
|
import { register as registerTelegram } from "./api/telegram.js";
|
|
@@ -109,6 +110,7 @@ export function buildApi({
|
|
|
109
110
|
registerAgents(app, ctx);
|
|
110
111
|
registerSessions(app, ctx);
|
|
111
112
|
registerMcps(app, ctx);
|
|
113
|
+
registerIntegrations(app, ctx);
|
|
112
114
|
registerVars(app, ctx);
|
|
113
115
|
registerMessages(app, ctx);
|
|
114
116
|
registerEngines(app, ctx);
|
|
@@ -215,10 +215,11 @@ export async function cmdSkillsList(args = {}) {
|
|
|
215
215
|
console.log("(no skills available)");
|
|
216
216
|
return;
|
|
217
217
|
}
|
|
218
|
-
console.log(`SLUG`.padEnd(28) + "SOURCE".padEnd(10) + "DESCRIPTION");
|
|
218
|
+
console.log(`SLUG`.padEnd(28) + "SOURCE".padEnd(10) + "STATE".padEnd(10) + "DESCRIPTION");
|
|
219
219
|
for (const s of out.skills) {
|
|
220
220
|
const desc = (s.description || "").slice(0, 70);
|
|
221
|
-
|
|
221
|
+
const state = s.private ? "private" : s.enabled === false ? "off" : "on";
|
|
222
|
+
console.log(s.slug.padEnd(28) + (s.source || "?").padEnd(10) + state.padEnd(10) + desc);
|
|
222
223
|
}
|
|
223
224
|
return;
|
|
224
225
|
}
|