Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
File renamed without changes.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.6.1
0.6.2
37 changes: 37 additions & 0 deletions cmd/localforge/api_config/cloudinary.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"serviceName": "Cloudinary",
"serviceDescription": "Image upload and CDN management. Upload images, list/delete resources, get secure URLs for Instagram and other integrations.",
"basic_auth": "${CLOUDINARY_API_KEY}:${CLOUDINARY_API_SECRET}",
"endpoints": [
{
"name": "upload_image",
"url": "https://api.cloudinary.com/v1_1/${CLOUDINARY_CLOUD_NAME}/image/upload",
"method": "POST",
"content_type": "form",
"description": "Upload an image to Cloudinary using Basic Auth (API Key + Secret). Returns secure_url — the public HTTPS URL to use with Instagram.",
"payload": "resolve_to_base64_$file: local file path to upload (e.g. /home/user/photo.jpg). Required.\npublic_id: string - Optional identifier for the asset (used in delivery URLs).\nfolder: string - Optional folder path to organise assets (e.g. \"instagram/posts\").\ntags: string - Optional comma-separated tags."
},
{
"name": "get_resource",
"url": "https://api.cloudinary.com/v1_1/${CLOUDINARY_CLOUD_NAME}/resources/image/upload/{public_id}",
"method": "GET",
"description": "Get details of an uploaded image by its public ID, including its secure_url.",
"url_parameters": "public_id: string - The public ID of the image."
},
{
"name": "list_resources",
"url": "https://api.cloudinary.com/v1_1/${CLOUDINARY_CLOUD_NAME}/resources/image/upload",
"method": "GET",
"description": "List uploaded images in the Cloudinary account.",
"query_params": "max_results: integer - Maximum number of resources to return (default: 10, max: 500).\nnext_cursor: string - Pagination cursor from a previous response."
},
{
"name": "delete_resource",
"url": "https://api.cloudinary.com/v1_1/${CLOUDINARY_CLOUD_NAME}/image/destroy",
"method": "POST",
"content_type": "form",
"description": "Delete an uploaded image by its public ID.",
"payload": "public_id: string - The public ID of the image to delete. Required.\ninvalidate: boolean - Whether to invalidate the CDN cache (default: false)."
}
]
}
2 changes: 1 addition & 1 deletion src/builder/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (t *Tool) getTool(
if !filepath.IsAbs(folderPath) {
folderPath = filepath.Join(workingDir, folderPath)
}
repositoryDir := filepath.Join(workingDir, "repository", "api_configs")
repositoryDir := filepath.Join(workingDir, "api_config")
_ = os.MkdirAll(repositoryDir, 0755)

services := make(map[string]api.ServiceConfig)
Expand Down
9 changes: 4 additions & 5 deletions src/plugins/procedures/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,18 @@ type Procedure struct {
type ProceduresPlugin struct {
// dir is the directory for user-created procedures (workingDir/procedures).
dir string
// repositoryDir is the directory for remotely installed procedures (workingDir/repository/procedures).
// repositoryDir is the directory for remotely installed procedures (workingDir/procedures).
repositoryDir string
procedures map[string]*Procedure
activeProcedure *Procedure
currentPhase int
}

// NewProceduresPlugin creates a new ProceduresPlugin.
// User procedures live in workingDir/procedures.
// Repository-installed procedures live in workingDir/repository/procedures.
// User and repository-installed procedures both live in workingDir/procedures.
func NewProceduresPlugin(workingDir string) *ProceduresPlugin {
dir := filepath.Join(workingDir, "procedures")
repositoryDir := filepath.Join(workingDir, "repository", "procedures")
repositoryDir := filepath.Join(workingDir, "procedures")
_ = os.MkdirAll(dir, 0755)
_ = os.MkdirAll(repositoryDir, 0755)
p := &ProceduresPlugin{
Expand Down Expand Up @@ -104,7 +103,7 @@ func (p *ProceduresPlugin) SystemPrompt() string {
sb.WriteString("[PROCEDURES]\n")
sb.WriteString("- Tool: procedure\n")
sb.WriteString("- Structured multi-step tasks. Actions: start_procedure, next_step, goto_step (jump to step by number).\n")
sb.WriteString("- User-created procedures live in procedures/. Repository-installed procedures live in repository/procedures/. When creating procedures, always use paths under procedures/ (e.g. procedures/my-procedure/).\n\n")
sb.WriteString("- User-created and repository-installed procedures live in procedures/. When creating procedures, always use paths under procedures/ (e.g. procedures/my-procedure/).\n\n")
sb.WriteString("[PROCEDURE EXECUTION RULE — MANDATORY]\n")
sb.WriteString("At ANY step or tool call, if the outcome is not exactly what the step describes, or a Tool returne any error or unexpected result as expected:\n")
sb.WriteString("1. STOP immediately. Do not continue, retry, guess, or attempt to work around the problem.\n")
Expand Down
10 changes: 5 additions & 5 deletions src/plugins/procedures/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,30 @@ const PROCEDURE_TOOL = "procedure"
func newProcedureTool(plugin *ProceduresPlugin) llms.Tool {
return &core.Tool{
Name: PROCEDURE_TOOL,
Description: `Execute structured multi-step procedures. start_procedure: begin named procedure (requires name). next_step: advance to next step. goto_step: jump to a specific step (requires stepNumber). installable_procedures: list procedures available on GitHub. install_procedure: download a procedure from GitHub into repository/procedures/ (requires procedureSlug).`,
Description: `Execute structured multi-step procedures. start_procedure: begin named procedure (requires name). next_step: advance to next step. goto_step: jump to a specific step (requires stepNumber). installable_procedures: list procedures available on GitHub. install_procedure: download a procedure from GitHub into procedures/ (requires procedureSlug).`,
AdvanceDesc: `[ACTIONS]
- start_procedure: Begin from step 0. Required: name
- next_step: Advance to next step of active procedure
- goto_step: Jump to a specific step by number. Required: stepNumber (0-based)
- installable_procedures: List all procedures available for installation from the remote GitHub repository
- install_procedure: Download a procedure from GitHub into the local repository/procedures/ folder. Required: procedureSlug (directory name in the remote repo, e.g. 'gmail-login')
- install_procedure: Download a procedure from GitHub into the local procedures/ folder. Required: procedureSlug (directory name in the remote repo, e.g. 'gmail-login')

[STEP CONTENT]
- Each action returns step folder files. Read for instructions.
- Procedure names in manifest.yaml. System prompt lists available procedures.

[CREATING PROCEDURES]
- New procedures MUST be created inside procedures/ (e.g. procedures/my-procedure/manifest.yaml, procedures/my-procedure/0/instructions.md). Never create at working dir root.
- Procedures installed from the repository are stored in repository/procedures/ and must not be edited directly.`,
- Procedures installed from the repository are stored in procedures/ alongside user-created procedures.`,
TroubleshootingInfo: `Troubleshooting:
- Ensure 'action' is 'start_procedure', 'next_step', 'goto_step', 'installable_procedures', or 'install_procedure'.
- 'name' is required for start_procedure and must match a known procedure name exactly.
- 'stepNumber' is required for goto_step (0-based index).
- 'procedureSlug' is required for install_procedure and must match a directory name in the remote repo's repository/procedures/ folder.
- 'procedureSlug' is required for install_procedure and must match a directory name in the remote repo's procedures folder.
- Call start_procedure before next_step or goto_step; there is no active procedure otherwise.
- next_step returns an error when the last step has already been reached.
- install_procedure requires internet access to reach the GitHub API.
- Installed procedures land in repository/procedures/, not procedures/.`,
- Installed procedures land in procedures/.`,
Parameters: []core.Parameter{
{
Name: "action",
Expand Down
2 changes: 1 addition & 1 deletion src/tools/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ services := map[string]api.ServiceConfig{
},
}

repositoryDir := "" // path to repository/api_configs for install_api_config; empty disables
repositoryDir := "" // path to api_config for install_api_config (workingDir/api_config when using builder); empty disables
workingDir := "/path/to/agent/working/dir" // base path for resolving relative file paths in resolvers
tool := api.NewApiTool("api", services, repositoryDir, workingDir)
```
Expand Down
4 changes: 2 additions & 2 deletions src/tools/api/tool.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (w *apiToolWrapper) ServiceNames() []string {
// NewApiTool creates a unified API tool from a map of service configurations.
// name is the tool name visible to the agent (typically "api").
// services maps service names to their endpoint + header configurations.
// repositoryDir is the local directory for remotely installed configs (empty = no repository).
// repositoryDir is the local directory for remotely installed configs (e.g. api_config; empty = no repository).
// workingDir is the base path for resolving relative file paths in resolvers (e.g. resolve_to_base64).
// The returned value also implements ServiceProvider for service discovery.
func NewApiTool(name string, services map[string]ServiceConfig, repositoryDir, workingDir string) ServiceProvider {
Expand Down Expand Up @@ -142,7 +142,7 @@ func (a *Api) generateParameters() []core.Parameter {
{
Name: "configName",
Type: "string",
Description: "Service config name to install (filename without .json from the remote repository/api_configs/ folder) — required for install_api_config",
Description: "Service config name to install (filename without .json from the remote repository; installed to api_config/) — required for install_api_config",
Required: false,
},
{
Expand Down
Loading