@elyracode/laravel 0.7.15 → 0.7.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +7 -0
- package/extensions/index.ts +230 -0
- package/package.json +1 -1
- package/skills/elyra-laravel/SKILL.md +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [Unreleased]
|
|
4
|
+
|
|
5
|
+
## [0.7.16] - 2026-05-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `laravel_docs` tool: semantic search across 17,000+ Laravel ecosystem documentation entries with version-specific results
|
|
9
|
+
- `laravel_logs` tool: read and filter recent entries from the Laravel application log
|
|
10
|
+
- `laravel_last_error` tool: quickly retrieve the most recent error from the application log
|
|
11
|
+
|
|
3
12
|
## [0.7.12] - 2026-05-24
|
|
4
13
|
|
|
5
14
|
### Added
|
package/README.md
CHANGED
|
@@ -15,11 +15,18 @@ elyra install npm:@elyracode/laravel
|
|
|
15
15
|
| `laravel_models` | Map all Eloquent models with relationships, casts, scopes, traits, and fillable fields |
|
|
16
16
|
| `laravel_routes` | List routes with middleware, controllers, form requests, and resources |
|
|
17
17
|
| `laravel_analyze` | Analyze project architecture: stack, patterns, auth, queue, conventions |
|
|
18
|
+
| `laravel_docs` | Semantic search of Laravel ecosystem documentation (Laravel, Livewire, Inertia, Filament, Pest, Tailwind, etc.) |
|
|
19
|
+
| `laravel_logs` | Read recent log entries from the application log with count and level filtering |
|
|
20
|
+
| `laravel_last_error` | Get the most recent error or exception from the Laravel log |
|
|
18
21
|
|
|
19
22
|
## Why
|
|
20
23
|
|
|
21
24
|
Other coding agents read Laravel files one at a time and guess at conventions. With these tools, the agent sees your entire data model and architecture before writing a single line — fewer wrong guesses, fewer wasted tokens, code that matches your project.
|
|
22
25
|
|
|
26
|
+
## Documentation Search
|
|
27
|
+
|
|
28
|
+
`laravel_docs` searches 17,000+ Laravel ecosystem documentation entries using semantic search, powered by [Laravel Boost](https://laravelboost.com)'s hosted API. Results are version-specific to the project's installed packages. Requires internet access; no authentication needed.
|
|
29
|
+
|
|
23
30
|
## Included Skill
|
|
24
31
|
|
|
25
32
|
The `elyra-laravel` skill provides deep knowledge of Laravel conventions, Eloquent patterns, common architectures, and best practices.
|
package/extensions/index.ts
CHANGED
|
@@ -404,6 +404,91 @@ function analyzeProject(cwd: string, models: ModelInfo[]): string {
|
|
|
404
404
|
return lines.join("\n");
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
+
// ── Installed Packages ───────────────────────────────────────────────────────
|
|
408
|
+
|
|
409
|
+
function getInstalledPackages(cwd: string): Array<{ name: string; version: string }> {
|
|
410
|
+
const packages: Array<{ name: string; version: string }> = [];
|
|
411
|
+
const ecosystemPrefixes = [
|
|
412
|
+
"laravel/",
|
|
413
|
+
"livewire/",
|
|
414
|
+
"inertiajs/",
|
|
415
|
+
"filament/",
|
|
416
|
+
"pestphp/",
|
|
417
|
+
"spatie/",
|
|
418
|
+
"tightenco/",
|
|
419
|
+
"nunomaduro/",
|
|
420
|
+
"barryvdh/",
|
|
421
|
+
];
|
|
422
|
+
const npmEcosystemPatterns = [
|
|
423
|
+
"@inertiajs/",
|
|
424
|
+
"tailwindcss",
|
|
425
|
+
"alpinejs",
|
|
426
|
+
"@tailwindcss/",
|
|
427
|
+
"@livewire/",
|
|
428
|
+
];
|
|
429
|
+
|
|
430
|
+
const composerLockPath = join(cwd, "composer.lock");
|
|
431
|
+
if (existsSync(composerLockPath)) {
|
|
432
|
+
try {
|
|
433
|
+
const lock = JSON.parse(readFileSync(composerLockPath, "utf-8")) as {
|
|
434
|
+
packages: Array<{ name: string; version: string }>;
|
|
435
|
+
};
|
|
436
|
+
for (const pkg of lock.packages) {
|
|
437
|
+
if (ecosystemPrefixes.some((prefix) => pkg.name.startsWith(prefix))) {
|
|
438
|
+
const major = pkg.version.replace(/^v/, "").split(".")[0];
|
|
439
|
+
packages.push({ name: pkg.name, version: `${major}.x` });
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
} catch {
|
|
443
|
+
// skip
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const packageJsonPath = join(cwd, "package.json");
|
|
448
|
+
if (existsSync(packageJsonPath)) {
|
|
449
|
+
try {
|
|
450
|
+
const pkgJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as Record<string, unknown>;
|
|
451
|
+
const deps = {
|
|
452
|
+
...(pkgJson.dependencies as Record<string, string> ?? {}),
|
|
453
|
+
...(pkgJson.devDependencies as Record<string, string> ?? {}),
|
|
454
|
+
};
|
|
455
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
456
|
+
if (npmEcosystemPatterns.some((p) => name === p || name.startsWith(p))) {
|
|
457
|
+
const clean = version.replace(/^[\^~>=<]*/, "");
|
|
458
|
+
const major = clean.split(".")[0];
|
|
459
|
+
packages.push({ name, version: `${major}.x` });
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
} catch {
|
|
463
|
+
// skip
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return packages;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// ── Log Parser ──────────────────────────────────────────────────────────────
|
|
471
|
+
|
|
472
|
+
function parseLogEntries(content: string): string[] {
|
|
473
|
+
const lines = content.split("\n");
|
|
474
|
+
const entries: string[] = [];
|
|
475
|
+
const entryPattern = /^\[\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\]/;
|
|
476
|
+
let currentLines: string[] = [];
|
|
477
|
+
|
|
478
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
479
|
+
const line = lines[i];
|
|
480
|
+
if (entryPattern.test(line)) {
|
|
481
|
+
currentLines.unshift(line);
|
|
482
|
+
entries.unshift(currentLines.join("\n"));
|
|
483
|
+
currentLines = [];
|
|
484
|
+
} else {
|
|
485
|
+
currentLines.unshift(line);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return entries;
|
|
490
|
+
}
|
|
491
|
+
|
|
407
492
|
// ── Extension ───────────────────────────────────────────────────────────────
|
|
408
493
|
|
|
409
494
|
export default function (elyra: ExtensionAPI): void {
|
|
@@ -527,4 +612,149 @@ function registerTools(elyra: ExtensionAPI, getCwd: () => string): void {
|
|
|
527
612
|
return { content: [{ type: "text", text: result }] };
|
|
528
613
|
},
|
|
529
614
|
});
|
|
615
|
+
|
|
616
|
+
// ── laravel_docs ─────────────────────────────────────────────────────
|
|
617
|
+
|
|
618
|
+
const docsSchema = Type.Object({
|
|
619
|
+
queries: Type.Array(Type.String({ description: "Search query" }), {
|
|
620
|
+
description: "List of documentation search queries. Pass multiple if uncertain about terminology.",
|
|
621
|
+
}),
|
|
622
|
+
packages: Type.Optional(
|
|
623
|
+
Type.Array(Type.String({ description: "Composer or npm package name (e.g. 'laravel/framework')" }), {
|
|
624
|
+
description: "Limit search to specific packages. Uses all detected packages if omitted.",
|
|
625
|
+
}),
|
|
626
|
+
),
|
|
627
|
+
token_limit: Type.Optional(
|
|
628
|
+
Type.Integer({ description: "Max tokens in response. Default 3000, max 100000." }),
|
|
629
|
+
),
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
elyra.registerTool({
|
|
633
|
+
name: "laravel_docs",
|
|
634
|
+
label: "Laravel Docs",
|
|
635
|
+
description:
|
|
636
|
+
"Search version-specific documentation for Laravel ecosystem packages (Laravel, Livewire, Inertia, Filament, Pest, Tailwind, etc). Uses semantic search across 17,000+ documentation entries. Always use this before consulting external documentation. Results are specific to the versions installed in this project.",
|
|
637
|
+
parameters: docsSchema,
|
|
638
|
+
promptSnippet: "Search Laravel ecosystem documentation for version-specific guidance",
|
|
639
|
+
async execute(_toolCallId, params) {
|
|
640
|
+
if (!isLaravelProject(getCwd())) {
|
|
641
|
+
return { content: [{ type: "text", text: "Not a Laravel project" }], isError: true };
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
let packages = getInstalledPackages(getCwd());
|
|
645
|
+
if (params.packages && params.packages.length > 0) {
|
|
646
|
+
const filter = new Set(params.packages);
|
|
647
|
+
packages = packages.filter((p) => filter.has(p.name));
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
const tokenLimit = Math.min(params.token_limit ?? 3000, 100000);
|
|
651
|
+
|
|
652
|
+
try {
|
|
653
|
+
const response = await fetch("https://boost.laravel.com/api/docs", {
|
|
654
|
+
method: "POST",
|
|
655
|
+
headers: { "Content-Type": "application/json" },
|
|
656
|
+
body: JSON.stringify({
|
|
657
|
+
queries: params.queries,
|
|
658
|
+
packages,
|
|
659
|
+
token_limit: tokenLimit,
|
|
660
|
+
format: "markdown",
|
|
661
|
+
}),
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
if (!response.ok) {
|
|
665
|
+
return { content: [{ type: "text", text: `Docs API returned ${response.status}: ${await response.text()}` }], isError: true };
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const text = await response.text();
|
|
669
|
+
return { content: [{ type: "text", text }] };
|
|
670
|
+
} catch (err) {
|
|
671
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
672
|
+
return { content: [{ type: "text", text: `Failed to fetch docs: ${message}` }], isError: true };
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
// ── laravel_logs ─────────────────────────────────────────────────────
|
|
678
|
+
|
|
679
|
+
const logsSchema = Type.Object({
|
|
680
|
+
entries: Type.Optional(Type.Integer({ description: "Number of log entries to return. Default 10, max 50." })),
|
|
681
|
+
level: Type.Optional(Type.String({ description: "Filter by log level: emergency, alert, critical, error, warning, notice, info, debug" })),
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
elyra.registerTool({
|
|
685
|
+
name: "laravel_logs",
|
|
686
|
+
label: "Laravel Logs",
|
|
687
|
+
description:
|
|
688
|
+
"Read recent log entries from the Laravel application log (storage/logs/laravel.log). Parses multi-line PSR-3 formatted entries. Use this to diagnose errors, check for warnings, or understand application behavior.",
|
|
689
|
+
parameters: logsSchema,
|
|
690
|
+
promptSnippet: "Read recent Laravel log entries",
|
|
691
|
+
async execute(_toolCallId, params) {
|
|
692
|
+
const logPath = join(getCwd(), "storage/logs/laravel.log");
|
|
693
|
+
if (!existsSync(logPath)) {
|
|
694
|
+
return { content: [{ type: "text", text: "Log file not found at storage/logs/laravel.log" }], isError: true };
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const content = readFileSync(logPath, "utf-8");
|
|
698
|
+
let entries = parseLogEntries(content);
|
|
699
|
+
|
|
700
|
+
if (params.level) {
|
|
701
|
+
const level = params.level.toUpperCase();
|
|
702
|
+
entries = entries.filter((entry) => {
|
|
703
|
+
const match = entry.match(/^\[\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\]\s+\w+\.(\w+):/);
|
|
704
|
+
return match && match[1].toUpperCase() === level;
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const count = Math.min(Math.max(params.entries ?? 10, 1), 50);
|
|
709
|
+
entries = entries.slice(-count);
|
|
710
|
+
|
|
711
|
+
if (entries.length === 0) {
|
|
712
|
+
return { content: [{ type: "text", text: params.level ? `No log entries found with level "${params.level}".` : "No log entries found." }] };
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
let output = entries.join("\n\n");
|
|
716
|
+
const maxSize = 50 * 1024;
|
|
717
|
+
if (output.length > maxSize) {
|
|
718
|
+
output = output.slice(-maxSize);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return { content: [{ type: "text", text: output }] };
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
// ── laravel_last_error ───────────────────────────────────────────────
|
|
726
|
+
|
|
727
|
+
const lastErrorSchema = Type.Object({});
|
|
728
|
+
|
|
729
|
+
elyra.registerTool({
|
|
730
|
+
name: "laravel_last_error",
|
|
731
|
+
label: "Laravel Last Error",
|
|
732
|
+
description:
|
|
733
|
+
"Get the most recent error or exception from the Laravel application log. Useful for quick debugging when something has gone wrong.",
|
|
734
|
+
parameters: lastErrorSchema,
|
|
735
|
+
promptSnippet: "Get the last error from Laravel logs",
|
|
736
|
+
async execute() {
|
|
737
|
+
const logPath = join(getCwd(), "storage/logs/laravel.log");
|
|
738
|
+
if (!existsSync(logPath)) {
|
|
739
|
+
return { content: [{ type: "text", text: "Log file not found at storage/logs/laravel.log" }], isError: true };
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
const content = readFileSync(logPath, "utf-8");
|
|
743
|
+
const entries = parseLogEntries(content);
|
|
744
|
+
const errorLevels = new Set(["ERROR", "CRITICAL", "EMERGENCY", "ALERT"]);
|
|
745
|
+
|
|
746
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
747
|
+
const match = entries[i].match(/^\[\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\]\s+\w+\.(\w+):/);
|
|
748
|
+
if (match && errorLevels.has(match[1].toUpperCase())) {
|
|
749
|
+
let entry = entries[i];
|
|
750
|
+
if (entry.length > 2048) {
|
|
751
|
+
entry = entry.slice(0, 2048) + "\n... (truncated)";
|
|
752
|
+
}
|
|
753
|
+
return { content: [{ type: "text", text: entry }] };
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return { content: [{ type: "text", text: "No error entries found in the log." }] };
|
|
758
|
+
},
|
|
759
|
+
});
|
|
530
760
|
}
|
package/package.json
CHANGED
|
@@ -20,6 +20,9 @@ Use Laravel tools when:
|
|
|
20
20
|
| `laravel_models` | Understanding data model, relationships, and model structure before generating code |
|
|
21
21
|
| `laravel_routes` | Understanding existing routes, middleware, and controllers before adding new endpoints |
|
|
22
22
|
| `laravel_analyze` | Understanding project conventions and patterns before writing any code |
|
|
23
|
+
| `laravel_docs` | Searching Laravel ecosystem documentation before consulting external sources |
|
|
24
|
+
| `laravel_logs` | Reading recent log entries with optional count and level filtering |
|
|
25
|
+
| `laravel_last_error` | Getting the most recent error or exception from the Laravel log |
|
|
23
26
|
|
|
24
27
|
## Combining with Database Tools
|
|
25
28
|
|
|
@@ -35,6 +38,18 @@ Compare the two to find gaps:
|
|
|
35
38
|
- Missing indexes on foreign key columns
|
|
36
39
|
- Missing foreign key constraints on `_id` columns
|
|
37
40
|
|
|
41
|
+
## Documentation Search
|
|
42
|
+
|
|
43
|
+
Use `laravel_docs` before consulting external documentation. It performs semantic search across 17,000+ documentation entries covering the Laravel ecosystem: Laravel, Livewire, Inertia, Filament, Pest, Tailwind, and more.
|
|
44
|
+
|
|
45
|
+
- Results are version-specific to the project's installed packages
|
|
46
|
+
- Pass multiple queries if you are uncertain about the exact terminology
|
|
47
|
+
- Filter by specific packages when you know which one is relevant
|
|
48
|
+
|
|
49
|
+
## Log Analysis
|
|
50
|
+
|
|
51
|
+
Use `laravel_last_error` when something has broken — it is the quickest way to see what went wrong. Use `laravel_logs` for broader log analysis, filtering entries by level (e.g. `error`, `warning`, `info`). Combine both with error context to understand application behavior over time.
|
|
52
|
+
|
|
38
53
|
## Key Principle
|
|
39
54
|
|
|
40
55
|
Run `laravel_analyze` or `laravel_models` BEFORE generating Laravel code. Match the project's existing patterns — if it uses Actions, don't create Services. If controllers are invokable, don't create resource controllers.
|