init project

This commit is contained in:
root
2026-07-31 13:12:54 -04:00
parent 0da92d5e02
commit f3863f760c
7215 changed files with 1860260 additions and 1 deletions
+79
View File
@@ -0,0 +1,79 @@
import { z } from 'zod';
import { config } from '../../config.js';
import { processManager } from '../../lib/processManager.js';
import { runCommand } from '../../lib/runCommand.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { ARTISAN_ALLOWED, ARTISAN_BACKGROUND, ARTISAN_DESTRUCTIVE, isAllowedArtisanCommand, } from './artisanAllowList.js';
import { requireLaravel } from './requireLaravel.js';
export function registerArtisanTool(server) {
server.registerTool('artisan', {
title: 'Run Artisan Command',
description: 'Run a whitelisted `php artisan` subcommand in the detected Laravel project and return stdout/stderr. ' +
`Allowed commands: ${ARTISAN_ALLOWED.join(', ')}. ` +
'Destructive commands (migrate:fresh, migrate:refresh, migrate:reset, db:wipe) require confirm: true. ' +
'Long-running commands (queue:work, queue:listen) are started as tracked background processes.',
inputSchema: {
command: z
.string()
.min(1)
.describe('Artisan subcommand, e.g. "make:controller" or "migrate".'),
args: z
.array(z.string())
.optional()
.default([])
.describe('Arguments forwarded after the subcommand, e.g. ["PostController", "--api"].'),
confirm: z
.boolean()
.optional()
.describe('Required as true for destructive commands (migrate:fresh/refresh/reset, db:wipe).'),
},
}, async ({ command, args, confirm }) => {
try {
if (!isAllowedArtisanCommand(command)) {
return errorResult(`Artisan command "${command}" is not on the allow-list. Allowed: ${ARTISAN_ALLOWED.join(', ')}.`);
}
if (ARTISAN_DESTRUCTIVE.has(command) && confirm !== true) {
return errorResult(`Artisan command "${command}" is destructive and requires confirm: true.`);
}
const laravel = requireLaravel();
const phpArgs = ['artisan', command, ...(args ?? [])];
if (ARTISAN_BACKGROUND.has(command)) {
const info = processManager.start({
label: `artisan:${command}`,
command: 'php',
args: phpArgs,
cwd: laravel.root,
});
return jsonResult({
background: true,
processId: info.id,
artisanCommand: command,
artisanArgs: args ?? [],
pid: info.pid,
status: info.status,
startedAt: info.startedAt,
cwd: info.cwd,
hint: 'Use get_process_logs / stop_process to manage this background artisan process.',
});
}
const result = await runCommand('php', phpArgs, {
cwd: laravel.root,
timeoutMs: config.scaffoldCommandTimeoutMs,
});
return jsonResult({
background: false,
artisanCommand: command,
artisanArgs: args ?? [],
exitCode: result.exitCode,
timedOut: result.timedOut,
failed: result.failed,
output: result.output,
cwd: result.cwd,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=artisan.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"artisan.js","sourceRoot":"","sources":["../../../src/tools/laravel/artisan.ts"],"names":[],"mappings":"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"}
+65
View File
@@ -0,0 +1,65 @@
/**
* Artisan subcommands the `artisan` tool is willing to run.
* Anything not on this list is rejected — the agent must not pass arbitrary
* artisan commands (e.g. `tinker` interactive, `db:wipe` without confirm, etc.).
*
* Destructive commands additionally require `confirm: true` in the tool call.
*/
export const ARTISAN_ALLOWED = [
'make:controller',
'make:model',
'make:migration',
'make:seeder',
'make:factory',
'make:request',
'make:resource',
'make:middleware',
'make:job',
'make:event',
'make:listener',
'make:mail',
'make:notification',
'make:policy',
'make:command',
'make:test',
'migrate',
'migrate:rollback',
'migrate:status',
'migrate:fresh',
'migrate:refresh',
'migrate:reset',
'db:seed',
'db:wipe',
'route:list',
'config:clear',
'config:cache',
'cache:clear',
'view:clear',
'route:clear',
'optimize:clear',
'queue:work',
'queue:listen',
'queue:failed',
'queue:retry',
'queue:batches',
'queue:clear',
'reverb:start',
'storage:link',
'key:generate',
'about',
'list',
];
/** Commands that mutate/destroy data and therefore require confirm: true. */
export const ARTISAN_DESTRUCTIVE = new Set([
'migrate:fresh',
'migrate:refresh',
'migrate:reset',
'db:wipe',
'queue:clear',
]);
/** Long-running commands tracked via processManager instead of runCommand. */
export const ARTISAN_BACKGROUND = new Set(['queue:work', 'queue:listen', 'reverb:start']);
export function isAllowedArtisanCommand(command) {
return ARTISAN_ALLOWED.includes(command);
}
//# sourceMappingURL=artisanAllowList.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"artisanAllowList.js","sourceRoot":"","sources":["../../../src/tools/laravel/artisanAllowList.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,iBAAiB;IACjB,YAAY;IACZ,gBAAgB;IAChB,aAAa;IACb,cAAc;IACd,cAAc;IACd,eAAe;IACf,iBAAiB;IACjB,UAAU;IACV,YAAY;IACZ,eAAe;IACf,WAAW;IACX,mBAAmB;IACnB,aAAa;IACb,cAAc;IACd,WAAW;IACX,SAAS;IACT,kBAAkB;IAClB,gBAAgB;IAChB,eAAe;IACf,iBAAiB;IACjB,eAAe;IACf,SAAS;IACT,SAAS;IACT,YAAY;IACZ,cAAc;IACd,cAAc;IACd,aAAa;IACb,YAAY;IACZ,aAAa;IACb,gBAAgB;IAChB,YAAY;IACZ,cAAc;IACd,cAAc;IACd,aAAa;IACb,eAAe;IACf,aAAa;IACb,cAAc;IACd,cAAc;IACd,cAAc;IACd,OAAO;IACP,MAAM;CACE,CAAC;AAIX,6EAA6E;AAC7E,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAS;IACjD,eAAe;IACf,iBAAiB;IACjB,eAAe;IACf,SAAS;IACT,aAAa;CACd,CAAC,CAAC;AAEH,8EAA8E;AAC9E,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAS,CAAC,YAAY,EAAE,cAAc,EAAE,cAAc,CAAC,CAAC,CAAC;AAElG,MAAM,UAAU,uBAAuB,CAAC,OAAe;IACrD,OAAQ,eAAqC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AAClE,CAAC"}
+57
View File
@@ -0,0 +1,57 @@
import { z } from 'zod';
import { config } from '../../config.js';
import { runCommand } from '../../lib/runCommand.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { requirePhpBackend } from './requireLaravel.js';
/**
* Composer helpers shared by Laravel and CodeIgniter (both are Composer-based).
* Mirrors `add_dependency` but is explicitly for PHP packages.
*/
export function registerComposerTools(server) {
server.registerTool('composer_require', {
title: 'Composer Require',
description: 'Add a Composer dependency to the detected PHP backend (Laravel or CodeIgniter). Equivalent to `composer require`.',
inputSchema: {
packages: z.array(z.string().min(1)).min(1).describe('Packagist package names, e.g. ["laravel/sanctum"].'),
dev: z.boolean().optional().default(false).describe('Install into require-dev (--dev).'),
},
}, async ({ packages, dev }) => {
try {
const backend = requirePhpBackend();
const args = ['require', ...(dev ? ['--dev'] : []), ...packages, '--no-interaction'];
const result = await runCommand('composer', args, {
cwd: backend.root,
timeoutMs: config.scaffoldCommandTimeoutMs,
});
return jsonResult({ adapter: backend.label, packages, dev, ...result });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
server.registerTool('composer_remove', {
title: 'Composer Remove',
description: 'Remove a Composer dependency from the detected PHP backend (Laravel or CodeIgniter). Requires confirm: true.',
inputSchema: {
packages: z.array(z.string().min(1)).min(1).describe('Packagist package names to remove.'),
confirm: z
.literal(true)
.describe('Must be exactly true; removing dependencies is a deliberate opt-in.'),
},
}, async ({ packages, confirm }) => {
if (!confirm)
return errorResult('confirm: true is required to run composer_remove.');
try {
const backend = requirePhpBackend();
const result = await runCommand('composer', ['remove', ...packages, '--no-interaction'], {
cwd: backend.root,
timeoutMs: config.scaffoldCommandTimeoutMs,
});
return jsonResult({ adapter: backend.label, packages, ...result });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=composer.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"composer.js","sourceRoot":"","sources":["../../../src/tools/laravel/composer.ts"],"names":[],"mappings":"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"}
+82
View File
@@ -0,0 +1,82 @@
import { z } from 'zod';
import { config } from '../../config.js';
import { hasComposerDependency, readComposerJson } from '../../lib/frameworks/composer.js';
import { runCommand } from '../../lib/runCommand.js';
import { pascalCase } from '../../lib/strings.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { requireLaravel } from './requireLaravel.js';
export function registerGenerateFilamentResourceTool(server) {
server.registerTool('generate_filament_resource', {
title: 'Generate Filament Resource',
description: 'Generate a Filament admin resource via `php artisan make:filament-resource`. Requires filament/filament to be installed.',
inputSchema: {
name: z.string().min(1).describe('Resource/model name, e.g. "Post".'),
generate: z
.boolean()
.optional()
.default(true)
.describe('Pass --generate to Filament when true (default).'),
},
}, async ({ name, generate }) => {
try {
const laravel = requireLaravel();
const composer = readComposerJson(laravel.root);
if (!hasComposerDependency(composer, 'filament/filament')) {
return errorResult('filament/filament is not installed. Run composer_require with package "filament/filament" first ' +
'(or `composer require filament/filament`).');
}
const resource = pascalCase(name);
const args = ['artisan', 'make:filament-resource', resource, '--no-interaction'];
if (generate !== false)
args.push('--generate');
const result = await runCommand('php', args, {
cwd: laravel.root,
timeoutMs: config.scaffoldCommandTimeoutMs,
});
return jsonResult({
package: 'filament/filament',
resource,
exitCode: result.exitCode,
failed: result.failed,
output: result.output,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
export function registerGenerateNovaResourceTool(server) {
server.registerTool('generate_nova_resource', {
title: 'Generate Nova Resource',
description: 'Generate a Laravel Nova resource via `php artisan nova:resource`. Requires laravel/nova to be installed.',
inputSchema: {
name: z.string().min(1).describe('Resource name, e.g. "Post".'),
},
}, async ({ name }) => {
try {
const laravel = requireLaravel();
const composer = readComposerJson(laravel.root);
if (!hasComposerDependency(composer, 'laravel/nova')) {
return errorResult('laravel/nova is not installed. Install Nova (license required) then re-run this tool. ' +
'See https://nova.laravel.com/docs for installation.');
}
const resource = pascalCase(name);
const result = await runCommand('php', ['artisan', 'nova:resource', resource, '--no-interaction'], {
cwd: laravel.root,
timeoutMs: config.scaffoldCommandTimeoutMs,
});
return jsonResult({
package: 'laravel/nova',
resource,
exitCode: result.exitCode,
failed: result.failed,
output: result.output,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=generateAdminResources.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"generateAdminResources.js","sourceRoot":"","sources":["../../../src/tools/laravel/generateAdminResources.ts"],"names":[],"mappings":"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"}
+116
View File
@@ -0,0 +1,116 @@
import fs from 'node:fs';
import path from 'node:path';
import { z } from 'zod';
import { camelCase, pascalCase } from '../../lib/strings.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { requireLaravel } from './requireLaravel.js';
const RELATION_TYPES = [
'belongsTo',
'hasOne',
'hasMany',
'belongsToMany',
'hasManyThrough',
'morphTo',
'morphMany',
'morphOne',
];
function findModelFile(root, model) {
const candidates = [
path.join(root, 'app', 'Models', `${model}.php`),
path.join(root, 'app', `${model}.php`),
];
return candidates.find((file) => fs.existsSync(file)) ?? null;
}
function relationMethodSource(methodName, relation, relatedModel) {
const related = `\\App\\Models\\${relatedModel}::class`;
return `
public function ${methodName}()
{
return $this->${relation}(${related});
}
`;
}
function insertMethodBeforeClosingBrace(source, method) {
const trimmed = method.trimEnd() + '\n';
if (source.includes(`function ${trimmed.match(/function\s+(\w+)/)?.[1] ?? ''}(`)) {
return source;
}
const lastBrace = source.lastIndexOf('}');
if (lastBrace === -1) {
throw new Error('Could not find closing class brace in model file.');
}
return `${source.slice(0, lastBrace)}${trimmed}${source.slice(lastBrace)}`;
}
export function registerGenerateEloquentRelationTool(server) {
server.registerTool('generate_eloquent_relation', {
title: 'Generate Eloquent Relation',
description: 'Add an Eloquent relationship method to a model (belongsTo, hasMany, etc.), optionally also adding the inverse relation on the related model.',
inputSchema: {
model: z.string().min(1).describe('Model class name, e.g. "Post".'),
relation: z.enum(RELATION_TYPES).describe('Eloquent relation type.'),
related: z.string().min(1).describe('Related model class name, e.g. "User".'),
method: z.string().optional().describe('Method name on the source model. Defaults from relation type.'),
inverse: z
.boolean()
.optional()
.default(false)
.describe('If true, also add a sensible inverse method on the related model.'),
},
}, async ({ model, relation, related, method, inverse }) => {
try {
const laravel = requireLaravel();
const modelName = pascalCase(model);
const relatedName = pascalCase(related);
const modelFile = findModelFile(laravel.root, modelName);
if (!modelFile) {
return errorResult(`Model file not found for "${modelName}" under app/Models or app/.`);
}
const defaultMethod = relation === 'belongsTo' || relation === 'hasOne' || relation === 'morphTo' || relation === 'morphOne'
? camelCase(relatedName)
: `${camelCase(relatedName)}s`;
const methodName = method?.trim() || defaultMethod;
const before = fs.readFileSync(modelFile, 'utf8');
const next = insertMethodBeforeClosingBrace(before, relationMethodSource(methodName, relation, relatedName));
fs.writeFileSync(modelFile, next);
const updated = [modelFile];
let inverseMethod = null;
let inverseFile = null;
if (inverse) {
const inverseMap = {
belongsTo: 'hasMany',
hasMany: 'belongsTo',
hasOne: 'belongsTo',
belongsToMany: 'belongsToMany',
morphMany: 'morphTo',
morphOne: 'morphTo',
};
const inverseRelation = inverseMap[relation];
if (inverseRelation) {
inverseFile = findModelFile(laravel.root, relatedName);
if (!inverseFile) {
return errorResult(`Added ${methodName}() on ${modelName}, but related model "${relatedName}" was not found for the inverse.`);
}
inverseMethod =
inverseRelation === 'belongsTo' || inverseRelation === 'hasOne' || inverseRelation === 'morphTo'
? camelCase(modelName)
: `${camelCase(modelName)}s`;
const relatedSource = fs.readFileSync(inverseFile, 'utf8');
fs.writeFileSync(inverseFile, insertMethodBeforeClosingBrace(relatedSource, relationMethodSource(inverseMethod, inverseRelation, modelName)));
updated.push(inverseFile);
}
}
return jsonResult({
model: modelName,
related: relatedName,
relation,
method: methodName,
inverseMethod,
files: updated,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=generateEloquentRelation.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"generateEloquentRelation.js","sourceRoot":"","sources":["../../../src/tools/laravel/generateEloquentRelation.ts"],"names":[],"mappings":"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"}
+113
View File
@@ -0,0 +1,113 @@
import fs from 'node:fs';
import path from 'node:path';
import { z } from 'zod';
import { config } from '../../config.js';
import { runCommand } from '../../lib/runCommand.js';
import { kebabCase, pascalCase } from '../../lib/strings.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { requireLaravel } from './requireLaravel.js';
function pluralize(word) {
const lower = word.toLowerCase();
if (lower.endsWith('ies'))
return word;
if (lower.endsWith('y') && !/[aeiou]y$/i.test(word))
return `${word.slice(0, -1)}ies`;
if (/(s|x|z|ch|sh)$/i.test(word))
return `${word}es`;
if (lower.endsWith('s'))
return word;
return `${word}s`;
}
export function registerGenerateLaravelResourceTool(server) {
server.registerTool('generate_laravel_resource', {
title: 'Generate Laravel Resource',
description: 'One-shot scaffold of a full Laravel resource: model + migration + factory + seeder + controller, ' +
'then wire an apiResource/resource route. Wraps the relevant `php artisan make:*` calls.',
inputSchema: {
name: z
.string()
.min(1)
.describe('Resource name, e.g. "Post" or "blog_post". Converted to PascalCase for the model/controller.'),
api: z
.boolean()
.optional()
.default(true)
.describe('If true (default), generate an API controller (--api) and register Route::apiResource in routes/api.php. If false, generate a web resource controller and register Route::resource in routes/web.php.'),
migrate: z
.boolean()
.optional()
.default(false)
.describe('If true, run `php artisan migrate` after scaffolding. Requires a configured database.'),
},
}, async ({ name, api, migrate }) => {
try {
const laravel = requireLaravel();
const model = pascalCase(name);
const controller = `${model}Controller`;
const routeName = kebabCase(pluralize(model));
const steps = [];
// Model + migration + factory + seeder in one go.
const modelResult = await runCommand('php', ['artisan', 'make:model', model, '-mfs', '--no-interaction'], { cwd: laravel.root, timeoutMs: config.scaffoldCommandTimeoutMs });
steps.push({ step: 'make:model -mfs', ...modelResult });
const controllerArgs = api
? ['artisan', 'make:controller', controller, '--api', '--model=' + model, '--no-interaction']
: ['artisan', 'make:controller', controller, '--resource', '--model=' + model, '--no-interaction'];
const controllerResult = await runCommand('php', controllerArgs, {
cwd: laravel.root,
timeoutMs: config.scaffoldCommandTimeoutMs,
});
steps.push({ step: 'make:controller', ...controllerResult });
const routesFile = path.join(laravel.root, 'routes', api ? 'api.php' : 'web.php');
const routeLine = api
? `Route::apiResource('${routeName}', \\App\\Http\\Controllers\\${controller}::class);`
: `Route::resource('${routeName}', \\App\\Http\\Controllers\\${controller}::class);`;
const routeWired = wireRoute(routesFile, routeLine, api);
steps.push({ step: 'wire-route', routesFile, routeLine, wired: routeWired });
let migrateResult = null;
if (migrate) {
migrateResult = await runCommand('php', ['artisan', 'migrate', '--no-interaction'], {
cwd: laravel.root,
timeoutMs: config.scaffoldCommandTimeoutMs,
});
steps.push({ step: 'migrate', ...migrateResult });
}
return jsonResult({
model,
controller,
route: `${api ? 'apiResource' : 'resource'} /${routeName}`,
routesFile,
routeWired,
steps,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
function wireRoute(routesFile, routeLine, api) {
fs.mkdirSync(path.dirname(routesFile), { recursive: true });
if (!fs.existsSync(routesFile)) {
const header = api
? `<?php\n\nuse Illuminate\\Support\\Facades\\Route;\n\n`
: `<?php\n\nuse Illuminate\\Support\\Facades\\Route;\n\n`;
fs.writeFileSync(routesFile, header + routeLine + '\n');
return true;
}
const existing = fs.readFileSync(routesFile, 'utf8');
if (existing.includes(routeLine.trim()))
return false;
// Ensure the Route facade is imported if the file already exists without it.
let next = existing;
if (!/use\s+Illuminate\\Support\\Facades\\Route\s*;/.test(next)) {
if (next.startsWith('<?php')) {
next = next.replace('<?php', "<?php\n\nuse Illuminate\\Support\\Facades\\Route;");
}
else {
next = `<?php\n\nuse Illuminate\\Support\\Facades\\Route;\n\n${next}`;
}
}
fs.writeFileSync(routesFile, `${next.trimEnd()}\n${routeLine}\n`);
return true;
}
//# sourceMappingURL=generateLaravelResource.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"generateLaravelResource.js","sourceRoot":"","sources":["../../../src/tools/laravel/generateLaravelResource.ts"],"names":[],"mappings":"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"}
+20
View File
@@ -0,0 +1,20 @@
import { registerArtisanTool } from './artisan.js';
import { registerGenerateLaravelResourceTool } from './generateLaravelResource.js';
import { registerComposerTools } from './composer.js';
import { registerPhpTestTools } from './runPhpTests.js';
import { registerTinkerEvalTool } from './tinkerEval.js';
import { registerGenerateEloquentRelationTool } from './generateEloquentRelation.js';
import { registerGenerateFilamentResourceTool, registerGenerateNovaResourceTool } from './generateAdminResources.js';
import { registerLaravelQueueStatusTool } from './queueStatus.js';
export function registerLaravelTools(server) {
registerArtisanTool(server);
registerGenerateLaravelResourceTool(server);
registerComposerTools(server);
registerPhpTestTools(server);
registerTinkerEvalTool(server);
registerGenerateEloquentRelationTool(server);
registerGenerateFilamentResourceTool(server);
registerGenerateNovaResourceTool(server);
registerLaravelQueueStatusTool(server);
}
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/tools/laravel/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,mCAAmC,EAAE,MAAM,8BAA8B,CAAC;AACnF,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,oCAAoC,EAAE,MAAM,+BAA+B,CAAC;AACrF,OAAO,EAAE,oCAAoC,EAAE,gCAAgC,EAAE,MAAM,6BAA6B,CAAC;AACrH,OAAO,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AAElE,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IACpD,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,mCAAmC,CAAC,MAAM,CAAC,CAAC;IAC5C,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC/B,oCAAoC,CAAC,MAAM,CAAC,CAAC;IAC7C,oCAAoC,CAAC,MAAM,CAAC,CAAC;IAC7C,gCAAgC,CAAC,MAAM,CAAC,CAAC;IACzC,8BAA8B,CAAC,MAAM,CAAC,CAAC;AACzC,CAAC"}
+57
View File
@@ -0,0 +1,57 @@
import { z } from 'zod';
import { config } from '../../config.js';
import { runCommand } from '../../lib/runCommand.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { requireLaravel } from './requireLaravel.js';
export function registerLaravelQueueStatusTool(server) {
server.registerTool('laravel_queue_status', {
title: 'Laravel Queue Status',
description: 'Summarize Laravel queue health via `php artisan queue:failed` (and optionally `queue:batches` when available). Read-only.',
inputSchema: {
includeBatches: z
.boolean()
.optional()
.default(false)
.describe('If true, also run `php artisan queue:batches` when supported.'),
},
}, async ({ includeBatches }) => {
try {
const laravel = requireLaravel();
const failed = await runCommand('php', ['artisan', 'queue:failed', '--no-interaction'], {
cwd: laravel.root,
timeoutMs: config.defaultCommandTimeoutMs,
});
let batches = null;
if (includeBatches) {
batches = await runCommand('php', ['artisan', 'queue:batches', '--no-interaction'], {
cwd: laravel.root,
timeoutMs: config.defaultCommandTimeoutMs,
});
}
const failedLines = failed.output
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean);
const failedCount = failedLines.filter((line) => /^\d+\s+/.test(line) || /\|/.test(line)).length;
return jsonResult({
failed: {
exitCode: failed.exitCode,
failed: failed.failed,
approxRows: failedCount,
output: failed.output,
},
batches: batches
? {
exitCode: batches.exitCode,
failed: batches.failed,
output: batches.output,
}
: null,
});
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=queueStatus.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"queueStatus.js","sourceRoot":"","sources":["../../../src/tools/laravel/queueStatus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,MAAM,UAAU,8BAA8B,CAAC,MAAiB;IAC9D,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,KAAK,EAAE,sBAAsB;QAC7B,WAAW,EACT,2HAA2H;QAC7H,WAAW,EAAE;YACX,cAAc,EAAE,CAAC;iBACd,OAAO,EAAE;iBACT,QAAQ,EAAE;iBACV,OAAO,CAAC,KAAK,CAAC;iBACd,QAAQ,CAAC,+DAA+D,CAAC;SAC7E;KACF,EACD,KAAK,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE;QAC3B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,cAAc,EAAE,kBAAkB,CAAC,EAAE;gBACtF,GAAG,EAAE,OAAO,CAAC,IAAI;gBACjB,SAAS,EAAE,MAAM,CAAC,uBAAuB;aAC1C,CAAC,CAAC;YAEH,IAAI,OAAO,GAAkD,IAAI,CAAC;YAClE,IAAI,cAAc,EAAE,CAAC;gBACnB,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,eAAe,EAAE,kBAAkB,CAAC,EAAE;oBAClF,GAAG,EAAE,OAAO,CAAC,IAAI;oBACjB,SAAS,EAAE,MAAM,CAAC,uBAAuB;iBAC1C,CAAC,CAAC;YACL,CAAC;YAED,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM;iBAC9B,KAAK,CAAC,OAAO,CAAC;iBACd,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;iBAC1B,MAAM,CAAC,OAAO,CAAC,CAAC;YACnB,MAAM,WAAW,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;YAEjG,OAAO,UAAU,CAAC;gBAChB,MAAM,EAAE;oBACN,QAAQ,EAAE,MAAM,CAAC,QAAQ;oBACzB,MAAM,EAAE,MAAM,CAAC,MAAM;oBACrB,UAAU,EAAE,WAAW;oBACvB,MAAM,EAAE,MAAM,CAAC,MAAM;iBACtB;gBACD,OAAO,EAAE,OAAO;oBACd,CAAC,CAAC;wBACE,QAAQ,EAAE,OAAO,CAAC,QAAQ;wBAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;wBACtB,MAAM,EAAE,OAAO,CAAC,MAAM;qBACvB;oBACH,CAAC,CAAC,IAAI;aACT,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
+21
View File
@@ -0,0 +1,21 @@
import { config } from '../../config.js';
import { detectProject } from '../../lib/frameworks/detect.js';
import { LaravelAdapter } from '../../lib/frameworks/laravel.js';
export function requireLaravel() {
const detected = detectProject(config.workspaceRoot);
if (detected.backend instanceof LaravelAdapter) {
return detected.backend;
}
throw new Error('No Laravel project detected in WORKSPACE_ROOT. ' +
'Expected composer.json with laravel/framework (or an artisan file) at the workspace root.');
}
/** Any Composer-based PHP backend (Laravel or CodeIgniter). */
export function requirePhpBackend() {
const detected = detectProject(config.workspaceRoot);
if (detected.backend && (detected.backend.kind === 'laravel' || detected.backend.kind === 'codeigniter')) {
return detected.backend;
}
throw new Error('No PHP backend (Laravel or CodeIgniter) detected in WORKSPACE_ROOT. ' +
'Expected a composer.json with laravel/framework or codeigniter4/framework.');
}
//# sourceMappingURL=requireLaravel.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"requireLaravel.js","sourceRoot":"","sources":["../../../src/tools/laravel/requireLaravel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAGjE,MAAM,UAAU,cAAc;IAC5B,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IACrD,IAAI,QAAQ,CAAC,OAAO,YAAY,cAAc,EAAE,CAAC;QAC/C,OAAO,QAAQ,CAAC,OAAO,CAAC;IAC1B,CAAC;IACD,MAAM,IAAI,KAAK,CACb,iDAAiD;QAC/C,2FAA2F,CAC9F,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,iBAAiB;IAC/B,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IACrD,IAAI,QAAQ,CAAC,OAAO,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,aAAa,CAAC,EAAE,CAAC;QACzG,OAAO,QAAQ,CAAC,OAAO,CAAC;IAC1B,CAAC;IACD,MAAM,IAAI,KAAK,CACb,sEAAsE;QACpE,4EAA4E,CAC/E,CAAC;AACJ,CAAC"}
+100
View File
@@ -0,0 +1,100 @@
import path from 'node:path';
import fs from 'node:fs';
import { z } from 'zod';
import { config } from '../../config.js';
import { vendorBinExists } from '../../lib/frameworks/composer.js';
import { runCommand } from '../../lib/runCommand.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { requireLaravel } from './requireLaravel.js';
function vendorBin(root, bin) {
const suffix = process.platform === 'win32' ? '.bat' : '';
return path.join(root, 'vendor', 'bin', `${bin}${suffix}`);
}
function parsePhpUnitSummary(output) {
// PHPUnit: "OK (12 tests, 34 assertions)" or "FAILURES!\nTests: 12, Assertions: 30, Failures: 2."
const ok = output.match(/OK\s*\((\d+)\s+tests?,\s*(\d+)\s+assertions?\)/i);
if (ok) {
return { passed: true, tests: Number(ok[1]), assertions: Number(ok[2]), failures: 0, errors: 0 };
}
const summary = output.match(/Tests:\s*(\d+),\s*Assertions:\s*(\d+)(?:,\s*Errors:\s*(\d+))?(?:,\s*Failures:\s*(\d+))?(?:,\s*Skipped:\s*(\d+))?/i);
if (summary) {
return {
passed: !/FAILURES!|ERRORS!/i.test(output),
tests: Number(summary[1]),
assertions: Number(summary[2]),
errors: Number(summary[3] ?? 0),
failures: Number(summary[4] ?? 0),
skipped: Number(summary[5] ?? 0),
};
}
// Pest often prints "Tests: 12 passed (34 assertions)" or similar.
const pest = output.match(/Tests:\s+(\d+)\s+passed/i);
if (pest) {
return { passed: !/failed|FAIL/i.test(output), tests: Number(pest[1]) };
}
return { passed: null, note: 'Could not parse a pass/fail summary from the runner output.' };
}
export function registerPhpTestTools(server) {
server.registerTool('run_phpunit', {
title: 'Run PHPUnit',
description: "Run the Laravel project's PHPUnit suite via vendor/bin/phpunit (or `php artisan test` as a fallback). Returns parsed pass/fail counts when possible.",
inputSchema: {
extraArgs: z
.array(z.string())
.optional()
.describe('Extra CLI args, e.g. ["--filter", "UserTest"].'),
},
}, async ({ extraArgs }) => {
try {
const laravel = requireLaravel();
const args = extraArgs ?? [];
let command;
let cmdArgs;
if (vendorBinExists(laravel.root, 'phpunit')) {
command = vendorBin(laravel.root, 'phpunit');
cmdArgs = args;
}
else if (fs.existsSync(path.join(laravel.root, 'artisan'))) {
command = 'php';
cmdArgs = ['artisan', 'test', '--without-tty', ...args];
}
else {
return errorResult(`No vendor/bin/phpunit found at ${laravel.root}. Run composer install (or run_install) first.`);
}
const result = await runCommand(command, cmdArgs, {
cwd: laravel.root,
timeoutMs: config.scaffoldCommandTimeoutMs,
});
return jsonResult({ runner: 'phpunit', summary: parsePhpUnitSummary(result.output), ...result });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
server.registerTool('run_pest', {
title: 'Run Pest',
description: "Run the Laravel project's Pest suite via vendor/bin/pest. Falls back to an error if Pest is not installed.",
inputSchema: {
extraArgs: z
.array(z.string())
.optional()
.describe('Extra CLI args, e.g. ["--filter", "it creates a post"].'),
},
}, async ({ extraArgs }) => {
try {
const laravel = requireLaravel();
if (!vendorBinExists(laravel.root, 'pest')) {
return errorResult(`No vendor/bin/pest found at ${laravel.root}. Install pestphp/pest (composer_require) or use run_phpunit instead.`);
}
const result = await runCommand(vendorBin(laravel.root, 'pest'), extraArgs ?? [], {
cwd: laravel.root,
timeoutMs: config.scaffoldCommandTimeoutMs,
});
return jsonResult({ runner: 'pest', summary: parsePhpUnitSummary(result.output), ...result });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=runPhpTests.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"runPhpTests.js","sourceRoot":"","sources":["../../../src/tools/laravel/runPhpTests.ts"],"names":[],"mappings":"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"}
+47
View File
@@ -0,0 +1,47 @@
import { z } from 'zod';
import { config } from '../../config.js';
import { runCommand } from '../../lib/runCommand.js';
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
import { requireLaravel } from './requireLaravel.js';
/**
* Heuristic: treat an expression as a write if it contains common Eloquent /
* DB mutation method names. Read-only inspections (find, get, all, count,
* toArray, …) do not require confirm.
*/
const WRITE_PATTERN = /\b(save|create|update|delete|destroy|forceDelete|insert|upsert|truncate|updateOrCreate|firstOrCreate|push|attach|detach|sync|restore|increment|decrement)\s*\(/i;
export function registerTinkerEvalTool(server) {
server.registerTool('laravel_tinker_eval', {
title: 'Laravel Tinker Eval',
description: 'Evaluate a short PHP expression/snippet via `php artisan tinker --execute` for quick data/model inspection. ' +
'Read-only by convention. Expressions that appear to mutate data (save/create/update/delete/…) require confirm: true.',
inputSchema: {
code: z
.string()
.min(1)
.max(4000)
.describe('PHP expression to evaluate, e.g. "\\\\App\\\\Models\\\\User::count()" or "config(\'app.name\')".'),
confirm: z
.boolean()
.optional()
.describe('Required as true when the expression appears to perform a write/mutation.'),
},
}, async ({ code, confirm }) => {
try {
if (WRITE_PATTERN.test(code) && confirm !== true) {
return errorResult('This expression looks like it mutates data (save/create/update/delete/…). ' +
'Re-run with confirm: true if you intentionally want to write.');
}
const laravel = requireLaravel();
// --execute runs the snippet non-interactively and prints the result.
const result = await runCommand('php', ['artisan', 'tinker', `--execute=${code}`], {
cwd: laravel.root,
timeoutMs: config.defaultCommandTimeoutMs,
});
return jsonResult({ code, writeDetected: WRITE_PATTERN.test(code), ...result });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=tinkerEval.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"tinkerEval.js","sourceRoot":"","sources":["../../../src/tools/laravel/tinkerEval.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;GAIG;AACH,MAAM,aAAa,GACjB,iKAAiK,CAAC;AAEpK,MAAM,UAAU,sBAAsB,CAAC,MAAiB;IACtD,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,KAAK,EAAE,qBAAqB;QAC5B,WAAW,EACT,8GAA8G;YAC9G,sHAAsH;QACxH,WAAW,EAAE;YACX,IAAI,EAAE,CAAC;iBACJ,MAAM,EAAE;iBACR,GAAG,CAAC,CAAC,CAAC;iBACN,GAAG,CAAC,IAAI,CAAC;iBACT,QAAQ,CAAC,kGAAkG,CAAC;YAC/G,OAAO,EAAE,CAAC;iBACP,OAAO,EAAE;iBACT,QAAQ,EAAE;iBACV,QAAQ,CAAC,2EAA2E,CAAC;SACzF;KACF,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;QAC1B,IAAI,CAAC;YACH,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBACjD,OAAO,WAAW,CAChB,4EAA4E;oBAC1E,+DAA+D,CAClE,CAAC;YACJ,CAAC;YAED,MAAM,OAAO,GAAG,cAAc,EAAE,CAAC;YACjC,sEAAsE;YACtE,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,aAAa,IAAI,EAAE,CAAC,EAAE;gBACjF,GAAG,EAAE,OAAO,CAAC,IAAI;gBACjB,SAAS,EAAE,MAAM,CAAC,uBAAuB;aAC1C,CAAC,CAAC;YACH,OAAO,UAAU,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;QAClF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,WAAW,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}