@mcpher/gas-fakes 2.3.13 → 2.3.14
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/gf_agent/scripts/builder.js +78 -47
- package/package.json +1 -1
- package/src/cli/setup.js +87 -9
|
@@ -1,67 +1,97 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
3
4
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
|
|
8
|
+
// The script is at gf_agent/scripts/builder.js, so the root is one level up
|
|
9
|
+
const GF_AGENT_DIR = path.resolve(__dirname, '..');
|
|
10
|
+
|
|
11
|
+
// Standardize paths relative to the gf_agent directory
|
|
12
|
+
const SKILLS_DIR = path.join(GF_AGENT_DIR, 'skills');
|
|
13
|
+
const INDEX_FILE = path.join(GF_AGENT_DIR, 'index.md');
|
|
14
|
+
const TEMPLATE_FILE = path.join(__dirname, 'SKILL.template.md');
|
|
15
|
+
const KNOWLEDGE_DIR = path.join(GF_AGENT_DIR, 'knowledge');
|
|
16
|
+
const SKILL_OUTPUT = path.join(GF_AGENT_DIR, 'SKILL.md');
|
|
17
|
+
|
|
18
|
+
// Use CWD for progress dir to allow user to provide it in a sparse clone/standalone env
|
|
19
|
+
const PROGRESS_DIR = path.resolve(process.cwd(), 'progress');
|
|
7
20
|
|
|
8
21
|
async function build() {
|
|
9
|
-
|
|
22
|
+
// Cleanup potential junk from previous runs where paths were relative to CWD
|
|
23
|
+
// (e.g. if run from within gf_agent/scripts, it might have created gf_agent/scripts/gf_agent)
|
|
24
|
+
try {
|
|
25
|
+
const junkDir = path.join(__dirname, 'gf_agent');
|
|
26
|
+
await fs.rm(junkDir, { recursive: true, force: true });
|
|
27
|
+
} catch (err) {
|
|
28
|
+
// Ignore
|
|
29
|
+
}
|
|
10
30
|
|
|
11
|
-
|
|
12
|
-
const mdFiles = files.filter(f => f.endsWith('.md') || f.endsWith('.MD'));
|
|
31
|
+
await fs.mkdir(SKILLS_DIR, { recursive: true });
|
|
13
32
|
|
|
14
33
|
let masterIndex = '# gf_agent Skills Index\n\nThis index lists all Google Apps Script services and classes supported by `gf_agent` via `gas-fakes`.\n\n';
|
|
15
34
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const className = match[1];
|
|
26
|
-
// Find the table for this class
|
|
27
|
-
const classSection = content.slice(match.index);
|
|
28
|
-
const tableEnd = classSection.indexOf('## Class:') > 0 ? classSection.indexOf('## Class:', 10) : classSection.length;
|
|
29
|
-
const tableContent = classSection.slice(0, tableEnd);
|
|
35
|
+
try {
|
|
36
|
+
// Check if progress directory exists before attempting to read
|
|
37
|
+
await fs.access(PROGRESS_DIR);
|
|
38
|
+
const files = await fs.readdir(PROGRESS_DIR);
|
|
39
|
+
const mdFiles = files.filter(f => f.endsWith('.md') || f.endsWith('.MD'));
|
|
40
|
+
|
|
41
|
+
for (const file of mdFiles) {
|
|
42
|
+
const content = await fs.readFile(path.join(PROGRESS_DIR, file), 'utf-8');
|
|
43
|
+
const serviceName = file.replace(/\.md$/i, '');
|
|
30
44
|
|
|
31
|
-
// Extract
|
|
32
|
-
const
|
|
33
|
-
const
|
|
45
|
+
// Extract classes
|
|
46
|
+
const classMatches = content.matchAll(/## Class: \[(.*?)\]/g);
|
|
47
|
+
const classes = [];
|
|
34
48
|
|
|
35
|
-
|
|
36
|
-
|
|
49
|
+
for (const match of classMatches) {
|
|
50
|
+
const className = match[1];
|
|
51
|
+
// Find the table for this class
|
|
52
|
+
const classSection = content.slice(match.index);
|
|
53
|
+
const tableEnd = classSection.indexOf('## Class:') > 0 ? classSection.indexOf('## Class:', 10) : classSection.length;
|
|
54
|
+
const tableContent = classSection.slice(0, tableEnd);
|
|
55
|
+
|
|
56
|
+
// Extract completed methods
|
|
57
|
+
const methodMatches = tableContent.matchAll(/\| \[(.*?)\]\(.*?\) \| .*? \| .*? \| .*? \| (completed) \|/g);
|
|
58
|
+
const methods = Array.from(methodMatches).map(m => m[1]);
|
|
59
|
+
|
|
60
|
+
if (methods.length > 0) {
|
|
61
|
+
classes.push({ name: className, methods });
|
|
62
|
+
}
|
|
37
63
|
}
|
|
38
|
-
}
|
|
39
64
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
65
|
+
if (classes.length > 0) {
|
|
66
|
+
const skillFile = `${serviceName.toLowerCase()}.md`;
|
|
67
|
+
let skillContent = `# Service: ${serviceName}\n\n`;
|
|
68
|
+
|
|
69
|
+
classes.forEach(c => {
|
|
70
|
+
skillContent += `## Class: ${c.name}\n\n`;
|
|
71
|
+
skillContent += `Supported Methods:\n`;
|
|
72
|
+
c.methods.forEach(m => {
|
|
73
|
+
skillContent += `- \`${m}\`\n`;
|
|
74
|
+
});
|
|
75
|
+
skillContent += '\n';
|
|
49
76
|
});
|
|
50
|
-
skillContent += '\n';
|
|
51
|
-
});
|
|
52
77
|
|
|
53
|
-
|
|
54
|
-
|
|
78
|
+
await fs.writeFile(path.join(SKILLS_DIR, skillFile), skillContent);
|
|
79
|
+
masterIndex += `- [${serviceName}](skills/${skillFile})\n`;
|
|
80
|
+
}
|
|
55
81
|
}
|
|
56
|
-
}
|
|
57
82
|
|
|
58
|
-
|
|
83
|
+
await fs.writeFile(INDEX_FILE, masterIndex);
|
|
84
|
+
console.log(`Skills and Index generated from ${PROGRESS_DIR}`);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (err.code === 'ENOENT') {
|
|
87
|
+
console.log(`Skipping skills index generation: ${PROGRESS_DIR} not found.`);
|
|
88
|
+
} else {
|
|
89
|
+
console.log(`Skipping skills index generation: ${err.message}`);
|
|
90
|
+
}
|
|
91
|
+
console.log(`(This is expected in a sparse clone environment).`);
|
|
92
|
+
}
|
|
59
93
|
|
|
60
94
|
// Aggregate knowledge files into SKILL.md
|
|
61
|
-
const TEMPLATE_FILE = './gf_agent/scripts/SKILL.template.md';
|
|
62
|
-
const KNOWLEDGE_DIR = './gf_agent/knowledge';
|
|
63
|
-
const SKILL_OUTPUT = './gf_agent/SKILL.md';
|
|
64
|
-
|
|
65
95
|
let skillMarkdown = await fs.readFile(TEMPLATE_FILE, 'utf-8');
|
|
66
96
|
|
|
67
97
|
try {
|
|
@@ -76,12 +106,13 @@ async function build() {
|
|
|
76
106
|
}
|
|
77
107
|
}
|
|
78
108
|
} catch (err) {
|
|
79
|
-
console.log(
|
|
109
|
+
console.log(`No knowledge directory found at ${KNOWLEDGE_DIR} or error reading it:`, err.message);
|
|
80
110
|
}
|
|
81
111
|
|
|
82
112
|
await fs.writeFile(SKILL_OUTPUT, skillMarkdown);
|
|
83
113
|
|
|
84
|
-
console.log(
|
|
114
|
+
console.log(`Build complete! Skills, Index, and monolithic SKILL.md generated at ${SKILL_OUTPUT}`);
|
|
85
115
|
}
|
|
86
116
|
|
|
87
117
|
build().catch(console.error);
|
|
118
|
+
|
package/package.json
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"name": "@mcpher/gas-fakes",
|
|
42
42
|
"author": "bruce mcpherson",
|
|
43
|
-
"version": "2.3.
|
|
43
|
+
"version": "2.3.14",
|
|
44
44
|
"license": "MIT",
|
|
45
45
|
"main": "main.js",
|
|
46
46
|
"description": "An implementation of the Google Workspace Apps Script runtime: Run native App Script Code on Node and Cloud Run",
|
package/src/cli/setup.js
CHANGED
|
@@ -626,20 +626,98 @@ export async function initializeConfiguration(options = {}) {
|
|
|
626
626
|
try {
|
|
627
627
|
// 1. Install or link the agent skill
|
|
628
628
|
let skillCmd;
|
|
629
|
-
const
|
|
630
|
-
const
|
|
629
|
+
const gfAgentSubdir = path.resolve(process.cwd(), "gf_agent", "SKILL.md");
|
|
630
|
+
const gfAgentCurrent = path.resolve(process.cwd(), "SKILL.md");
|
|
631
631
|
|
|
632
|
-
if (
|
|
632
|
+
if (fs.existsSync(gfAgentSubdir)) {
|
|
633
633
|
console.log("Detected local gas-fakes repository. Linking local skill for development...");
|
|
634
|
-
skillCmd = "gemini skills link ./gf_agent";
|
|
634
|
+
skillCmd = "gemini skills link ./gf_agent --consent";
|
|
635
635
|
manualSkillCmd = "1. gemini skills link ./gf_agent";
|
|
636
|
+
execSync(skillCmd, { stdio: ["ignore", "pipe", "ignore"] });
|
|
637
|
+
console.log("Skill linked successfully.");
|
|
638
|
+
} else if (fs.existsSync(gfAgentCurrent) && fs.existsSync(path.resolve(process.cwd(), "index.md"))) {
|
|
639
|
+
console.log("Detected local gf_agent directory. Linking local skill for development...");
|
|
640
|
+
skillCmd = "gemini skills link . --consent";
|
|
641
|
+
manualSkillCmd = "1. gemini skills link .";
|
|
642
|
+
execSync(skillCmd, { stdio: ["ignore", "pipe", "ignore"] });
|
|
643
|
+
console.log("Skill linked successfully.");
|
|
636
644
|
} else {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
645
|
+
// Not a local clone, check if already installed to avoid overwriting
|
|
646
|
+
let isAlreadyInstalled = false;
|
|
647
|
+
try {
|
|
648
|
+
const existingSkills = execSync("gemini skills list", {
|
|
649
|
+
encoding: "utf8",
|
|
650
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
651
|
+
});
|
|
652
|
+
if (existingSkills.includes("gf_agent")) {
|
|
653
|
+
isAlreadyInstalled = true;
|
|
654
|
+
}
|
|
655
|
+
} catch (err) {
|
|
656
|
+
// Ignore errors checking skills list
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (isAlreadyInstalled) {
|
|
660
|
+
console.log("gf_agent skill is already installed. Skipping remote installation.");
|
|
661
|
+
manualSkillCmd = "1. gemini skills update gf_agent (if needed)";
|
|
662
|
+
} else {
|
|
663
|
+
const installChoice = await prompts({
|
|
664
|
+
type: "select",
|
|
665
|
+
name: "method",
|
|
666
|
+
message: "How would you like to install the gf_agent skill?",
|
|
667
|
+
choices: [
|
|
668
|
+
{
|
|
669
|
+
title: "Global (Standard)",
|
|
670
|
+
value: "global",
|
|
671
|
+
description: "Recommended for most users. Installs a read-only copy globally.",
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
title: "Local Standalone (Contributor)",
|
|
675
|
+
value: "local",
|
|
676
|
+
description: "Installs a local sparse-clone for skill development and linking.",
|
|
677
|
+
},
|
|
678
|
+
],
|
|
679
|
+
initial: 0,
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
if (installChoice.method === "local") {
|
|
683
|
+
const standaloneDir = "gf_agent_standalone";
|
|
684
|
+
const fullStandalonePath = path.resolve(process.cwd(), standaloneDir);
|
|
685
|
+
console.log(`Setting up local standalone skill environment in "./${standaloneDir}"...`);
|
|
686
|
+
|
|
687
|
+
try {
|
|
688
|
+
if (!fs.existsSync(fullStandalonePath)) {
|
|
689
|
+
fs.mkdirSync(fullStandalonePath, { recursive: true });
|
|
690
|
+
execSync("git init", { cwd: fullStandalonePath, stdio: "ignore" });
|
|
691
|
+
execSync("git remote add origin https://github.com/brucemcpherson/gas-fakes.git", {
|
|
692
|
+
cwd: fullStandalonePath,
|
|
693
|
+
stdio: "ignore",
|
|
694
|
+
});
|
|
695
|
+
execSync("git config core.sparseCheckout true", { cwd: fullStandalonePath, stdio: "ignore" });
|
|
696
|
+
|
|
697
|
+
const sparsePath = path.join(fullStandalonePath, ".git", "info", "sparse-checkout");
|
|
698
|
+
fs.writeFileSync(sparsePath, "gf_agent/*\n");
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
execSync("git pull origin main", { cwd: fullStandalonePath, stdio: "ignore" });
|
|
640
702
|
|
|
641
|
-
|
|
642
|
-
|
|
703
|
+
skillCmd = "gemini skills link ./gf_agent --consent";
|
|
704
|
+
execSync(skillCmd, { cwd: fullStandalonePath, stdio: ["ignore", "pipe", "ignore"] });
|
|
705
|
+
console.log("Skill linked successfully.");
|
|
706
|
+
|
|
707
|
+
manualSkillCmd = `1. cd ${standaloneDir} && gemini skills link ./gf_agent`;
|
|
708
|
+
} catch (gitErr) {
|
|
709
|
+
console.error(`Error during local setup: ${gitErr.message}`);
|
|
710
|
+
throw gitErr;
|
|
711
|
+
}
|
|
712
|
+
} else {
|
|
713
|
+
skillCmd = "gemini skills install https://github.com/brucemcpherson/gas-fakes.git --path gf_agent --consent";
|
|
714
|
+
manualSkillCmd = "1. gemini skills install https://github.com/brucemcpherson/gas-fakes.git --path gf_agent";
|
|
715
|
+
console.log(`Installing global skill from remote...`);
|
|
716
|
+
execSync(skillCmd, { stdio: ["ignore", "pipe", "ignore"] });
|
|
717
|
+
console.log("Skill installed successfully.");
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
643
721
|
|
|
644
722
|
// 2. Add the MCP server
|
|
645
723
|
const mcpCmd = "gemini mcp add --scope project gas-fakes-mcp gas-fakes mcp";
|