{
  "schema" : "intouch-base-tools/1",
  "buildVersion" : "8.0.3",
  "note" : "Built-in tool contracts for this InTouch build. `publishes` lists the ONLY keys a later task may reference as {{taskName.key}} — never invent one that is not listed. `fields` is the exact field template; `required` is what the executor refuses to run without.",
  "tools" : [ {
    "name" : "anthropic",
    "displayName" : "Anthropic Claude",
    "category" : "AI",
    "summary" : "Send a prompt to Claude (Anthropic) and use the response.",
    "credentialRequired" : true,
    "credentialType" : "anthropic",
    "fields" : {
      "prompt" : "",
      "promptFile" : "",
      "systemPrompt" : "",
      "systemPromptFile" : "",
      "model" : "",
      "maxTokens" : 4096,
      "temperature" : 1.0,
      "topP" : -1.0,
      "topK" : -1,
      "extendedThinking" : false,
      "thinkingBudget" : 10000,
      "stopSequences" : "",
      "outputFile" : "",
      "thinkingOutputFile" : "",
      "timeoutSeconds" : 120
    },
    "publishes" : [ "inputTokens", "model", "outputTokens", "response", "stopReason", "thinking", "totalTokens" ]
  }, {
    "name" : "anthropic_agent",
    "displayName" : "Anthropic Claude Agent",
    "category" : "AI",
    "summary" : "Autonomous Claude agent that can drive InTouch over multiple steps.",
    "credentialRequired" : true,
    "credentialType" : "anthropic",
    "fields" : {
      "prompt" : "",
      "promptFile" : "",
      "systemPrompt" : "",
      "systemPromptFile" : "",
      "intouchBaseUrl" : "http://localhost:2200",
      "intouchSessionToken" : "",
      "maxIterations" : 20,
      "model" : "",
      "maxTokens" : 4096,
      "temperature" : 1.0,
      "extendedThinking" : false,
      "thinkingBudget" : 10000,
      "jobPollIntervalSeconds" : 10,
      "jobMaxWaitSeconds" : 600,
      "outputFile" : "",
      "toolHistoryFile" : "",
      "timeoutSeconds" : 120
    },
    "publishes" : [ "iterations", "model", "response", "toolCallCount", "toolHistory", "totalInputTokens", "totalOutputTokens", "totalTokens" ]
  }, {
    "name" : "anthropic_batch",
    "displayName" : "Anthropic Claude Batch",
    "category" : "AI",
    "summary" : "Process many prompts in bulk via Claude's Batch API (cheaper, async).",
    "credentialRequired" : true,
    "credentialType" : "anthropic",
    "fields" : {
      "inputFile" : "",
      "systemPrompt" : "",
      "systemPromptFile" : "",
      "model" : "",
      "maxTokens" : 4096,
      "temperature" : 1.0,
      "outputDir" : "",
      "outputFile" : "",
      "pollIntervalSeconds" : 30,
      "maxWaitSeconds" : 3600
    },
    "publishes" : [ "batchId", "errored", "expired", "firstResponse", "processingStatus", "resultsRetrieved", "resultsSucceeded", "succeeded", "totalRequests" ]
  }, {
    "name" : "anthropic_document",
    "displayName" : "Anthropic Claude Document",
    "category" : "AI",
    "summary" : "Ask Claude about a PDF or document, with source citations.",
    "credentialRequired" : true,
    "credentialType" : "anthropic",
    "fields" : {
      "prompt" : "",
      "promptFile" : "",
      "systemPrompt" : "",
      "systemPromptFile" : "",
      "documentFiles" : "",
      "imageUrls" : "",
      "enableCitations" : true,
      "model" : "",
      "maxTokens" : 4096,
      "temperature" : 1.0,
      "extendedThinking" : false,
      "thinkingBudget" : 10000,
      "outputFile" : "",
      "citationsOutputFile" : "",
      "timeoutSeconds" : 300
    },
    "publishes" : [ "citationCount", "citations", "inputTokens", "model", "outputTokens", "response", "stopReason", "totalTokens" ]
  }, {
    "name" : "anthropic_tools",
    "displayName" : "Anthropic Claude Tools",
    "category" : "AI",
    "summary" : "Claude with custom tool / function calling.",
    "credentialRequired" : true,
    "credentialType" : "anthropic",
    "fields" : {
      "prompt" : "",
      "promptFile" : "",
      "systemPrompt" : "",
      "systemPromptFile" : "",
      "toolsFile" : "",
      "toolsJson" : "",
      "maxIterations" : 10,
      "model" : "",
      "maxTokens" : 4096,
      "temperature" : 1.0,
      "outputFile" : "",
      "toolHistoryFile" : "",
      "timeoutSeconds" : 120
    },
    "publishes" : [ "iterations", "model", "response", "toolCallCount", "toolHistory", "totalInputTokens", "totalOutputTokens", "totalTokens" ]
  }, {
    "name" : "archive",
    "displayName" : "Archive",
    "category" : "Files & System",
    "summary" : "Create and open archives — zip, unzip, tar, untar, and list contents without extracting.",
    "usage" : "archive operation (path destination include exclude format) — Create or open an archive DIRECTLY (no shell, no `unzip`/`tar` binary — works the same on Windows, Linux and macOS). This is the ONLY way to read a .zip/.tar; the `os` tool cannot open archives.\n    operation: required. One of:\n      list(path[, include, format]) — read the table of contents WITHOUT extracting. ALWAYS DO THIS FIRST when you do not already know what is inside an archive; do not extract just to look.\n      unzip(path, destination[, include, exclude])  — extract a .zip into a directory\n      untar(path, destination[, include, exclude])  — extract a .tar / .tar.gz / .tgz (gzip detected from the name)\n      zip(path, destination[, include, exclude])    — write a .zip from a file or a directory tree\n      tar(path, destination[, include, exclude])    — write a .tar, or .tar.gz / .tgz for gzip\n    path: the archive (list/unzip/untar) or the source file/directory (zip/tar). Required.\n    destination: the output directory (unzip/untar) or the archive to write (zip/tar). Required except for list; created if missing.\n    include / exclude: optional globs narrowing which entries are handled — matched against BOTH the full entry path and the bare filename, so \"*.csv\" catches \"Jobs/Saved Jobs.csv\". Use include to pull ONE file out of a large archive instead of exploding the whole thing across disk. An include that matches nothing FAILS rather than reporting 0 files.\n    format: only for list, and only when the extension is missing or unusual — zip | tar | tar.gz.\n    Outputs referenced as {{taskName.field}}: {{x.entries}} (one per line, name<TAB>size, directories end in /), {{x.files}} (names only), {{x.file}} (full path of the extracted file when exactly one came out — the usual hand-off to the next task), {{x.fileCount}}, {{x.destination}}, {{x.path}}, {{x.size}}, {{x.summary}}.\n    Extraction refuses entries that resolve outside the destination (zip-slip / path traversal) and fails the task rather than writing them.\n    PATHS ARE PLATFORM-SPECIFIC — read the host's own paths first ({\"operation\":\"env\",\"name\":\"TEMP\"} on the os tool) rather than hardcoding /tmp or C:\\\\.\n    Example (pull one CSV out of a big export and read it, without extracting the rest):\n      task 'toc':  {\"operation\":\"list\",\"path\":\"/home/u/Downloads/export.zip\"}\n      task 'get':  {\"operation\":\"unzip\",\"path\":\"/home/u/Downloads/export.zip\",\"destination\":\"/home/u/Downloads/export\",\"include\":\"Connections.csv\"}\n      task 'read': {\"operation\":\"readfile\",\"path\":\"{{get.file}}\"}          ← os tool",
    "credentialRequired" : false,
    "fields" : {
      "operation" : "list",
      "path" : "/tmp/example.zip",
      "destination" : "",
      "include" : ""
    },
    "publishes" : [ "count", "destination", "entries", "file", "fileCount", "files", "format", "operation", "path", "size", "summary" ]
  }, {
    "name" : "aws",
    "displayName" : "AWS",
    "category" : "Cloud",
    "summary" : "Run AWS operations via the AWS CLI.",
    "usage" : "aws credentialName service operation (args region outputFormat) — Run an AWS CLI command.\n    credentialName: required, name of an existing aws credential.\n    service: s3|ec2|lambda|iam|cloudwatch|sns|sqs|dynamodb|rds|ecs|etc.\n    operation: the AWS CLI subcommand (e.g. ls, cp, describe-instances, invoke)\n    args: additional CLI arguments (optional)\n    region: override region for this command (optional, defaults to credential region)\n    outputFormat: json|text|table (optional, default json)\n  Example: {\"name\":\"aws-step\",\"tool\":\"aws\",\"credentialName\":\"my-aws\",\"operation\":\"ls\",\"properties\":{\"service\":\"s3\",\"args\":\"s3://my-bucket/\"}}",
    "credentialRequired" : true,
    "credentialType" : "aws",
    "fields" : {
      "service" : "",
      "operation" : "",
      "args" : "",
      "region" : "",
      "outputFormat" : "json",
      "query" : "",
      "outputFile" : "",
      "scriptFile" : "",
      "workingDir" : "",
      "successCodes" : "0",
      "timeoutSeconds" : 300
    },
    "publishes" : [ "commandCount", "exitCode", "output" ]
  }, {
    "name" : "deepseek",
    "displayName" : "DeepSeek",
    "category" : "AI",
    "summary" : "Send a prompt to DeepSeek and use the response.",
    "credentialRequired" : true,
    "credentialType" : "deepseek",
    "fields" : {
      "prompt" : "",
      "promptFile" : "",
      "systemPrompt" : "",
      "systemPromptFile" : "",
      "model" : "",
      "maxTokens" : 4096,
      "temperature" : 1.0,
      "topP" : -1.0,
      "frequencyPenalty" : 0.0,
      "presencePenalty" : 0.0,
      "responseFormat" : "text",
      "stopSequences" : "",
      "outputFile" : "",
      "timeoutSeconds" : 120,
      "outputSchema" : "",
      "validationRetries" : 0,
      "redactPatterns" : ""
    },
    "publishes" : [ "completionTokens", "finishReason", "model", "promptTokens", "response", "totalTokens", "validationError" ]
  }, {
    "name" : "discord",
    "displayName" : "Discord",
    "category" : "Messaging",
    "summary" : "Post messages to Discord (outbound).",
    "credentialRequired" : true,
    "credentialType" : "discord",
    "fields" : { },
    "publishes" : [ ]
  }, {
    "name" : "document_chunker",
    "displayName" : "Document Chunker",
    "category" : "AI",
    "summary" : "Split a document into chunks for AI processing.",
    "credentialRequired" : false,
    "fields" : {
      "inputFile" : "",
      "input" : "",
      "splitBy" : "tokens",
      "chunkSize" : 512,
      "chunkOverlap" : 50,
      "separator" : "",
      "metadata" : "",
      "outputFile" : "",
      "maxChunks" : 0
    },
    "publishes" : [ "chunkCount", "chunks", "totalChars", "totalTokenEstimate" ]
  }, {
    "name" : "email-read",
    "displayName" : "Email (IMAP Read)",
    "category" : "Communication",
    "summary" : "Read messages from an IMAP mailbox (read-only; never marks mail read).",
    "usage" : "email-read credentialName (folder unreadOnly sinceDays maxMessages) — Read messages from an IMAP mailbox, READ-ONLY (never marks mail read), and publish them as JSON for the email-authenticity tools.\n    credentialName: an existing email-read credential (IMAP host/port/email/app-password).\n    folder: mailbox folder (default INBOX). unreadOnly: true to return only unread. sinceDays: only mail from the last N days (0 = all). maxMessages: cap, newest first (default 25).\n  Publishes: output (JSON {messages:[…],scanned:N}), count, ids.\n  Example: {\"name\":\"email-read-step\",\"tool\":\"email-read\",\"credentialName\":\"my-gmail\",\"properties\":{\"unreadOnly\":true,\"sinceDays\":1,\"maxMessages\":25}}",
    "credentialRequired" : true,
    "credentialType" : "email-read",
    "fields" : {
      "name" : "",
      "folder" : "",
      "unreadOnly" : true,
      "sinceDays" : 0,
      "maxMessages" : 25,
      "includeBody" : true,
      "sinceState" : ""
    },
    "publishes" : [ "count", "ids", "memory", "messages", "output" ]
  }, {
    "name" : "embeddings",
    "displayName" : "Embeddings",
    "category" : "AI",
    "summary" : "Turn text into a vector embedding for search and RAG.",
    "credentialRequired" : true,
    "credentialType" : "openai",
    "fields" : {
      "input" : "",
      "inputFile" : "",
      "model" : "",
      "dimensions" : 0,
      "encodingFormat" : "float",
      "outputFile" : "",
      "timeoutSeconds" : 120
    },
    "requiredOneOf" : [ [ "input", "inputFile" ] ],
    "publishes" : [ "dimensions", "embedding", "embeddingCount", "model", "promptTokens", "totalTokens" ]
  }, {
    "name" : "fine_tuning",
    "displayName" : "Fine-tuning",
    "category" : "AI",
    "summary" : "Create and manage model fine-tuning jobs.",
    "credentialRequired" : true,
    "credentialType" : "openai",
    "fields" : {
      "operation" : "status",
      "model" : "",
      "trainingFile" : "",
      "validationFile" : "",
      "fineTuneJobId" : "",
      "suffix" : "",
      "epochs" : 0,
      "learningRateMultiplier" : 0.0,
      "batchSize" : 0,
      "outputFile" : "",
      "timeoutSeconds" : 300
    },
    "publishes" : [ "fineTunedModel", "workflowId", "operation", "response", "status" ]
  }, {
    "name" : "gemini",
    "displayName" : "Google Gemini",
    "category" : "AI",
    "summary" : "Send a prompt to Google Gemini and use the response.",
    "credentialRequired" : true,
    "credentialType" : "gemini",
    "fields" : {
      "prompt" : "",
      "promptFile" : "",
      "systemPrompt" : "",
      "systemPromptFile" : "",
      "model" : "",
      "maxOutputTokens" : 4096,
      "temperature" : 1.0,
      "topP" : -1.0,
      "topK" : -1,
      "stopSequences" : "",
      "safetyThreshold" : "BLOCK_MEDIUM_AND_ABOVE",
      "outputFile" : "",
      "thinking" : false,
      "thinkingBudget" : 8192,
      "thinkingOutputFile" : "",
      "timeoutSeconds" : 120
    },
    "publishes" : [ "candidateTokens", "finishReason", "model", "promptTokens", "response", "thinking", "thinkingTokens", "totalTokens" ]
  }, {
    "name" : "google_workspace",
    "displayName" : "Google Workspace",
    "category" : "Cloud",
    "summary" : "Read and manage a Google account — Gmail, Calendar, Drive, Sheets, Docs. Reads mailboxes; NOT how InTouch sends email (use `message`).",
    "usage" : "google_workspace credentialName service operation (args account outputFormat) — The native tool for Gmail, Calendar, Drive, Sheets, Docs and the rest of Google Workspace. ALWAYS use this for anything Google Workspace — never reach for runtimeenv, spawn, or a shell command to call Google.\n    credentialName: required, name of an existing google_workspace credential.\n    service: gmail|calendar|drive|sheets|contacts|tasks|chat|docs|slides|forms|people|keep|admin|groups|appscript|classroom|sites|analytics|searchconsole|photos|maps|meet|youtube\n    operation: the action for the service (e.g. gmail→search/get/send; calendar→events/create; drive→ls/search/upload). Gmail listing is \"search\" (NOT \"list\"/\"list-messages\").\n    args: additional arguments for the operation (the search query is POSITIONAL, not a flag — e.g. \"is:unread --max 5\"; --query is the dead pre-0.19 form and errors)\n    account: google account email override (optional)\n    outputFormat: json|plain|table (optional, default json)\n  Example: {\"name\":\"google-workspace-step\",\"tool\":\"google_workspace\",\"credentialName\":\"my-gws\",\"operation\":\"search\",\"properties\":{\"service\":\"gmail\",\"args\":\"is:unread --max 5\"}}",
    "credentialRequired" : true,
    "credentialType" : "google_workspace",
    "fields" : {
      "service" : "",
      "operation" : "",
      "args" : "",
      "account" : "",
      "outputFormat" : "json",
      "outputFile" : "",
      "scriptFile" : "",
      "workingDir" : "",
      "successCodes" : "0",
      "timeoutSeconds" : 300,
      "query" : "",
      "max" : 0,
      "messageId" : "",
      "to" : "",
      "subject" : "",
      "bodyHtml" : "",
      "cc" : "",
      "bcc" : "",
      "calendarRange" : "",
      "parent" : "",
      "localPath" : "",
      "fileName" : "",
      "spreadsheetId" : "",
      "range" : "",
      "values" : ""
    },
    "publishes" : [ "commandCount", "exitCode", "output" ]
  }, {
    "name" : "groq",
    "displayName" : "Groq",
    "category" : "AI",
    "summary" : "Send a prompt to a Groq-hosted model and use the response.",
    "credentialRequired" : true,
    "credentialType" : "groq",
    "fields" : {
      "prompt" : "",
      "promptFile" : "",
      "systemPrompt" : "",
      "systemPromptFile" : "",
      "model" : "",
      "maxTokens" : 4096,
      "temperature" : 1.0,
      "topP" : -1.0,
      "frequencyPenalty" : 0.0,
      "presencePenalty" : 0.0,
      "responseFormat" : "text",
      "stopSequences" : "",
      "outputFile" : "",
      "timeoutSeconds" : 120,
      "outputSchema" : "",
      "validationRetries" : 0,
      "redactPatterns" : ""
    },
    "publishes" : [ "completionTokens", "finishReason", "model", "promptTokens", "response", "totalTokens", "validationError" ]
  }, {
    "name" : "http",
    "displayName" : "HTTP Request",
    "category" : "Communication",
    "summary" : "Call any web API or webhook over HTTP (GET, POST, …).",
    "usage" : "http [credentialName] method url (body headers contentType) — Make an HTTP request.\n    credentialName: OPTIONAL. Name of an existing http credential when you want to reuse\n                    a base URL + auth + default headers. Omit it for one-off public calls.\n    method: GET|POST|PUT|DELETE|PATCH\n    url: When a credential is set, this is a PATH appended to the credential's base URL\n         (e.g. \"/users?limit=10\"). When no credential is set, this MUST be an absolute URL\n         (e.g. \"https://api.example.com/users?limit=10\").\n    body: request body (optional, for POST/PUT/PATCH)\n    headers: JSON object of extra headers (optional)\n    contentType: default application/json\n  Example with credential:    {\"name\":\"http-step\",\"tool\":\"http\",\"credentialName\":\"my-api\",\"properties\":{\"method\":\"GET\",\"url\":\"/users?limit=10\"}}\n  Example without credential: {\"name\":\"http-step\",\"tool\":\"http\",\"properties\":{\"method\":\"GET\",\"url\":\"https://api.github.com/users/octocat\"}}",
    "credentialRequired" : false,
    "fields" : {
      "url" : "",
      "method" : "GET",
      "headers" : { },
      "body" : "",
      "contentType" : "application/json",
      "timeoutSeconds" : 30,
      "successCodes" : "200,201,202,204",
      "outputFile" : "",
      "followRedirects" : true
    },
    "publishes" : [ "responseBody", "responseCode" ]
  }, {
    "name" : "huggingface",
    "displayName" : "Hugging Face",
    "category" : "AI",
    "summary" : "Run a Hugging Face model (summarize, classify, generate, …).",
    "credentialRequired" : true,
    "credentialType" : "huggingface",
    "fields" : {
      "task" : "text-generation",
      "model" : "",
      "input" : "",
      "inputFile" : "",
      "parameters" : "",
      "candidateLabels" : "",
      "context" : "",
      "waitForModel" : true,
      "outputFile" : "",
      "timeoutSeconds" : 300
    },
    "publishes" : [ "embeddings", "entityCount", "label", "model", "rawResponse", "response", "score", "similarities", "task", "topToken" ]
  }, {
    "name" : "if",
    "displayName" : "If",
    "category" : "Flow control",
    "summary" : "Run steps only when a condition is met.",
    "usage" : "if arms — Conditional gate inside a job. Evaluates arms top-to-bottom and fires the first matching arm's `do:` actions.\n  Each arm has exactly one of:\n    condition: \"<expression>\"    boolean expression (==, !=, <, <=, >, >=, contains, &&, ||) over upstream task props\n    ai: \"<natural-language>\"     AI-evaluated test against the substituted bindings\n    else: true                   catch-all (always matches if no earlier arm did)\n  Actions in do:\n    nothing                                          explicit no-op\n    notify { alertNames, userNames, subject, body }\n    skip_remaining                                   stop the job (completes as success)\n    goto { task: \"<later-task-name>\" }              forward-only jump (no cycles by construction)\n    run_tool { name, args, credentialName?, runtimeEnvName? }   synchronous inline tool invocation\n    run_skill { name, input }                       synchronous skill invocation\n    run_jobfile { name, args }                      async spawn of a IML job file\n  To spawn a NATIVE job conditionally, place a `workflow` task downstream of the if-task and gate it with skip_remaining/goto, or use run_tool with name=\"workflow\".\n  Variables in conditions/bodies (${task.X.Y}, ${job.X}, ${args.X}, ${env(NAME)}, ${date(...)}) are resolved BEFORE this task runs — the if-task itself fetches no data.\n  Example: {\"action\":\"if\",\"arms\":[{\"condition\":\"${task.fetch.responseCode} >= 400\",\"do\":[{\"type\":\"notify\",\"alertNames\":[\"service-degraded\"]},{\"type\":\"skip_remaining\"}]}]}",
    "credentialRequired" : false,
    "fields" : {
      "arms" : [ ]
    },
    "publishes" : [ ]
  }, {
    "name" : "image_generation",
    "displayName" : "Image Generation",
    "category" : "AI",
    "summary" : "Generate an image from a text prompt.",
    "credentialRequired" : true,
    "credentialType" : "openai",
    "fields" : {
      "prompt" : "",
      "model" : "",
      "size" : "1024x1024",
      "quality" : "standard",
      "style" : "vivid",
      "n" : 1,
      "responseFormat" : "url",
      "outputFile" : "",
      "timeoutSeconds" : 120
    },
    "publishes" : [ "imageCount", "imageUrl", "model", "outputFile", "revisedPrompt" ]
  }, {
    "name" : "iml",
    "displayName" : "IML Tool Credential",
    "category" : "IML Tools",
    "summary" : "",
    "credentialRequired" : true,
    "credentialType" : "iml",
    "fields" : { },
    "publishes" : [ ]
  }, {
    "name" : "intouch-skill",
    "displayName" : "Skill",
    "category" : "Skills",
    "summary" : "Run an installed InTouch skill.",
    "usage" : "intouch-skill skillName input — Run a locally-installed InTouch skill (SKILL.md) by name with optional input.\n  Unlike the openclaw task, this does NOT convert the skill — the skill body is injected as the assistant system prompt and executed fresh each run. Tokens consumed per execution; output is non-deterministic.\n  Example: {\"name\":\"intouch-skill-step\",\"tool\":\"intouch-skill\",\"properties\":{\"skillName\":\"weekly-report\",\"input\":\"for last 7 days\"}}",
    "credentialRequired" : false,
    "fields" : {
      "skillName" : "",
      "input" : ""
    },
    "publishes" : [ "answer", "inputTokens", "model", "outputTokens", "skillName", "toolCallCount" ]
  }, {
    "name" : "intouch_user",
    "displayName" : "InTouch User",
    "category" : "InTouch",
    "summary" : "",
    "credentialRequired" : true,
    "credentialType" : "intouch_user",
    "fields" : { },
    "publishes" : [ ]
  }, {
    "name" : "line",
    "displayName" : "LINE",
    "category" : "Messaging",
    "summary" : "Send messages via the LINE Messaging API (outbound). Department+.",
    "credentialRequired" : true,
    "credentialType" : "line",
    "fields" : { },
    "publishes" : [ ]
  }, {
    "name" : "message",
    "displayName" : "Message",
    "category" : "Communication",
    "summary" : "Send email or a message to people — THE way InTouch sends anything outbound. Address a contact; the contact's channel decides the transport (email, Slack, Discord, …). \"email me\" means this tool with an email-channel contact.",
    "usage" : "message subject body (contactNames) — Send a notification message. Does NOT use a credential — uses the server's configured transports (email, SMS gateway, Slack bot token, etc.).\n    subject: message subject line\n    body: message body text\n    contactNames: comma-separated contact names (optional, defaults to workflow contacts). Contacts must have a channel configured.\n  Prerequisites: a contact with a channel on the transport you want. EMAIL needs nothing else on Personal/Solo/Team — the built-in mail relay carries it. On Department/Enterprise, email goes over the customer's own SMTP server, so an `smtp` credential must exist first.\n  Example: {\"name\":\"message-step\",\"tool\":\"message\",\"properties\":{\"subject\":\"Report Ready\",\"body\":\"The daily report has been generated.\",\"userNames\":[\"admin\"]}}",
    "credentialRequired" : false,
    "fields" : {
      "alertName" : "",
      "subject" : "",
      "body" : "",
      "bodyType" : "text",
      "attachments" : [ ],
      "contactNames" : [ ],
      "userNames" : [ ]
    },
    "publishes" : [ ]
  }, {
    "name" : "mistral",
    "displayName" : "Mistral AI",
    "category" : "AI",
    "summary" : "Send a prompt to Mistral and use the response.",
    "credentialRequired" : true,
    "credentialType" : "mistral",
    "fields" : {
      "prompt" : "",
      "promptFile" : "",
      "systemPrompt" : "",
      "systemPromptFile" : "",
      "model" : "",
      "maxTokens" : 4096,
      "temperature" : 1.0,
      "topP" : -1.0,
      "frequencyPenalty" : 0.0,
      "presencePenalty" : 0.0,
      "responseFormat" : "text",
      "stopSequences" : "",
      "outputFile" : "",
      "timeoutSeconds" : 120,
      "outputSchema" : "",
      "validationRetries" : 0,
      "redactPatterns" : ""
    },
    "publishes" : [ "completionTokens", "finishReason", "model", "promptTokens", "response", "totalTokens", "validationError" ]
  }, {
    "name" : "oauth",
    "displayName" : "OAuth 2.0",
    "category" : "Authentication",
    "summary" : "",
    "credentialRequired" : true,
    "credentialType" : "oauth",
    "fields" : { },
    "publishes" : [ ]
  }, {
    "name" : "ollama",
    "displayName" : "Ollama",
    "category" : "AI",
    "summary" : "Send a prompt to a local Ollama model (private, no API cost).",
    "credentialRequired" : true,
    "credentialType" : "ollama",
    "fields" : {
      "prompt" : "",
      "promptFile" : "",
      "systemPrompt" : "",
      "systemPromptFile" : "",
      "model" : "",
      "temperature" : 0.7,
      "maxTokens" : 4096,
      "topP" : 0.9,
      "outputFile" : "",
      "timeoutSeconds" : 300
    },
    "publishes" : [ "durationMs", "model", "outputTokens", "promptTokens", "response", "totalTokens" ]
  }, {
    "name" : "openai",
    "displayName" : "OpenAI",
    "category" : "AI",
    "summary" : "Send a prompt to OpenAI (GPT) and use the response.",
    "credentialRequired" : true,
    "credentialType" : "openai",
    "fields" : {
      "prompt" : "",
      "promptFile" : "",
      "systemPrompt" : "",
      "systemPromptFile" : "",
      "model" : "",
      "maxTokens" : 4096,
      "temperature" : 1.0,
      "topP" : -1.0,
      "frequencyPenalty" : 0.0,
      "presencePenalty" : 0.0,
      "responseFormat" : "text",
      "stopSequences" : "",
      "outputFile" : "",
      "timeoutSeconds" : 120,
      "outputSchema" : "",
      "validationRetries" : 0,
      "redactPatterns" : ""
    },
    "publishes" : [ "completionTokens", "finishReason", "model", "promptTokens", "response", "totalTokens", "validationError" ]
  }, {
    "name" : "openrouter",
    "displayName" : "OpenRouter",
    "category" : "AI",
    "summary" : "Send a prompt to OpenRouter (any vendor's model via one API) and use the response.",
    "credentialRequired" : true,
    "credentialType" : "openrouter",
    "fields" : {
      "prompt" : "",
      "promptFile" : "",
      "systemPrompt" : "",
      "systemPromptFile" : "",
      "model" : "",
      "maxTokens" : 4096,
      "temperature" : 1.0,
      "topP" : -1.0,
      "frequencyPenalty" : 0.0,
      "presencePenalty" : 0.0,
      "responseFormat" : "text",
      "stopSequences" : "",
      "outputFile" : "",
      "timeoutSeconds" : 120,
      "outputSchema" : "",
      "validationRetries" : 0,
      "redactPatterns" : ""
    },
    "publishes" : [ "completionTokens", "finishReason", "model", "promptTokens", "response", "totalTokens", "validationError" ]
  }, {
    "name" : "os",
    "displayName" : "OS Operation",
    "category" : "Files & System",
    "summary" : "Common OS operations (files, search, disk, date, sleep) — declarative, no shell, fail-loud.",
    "usage" : "os operation (path/src/dest/content/text/pattern/find/replace/glob/seconds/pid/format/json…) — Run a common OS operation DIRECTLY (no shell, no script). PREFER this over runtimeenv for routine work; use runtimeenv ONLY to run a real program/script with logic. An os operation either does the thing or fails loud — it cannot silently no-op (that is the runtimeenv trap this replaces).\n    operation: required. One of:\n      Files: readfile(path) · writefile(path, content) · appendfile(path, text) · deletefile(path[, recursive]) · copyfile(src, dest) · movefile(src, dest) · touchfile(path) · makedir(path) · exists(path) · dir(path[, recursive, glob]) · findfiles(path, glob) · searchfiles(path, pattern[, glob, ignoreCase]) · replaceinfile(path, find, replace[, all, regex]) · countlines(path) · head/tail(path, lines) · filehash(path[, algorithm])\n      System: usage(path) — disk free/total/percentUsed · systeminfo · date(format) · sleep(seconds) · killpid(pid[, force]) · env(name)\n      Data: jsonget(json, path) — extract a value by dot path (supports arr[0])\n    Outputs are published and referenced as {{taskName.field}} — e.g. {{read.content}}, {{disk.percentUsed}}, {{now.date}}, {{find.files}}.\n    PATHS ARE PLATFORM-SPECIFIC. This tool runs wherever InTouch is installed — Windows, Linux or macOS.\n    Never hardcode /tmp, /var, /home or C:\\\\ from habit: read the host's own paths first\n    ({\"operation\":\"env\",\"name\":\"TEMP\"} or systeminfo) and build from what it reports.\n    Example (append a daily metric line, no shell):\n      task 'tmp':  {\"operation\":\"env\",\"name\":\"TEMP\"}\n      task 'now':  {\"operation\":\"date\",\"format\":\"yyyy-MM-dd HH:mm:ss\"}\n      task 'disk': {\"operation\":\"usage\",\"path\":\"{{tmp.value}}\"}\n      task 'log':  {\"operation\":\"appendfile\",\"path\":\"{{tmp.value}}/health.log\",\"text\":\"{{now.date}} disk {{disk.percentUsed}}% used\"}",
    "credentialRequired" : false,
    "fields" : {
      "operation" : "appendfile",
      "path" : "/tmp/example.log",
      "text" : "hello"
    },
    "publishes" : [ "algorithm", "bytes", "content", "count", "cpuCores", "date", "deleted", "dest", "disk", "entries", "epochMillis", "exists", "files", "free", "hash", "java", "killed", "lineCount", "lines", "matches", "memory", "modified", "os", "path", "percentUsed", "pid", "replacements", "set", "size", "slept", "source", "summary", "total", "type", "used", "user", "value" ]
  }, {
    "name" : "runtimeenv",
    "displayName" : "Runtime Environment",
    "category" : "Execution",
    "summary" : "Last resort: run a custom script when no native tool covers the task.",
    "usage" : "runtimeenv runtimeEnvName (script | scriptContent) (args environmentVariables credentialNames credentialEnvVarOverrides timeoutSeconds outputFile credentialName) — Run a custom script within a named runtime environment.\n    LAST RESORT — use this ONLY when nothing already built fits. Prefer, in order: a native tool (google_workspace for Gmail/Calendar/Drive/Sheets/Docs, http for REST/web, sql for databases, s3/aws, ftp, ssh, message for notifications) → a pre-built operation (run_operation) → an installed skill (run_skill) → an installed IML tool or job (run_iml_workflow / run_workflows_by_name / run_jobfile). They all work. Do NOT shell out via runtimeenv to do what any of those already does (e.g. never run `gog`/curl/a Google API call here — use google_workspace).\n    runtimeEnvName: required, name of the runtime environment (provides interpreter, env vars, working directory).\n    Supply EXACTLY ONE of:\n      script:        FULL FILE PATH to a script that ALREADY EXISTS on disk. Use this when the user has staged the file.\n      scriptContent: INLINE script body. The server writes it to a tracked temp file inside the runtime env's working dir, runs it, and deletes it after the task completes. Use this only when you must run genuinely custom logic that no native tool provides — DO NOT try to write a file first via http or any other tool.\n    args: command-line arguments passed to the script (optional).\n    environmentVariables: additional env vars as \"key=value\" lines (optional, merged with runtime env vars).\n    credentialNames: list of credential names whose values are injected as env vars before the script runs. Each credential's standard env vars come from its connector type (e.g. `openai` → OPENAI_API_KEY; `aws` → AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY; `mysql` → MYSQL_HOST/PORT/USER/PASSWORD). USE THIS — never have the user paste secrets into environmentVariables.\n    credentialEnvVarOverrides: optional map { credentialName: { fieldName: customEnvVarName } } to add aliases for non-standard env var names.\n    timeoutSeconds: max execution time (optional, default 300).\n    outputFile: file path to write stdout to (optional).\n    credentialName: optional, name of an `intouch_user` credential. When supplied, the server logs in as that user and exposes SESSION_TOKEN/INTOUCH_SERVER/INTOUCH_PORT — for scripts that call back into THIS InTouch server. Do NOT list `intouch_user` credentials in credentialNames — they go in this single privileged slot.\n  IMPORTANT: To install skills/iml-tools/iml-jobs, use create_skill / create_iml_tool / create_iml_job. NEVER POST to /api/skill/install via the http tool — it requires a credential and there is a built-in tool for this exact purpose.\n  Use list_runtimeenvs to find available runtime environments. If none exist, use create_runtimeenv first.\n  Example (inline): {\"name\":\"runtimeenv-step\",\"tool\":\"runtimeenv\",\"runtimeEnvName\":\"python3\",\"properties\":{\"scriptContent\":\"import os; print(os.environ['OPENAI_API_KEY'][:6])\",\"credentialNames\":[\"my_openai\"]}}\n  Example (path):   {\"name\":\"runtimeenv-step\",\"tool\":\"runtimeenv\",\"runtimeEnvName\":\"python3\",\"properties\":{\"script\":\"/opt/scripts/etl.py\",\"args\":\"--date 2026-03-29\"}}",
    "credentialRequired" : false,
    "fields" : {
      "scriptSource" : "inline",
      "script" : "",
      "scriptContent" : "",
      "args" : "",
      "environmentVariables" : "",
      "envVars" : { },
      "credentialNames" : [ ],
      "credentialEnvVarOverrides" : { },
      "successCodes" : "0",
      "timeoutSeconds" : 300,
      "outputFile" : ""
    },
    "required" : [ "runtimeEnvName" ],
    "publishes" : [ "exitCode", "output" ]
  }, {
    "name" : "slack",
    "displayName" : "Slack",
    "category" : "Messaging",
    "summary" : "Post messages to Slack (outbound).",
    "credentialRequired" : true,
    "credentialType" : "slack",
    "fields" : { },
    "publishes" : [ ]
  }, {
    "name" : "sleep",
    "displayName" : "Sleep",
    "category" : "Flow Control",
    "summary" : "Pause the job for a set amount of time.",
    "usage" : "sleep seconds — Pause job execution for N seconds (capped at 3600 = 1 hour). Honors interruption: a kill request exits cleanly within ~1s.\n    seconds: required, integer 0-3600. 0 is a no-op.\n  Use to insert deliberate delays between tasks (e.g. wait for an upstream job to finish, throttle API calls, give a remote system time to settle).\n  For waits longer than an hour, do NOT use sleep — split into two jobs and schedule the second one offset from the first.\n  Example: {\"name\":\"sleep-step\",\"tool\":\"sleep\",\"properties\":{\"seconds\":30}}",
    "credentialRequired" : false,
    "fields" : {
      "seconds" : 0
    },
    "publishes" : [ ]
  }, {
    "name" : "sms",
    "displayName" : "SMS",
    "category" : "Messaging",
    "summary" : "Send SMS via Telnyx, Twilio, Plivo or AWS SNS (outbound).",
    "credentialRequired" : true,
    "credentialType" : "sms",
    "fields" : { },
    "publishes" : [ ]
  }, {
    "name" : "smtp",
    "displayName" : "Email (SMTP Send)",
    "category" : "Messaging",
    "summary" : "SMTP transport configuration — the server-wide outbound email path used by `message`. A credential type, not a task: nothing calls this directly.",
    "credentialRequired" : true,
    "credentialType" : "smtp",
    "fields" : { },
    "publishes" : [ ]
  }, {
    "name" : "speech_to_text",
    "displayName" : "Speech to Text",
    "category" : "AI",
    "summary" : "Transcribe an audio file into text.",
    "credentialRequired" : true,
    "credentialType" : "openai",
    "fields" : {
      "audioFile" : "",
      "model" : "",
      "language" : "",
      "prompt" : "",
      "responseFormat" : "text",
      "temperature" : 0.0,
      "outputFile" : "",
      "timeoutSeconds" : 300
    },
    "publishes" : [ "duration", "language", "model", "transcription" ]
  }, {
    "name" : "teams",
    "displayName" : "Microsoft Teams",
    "category" : "Messaging",
    "summary" : "Send messages to Microsoft Teams (outbound). Department+.",
    "credentialRequired" : true,
    "credentialType" : "teams",
    "fields" : { },
    "publishes" : [ ]
  }, {
    "name" : "telegram",
    "displayName" : "Telegram",
    "category" : "Messaging",
    "summary" : "Send messages via a Telegram bot (outbound).",
    "credentialRequired" : true,
    "credentialType" : "telegram",
    "fields" : { },
    "publishes" : [ ]
  }, {
    "name" : "text_to_speech",
    "displayName" : "Text to Speech",
    "category" : "AI",
    "summary" : "Convert text into spoken audio.",
    "credentialRequired" : true,
    "credentialType" : "openai",
    "fields" : {
      "input" : "",
      "inputFile" : "",
      "model" : "",
      "voice" : "alloy",
      "responseFormat" : "mp3",
      "speed" : 1.0,
      "outputFile" : "",
      "timeoutSeconds" : 120
    },
    "requiredOneOf" : [ [ "input", "inputFile" ] ],
    "publishes" : [ "audioSize", "format", "inputLength", "model", "outputFile", "voice" ]
  }, {
    "name" : "vector_store",
    "displayName" : "Vector Store",
    "category" : "Data",
    "summary" : "Store and search vector embeddings (semantic search).",
    "credentialRequired" : true,
    "credentialType" : "vector_store",
    "fields" : {
      "operation" : "query",
      "indexName" : "",
      "namespace" : "",
      "queryVector" : "",
      "queryText" : "",
      "topK" : 10,
      "filter" : "",
      "includeMetadata" : true,
      "includeValues" : false,
      "vectors" : "",
      "vectorsFile" : "",
      "deleteIds" : "",
      "deleteFilter" : "",
      "outputFile" : "",
      "timeoutSeconds" : 120
    },
    "publishes" : [ "operation", "response" ]
  }, {
    "name" : "whatsapp",
    "displayName" : "WhatsApp",
    "category" : "Messaging",
    "summary" : "Send WhatsApp messages via the Meta Cloud API (outbound). Department+.",
    "credentialRequired" : true,
    "credentialType" : "whatsapp",
    "fields" : { },
    "publishes" : [ ]
  }, {
    "name" : "workflow",
    "displayName" : "Workflow",
    "category" : "Automation",
    "summary" : "Run, enable or disable ANOTHER workflow from inside this one.",
    "usage" : "workflow operation ids — Act on OTHER workflows from a step inside this one: run them, or enable/disable them. This is how workflows compose (chaining, sub-workflows, fan-out). It does NOT group steps — steps are grouped by being tasks in the same workflow.\n    operation: required integer.\n      1 = Enable          turn the target workflows ON\n      2 = Disable         turn the target workflows OFF\n      3 = Submit          start them and continue immediately (default)\n      4 = SubmitAndWait   start them and BLOCK until every one finishes — use this when a later task depends on their output\n    ids: required, a list of workflow IDs (integers) — NOT names. Get them from find(type=workflow). An empty list is not an error and not a no-op you want: the task completes \"successfully\" having done nothing at all.\n  This task cannot be overridden at run time (properties passed as overrides are refused outright, by design).\n  Enable/Disable act on the TARGET ids, never on the workflow containing this step.\n  Example — run workflows 12 and 15 and wait for both: {\"operation\":4,\"ids\":[12,15]}",
    "credentialRequired" : false,
    "fields" : {
      "operation" : 3,
      "ids" : [ ]
    },
    "required" : [ "ids" ],
    "publishes" : [ ]
  }, {
    "name" : "xai",
    "displayName" : "xAI (Grok)",
    "category" : "AI",
    "summary" : "Send a prompt to xAI (Grok) and use the response.",
    "credentialRequired" : true,
    "credentialType" : "xai",
    "fields" : {
      "prompt" : "",
      "promptFile" : "",
      "systemPrompt" : "",
      "systemPromptFile" : "",
      "model" : "",
      "maxTokens" : 4096,
      "temperature" : 1.0,
      "topP" : -1.0,
      "frequencyPenalty" : 0.0,
      "presencePenalty" : 0.0,
      "responseFormat" : "text",
      "stopSequences" : "",
      "outputFile" : "",
      "timeoutSeconds" : 120,
      "outputSchema" : "",
      "validationRetries" : 0,
      "redactPatterns" : ""
    },
    "publishes" : [ "completionTokens", "finishReason", "model", "promptTokens", "response", "totalTokens", "validationError" ]
  } ],
  "toolCount" : 44
}