@agentprojectcontext/apx 1.6.0 → 1.8.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.
Files changed (41) hide show
  1. package/README.md +4 -0
  2. package/package.json +1 -1
  3. package/src/cli/commands/config.js +23 -0
  4. package/src/cli/commands/messages.js +45 -0
  5. package/src/cli/commands/routine.js +27 -2
  6. package/src/cli/commands/setup.js +2 -2
  7. package/src/cli/index.js +969 -3
  8. package/src/core/apc-context-skill.md +3 -0
  9. package/src/core/apx-skill.md +30 -0
  10. package/src/core/config.js +2 -0
  11. package/src/core/mascot.js +5 -7
  12. package/src/core/messages-store.js +94 -20
  13. package/src/core/routines-store.js +3 -1
  14. package/src/daemon/api.js +3 -3
  15. package/src/daemon/index.js +38 -2
  16. package/src/daemon/plugins/telegram.js +32 -2
  17. package/src/daemon/routines.js +64 -2
  18. package/src/daemon/super-agent-tools/helpers.js +120 -0
  19. package/src/daemon/super-agent-tools/index.js +56 -0
  20. package/src/daemon/super-agent-tools/tools/add-project.js +36 -0
  21. package/src/daemon/super-agent-tools/tools/call-agent.js +45 -0
  22. package/src/daemon/super-agent-tools/tools/call-mcp.js +30 -0
  23. package/src/daemon/super-agent-tools/tools/call-runtime.js +107 -0
  24. package/src/daemon/super-agent-tools/tools/edit-file.js +44 -0
  25. package/src/daemon/super-agent-tools/tools/import-agent.js +48 -0
  26. package/src/daemon/super-agent-tools/tools/list-agents.js +36 -0
  27. package/src/daemon/super-agent-tools/tools/list-files.js +38 -0
  28. package/src/daemon/super-agent-tools/tools/list-mcps.js +48 -0
  29. package/src/daemon/super-agent-tools/tools/list-projects.js +20 -0
  30. package/src/daemon/super-agent-tools/tools/list-vault-agents.js +18 -0
  31. package/src/daemon/super-agent-tools/tools/read-agent-memory.js +28 -0
  32. package/src/daemon/super-agent-tools/tools/read-file.js +33 -0
  33. package/src/daemon/super-agent-tools/tools/run-shell.js +86 -0
  34. package/src/daemon/super-agent-tools/tools/search-messages.js +34 -0
  35. package/src/daemon/super-agent-tools/tools/send-telegram.js +30 -0
  36. package/src/daemon/super-agent-tools/tools/set-identity.js +35 -0
  37. package/src/daemon/super-agent-tools/tools/set-permission-mode.js +32 -0
  38. package/src/daemon/super-agent-tools/tools/tail-messages.js +39 -0
  39. package/src/daemon/super-agent-tools/tools/write-file.js +33 -0
  40. package/src/daemon/super-agent-tools.js +1 -539
  41. package/src/daemon/super-agent.js +56 -7
@@ -0,0 +1,120 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export function projectMeta(projects, entry) {
5
+ const meta = projects.list().find((p) => p.id === entry.id);
6
+ return {
7
+ id: entry.id,
8
+ name: meta?.name || path.basename(entry.path),
9
+ path: entry.path,
10
+ };
11
+ }
12
+
13
+ export function resolveProject(projects, target, { allowMulti = false } = {}) {
14
+ if (target === undefined || target === null || target === "") {
15
+ if (allowMulti) return null;
16
+ const defaultProject = projects.get(0);
17
+ if (defaultProject) return defaultProject;
18
+ const all = projects.list();
19
+ if (all.length === 1) return projects.get(all[0].id);
20
+ throw new Error(`multiple projects registered (${all.length}); specify project=<id|name|path>`);
21
+ }
22
+
23
+ const tgt = String(target);
24
+ if (tgt.toLowerCase() === "default") {
25
+ const defaultProject = projects.get(0);
26
+ if (!defaultProject) throw new Error("default project not available");
27
+ return defaultProject;
28
+ }
29
+
30
+ if (typeof target === "number" || /^\d+$/.test(tgt)) {
31
+ const entry = projects.get(parseInt(tgt, 10));
32
+ if (!entry) throw new Error(`project id ${target} not found`);
33
+ return entry;
34
+ }
35
+
36
+ const all = projects.list();
37
+ const byPath = all.find((p) => p.path === path.resolve(tgt));
38
+ if (byPath) return projects.get(byPath.id);
39
+
40
+ const byName = all.find((p) => p.name === tgt);
41
+ if (byName) return projects.get(byName.id);
42
+
43
+ const tgtLow = tgt.toLowerCase();
44
+ const fuzzy = all.filter(
45
+ (p) => p.name.toLowerCase().includes(tgtLow) || p.path.toLowerCase().includes(tgtLow)
46
+ );
47
+ if (fuzzy.length === 1) return projects.get(fuzzy[0].id);
48
+ if (fuzzy.length > 1) {
49
+ throw new Error(`project "${tgt}" is ambiguous; matches: ${fuzzy.map((p) => p.name).join(", ")}`);
50
+ }
51
+ throw new Error(`project "${tgt}" not found`);
52
+ }
53
+
54
+ export function safePathJoin(root, sub = ".") {
55
+ const target = path.resolve(root, sub || ".");
56
+ const rootResolved = path.resolve(root);
57
+ if (target !== rootResolved && !target.startsWith(rootResolved + path.sep)) {
58
+ throw new Error(`path "${sub}" escapes the project root`);
59
+ }
60
+ return target;
61
+ }
62
+
63
+ export function skillsFromFields(fields = {}) {
64
+ if (Array.isArray(fields.Skills)) return fields.Skills;
65
+ return (fields.Skills || "").split(",").map((s) => s.trim()).filter(Boolean);
66
+ }
67
+
68
+ export function agentRow(agent) {
69
+ return {
70
+ slug: agent.slug,
71
+ role: agent.fields.Role || null,
72
+ model: agent.fields.Model || null,
73
+ language: agent.fields.Language || null,
74
+ description: agent.fields.Description || null,
75
+ skills: skillsFromFields(agent.fields),
76
+ };
77
+ }
78
+
79
+ export function buildAgentSystem(project, agent) {
80
+ const parts = [];
81
+ if (agent.fields.Description) parts.push(agent.fields.Description);
82
+ if (agent.fields.Role) parts.push(`Role: ${agent.fields.Role}`);
83
+ if (agent.fields.Language) parts.push(`Default language: ${agent.fields.Language}`);
84
+
85
+ const memPath = path.join(project.path, ".apc", "agents", agent.slug, "memory.md");
86
+ if (fs.existsSync(memPath)) parts.push("## Memory\n" + fs.readFileSync(memPath, "utf8"));
87
+
88
+ const apxSkill = path.join(project.path, ".apc", "skills", "apx.md");
89
+ if (fs.existsSync(apxSkill)) parts.push("## APX\n" + fs.readFileSync(apxSkill, "utf8"));
90
+
91
+ for (const skill of skillsFromFields(agent.fields)) {
92
+ const skillPath = path.join(project.path, ".apc", "skills", `${skill}.md`);
93
+ if (fs.existsSync(skillPath)) parts.push(`## Skill: ${skill}\n` + fs.readFileSync(skillPath, "utf8"));
94
+ }
95
+
96
+ return parts.join("\n\n");
97
+ }
98
+
99
+ export function createPermissionGuard(globalConfig = {}, { implicitConfirmation = false } = {}) {
100
+ const permissionMode = globalConfig.super_agent?.permission_mode || "automatico";
101
+ const allowedTools = new Set(globalConfig.super_agent?.allowed_tools || []);
102
+
103
+ return function requirePermission(tool, { dangerous = false, confirmed = false } = {}) {
104
+ const ok = confirmed || implicitConfirmation;
105
+ if (permissionMode === "total") return;
106
+ if (permissionMode === "permiso" && !allowedTools.has(tool) && !ok) {
107
+ throw new Error(`requires_confirmation: permission_mode=permiso blocks ${tool}`);
108
+ }
109
+ if (permissionMode === "automatico" && dangerous && !ok) {
110
+ throw new Error(`requires_confirmation: permission_mode=automatico requires confirmation for ${tool}`);
111
+ }
112
+ };
113
+ }
114
+
115
+ export function confirmedProperty(description) {
116
+ return {
117
+ type: "boolean",
118
+ description: description || "true only after explicit user confirmation for this exact action",
119
+ };
120
+ }
@@ -0,0 +1,56 @@
1
+ import listProjects from "./tools/list-projects.js";
2
+ import listAgents from "./tools/list-agents.js";
3
+ import listVaultAgents from "./tools/list-vault-agents.js";
4
+ import importAgent from "./tools/import-agent.js";
5
+ import addProject from "./tools/add-project.js";
6
+ import listMcps from "./tools/list-mcps.js";
7
+ import readAgentMemory from "./tools/read-agent-memory.js";
8
+ import listFiles from "./tools/list-files.js";
9
+ import readFile from "./tools/read-file.js";
10
+ import writeFile from "./tools/write-file.js";
11
+ import editFile from "./tools/edit-file.js";
12
+ import runShell from "./tools/run-shell.js";
13
+ import tailMessages from "./tools/tail-messages.js";
14
+ import searchMessages from "./tools/search-messages.js";
15
+ import callAgent from "./tools/call-agent.js";
16
+ import callMcp from "./tools/call-mcp.js";
17
+ import callRuntime from "./tools/call-runtime.js";
18
+ import sendTelegram from "./tools/send-telegram.js";
19
+ import setIdentity from "./tools/set-identity.js";
20
+ import setPermissionMode from "./tools/set-permission-mode.js";
21
+ import { createPermissionGuard } from "./helpers.js";
22
+
23
+ const TOOLS = [
24
+ listProjects,
25
+ listAgents,
26
+ listVaultAgents,
27
+ importAgent,
28
+ addProject,
29
+ listMcps,
30
+ readAgentMemory,
31
+ listFiles,
32
+ readFile,
33
+ writeFile,
34
+ editFile,
35
+ runShell,
36
+ tailMessages,
37
+ searchMessages,
38
+ callAgent,
39
+ callMcp,
40
+ callRuntime,
41
+ sendTelegram,
42
+ setIdentity,
43
+ setPermissionMode,
44
+ ];
45
+
46
+ export const TOOL_SCHEMAS = TOOLS.map((tool) => tool.schema);
47
+
48
+ export function makeToolHandlers(ctx) {
49
+ const toolCtx = {
50
+ ...ctx,
51
+ requirePermission: createPermissionGuard(ctx.globalConfig || {}, {
52
+ implicitConfirmation: !!ctx.implicitConfirmation,
53
+ }),
54
+ };
55
+ return Object.fromEntries(TOOLS.map((tool) => [tool.name, tool.makeHandler(toolCtx)]));
56
+ }
@@ -0,0 +1,36 @@
1
+ import path from "node:path";
2
+ import { readConfig, addProject as addProjectInConfig } from "../../../core/config.js";
3
+ import { confirmedProperty, projectMeta } from "../helpers.js";
4
+
5
+ export default {
6
+ name: "add_project",
7
+ schema: {
8
+ type: "function",
9
+ function: {
10
+ name: "add_project",
11
+ description: "Register an existing APC project path with the APX daemon. The path must contain AGENTS.md and .apc/project.json.",
12
+ parameters: {
13
+ type: "object",
14
+ properties: {
15
+ path: { type: "string", description: "absolute or relative filesystem path to an APC project" },
16
+ confirmed: confirmedProperty("true only after explicit user confirmation for this exact project registration"),
17
+ },
18
+ required: ["path"],
19
+ },
20
+ },
21
+ },
22
+ makeHandler: ({ projects, requirePermission }) => ({ path: projectPath, confirmed = false }) => {
23
+ requirePermission("add_project", { dangerous: true, confirmed });
24
+ if (!projectPath) throw new Error("add_project: path required");
25
+
26
+ const cfg = readConfig();
27
+ const result = addProjectInConfig(cfg, projectPath);
28
+ const p = projects.register(result.project.path);
29
+ return {
30
+ ok: true,
31
+ added: result.added,
32
+ project: projectMeta(projects, p),
33
+ normalized_path: path.resolve(projectPath),
34
+ };
35
+ },
36
+ };
@@ -0,0 +1,45 @@
1
+ import { callEngine } from "../../engines/index.js";
2
+ import { readAgents } from "../../../core/parser.js";
3
+ import { buildAgentSystem, resolveProject } from "../helpers.js";
4
+
5
+ export default {
6
+ name: "call_agent",
7
+ schema: {
8
+ type: "function",
9
+ function: {
10
+ name: "call_agent",
11
+ description: "Run a one-shot prompt through a project agent's configured LLM engine.",
12
+ parameters: {
13
+ type: "object",
14
+ properties: {
15
+ project: { type: "string" },
16
+ agent: { type: "string", description: "agent slug" },
17
+ prompt: { type: "string" },
18
+ },
19
+ required: ["agent", "prompt"],
20
+ },
21
+ },
22
+ },
23
+ makeHandler: ({ projects, globalConfig }) => async ({ project, agent: slug, prompt }) => {
24
+ const p = resolveProject(projects, project);
25
+ const agent = readAgents(p.path).find((a) => a.slug === slug);
26
+ if (!agent) throw new Error(`agent ${slug} not found`);
27
+ if (!agent.fields.Model) throw new Error(`agent ${slug} has no model`);
28
+
29
+ const result = await callEngine({
30
+ modelId: agent.fields.Model,
31
+ system: buildAgentSystem(p, agent),
32
+ messages: [{ role: "user", content: prompt }],
33
+ config: p.config || globalConfig,
34
+ });
35
+ p.logMessage({
36
+ agent_slug: slug,
37
+ channel: "engine",
38
+ direction: "out",
39
+ author: slug,
40
+ body: result.text,
41
+ meta: { invoked_by: "super_agent_tool", usage: result.usage },
42
+ });
43
+ return { text: result.text, usage: result.usage };
44
+ },
45
+ };
@@ -0,0 +1,30 @@
1
+ import { confirmedProperty, resolveProject } from "../helpers.js";
2
+
3
+ export default {
4
+ name: "call_mcp",
5
+ schema: {
6
+ type: "function",
7
+ function: {
8
+ name: "call_mcp",
9
+ description: "Call a tool on an MCP server registered in default or a project. Args is a JSON object.",
10
+ parameters: {
11
+ type: "object",
12
+ properties: {
13
+ project: { type: "string" },
14
+ mcp: { type: "string", description: "MCP server name" },
15
+ tool: { type: "string", description: "tool name on that MCP" },
16
+ args: { type: "object", description: "arguments object" },
17
+ confirmed: confirmedProperty("true only after explicit user confirmation for this exact MCP call"),
18
+ },
19
+ required: ["mcp", "tool"],
20
+ },
21
+ },
22
+ },
23
+ makeHandler: ({ projects, registries, requirePermission }) => async ({ project, mcp, tool, args = {}, confirmed = false }) => {
24
+ requirePermission("call_mcp", { dangerous: true, confirmed });
25
+ const p = resolveProject(projects, project);
26
+ if (!registries) throw new Error("MCP registry unavailable");
27
+ const registry = registries.for ? registries.for(p) : registries.ensure(p);
28
+ return registry.call(mcp, tool, args);
29
+ },
30
+ };
@@ -0,0 +1,107 @@
1
+ import { readAgents } from "../../../core/parser.js";
2
+ import { getRuntime, RUNTIME_IDS } from "../../runtimes/index.js";
3
+ import { buildAgentSystem, confirmedProperty, resolveProject } from "../helpers.js";
4
+
5
+ function resolveProjectForAgent(projects, project, slug) {
6
+ if (project) return resolveProject(projects, project);
7
+
8
+ const defaultProject = projects.get(0);
9
+ if (defaultProject && readAgents(defaultProject.path).find((a) => a.slug === slug)) {
10
+ return defaultProject;
11
+ }
12
+
13
+ const matches = [];
14
+ for (const entry of projects.list()) {
15
+ const p = projects.get(entry.id);
16
+ if (readAgents(p.path).find((a) => a.slug === slug)) matches.push(p);
17
+ }
18
+ if (matches.length === 1) return matches[0];
19
+ if (defaultProject) return defaultProject;
20
+ return resolveProject(projects, project);
21
+ }
22
+
23
+ export default {
24
+ name: "call_runtime",
25
+ schema: {
26
+ type: "function",
27
+ function: {
28
+ name: "call_runtime",
29
+ description: "Spawn an external CLI runtime (Claude Code, Codex, OpenCode, Aider) impersonating an APC agent.",
30
+ parameters: {
31
+ type: "object",
32
+ properties: {
33
+ project: { type: "string" },
34
+ agent: { type: "string", description: "APC agent slug from AGENTS.md, not runtime name" },
35
+ runtime: {
36
+ type: "string",
37
+ enum: ["claude-code", "codex", "opencode", "aider"],
38
+ description: "external CLI runtime",
39
+ },
40
+ prompt: { type: "string" },
41
+ timeout_s: { type: "integer", description: "seconds before SIGTERM; default 300" },
42
+ confirmed: confirmedProperty("true only after explicit user confirmation for this exact runtime command"),
43
+ },
44
+ required: ["agent", "runtime", "prompt"],
45
+ },
46
+ },
47
+ },
48
+ makeHandler: ({ projects, requirePermission }) => async ({ project, agent: slug, runtime, prompt, timeout_s = 300, confirmed = false }) => {
49
+ requirePermission("call_runtime", { dangerous: true, confirmed });
50
+
51
+ const p = resolveProjectForAgent(projects, project, slug);
52
+ const agent = readAgents(p.path).find((a) => a.slug === slug);
53
+ if (!agent) {
54
+ const directory = projects.list().map((entry) => ({
55
+ project: entry.name,
56
+ kind: entry.id === 0 ? "default" : "project",
57
+ path: entry.path,
58
+ agents: readAgents(projects.get(entry.id).path).map((a) => a.slug),
59
+ }));
60
+ return { error: `agent "${slug}" not found in selected project`, directory };
61
+ }
62
+
63
+ let rt;
64
+ try {
65
+ rt = getRuntime(runtime);
66
+ } catch (e) {
67
+ return { error: `${e.message}. Available runtimes: ${RUNTIME_IDS.join(", ")}` };
68
+ }
69
+
70
+ const r = await rt.run({
71
+ system: buildAgentSystem(p, agent),
72
+ prompt,
73
+ cwd: p.path,
74
+ timeoutMs: timeout_s * 1000,
75
+ });
76
+
77
+ p.logMessage({
78
+ agent_slug: slug,
79
+ channel: "runtime",
80
+ direction: "in",
81
+ author: "user",
82
+ body: prompt,
83
+ meta: { runtime, invoked_by: "super_agent_tool" },
84
+ });
85
+ p.logMessage({
86
+ agent_slug: slug,
87
+ channel: "runtime",
88
+ direction: "out",
89
+ author: slug,
90
+ body: r.output || "",
91
+ meta: {
92
+ runtime,
93
+ exit_code: r.exitCode,
94
+ external_session_path: r.externalSessionPath || null,
95
+ invoked_by: "super_agent_tool",
96
+ },
97
+ });
98
+
99
+ return {
100
+ runtime,
101
+ exit_code: r.exitCode,
102
+ output: (r.output || "").slice(0, 4000),
103
+ truncated: (r.output || "").length > 4000,
104
+ external_session_path: r.externalSessionPath || null,
105
+ };
106
+ },
107
+ };
@@ -0,0 +1,44 @@
1
+ import fs from "node:fs";
2
+ import { confirmedProperty, resolveProject, safePathJoin } from "../helpers.js";
3
+
4
+ export default {
5
+ name: "edit_file",
6
+ schema: {
7
+ type: "function",
8
+ function: {
9
+ name: "edit_file",
10
+ description: "Edit a UTF-8 text file inside default or a project by replacing one exact string with another.",
11
+ parameters: {
12
+ type: "object",
13
+ properties: {
14
+ project: { type: "string" },
15
+ path: { type: "string", description: "relative path inside the project" },
16
+ search: { type: "string", description: "exact text to replace" },
17
+ replace: { type: "string", description: "replacement text" },
18
+ all: { type: "boolean", description: "replace all matches; default false replaces one match" },
19
+ confirmed: confirmedProperty("true only after explicit user confirmation for this exact file edit"),
20
+ },
21
+ required: ["path", "search", "replace"],
22
+ },
23
+ },
24
+ },
25
+ makeHandler: ({ projects, requirePermission }) => ({ project, path, search, replace, all = false, confirmed = false }) => {
26
+ requirePermission("edit_file", { dangerous: true, confirmed });
27
+ if (!path) throw new Error("edit_file: path required");
28
+ if (!search) throw new Error("edit_file: search required");
29
+
30
+ const p = resolveProject(projects, project);
31
+ const target = safePathJoin(p.path, path);
32
+ if (!fs.existsSync(target)) throw new Error(`file not found: ${path}`);
33
+ const before = fs.readFileSync(target, "utf8");
34
+ const matches = before.split(search).length - 1;
35
+ if (matches === 0) throw new Error("search text not found");
36
+ if (!all && matches > 1) {
37
+ throw new Error(`search text appears ${matches} times; set all=true or use a more specific search`);
38
+ }
39
+
40
+ const after = all ? before.split(search).join(replace) : before.replace(search, replace);
41
+ fs.writeFileSync(target, after, "utf8");
42
+ return { ok: true, path: target, replacements: all ? matches : 1 };
43
+ },
44
+ };
@@ -0,0 +1,48 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { readVaultAgents, VAULT_DIR } from "../../../core/parser.js";
4
+ import { addImportedAgent, ensureAgentDir, regenerateAgentsMd } from "../../../core/scaffold.js";
5
+ import { confirmedProperty, projectMeta, resolveProject } from "../helpers.js";
6
+
7
+ export default {
8
+ name: "import_agent",
9
+ schema: {
10
+ type: "function",
11
+ function: {
12
+ name: "import_agent",
13
+ description: "Import an agent template from the APX vault into default or a registered project.",
14
+ parameters: {
15
+ type: "object",
16
+ properties: {
17
+ project: { type: "string", description: "project id/name/path; omit or use 'default' for ~/.apx/projects/default" },
18
+ agent: { type: "string", description: "agent slug from list_vault_agents" },
19
+ confirmed: confirmedProperty("true only after explicit user confirmation for this exact import"),
20
+ },
21
+ required: ["agent"],
22
+ },
23
+ },
24
+ },
25
+ makeHandler: ({ projects, requirePermission }) => ({ project, agent: slug, confirmed = false }) => {
26
+ requirePermission("import_agent", { dangerous: true, confirmed });
27
+ if (!slug) throw new Error("import_agent: agent required");
28
+
29
+ const vaultPath = path.join(VAULT_DIR, `${slug}.md`);
30
+ if (!fs.existsSync(vaultPath)) {
31
+ const available = readVaultAgents().map((a) => a.slug).join(", ") || "(none)";
32
+ throw new Error(`agent "${slug}" not found in vault. Available: ${available}`);
33
+ }
34
+
35
+ const p = resolveProject(projects, project || "default");
36
+ addImportedAgent(p.path, slug);
37
+ ensureAgentDir(p.path, slug);
38
+ regenerateAgentsMd(p.path);
39
+ projects.rebuild(p.id);
40
+
41
+ return {
42
+ ok: true,
43
+ agent: slug,
44
+ project: projectMeta(projects, p),
45
+ source: vaultPath,
46
+ };
47
+ },
48
+ };
@@ -0,0 +1,36 @@
1
+ import { readAgents } from "../../../core/parser.js";
2
+ import { agentRow, resolveProject } from "../helpers.js";
3
+
4
+ export default {
5
+ name: "list_agents",
6
+ schema: {
7
+ type: "function",
8
+ function: {
9
+ name: "list_agents",
10
+ description: "List agents. If project is omitted, returns all agents grouped by project, including default.",
11
+ parameters: {
12
+ type: "object",
13
+ properties: {
14
+ project: { type: "string", description: "project id, name, path, or substring; omit to list all projects" },
15
+ },
16
+ required: [],
17
+ },
18
+ },
19
+ },
20
+ makeHandler: ({ projects }) => ({ project } = {}) => {
21
+ const p = resolveProject(projects, project, { allowMulti: true });
22
+ if (p) return readAgents(p.path).map(agentRow);
23
+ return projects.list().map((entry) => {
24
+ const e = projects.get(entry.id);
25
+ return {
26
+ project: {
27
+ id: entry.id,
28
+ kind: entry.id === 0 ? "default" : "project",
29
+ name: entry.name,
30
+ path: entry.path,
31
+ },
32
+ agents: readAgents(e.path).map(agentRow),
33
+ };
34
+ });
35
+ },
36
+ };
@@ -0,0 +1,38 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { resolveProject, safePathJoin } from "../helpers.js";
4
+
5
+ export default {
6
+ name: "list_files",
7
+ schema: {
8
+ type: "function",
9
+ function: {
10
+ name: "list_files",
11
+ description: "List files and subdirectories inside default or a project.",
12
+ parameters: {
13
+ type: "object",
14
+ properties: {
15
+ project: { type: "string" },
16
+ path: { type: "string", description: "relative path inside the project; default '.'" },
17
+ },
18
+ required: [],
19
+ },
20
+ },
21
+ },
22
+ makeHandler: ({ projects }) => ({ project, path: sub = "." } = {}) => {
23
+ const p = resolveProject(projects, project);
24
+ const target = safePathJoin(p.path, sub);
25
+ if (!fs.existsSync(target)) return { error: `path not found: ${sub}` };
26
+ if (!fs.statSync(target).isDirectory()) return { error: `${sub} is not a directory` };
27
+
28
+ return fs.readdirSync(target).map((name) => {
29
+ const full = path.join(target, name);
30
+ const stat = fs.statSync(full);
31
+ return {
32
+ name,
33
+ type: stat.isDirectory() ? "dir" : "file",
34
+ size: stat.size,
35
+ };
36
+ });
37
+ },
38
+ };
@@ -0,0 +1,48 @@
1
+ import { resolveProject } from "../helpers.js";
2
+
3
+ export default {
4
+ name: "list_mcps",
5
+ schema: {
6
+ type: "function",
7
+ function: {
8
+ name: "list_mcps",
9
+ description: "List MCPs. If project is omitted, returns all MCPs grouped by project, including default.",
10
+ parameters: {
11
+ type: "object",
12
+ properties: {
13
+ project: { type: "string", description: "project id/name/path; omit to list all projects" },
14
+ },
15
+ required: [],
16
+ },
17
+ },
18
+ },
19
+ makeHandler: ({ projects, registries }) => ({ project } = {}) => {
20
+ const row = (m) => ({
21
+ name: m.name,
22
+ source: m.source,
23
+ transport: m.transport,
24
+ enabled: !!m.enabled,
25
+ command: m.command,
26
+ url: m.url,
27
+ });
28
+
29
+ const p = resolveProject(projects, project, { allowMulti: true });
30
+ if (p) {
31
+ if (!registries) throw new Error("MCP registry unavailable");
32
+ return registries.for(p).list().map(row);
33
+ }
34
+
35
+ return projects.list().map((entry) => {
36
+ const e = projects.get(entry.id);
37
+ return {
38
+ project: {
39
+ id: entry.id,
40
+ kind: entry.id === 0 ? "default" : "project",
41
+ name: entry.name,
42
+ path: entry.path,
43
+ },
44
+ mcps: registries ? registries.for(e).list().map(row) : [],
45
+ };
46
+ });
47
+ },
48
+ };
@@ -0,0 +1,20 @@
1
+ export default {
2
+ name: "list_projects",
3
+ schema: {
4
+ type: "function",
5
+ function: {
6
+ name: "list_projects",
7
+ description: "List the APX default project and every registered APC project. Returns id, kind, name, path, and agent count.",
8
+ parameters: { type: "object", properties: {}, required: [] },
9
+ },
10
+ },
11
+ makeHandler: ({ projects }) => () => {
12
+ return projects.list().map((p) => ({
13
+ id: p.id,
14
+ kind: p.id === 0 ? "default" : "project",
15
+ name: p.name,
16
+ path: p.path,
17
+ agents: p.agents,
18
+ }));
19
+ },
20
+ };
@@ -0,0 +1,18 @@
1
+ import { readVaultAgents, VAULT_DIR } from "../../../core/parser.js";
2
+ import { agentRow } from "../helpers.js";
3
+
4
+ export default {
5
+ name: "list_vault_agents",
6
+ schema: {
7
+ type: "function",
8
+ function: {
9
+ name: "list_vault_agents",
10
+ description: "List reusable agent templates from the APX vault (~/.apx/agents). These can be imported into default or a project.",
11
+ parameters: { type: "object", properties: {}, required: [] },
12
+ },
13
+ },
14
+ makeHandler: () => () => ({
15
+ vault: VAULT_DIR,
16
+ agents: readVaultAgents().map(agentRow),
17
+ }),
18
+ };