agents/openai.yaml
interface:
display_name: "Hexabot Action Creator"
short_description: "Design and implement Hexabot v3 actions"
default_prompt: "Use $hexabot-ai/action-creator to design and implement a Hexabot v3 action."
policy:
allow_implicit_invocation: true
examples/create-crm-ticket.action.example.ts
/*
* Hexabot - Fair Core License (FCL-1.0-ALv2)
* Copyright (c) 2026 Hexastack.
* Full terms: see LICENSE.md.
*/
import { z } from 'zod';
// This mirrors Hexabot monorepo API source actions. In a standalone
// hexabot-action-* package, replace these imports with the package template's
// public imports or configured TypeScript path aliases after inspecting it.
import { createAction } from '@/actions/create-action';
import { WorkflowRuntimeContext } from '@/workflow/contexts/workflow-runtime.context';
const createCrmTicketInputSchema = z.object({
subject: z.string().trim().min(1).meta({
title: 'Subject',
description: 'Short ticket title shown to the CRM support team.',
}),
description: z.string().trim().min(1).meta({
title: 'Description',
description: 'Detailed issue or request summary.',
}),
requester_email: z.email().meta({
title: 'Requester email',
description: 'Email address of the person requesting support.',
}),
priority: z.enum(['low', 'normal', 'high']).default('normal').meta({
title: 'Priority',
description: 'Ticket priority in the external CRM.',
}),
idempotency_key: z.string().trim().min(1).optional().meta({
title: 'Idempotency key',
description:
'Stable key used by the CRM to avoid duplicate tickets when retries occur.',
}),
});
const createCrmTicketSettingsSchema = z.strictObject({
base_url: z.url().meta({
title: 'Base URL',
description: 'CRM API base URL.',
}),
api_key: z.string().optional().meta({
title: 'Credential',
description: 'Credential containing the CRM API token.',
'ui:widget': 'AutoCompleteWidget',
'ui:options': {
entity: 'Credential',
valueKey: 'id',
labelKey: 'name',
enableEntityAddButton: true,
},
}),
});
const createCrmTicketOutputSchema = z.union([
z.object({
success: z.literal(true),
ticket_id: z.string(),
status: z.string(),
url: z.url().optional(),
}),
z.object({
success: z.literal(false),
status: z.string(),
error: z.string(),
}),
]);
type CreateCrmTicketInput = z.infer<typeof createCrmTicketInputSchema>;
type CreateCrmTicketOutput = z.infer<typeof createCrmTicketOutputSchema>;
type CreateCrmTicketSettings = z.infer<typeof createCrmTicketSettingsSchema>;
const normalizeBaseUrl = (baseUrl: string) => baseUrl.replace(/\/+$/, '');
const parseJsonBody = async (response: Response): Promise<unknown> => {
const text = await response.text();
if (!text.trim()) {
return {};
}
try {
return JSON.parse(text) as unknown;
} catch {
return { raw: text };
}
};
const readString = (
payload: Record<string, unknown>,
key: string,
): string | undefined => {
const value = payload[key];
return typeof value === 'string' && value.trim() ? value : undefined;
};
export const CreateCrmTicketAction = createAction<
CreateCrmTicketInput,
CreateCrmTicketOutput,
WorkflowRuntimeContext,
CreateCrmTicketSettings
>({
name: 'create_crm_ticket',
description:
'Creates a support ticket in an external CRM using a stored credential reference.',
group: 'crm',
icon: 'TicketPlus',
color: '#2f80ed',
inputSchema: createCrmTicketInputSchema,
outputSchema: createCrmTicketOutputSchema,
settingsSchema: createCrmTicketSettingsSchema,
async execute({ input, context, settings }) {
const logger = context.services.logger;
const token = await context.services.credentials.findOneValue(
settings.api_key,
);
if (!token) {
throw new Error(
'Missing CRM credential. Configure the create_crm_ticket api_key setting.',
);
}
const endpoint = `${normalizeBaseUrl(settings.base_url)}/tickets`;
const timeoutMs = settings.timeout_ms ?? 10000;
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
authorization: `Bearer ${token}`,
'content-type': 'application/json',
...(input.idempotency_key
? { 'idempotency-key': input.idempotency_key }
: {}),
},
body: JSON.stringify({
subject: input.subject,
description: input.description,
requester_email: input.requester_email,
priority: input.priority,
}),
signal: AbortSignal.timeout(timeoutMs),
});
const body = await parseJsonBody(response);
const payload =
body && typeof body === 'object' && !Array.isArray(body)
? (body as Record<string, unknown>)
: {};
if (!response.ok) {
const message =
readString(payload, 'message') ??
readString(payload, 'error') ??
`CRM returned HTTP ${response.status}`;
logger.warn('create_crm_ticket failed', {
status: response.status,
requester_email: input.requester_email,
idempotency_key: input.idempotency_key,
});
return {
success: false,
status: String(response.status),
error: message,
};
}
const ticketId = readString(payload, 'id') ?? readString(payload, 'key');
if (!ticketId) {
throw new Error('CRM response did not include a ticket identifier.');
}
return {
success: true,
ticket_id: ticketId,
status: readString(payload, 'status') ?? 'created',
url: readString(payload, 'url'),
};
} catch (error) {
const message =
error instanceof Error ? error.message : 'Unknown CRM request error';
logger.warn('create_crm_ticket request error', {
error: message,
requester_email: input.requester_email,
idempotency_key: input.idempotency_key,
});
return {
success: false,
status: 'request_error',
error: message,
};
}
},
});
export default CreateCrmTicketAction;
references/action-authoring-guide.md
# Action Authoring Guide
Use this guide after inspecting the current Hexabot repo, application repo, or standalone action package. Target project code and installed package types win over this reference when details differ. If only this skill bundle is available, treat this guide as versioned background and state that runtime verification still needs a Hexabot project.
## Current API action architecture
- API actions live on top of `@hexabot-ai/agentic` and are exposed by the Hexabot API package.
- `BaseAction` extends `AbstractAction`, is `@Injectable()`, implements `OnModuleInit`, and registers itself with `ActionService`.
- `createAction` returns an injectable `BaseAction` subclass for simple actions.
- `ActionService` stores a registry keyed by action name and exposes JSON Schema definitions for UI/workflow authoring.
- Omitted API input/output/settings schemas default to strict empty objects. Add explicit schemas for real actions.
- There is no repo-defined `@Action` decorator. Do not invent one.
## Pattern selection
- Prefer `createAction` when the action only needs `input`, `settings`, `bindings`, and `context.services`.
- Extend `BaseAction` when the action needs constructor injection, class inheritance, protected helpers, or action-specific lifecycle behavior.
- Use `defineAction` only for standalone `@hexabot-ai/agentic` workflows outside the Nest API extension runtime.
- Use existing base classes for families of actions when present, such as AI and messaging base actions.
## File placement and discovery
- In the Hexabot monorepo, built-in actions are under `packages/api/src/extensions/actions/<group>/*.action.ts`.
- In a generated or standalone `hexabot-action-*` package, inspect the package template, `package.json`, TypeScript config, and source tree before choosing import paths or file placement.
- In an application repo, inspect the configured extension output/source locations before assuming where custom actions compile.
- Runtime discovery loads compiled `.action.js` files from:
- `node_modules/@hexabot-ai/api/dist/extensions/actions/**/*.action.js`
- `node_modules/hexabot-action-*/**/*.action.js`
- `dist/extensions/actions/**/*.action.js`
- Runtime binding kinds load compiled `.binding.js` files from equivalent action extension paths.
- For npm-distributed custom actions, the package name convention is `hexabot-action-*`.
## Implementation steps
1. Inspect the active project's action APIs first. In the Hexabot monorepo, start with `packages/api/src/actions/types.ts`, `base-action.ts`, and `create-action.ts`. Outside the monorepo, inspect the generated package template or installed Hexabot package exports before choosing imports.
2. Inspect at least one nearby action with the same concern:
- External HTTP: `extensions/actions/web/http-request.action.ts`.
- Memory writes: `extensions/actions/memory/update-memory.action.ts`.
- LLM actions: `extensions/actions/ai/*`.
- Subscriber actions: `extensions/actions/subscriber/*`.
- Messaging actions: `extensions/actions/messaging/*`.
3. Define Zod schemas before writing business logic.
4. Use `snake_case` action names and stable names that match workflow `defs.*.action`.
5. Set `description`, `group`, and optionally `icon`, `color`, and `workflowTypes`.
6. Return JSON-serializable outputs only.
7. Export the action as both a named export and default export when following built-in examples.
## Workflow types
- API actions default to all workflow types unless `workflowTypes` is specified.
- Restrict conversational-only actions to `WorkflowType.conversational` when they need subscriber, message, thread, or inbound event state.
- Use `WorkflowRuntimeContext` for generic manual/scheduled/conversational actions.
- Use `ConversationalWorkflowContext` when the action requires `context.event.getInitiator()`, subscriber labels, thread context, or channel messaging.
## Runtime context services
Common services available through `context.services` include `logger`, `settings`, `content`, `contentType`, `message`, `subscriber`, `actions`, `credentials`, and `mcp`.
- Prefer `context.services.logger` over `console`.
- Use `context.memoryStore` for workflow memory operations.
- Use `context.services.credentials.findOneValue(id)` for credential values.
- Do not hold workflow runtime context references after execution.
## Localization and UI metadata
- Zod `.meta({ title, description, ... })` drives generated JSON schemas in the editor.
- Built-in actions may provide i18n JSON under paths like `i18n/en.translations.json` and `i18n/fr.translations.json`.
- Do not create or update extra locale files or translations, including French `fr.translations.json`, unless the user explicitly asks for localization work.
- If UI-visible text needs a default-language entry for the target repo pattern, update only that required default locale and mention optional additional translations as a follow-up.
## Guardrails
- Preserve source license headers in Hexabot source files.
- Do not modify generated `dist/` files directly; update source files and rebuild.
- Do not add dependencies without a clear need and without following the target project's package manager and workspace conventions.
- Do not hand-edit lockfiles unless the target project's established workflow requires it.
- Keep workflow orchestration in YAML; keep one bounded capability inside each action.
references/action-contract-guide.md
# Action Contract Guide
Design the action contract before writing code. The contract should be stable enough for workflow YAML authors to rely on.
## Contract checklist
- Action name: `snake_case`, usually verb-object, for example `create_ticket`, `update_memory`, `retrieve_rag_content`.
- Purpose: one bounded capability and explicit side effects.
- Workflow types: all by default, or a restricted list from `WorkflowType`.
- Input schema: business payload evaluated from YAML literals or JSONata.
- Output schema: stable JSON-serializable result referenced through `$output.<task>`.
- Settings schema: execution configuration, not per-call business payload.
- Supported bindings: only binding kinds the action actually consumes.
- Failure behavior: throw, retry, or return an explicit failure output.
## Input schemas
- Use Zod schemas and infer TypeScript types with `z.infer`.
- Prefer strict validation for externally meaningful contracts.
- Use `.meta({ title, description })` for editor-friendly fields.
- Put payload fields in `inputs`, not `settings`.
- Validate IDs, URLs, emails, enums, minimum lengths, and required combinations with `.superRefine()` when needed.
## Output schemas
- Return plain objects, arrays, strings, numbers, booleans, or null-compatible data.
- Include stable external identifiers such as `ticket_id`, `request_id`, or `status`.
- Prefer explicit success/failure unions when workflows should branch on the result.
- Throw instead of returning failure when configuration is invalid or the workflow cannot reasonably recover.
- Do not expose raw credential values, bearer tokens, or full sensitive upstream payloads.
## Settings schemas
- Use settings for execution knobs: API base URL, credential reference, timeout-sensitive mode, method, model parameters, or behavior toggles.
- Do not redefine base settings keys from `@hexabot-ai/agentic`: `timeout_ms` and `retries`.
- Base settings are parsed together with action settings and are available during execution through `settings.timeout_ms` and `settings.retries`.
- Use `z.strictObject()` for action settings unless the existing local pattern uses a looser schema.
- Use defaults for safe, unsurprising behavior.
## Bindings
- Declare `supportedBindings` only when the action consumes runtime bindings.
- Built-in AI actions commonly support `tools`, `mcp`, `model`, and `memory`.
- `model` is a single binding kind; `tools`, `memory`, and `mcp` are multiple binding kinds in the current API.
- If adding a new binding kind inside the Hexabot monorepo, inspect `packages/api/src/bindings/**` and follow `createBindingKind` examples. In a standalone package, inspect the installed API exports or target package template first. Do not invent binding metadata.
- Validate nested binding support when a binding definition mounts other bindings.
## Workflow compatibility
- A task def calls an action with:
```yaml
defs:
create_ticket:
kind: task
action: create_crm_ticket
inputs:
subject: =$input.subject
```
- Task inputs are evaluated before the action runs.
- The raw action output is stored under `$output.<task>`.
- Task-level output mapping is not supported in the current DSL.
- Workflow metadata such as name, schedule, and type lives outside the YAML definition.
## Failure and retry policy
- Reads should be repeatable and retry-safe.
- Writes should accept an idempotency key or derive a stable external ID when the external system supports it.
- Do not enable retries blindly for non-idempotent side effects such as creating tickets, sending messages, or charging accounts.
- Log safe correlation data and return safe failure fields that workflows can branch on.
references/output-templates.md
# Output Templates
Use these response shapes when the user wants polished Hexabot action work.
## Action design
````markdown
Action: `action_name`
Purpose:
- ...
Runtime:
- Target: Hexabot monorepo API action | application API extension action | npm `hexabot-action-*` package | standalone agentic action
- Workflow types: conversational | manual | scheduled
- Pattern: `createAction` | `BaseAction` | `defineAction`
Contract:
```ts
// input schema
// output schema
// settings schema
```
Bindings:
- ...
Behavior:
- Side effects: ...
- Idempotency: ...
- Failure handling: ...
- Observability: ...
Tests:
- ...
````
## Scaffold or implementation summary
````markdown
Implemented:
- ...
Contract:
- Action name: `...`
- Inputs: ...
- Outputs: ...
- Settings: ...
- Bindings: ...
Validation:
- ...
Notes:
- ...
````
## Action review
````markdown
Findings:
- Critical: `path:line` - ...
- High: `path:line` - ...
- Medium: `path:line` - ...
Open questions:
- ...
Suggested changes:
```ts
// focused patch or contract sketch
```
Validation gaps:
- ...
````
## External integration action
````markdown
Integration: ...
Action contract:
- Name: `...`
- Inputs: ...
- Outputs: ...
- Settings: ...
- Credential handling: ...
Runtime behavior:
- Request: ...
- Response normalization: ...
- Error handling: ...
- Idempotency: ...
- Rate limits/retries: ...
Required setup:
- Credential record: ...
- Environment/config: ...
- Dependency/package: ...
Tests:
- ...
````
## Action documentation
````markdown
### `action_name`
Purpose: ...
Inputs:
- `field`: ...
Outputs:
- `field`: ...
Settings:
- `field`: ...
Bindings:
- ...
Example workflow task:
```yaml
defs:
task_name:
kind: task
action: action_name
inputs:
field: =$input.field
```
Operational notes:
- ...
````
references/settings-and-secrets-guide.md
# Settings And Secrets Guide
Use this guide for actions that call external systems, use LLMs, or need runtime configuration.
## Principle
Workflow YAML should reference configuration and credentials, not contain secret values. Action code resolves secrets at runtime and never logs or returns them.
## Credentials
- Prefer Hexabot credential references over raw tokens in input or settings.
- Existing API examples use UI metadata with `AutoCompleteWidget` and `entity: 'Credential'`.
- Resolve values with `context.services.credentials.findOneValue(credentialId)`.
- Treat an empty credential value as a configuration error and throw an actionable message.
- Do not include raw credential values in logs, action outputs, errors returned to workflows, or test snapshots.
Example setting field:
```ts
api_key: z.string().optional().meta({
title: 'Credential',
description: 'Credential used to authenticate with the external API.',
'ui:widget': 'AutoCompleteWidget',
'ui:options': {
entity: 'Credential',
valueKey: 'id',
labelKey: 'name',
enableEntityAddButton: true,
},
});
```
## Environment and config
- Inspect current config and settings services before reading environment variables directly.
- API package guidance prefers central config over direct `process.env` access.
- For external action packages, document required env vars or credential records, but keep workflow YAML secret-free.
- Use settings for non-secret runtime choices such as base URL, mode, region, or response limits.
## External API actions
- Use `settings.timeout_ms` as the request timeout when the HTTP client supports it.
- Normalize upstream responses into a stable output shape.
- Include upstream status and safe request IDs when useful.
- Redact headers and payload fields that may contain secrets or PII.
- Use idempotency keys for create/update side effects when supported.
- Decide whether network errors should throw or return `{ success: false, error }` based on whether workflow branching should handle them.
## LLM actions
- Prefer existing AI base actions and schemas before creating new LLM action logic.
- Use `model` bindings for model provider, model ID, base URL, organization, and credential reference when following current built-in AI patterns.
- Do not put provider API keys directly in action inputs, outputs, or workflow YAML.
- Be explicit about prompt source, memory usage, tool access, output schema, and model settings.
- Avoid passing secrets or unnecessary PII into prompts.
## Memory actions
- Use `context.memoryStore` for workflow memory reads/writes.
- Validate memory slugs and fields against current memory definitions when the repo exposes that information.
- Return the updated or read memory values in a stable shape.
- For AI memory bindings, inspect current `memory.binding.ts` and AI base action behavior before generating code.
## Logging
- Use `context.services.logger`.
- Log action name, target system, status, safe IDs, and useful error summaries.
- Do not use `console`.
- Never log raw tokens, authorization headers, credential values, complete customer records, or full model prompts unless the existing observability policy explicitly allows it.
references/testing-and-review-checklist.md
# Testing And Review Checklist
Use this checklist when reviewing an action or planning validation for a new action.
## Contract tests
- Parse valid input, output, and settings with the action schemas.
- Assert invalid inputs fail for required fields, formats, enums, and cross-field constraints.
- Assert custom settings do not redefine `timeout_ms` or `retries`.
- Assert default settings behave as expected.
- Assert outputs match the declared output schema on both success and failure paths.
## Registration and schema tests
- For API actions, instantiate with a mocked `ActionService` and assert registration through `onModuleInit()` when relevant.
- For built-in or module-loaded actions, verify `ActionService.getAllSchemaDefinitions()` exposes name, description, group, icon/color, workflow types, bindings, input schema, output schema, and settings schema.
- Check action names are unique and `snake_case`.
- Confirm workflow type filters hide incompatible actions.
## Execution tests
- Mock external SDKs, HTTP clients, mailers, subscriber services, credentials, memory stores, and LLM providers.
- Assert safe request construction, timeout usage, headers, payload normalization, and idempotency key propagation.
- Cover upstream success, non-2xx responses, network failure, malformed JSON, missing credentials, and invalid configuration.
- Assert logs use safe metadata and do not include raw secrets.
## Binding tests
- Assert actions reject unsupported binding kinds through the base runtime when applicable.
- For actions using `model`, `tools`, `mcp`, or `memory`, test missing required bindings and malformed binding settings.
- For custom binding kinds, test registration through `RuntimeBindingsService` and generated JSON schema definitions.
- Check nested binding allowlists when tools can mount other actions or bindings.
## Memory tests
- Test reads/writes through `context.memoryStore`.
- Validate memory slug constraints and selected memory definitions.
- Assert memory output is stable and JSON-serializable.
- For LLM memory, test prompt construction without leaking unrelated memory.
## Idempotency and retry review
- Mark read actions as retry-safe.
- For write actions, require an idempotency strategy or explicitly document that retries may duplicate side effects.
- Check task or default retry settings in any accompanying workflow YAML.
- Ensure failure outputs give workflows enough information to branch or escalate.
## Security review
- No raw secrets in inputs, outputs, workflow YAML, logs, thrown messages, or snapshots.
- No full PII payloads in logs unless already established by repo policy.
- External URLs and methods are constrained when the action could become an SSRF or data-exfiltration path.
- LLM prompts avoid unnecessary sensitive context and tool access is bounded.
## Commands
Use the target project's existing package manager and scripts. In the Hexabot monorepo, package-scoped checks usually look like:
```bash
pnpm --filter @hexabot-ai/api run typecheck
pnpm --filter @hexabot-ai/api run lint
pnpm --filter @hexabot-ai/api run test
pnpm --filter @hexabot-ai/api run build
```
For standalone `@hexabot-ai/agentic` action examples:
```bash
pnpm --filter @hexabot-ai/agentic run typecheck
pnpm --filter @hexabot-ai/agentic run test
```
For a separate `hexabot-action-*` package, run that package's local typecheck, lint, test, and build scripts, for example `pnpm run typecheck`, `pnpm run test`, and `pnpm run build` when those scripts exist. If the workspace is only this skill bundle, report that no Hexabot runtime checks were available.
SKILL.md
---
name: hexabot-action-creator
description: Create, scaffold, implement, review, document, and improve Hexabot v3 actions, including custom workflow actions, external API integrations, LLM-powered actions, memory reads/writes, settings or credential handling, action bindings, extension/plugin action packages, and input/output contract design. Use when wrapping business integration requirements as Hexabot actions. Do not use for generic NestJS, frontend-only work, generic workflow YAML unless action implementation is required, chatbot copywriting, generic API clients not wrapped as Hexabot actions, or non-Hexabot automation frameworks.
---
# hexabot-ai/action-creator
Use this skill for Hexabot v3 action work. Hexabot action APIs are project-defined and can evolve, so inspect the user's active Hexabot repo, custom action package, or installed package types before producing final code.
## Source of truth
Before final action code or review findings, inspect the relevant current project files. If the workspace is only this packaged skill, use the bundled references and examples, then state that runtime verification still needs a Hexabot project or action package.
When the user is working inside the Hexabot monorepo, these are the usual landmarks:
- API action surface: `packages/api/src/actions/**`.
- Built-in API action examples: `packages/api/src/extensions/actions/**`.
- Runtime binding system: `packages/api/src/bindings/**` and action binding files under `packages/api/src/extensions/actions/**/*.binding.ts`.
- Workflow runtime context and services: `packages/api/src/workflow/contexts/**`.
- Agentic action base: `packages/agentic/src/action/**`, `packages/agentic/src/dsl.types.ts`, and `packages/agentic/README.md`.
- Extension discovery: `packages/api/src/actions/actions.module.ts`, `packages/api/src/bindings/bindings.module.ts`, and `packages/api/README.md`.
When the user is working in a separate `hexabot-action-*` package or application repo, inspect that package's `package.json`, source tree, generated templates, TypeScript path aliases, and dependency exports instead of assuming monorepo `packages/*` paths exist.
Do not invent decorators, schema fields, binding kinds, registration hooks, package layout, import paths, or config conventions that are not present in the active project or installed Hexabot packages.
## Core workflow
1. Identify the requested action capability, side effects, workflow type, and target location: Hexabot monorepo API source, custom application action, npm package named `hexabot-action-*`, or standalone `@hexabot-ai/agentic` example.
2. Inspect nearby examples with the same shape when the target project is available: web/API, AI/LLM, memory, subscriber, messaging, or binding-aware action. If only this skill bundle is available, use the bundled example as illustrative guidance and call out unverified runtime assumptions.
3. Choose the implementation pattern:
- Use API `createAction` for simple Nest-discovered actions with no custom constructor dependencies.
- Extend API `BaseAction` for constructor-injected services, inheritance, or richer behavior.
- Use `defineAction` only for standalone `@hexabot-ai/agentic` runtime examples, not Nest API extension actions.
4. Design the contract before code: action name, workflow types, input schema, output schema, settings schema, supported bindings, idempotency, failure behavior, logging, and tests.
5. Implement narrowly with Zod schemas, current context services, safe credential handling, and explicit outputs that workflows can reference through `$output.<task>`.
6. Add focused tests or review notes that cover parsing, settings, output shape, error paths, idempotency, and binding compatibility.
## Bundled resources
- Read `references/action-authoring-guide.md` before implementing or scaffolding action code.
- Read `references/action-contract-guide.md` when designing input/output/settings schemas or supported bindings.
- Read `references/settings-and-secrets-guide.md` for credentials, settings, environment variables, LLM model bindings, and external API calls.
- Read `references/testing-and-review-checklist.md` when reviewing an action or planning validation.
- Read `references/output-templates.md` when the user wants a polished design, scaffold, review, or documentation response.
- See `examples/create-crm-ticket.action.example.ts` for an illustrative API `createAction` pattern.
## Output defaults
- For implementation tasks, make the code change when the target Hexabot repo or action package is available and the user asked for implementation.
- For design-only tasks, provide a decision-complete action contract and call out any missing repo facts or runtime assumptions.
- For reviews, lead with findings ordered by severity and cite files or action contract fields.
- For workflow-only requests, use the Hexabot workflow skill unless action code or action contracts are required.
- Do not add or update extra locale files or translations, including French `fr.translations.json`, unless the user explicitly asks for localization work.