@framers/agentos-ext-cloud-railway 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/manifest.json CHANGED
@@ -2,16 +2,16 @@
2
2
  "$schema": "https://agentos.sh/schemas/extension-manifest-v1.json",
3
3
  "id": "com.framers.cloud.railway",
4
4
  "name": "Railway Cloud Deployment",
5
- "version": "0.1.0",
5
+ "version": "0.1.2",
6
6
  "description": "Deploy services to Railway, manage projects and environments, provision database plugins (Postgres, Redis, MySQL, MongoDB), and stream deployment logs via the Railway GraphQL API.",
7
7
  "author": {
8
8
  "name": "Frame.dev",
9
9
  "email": "support@frame.dev",
10
- "url": "https://github.com/framersai"
10
+ "url": "https://github.com/framerslab"
11
11
  },
12
- "license": "MIT",
12
+ "license": "Apache-2.0",
13
13
  "keywords": ["cloud", "railway", "deploy", "paas", "graphql", "database"],
14
- "agentosVersion": "^2.0.0",
14
+ "agentosVersion": ">=0.10.40",
15
15
  "categories": ["tool", "cloud"],
16
16
  "platforms": [],
17
17
  "sdk": "none",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@framers/agentos-ext-cloud-railway",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Railway cloud deployment extension — deploy services, manage projects, provision databases, and retrieve logs via the Railway GraphQL API.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -35,29 +35,37 @@
35
35
  "url": "https://github.com/jddunn"
36
36
  }
37
37
  ],
38
- "license": "MIT",
38
+ "license": "Apache-2.0",
39
39
  "repository": {
40
40
  "type": "git",
41
- "url": "https://github.com/framersai/agentos-extensions.git",
41
+ "url": "git+https://github.com/framerslab/agentos-extensions.git",
42
42
  "directory": "registry/curated/cloud/railway"
43
43
  },
44
44
  "publishConfig": {
45
45
  "access": "public"
46
46
  },
47
47
  "peerDependencies": {
48
- "@framers/agentos": "^0.5.2"
48
+ "@framers/agentos": ">=0.10.40"
49
49
  },
50
50
  "devDependencies": {
51
- "@framers/agentos": "^0.6.6",
52
51
  "@types/node": "^20.12.12",
53
52
  "rimraf": "^5.0.7",
54
53
  "typescript": "^5.4.5",
55
- "vitest": "^1.6.0"
54
+ "vitest": "^3.2.7",
55
+ "@framers/agentos": "^0.13.40"
56
56
  },
57
+ "homepage": "https://github.com/framerslab/agentos-extensions/tree/master/registry/curated/cloud/railway#readme",
58
+ "bugs": {
59
+ "url": "https://github.com/framerslab/agentos-extensions/issues"
60
+ },
61
+ "files": [
62
+ "dist",
63
+ "manifest.json"
64
+ ],
57
65
  "scripts": {
58
66
  "build": "tsc",
59
67
  "dev": "tsc --watch",
60
- "test": "vitest run",
68
+ "test": "vitest run --passWithNoTests",
61
69
  "test:watch": "vitest",
62
70
  "typecheck": "tsc --noEmit",
63
71
  "clean": "rimraf dist"
@@ -1,415 +0,0 @@
1
- // @ts-nocheck
2
- /**
3
- * @fileoverview Railway GraphQL API service layer.
4
- *
5
- * Wraps the Railway GraphQL API v2 for project management, service deployment,
6
- * database plugin provisioning, and deployment log retrieval.
7
- */
8
-
9
- // ---------------------------------------------------------------------------
10
- // Types
11
- // ---------------------------------------------------------------------------
12
-
13
- export interface RailwayConfig {
14
- /** Railway API token (from https://railway.com/account/tokens) */
15
- token: string;
16
- /** GraphQL API base URL (defaults to https://backboard.railway.com/graphql/v2) */
17
- baseUrl?: string;
18
- }
19
-
20
- export interface RailwayProject {
21
- id: string;
22
- name: string;
23
- description: string;
24
- createdAt: string;
25
- updatedAt: string;
26
- environments: RailwayEnvironment[];
27
- services: RailwayService_[];
28
- }
29
-
30
- export interface RailwayEnvironment {
31
- id: string;
32
- name: string;
33
- isEphemeral: boolean;
34
- }
35
-
36
- /** Underscore suffix to avoid clash with the service class name. */
37
- export interface RailwayService_ {
38
- id: string;
39
- name: string;
40
- projectId: string;
41
- createdAt: string;
42
- updatedAt: string;
43
- icon?: string;
44
- }
45
-
46
- export interface RailwayDeployment {
47
- id: string;
48
- serviceId: string;
49
- environmentId: string;
50
- status: string;
51
- createdAt: string;
52
- url?: string;
53
- staticUrl?: string;
54
- }
55
-
56
- export interface RailwayPlugin {
57
- id: string;
58
- name: string;
59
- status: string;
60
- friendlyName: string;
61
- }
62
-
63
- export interface RailwayLogEntry {
64
- timestamp: string;
65
- message: string;
66
- severity: string;
67
- }
68
-
69
- // ---------------------------------------------------------------------------
70
- // Service
71
- // ---------------------------------------------------------------------------
72
-
73
- export class RailwayService {
74
- private config: RailwayConfig;
75
- private running = false;
76
-
77
- constructor(config: RailwayConfig) {
78
- this.config = {
79
- ...config,
80
- baseUrl: config.baseUrl ?? 'https://backboard.railway.com/graphql/v2',
81
- };
82
- }
83
-
84
- async initialize(): Promise<void> {
85
- // Validate token by fetching current user
86
- const result = await this.gql<{ me: { id: string } }>(`query { me { id name email } }`);
87
- if (!result.me?.id) {
88
- throw new Error('Railway auth failed: could not retrieve user');
89
- }
90
- this.running = true;
91
- }
92
-
93
- async shutdown(): Promise<void> {
94
- this.running = false;
95
- }
96
-
97
- get isRunning(): boolean {
98
- return this.running;
99
- }
100
-
101
- // ── Projects ─────────────────────────────────────────────────────────────
102
-
103
- /** List all projects. */
104
- async listProjects(): Promise<RailwayProject[]> {
105
- const result = await this.gql<{ projects: { edges: Array<{ node: any }> } }>(`
106
- query {
107
- projects {
108
- edges {
109
- node {
110
- id
111
- name
112
- description
113
- createdAt
114
- updatedAt
115
- environments {
116
- edges { node { id name isEphemeral } }
117
- }
118
- services {
119
- edges { node { id name projectId createdAt updatedAt icon } }
120
- }
121
- }
122
- }
123
- }
124
- }
125
- `);
126
-
127
- return (result.projects?.edges ?? []).map((e: any) => this.mapProject(e.node));
128
- }
129
-
130
- /** Get a single project by ID. */
131
- async getProject(projectId: string): Promise<RailwayProject> {
132
- const result = await this.gql<{ project: any }>(`
133
- query($id: String!) {
134
- project(id: $id) {
135
- id
136
- name
137
- description
138
- createdAt
139
- updatedAt
140
- environments {
141
- edges { node { id name isEphemeral } }
142
- }
143
- services {
144
- edges { node { id name projectId createdAt updatedAt icon } }
145
- }
146
- }
147
- }
148
- `, { id: projectId });
149
-
150
- return this.mapProject(result.project);
151
- }
152
-
153
- /** Create a new project. */
154
- async createProject(name: string, description?: string): Promise<RailwayProject> {
155
- const result = await this.gql<{ projectCreate: any }>(`
156
- mutation($input: ProjectCreateInput!) {
157
- projectCreate(input: $input) {
158
- id
159
- name
160
- description
161
- createdAt
162
- updatedAt
163
- environments {
164
- edges { node { id name isEphemeral } }
165
- }
166
- services {
167
- edges { node { id name projectId createdAt updatedAt icon } }
168
- }
169
- }
170
- }
171
- `, { input: { name, description: description ?? '' } });
172
-
173
- return this.mapProject(result.projectCreate);
174
- }
175
-
176
- // ── Services ─────────────────────────────────────────────────────────────
177
-
178
- /** Create a service in a project from a GitHub repo. */
179
- async createService(projectId: string, opts: {
180
- name?: string;
181
- source?: { repo: string };
182
- variables?: Record<string, string>;
183
- }): Promise<RailwayService_> {
184
- const result = await this.gql<{ serviceCreate: any }>(`
185
- mutation($input: ServiceCreateInput!) {
186
- serviceCreate(input: $input) {
187
- id
188
- name
189
- projectId
190
- createdAt
191
- updatedAt
192
- }
193
- }
194
- `, {
195
- input: {
196
- projectId,
197
- name: opts.name,
198
- source: opts.source ? { repo: opts.source.repo } : undefined,
199
- },
200
- });
201
-
202
- const service = result.serviceCreate;
203
-
204
- // Set env variables if provided
205
- if (opts.variables && Object.keys(opts.variables).length > 0) {
206
- // Get the production environment
207
- const project = await this.getProject(projectId);
208
- const prodEnv = project.environments.find(e => !e.isEphemeral) ?? project.environments[0];
209
- if (prodEnv) {
210
- await this.upsertVariables(service.id, prodEnv.id, opts.variables);
211
- }
212
- }
213
-
214
- return {
215
- id: service.id,
216
- name: service.name ?? '',
217
- projectId: service.projectId ?? projectId,
218
- createdAt: service.createdAt ?? '',
219
- updatedAt: service.updatedAt ?? '',
220
- };
221
- }
222
-
223
- /** Trigger a redeployment for a service in a given environment. */
224
- async redeployService(serviceId: string, environmentId: string): Promise<RailwayDeployment> {
225
- const result = await this.gql<{ serviceInstanceRedeploy: boolean }>(`
226
- mutation($serviceId: String!, $environmentId: String!) {
227
- serviceInstanceRedeploy(serviceId: $serviceId, environmentId: $environmentId)
228
- }
229
- `, { serviceId, environmentId });
230
-
231
- // The redeploy mutation returns a boolean; fetch latest deployment
232
- return this.getLatestDeployment(serviceId, environmentId);
233
- }
234
-
235
- /** Set environment variables for a service instance. */
236
- async upsertVariables(serviceId: string, environmentId: string, variables: Record<string, string>): Promise<boolean> {
237
- const result = await this.gql<{ variableCollectionUpsert: boolean }>(`
238
- mutation($input: VariableCollectionUpsertInput!) {
239
- variableCollectionUpsert(input: $input)
240
- }
241
- `, {
242
- input: {
243
- serviceId,
244
- environmentId,
245
- variables,
246
- },
247
- });
248
-
249
- return result.variableCollectionUpsert ?? false;
250
- }
251
-
252
- // ── Plugins (Databases) ──────────────────────────────────────────────────
253
-
254
- /** Create a database plugin in a project. */
255
- async createPlugin(projectId: string, plugin: 'postgresql' | 'redis' | 'mysql' | 'mongodb'): Promise<RailwayPlugin> {
256
- const result = await this.gql<{ pluginCreate: any }>(`
257
- mutation($input: PluginCreateInput!) {
258
- pluginCreate(input: $input) {
259
- id
260
- name
261
- status
262
- friendlyName
263
- }
264
- }
265
- `, {
266
- input: {
267
- projectId,
268
- name: plugin,
269
- },
270
- });
271
-
272
- const p = result.pluginCreate;
273
- return {
274
- id: p.id,
275
- name: p.name ?? plugin,
276
- status: p.status ?? 'provisioning',
277
- friendlyName: p.friendlyName ?? plugin,
278
- };
279
- }
280
-
281
- // ── Deployments ──────────────────────────────────────────────────────────
282
-
283
- /** Get the latest deployment for a service in an environment. */
284
- async getLatestDeployment(serviceId: string, environmentId: string): Promise<RailwayDeployment> {
285
- const result = await this.gql<{ deployments: { edges: Array<{ node: any }> } }>(`
286
- query($input: DeploymentListInput!) {
287
- deployments(input: $input, first: 1) {
288
- edges {
289
- node {
290
- id
291
- serviceId
292
- environmentId
293
- status
294
- createdAt
295
- staticUrl
296
- }
297
- }
298
- }
299
- }
300
- `, {
301
- input: { serviceId, environmentId },
302
- });
303
-
304
- const edge = result.deployments?.edges?.[0];
305
- if (!edge) {
306
- return {
307
- id: '',
308
- serviceId,
309
- environmentId,
310
- status: 'unknown',
311
- createdAt: '',
312
- };
313
- }
314
-
315
- const d = edge.node;
316
- return {
317
- id: d.id,
318
- serviceId: d.serviceId ?? serviceId,
319
- environmentId: d.environmentId ?? environmentId,
320
- status: d.status ?? 'unknown',
321
- createdAt: d.createdAt ?? '',
322
- url: d.url,
323
- staticUrl: d.staticUrl,
324
- };
325
- }
326
-
327
- // ── Logs ─────────────────────────────────────────────────────────────────
328
-
329
- /** Get deployment logs. */
330
- async getDeploymentLogs(deploymentId: string): Promise<RailwayLogEntry[]> {
331
- const result = await this.gql<{ deploymentLogs: any[] }>(`
332
- query($deploymentId: String!) {
333
- deploymentLogs(deploymentId: $deploymentId) {
334
- timestamp
335
- message
336
- severity
337
- }
338
- }
339
- `, { deploymentId });
340
-
341
- return (result.deploymentLogs ?? []).map((l: any) => ({
342
- timestamp: l.timestamp ?? '',
343
- message: l.message ?? '',
344
- severity: l.severity ?? 'info',
345
- }));
346
- }
347
-
348
- /** Get build logs for a deployment. */
349
- async getBuildLogs(deploymentId: string): Promise<RailwayLogEntry[]> {
350
- const result = await this.gql<{ buildLogs: any[] }>(`
351
- query($deploymentId: String!) {
352
- buildLogs(deploymentId: $deploymentId) {
353
- timestamp
354
- message
355
- severity
356
- }
357
- }
358
- `, { deploymentId });
359
-
360
- return (result.buildLogs ?? []).map((l: any) => ({
361
- timestamp: l.timestamp ?? '',
362
- message: l.message ?? '',
363
- severity: l.severity ?? 'info',
364
- }));
365
- }
366
-
367
- // ── Helpers ──────────────────────────────────────────────────────────────
368
-
369
- private async gql<T = any>(query: string, variables?: Record<string, unknown>): Promise<T> {
370
- const res = await globalThis.fetch(this.config.baseUrl!, {
371
- method: 'POST',
372
- headers: {
373
- 'Authorization': `Bearer ${this.config.token}`,
374
- 'Content-Type': 'application/json',
375
- },
376
- body: JSON.stringify({ query, variables }),
377
- });
378
-
379
- if (!res.ok) {
380
- const text = await res.text();
381
- throw new Error(`Railway API ${res.status}: ${text.slice(0, 500)}`);
382
- }
383
-
384
- const json = await res.json() as { data?: T; errors?: Array<{ message: string }> };
385
-
386
- if (json.errors && json.errors.length > 0) {
387
- throw new Error(`Railway GraphQL: ${json.errors.map(e => e.message).join('; ')}`);
388
- }
389
-
390
- return json.data as T;
391
- }
392
-
393
- private mapProject(data: any): RailwayProject {
394
- return {
395
- id: data.id,
396
- name: data.name ?? '',
397
- description: data.description ?? '',
398
- createdAt: data.createdAt ?? '',
399
- updatedAt: data.updatedAt ?? '',
400
- environments: (data.environments?.edges ?? []).map((e: any) => ({
401
- id: e.node.id,
402
- name: e.node.name ?? '',
403
- isEphemeral: e.node.isEphemeral ?? false,
404
- })),
405
- services: (data.services?.edges ?? []).map((e: any) => ({
406
- id: e.node.id,
407
- name: e.node.name ?? '',
408
- projectId: e.node.projectId ?? data.id,
409
- createdAt: e.node.createdAt ?? '',
410
- updatedAt: e.node.updatedAt ?? '',
411
- icon: e.node.icon,
412
- })),
413
- };
414
- }
415
- }
package/src/index.ts DELETED
@@ -1,109 +0,0 @@
1
- // @ts-nocheck
2
- /**
3
- * @fileoverview Railway Cloud Extension for AgentOS.
4
- *
5
- * Provides 4 tools for deploying services, listing projects, provisioning
6
- * databases, and retrieving logs via the Railway GraphQL API.
7
- *
8
- * @module @framers/agentos-ext-cloud-railway
9
- */
10
-
11
- import { RailwayService } from './RailwayService.js';
12
- import type { RailwayConfig } from './RailwayService.js';
13
- import { RailwayDeployServiceTool } from './tools/deployService.js';
14
- import { RailwayListServicesTool } from './tools/listServices.js';
15
- import { RailwayAddDatabaseTool } from './tools/addDatabase.js';
16
- import { RailwayGetLogsTool } from './tools/getLogs.js';
17
-
18
- // ---------------------------------------------------------------------------
19
- // Options
20
- // ---------------------------------------------------------------------------
21
-
22
- export interface RailwayCloudOptions {
23
- token?: string;
24
- secrets?: Record<string, string>;
25
- }
26
-
27
- // ---------------------------------------------------------------------------
28
- // Secret Resolution
29
- // ---------------------------------------------------------------------------
30
-
31
- function resolveConfig(opts: RailwayCloudOptions, secrets: Record<string, string>): RailwayConfig {
32
- return {
33
- token:
34
- opts.token ?? secrets['railway.token']
35
- ?? process.env.RAILWAY_TOKEN ?? '',
36
- };
37
- }
38
-
39
- // ---------------------------------------------------------------------------
40
- // Extension Context
41
- // ---------------------------------------------------------------------------
42
-
43
- export interface ExtensionContext {
44
- options?: Record<string, unknown>;
45
- secrets?: Record<string, string>;
46
- getSecret?: (key: string) => string | undefined;
47
- logger?: { info: (msg: string) => void };
48
- }
49
-
50
- export interface ExtensionPack {
51
- name: string;
52
- version: string;
53
- descriptors: Array<{ id: string; kind: string; priority?: number; payload: unknown }>;
54
- onActivate?: () => Promise<void>;
55
- onDeactivate?: () => Promise<void>;
56
- }
57
-
58
- // ---------------------------------------------------------------------------
59
- // Factory
60
- // ---------------------------------------------------------------------------
61
-
62
- export function createExtensionPack(context: ExtensionContext): ExtensionPack {
63
- const opts = (context.options ?? {}) as RailwayCloudOptions;
64
- const secrets = opts.secrets ?? context.secrets ?? {};
65
- const config = resolveConfig(opts, secrets);
66
-
67
- const service = new RailwayService(config);
68
-
69
- const deployServiceTool = new RailwayDeployServiceTool(service);
70
- const listServicesTool = new RailwayListServicesTool(service);
71
- const addDatabaseTool = new RailwayAddDatabaseTool(service);
72
- const getLogsTool = new RailwayGetLogsTool(service);
73
-
74
- return {
75
- name: '@framers/agentos-ext-cloud-railway',
76
- version: '0.1.0',
77
- descriptors: [
78
- { id: 'railwayDeployService', kind: 'tool', priority: 40, payload: deployServiceTool },
79
- { id: 'railwayListServices', kind: 'tool', priority: 40, payload: listServicesTool },
80
- { id: 'railwayAddDatabase', kind: 'tool', priority: 40, payload: addDatabaseTool },
81
- { id: 'railwayGetLogs', kind: 'tool', priority: 40, payload: getLogsTool },
82
- ],
83
- onActivate: async () => {
84
- if (!config.token) {
85
- throw new Error(
86
- 'Railway: no API token provided. Set RAILWAY_TOKEN environment variable, '
87
- + 'or provide it via secrets["railway.token"].',
88
- );
89
- }
90
- await service.initialize();
91
- },
92
- onDeactivate: async () => {
93
- await service.shutdown();
94
- },
95
- };
96
- }
97
-
98
- // ---------------------------------------------------------------------------
99
- // Public exports
100
- // ---------------------------------------------------------------------------
101
-
102
- export { RailwayService } from './RailwayService.js';
103
- export type { RailwayConfig, RailwayProject, RailwayEnvironment, RailwayService_ as RailwayServiceInfo, RailwayDeployment, RailwayPlugin, RailwayLogEntry } from './RailwayService.js';
104
- export { RailwayDeployServiceTool } from './tools/deployService.js';
105
- export { RailwayListServicesTool } from './tools/listServices.js';
106
- export { RailwayAddDatabaseTool } from './tools/addDatabase.js';
107
- export { RailwayGetLogsTool } from './tools/getLogs.js';
108
-
109
- export default createExtensionPack;
@@ -1,45 +0,0 @@
1
- // @ts-nocheck
2
- /**
3
- * @fileoverview RailwayAddDatabaseTool — provision a database plugin on Railway.
4
- */
5
-
6
- import type { RailwayService, RailwayPlugin } from '../RailwayService.js';
7
-
8
- export interface RailwayAddDatabaseInput {
9
- projectId: string;
10
- plugin: 'postgresql' | 'redis' | 'mysql' | 'mongodb';
11
- }
12
-
13
- export class RailwayAddDatabaseTool {
14
- readonly id = 'railwayAddDatabase';
15
- readonly name = 'railwayAddDatabase';
16
- readonly displayName = 'Add Railway Database';
17
- readonly description = 'Provision a database plugin in a Railway project. Supports PostgreSQL, Redis, MySQL, and MongoDB. The database will be automatically connected to services in the same project via environment variables.';
18
- readonly category = 'cloud';
19
- readonly version = '0.1.0';
20
- readonly hasSideEffects = true;
21
-
22
- readonly inputSchema = {
23
- type: 'object' as const,
24
- properties: {
25
- projectId: { type: 'string', description: 'Railway project ID to add the database to' },
26
- plugin: {
27
- type: 'string',
28
- enum: ['postgresql', 'redis', 'mysql', 'mongodb'],
29
- description: 'Database type to provision',
30
- },
31
- },
32
- required: ['projectId', 'plugin'],
33
- };
34
-
35
- constructor(private service: RailwayService) {}
36
-
37
- async execute(args: RailwayAddDatabaseInput): Promise<{ success: boolean; data?: RailwayPlugin; error?: string }> {
38
- try {
39
- const plugin = await this.service.createPlugin(args.projectId, args.plugin);
40
- return { success: true, data: plugin };
41
- } catch (err: any) {
42
- return { success: false, error: err.message };
43
- }
44
- }
45
- }