@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.
@@ -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 PROGRESS_DIR = './progress';
5
- const SKILLS_DIR = './gf_agent/skills';
6
- const INDEX_FILE = './gf_agent/index.md';
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
- await fs.mkdir(SKILLS_DIR, { recursive: true });
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
- const files = await fs.readdir(PROGRESS_DIR);
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
- for (const file of mdFiles) {
17
- const content = await fs.readFile(path.join(PROGRESS_DIR, file), 'utf-8');
18
- const serviceName = file.replace(/\.md$/i, '');
19
-
20
- // Extract classes
21
- const classMatches = content.matchAll(/## Class: \[(.*?)\]/g);
22
- const classes = [];
23
-
24
- for (const match of classMatches) {
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 completed methods
32
- const methodMatches = tableContent.matchAll(/\| \[(.*?)\]\(.*?\) \| .*? \| .*? \| .*? \| (completed) \|/g);
33
- const methods = Array.from(methodMatches).map(m => m[1]);
45
+ // Extract classes
46
+ const classMatches = content.matchAll(/## Class: \[(.*?)\]/g);
47
+ const classes = [];
34
48
 
35
- if (methods.length > 0) {
36
- classes.push({ name: className, methods });
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
- if (classes.length > 0) {
41
- const skillFile = `${serviceName.toLowerCase()}.md`;
42
- let skillContent = `# Service: ${serviceName}\n\n`;
43
-
44
- classes.forEach(c => {
45
- skillContent += `## Class: ${c.name}\n\n`;
46
- skillContent += `Supported Methods:\n`;
47
- c.methods.forEach(m => {
48
- skillContent += `- \`${m}\`\n`;
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
- await fs.writeFile(path.join(SKILLS_DIR, skillFile), skillContent);
54
- masterIndex += `- [${serviceName}](skills/${skillFile})\n`;
78
+ await fs.writeFile(path.join(SKILLS_DIR, skillFile), skillContent);
79
+ masterIndex += `- [${serviceName}](skills/${skillFile})\n`;
80
+ }
55
81
  }
56
- }
57
82
 
58
- await fs.writeFile(INDEX_FILE, masterIndex);
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("No knowledge directory found or error reading it:", err.message);
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('Build complete! Skills, Index, and monolithic SKILL.md generated.');
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.13",
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 localSkillPath = path.resolve(process.cwd(), "gf_agent", "SKILL.md");
630
- const isLocalClone = fs.existsSync(localSkillPath);
629
+ const gfAgentSubdir = path.resolve(process.cwd(), "gf_agent", "SKILL.md");
630
+ const gfAgentCurrent = path.resolve(process.cwd(), "SKILL.md");
631
631
 
632
- if (isLocalClone) {
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
- skillCmd = "gemini skills install https://github.com/brucemcpherson/gas-fakes.git --path gf_agent";
638
- manualSkillCmd = "1. gemini skills install https://github.com/brucemcpherson/gas-fakes.git --path gf_agent";
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
- console.log(`Executing: ${skillCmd}`);
642
- execSync(skillCmd, { stdio: "inherit" });
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";