chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,132 @@
import { filterUndefined } from '@sim/utils/object'
import type {
RailwayCreatedResource,
RailwayCreateEnvironmentParams,
RailwayCreateEnvironmentResponse,
} from '@/tools/railway/types'
import {
optionalString,
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayCreateEnvironmentData {
environmentCreate?: RailwayCreatedResource
}
export const railwayCreateEnvironmentTool: ToolConfig<
RailwayCreateEnvironmentParams,
RailwayCreateEnvironmentResponse
> = {
id: 'railway_create_environment',
name: 'Railway Create Environment',
description: 'Create a Railway project environment',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway project ID',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Environment name',
},
sourceEnvironmentId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Environment ID to clone from',
},
ephemeral: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the environment is ephemeral',
},
skipInitialDeploys: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to skip initial deploys for the environment',
},
stageInitialChanges: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to stage initial changes instead of applying them immediately',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
mutation CreateEnvironment($input: EnvironmentCreateInput!) {
environmentCreate(input: $input) {
id
name
}
}
`,
variables: {
input: filterUndefined({
projectId: params.projectId.trim(),
name: params.name.trim(),
sourceEnvironmentId: optionalString(params.sourceEnvironmentId),
ephemeral: params.ephemeral,
skipInitialDeploys: params.skipInitialDeploys,
stageInitialChanges: params.stageInitialChanges,
}),
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayCreateEnvironmentData>(response)
const environment = data.data?.environmentCreate
if (!environment) throw new Error('Railway did not return a created environment')
return {
success: true,
output: {
environment: {
id: environment.id,
name: environment.name,
},
},
}
},
outputs: {
environment: {
type: 'object',
description: 'Created environment',
properties: {
id: { type: 'string', description: 'Environment ID' },
name: { type: 'string', description: 'Environment name' },
},
},
},
}
+132
View File
@@ -0,0 +1,132 @@
import { filterUndefined } from '@sim/utils/object'
import type {
RailwayCreatedResource,
RailwayCreateProjectParams,
RailwayCreateProjectResponse,
} from '@/tools/railway/types'
import {
optionalString,
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayCreateProjectData {
projectCreate?: RailwayCreatedResource
}
export const railwayCreateProjectTool: ToolConfig<
RailwayCreateProjectParams,
RailwayCreateProjectResponse
> = {
id: 'railway_create_project',
name: 'Railway Create Project',
description: 'Create a Railway project',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project name',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Project description',
},
workspaceId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Workspace ID to create the project in',
},
isPublic: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the project should be publicly visible',
},
defaultEnvironmentName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Name for the default environment',
},
prDeploys: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to enable pull request deploys',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
mutation CreateProject($input: ProjectCreateInput!) {
projectCreate(input: $input) {
id
name
}
}
`,
variables: {
input: filterUndefined({
name: params.name.trim(),
description: optionalString(params.description),
workspaceId: optionalString(params.workspaceId),
isPublic: params.isPublic,
defaultEnvironmentName: optionalString(params.defaultEnvironmentName),
prDeploys: params.prDeploys,
}),
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayCreateProjectData>(response)
const project = data.data?.projectCreate
if (!project) throw new Error('Railway did not return a created project')
return {
success: true,
output: {
project: {
id: project.id,
name: project.name,
},
},
}
},
outputs: {
project: {
type: 'object',
description: 'Created project',
properties: {
id: { type: 'string', description: 'Project ID' },
name: { type: 'string', description: 'Project name' },
},
},
},
}
+131
View File
@@ -0,0 +1,131 @@
import type {
RailwayCreatedResource,
RailwayCreateServiceParams,
RailwayCreateServiceResponse,
} from '@/tools/railway/types'
import {
optionalString,
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayCreateServiceData {
serviceCreate?: RailwayCreatedResource
}
export const railwayCreateServiceTool: ToolConfig<
RailwayCreateServiceParams,
RailwayCreateServiceResponse
> = {
id: 'railway_create_service',
name: 'Railway Create Service',
description: 'Create a Railway service from a GitHub repo or Docker image',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway project ID',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Service name',
},
repo: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'GitHub repository in owner/name format to deploy from',
},
image: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Docker image to deploy, for example redis:7-alpine',
},
branch: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Git branch to deploy when using a repository source',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => {
const repo = optionalString(params.repo)
const image = optionalString(params.image)
const branch = optionalString(params.branch)
const source = repo ? { repo } : image ? { image } : undefined
return {
query: `
mutation CreateService($input: ServiceCreateInput!) {
serviceCreate(input: $input) {
id
name
}
}
`,
variables: {
input: {
projectId: params.projectId.trim(),
name: params.name.trim(),
...(source ? { source } : {}),
...(branch ? { branch } : {}),
},
},
}
},
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayCreateServiceData>(response)
const service = data.data?.serviceCreate
if (!service) throw new Error('Railway did not return a created service')
return {
success: true,
output: {
service: {
id: service.id,
name: service.name,
},
},
}
},
outputs: {
service: {
type: 'object',
description: 'Created service',
properties: {
id: { type: 'string', description: 'Service ID' },
name: { type: 'string', description: 'Service name' },
},
},
},
}
@@ -0,0 +1,83 @@
import type {
RailwayDeleteEnvironmentParams,
RailwayDeleteEnvironmentResponse,
} from '@/tools/railway/types'
import {
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayDeleteEnvironmentData {
environmentDelete?: boolean
}
export const railwayDeleteEnvironmentTool: ToolConfig<
RailwayDeleteEnvironmentParams,
RailwayDeleteEnvironmentResponse
> = {
id: 'railway_delete_environment',
name: 'Railway Delete Environment',
description: 'Delete a Railway project environment',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
environmentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway environment ID',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
mutation DeleteEnvironment($id: String!) {
environmentDelete(id: $id)
}
`,
variables: {
id: params.environmentId.trim(),
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayDeleteEnvironmentData>(response)
if (typeof data.data?.environmentDelete !== 'boolean') {
throw new Error('Railway did not return an environment deletion result')
}
return {
success: true,
output: {
success: data.data.environmentDelete,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the environment was deleted',
},
},
}
+83
View File
@@ -0,0 +1,83 @@
import type {
RailwayDeleteProjectParams,
RailwayDeleteProjectResponse,
} from '@/tools/railway/types'
import {
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayDeleteProjectData {
projectDelete?: boolean
}
export const railwayDeleteProjectTool: ToolConfig<
RailwayDeleteProjectParams,
RailwayDeleteProjectResponse
> = {
id: 'railway_delete_project',
name: 'Railway Delete Project',
description: 'Delete a Railway project',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway project ID',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
mutation DeleteProject($id: String!) {
projectDelete(id: $id)
}
`,
variables: {
id: params.projectId.trim(),
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayDeleteProjectData>(response)
if (typeof data.data?.projectDelete !== 'boolean') {
throw new Error('Railway did not return a project deletion result')
}
return {
success: true,
output: {
success: data.data.projectDelete,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the project was deleted',
},
},
}
+83
View File
@@ -0,0 +1,83 @@
import type {
RailwayDeleteServiceParams,
RailwayDeleteServiceResponse,
} from '@/tools/railway/types'
import {
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayDeleteServiceData {
serviceDelete?: boolean
}
export const railwayDeleteServiceTool: ToolConfig<
RailwayDeleteServiceParams,
RailwayDeleteServiceResponse
> = {
id: 'railway_delete_service',
name: 'Railway Delete Service',
description: 'Delete a Railway service and all of its deployments',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
serviceId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway service ID',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
mutation DeleteService($id: String!) {
serviceDelete(id: $id)
}
`,
variables: {
id: params.serviceId.trim(),
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayDeleteServiceData>(response)
if (typeof data.data?.serviceDelete !== 'boolean') {
throw new Error('Railway did not return a service deletion result')
}
return {
success: true,
output: {
success: data.data.serviceDelete,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the service was deleted',
},
},
}
+110
View File
@@ -0,0 +1,110 @@
import { filterUndefined } from '@sim/utils/object'
import type {
RailwayDeleteVariableParams,
RailwayDeleteVariableResponse,
} from '@/tools/railway/types'
import {
optionalString,
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayDeleteVariableData {
variableDelete?: boolean
}
export const railwayDeleteVariableTool: ToolConfig<
RailwayDeleteVariableParams,
RailwayDeleteVariableResponse
> = {
id: 'railway_delete_variable',
name: 'Railway Delete Variable',
description: 'Delete a Railway environment variable',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway project ID',
},
environmentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway environment ID',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Variable name to delete',
},
serviceId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Railway service ID. Omit to delete a shared variable.',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
mutation DeleteVariable($input: VariableDeleteInput!) {
variableDelete(input: $input)
}
`,
variables: {
input: {
projectId: params.projectId.trim(),
environmentId: params.environmentId.trim(),
name: params.name.trim(),
...filterUndefined({
serviceId: optionalString(params.serviceId),
}),
},
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayDeleteVariableData>(response)
if (typeof data.data?.variableDelete !== 'boolean') {
throw new Error('Railway did not return a variable deletion result')
}
return {
success: true,
output: {
success: data.data.variableDelete,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the variable was deleted',
},
},
}
+119
View File
@@ -0,0 +1,119 @@
import type {
RailwayDeployServiceParams,
RailwayDeployServiceResponse,
} from '@/tools/railway/types'
import {
optionalString,
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayDeployServiceData {
serviceInstanceDeployV2?: string
}
export const railwayDeployServiceTool: ToolConfig<
RailwayDeployServiceParams,
RailwayDeployServiceResponse
> = {
id: 'railway_deploy_service',
name: 'Railway Deploy Service',
description: 'Trigger a deployment for a Railway service in an environment',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
serviceId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway service ID',
},
environmentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway environment ID',
},
commitSha: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Specific Git commit SHA to deploy',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => {
const commitSha = optionalString(params.commitSha)
if (commitSha) {
return {
query: `
mutation DeployService($serviceId: String!, $environmentId: String!, $commitSha: String!) {
serviceInstanceDeployV2(
serviceId: $serviceId
environmentId: $environmentId
commitSha: $commitSha
)
}
`,
variables: {
serviceId: params.serviceId.trim(),
environmentId: params.environmentId.trim(),
commitSha,
},
}
}
return {
query: `
mutation DeployService($serviceId: String!, $environmentId: String!) {
serviceInstanceDeployV2(serviceId: $serviceId, environmentId: $environmentId)
}
`,
variables: {
serviceId: params.serviceId.trim(),
environmentId: params.environmentId.trim(),
},
}
},
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayDeployServiceData>(response)
const deploymentId = data.data?.serviceInstanceDeployV2
if (!deploymentId) throw new Error('Railway did not return a deployment ID')
return {
success: true,
output: {
deploymentId,
},
}
},
outputs: {
deploymentId: {
type: 'string',
description: 'Created deployment ID',
},
},
}
+121
View File
@@ -0,0 +1,121 @@
import type {
RailwayDeploymentDetail,
RailwayGetDeploymentParams,
RailwayGetDeploymentResponse,
} from '@/tools/railway/types'
import {
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayGetDeploymentData {
deployment?: {
id: string
status: string
createdAt: string
url?: string | null
staticUrl?: string | null
canRollback?: boolean | null
canRedeploy?: boolean | null
}
}
export const railwayGetDeploymentTool: ToolConfig<
RailwayGetDeploymentParams,
RailwayGetDeploymentResponse
> = {
id: 'railway_get_deployment',
name: 'Railway Get Deployment',
description: 'Get details for a single Railway deployment',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
deploymentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway deployment ID',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
query GetDeployment($id: String!) {
deployment(id: $id) {
id
status
createdAt
url
staticUrl
canRollback
canRedeploy
}
}
`,
variables: {
id: params.deploymentId.trim(),
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayGetDeploymentData>(response)
const deployment = data.data?.deployment
if (!deployment) throw new Error('Railway did not return a deployment')
const detail: RailwayDeploymentDetail = {
id: deployment.id,
status: deployment.status,
createdAt: deployment.createdAt,
url: deployment.url ?? null,
staticUrl: deployment.staticUrl ?? null,
canRollback: deployment.canRollback ?? false,
canRedeploy: deployment.canRedeploy ?? false,
}
return {
success: true,
output: {
deployment: detail,
},
}
},
outputs: {
deployment: {
type: 'object',
description: 'Deployment details',
properties: {
id: { type: 'string', description: 'Deployment ID' },
status: { type: 'string', description: 'Deployment status' },
createdAt: { type: 'string', description: 'Deployment creation timestamp' },
url: { type: 'string', description: 'Deployment URL', optional: true },
staticUrl: { type: 'string', description: 'Static deployment URL', optional: true },
canRollback: {
type: 'boolean',
description: 'Whether the deployment can be rolled back to',
},
canRedeploy: { type: 'boolean', description: 'Whether the deployment can be redeployed' },
},
},
},
}
@@ -0,0 +1,117 @@
import type {
RailwayDeploymentLog,
RailwayGetDeploymentLogsParams,
RailwayGetDeploymentLogsResponse,
} from '@/tools/railway/types'
import {
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayGetDeploymentLogsData {
deploymentLogs?: Array<{
timestamp: string
message: string
severity?: string | null
}>
}
export const railwayGetDeploymentLogsTool: ToolConfig<
RailwayGetDeploymentLogsParams,
RailwayGetDeploymentLogsResponse
> = {
id: 'railway_get_deployment_logs',
name: 'Railway Get Deployment Logs',
description: 'Retrieve runtime logs for a Railway deployment',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
deploymentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway deployment ID',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of log lines to return',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
query DeploymentLogs($deploymentId: String!, $limit: Int) {
deploymentLogs(deploymentId: $deploymentId, limit: $limit) {
timestamp
message
severity
}
}
`,
variables: {
deploymentId: params.deploymentId.trim(),
limit: params.limit ? Number(params.limit) : undefined,
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayGetDeploymentLogsData>(response)
const logEntries = data.data?.deploymentLogs
if (!logEntries) throw new Error('Railway did not return deployment logs')
const logs: RailwayDeploymentLog[] = logEntries.map((log) => ({
timestamp: log.timestamp,
message: log.message,
severity: log.severity ?? null,
}))
return {
success: true,
output: {
logs,
count: logs.length,
},
}
},
outputs: {
logs: {
type: 'array',
description: 'Deployment log entries',
items: {
type: 'object',
properties: {
timestamp: { type: 'string', description: 'Log timestamp' },
message: { type: 'string', description: 'Log message' },
severity: { type: 'string', description: 'Log severity', optional: true },
},
},
},
count: {
type: 'number',
description: 'Number of log entries returned',
},
},
}
+170
View File
@@ -0,0 +1,170 @@
import type {
RailwayGetProjectParams,
RailwayGetProjectResponse,
RailwayProjectEnvironment,
RailwayProjectService,
RailwayProjectSummary,
} from '@/tools/railway/types'
import {
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayGetProjectData {
project?: RailwayProjectSummary & {
services?: {
edges?: Array<{
node?: RailwayProjectService
}>
}
environments?: {
edges?: Array<{
node?: RailwayProjectEnvironment
}>
}
}
}
export const railwayGetProjectTool: ToolConfig<RailwayGetProjectParams, RailwayGetProjectResponse> =
{
id: 'railway_get_project',
name: 'Railway Get Project',
description: 'Get a Railway project with its services and environments',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway project ID',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
query GetProject($id: String!) {
project(id: $id) {
id
name
description
createdAt
updatedAt
services {
edges {
node {
id
name
icon
}
}
}
environments {
edges {
node {
id
name
}
}
}
}
}
`,
variables: { id: params.projectId.trim() },
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayGetProjectData>(response)
const project = data.data?.project
if (!project) throw new Error('Railway did not return a project')
const services = (project.services?.edges ?? [])
.map((edge) => edge.node)
.filter((service): service is RailwayProjectService => Boolean(service))
.map((service) => ({
id: service.id,
name: service.name,
icon: service.icon ?? null,
}))
const environments = (project.environments?.edges ?? [])
.map((edge) => edge.node)
.filter((environment): environment is RailwayProjectEnvironment => Boolean(environment))
.map((environment) => ({
id: environment.id,
name: environment.name,
}))
return {
success: true,
output: {
project: {
id: project.id,
name: project.name,
description: project.description ?? null,
createdAt: project.createdAt,
updatedAt: project.updatedAt ?? null,
services,
environments,
},
},
}
},
outputs: {
project: {
type: 'object',
description: 'Project with services and environments',
properties: {
id: { type: 'string', description: 'Project ID' },
name: { type: 'string', description: 'Project name' },
description: { type: 'string', description: 'Project description', optional: true },
createdAt: { type: 'string', description: 'Project creation timestamp' },
updatedAt: { type: 'string', description: 'Project update timestamp', optional: true },
services: {
type: 'array',
description: 'Project services',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Service ID' },
name: { type: 'string', description: 'Service name' },
icon: { type: 'string', description: 'Service icon', optional: true },
},
},
},
environments: {
type: 'array',
description: 'Project environments',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Environment ID' },
name: { type: 'string', description: 'Environment name' },
},
},
},
},
},
},
}
+45
View File
@@ -0,0 +1,45 @@
import { railwayCreateEnvironmentTool } from '@/tools/railway/create_environment'
import { railwayCreateProjectTool } from '@/tools/railway/create_project'
import { railwayCreateServiceTool } from '@/tools/railway/create_service'
import { railwayDeleteEnvironmentTool } from '@/tools/railway/delete_environment'
import { railwayDeleteProjectTool } from '@/tools/railway/delete_project'
import { railwayDeleteServiceTool } from '@/tools/railway/delete_service'
import { railwayDeleteVariableTool } from '@/tools/railway/delete_variable'
import { railwayDeployServiceTool } from '@/tools/railway/deploy_service'
import { railwayGetDeploymentTool } from '@/tools/railway/get_deployment'
import { railwayGetDeploymentLogsTool } from '@/tools/railway/get_deployment_logs'
import { railwayGetProjectTool } from '@/tools/railway/get_project'
import { railwayListDeploymentsTool } from '@/tools/railway/list_deployments'
import { railwayListProjectMembersTool } from '@/tools/railway/list_project_members'
import { railwayListProjectsTool } from '@/tools/railway/list_projects'
import { railwayListVariablesTool } from '@/tools/railway/list_variables'
import { railwayRestartDeploymentTool } from '@/tools/railway/restart_deployment'
import { railwayRollbackDeploymentTool } from '@/tools/railway/rollback_deployment'
import { railwayTransferProjectTool } from '@/tools/railway/transfer_project'
import { railwayUpdateProjectTool } from '@/tools/railway/update_project'
import { railwayUpsertVariableTool } from '@/tools/railway/upsert_variable'
export {
railwayCreateEnvironmentTool,
railwayCreateProjectTool,
railwayCreateServiceTool,
railwayDeleteEnvironmentTool,
railwayDeleteProjectTool,
railwayDeleteServiceTool,
railwayDeleteVariableTool,
railwayDeployServiceTool,
railwayGetDeploymentTool,
railwayGetDeploymentLogsTool,
railwayGetProjectTool,
railwayListDeploymentsTool,
railwayListProjectMembersTool,
railwayListProjectsTool,
railwayListVariablesTool,
railwayRestartDeploymentTool,
railwayRollbackDeploymentTool,
railwayTransferProjectTool,
railwayUpdateProjectTool,
railwayUpsertVariableTool,
}
export * from '@/tools/railway/types'
+184
View File
@@ -0,0 +1,184 @@
import type {
RailwayDeploymentSummary,
RailwayListDeploymentsParams,
RailwayListDeploymentsResponse,
RailwayPageInfo,
} from '@/tools/railway/types'
import {
optionalString,
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayListDeploymentsData {
deployments?: {
edges?: Array<{
node?: RailwayDeploymentSummary
}>
pageInfo?: RailwayPageInfo
}
}
export const railwayListDeploymentsTool: ToolConfig<
RailwayListDeploymentsParams,
RailwayListDeploymentsResponse
> = {
id: 'railway_list_deployments',
name: 'Railway List Deployments',
description: 'List deployments for a Railway service in an environment',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway project ID',
},
serviceId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway service ID',
},
environmentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway environment ID',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of deployments to return',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
query ListDeployments($input: DeploymentListInput!, $first: Int, $after: String) {
deployments(input: $input, first: $first, after: $after) {
edges {
node {
id
status
createdAt
url
staticUrl
canRollback
canRedeploy
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
input: {
projectId: params.projectId.trim(),
serviceId: params.serviceId.trim(),
environmentId: params.environmentId.trim(),
},
first: params.first ? Number(params.first) : 10,
after: optionalString(params.after),
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayListDeploymentsData>(response)
const deploymentConnection = data.data?.deployments
if (!deploymentConnection) throw new Error('Railway did not return deployments')
const deployments = (deploymentConnection.edges ?? [])
.map((edge) => edge.node)
.filter((deployment): deployment is RailwayDeploymentSummary => Boolean(deployment))
.map((deployment) => ({
id: deployment.id,
status: deployment.status,
createdAt: deployment.createdAt,
url: deployment.url ?? null,
staticUrl: deployment.staticUrl ?? null,
canRollback: deployment.canRollback ?? false,
canRedeploy: deployment.canRedeploy ?? false,
}))
return {
success: true,
output: {
deployments,
pageInfo: {
hasNextPage: deploymentConnection.pageInfo?.hasNextPage ?? false,
endCursor: deploymentConnection.pageInfo?.endCursor ?? null,
},
count: deployments.length,
},
}
},
outputs: {
deployments: {
type: 'array',
description: 'Service deployments',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Deployment ID' },
status: { type: 'string', description: 'Deployment status' },
createdAt: { type: 'string', description: 'Deployment creation timestamp' },
url: { type: 'string', description: 'Deployment URL', optional: true },
staticUrl: { type: 'string', description: 'Static deployment URL', optional: true },
canRollback: {
type: 'boolean',
description: 'Whether this deployment can be rolled back to',
},
canRedeploy: {
type: 'boolean',
description: 'Whether this deployment can be redeployed',
},
},
},
},
count: {
type: 'number',
description: 'Number of deployments returned',
},
pageInfo: {
type: 'object',
description: 'Pagination information',
properties: {
hasNextPage: { type: 'boolean', description: 'Whether more deployments are available' },
endCursor: { type: 'string', description: 'Cursor for the next page', optional: true },
},
},
},
}
@@ -0,0 +1,112 @@
import type {
RailwayListProjectMembersParams,
RailwayListProjectMembersResponse,
RailwayProjectMember,
} from '@/tools/railway/types'
import {
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayListProjectMembersData {
projectMembers?: RailwayProjectMember[]
}
export const railwayListProjectMembersTool: ToolConfig<
RailwayListProjectMembersParams,
RailwayListProjectMembersResponse
> = {
id: 'railway_list_project_members',
name: 'Railway List Project Members',
description: 'List members for a Railway project',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway project ID',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
query ProjectMembers($projectId: String!) {
projectMembers(projectId: $projectId) {
id
role
name
email
avatar
}
}
`,
variables: {
projectId: params.projectId.trim(),
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayListProjectMembersData>(response)
const projectMembers = data.data?.projectMembers
if (!projectMembers) throw new Error('Railway did not return project members')
const members = projectMembers.map((member) => ({
id: member.id,
role: member.role,
name: member.name ?? null,
email: member.email ?? null,
avatar: member.avatar ?? null,
}))
return {
success: true,
output: {
members,
count: members.length,
},
}
},
outputs: {
members: {
type: 'array',
description: 'Project members',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Member user ID' },
role: { type: 'string', description: 'Project role' },
name: { type: 'string', description: 'Member name', optional: true },
email: { type: 'string', description: 'Member email', optional: true },
avatar: { type: 'string', description: 'Member avatar URL', optional: true },
},
},
},
count: {
type: 'number',
description: 'Number of members returned',
},
},
}
+157
View File
@@ -0,0 +1,157 @@
import { filterUndefined } from '@sim/utils/object'
import type {
RailwayListProjectsParams,
RailwayListProjectsResponse,
RailwayPageInfo,
RailwayProjectSummary,
} from '@/tools/railway/types'
import {
optionalString,
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayListProjectsData {
projects?: {
edges?: Array<{
node?: RailwayProjectSummary
}>
pageInfo?: RailwayPageInfo
}
}
export const railwayListProjectsTool: ToolConfig<
RailwayListProjectsParams,
RailwayListProjectsResponse
> = {
id: 'railway_list_projects',
name: 'Railway List Projects',
description: 'List Railway projects visible to the provided token',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
workspaceId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Workspace ID to list projects from',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of projects to return',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
query ListProjects($workspaceId: String, $first: Int, $after: String) {
projects(workspaceId: $workspaceId, first: $first, after: $after) {
edges {
node {
id
name
description
createdAt
updatedAt
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: filterUndefined({
workspaceId: optionalString(params.workspaceId),
first: params.first ? Number(params.first) : undefined,
after: optionalString(params.after),
}),
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayListProjectsData>(response)
const projectConnection = data.data?.projects
if (!projectConnection) throw new Error('Railway did not return projects')
const projects = (projectConnection.edges ?? [])
.map((edge) => edge.node)
.filter((project): project is RailwayProjectSummary => Boolean(project))
.map((project) => ({
id: project.id,
name: project.name,
description: project.description ?? null,
createdAt: project.createdAt,
updatedAt: project.updatedAt ?? null,
}))
return {
success: true,
output: {
projects,
pageInfo: {
hasNextPage: projectConnection.pageInfo?.hasNextPage ?? false,
endCursor: projectConnection.pageInfo?.endCursor ?? null,
},
count: projects.length,
},
}
},
outputs: {
projects: {
type: 'array',
description: 'Railway projects',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Project ID' },
name: { type: 'string', description: 'Project name' },
description: { type: 'string', description: 'Project description', optional: true },
createdAt: { type: 'string', description: 'Project creation timestamp' },
updatedAt: { type: 'string', description: 'Project update timestamp', optional: true },
},
},
},
pageInfo: {
type: 'object',
description: 'Pagination information',
properties: {
hasNextPage: { type: 'boolean', description: 'Whether more projects are available' },
endCursor: { type: 'string', description: 'Cursor for the next page', optional: true },
},
},
count: {
type: 'number',
description: 'Number of projects returned',
},
},
}
+103
View File
@@ -0,0 +1,103 @@
import { filterUndefined } from '@sim/utils/object'
import type {
RailwayListVariablesParams,
RailwayListVariablesResponse,
} from '@/tools/railway/types'
import {
optionalString,
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayListVariablesData {
variables?: Record<string, string>
}
export const railwayListVariablesTool: ToolConfig<
RailwayListVariablesParams,
RailwayListVariablesResponse
> = {
id: 'railway_list_variables',
name: 'Railway List Variables',
description: 'List Railway environment variables for a service or shared environment',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway project ID',
},
environmentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway environment ID',
},
serviceId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Railway service ID. Omit for shared environment variables.',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
query Variables($projectId: String!, $environmentId: String!, $serviceId: String) {
variables(projectId: $projectId, environmentId: $environmentId, serviceId: $serviceId)
}
`,
variables: filterUndefined({
projectId: params.projectId.trim(),
environmentId: params.environmentId.trim(),
serviceId: optionalString(params.serviceId),
}),
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayListVariablesData>(response)
const variables = data.data?.variables
if (!variables) throw new Error('Railway did not return variables')
return {
success: true,
output: {
variables,
count: Object.keys(variables).length,
},
}
},
outputs: {
variables: {
type: 'object',
description: 'Variable names and values',
},
count: {
type: 'number',
description: 'Number of variables returned',
},
},
}
@@ -0,0 +1,83 @@
import type {
RailwayRestartDeploymentParams,
RailwayRestartDeploymentResponse,
} from '@/tools/railway/types'
import {
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayRestartDeploymentData {
deploymentRestart?: boolean
}
export const railwayRestartDeploymentTool: ToolConfig<
RailwayRestartDeploymentParams,
RailwayRestartDeploymentResponse
> = {
id: 'railway_restart_deployment',
name: 'Railway Restart Deployment',
description: 'Restart a running Railway deployment without rebuilding',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
deploymentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway deployment ID',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
mutation RestartDeployment($id: String!) {
deploymentRestart(id: $id)
}
`,
variables: {
id: params.deploymentId.trim(),
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayRestartDeploymentData>(response)
if (typeof data.data?.deploymentRestart !== 'boolean') {
throw new Error('Railway did not return a deployment restart result')
}
return {
success: true,
output: {
success: data.data.deploymentRestart,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the deployment was restarted',
},
},
}
@@ -0,0 +1,83 @@
import type {
RailwayRollbackDeploymentParams,
RailwayRollbackDeploymentResponse,
} from '@/tools/railway/types'
import {
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayRollbackDeploymentData {
deploymentRollback?: boolean
}
export const railwayRollbackDeploymentTool: ToolConfig<
RailwayRollbackDeploymentParams,
RailwayRollbackDeploymentResponse
> = {
id: 'railway_rollback_deployment',
name: 'Railway Rollback Deployment',
description: 'Roll a Railway service back to a previous deployment',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
deploymentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway deployment ID to roll back to (must have canRollback)',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
mutation RollbackDeployment($id: String!) {
deploymentRollback(id: $id)
}
`,
variables: {
id: params.deploymentId.trim(),
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayRollbackDeploymentData>(response)
if (typeof data.data?.deploymentRollback !== 'boolean') {
throw new Error('Railway did not return a rollback result')
}
return {
success: true,
output: {
success: data.data.deploymentRollback,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the rollback was triggered',
},
},
}
@@ -0,0 +1,92 @@
import type {
RailwayTransferProjectParams,
RailwayTransferProjectResponse,
} from '@/tools/railway/types'
import {
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayTransferProjectData {
projectTransfer?: boolean
}
export const railwayTransferProjectTool: ToolConfig<
RailwayTransferProjectParams,
RailwayTransferProjectResponse
> = {
id: 'railway_transfer_project',
name: 'Railway Transfer Project',
description: 'Transfer a Railway project to another workspace',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway project ID',
},
workspaceId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Destination workspace ID',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
mutation TransferProject($projectId: String!, $input: ProjectTransferInput!) {
projectTransfer(projectId: $projectId, input: $input)
}
`,
variables: {
projectId: params.projectId.trim(),
input: {
workspaceId: params.workspaceId.trim(),
},
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayTransferProjectData>(response)
if (typeof data.data?.projectTransfer !== 'boolean') {
throw new Error('Railway did not return a project transfer result')
}
return {
success: true,
output: {
success: data.data.projectTransfer,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the project was transferred',
},
},
}
+347
View File
@@ -0,0 +1,347 @@
import type { ToolResponse } from '@/tools/types'
export type RailwayTokenType = 'account' | 'workspace' | 'project' | 'oauth'
export interface RailwayAuthParams {
apiKey: string
tokenType?: RailwayTokenType
}
export interface RailwayPageInfo {
hasNextPage: boolean
endCursor: string | null
}
export interface RailwayProjectSummary {
id: string
name: string
description: string | null
createdAt: string
updatedAt?: string | null
}
export interface RailwayUpdatedProject {
id: string
name: string
description: string | null
}
export interface RailwayProjectService {
id: string
name: string
icon: string | null
}
export interface RailwayProjectEnvironment {
id: string
name: string
}
export interface RailwayProjectMember {
id: string
role: string
name: string | null
email: string | null
avatar: string | null
}
export interface RailwayCreatedResource {
id: string
name: string
}
export interface RailwayDeploymentSummary {
id: string
status: string
createdAt: string
url: string | null
staticUrl: string | null
canRollback: boolean
canRedeploy: boolean
}
export interface RailwayListProjectsParams extends RailwayAuthParams {
workspaceId?: string
first?: number
after?: string
}
export interface RailwayGetProjectParams extends RailwayAuthParams {
projectId: string
}
export interface RailwayCreateProjectParams extends RailwayAuthParams {
name: string
description?: string
workspaceId?: string
isPublic?: boolean
defaultEnvironmentName?: string
prDeploys?: boolean
}
export interface RailwayUpdateProjectParams extends RailwayAuthParams {
projectId: string
name?: string
description?: string
isPublic?: boolean
prDeploys?: boolean
}
export interface RailwayDeleteProjectParams extends RailwayAuthParams {
projectId: string
}
export interface RailwayTransferProjectParams extends RailwayAuthParams {
projectId: string
workspaceId: string
}
export interface RailwayListProjectMembersParams extends RailwayAuthParams {
projectId: string
}
export interface RailwayCreateEnvironmentParams extends RailwayAuthParams {
projectId: string
name: string
sourceEnvironmentId?: string
ephemeral?: boolean
skipInitialDeploys?: boolean
stageInitialChanges?: boolean
}
export interface RailwayDeleteEnvironmentParams extends RailwayAuthParams {
environmentId: string
}
export interface RailwayListDeploymentsParams extends RailwayAuthParams {
projectId: string
serviceId: string
environmentId: string
first?: number
after?: string
}
export interface RailwayDeployServiceParams extends RailwayAuthParams {
serviceId: string
environmentId: string
commitSha?: string
}
export interface RailwayCreateServiceParams extends RailwayAuthParams {
projectId: string
name: string
repo?: string
image?: string
branch?: string
}
export interface RailwayDeleteServiceParams extends RailwayAuthParams {
serviceId: string
}
export interface RailwayGetDeploymentParams extends RailwayAuthParams {
deploymentId: string
}
export interface RailwayRestartDeploymentParams extends RailwayAuthParams {
deploymentId: string
}
export interface RailwayRollbackDeploymentParams extends RailwayAuthParams {
deploymentId: string
}
export interface RailwayGetDeploymentLogsParams extends RailwayAuthParams {
deploymentId: string
limit?: number
}
export interface RailwayDeleteVariableParams extends RailwayAuthParams {
projectId: string
environmentId: string
name: string
serviceId?: string
}
export interface RailwayListVariablesParams extends RailwayAuthParams {
projectId: string
environmentId: string
serviceId?: string
}
export interface RailwayUpsertVariableParams extends RailwayAuthParams {
projectId: string
environmentId: string
name: string
value: string
serviceId?: string
skipDeploys?: boolean
}
export interface RailwayListProjectsResponse extends ToolResponse {
output: {
projects: RailwayProjectSummary[]
pageInfo: RailwayPageInfo
count: number
}
}
export interface RailwayGetProjectResponse extends ToolResponse {
output: {
project: RailwayProjectSummary & {
services: RailwayProjectService[]
environments: RailwayProjectEnvironment[]
}
}
}
export interface RailwayCreateProjectResponse extends ToolResponse {
output: {
project: RailwayCreatedResource
}
}
export interface RailwayUpdateProjectResponse extends ToolResponse {
output: {
project: RailwayUpdatedProject
}
}
export interface RailwayDeleteProjectResponse extends ToolResponse {
output: {
success: boolean
}
}
export interface RailwayTransferProjectResponse extends ToolResponse {
output: {
success: boolean
}
}
export interface RailwayListProjectMembersResponse extends ToolResponse {
output: {
members: RailwayProjectMember[]
count: number
}
}
export interface RailwayCreateEnvironmentResponse extends ToolResponse {
output: {
environment: RailwayCreatedResource
}
}
export interface RailwayDeleteEnvironmentResponse extends ToolResponse {
output: {
success: boolean
}
}
export interface RailwayListDeploymentsResponse extends ToolResponse {
output: {
deployments: RailwayDeploymentSummary[]
pageInfo: RailwayPageInfo
count: number
}
}
export interface RailwayDeployServiceResponse extends ToolResponse {
output: {
deploymentId: string
}
}
export interface RailwayCreateServiceResponse extends ToolResponse {
output: {
service: RailwayCreatedResource
}
}
export interface RailwayDeleteServiceResponse extends ToolResponse {
output: {
success: boolean
}
}
export interface RailwayDeploymentDetail {
id: string
status: string
createdAt: string
url: string | null
staticUrl: string | null
canRollback: boolean
canRedeploy: boolean
}
export interface RailwayGetDeploymentResponse extends ToolResponse {
output: {
deployment: RailwayDeploymentDetail
}
}
export interface RailwayRestartDeploymentResponse extends ToolResponse {
output: {
success: boolean
}
}
export interface RailwayRollbackDeploymentResponse extends ToolResponse {
output: {
success: boolean
}
}
export interface RailwayDeploymentLog {
timestamp: string
message: string
severity: string | null
}
export interface RailwayGetDeploymentLogsResponse extends ToolResponse {
output: {
logs: RailwayDeploymentLog[]
count: number
}
}
export interface RailwayDeleteVariableResponse extends ToolResponse {
output: {
success: boolean
}
}
export interface RailwayListVariablesResponse extends ToolResponse {
output: {
variables: Record<string, string>
count: number
}
}
export interface RailwayUpsertVariableResponse extends ToolResponse {
output: {
success: boolean
}
}
export type RailwayResponse =
| RailwayListProjectsResponse
| RailwayGetProjectResponse
| RailwayCreateProjectResponse
| RailwayUpdateProjectResponse
| RailwayDeleteProjectResponse
| RailwayTransferProjectResponse
| RailwayListProjectMembersResponse
| RailwayCreateEnvironmentResponse
| RailwayDeleteEnvironmentResponse
| RailwayListDeploymentsResponse
| RailwayDeployServiceResponse
| RailwayCreateServiceResponse
| RailwayDeleteServiceResponse
| RailwayGetDeploymentResponse
| RailwayRestartDeploymentResponse
| RailwayRollbackDeploymentResponse
| RailwayGetDeploymentLogsResponse
| RailwayListVariablesResponse
| RailwayUpsertVariableResponse
| RailwayDeleteVariableResponse
+128
View File
@@ -0,0 +1,128 @@
import { filterUndefined } from '@sim/utils/object'
import type {
RailwayUpdatedProject,
RailwayUpdateProjectParams,
RailwayUpdateProjectResponse,
} from '@/tools/railway/types'
import {
optionalString,
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayUpdateProjectData {
projectUpdate?: RailwayUpdatedProject
}
export const railwayUpdateProjectTool: ToolConfig<
RailwayUpdateProjectParams,
RailwayUpdateProjectResponse
> = {
id: 'railway_update_project',
name: 'Railway Update Project',
description: 'Update a Railway project name or description',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway project ID',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated project name',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated project description',
},
isPublic: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the project should be publicly visible',
},
prDeploys: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to enable pull request deploy environments',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
mutation UpdateProject($id: String!, $input: ProjectUpdateInput!) {
projectUpdate(id: $id, input: $input) {
id
name
description
}
}
`,
variables: {
id: params.projectId.trim(),
input: filterUndefined({
name: optionalString(params.name),
description: optionalString(params.description),
isPublic: params.isPublic,
prDeploys: params.prDeploys,
}),
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayUpdateProjectData>(response)
const project = data.data?.projectUpdate
if (!project) throw new Error('Railway did not return an updated project')
return {
success: true,
output: {
project: {
id: project.id,
name: project.name,
description: project.description ?? null,
},
},
}
},
outputs: {
project: {
type: 'object',
description: 'Updated project',
properties: {
id: { type: 'string', description: 'Project ID' },
name: { type: 'string', description: 'Project name' },
description: { type: 'string', description: 'Project description', optional: true },
},
},
},
}
+124
View File
@@ -0,0 +1,124 @@
import { filterUndefined } from '@sim/utils/object'
import type {
RailwayUpsertVariableParams,
RailwayUpsertVariableResponse,
} from '@/tools/railway/types'
import {
optionalString,
parseRailwayGraphqlResponse,
RAILWAY_GRAPHQL_URL,
railwayHeaders,
} from '@/tools/railway/utils'
import type { ToolConfig } from '@/tools/types'
interface RailwayUpsertVariableData {
variableUpsert?: boolean
}
export const railwayUpsertVariableTool: ToolConfig<
RailwayUpsertVariableParams,
RailwayUpsertVariableResponse
> = {
id: 'railway_upsert_variable',
name: 'Railway Upsert Variable',
description: 'Create or update a Railway environment variable',
version: '1.0.0',
params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Railway API token',
},
tokenType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Railway token type. Use "account" for account, workspace, or OAuth tokens, or "project" for project tokens.',
},
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway project ID',
},
environmentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Railway environment ID',
},
serviceId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Railway service ID. Omit to create or update a shared variable.',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Variable name',
},
value: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Variable value',
},
skipDeploys: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to skip automatic redeploys after changing the variable',
},
},
request: {
url: RAILWAY_GRAPHQL_URL,
method: 'POST',
headers: (params) => railwayHeaders(params.apiKey, params.tokenType),
body: (params) => ({
query: `
mutation UpsertVariable($input: VariableUpsertInput!) {
variableUpsert(input: $input)
}
`,
variables: {
input: {
projectId: params.projectId.trim(),
environmentId: params.environmentId.trim(),
name: params.name.trim(),
value: params.value,
...filterUndefined({
serviceId: optionalString(params.serviceId),
skipDeploys: params.skipDeploys,
}),
},
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseRailwayGraphqlResponse<RailwayUpsertVariableData>(response)
if (typeof data.data?.variableUpsert !== 'boolean') {
throw new Error('Railway did not return a variable upsert result')
}
return {
success: true,
output: {
success: data.data.variableUpsert,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the variable was created or updated',
},
},
}
+54
View File
@@ -0,0 +1,54 @@
import type { RailwayTokenType } from '@/tools/railway/types'
export const RAILWAY_GRAPHQL_URL = 'https://backboard.railway.com/graphql/v2'
interface RailwayGraphqlError {
message?: string
}
interface RailwayGraphqlResponse<TData> {
data?: TData
errors?: RailwayGraphqlError[]
}
export function railwayHeaders(
apiKey: string,
tokenType?: RailwayTokenType
): Record<string, string> {
if (!apiKey) {
throw new Error('Missing API token for Railway API request')
}
if (tokenType === 'project') {
return {
'Content-Type': 'application/json',
'Project-Access-Token': apiKey,
}
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
}
}
export async function parseRailwayGraphqlResponse<TData>(
response: Response
): Promise<RailwayGraphqlResponse<TData>> {
const data = (await response.json()) as RailwayGraphqlResponse<TData>
if (!response.ok) {
throw new Error(data.errors?.[0]?.message ?? `HTTP ${response.status}: ${response.statusText}`)
}
if (data.errors?.length) {
throw new Error(data.errors[0]?.message ?? 'Railway API returned a GraphQL error')
}
return data
}
export function optionalString(value?: string): string | undefined {
const trimmed = value?.trim()
return trimmed ? trimmed : undefined
}