This commit is contained in:
2026-05-17 12:43:53 -07:00
commit 7c8def0aaa
7507 changed files with 1419399 additions and 0 deletions

10
dist/tools/agent-api.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
export interface AgentRunnerConfig {
runnerUrl: string;
}
export interface SpawnAgentInput {
agent: string;
task: string;
repo: string;
}
export declare function spawnAgent(cfg: AgentRunnerConfig, input: SpawnAgentInput): Promise<unknown>;
export declare function getJobStatus(cfg: AgentRunnerConfig, jobId: string): Promise<unknown>;

40
dist/tools/agent-api.js vendored Normal file
View File

@@ -0,0 +1,40 @@
"use strict";
// =============================================================================
// Pure sub-agent orchestration API. Wraps the vibn-agent-runner HTTP endpoints
// so the same logic is usable from the in-process tool and from an MCP server.
// =============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.spawnAgent = spawnAgent;
exports.getJobStatus = getJobStatus;
async function spawnAgent(cfg, input) {
try {
const res = await fetch(`${cfg.runnerUrl}/api/agent/run`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Internal': 'true' },
body: JSON.stringify({ agent: input.agent, task: input.task, repo: input.repo }),
});
const data = (await res.json());
return { jobId: data.jobId, agent: input.agent, status: 'dispatched' };
}
catch (err) {
return { error: `Failed to spawn agent: ${err instanceof Error ? err.message : String(err)}` };
}
}
async function getJobStatus(cfg, jobId) {
try {
const res = await fetch(`${cfg.runnerUrl}/api/jobs/${jobId}`);
const job = (await res.json());
return {
id: job.id,
agent: job.agent,
status: job.status,
progress: job.progress,
toolCalls: job.toolCalls?.length,
result: job.result,
error: job.error,
};
}
catch (err) {
return { error: `Failed to get job: ${err instanceof Error ? err.message : String(err)}` };
}
}

1
dist/tools/agent.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

73
dist/tools/agent.js vendored Normal file
View File

@@ -0,0 +1,73 @@
"use strict";
// =============================================================================
// Sub-agent orchestration tool registrations. Logic lives in ./agent-api.ts.
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const registry_1 = require("./registry");
const api = __importStar(require("./agent-api"));
function runnerUrl() {
return process.env.AGENT_RUNNER_URL || 'http://localhost:3333';
}
(0, registry_1.registerTool)({
name: 'spawn_agent',
description: 'Dispatch a sub-agent job to run in the background. Returns a job ID. Use this to delegate specialized work to Coder, PM, or Marketing agents.',
parameters: {
type: 'object',
properties: {
agent: { type: 'string', description: '"Coder", "PM", or "Marketing"' },
task: { type: 'string', description: 'Detailed task description for the agent' },
repo: { type: 'string', description: 'Gitea repo in "owner/name" format the agent should work on' }
},
required: ['agent', 'task', 'repo']
},
async handler(args, _ctx) {
return api.spawnAgent({ runnerUrl: runnerUrl() }, { agent: String(args.agent), task: String(args.task), repo: String(args.repo) });
}
});
(0, registry_1.registerTool)({
name: 'get_job_status',
description: 'Check the status of a previously spawned agent job by job ID.',
parameters: {
type: 'object',
properties: {
job_id: { type: 'string', description: 'Job ID returned by spawn_agent' }
},
required: ['job_id']
},
async handler(args, _ctx) {
return api.getJobStatus({ runnerUrl: runnerUrl() }, String(args.job_id));
}
});

19
dist/tools/context.d.ts vendored Normal file
View File

@@ -0,0 +1,19 @@
export interface MemoryUpdate {
key: string;
type: string;
value: string;
}
export interface ToolContext {
workspaceRoot: string;
gitea: {
apiUrl: string;
apiToken: string;
username: string;
};
coolify: {
apiUrl: string;
apiToken: string;
};
/** Accumulated memory updates from save_memory tool calls in this turn */
memoryUpdates: MemoryUpdate[];
}

2
dist/tools/context.js vendored Normal file
View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

11
dist/tools/coolify-api.d.ts vendored Normal file
View File

@@ -0,0 +1,11 @@
export interface CoolifyConfig {
apiUrl: string;
apiToken: string;
}
export declare function listProjects(cfg: CoolifyConfig): Promise<unknown>;
export declare function listApplications(cfg: CoolifyConfig, projectUuid: string): Promise<unknown>;
export declare function deploy(cfg: CoolifyConfig, applicationUuid: string): Promise<unknown>;
export declare function getLogs(cfg: CoolifyConfig, applicationUuid: string, limit?: number): Promise<unknown>;
export declare function listAllApps(cfg: CoolifyConfig): Promise<unknown>;
export declare function getAppStatus(cfg: CoolifyConfig, appName: string): Promise<unknown>;
export declare function deployApp(cfg: CoolifyConfig, appName: string): Promise<unknown>;

118
dist/tools/coolify-api.js vendored Normal file
View File

@@ -0,0 +1,118 @@
"use strict";
// =============================================================================
// Pure Coolify API — no ToolContext coupling, no registry coupling.
//
// Everything in here takes a plain { apiUrl, apiToken } config and calls
// the Coolify v1 API directly. Security guardrails (PROTECTED_COOLIFY_PROJECT,
// PROTECTED_COOLIFY_APPS, assertCoolifyDeployable) are enforced inside each
// function so every caller — in-process tool handler, MCP server, or future
// direct SDK user — gets the same protection.
//
// This is the shared core consumed by:
// - tools/coolify.ts (in-process registry used by agent-runner loop)
// - mcp/coolify-server.ts (stdio MCP server exposed to Goose/Claude/Cursor)
// =============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.listProjects = listProjects;
exports.listApplications = listApplications;
exports.deploy = deploy;
exports.getLogs = getLogs;
exports.listAllApps = listAllApps;
exports.getAppStatus = getAppStatus;
exports.deployApp = deployApp;
const security_1 = require("./security");
async function coolifyFetch(cfg, path, method = 'GET', body) {
const res = await fetch(`${cfg.apiUrl}/api/v1${path}`, {
method,
headers: {
'Authorization': `Bearer ${cfg.apiToken}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: body ? JSON.stringify(body) : undefined
});
if (!res.ok) {
return { error: `Coolify API error: ${res.status} ${res.statusText}` };
}
return res.json();
}
// ---------------------------------------------------------------------------
// Public API — each function corresponds 1:1 with a registered tool today
// ---------------------------------------------------------------------------
async function listProjects(cfg) {
const projects = await coolifyFetch(cfg, '/projects');
if (!Array.isArray(projects))
return projects;
return projects.filter((p) => p.uuid !== security_1.PROTECTED_COOLIFY_PROJECT);
}
async function listApplications(cfg, projectUuid) {
const all = await coolifyFetch(cfg, '/applications');
if (!Array.isArray(all))
return all;
return all.filter((a) => a.project_uuid === projectUuid);
}
async function deploy(cfg, applicationUuid) {
(0, security_1.assertCoolifyDeployable)(applicationUuid);
const apps = await coolifyFetch(cfg, '/applications');
if (Array.isArray(apps)) {
const app = apps.find((a) => a.uuid === applicationUuid);
if (app?.project_uuid === security_1.PROTECTED_COOLIFY_PROJECT) {
return {
error: `SECURITY: App "${applicationUuid}" belongs to the protected Vibn project. Agents cannot deploy platform apps.`
};
}
}
return coolifyFetch(cfg, `/applications/${applicationUuid}/deploy`, 'POST');
}
async function getLogs(cfg, applicationUuid, limit = 50) {
return coolifyFetch(cfg, `/applications/${applicationUuid}/logs?limit=${limit}`);
}
async function listAllApps(cfg) {
const apps = await coolifyFetch(cfg, '/applications');
if (!Array.isArray(apps))
return apps;
return apps
.filter((a) => a.project_uuid !== security_1.PROTECTED_COOLIFY_PROJECT && !security_1.PROTECTED_COOLIFY_APPS.has(a.uuid))
.map((a) => ({
uuid: a.uuid,
name: a.name,
fqdn: a.fqdn,
status: a.status,
repo: a.git_repository,
branch: a.git_branch
}));
}
async function getAppStatus(cfg, appName) {
const apps = await coolifyFetch(cfg, '/applications');
if (!Array.isArray(apps))
return apps;
const app = apps.find((a) => a.name?.toLowerCase() === appName.toLowerCase() || a.uuid === appName);
if (!app)
return { error: `App "${appName}" not found` };
if (security_1.PROTECTED_COOLIFY_APPS.has(app.uuid) || app.project_uuid === security_1.PROTECTED_COOLIFY_PROJECT) {
return {
error: `SECURITY: "${appName}" is a protected Vibn platform app. Status is not exposed to agents.`
};
}
const logs = await coolifyFetch(cfg, `/applications/${app.uuid}/logs?limit=20`);
return { name: app.name, uuid: app.uuid, status: app.status, fqdn: app.fqdn, logs };
}
async function deployApp(cfg, appName) {
const apps = await coolifyFetch(cfg, '/applications');
if (!Array.isArray(apps))
return apps;
const app = apps.find((a) => a.name?.toLowerCase() === appName.toLowerCase() || a.uuid === appName);
if (!app)
return { error: `App "${appName}" not found` };
if (security_1.PROTECTED_COOLIFY_APPS.has(app.uuid) || app.project_uuid === security_1.PROTECTED_COOLIFY_PROJECT) {
return {
error: `SECURITY: "${appName}" is a protected Vibn platform application. ` +
`Agents can only deploy user project apps, not platform infrastructure.`
};
}
// Non-project-prefixed deploy endpoint — older Coolify entry point still in use
const result = await fetch(`${cfg.apiUrl}/api/v1/deploy?uuid=${app.uuid}&force=false`, {
headers: { 'Authorization': `Bearer ${cfg.apiToken}` }
});
return result.json();
}

1
dist/tools/coolify.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

131
dist/tools/coolify.js vendored Normal file
View File

@@ -0,0 +1,131 @@
"use strict";
// =============================================================================
// Coolify tool registrations (in-process path used by agent-runner).
//
// All logic lives in ./coolify-api.ts so the MCP server (src/mcp/coolify-server.ts)
// and this in-process registry call the exact same code path. Keep this file
// purely about: (a) surface-shape for the LLM (name/description/parameters),
// (b) mapping ctx.coolify → CoolifyConfig. No business logic here.
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const registry_1 = require("./registry");
const api = __importStar(require("./coolify-api"));
(0, registry_1.registerTool)({
name: 'coolify_list_projects',
description: 'List all projects in the Coolify instance. Returns project names and UUIDs.',
parameters: { type: 'object', properties: {} },
async handler(_args, ctx) {
return api.listProjects(ctx.coolify);
}
});
(0, registry_1.registerTool)({
name: 'coolify_list_applications',
description: 'List applications in a Coolify project.',
parameters: {
type: 'object',
properties: {
project_uuid: { type: 'string', description: 'Project UUID from coolify_list_projects' }
},
required: ['project_uuid']
},
async handler(args, ctx) {
return api.listApplications(ctx.coolify, String(args.project_uuid));
}
});
(0, registry_1.registerTool)({
name: 'coolify_deploy',
description: 'Trigger a deployment for a Coolify application.',
parameters: {
type: 'object',
properties: {
application_uuid: { type: 'string', description: 'Application UUID to deploy' }
},
required: ['application_uuid']
},
async handler(args, ctx) {
return api.deploy(ctx.coolify, String(args.application_uuid));
}
});
(0, registry_1.registerTool)({
name: 'coolify_get_logs',
description: 'Get recent deployment logs for a Coolify application.',
parameters: {
type: 'object',
properties: {
application_uuid: { type: 'string', description: 'Application UUID' }
},
required: ['application_uuid']
},
async handler(args, ctx) {
return api.getLogs(ctx.coolify, String(args.application_uuid));
}
});
(0, registry_1.registerTool)({
name: 'list_all_apps',
description: 'List all Coolify applications across all projects with their status (running/stopped/error) and domain.',
parameters: { type: 'object', properties: {} },
async handler(_args, ctx) {
return api.listAllApps(ctx.coolify);
}
});
(0, registry_1.registerTool)({
name: 'get_app_status',
description: 'Get the current deployment status and recent logs for a specific Coolify application by name or UUID.',
parameters: {
type: 'object',
properties: {
app_name: { type: 'string', description: 'Application name (e.g. "vibn-frontend") or UUID' }
},
required: ['app_name']
},
async handler(args, ctx) {
return api.getAppStatus(ctx.coolify, String(args.app_name));
}
});
(0, registry_1.registerTool)({
name: 'deploy_app',
description: 'Trigger a Coolify deployment for an app by name. Use after an agent commits code.',
parameters: {
type: 'object',
properties: {
app_name: { type: 'string', description: 'Application name (e.g. "vibn-frontend")' }
},
required: ['app_name']
},
async handler(args, ctx) {
return api.deployApp(ctx.coolify, String(args.app_name));
}
});

6
dist/tools/file-api.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
export declare function readFile(workspaceRoot: string, relPath: string): Promise<unknown>;
export declare function writeFile(workspaceRoot: string, relPath: string, content: string): Promise<unknown>;
export declare function replaceInFile(workspaceRoot: string, relPath: string, oldContent: string, newContent: string): Promise<unknown>;
export declare function listDirectory(workspaceRoot: string, relPath: string): Promise<unknown>;
export declare function findFiles(workspaceRoot: string, pattern: string): Promise<unknown>;
export declare function searchCode(workspaceRoot: string, query: string, fileExtensions?: string[]): Promise<unknown>;

149
dist/tools/file-api.js vendored Normal file
View File

@@ -0,0 +1,149 @@
"use strict";
// =============================================================================
// Pure file-system API — no ToolContext coupling.
// Takes a workspaceRoot string and safely-resolves paths beneath it.
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.readFile = readFile;
exports.writeFile = writeFile;
exports.replaceInFile = replaceInFile;
exports.listDirectory = listDirectory;
exports.findFiles = findFiles;
exports.searchCode = searchCode;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const cp = __importStar(require("child_process"));
const util = __importStar(require("util"));
const minimatch_1 = require("minimatch");
const utils_1 = require("./utils");
const execAsync = util.promisify(cp.exec);
async function readFile(workspaceRoot, relPath) {
const abs = (0, utils_1.safeResolve)(workspaceRoot, relPath);
try {
return fs.readFileSync(abs, 'utf8');
}
catch {
return { error: `File not found: ${relPath}` };
}
}
async function writeFile(workspaceRoot, relPath, content) {
const abs = (0, utils_1.safeResolve)(workspaceRoot, relPath);
fs.mkdirSync(path.dirname(abs), { recursive: true });
fs.writeFileSync(abs, content, 'utf8');
return { success: true, path: relPath, bytes: Buffer.byteLength(content) };
}
async function replaceInFile(workspaceRoot, relPath, oldContent, newContent) {
const abs = (0, utils_1.safeResolve)(workspaceRoot, relPath);
const current = fs.readFileSync(abs, 'utf8');
if (!current.includes(oldContent)) {
return { error: 'old_content not found in file. Read the file again to get the current content.' };
}
fs.writeFileSync(abs, current.replace(oldContent, newContent), 'utf8');
return { success: true, path: relPath };
}
async function listDirectory(workspaceRoot, relPath) {
const abs = (0, utils_1.safeResolve)(workspaceRoot, relPath);
try {
const entries = fs.readdirSync(abs, { withFileTypes: true });
return entries
.filter(e => !utils_1.EXCLUDED.has(e.name))
.map(e => e.isDirectory() ? `${e.name}/` : e.name);
}
catch {
return { error: `Directory not found: ${relPath}` };
}
}
async function findFiles(workspaceRoot, pattern) {
const matcher = new minimatch_1.Minimatch(pattern, { dot: false });
const results = [];
function walk(dir) {
if (results.length >= 200)
return;
let entries;
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
}
catch {
return;
}
for (const e of entries) {
if (utils_1.EXCLUDED.has(e.name))
continue;
const abs = path.join(dir, e.name);
const rel = path.relative(workspaceRoot, abs).split(path.sep).join('/');
if (e.isDirectory()) {
walk(abs);
}
else if (matcher.match(rel)) {
results.push(rel);
}
}
}
walk(workspaceRoot);
return { files: results, truncated: results.length >= 200 };
}
async function searchCode(workspaceRoot, query, fileExtensions) {
const globPatterns = fileExtensions?.map(e => `*.${e}`) || [];
const rgArgs = ['--line-number', '--no-heading', '--color=never', '--max-count=30'];
for (const ex of utils_1.EXCLUDED) {
rgArgs.push('--glob', `!${ex}`);
}
if (globPatterns.length > 0) {
for (const g of globPatterns)
rgArgs.push('--glob', g);
}
rgArgs.push('--fixed-strings', query, workspaceRoot);
try {
const { stdout } = await execAsync(`rg ${rgArgs.map(a => `'${a}'`).join(' ')}`, {
cwd: workspaceRoot, timeout: 15000,
});
return stdout.trim().split('\n').filter(Boolean).map(line => {
const m = line.match(/^(.+?):(\d+):(.*)$/);
if (!m)
return null;
return {
file: path.relative(workspaceRoot, m[1]).split(path.sep).join('/'),
line: parseInt(m[2]),
content: m[3].trim(),
};
}).filter(Boolean);
}
catch (err) {
if (err.code === 1)
return []; // ripgrep exit 1 = no matches
return { error: `Search failed: ${err.message}` };
}
}

1
dist/tools/file.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

134
dist/tools/file.js vendored Normal file
View File

@@ -0,0 +1,134 @@
"use strict";
// =============================================================================
// File-system tool registrations (in-process path used by agent-runner).
// All logic lives in ./file-api.ts.
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const registry_1 = require("./registry");
const api = __importStar(require("./file-api"));
(0, registry_1.registerTool)({
name: 'read_file',
description: 'Read the complete content of a file in the workspace. Always read before editing.',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'Relative path from workspace root (e.g. "src/index.ts")' }
},
required: ['path']
},
async handler(args, ctx) {
return api.readFile(ctx.workspaceRoot, String(args.path));
}
});
(0, registry_1.registerTool)({
name: 'write_file',
description: 'Write complete content to a file. Creates parent directories if needed. Overwrites existing files.',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'Relative path from workspace root' },
content: { type: 'string', description: 'Complete new file content' }
},
required: ['path', 'content']
},
async handler(args, ctx) {
return api.writeFile(ctx.workspaceRoot, String(args.path), String(args.content));
}
});
(0, registry_1.registerTool)({
name: 'replace_in_file',
description: 'Replace an exact string in a file. The old_content must match character-for-character. Read the file first.',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'Relative path from workspace root' },
old_content: { type: 'string', description: 'Exact text to replace' },
new_content: { type: 'string', description: 'Replacement text' }
},
required: ['path', 'old_content', 'new_content']
},
async handler(args, ctx) {
return api.replaceInFile(ctx.workspaceRoot, String(args.path), String(args.old_content), String(args.new_content));
}
});
(0, registry_1.registerTool)({
name: 'list_directory',
description: 'List files and subdirectories in a directory. Directories have trailing "/".',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'Relative path from workspace root. Use "." for root.' }
},
required: ['path']
},
async handler(args, ctx) {
return api.listDirectory(ctx.workspaceRoot, String(args.path));
}
});
(0, registry_1.registerTool)({
name: 'find_files',
description: 'Find files matching a glob pattern in the workspace. Returns up to 200 relative paths.',
parameters: {
type: 'object',
properties: {
pattern: { type: 'string', description: 'Glob pattern e.g. "**/*.ts", "src/**/*.test.js"' }
},
required: ['pattern']
},
async handler(args, ctx) {
return api.findFiles(ctx.workspaceRoot, String(args.pattern));
}
});
(0, registry_1.registerTool)({
name: 'search_code',
description: 'Search file contents for a string or regex pattern. Returns file path, line number, and matching line.',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search term or regex' },
file_extensions: {
type: 'array',
items: { type: 'string' },
description: 'Optional: limit to these extensions e.g. ["ts","js"]'
}
},
required: ['query']
},
async handler(args, ctx) {
const exts = Array.isArray(args.file_extensions) ? args.file_extensions : undefined;
return api.searchCode(ctx.workspaceRoot, String(args.query), exts);
}
});

6
dist/tools/git-api.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
export interface GitPushConfig {
apiUrl: string;
apiToken: string;
username: string;
}
export declare function gitCommitAndPush(workspaceRoot: string, message: string, cfg: GitPushConfig): Promise<unknown>;

86
dist/tools/git-api.js vendored Normal file
View File

@@ -0,0 +1,86 @@
"use strict";
// =============================================================================
// Pure git API — no ToolContext coupling.
// Requires a GitPushConfig with Gitea credentials for authenticated push.
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.gitCommitAndPush = gitCommitAndPush;
const cp = __importStar(require("child_process"));
const util = __importStar(require("util"));
const security_1 = require("./security");
const execAsync = util.promisify(cp.exec);
async function gitCommitAndPush(workspaceRoot, message, cfg) {
const cwd = workspaceRoot;
const { apiUrl, apiToken, username } = cfg;
try {
// Check remote URL before committing — block pushes to protected repos
let remoteCheck = '';
try {
remoteCheck = (await execAsync('git remote get-url origin', { cwd })).stdout.trim();
}
catch { /* no remote yet */ }
for (const protectedRepo of security_1.PROTECTED_GITEA_REPOS) {
const repoPath = protectedRepo.replace('mark/', '');
if (remoteCheck.includes(`/${repoPath}`) || remoteCheck.includes(`/${repoPath}.git`)) {
return {
error: `SECURITY: This workspace is linked to a protected Vibn platform repo (${protectedRepo}). ` +
`Agents cannot push to platform repos. Only user project repos are writable.`,
};
}
}
await execAsync('git add -A', { cwd });
await execAsync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { cwd });
// Strip any existing credentials from remote URL and re-inject cleanly
let remoteUrl = '';
try {
remoteUrl = (await execAsync('git remote get-url origin', { cwd })).stdout.trim();
}
catch { /* no remote */ }
const cleanUrl = remoteUrl.replace(/https:\/\/[^@]+@/, 'https://');
const baseUrl = cleanUrl || apiUrl;
const authedUrl = baseUrl.replace('https://', `https://${username}:${apiToken}@`);
await execAsync(`git remote set-url origin "${authedUrl}"`, { cwd }).catch(async () => {
await execAsync(`git remote add origin "${authedUrl}"`, { cwd });
});
const branch = (await execAsync('git rev-parse --abbrev-ref HEAD', { cwd })).stdout.trim();
await execAsync(`git push -u origin "${branch}"`, { cwd, timeout: 60000 });
return { success: true, message, branch };
}
catch (err) {
const cleaned = (err.message || '').replace(new RegExp(apiToken, 'g'), '***');
return { error: `Git operation failed: ${cleaned}` };
}
}

1
dist/tools/git.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

58
dist/tools/git.js vendored Normal file
View File

@@ -0,0 +1,58 @@
"use strict";
// =============================================================================
// Git commit-and-push tool registration. Logic lives in ./git-api.ts.
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const registry_1 = require("./registry");
const api = __importStar(require("./git-api"));
(0, registry_1.registerTool)({
name: 'git_commit_and_push',
description: 'Stage all changes, commit with a message, and push to the remote. Call this when work is complete.',
parameters: {
type: 'object',
properties: {
message: { type: 'string', description: 'Commit message describing the changes made' }
},
required: ['message']
},
async handler(args, ctx) {
return api.gitCommitAndPush(ctx.workspaceRoot, String(args.message), {
apiUrl: ctx.gitea.apiUrl,
apiToken: ctx.gitea.apiToken,
username: ctx.gitea.username,
});
}
});

20
dist/tools/gitea-api.d.ts vendored Normal file
View File

@@ -0,0 +1,20 @@
export interface GiteaConfig {
apiUrl: string;
apiToken: string;
username?: string;
}
export interface CreateIssueInput {
repo: string;
title: string;
body: string;
labels?: string[];
}
export declare function createIssue(cfg: GiteaConfig, input: CreateIssueInput): Promise<unknown>;
export declare function listIssues(cfg: GiteaConfig, repo: string, state?: string): Promise<unknown>;
export declare function closeIssue(cfg: GiteaConfig, repo: string, issueNumber: number): Promise<unknown>;
export declare function listRepos(cfg: GiteaConfig): Promise<unknown>;
export declare function listAllIssues(cfg: GiteaConfig, opts?: {
repo?: string;
state?: string;
}): Promise<unknown>;
export declare function readRepoFile(cfg: GiteaConfig, repo: string, filePath: string): Promise<unknown>;

121
dist/tools/gitea-api.js vendored Normal file
View File

@@ -0,0 +1,121 @@
"use strict";
// =============================================================================
// Pure Gitea API — no ToolContext coupling, no registry coupling.
//
// Takes a plain { apiUrl, apiToken, username } config. Security guardrails
// (PROTECTED_GITEA_REPOS, assertGiteaWritable) are enforced inside each
// function so every caller gets the same protection.
//
// Consumed by:
// - tools/gitea.ts (in-process registry used by agent-runner loop)
// - mcp/gitea-server.ts (stdio MCP server exposed to any MCP client)
// =============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.createIssue = createIssue;
exports.listIssues = listIssues;
exports.closeIssue = closeIssue;
exports.listRepos = listRepos;
exports.listAllIssues = listAllIssues;
exports.readRepoFile = readRepoFile;
const security_1 = require("./security");
async function giteaFetch(cfg, path, method = 'GET', body) {
const res = await fetch(`${cfg.apiUrl}/api/v1${path}`, {
method,
headers: {
'Authorization': `token ${cfg.apiToken}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: body ? JSON.stringify(body) : undefined
});
if (!res.ok) {
return { error: `Gitea API error: ${res.status} ${res.statusText}` };
}
return res.json();
}
async function createIssue(cfg, input) {
(0, security_1.assertGiteaWritable)(input.repo);
return giteaFetch(cfg, `/repos/${input.repo}/issues`, 'POST', {
title: input.title,
body: input.body,
labels: input.labels,
});
}
async function listIssues(cfg, repo, state = 'open') {
return giteaFetch(cfg, `/repos/${repo}/issues?state=${state}&limit=20`);
}
async function closeIssue(cfg, repo, issueNumber) {
(0, security_1.assertGiteaWritable)(repo);
return giteaFetch(cfg, `/repos/${repo}/issues/${issueNumber}`, 'PATCH', { state: 'closed' });
}
async function listRepos(cfg) {
const res = await fetch(`${cfg.apiUrl}/api/v1/repos/search?limit=50`, {
headers: { 'Authorization': `token ${cfg.apiToken}` }
});
if (!res.ok) {
return { error: `Gitea API error: ${res.status} ${res.statusText}` };
}
const data = await res.json();
return (data.data || [])
.filter((r) => !security_1.PROTECTED_GITEA_REPOS.has(r.full_name))
.map((r) => ({
name: r.full_name,
description: r.description,
default_branch: r.default_branch,
updated: r.updated,
stars: r.stars_count,
open_issues: r.open_issues_count,
}));
}
async function listAllIssues(cfg, opts = {}) {
const state = opts.state || 'open';
if (opts.repo) {
if (security_1.PROTECTED_GITEA_REPOS.has(opts.repo)) {
return {
error: `SECURITY: "${opts.repo}" is a protected Vibn platform repo. Agents cannot access its issues.`
};
}
return giteaFetch(cfg, `/repos/${opts.repo}/issues?state=${state}&limit=20`);
}
// Fetch across all non-protected repos (cap at 10 repos to bound request count)
const reposRes = await fetch(`${cfg.apiUrl}/api/v1/repos/search?limit=50`, {
headers: { 'Authorization': `token ${cfg.apiToken}` }
});
if (!reposRes.ok) {
return { error: `Gitea API error: ${reposRes.status} ${reposRes.statusText}` };
}
const reposData = await reposRes.json();
const repos = (reposData.data || []).filter((r) => !security_1.PROTECTED_GITEA_REPOS.has(r.full_name));
const allIssues = [];
for (const r of repos.slice(0, 10)) {
const issues = await giteaFetch(cfg, `/repos/${r.full_name}/issues?state=${state}&limit=10`);
if (Array.isArray(issues)) {
allIssues.push(...issues.map((i) => ({
repo: r.full_name,
number: i.number,
title: i.title,
state: i.state,
labels: i.labels?.map((l) => l.name),
created: i.created_at,
})));
}
}
return allIssues;
}
async function readRepoFile(cfg, repo, filePath) {
try {
const res = await fetch(`${cfg.apiUrl}/api/v1/repos/${repo}/contents/${filePath}`, {
headers: { 'Authorization': `token ${cfg.apiToken}` }
});
if (!res.ok)
return { error: `File not found: ${filePath} in ${repo}` };
const data = await res.json();
const content = Buffer.from(data.content, 'base64').toString('utf8');
return { repo, path: filePath, content };
}
catch (err) {
return {
error: `Failed to read ${filePath}: ${err instanceof Error ? err.message : String(err)}`
};
}
}

1
dist/tools/gitea.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

137
dist/tools/gitea.js vendored Normal file
View File

@@ -0,0 +1,137 @@
"use strict";
// =============================================================================
// Gitea tool registrations (in-process path used by agent-runner).
//
// All logic lives in ./gitea-api.ts so the MCP server (src/mcp/gitea-server.ts)
// and this in-process registry call the exact same code path. Keep this file
// purely about: (a) surface-shape for the LLM (name/description/parameters),
// (b) mapping ctx.gitea → GiteaConfig. No business logic here.
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const registry_1 = require("./registry");
const api = __importStar(require("./gitea-api"));
(0, registry_1.registerTool)({
name: 'gitea_create_issue',
description: 'Create a new issue in a Gitea repository.',
parameters: {
type: 'object',
properties: {
repo: { type: 'string', description: 'Repository in "owner/name" format' },
title: { type: 'string', description: 'Issue title' },
body: { type: 'string', description: 'Issue body (markdown)' },
labels: { type: 'array', items: { type: 'string' }, description: 'Optional label names' }
},
required: ['repo', 'title', 'body']
},
async handler(args, ctx) {
return api.createIssue(ctx.gitea, {
repo: String(args.repo),
title: String(args.title),
body: String(args.body),
labels: Array.isArray(args.labels) ? args.labels : undefined,
});
}
});
(0, registry_1.registerTool)({
name: 'gitea_list_issues',
description: 'List open issues in a Gitea repository.',
parameters: {
type: 'object',
properties: {
repo: { type: 'string', description: 'Repository in "owner/name" format' },
state: { type: 'string', description: '"open", "closed", or "all". Default: "open"' }
},
required: ['repo']
},
async handler(args, ctx) {
return api.listIssues(ctx.gitea, String(args.repo), String(args.state || 'open'));
}
});
(0, registry_1.registerTool)({
name: 'gitea_close_issue',
description: 'Close an issue in a Gitea repository.',
parameters: {
type: 'object',
properties: {
repo: { type: 'string', description: 'Repository in "owner/name" format' },
issue_number: { type: 'number', description: 'Issue number to close' }
},
required: ['repo', 'issue_number']
},
async handler(args, ctx) {
return api.closeIssue(ctx.gitea, String(args.repo), Number(args.issue_number));
}
});
(0, registry_1.registerTool)({
name: 'list_repos',
description: 'List all Git repositories in the Gitea organization. Returns repo names, descriptions, and last update time.',
parameters: { type: 'object', properties: {} },
async handler(_args, ctx) {
return api.listRepos(ctx.gitea);
}
});
(0, registry_1.registerTool)({
name: 'list_all_issues',
description: 'List open issues across all repos or a specific repo. Use this to understand what work is queued or in progress.',
parameters: {
type: 'object',
properties: {
repo: { type: 'string', description: 'Optional: "owner/name" to scope to one repo. Omit for all repos.' },
state: { type: 'string', description: '"open", "closed", or "all". Default: "open"' }
}
},
async handler(args, ctx) {
return api.listAllIssues(ctx.gitea, {
repo: args.repo ? String(args.repo) : undefined,
state: args.state ? String(args.state) : undefined,
});
}
});
(0, registry_1.registerTool)({
name: 'read_repo_file',
description: 'Read a file from any Gitea repository without cloning it. Useful for understanding project structure.',
parameters: {
type: 'object',
properties: {
repo: { type: 'string', description: 'Repo in "owner/name" format' },
path: { type: 'string', description: 'File path within the repo (e.g. "src/app/page.tsx")' }
},
required: ['repo', 'path']
},
async handler(args, ctx) {
return api.readRepoFile(ctx.gitea, String(args.repo), String(args.path));
}
});

13
dist/tools/index.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
import './file';
import './shell';
import './git';
import './gitea';
import './coolify';
import './agent';
import './memory';
import './skills';
import './prd';
import './search';
export { ALL_TOOLS, executeTool, ToolDefinition } from './registry';
export { ToolContext, MemoryUpdate } from './context';
export { PROTECTED_GITEA_REPOS, PROTECTED_COOLIFY_PROJECT, PROTECTED_COOLIFY_APPS, assertGiteaWritable, assertCoolifyDeployable } from './security';

25
dist/tools/index.js vendored Normal file
View File

@@ -0,0 +1,25 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.assertCoolifyDeployable = exports.assertGiteaWritable = exports.PROTECTED_COOLIFY_APPS = exports.PROTECTED_COOLIFY_PROJECT = exports.PROTECTED_GITEA_REPOS = exports.executeTool = exports.ALL_TOOLS = void 0;
// Import domain files first — side effects register each tool into the registry.
// Order determines ALL_TOOLS array order (informational only).
require("./file");
require("./shell");
require("./git");
require("./gitea");
require("./coolify");
require("./agent");
require("./memory");
require("./skills");
require("./prd");
require("./search");
// Re-export the public API — identical surface to the old tools.ts
var registry_1 = require("./registry");
Object.defineProperty(exports, "ALL_TOOLS", { enumerable: true, get: function () { return registry_1.ALL_TOOLS; } });
Object.defineProperty(exports, "executeTool", { enumerable: true, get: function () { return registry_1.executeTool; } });
var security_1 = require("./security");
Object.defineProperty(exports, "PROTECTED_GITEA_REPOS", { enumerable: true, get: function () { return security_1.PROTECTED_GITEA_REPOS; } });
Object.defineProperty(exports, "PROTECTED_COOLIFY_PROJECT", { enumerable: true, get: function () { return security_1.PROTECTED_COOLIFY_PROJECT; } });
Object.defineProperty(exports, "PROTECTED_COOLIFY_APPS", { enumerable: true, get: function () { return security_1.PROTECTED_COOLIFY_APPS; } });
Object.defineProperty(exports, "assertGiteaWritable", { enumerable: true, get: function () { return security_1.assertGiteaWritable; } });
Object.defineProperty(exports, "assertCoolifyDeployable", { enumerable: true, get: function () { return security_1.assertCoolifyDeployable; } });

17
dist/tools/memory-api.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
export interface MemoryEntry {
key: string;
type: string;
value: string;
}
export interface MemoryInput {
key: string;
type: string;
value: string;
}
export declare function toEntry(input: MemoryInput): MemoryEntry;
export declare function saveMemoryToStore(sessionKey: string, input: MemoryInput): {
saved: true;
entry: MemoryEntry;
};
export declare function listMemoryFromStore(sessionKey: string): MemoryEntry[];
export declare function clearMemoryStore(sessionKey: string): void;

33
dist/tools/memory-api.js vendored Normal file
View File

@@ -0,0 +1,33 @@
"use strict";
// =============================================================================
// Pure memory API. The in-process agent-runner collects memory updates into an
// array on the ToolContext (ctx.memoryUpdates) so the supervisor loop can
// persist them at end-of-turn. MCP clients don't share that array, so the MCP
// server keeps its own module-level store keyed by an optional sessionKey.
// =============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.toEntry = toEntry;
exports.saveMemoryToStore = saveMemoryToStore;
exports.listMemoryFromStore = listMemoryFromStore;
exports.clearMemoryStore = clearMemoryStore;
function toEntry(input) {
return { key: input.key, type: input.type, value: input.value };
}
// -------------------------------------------------------------------
// In-memory store used by the MCP server path (the in-process path
// appends directly to ctx.memoryUpdates and ignores this store).
// -------------------------------------------------------------------
const memoryStore = new Map();
function saveMemoryToStore(sessionKey, input) {
const entry = toEntry(input);
const list = memoryStore.get(sessionKey) ?? [];
list.push(entry);
memoryStore.set(sessionKey, list);
return { saved: true, entry };
}
function listMemoryFromStore(sessionKey) {
return [...(memoryStore.get(sessionKey) ?? [])];
}
function clearMemoryStore(sessionKey) {
memoryStore.delete(sessionKey);
}

1
dist/tools/memory.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

68
dist/tools/memory.js vendored Normal file
View File

@@ -0,0 +1,68 @@
"use strict";
// =============================================================================
// save_memory tool registration. Logic lives in ./memory-api.ts.
// In-process: appends to ctx.memoryUpdates so the supervisor loop can persist
// at end-of-turn. MCP server path uses memory-api's internal store.
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const registry_1 = require("./registry");
const api = __importStar(require("./memory-api"));
(0, registry_1.registerTool)({
name: 'save_memory',
description: 'Persist an important fact about this project to long-term memory. Use this to save decisions, tech stack choices, feature descriptions, constraints, or goals so they are remembered across conversations.',
parameters: {
type: 'object',
properties: {
key: { type: 'string', description: 'Short unique label (e.g. "primary_language", "auth_strategy", "deploy_target")' },
type: {
type: 'string',
enum: ['tech_stack', 'decision', 'feature', 'goal', 'constraint', 'note'],
description: 'Category of the memory item'
},
value: { type: 'string', description: 'The fact to remember (1-3 sentences)' }
},
required: ['key', 'type', 'value']
},
async handler(args, ctx) {
const entry = api.toEntry({
key: String(args.key),
type: String(args.type),
value: String(args.value),
});
ctx.memoryUpdates.push(entry);
return { saved: true, key: entry.key, type: entry.type };
}
});

7
dist/tools/prd-api.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
/** sessionKey (workspaceRoot) → PRD markdown */
export declare const prdStore: Map<string, string>;
export declare function finalizePrd(sessionKey: string, content: string): {
saved: true;
message: string;
};
export declare function getPrd(sessionKey: string): string | null;

22
dist/tools/prd-api.js vendored Normal file
View File

@@ -0,0 +1,22 @@
"use strict";
// =============================================================================
// Pure PRD API. The store is module-level so atlas.ts can inspect it after each
// turn (it imports `prdStore` from prd.ts which re-exports from here). Keep
// this module side-effect-free otherwise.
// =============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.prdStore = void 0;
exports.finalizePrd = finalizePrd;
exports.getPrd = getPrd;
/** sessionKey (workspaceRoot) → PRD markdown */
exports.prdStore = new Map();
function finalizePrd(sessionKey, content) {
exports.prdStore.set(sessionKey, content);
return {
saved: true,
message: 'PRD saved. Let the user know their product requirements document is ready and the platform will now architect the technical solution.',
};
}
function getPrd(sessionKey) {
return exports.prdStore.get(sessionKey) ?? null;
}

1
dist/tools/prd.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export { prdStore } from './prd-api';

62
dist/tools/prd.js vendored Normal file
View File

@@ -0,0 +1,62 @@
"use strict";
// =============================================================================
// finalize_prd tool registration. Logic + store live in ./prd-api.ts.
// We re-export `prdStore` so existing imports (atlas.ts) continue to work.
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.prdStore = void 0;
const registry_1 = require("./registry");
const api = __importStar(require("./prd-api"));
var prd_api_1 = require("./prd-api");
Object.defineProperty(exports, "prdStore", { enumerable: true, get: function () { return prd_api_1.prdStore; } });
(0, registry_1.registerTool)({
name: 'finalize_prd',
description: 'Call this when you have finished writing the complete PRD document. Pass the full PRD markdown as content. This saves the document and signals to the user that discovery is complete.',
parameters: {
type: 'object',
properties: {
content: {
type: 'string',
description: 'The complete PRD document in markdown format'
}
},
required: ['content']
},
async handler(args, ctx) {
// Store against workspaceRoot as a unique key (each project has its own workspace)
return api.finalizePrd(ctx.workspaceRoot, String(args.content));
}
});

16
dist/tools/registry.d.ts vendored Normal file
View File

@@ -0,0 +1,16 @@
import { ToolContext } from './context';
export interface ToolDefinition {
name: string;
description: string;
parameters: Record<string, unknown>;
/** Implementation — called by executeTool(). Not sent to the LLM. */
handler: (args: Record<string, unknown>, ctx: ToolContext) => Promise<unknown>;
}
/**
* Mutable array kept in sync with the registry.
* Used by agents.ts to pick tool subsets by name (backwards-compatible with ALL_TOOLS).
*/
export declare const ALL_TOOLS: ToolDefinition[];
export declare function registerTool(tool: ToolDefinition): void;
/** Dispatch a tool call by name — O(1) map lookup, no switch needed. */
export declare function executeTool(name: string, args: Record<string, unknown>, ctx: ToolContext): Promise<unknown>;

23
dist/tools/registry.js vendored Normal file
View File

@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ALL_TOOLS = void 0;
exports.registerTool = registerTool;
exports.executeTool = executeTool;
/** Live registry — grows as domain files are imported. */
const _registry = new Map();
/**
* Mutable array kept in sync with the registry.
* Used by agents.ts to pick tool subsets by name (backwards-compatible with ALL_TOOLS).
*/
exports.ALL_TOOLS = [];
function registerTool(tool) {
_registry.set(tool.name, tool);
exports.ALL_TOOLS.push(tool);
}
/** Dispatch a tool call by name — O(1) map lookup, no switch needed. */
async function executeTool(name, args, ctx) {
const tool = _registry.get(name);
if (!tool)
return { error: `Unknown tool: ${name}` };
return tool.handler(args, ctx);
}

1
dist/tools/search-api.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export declare function webSearch(query: string): Promise<unknown>;

54
dist/tools/search-api.js vendored Normal file
View File

@@ -0,0 +1,54 @@
"use strict";
// =============================================================================
// Pure web-search API via DuckDuckGo HTML endpoint. No API key required.
// =============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.webSearch = webSearch;
async function webSearch(query) {
const trimmed = query.trim();
if (!trimmed)
return { error: 'No query provided' };
const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(trimmed)}`;
try {
const res = await fetch(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; VIBN-Atlas/1.0)',
Accept: 'text/html',
},
signal: AbortSignal.timeout(15000),
});
if (!res.ok) {
return { error: `Search failed with status ${res.status}` };
}
const html = await res.text();
const titles = [];
for (const m of html.matchAll(/class="result__a"[^>]*href="[^"]*"[^>]*>(.*?)<\/a>/gs)) {
const title = m[1].replace(/<[^>]+>/g, '').trim();
if (title)
titles.push(title);
}
const snippets = [];
for (const m of html.matchAll(/class="result__snippet"[^>]*>(.*?)<\/a>/gs)) {
const snippet = m[1].replace(/<[^>]+>/g, '').trim();
if (snippet)
snippets.push(snippet);
}
const count = Math.min(6, Math.max(titles.length, snippets.length));
const results = [];
for (let i = 0; i < count; i++) {
const title = titles[i] || '';
const snippet = snippets[i] || '';
if (title || snippet)
results.push(`**${title}**\n${snippet}`);
}
if (results.length === 0)
return { error: 'No results found' };
const text = results.join('\n\n');
const truncated = text.length > 5000 ? text.slice(0, 5000) + '\n\n[...results truncated]' : text;
return { query: trimmed, results: truncated };
}
catch (err) {
const message = err instanceof Error ? err.message : String(err);
return { error: `Search request failed: ${message}` };
}
}

1
dist/tools/search.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

57
dist/tools/search.js vendored Normal file
View File

@@ -0,0 +1,57 @@
"use strict";
// =============================================================================
// web_search tool registration. Logic lives in ./search-api.ts.
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const registry_1 = require("./registry");
const api = __importStar(require("./search-api"));
(0, registry_1.registerTool)({
name: 'web_search',
description: 'Search the web for current information. Use this to research competitors, market trends, pricing models, existing solutions, technology choices, or any topic the user mentions that would benefit from real-world context. Returns a summary of top search results.',
parameters: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'The search query. Be specific — e.g. "SaaS project management tools pricing 2024" rather than just "project management".'
}
},
required: ['query']
},
async handler(args) {
return api.webSearch(String(args.query));
}
});

11
dist/tools/security.d.ts vendored Normal file
View File

@@ -0,0 +1,11 @@
/** Gitea repos agents can NEVER push to, commit to, or write issues on. */
export declare const PROTECTED_GITEA_REPOS: Set<string>;
/** Coolify project UUID for the VIBN platform — agents cannot deploy here. */
export declare const PROTECTED_COOLIFY_PROJECT = "f4owwggokksgw0ogo0844os0";
/**
* Specific Coolify app UUIDs that must never be deployed by an agent.
* Belt-and-suspenders check in case the project UUID filter is bypassed.
*/
export declare const PROTECTED_COOLIFY_APPS: Set<string>;
export declare function assertGiteaWritable(repo: string): void;
export declare function assertCoolifyDeployable(appUuid: string): void;

43
dist/tools/security.js vendored Normal file
View File

@@ -0,0 +1,43 @@
"use strict";
// =============================================================================
// SECURITY GUARDRAILS — Protected VIBN Platform Resources
//
// These repos and Coolify resources belong to the Vibn platform itself.
// Agents must never be allowed to push code or trigger deployments here.
// Read-only operations (list, read file, get status) are still permitted
// so agents can observe platform state, but all mutations are blocked.
// =============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.PROTECTED_COOLIFY_APPS = exports.PROTECTED_COOLIFY_PROJECT = exports.PROTECTED_GITEA_REPOS = void 0;
exports.assertGiteaWritable = assertGiteaWritable;
exports.assertCoolifyDeployable = assertCoolifyDeployable;
/** Gitea repos agents can NEVER push to, commit to, or write issues on. */
exports.PROTECTED_GITEA_REPOS = new Set([
'mark/vibn-frontend',
'mark/vibn-agent-runner',
'mark/vibn-api',
'mark/master-ai',
]);
/** Coolify project UUID for the VIBN platform — agents cannot deploy here. */
exports.PROTECTED_COOLIFY_PROJECT = 'f4owwggokksgw0ogo0844os0';
/**
* Specific Coolify app UUIDs that must never be deployed by an agent.
* Belt-and-suspenders check in case the project UUID filter is bypassed.
*/
exports.PROTECTED_COOLIFY_APPS = new Set([
'y4cscsc8s08c8808go0448s0', // vibn-frontend
'kggs4ogckc0w8ggwkkk88kck', // vibn-postgres
'o4wwck0g0c04wgoo4g4s0004', // gitea
]);
function assertGiteaWritable(repo) {
if (exports.PROTECTED_GITEA_REPOS.has(repo)) {
throw new Error(`SECURITY: Repo "${repo}" is a protected Vibn platform repo. ` +
`Agents cannot push code or modify issues in this repository.`);
}
}
function assertCoolifyDeployable(appUuid) {
if (exports.PROTECTED_COOLIFY_APPS.has(appUuid)) {
throw new Error(`SECURITY: App "${appUuid}" is a protected Vibn platform application. ` +
`Agents cannot trigger deployments for this application.`);
}
}

1
dist/tools/shell-api.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export declare function executeCommand(workspaceRoot: string, command: string, workingDirectory?: string): Promise<unknown>;

64
dist/tools/shell-api.js vendored Normal file
View File

@@ -0,0 +1,64 @@
"use strict";
// =============================================================================
// Pure shell execution API — no ToolContext coupling.
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeCommand = executeCommand;
const cp = __importStar(require("child_process"));
const util = __importStar(require("util"));
const utils_1 = require("./utils");
const execAsync = util.promisify(cp.exec);
const BLOCKED_COMMANDS = ['rm -rf /', 'mkfs', ':(){:|:&};:'];
async function executeCommand(workspaceRoot, command, workingDirectory) {
if (BLOCKED_COMMANDS.some(b => command.includes(b))) {
return { error: 'Command blocked for safety.' };
}
const cwd = workingDirectory ? (0, utils_1.safeResolve)(workspaceRoot, workingDirectory) : workspaceRoot;
try {
const { stdout, stderr } = await execAsync(command, {
cwd, timeout: 120000, maxBuffer: 1024 * 1024,
});
return { exitCode: 0, stdout: stdout.trim(), stderr: stderr.trim() };
}
catch (err) {
return {
exitCode: err.code,
stdout: (err.stdout || '').trim(),
stderr: (err.stderr || '').trim(),
error: err.message,
};
}
}

1
dist/tools/shell.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

55
dist/tools/shell.js vendored Normal file
View File

@@ -0,0 +1,55 @@
"use strict";
// =============================================================================
// Shell execution tool registration. Logic lives in ./shell-api.ts.
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const registry_1 = require("./registry");
const api = __importStar(require("./shell-api"));
(0, registry_1.registerTool)({
name: 'execute_command',
description: 'Run a shell command in the workspace and return stdout + stderr. 120s timeout. Use for: npm install, npm test, git status, building, etc.',
parameters: {
type: 'object',
properties: {
command: { type: 'string', description: 'Shell command to run' },
working_directory: { type: 'string', description: 'Optional: relative subdirectory to run in' }
},
required: ['command']
},
async handler(args, ctx) {
return api.executeCommand(ctx.workspaceRoot, String(args.command), args.working_directory ? String(args.working_directory) : undefined);
}
});

6
dist/tools/skills-api.d.ts vendored Normal file
View File

@@ -0,0 +1,6 @@
export interface GiteaReadConfig {
apiUrl: string;
apiToken: string;
}
export declare function listSkills(cfg: GiteaReadConfig, repo: string): Promise<unknown>;
export declare function getSkill(cfg: GiteaReadConfig, repo: string, skillName: string): Promise<unknown>;

40
dist/tools/skills-api.js vendored Normal file
View File

@@ -0,0 +1,40 @@
"use strict";
// =============================================================================
// Pure skills API. Skills live in a Gitea repo at .skills/<name>/SKILL.md.
// Takes a GiteaReadConfig so it can read from any Gitea instance (in-process
// agent passes ctx.gitea, MCP server loads from env).
// =============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.listSkills = listSkills;
exports.getSkill = getSkill;
const SKILL_FILE = 'SKILL.md';
const SKILLS_DIR = '.skills';
async function giteaGetContents(cfg, repo, filePath) {
const res = await fetch(`${cfg.apiUrl}/api/v1/repos/${repo}/contents/${filePath}`, {
headers: { Authorization: `token ${cfg.apiToken}` },
});
if (!res.ok)
return null;
return res.json();
}
async function listSkills(cfg, repo) {
const contents = await giteaGetContents(cfg, repo, SKILLS_DIR);
if (!contents || !Array.isArray(contents)) {
return { skills: [], message: `No .skills/ directory found in ${repo}` };
}
const skills = contents
.filter((entry) => entry.type === 'dir')
.map((entry) => ({ name: entry.name, path: entry.path }));
return { repo, skills };
}
async function getSkill(cfg, repo, skillName) {
const filePath = `${SKILLS_DIR}/${skillName}/${SKILL_FILE}`;
const file = await giteaGetContents(cfg, repo, filePath);
if (!file || !file.content) {
return { error: `Skill "${skillName}" not found in ${repo}. Try list_skills to see available skills.` };
}
const content = Buffer.from(file.content, 'base64').toString('utf8');
// Strip YAML frontmatter if present, return just the markdown body
const body = content.replace(/^---[\s\S]*?---\s*/m, '').trim();
return { repo, skill: skillName, content: body };
}

1
dist/tools/skills.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
export {};

69
dist/tools/skills.js vendored Normal file
View File

@@ -0,0 +1,69 @@
"use strict";
// =============================================================================
// Skills tool registrations. Logic lives in ./skills-api.ts.
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const registry_1 = require("./registry");
const api = __importStar(require("./skills-api"));
(0, registry_1.registerTool)({
name: 'list_skills',
description: `List available skills for a project repo. Skills are stored in .skills/<name>/SKILL.md and provide reusable instructions the agent should follow (e.g. deploy process, test commands, code conventions).`,
parameters: {
type: 'object',
properties: {
repo: { type: 'string', description: 'Repo in "owner/name" format' }
},
required: ['repo']
},
async handler(args, ctx) {
return api.listSkills({ apiUrl: ctx.gitea.apiUrl, apiToken: ctx.gitea.apiToken }, String(args.repo));
}
});
(0, registry_1.registerTool)({
name: 'get_skill',
description: `Read the full content of a specific skill from a project repo. Call list_skills first to see what's available. Use this before spawning agents so they have the relevant project-specific instructions.`,
parameters: {
type: 'object',
properties: {
repo: { type: 'string', description: 'Repo in "owner/name" format' },
skill_name: { type: 'string', description: 'Skill name (directory name inside .skills/)' }
},
required: ['repo', 'skill_name']
},
async handler(args, ctx) {
return api.getSkill({ apiUrl: ctx.gitea.apiUrl, apiToken: ctx.gitea.apiToken }, String(args.repo), String(args.skill_name));
}
});

4
dist/tools/utils.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
/** Directory names to skip when walking or listing workspaces. */
export declare const EXCLUDED: Set<string>;
/** Resolve a relative path safely within a workspace root — throws if it tries to escape. */
export declare function safeResolve(root: string, rel: string): string;

48
dist/tools/utils.js vendored Normal file
View File

@@ -0,0 +1,48 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.EXCLUDED = void 0;
exports.safeResolve = safeResolve;
const path = __importStar(require("path"));
/** Directory names to skip when walking or listing workspaces. */
exports.EXCLUDED = new Set(['node_modules', '.git', 'dist', 'build', 'lib', '.cache', 'coverage']);
/** Resolve a relative path safely within a workspace root — throws if it tries to escape. */
function safeResolve(root, rel) {
const resolved = path.resolve(root, rel);
if (!resolved.startsWith(path.resolve(root))) {
throw new Error(`Path escapes workspace: ${rel}`);
}
return resolved;
}