aem-ext-daemon 0.1.0 → 0.3.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/dist/capabilities/fs.d.ts +3 -1
- package/dist/capabilities/fs.js +19 -1
- package/dist/capabilities/skills.d.ts +23 -0
- package/dist/capabilities/skills.js +119 -0
- package/dist/dispatcher.js +19 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Filesystem capabilities — browse, read, write, list.
|
|
2
|
+
* Filesystem capabilities — browse, read, write, list, roots.
|
|
3
3
|
*/
|
|
4
4
|
export interface BrowseEntry {
|
|
5
5
|
name: string;
|
|
@@ -9,6 +9,8 @@ export interface BrowseEntry {
|
|
|
9
9
|
}
|
|
10
10
|
/** Browse a directory — returns immediate children with metadata. */
|
|
11
11
|
export declare function browse(dirPath: string, showHidden?: boolean): string;
|
|
12
|
+
/** Return useful root paths for folder browsing (home dir, system root, etc). */
|
|
13
|
+
export declare function roots(): string;
|
|
12
14
|
/** Read a file and return its content. */
|
|
13
15
|
export declare function readFile(filePath: string): string;
|
|
14
16
|
/** Write content to a file. Creates parent directories if needed. */
|
package/dist/capabilities/fs.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Filesystem capabilities — browse, read, write, list.
|
|
2
|
+
* Filesystem capabilities — browse, read, write, list, roots.
|
|
3
3
|
*/
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import os from "node:os";
|
|
6
7
|
/** Browse a directory — returns immediate children with metadata. */
|
|
7
8
|
export function browse(dirPath, showHidden = false) {
|
|
8
9
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
@@ -24,6 +25,23 @@ export function browse(dirPath, showHidden = false) {
|
|
|
24
25
|
});
|
|
25
26
|
return JSON.stringify(result);
|
|
26
27
|
}
|
|
28
|
+
/** Return useful root paths for folder browsing (home dir, system root, etc). */
|
|
29
|
+
export function roots() {
|
|
30
|
+
const home = os.homedir();
|
|
31
|
+
const entries = [
|
|
32
|
+
{ name: "Home", path: home },
|
|
33
|
+
{ name: "Desktop", path: path.join(home, "Desktop") },
|
|
34
|
+
{ name: "Documents", path: path.join(home, "Documents") },
|
|
35
|
+
];
|
|
36
|
+
if (process.platform === "darwin") {
|
|
37
|
+
entries.push({ name: "Volumes", path: "/Volumes" });
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
entries.push({ name: "/", path: "/" });
|
|
41
|
+
}
|
|
42
|
+
// Filter to only existing paths
|
|
43
|
+
return JSON.stringify(entries.filter((e) => fs.existsSync(e.path)));
|
|
44
|
+
}
|
|
27
45
|
/** Read a file and return its content. */
|
|
28
46
|
export function readFile(filePath) {
|
|
29
47
|
if (!fs.existsSync(filePath)) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills capabilities — sync skills from local .skills folder,
|
|
3
|
+
* seed default skills from GitHub if none exist.
|
|
4
|
+
*/
|
|
5
|
+
export interface SkillEntry {
|
|
6
|
+
name: string;
|
|
7
|
+
content: string;
|
|
8
|
+
scripts?: Array<{
|
|
9
|
+
name: string;
|
|
10
|
+
content: string;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Read all skills from the .skills folder in the given workspace root.
|
|
15
|
+
* Returns an array of skill entries with name, SKILL.md content, and any scripts.
|
|
16
|
+
*/
|
|
17
|
+
export declare function syncSkills(workspaceRoot: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Seed default skills into the workspace .skills folder by cloning from GitHub.
|
|
20
|
+
* Only seeds if .skills folder doesn't already exist.
|
|
21
|
+
* Uses sparse checkout to only pull the skills subfolder.
|
|
22
|
+
*/
|
|
23
|
+
export declare function seedSkills(workspaceRoot: string): string;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills capabilities — sync skills from local .skills folder,
|
|
3
|
+
* seed default skills from GitHub if none exist.
|
|
4
|
+
*/
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { execSync } from "node:child_process";
|
|
8
|
+
const DEFAULT_SKILLS_REPO = "https://github.com/znikolovski/skills.git";
|
|
9
|
+
const DEFAULT_SKILLS_PATH = "skills/aem/ui-extensibility/skills";
|
|
10
|
+
/**
|
|
11
|
+
* Read all skills from the .skills folder in the given workspace root.
|
|
12
|
+
* Returns an array of skill entries with name, SKILL.md content, and any scripts.
|
|
13
|
+
*/
|
|
14
|
+
export function syncSkills(workspaceRoot) {
|
|
15
|
+
const skillsDir = path.join(workspaceRoot, ".skills");
|
|
16
|
+
if (!fs.existsSync(skillsDir)) {
|
|
17
|
+
return JSON.stringify({ found: false, skills: [] });
|
|
18
|
+
}
|
|
19
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
20
|
+
const skills = [];
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
if (!entry.isDirectory())
|
|
23
|
+
continue;
|
|
24
|
+
const skillMdPath = path.join(skillsDir, entry.name, "SKILL.md");
|
|
25
|
+
if (!fs.existsSync(skillMdPath))
|
|
26
|
+
continue;
|
|
27
|
+
const skill = {
|
|
28
|
+
name: entry.name,
|
|
29
|
+
content: fs.readFileSync(skillMdPath, "utf-8"),
|
|
30
|
+
};
|
|
31
|
+
// Check for scripts subfolder
|
|
32
|
+
const scriptsDir = path.join(skillsDir, entry.name, "scripts");
|
|
33
|
+
if (fs.existsSync(scriptsDir)) {
|
|
34
|
+
const scriptFiles = fs.readdirSync(scriptsDir).filter((f) => fs.statSync(path.join(scriptsDir, f)).isFile());
|
|
35
|
+
skill.scripts = scriptFiles.map((f) => ({
|
|
36
|
+
name: f,
|
|
37
|
+
content: fs.readFileSync(path.join(scriptsDir, f), "utf-8"),
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
skills.push(skill);
|
|
41
|
+
}
|
|
42
|
+
return JSON.stringify({ found: true, skills });
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Seed default skills into the workspace .skills folder by cloning from GitHub.
|
|
46
|
+
* Only seeds if .skills folder doesn't already exist.
|
|
47
|
+
* Uses sparse checkout to only pull the skills subfolder.
|
|
48
|
+
*/
|
|
49
|
+
export function seedSkills(workspaceRoot) {
|
|
50
|
+
const skillsDir = path.join(workspaceRoot, ".skills");
|
|
51
|
+
if (fs.existsSync(skillsDir)) {
|
|
52
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
53
|
+
const skillCount = entries.filter((e) => e.isDirectory()).length;
|
|
54
|
+
if (skillCount > 0) {
|
|
55
|
+
return JSON.stringify({
|
|
56
|
+
seeded: false,
|
|
57
|
+
reason: "Skills folder already exists with " + skillCount + " skills",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Clone into a temp dir, then copy the skills subfolder
|
|
62
|
+
const tmpDir = path.join(workspaceRoot, ".skills-tmp-" + Date.now());
|
|
63
|
+
try {
|
|
64
|
+
// Shallow clone the repo
|
|
65
|
+
execSync(`git clone --depth 1 --filter=blob:none --sparse "${DEFAULT_SKILLS_REPO}" "${tmpDir}"`, { timeout: 60_000, stdio: "pipe" });
|
|
66
|
+
// Sparse checkout just the skills path
|
|
67
|
+
execSync(`git -C "${tmpDir}" sparse-checkout set "${DEFAULT_SKILLS_PATH}"`, {
|
|
68
|
+
timeout: 30_000,
|
|
69
|
+
stdio: "pipe",
|
|
70
|
+
});
|
|
71
|
+
// Copy skills into .skills
|
|
72
|
+
const srcDir = path.join(tmpDir, DEFAULT_SKILLS_PATH);
|
|
73
|
+
if (!fs.existsSync(srcDir)) {
|
|
74
|
+
throw new Error("Skills path not found in cloned repo: " + DEFAULT_SKILLS_PATH);
|
|
75
|
+
}
|
|
76
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
77
|
+
const skillDirs = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
78
|
+
for (const dir of skillDirs) {
|
|
79
|
+
if (!dir.isDirectory())
|
|
80
|
+
continue;
|
|
81
|
+
copyDirRecursive(path.join(srcDir, dir.name), path.join(skillsDir, dir.name));
|
|
82
|
+
}
|
|
83
|
+
// Count what we seeded
|
|
84
|
+
const seededSkills = fs
|
|
85
|
+
.readdirSync(skillsDir, { withFileTypes: true })
|
|
86
|
+
.filter((e) => e.isDirectory())
|
|
87
|
+
.map((e) => e.name);
|
|
88
|
+
return JSON.stringify({ seeded: true, skills: seededSkills });
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
return JSON.stringify({
|
|
92
|
+
seeded: false,
|
|
93
|
+
reason: err.message,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
// Clean up temp dir
|
|
98
|
+
try {
|
|
99
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// ignore cleanup errors
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function copyDirRecursive(src, dest) {
|
|
107
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
108
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
109
|
+
for (const entry of entries) {
|
|
110
|
+
const srcPath = path.join(src, entry.name);
|
|
111
|
+
const destPath = path.join(dest, entry.name);
|
|
112
|
+
if (entry.isDirectory()) {
|
|
113
|
+
copyDirRecursive(srcPath, destPath);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
fs.copyFileSync(srcPath, destPath);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
package/dist/dispatcher.js
CHANGED
|
@@ -9,6 +9,7 @@ import { getWorkspaceRoot, setWorkspaceRoot } from "./identity.js";
|
|
|
9
9
|
import * as fsCap from "./capabilities/fs.js";
|
|
10
10
|
import * as gitCap from "./capabilities/git.js";
|
|
11
11
|
import * as shellCap from "./capabilities/shell.js";
|
|
12
|
+
import * as skillsCap from "./capabilities/skills.js";
|
|
12
13
|
/**
|
|
13
14
|
* Validate that a given path is within the workspace root.
|
|
14
15
|
* Prevents path traversal attacks.
|
|
@@ -30,9 +31,13 @@ function validatePath(targetPath) {
|
|
|
30
31
|
export async function dispatch(command, payload, connection) {
|
|
31
32
|
switch (command) {
|
|
32
33
|
// ─── Filesystem ─────────────────────────────────────
|
|
34
|
+
case "fs:roots": {
|
|
35
|
+
return fsCap.roots();
|
|
36
|
+
}
|
|
33
37
|
case "fs:browse": {
|
|
38
|
+
// fs:browse allows absolute paths for folder picking (not sandboxed to workspace)
|
|
34
39
|
const target = payload.path
|
|
35
|
-
?
|
|
40
|
+
? path.resolve(payload.path)
|
|
36
41
|
: getWorkspaceRoot();
|
|
37
42
|
return fsCap.browse(target, payload.showHidden);
|
|
38
43
|
}
|
|
@@ -86,6 +91,19 @@ export async function dispatch(command, payload, connection) {
|
|
|
86
91
|
: getWorkspaceRoot();
|
|
87
92
|
return shellCap.exec(payload.command, cwd);
|
|
88
93
|
}
|
|
94
|
+
// ─── Skills ────────────────────────────────────────
|
|
95
|
+
case "skills:sync": {
|
|
96
|
+
const workspace = getWorkspaceRoot();
|
|
97
|
+
if (!workspace)
|
|
98
|
+
throw new Error("No workspace root configured.");
|
|
99
|
+
return skillsCap.syncSkills(workspace);
|
|
100
|
+
}
|
|
101
|
+
case "skills:seed": {
|
|
102
|
+
const workspace = getWorkspaceRoot();
|
|
103
|
+
if (!workspace)
|
|
104
|
+
throw new Error("No workspace root configured.");
|
|
105
|
+
return skillsCap.seedSkills(workspace);
|
|
106
|
+
}
|
|
89
107
|
default:
|
|
90
108
|
throw new Error(`Unknown command: ${command}`);
|
|
91
109
|
}
|