@elyracode/laravel-starters 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/extensions/index.ts +84 -1
- package/extensions/starters.ts +57 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ elyra install npm:@elyracode/laravel-starters
|
|
|
21
21
|
| Tool | Description |
|
|
22
22
|
|------|-------------|
|
|
23
23
|
| `fetch_laravel_starter` | Fetch an official Laravel starter kit as reference |
|
|
24
|
+
| `apply_starter_files` | Write starter kit files directly to the project directory |
|
|
24
25
|
| `fetch_github_repo` | Fetch any public GitHub repository as reference context |
|
|
25
26
|
|
|
26
27
|
### Available Starter Kits
|
|
@@ -75,3 +76,19 @@ The agent can also fetch any public GitHub repo:
|
|
|
75
76
|
4. The agent uses the official code as reference for your customizations
|
|
76
77
|
|
|
77
78
|
No GitHub token required for public repos.
|
|
79
|
+
|
|
80
|
+
## Caching
|
|
81
|
+
|
|
82
|
+
Fetched repos are cached at `~/.elyra/cache/github/` for 24 hours. Repeat fetches are instant and don't hit GitHub's API rate limits.
|
|
83
|
+
|
|
84
|
+
## Apply to Disk
|
|
85
|
+
|
|
86
|
+
The agent can write starter kit files directly into your project:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
> Apply the Vue starter kit to my project, but skip package.json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The `apply_starter_files` tool supports:
|
|
93
|
+
- **path_prefix**: Only apply files under a specific path (e.g., `resources/`)
|
|
94
|
+
- **exclude**: Skip specific files (e.g., `package.json`, `composer.json`)
|
package/extensions/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
1
3
|
import type { ExtensionAPI } from "@elyracode/coding-agent";
|
|
2
4
|
import { Type } from "typebox";
|
|
3
|
-
import { fetchRepoContents, formatAsContext, STARTER_KITS } from "./starters.js";
|
|
5
|
+
import { type FetchedFile, fetchRepoContents, formatAsContext, STARTER_KITS } from "./starters.js";
|
|
4
6
|
|
|
5
7
|
export default function (elyra: ExtensionAPI): void {
|
|
6
8
|
// -- Command: /laravel:starter --
|
|
@@ -97,6 +99,87 @@ export default function (elyra: ExtensionAPI): void {
|
|
|
97
99
|
},
|
|
98
100
|
});
|
|
99
101
|
|
|
102
|
+
// -- Tool: apply_starter_files --
|
|
103
|
+
// Write fetched starter kit files to disk
|
|
104
|
+
elyra.registerTool({
|
|
105
|
+
name: "apply_starter_files",
|
|
106
|
+
label: "Apply Starter Kit Files",
|
|
107
|
+
description:
|
|
108
|
+
"Write files from a Laravel starter kit directly into the project directory. " +
|
|
109
|
+
"Use this after fetching a starter kit when the user confirms they want the files applied. " +
|
|
110
|
+
"Files are written relative to the current working directory. " +
|
|
111
|
+
"Existing files will be overwritten. Creates directories as needed.",
|
|
112
|
+
parameters: Type.Object({
|
|
113
|
+
kit_id: Type.String({
|
|
114
|
+
description: "The starter kit ID: react, vue, svelte, livewire",
|
|
115
|
+
}),
|
|
116
|
+
path_prefix: Type.Optional(
|
|
117
|
+
Type.String({
|
|
118
|
+
description:
|
|
119
|
+
"Only apply files under this path prefix (e.g., 'resources/', 'app/Http/'). Omit to apply all files.",
|
|
120
|
+
}),
|
|
121
|
+
),
|
|
122
|
+
exclude: Type.Optional(
|
|
123
|
+
Type.Array(Type.String(), {
|
|
124
|
+
description: "File paths to exclude (e.g., ['package.json', 'composer.json'])",
|
|
125
|
+
}),
|
|
126
|
+
),
|
|
127
|
+
}),
|
|
128
|
+
execute: async (_toolCallId, params) => {
|
|
129
|
+
const kit = STARTER_KITS.find((k) => k.id === params.kit_id);
|
|
130
|
+
if (!kit) {
|
|
131
|
+
const available = STARTER_KITS.map((k) => k.id).join(", ");
|
|
132
|
+
return {
|
|
133
|
+
content: [{ type: "text", text: `Unknown kit ID: ${params.kit_id}. Available: ${available}` }],
|
|
134
|
+
details: {},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
let files = await fetchRepoContents(kit.repo, kit.branch);
|
|
140
|
+
const cwd = process.cwd();
|
|
141
|
+
const excludeSet = new Set(params.exclude ?? []);
|
|
142
|
+
|
|
143
|
+
if (params.path_prefix) {
|
|
144
|
+
files = files.filter((f) => f.path.startsWith(params.path_prefix!));
|
|
145
|
+
}
|
|
146
|
+
files = files.filter((f) => !excludeSet.has(f.path));
|
|
147
|
+
|
|
148
|
+
const written: string[] = [];
|
|
149
|
+
const skipped: string[] = [];
|
|
150
|
+
|
|
151
|
+
for (const file of files) {
|
|
152
|
+
try {
|
|
153
|
+
const targetPath = join(cwd, file.path);
|
|
154
|
+
const dir = dirname(targetPath);
|
|
155
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
156
|
+
writeFileSync(targetPath, file.content, "utf-8");
|
|
157
|
+
written.push(file.path);
|
|
158
|
+
} catch {
|
|
159
|
+
skipped.push(file.path);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const summary = [`Applied ${written.length} files from ${kit.name} to ${cwd}`];
|
|
164
|
+
if (skipped.length > 0) {
|
|
165
|
+
summary.push(`Skipped ${skipped.length} files: ${skipped.join(", ")}`);
|
|
166
|
+
}
|
|
167
|
+
summary.push("", "Written files:", ...written.map((f) => ` ${f}`));
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
content: [{ type: "text", text: summary.join("\n") }],
|
|
171
|
+
details: { written: written.length, skipped: skipped.length },
|
|
172
|
+
};
|
|
173
|
+
} catch (error) {
|
|
174
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
175
|
+
return {
|
|
176
|
+
content: [{ type: "text", text: `Failed to apply ${kit.name}: ${msg}` }],
|
|
177
|
+
details: {},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
100
183
|
// -- Tool: fetch_github_repo --
|
|
101
184
|
// Generic tool to fetch any GitHub repo as reference
|
|
102
185
|
elyra.registerTool({
|
package/extensions/starters.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Laravel starter kit registry
|
|
2
|
+
* Laravel starter kit registry, GitHub repo fetching, and file-based caching.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
5
9
|
export interface StarterKit {
|
|
6
10
|
id: string;
|
|
7
11
|
name: string;
|
|
@@ -101,6 +105,15 @@ export async function fetchRepoContents(
|
|
|
101
105
|
".env.example",
|
|
102
106
|
];
|
|
103
107
|
|
|
108
|
+
// Check cache first
|
|
109
|
+
const cached = loadFromCache(repo, branch);
|
|
110
|
+
if (cached) {
|
|
111
|
+
let files = cached;
|
|
112
|
+
files = files.filter((f) => extensions.some((ext) => f.path.endsWith(ext)));
|
|
113
|
+
files = files.filter((f) => f.content.length <= 100000);
|
|
114
|
+
return files.slice(0, maxFiles);
|
|
115
|
+
}
|
|
116
|
+
|
|
104
117
|
// Fetch the repo tree
|
|
105
118
|
const treeUrl = `https://api.github.com/repos/${repo}/git/trees/${branch}?recursive=1`;
|
|
106
119
|
const treeResponse = await fetch(treeUrl, {
|
|
@@ -149,9 +162,52 @@ export async function fetchRepoContents(
|
|
|
149
162
|
}
|
|
150
163
|
}
|
|
151
164
|
|
|
165
|
+
// Save to cache
|
|
166
|
+
saveToCache(repo, branch, files);
|
|
167
|
+
|
|
152
168
|
return files;
|
|
153
169
|
}
|
|
154
170
|
|
|
171
|
+
// ── Cache ──
|
|
172
|
+
|
|
173
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
174
|
+
|
|
175
|
+
interface CacheEntry {
|
|
176
|
+
timestamp: number;
|
|
177
|
+
files: FetchedFile[];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function getCacheDir(): string {
|
|
181
|
+
return join(homedir(), ".elyra", "cache", "github");
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function getCacheKey(repo: string, branch: string): string {
|
|
185
|
+
return `${repo.replace(/\//g, "--")}@${branch}.json`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function loadFromCache(repo: string, branch: string): FetchedFile[] | undefined {
|
|
189
|
+
try {
|
|
190
|
+
const cachePath = join(getCacheDir(), getCacheKey(repo, branch));
|
|
191
|
+
if (!existsSync(cachePath)) return undefined;
|
|
192
|
+
const entry = JSON.parse(readFileSync(cachePath, "utf-8")) as CacheEntry;
|
|
193
|
+
if (Date.now() - entry.timestamp > CACHE_TTL_MS) return undefined;
|
|
194
|
+
return entry.files;
|
|
195
|
+
} catch {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function saveToCache(repo: string, branch: string, files: FetchedFile[]): void {
|
|
201
|
+
try {
|
|
202
|
+
const dir = getCacheDir();
|
|
203
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
204
|
+
const entry: CacheEntry = { timestamp: Date.now(), files };
|
|
205
|
+
writeFileSync(join(dir, getCacheKey(repo, branch)), JSON.stringify(entry));
|
|
206
|
+
} catch {
|
|
207
|
+
// Never fail on cache write errors
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
155
211
|
/**
|
|
156
212
|
* Format fetched files as a context string for the agent's system prompt.
|
|
157
213
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elyracode/laravel-starters",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Elyra extension for fetching, analyzing, and customizing Laravel starter kits from GitHub",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": ["elyra-package", "laravel", "starter-kit", "breeze", "jetstream", "scaffolding"],
|