@elyracode/laravel-starters 0.3.4 → 0.3.7

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 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`)
@@ -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({
@@ -1,7 +1,11 @@
1
1
  /**
2
- * Laravel starter kit registry and GitHub repo fetching.
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.4",
3
+ "version": "0.3.7",
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"],