@openserv-labs/deploy 0.1.0
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/README.md +125 -0
- package/dist/index.js +392 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# serv
|
|
2
|
+
|
|
3
|
+
CLI to deploy agents to the [OpenServ](https://openserv.ai) platform.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g serv
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run directly with `npx`:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx serv deploy
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
serv <command> [path]
|
|
21
|
+
|
|
22
|
+
Commands:
|
|
23
|
+
deploy [path] Deploy an agent to OpenServ (default path: .)
|
|
24
|
+
|
|
25
|
+
Options:
|
|
26
|
+
--help, -h Show this help message
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Deploy an agent
|
|
30
|
+
|
|
31
|
+
From the agent project directory:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
serv deploy
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or specify a path:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
serv deploy ./my-agent
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The deploy command will:
|
|
44
|
+
|
|
45
|
+
1. Read configuration from `.env` and `.openserv.json` in the target directory.
|
|
46
|
+
2. Resolve or create a container on the OpenServ platform.
|
|
47
|
+
3. Archive the project files (respecting `.gitignore`), upload them, and run `npm install`.
|
|
48
|
+
4. Start (or restart) the agent process and make it publicly available.
|
|
49
|
+
5. If an agent API key is configured, update the agent's endpoint URL on the platform.
|
|
50
|
+
|
|
51
|
+
## Configuration
|
|
52
|
+
|
|
53
|
+
### Environment variables
|
|
54
|
+
|
|
55
|
+
Set these in a `.env` file in your project directory or as shell environment variables:
|
|
56
|
+
|
|
57
|
+
| Variable | Required | Description |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `OPENSERV_USER_API_KEY` | Yes | Your OpenServ API key |
|
|
60
|
+
| `OPENSERV_CONTAINER_ID` | No | Container ID for redeployment (auto-set after first deploy) |
|
|
61
|
+
| `OPENSERV_ORCHESTRATOR_URL` | No | Custom orchestrator URL (defaults to `https://agent-orchestrator.openserv.ai`) |
|
|
62
|
+
| `OPENSERV_API_URL` | No | Custom platform API URL (defaults to `https://api.openserv.ai`) |
|
|
63
|
+
|
|
64
|
+
### `.openserv.json`
|
|
65
|
+
|
|
66
|
+
Place this file in your project root to link deploys to a registered agent. The CLI reads the first agent entry:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"agents": {
|
|
71
|
+
"My Agent": {
|
|
72
|
+
"id": 42,
|
|
73
|
+
"apiKey": "your-agent-api-key"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
When both `id` and `apiKey` are present, the CLI will automatically update the agent's endpoint URL after going live.
|
|
80
|
+
|
|
81
|
+
### Files included in the deploy archive
|
|
82
|
+
|
|
83
|
+
The CLI creates a tar.gz archive of your project, applying these rules:
|
|
84
|
+
|
|
85
|
+
- **Always excluded:** `node_modules`, `.git`, `dist`, `.next`, `.turbo`, `.env`, `.env.example`, `.env.local`, `.env.*.local`, `*.tsbuildinfo`
|
|
86
|
+
- **`.gitignore` rules** are respected if a `.gitignore` file exists.
|
|
87
|
+
|
|
88
|
+
## Publishing to npm
|
|
89
|
+
|
|
90
|
+
### Prerequisites
|
|
91
|
+
|
|
92
|
+
- An [npm](https://www.npmjs.com/) account with publish access.
|
|
93
|
+
- Node.js 20+.
|
|
94
|
+
|
|
95
|
+
### Steps
|
|
96
|
+
|
|
97
|
+
1. **Build the package:**
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npm run build
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
2. **Update the version** (follow [semver](https://semver.org/)):
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
npm version patch # or minor / major
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
3. **Publish:**
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npm publish
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The `"files"` field in `package.json` ensures only the `dist/` directory is included in the published package. The `"bin"` field registers the `serv` command globally when installed with `-g`.
|
|
116
|
+
|
|
117
|
+
## Development
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Run locally without building
|
|
121
|
+
npm run dev -- deploy ./my-agent
|
|
122
|
+
|
|
123
|
+
# Build
|
|
124
|
+
npm run build
|
|
125
|
+
```
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/deploy.ts
|
|
4
|
+
import path4 from "path";
|
|
5
|
+
|
|
6
|
+
// src/api-client.ts
|
|
7
|
+
import axios from "axios";
|
|
8
|
+
var DEFAULT_ORCHESTRATOR_URL = "https://agent-orchestrator.openserv.ai";
|
|
9
|
+
var ApiError = class extends Error {
|
|
10
|
+
constructor(message, statusCode) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.statusCode = statusCode;
|
|
13
|
+
this.name = "ApiError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var ApiClient = class {
|
|
17
|
+
client;
|
|
18
|
+
constructor(opts) {
|
|
19
|
+
const headers = {
|
|
20
|
+
"x-openserv-key": opts.apiKey
|
|
21
|
+
};
|
|
22
|
+
if (opts.agentId != null) {
|
|
23
|
+
headers["x-openserv-agent-id"] = String(opts.agentId);
|
|
24
|
+
}
|
|
25
|
+
this.client = axios.create({
|
|
26
|
+
baseURL: opts.orchestratorUrl || DEFAULT_ORCHESTRATOR_URL,
|
|
27
|
+
headers,
|
|
28
|
+
maxBodyLength: 100 * 1024 * 1024,
|
|
29
|
+
maxContentLength: 100 * 1024 * 1024
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async createContainer() {
|
|
33
|
+
const res = await this.request("POST", "/container/create");
|
|
34
|
+
return res;
|
|
35
|
+
}
|
|
36
|
+
async getStatus(id) {
|
|
37
|
+
return this.request("GET", `/container/${id}/status`);
|
|
38
|
+
}
|
|
39
|
+
async findContainerByAgent(agentId) {
|
|
40
|
+
try {
|
|
41
|
+
const status = await this.getStatus(String(agentId));
|
|
42
|
+
return {
|
|
43
|
+
id: status.id,
|
|
44
|
+
appName: status.appName,
|
|
45
|
+
machineId: status.machineId,
|
|
46
|
+
status: status.status
|
|
47
|
+
};
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (err instanceof ApiError && err.statusCode === 404) return null;
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async upload(id, tarBuffer) {
|
|
54
|
+
await this.client.post(`/container/${id}/upload`, tarBuffer, {
|
|
55
|
+
headers: { "Content-Type": "application/gzip" }
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async exec(id, command, timeout) {
|
|
59
|
+
return this.request("POST", `/container/${id}/exec`, {
|
|
60
|
+
command,
|
|
61
|
+
timeout
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async start(id, entrypoint) {
|
|
65
|
+
await this.request("POST", `/container/${id}/start`, {
|
|
66
|
+
entrypoint: entrypoint || "npx tsx src/agent.ts"
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async restart(id) {
|
|
70
|
+
await this.request("POST", `/container/${id}/restart`);
|
|
71
|
+
}
|
|
72
|
+
async goLive(id, mode = "on-demand") {
|
|
73
|
+
return this.request("POST", `/container/${id}/go-live`, {
|
|
74
|
+
mode
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async updateEndpointUrl(agentId, agentApiKey, endpointUrl) {
|
|
78
|
+
const platformUrl = process.env.OPENSERV_API_URL || "https://api.openserv.ai";
|
|
79
|
+
await axios.put(
|
|
80
|
+
`${platformUrl}/agents/${agentId}/endpoint-url`,
|
|
81
|
+
{ endpoint_url: endpointUrl },
|
|
82
|
+
{ headers: { "x-openserv-key": agentApiKey } }
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
async request(method, path5, body) {
|
|
86
|
+
try {
|
|
87
|
+
const res = await this.client.request({
|
|
88
|
+
method,
|
|
89
|
+
url: path5,
|
|
90
|
+
data: body
|
|
91
|
+
});
|
|
92
|
+
return res.data;
|
|
93
|
+
} catch (err) {
|
|
94
|
+
const axiosErr = err;
|
|
95
|
+
const statusCode = axiosErr.response?.status;
|
|
96
|
+
const data = axiosErr.response?.data ?? axiosErr.message;
|
|
97
|
+
const detail = typeof data === "string" ? data : JSON.stringify(data);
|
|
98
|
+
throw new ApiError(
|
|
99
|
+
`${method} ${path5} failed (${statusCode ?? "unknown"}): ${detail}`,
|
|
100
|
+
statusCode
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// src/env.ts
|
|
107
|
+
import fs from "fs";
|
|
108
|
+
import path from "path";
|
|
109
|
+
import { config as loadDotenv } from "dotenv";
|
|
110
|
+
function readEnv(dir) {
|
|
111
|
+
const envPath = path.join(dir, ".env");
|
|
112
|
+
const parsed = loadDotenv({ path: envPath, override: false });
|
|
113
|
+
const env = parsed.parsed ?? {};
|
|
114
|
+
return {
|
|
115
|
+
apiKey: env.OPENSERV_USER_API_KEY || process.env.OPENSERV_USER_API_KEY,
|
|
116
|
+
containerId: env.OPENSERV_CONTAINER_ID || process.env.OPENSERV_CONTAINER_ID,
|
|
117
|
+
orchestratorUrl: env.OPENSERV_ORCHESTRATOR_URL || process.env.OPENSERV_ORCHESTRATOR_URL
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function writeContainerId(dir, containerId) {
|
|
121
|
+
const envPath = path.join(dir, ".env");
|
|
122
|
+
let content = "";
|
|
123
|
+
if (fs.existsSync(envPath)) {
|
|
124
|
+
content = fs.readFileSync(envPath, "utf8");
|
|
125
|
+
}
|
|
126
|
+
const key = "OPENSERV_CONTAINER_ID";
|
|
127
|
+
const line = `${key}=${containerId}`;
|
|
128
|
+
const regex = new RegExp(`^${key}=.*$`, "m");
|
|
129
|
+
if (regex.test(content)) {
|
|
130
|
+
content = content.replace(regex, line);
|
|
131
|
+
} else {
|
|
132
|
+
const separator = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
133
|
+
content = `${content}${separator}${line}
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
fs.writeFileSync(envPath, content, "utf8");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/openserv-json.ts
|
|
140
|
+
import fs2 from "fs";
|
|
141
|
+
import path2 from "path";
|
|
142
|
+
function readAgentConfig(dir) {
|
|
143
|
+
const filePath = path2.join(dir, ".openserv.json");
|
|
144
|
+
if (!fs2.existsSync(filePath)) {
|
|
145
|
+
return void 0;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
149
|
+
const data = JSON.parse(raw);
|
|
150
|
+
if (!data.agents) return void 0;
|
|
151
|
+
const firstAgent = Object.values(data.agents)[0];
|
|
152
|
+
if (!firstAgent) return void 0;
|
|
153
|
+
return { id: firstAgent.id, apiKey: firstAgent.apiKey };
|
|
154
|
+
} catch {
|
|
155
|
+
return void 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/tar.ts
|
|
160
|
+
import fs3 from "fs";
|
|
161
|
+
import os from "os";
|
|
162
|
+
import path3 from "path";
|
|
163
|
+
import ignore from "ignore";
|
|
164
|
+
import * as tar from "tar";
|
|
165
|
+
var ALWAYS_EXCLUDE = [
|
|
166
|
+
"node_modules",
|
|
167
|
+
".git",
|
|
168
|
+
"dist",
|
|
169
|
+
".next",
|
|
170
|
+
".turbo",
|
|
171
|
+
".env",
|
|
172
|
+
".env.example",
|
|
173
|
+
".env.local",
|
|
174
|
+
".env.*.local"
|
|
175
|
+
];
|
|
176
|
+
var ALWAYS_EXCLUDE_EXTENSIONS = [".tsbuildinfo"];
|
|
177
|
+
async function createTarBuffer(dir) {
|
|
178
|
+
const ig = ignore();
|
|
179
|
+
ig.add(ALWAYS_EXCLUDE);
|
|
180
|
+
const gitignorePath = path3.join(dir, ".gitignore");
|
|
181
|
+
if (fs3.existsSync(gitignorePath)) {
|
|
182
|
+
const content = fs3.readFileSync(gitignorePath, "utf8");
|
|
183
|
+
ig.add(content);
|
|
184
|
+
}
|
|
185
|
+
const entries = collectEntries(dir, dir, ig);
|
|
186
|
+
const tmpFile = path3.join(os.tmpdir(), `serv-deploy-${Date.now()}.tar.gz`);
|
|
187
|
+
try {
|
|
188
|
+
await tar.create(
|
|
189
|
+
{
|
|
190
|
+
gzip: true,
|
|
191
|
+
cwd: dir,
|
|
192
|
+
portable: true,
|
|
193
|
+
file: tmpFile
|
|
194
|
+
},
|
|
195
|
+
entries
|
|
196
|
+
);
|
|
197
|
+
return { buffer: fs3.readFileSync(tmpFile), files: entries };
|
|
198
|
+
} finally {
|
|
199
|
+
try {
|
|
200
|
+
fs3.unlinkSync(tmpFile);
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function collectEntries(baseDir, currentDir, ig) {
|
|
206
|
+
const entries = [];
|
|
207
|
+
const items = fs3.readdirSync(currentDir, { withFileTypes: true });
|
|
208
|
+
for (const item of items) {
|
|
209
|
+
const fullPath = path3.join(currentDir, item.name);
|
|
210
|
+
const relativePath = path3.relative(baseDir, fullPath);
|
|
211
|
+
if (ALWAYS_EXCLUDE_EXTENSIONS.some((ext) => item.name.endsWith(ext))) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const testPath = item.isDirectory() ? `${relativePath}/` : relativePath;
|
|
215
|
+
if (ig.ignores(testPath)) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (item.isDirectory()) {
|
|
219
|
+
entries.push(...collectEntries(baseDir, fullPath, ig));
|
|
220
|
+
} else {
|
|
221
|
+
entries.push(relativePath);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return entries;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/deploy.ts
|
|
228
|
+
async function resolveContainer(client, dir, containerId, agentId) {
|
|
229
|
+
if (containerId) {
|
|
230
|
+
console.log(`Using existing container: ${containerId}`);
|
|
231
|
+
return { id: containerId, isFirstDeploy: false };
|
|
232
|
+
}
|
|
233
|
+
if (agentId) {
|
|
234
|
+
console.log(
|
|
235
|
+
`Agent ID found: ${agentId}. Checking for existing container...`
|
|
236
|
+
);
|
|
237
|
+
const existing = await client.findContainerByAgent(agentId);
|
|
238
|
+
if (existing) {
|
|
239
|
+
writeContainerId(dir, existing.id);
|
|
240
|
+
console.log(` Found container: ${existing.id}`);
|
|
241
|
+
console.log(" Saved to .env\n");
|
|
242
|
+
return { id: existing.id, isFirstDeploy: false };
|
|
243
|
+
}
|
|
244
|
+
console.log(" No container found. Creating new container...");
|
|
245
|
+
} else {
|
|
246
|
+
console.log("Creating new container...");
|
|
247
|
+
}
|
|
248
|
+
const container = await client.createContainer();
|
|
249
|
+
writeContainerId(dir, container.id);
|
|
250
|
+
console.log(` Container ID: ${container.id}`);
|
|
251
|
+
console.log(" Written to .env\n");
|
|
252
|
+
return { id: container.id, isFirstDeploy: true };
|
|
253
|
+
}
|
|
254
|
+
async function deploy(targetPath) {
|
|
255
|
+
const dir = path4.resolve(targetPath);
|
|
256
|
+
console.log(`Deploying from ${dir}
|
|
257
|
+
`);
|
|
258
|
+
const env = readEnv(dir);
|
|
259
|
+
const agentConfig = readAgentConfig(dir);
|
|
260
|
+
const agentId = agentConfig?.id;
|
|
261
|
+
if (!env.apiKey) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
"OPENSERV_USER_API_KEY not found. Set it in your .env file or as an environment variable."
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
const client = new ApiClient({
|
|
267
|
+
apiKey: env.apiKey,
|
|
268
|
+
agentId,
|
|
269
|
+
orchestratorUrl: env.orchestratorUrl
|
|
270
|
+
});
|
|
271
|
+
const { id: targetId, isFirstDeploy } = await resolveContainer(
|
|
272
|
+
client,
|
|
273
|
+
dir,
|
|
274
|
+
env.containerId,
|
|
275
|
+
agentId
|
|
276
|
+
);
|
|
277
|
+
let currentStatus;
|
|
278
|
+
let appName;
|
|
279
|
+
if (!isFirstDeploy) {
|
|
280
|
+
try {
|
|
281
|
+
const status = await client.getStatus(targetId);
|
|
282
|
+
currentStatus = status.status;
|
|
283
|
+
appName = status.appName;
|
|
284
|
+
console.log(` Current status: ${currentStatus}`);
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
console.log("\nCreating archive...");
|
|
289
|
+
const { buffer: tarBuffer, files } = await createTarBuffer(dir);
|
|
290
|
+
for (const file of files) {
|
|
291
|
+
console.log(` ${file}`);
|
|
292
|
+
}
|
|
293
|
+
console.log(
|
|
294
|
+
` ${files.length} files, ${(tarBuffer.length / 1024).toFixed(1)} KB`
|
|
295
|
+
);
|
|
296
|
+
console.log("\nUploading files...");
|
|
297
|
+
await client.upload(targetId, tarBuffer);
|
|
298
|
+
console.log(" Done.");
|
|
299
|
+
const verify = await client.exec(targetId, ["ls", "-la", "/app"], 30);
|
|
300
|
+
if (verify.exitCode === 0) {
|
|
301
|
+
console.log(" Verified /app contents:");
|
|
302
|
+
for (const line of verify.stdout.split("\n").filter(Boolean)) {
|
|
303
|
+
console.log(` ${line}`);
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
console.error(" Warning: could not verify upload");
|
|
307
|
+
if (verify.stderr) console.error(` ${verify.stderr}`);
|
|
308
|
+
}
|
|
309
|
+
console.log("\nInstalling dependencies...");
|
|
310
|
+
const installResult = await client.exec(targetId, ["npm", "install"], 600);
|
|
311
|
+
if (installResult.exitCode !== 0) {
|
|
312
|
+
const parts = [`npm install failed (exit code ${installResult.exitCode})`];
|
|
313
|
+
if (installResult.stdout)
|
|
314
|
+
parts.push(`stdout: ${installResult.stdout.slice(0, 500)}`);
|
|
315
|
+
if (installResult.stderr)
|
|
316
|
+
parts.push(`stderr: ${installResult.stderr.slice(0, 500)}`);
|
|
317
|
+
throw new Error(parts.join("\n"));
|
|
318
|
+
}
|
|
319
|
+
console.log(" Done.");
|
|
320
|
+
const needsStart = isFirstDeploy || !currentStatus || currentStatus === "ready";
|
|
321
|
+
if (needsStart) {
|
|
322
|
+
console.log("\nStarting agent...");
|
|
323
|
+
await client.start(targetId);
|
|
324
|
+
console.log(" Agent started.");
|
|
325
|
+
} else {
|
|
326
|
+
console.log("\nRestarting container...");
|
|
327
|
+
await client.restart(targetId);
|
|
328
|
+
console.log(" Container restarted.");
|
|
329
|
+
}
|
|
330
|
+
let publicUrl;
|
|
331
|
+
if (currentStatus !== "live") {
|
|
332
|
+
console.log("\nGoing live...");
|
|
333
|
+
const result = await client.goLive(targetId, "continuous");
|
|
334
|
+
publicUrl = result.publicUrl;
|
|
335
|
+
console.log(` Public URL: ${publicUrl}`);
|
|
336
|
+
} else {
|
|
337
|
+
if (appName) {
|
|
338
|
+
publicUrl = `https://${appName}.fly.dev`;
|
|
339
|
+
}
|
|
340
|
+
console.log("\nAlready live.");
|
|
341
|
+
}
|
|
342
|
+
if (agentConfig?.apiKey && agentConfig.id && publicUrl) {
|
|
343
|
+
console.log("\nUpdating agent endpoint URL...");
|
|
344
|
+
await client.updateEndpointUrl(
|
|
345
|
+
agentConfig.id,
|
|
346
|
+
agentConfig.apiKey,
|
|
347
|
+
publicUrl
|
|
348
|
+
);
|
|
349
|
+
console.log(` Agent endpoint set to ${publicUrl}`);
|
|
350
|
+
}
|
|
351
|
+
console.log("\nDeploy complete!");
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/index.ts
|
|
355
|
+
var HELP = `
|
|
356
|
+
Usage: serv <command> [path]
|
|
357
|
+
|
|
358
|
+
Commands:
|
|
359
|
+
deploy [path] Deploy an agent to OpenServ (default path: .)
|
|
360
|
+
|
|
361
|
+
Options:
|
|
362
|
+
--help, -h Show this help message
|
|
363
|
+
|
|
364
|
+
Environment variables (set in .env or shell):
|
|
365
|
+
OPENSERV_USER_API_KEY Your OpenServ API key (required)
|
|
366
|
+
OPENSERV_CONTAINER_ID Container ID for redeployment (auto-set after first deploy)
|
|
367
|
+
OPENSERV_ORCHESTRATOR_URL Custom orchestrator URL (optional)
|
|
368
|
+
`.trim();
|
|
369
|
+
async function main() {
|
|
370
|
+
const args = process.argv.slice(2);
|
|
371
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
372
|
+
console.log(HELP);
|
|
373
|
+
process.exit(0);
|
|
374
|
+
}
|
|
375
|
+
const command = args[0];
|
|
376
|
+
switch (command) {
|
|
377
|
+
case "deploy": {
|
|
378
|
+
const targetPath = args[1] || ".";
|
|
379
|
+
await deploy(targetPath);
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
default:
|
|
383
|
+
console.error(`Unknown command: ${command}
|
|
384
|
+
`);
|
|
385
|
+
console.log(HELP);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
main().catch((err) => {
|
|
390
|
+
console.error("\nFailed:", err instanceof Error ? err.message : err);
|
|
391
|
+
process.exit(1);
|
|
392
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@openserv-labs/deploy",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI to deploy agents to the OpenServ platform",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"openserv",
|
|
8
|
+
"agent",
|
|
9
|
+
"deploy",
|
|
10
|
+
"cli"
|
|
11
|
+
],
|
|
12
|
+
"type": "module",
|
|
13
|
+
"bin": {
|
|
14
|
+
"serv": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"dev": "tsx src/index.ts"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"axios": "^1.7.2",
|
|
29
|
+
"dotenv": "^16.4.7",
|
|
30
|
+
"ignore": "^7.0.3",
|
|
31
|
+
"tar": "^7.4.3"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^20.14.0",
|
|
35
|
+
"tsup": "^8.5.0",
|
|
36
|
+
"tsx": "^4.19.4",
|
|
37
|
+
"typescript": "^5.9.3"
|
|
38
|
+
}
|
|
39
|
+
}
|