1515package skills
1616
1717import (
18- "bytes"
19- "encoding/json"
2018 "fmt"
2119 "sort"
2220 "strings"
2321 "text/template"
2422
25- "github.com/goccy/go-yaml"
26- "github.com/googleapis/genai-toolbox/internal/server"
27- "github.com/googleapis/genai-toolbox/internal/sources"
2823 "github.com/googleapis/genai-toolbox/internal/tools"
2924 "github.com/googleapis/genai-toolbox/internal/util/parameters"
3025)
@@ -39,10 +34,10 @@ description: {{.SkillDescription}}
3934All scripts can be executed using Node.js. Replace ` + "`" + `<param_name>` + "`" + ` and ` + "`" + `<param_value>` + "`" + ` with actual values.
4035
4136**Bash:**
42- ` + "`" + `node scripts/<script_name>.js '{"<param_name>": "<param_value>"}'` + "`" + `
37+ ` + "`" + `node <skill_dir>/ scripts/<script_name>.js '{"<param_name>": "<param_value>"}'` + "`" + `
4338
4439**PowerShell:**
45- ` + "`" + `node scripts/<script_name>.js '{\"<param_name>\": \"<param_value>\"}'` + "`" + `
40+ ` + "`" + `node <skill_dir>/ scripts/<script_name>.js '{\"<param_name>\": \"<param_value>\"}'` + "`" + `
4641
4742## Scripts
4843
@@ -118,29 +113,33 @@ func generateSkillMarkdown(skillName, skillDescription string, toolsMap map[stri
118113}
119114
120115const nodeScriptTemplate = `#!/usr/bin/env node
121-
116+ {{if .LicenseHeader}}
117+ {{.LicenseHeader}}
118+ {{end}}
122119const { spawn, execSync } = require('child_process');
123120const path = require('path');
124121const fs = require('fs');
125122
126123const toolName = "{{.Name}}";
127- const toolsFileName = " {{.ToolsFileName}}" ;
124+ const configArgs = [ {{.ConfigArgs}}] ;
128125
129126function getToolboxPath() {
127+ if (process.env.GEMINI_CLI === '1') {
128+ const localPath = path.resolve(__dirname, '../../../toolbox');
129+ if (fs.existsSync(localPath)) {
130+ return localPath;
131+ }
132+ }
130133 try {
131134 const checkCommand = process.platform === 'win32' ? 'where toolbox' : 'which toolbox';
132135 const globalPath = execSync(checkCommand, { stdio: 'pipe', encoding: 'utf-8' }).trim();
133136 if (globalPath) {
134137 return globalPath.split('\n')[0].trim();
135138 }
139+ throw new Error("Toolbox binary not found");
136140 } catch (e) {
137- // Ignore error;
138- }
139- const localPath = path.resolve(__dirname, '../../../toolbox');
140- if (fs.existsSync(localPath)) {
141- return localPath;
141+ throw new Error("Toolbox binary not found");
142142 }
143- throw new Error("Toolbox binary not found");
144143}
145144
146145let toolboxBinary;
@@ -151,15 +150,38 @@ try {
151150 process.exit(1);
152151}
153152
154- let configArgs = [];
155- if (toolsFileName) {
156- configArgs.push("--tools-file", path.join(__dirname, "..", "assets", toolsFileName));
153+ function getEnv() {
154+ const envPath = path.resolve(__dirname, '../../../.env');
155+ const env = { ...process.env };
156+ if (fs.existsSync(envPath)) {
157+ const envContent = fs.readFileSync(envPath, 'utf-8');
158+ envContent.split('\n').forEach(line => {
159+ const trimmed = line.trim();
160+ if (trimmed && !trimmed.startsWith('#')) {
161+ const splitIdx = trimmed.indexOf('=');
162+ if (splitIdx !== -1) {
163+ const key = trimmed.slice(0, splitIdx).trim();
164+ let value = trimmed.slice(splitIdx + 1).trim();
165+ value = value.replace(/(^['"]|['"]$)/g, '');
166+ if (env[key] === undefined) {
167+ env[key] = value;
168+ }
169+ }
170+ }
171+ });
172+ }
173+ return env;
174+ }
175+
176+ let env = process.env;
177+ if (process.env.GEMINI_CLI === '1') {
178+ env = getEnv();
157179}
158180
159181const args = process.argv.slice(2);
160- const toolboxArgs = [...configArgs, "invoke", toolName, ...args];
182+ const toolboxArgs = ["--log-level", "error", ...configArgs, "invoke", toolName, ...args];
161183
162- const child = spawn(toolboxBinary, toolboxArgs, { stdio: 'inherit' });
184+ const child = spawn(toolboxBinary, toolboxArgs, { stdio: 'inherit', env });
163185
164186child.on('close', (code) => {
165187 process.exit(code);
@@ -173,16 +195,18 @@ child.on('error', (err) => {
173195
174196type scriptData struct {
175197 Name string
176- ToolsFileName string
198+ ConfigArgs string
199+ LicenseHeader string
177200}
178201
179202// generateScriptContent creates the content for a Node.js wrapper script.
180203// This script invokes the toolbox CLI with the appropriate configuration
181204// (using a generated tools file) and arguments to execute the specific tool.
182- func generateScriptContent (name string , toolsFileName string ) (string , error ) {
205+ func generateScriptContent (name string , configArgs string , licenseHeader string ) (string , error ) {
183206 data := scriptData {
184207 Name : name ,
185- ToolsFileName : toolsFileName ,
208+ ConfigArgs : configArgs ,
209+ LicenseHeader : licenseHeader ,
186210 }
187211
188212 tmpl , err := template .New ("script" ).Parse (nodeScriptTemplate )
@@ -205,105 +229,31 @@ func formatParameters(params []parameters.ParameterManifest, envVars map[string]
205229 return "" , nil
206230 }
207231
208- properties := make (map [string ]interface {})
209- var required []string
232+ var sb strings.Builder
233+ sb .WriteString ("#### Parameters\n \n " )
234+ sb .WriteString ("| Name | Type | Description | Required | Default |\n " )
235+ sb .WriteString ("| :--- | :--- | :--- | :--- | :--- |\n " )
210236
211237 for _ , p := range params {
212- paramMap := map [ string ] interface {}{
213- "type" : p . Type ,
214- "description" : p . Description ,
238+ required := "No"
239+ if p . Required {
240+ required = "Yes"
215241 }
242+ defaultValue := ""
216243 if p .Default != nil {
217- defaultValue := p .Default
218- // Check if default value is pre-configured, if so, remove it as the the value will be
219- // read by the tool at runtime and the agent does not need to be aware of it.
220- if strVal , ok := defaultValue .(string ); ok {
244+ defaultValue = fmt .Sprintf ("`%v`" , p .Default )
245+ // Check if default value matches any env var
246+ if strVal , ok := p .Default .(string ); ok {
221247 for _ , envVal := range envVars {
222248 if envVal == strVal {
223- defaultValue = nil
249+ defaultValue = ""
224250 break
225251 }
226252 }
227253 }
228- if defaultValue != nil {
229- paramMap ["default" ] = defaultValue
230- }
231- }
232- properties [p .Name ] = paramMap
233- if p .Required {
234- required = append (required , p .Name )
235- }
236- }
237-
238- schema := map [string ]interface {}{
239- "type" : "object" ,
240- "properties" : properties ,
241- }
242- if len (required ) > 0 {
243- schema ["required" ] = required
244- }
245-
246- schemaJSON , err := json .MarshalIndent (schema , "" , " " )
247- if err != nil {
248- return "" , fmt .Errorf ("error generating parameters schema: %w" , err )
249- }
250-
251- return fmt .Sprintf ("#### Parameters\n \n ```json\n %s\n ```" , string (schemaJSON )), nil
252- }
253-
254- // generateToolConfigYAML generates the YAML configuration for a single tool and its dependency (source).
255- // It extracts the relevant tool and source configurations from the server config and formats them
256- // into a YAML document suitable for inclusion in the skill's assets.
257- func generateToolConfigYAML (cfg server.ServerConfig , toolName string ) ([]byte , error ) {
258- toolCfg , ok := cfg .ToolConfigs [toolName ]
259- if ! ok {
260- return nil , fmt .Errorf ("error finding tool config: %s" , toolName )
261- }
262-
263- var buf bytes.Buffer
264- encoder := yaml .NewEncoder (& buf )
265-
266- // Process Tool Config
267- toolWrapper := struct {
268- Kind string `yaml:"kind"`
269- Config tools.ToolConfig `yaml:",inline"`
270- }{
271- Kind : "tools" ,
272- Config : toolCfg ,
273- }
274-
275- if err := encoder .Encode (toolWrapper ); err != nil {
276- return nil , fmt .Errorf ("error encoding tool config: %w" , err )
277- }
278-
279- // Process Source Config
280- var toolMap map [string ]interface {}
281- b , err := yaml .Marshal (toolCfg )
282- if err != nil {
283- return nil , fmt .Errorf ("error marshaling tool config: %w" , err )
284- }
285- if err := yaml .Unmarshal (b , & toolMap ); err != nil {
286- return nil , fmt .Errorf ("error unmarshaling tool config map: %w" , err )
287- }
288-
289- if sourceName , ok := toolMap ["source" ].(string ); ok && sourceName != "" {
290- sourceCfg , ok := cfg .SourceConfigs [sourceName ]
291- if ! ok {
292- return nil , fmt .Errorf ("error finding source config: %s" , sourceName )
293- }
294-
295- sourceWrapper := struct {
296- Kind string `yaml:"kind"`
297- Config sources.SourceConfig `yaml:",inline"`
298- }{
299- Kind : "sources" ,
300- Config : sourceCfg ,
301- }
302-
303- if err := encoder .Encode (sourceWrapper ); err != nil {
304- return nil , fmt .Errorf ("error encoding source config: %w" , err )
305254 }
255+ fmt .Fprintf (& sb , "| %s | %s | %s | %s | %s |\n " , p .Name , p .Type , p .Description , required , defaultValue )
306256 }
307257
308- return buf . Bytes (), nil
258+ return sb . String (), nil
309259}
0 commit comments