@openserv-labs/client 2.4.8 → 2.5.1

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 CHANGED
@@ -719,6 +719,83 @@ triggers.manual({
719
719
  // { type: 'manual', name: '...', description: '...' }
720
720
  ```
721
721
 
722
+ ## Deploy to OpenServ Cloud
723
+
724
+ Deploy your agent to the OpenServ managed cloud with a single command:
725
+
726
+ ```bash
727
+ npx @openserv-labs/client deploy [path]
728
+ ```
729
+
730
+ Where `[path]` is the directory containing your agent code (defaults to current directory).
731
+
732
+ ### Prerequisites
733
+
734
+ 1. **`OPENSERV_USER_API_KEY` in `.env`** — The deploy CLI needs this key to authenticate with the platform. If you've already run `provision()` at least once, check your `.openserv.json` file — it contains a `userApiKey` field you can use:
735
+
736
+ ```bash
737
+ # Look for the userApiKey in .openserv.json
738
+ cat .openserv.json | grep userApiKey
739
+ ```
740
+
741
+ Copy that value into your `.env`:
742
+
743
+ ```env
744
+ OPENSERV_USER_API_KEY=<userApiKey from .openserv.json>
745
+ ```
746
+
747
+ If you haven't provisioned yet, get the key from the [OpenServ platform dashboard](https://platform.openserv.ai/profile/api-keys) instead.
748
+
749
+ 2. **Run `provision()` first** — Your agent must be provisioned at least once before deploying. `provision()` registers the agent on the platform and writes credentials to `.openserv.json`. Starting the agent locally (`npm run dev`) is enough if your code calls `provision()` before `run(agent)`.
750
+
751
+ ### How It Works
752
+
753
+ The deploy command:
754
+
755
+ 1. Reads `.openserv.json` to find the provisioned agent ID
756
+ 2. Creates or reuses a cloud container (saves `OPENSERV_CONTAINER_ID` to `.env`)
757
+ 3. Archives your source code (respects `.gitignore`, excludes `node_modules`, `.git`, `.env`, `dist`)
758
+ 4. Uploads and installs dependencies in the container
759
+ 5. Starts (or restarts) the agent and exposes a public URL
760
+ 6. Updates the agent's endpoint URL on the platform automatically
761
+
762
+ ### Deploy Workflow
763
+
764
+ ```bash
765
+ # 1. Run locally once to provision (registers agent + writes .openserv.json)
766
+ npm run dev
767
+
768
+ # 2. Set OPENSERV_USER_API_KEY in .env
769
+ # - If you already provisioned: grab userApiKey from .openserv.json
770
+ # - Otherwise: get it from the platform dashboard
771
+ echo "OPENSERV_USER_API_KEY=your-key" >> .env
772
+
773
+ # 3. Deploy to cloud
774
+ npx @openserv-labs/client deploy .
775
+ ```
776
+
777
+ On subsequent deploys, the CLI reuses the existing container — it uploads your latest code, reinstalls dependencies, and restarts the agent.
778
+
779
+ ### Environment Variables for Deploy
780
+
781
+ | Variable | Description | Required |
782
+ | --------------------------- | ---------------------------------------- | -------- |
783
+ | `OPENSERV_USER_API_KEY` | Your OpenServ user API key | Yes |
784
+ | `OPENSERV_CONTAINER_ID` | Container ID (auto-set after first deploy) | No |
785
+
786
+ ### CLI Reference
787
+
788
+ The package also exposes a `serv` binary:
789
+
790
+ ```bash
791
+ # Via npx
792
+ npx @openserv-labs/client deploy [path]
793
+
794
+ # Or if installed globally / as a dependency
795
+ serv deploy [path]
796
+ serv --help
797
+ ```
798
+
722
799
  ## Environment Variables
723
800
 
724
801
  | Variable | Description | Required |
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import "./deploy/index.js";
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,mBAAmB,CAAC"}
package/dist/cli.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ require("./deploy/index.js");
@@ -0,0 +1,46 @@
1
+ export declare class ApiError extends Error {
2
+ readonly statusCode: number | undefined;
3
+ constructor(message: string, statusCode: number | undefined);
4
+ }
5
+ export interface ApiClientOptions {
6
+ apiKey: string;
7
+ agentId?: number;
8
+ orchestratorUrl?: string;
9
+ }
10
+ export interface ContainerInfo {
11
+ id: string;
12
+ appName: string;
13
+ machineId: string;
14
+ status: string;
15
+ }
16
+ export interface StatusInfo {
17
+ id: string;
18
+ appName: string;
19
+ machineId: string;
20
+ status: string;
21
+ machineState: string;
22
+ metadata: Record<string, unknown>;
23
+ }
24
+ export interface ExecResult {
25
+ stdout: string;
26
+ stderr: string;
27
+ exitCode: number;
28
+ }
29
+ export interface GoLiveResult {
30
+ publicUrl: string;
31
+ }
32
+ export declare class ApiClient {
33
+ private client;
34
+ constructor(opts: ApiClientOptions);
35
+ createContainer(): Promise<ContainerInfo>;
36
+ getStatus(id: string): Promise<StatusInfo>;
37
+ findContainerByAgent(agentId: number): Promise<ContainerInfo | null>;
38
+ upload(id: string, tarBuffer: Buffer): Promise<void>;
39
+ exec(id: string, command: string[], timeout?: number): Promise<ExecResult>;
40
+ start(id: string, entrypoint?: string): Promise<void>;
41
+ restart(id: string): Promise<void>;
42
+ goLive(id: string, mode?: string): Promise<GoLiveResult>;
43
+ updateEndpointUrl(agentId: number, agentApiKey: string, endpointUrl: string): Promise<void>;
44
+ private request;
45
+ }
46
+ //# sourceMappingURL=api-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/deploy/api-client.ts"],"names":[],"mappings":"AAIA,qBAAa,QAAS,SAAQ,KAAK;aAGf,UAAU,EAAE,MAAM,GAAG,SAAS;gBAD9C,OAAO,EAAE,MAAM,EACC,UAAU,EAAE,MAAM,GAAG,SAAS;CAKjD;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAgB;gBAElB,IAAI,EAAE,gBAAgB;IAgB5B,eAAe,IAAI,OAAO,CAAC,aAAa,CAAC;IAIzC,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAI1C,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAepE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMpD,IAAI,CACR,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,CAAC;IAOhB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMrD,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,SAAc,GAAG,OAAO,CAAC,YAAY,CAAC;IAM7D,iBAAiB,CACrB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,IAAI,CAAC;YAUF,OAAO;CAyBtB"}
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ApiClient = exports.ApiError = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const DEFAULT_ORCHESTRATOR_URL = "https://agent-orchestrator.openserv.ai";
9
+ class ApiError extends Error {
10
+ statusCode;
11
+ constructor(message, statusCode) {
12
+ super(message);
13
+ this.statusCode = statusCode;
14
+ this.name = "ApiError";
15
+ }
16
+ }
17
+ exports.ApiError = ApiError;
18
+ class ApiClient {
19
+ client;
20
+ constructor(opts) {
21
+ const headers = {
22
+ "x-openserv-key": opts.apiKey,
23
+ };
24
+ if (opts.agentId != null) {
25
+ headers["x-openserv-agent-id"] = String(opts.agentId);
26
+ }
27
+ this.client = axios_1.default.create({
28
+ baseURL: opts.orchestratorUrl || DEFAULT_ORCHESTRATOR_URL,
29
+ headers,
30
+ maxBodyLength: 100 * 1024 * 1024,
31
+ maxContentLength: 100 * 1024 * 1024,
32
+ });
33
+ }
34
+ async createContainer() {
35
+ return this.request("POST", "/container/create");
36
+ }
37
+ async getStatus(id) {
38
+ return this.request("GET", `/container/${id}/status`);
39
+ }
40
+ async findContainerByAgent(agentId) {
41
+ try {
42
+ const status = await this.getStatus(String(agentId));
43
+ return {
44
+ id: status.id,
45
+ appName: status.appName,
46
+ machineId: status.machineId,
47
+ status: status.status,
48
+ };
49
+ }
50
+ catch (err) {
51
+ if (err instanceof ApiError && err.statusCode === 404)
52
+ return null;
53
+ throw err;
54
+ }
55
+ }
56
+ async upload(id, tarBuffer) {
57
+ await this.request("POST", `/container/${id}/upload`, tarBuffer, {
58
+ headers: { "Content-Type": "application/gzip" },
59
+ });
60
+ }
61
+ async exec(id, command, timeout) {
62
+ return this.request("POST", `/container/${id}/exec`, {
63
+ command,
64
+ timeout,
65
+ });
66
+ }
67
+ async start(id, entrypoint) {
68
+ await this.request("POST", `/container/${id}/start`, {
69
+ entrypoint: entrypoint || "npx tsx src/agent.ts",
70
+ });
71
+ }
72
+ async restart(id) {
73
+ await this.request("POST", `/container/${id}/restart`);
74
+ }
75
+ async goLive(id, mode = "on-demand") {
76
+ return this.request("POST", `/container/${id}/go-live`, {
77
+ mode,
78
+ });
79
+ }
80
+ async updateEndpointUrl(agentId, agentApiKey, endpointUrl) {
81
+ const platformUrl = process.env.OPENSERV_API_URL || "https://api.openserv.ai";
82
+ await axios_1.default.put(`${platformUrl}/agents/${agentId}/endpoint-url`, { endpoint_url: endpointUrl }, { headers: { "x-openserv-key": agentApiKey } });
83
+ }
84
+ async request(method, path, body, config) {
85
+ try {
86
+ const res = await this.client.request({
87
+ method,
88
+ url: path,
89
+ data: body,
90
+ ...config,
91
+ });
92
+ return res.data;
93
+ }
94
+ catch (err) {
95
+ const axiosErr = err;
96
+ const statusCode = axiosErr.response?.status;
97
+ const data = axiosErr.response?.data ?? axiosErr.message;
98
+ const detail = typeof data === "string" ? data : JSON.stringify(data);
99
+ throw new ApiError(`${method} ${path} failed (${statusCode ?? "unknown"}): ${detail}`, statusCode);
100
+ }
101
+ }
102
+ }
103
+ exports.ApiClient = ApiClient;
@@ -0,0 +1,2 @@
1
+ export declare function deploy(targetPath: string): Promise<void>;
2
+ //# sourceMappingURL=deploy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/deploy/deploy.ts"],"names":[],"mappings":"AA8CA,wBAAsB,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgH9D"}
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.deploy = deploy;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const api_client_js_1 = require("./api-client.js");
9
+ const env_js_1 = require("./env.js");
10
+ const logger_js_1 = require("./logger.js");
11
+ const openserv_json_js_1 = require("./openserv-json.js");
12
+ const tar_js_1 = require("./tar.js");
13
+ async function resolveContainer(client, dir, containerId, agentId) {
14
+ if (containerId) {
15
+ logger_js_1.logger.info(`Using existing container: ${containerId}`);
16
+ return { id: containerId, isFirstDeploy: false };
17
+ }
18
+ if (agentId) {
19
+ logger_js_1.logger.info(`Agent ID found: ${agentId}. Checking for existing container...`);
20
+ const existing = await client.findContainerByAgent(agentId);
21
+ if (existing) {
22
+ (0, env_js_1.writeContainerId)(dir, existing.id);
23
+ logger_js_1.logger.info(` Found container: ${existing.id}`);
24
+ logger_js_1.logger.info(" Saved to .env\n");
25
+ return { id: existing.id, isFirstDeploy: false };
26
+ }
27
+ logger_js_1.logger.info(" No container found. Creating new container...");
28
+ }
29
+ else {
30
+ logger_js_1.logger.info("Creating new container...");
31
+ }
32
+ const container = await client.createContainer();
33
+ (0, env_js_1.writeContainerId)(dir, container.id);
34
+ logger_js_1.logger.info(` Container ID: ${container.id}`);
35
+ logger_js_1.logger.info(" Written to .env\n");
36
+ return { id: container.id, isFirstDeploy: true };
37
+ }
38
+ async function deploy(targetPath) {
39
+ const dir = node_path_1.default.resolve(targetPath);
40
+ logger_js_1.logger.info(`Deploying from ${dir}\n`);
41
+ const env = (0, env_js_1.readEnv)(dir);
42
+ const agentConfig = (0, openserv_json_js_1.readAgentConfig)(dir);
43
+ const agentId = agentConfig?.id;
44
+ if (!env.apiKey) {
45
+ throw new Error("OPENSERV_USER_API_KEY not found. Set it in your .env file or as an environment variable.");
46
+ }
47
+ const client = new api_client_js_1.ApiClient({
48
+ apiKey: env.apiKey,
49
+ agentId,
50
+ orchestratorUrl: env.orchestratorUrl,
51
+ });
52
+ const { id: targetId, isFirstDeploy } = await resolveContainer(client, dir, env.containerId, agentId);
53
+ let currentStatus;
54
+ let appName;
55
+ if (!isFirstDeploy) {
56
+ try {
57
+ const status = await client.getStatus(targetId);
58
+ currentStatus = status.status;
59
+ appName = status.appName;
60
+ logger_js_1.logger.info(` Current status: ${currentStatus}`);
61
+ }
62
+ catch {
63
+ // Container might not be reachable yet
64
+ }
65
+ }
66
+ logger_js_1.logger.info("\nCreating archive...");
67
+ const { buffer: tarBuffer, files } = await (0, tar_js_1.createTarBuffer)(dir);
68
+ for (const file of files) {
69
+ logger_js_1.logger.info(` ${file}`);
70
+ }
71
+ logger_js_1.logger.info(` ${files.length} files, ${(tarBuffer.length / 1024).toFixed(1)} KB`);
72
+ logger_js_1.logger.info("\nUploading files...");
73
+ await client.upload(targetId, tarBuffer);
74
+ logger_js_1.logger.info(" Done.");
75
+ const verify = await client.exec(targetId, ["ls", "-la", "/app"], 30);
76
+ if (verify.exitCode === 0) {
77
+ logger_js_1.logger.info(" Verified /app contents:");
78
+ for (const line of verify.stdout.split("\n").filter(Boolean)) {
79
+ logger_js_1.logger.info(` ${line}`);
80
+ }
81
+ }
82
+ else {
83
+ logger_js_1.logger.error(" Warning: could not verify upload");
84
+ if (verify.stderr)
85
+ logger_js_1.logger.error(` ${verify.stderr}`);
86
+ }
87
+ logger_js_1.logger.info("\nInstalling dependencies...");
88
+ const installResult = await client.exec(targetId, ["npm", "install"], 600);
89
+ if (installResult.exitCode !== 0) {
90
+ const parts = [`npm install failed (exit code ${installResult.exitCode})`];
91
+ if (installResult.stdout)
92
+ parts.push(`stdout: ${installResult.stdout.slice(0, 500)}`);
93
+ if (installResult.stderr)
94
+ parts.push(`stderr: ${installResult.stderr.slice(0, 500)}`);
95
+ throw new Error(parts.join("\n"));
96
+ }
97
+ logger_js_1.logger.info(" Done.");
98
+ const needsStart = isFirstDeploy || !currentStatus || currentStatus === "ready";
99
+ if (needsStart) {
100
+ logger_js_1.logger.info("\nStarting agent...");
101
+ await client.start(targetId);
102
+ logger_js_1.logger.info(" Agent started.");
103
+ }
104
+ else {
105
+ logger_js_1.logger.info("\nRestarting container...");
106
+ await client.restart(targetId);
107
+ logger_js_1.logger.info(" Container restarted.");
108
+ }
109
+ let publicUrl;
110
+ if (currentStatus !== "live") {
111
+ logger_js_1.logger.info("\nGoing live...");
112
+ const result = await client.goLive(targetId, "continuous");
113
+ publicUrl = result.publicUrl;
114
+ logger_js_1.logger.info(` Public URL: ${publicUrl}`);
115
+ }
116
+ else {
117
+ if (appName) {
118
+ publicUrl = `https://${appName}.fly.dev`;
119
+ }
120
+ logger_js_1.logger.info("\nAlready live.");
121
+ }
122
+ if (agentConfig?.apiKey && agentConfig.id && publicUrl) {
123
+ logger_js_1.logger.info("\nUpdating agent endpoint URL...");
124
+ await client.updateEndpointUrl(agentConfig.id, agentConfig.apiKey, publicUrl);
125
+ logger_js_1.logger.info(` Agent endpoint set to ${publicUrl}`);
126
+ }
127
+ logger_js_1.logger.info("\nDeploy complete!");
128
+ }
@@ -0,0 +1,8 @@
1
+ export interface EnvValues {
2
+ apiKey?: string;
3
+ containerId?: string;
4
+ orchestratorUrl?: string;
5
+ }
6
+ export declare function readEnv(dir: string): EnvValues;
7
+ export declare function writeContainerId(dir: string, containerId: string): void;
8
+ //# sourceMappingURL=env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/deploy/env.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAY9C;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAoBvE"}
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.readEnv = readEnv;
7
+ exports.writeContainerId = writeContainerId;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const dotenv_1 = require("dotenv");
11
+ function readEnv(dir) {
12
+ const envPath = node_path_1.default.join(dir, ".env");
13
+ const parsed = (0, dotenv_1.config)({ path: envPath });
14
+ const env = parsed.parsed ?? {};
15
+ return {
16
+ apiKey: env.OPENSERV_USER_API_KEY || process.env.OPENSERV_USER_API_KEY,
17
+ containerId: env.OPENSERV_CONTAINER_ID || process.env.OPENSERV_CONTAINER_ID,
18
+ orchestratorUrl: env.OPENSERV_ORCHESTRATOR_URL || process.env.OPENSERV_ORCHESTRATOR_URL,
19
+ };
20
+ }
21
+ function writeContainerId(dir, containerId) {
22
+ const envPath = node_path_1.default.join(dir, ".env");
23
+ let content = "";
24
+ if (node_fs_1.default.existsSync(envPath)) {
25
+ content = node_fs_1.default.readFileSync(envPath, "utf8");
26
+ }
27
+ const key = "OPENSERV_CONTAINER_ID";
28
+ const line = `${key}=${containerId}`;
29
+ const regex = new RegExp(`^${key}=.*$`, "m");
30
+ if (regex.test(content)) {
31
+ content = content.replace(regex, line);
32
+ }
33
+ else {
34
+ const separator = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
35
+ content = `${content}${separator}${line}\n`;
36
+ }
37
+ node_fs_1.default.writeFileSync(envPath, content, "utf8");
38
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/deploy/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const deploy_js_1 = require("./deploy.js");
4
+ const HELP = `
5
+ Usage: serv <command> [path]
6
+
7
+ Commands:
8
+ deploy [path] Deploy an agent to OpenServ (default path: .)
9
+
10
+ Options:
11
+ --help, -h Show this help message
12
+
13
+ Environment variables (set in .env or shell):
14
+ OPENSERV_USER_API_KEY Your OpenServ API key (required)
15
+ OPENSERV_CONTAINER_ID Container ID for redeployment (auto-set after first deploy)
16
+ OPENSERV_ORCHESTRATOR_URL Custom orchestrator URL (optional)
17
+ `.trim();
18
+ async function main() {
19
+ const args = process.argv.slice(2);
20
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
21
+ console.log(HELP);
22
+ process.exit(0);
23
+ }
24
+ const command = args[0];
25
+ switch (command) {
26
+ case "deploy": {
27
+ const targetPath = args[1] || ".";
28
+ await (0, deploy_js_1.deploy)(targetPath);
29
+ break;
30
+ }
31
+ default:
32
+ console.error(`Unknown command: ${command}\n`);
33
+ console.log(HELP);
34
+ process.exit(1);
35
+ }
36
+ }
37
+ main().catch((err) => {
38
+ console.error("\nFailed:", err instanceof Error ? err.message : err);
39
+ process.exit(1);
40
+ });
@@ -0,0 +1,6 @@
1
+ export declare const logger: {
2
+ info: (...args: unknown[]) => void;
3
+ warn: (...args: unknown[]) => void;
4
+ error: (...args: unknown[]) => void;
5
+ };
6
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/deploy/logger.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM;oBACD,OAAO,EAAE;oBACT,OAAO,EAAE;qBACR,OAAO,EAAE;CAC3B,CAAC"}
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.logger = void 0;
4
+ exports.logger = {
5
+ info: (...args) => console.log(...args),
6
+ warn: (...args) => console.warn(...args),
7
+ error: (...args) => console.error(...args),
8
+ };
@@ -0,0 +1,6 @@
1
+ export interface AgentConfig {
2
+ id: number;
3
+ apiKey?: string;
4
+ }
5
+ export declare function readAgentConfig(dir: string): AgentConfig | undefined;
6
+ //# sourceMappingURL=openserv-json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openserv-json.d.ts","sourceRoot":"","sources":["../../src/deploy/openserv-json.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAmBpE"}
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.readAgentConfig = readAgentConfig;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ function readAgentConfig(dir) {
10
+ const filePath = node_path_1.default.join(dir, ".openserv.json");
11
+ if (!node_fs_1.default.existsSync(filePath)) {
12
+ return undefined;
13
+ }
14
+ try {
15
+ const raw = node_fs_1.default.readFileSync(filePath, "utf8");
16
+ const data = JSON.parse(raw);
17
+ if (!data.agents)
18
+ return undefined;
19
+ const firstAgent = Object.values(data.agents)[0];
20
+ if (!firstAgent)
21
+ return undefined;
22
+ return { id: firstAgent.id, apiKey: firstAgent.apiKey };
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }
@@ -0,0 +1,6 @@
1
+ export interface TarResult {
2
+ buffer: Buffer;
3
+ files: string[];
4
+ }
5
+ export declare function createTarBuffer(dir: string): Promise<TarResult>;
6
+ //# sourceMappingURL=tar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tar.d.ts","sourceRoot":"","sources":["../../src/deploy/tar.ts"],"names":[],"mappings":"AAuBA,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAerE"}
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createTarBuffer = createTarBuffer;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const ignore_1 = __importDefault(require("ignore"));
10
+ const nanotar_1 = require("nanotar");
11
+ const ALWAYS_EXCLUDE = [
12
+ "node_modules",
13
+ ".git",
14
+ "dist",
15
+ ".next",
16
+ ".turbo",
17
+ ".env.example",
18
+ ".env.local",
19
+ ".env.*.local",
20
+ ];
21
+ const ALWAYS_EXCLUDE_EXTENSIONS = [".tsbuildinfo"];
22
+ async function createTarBuffer(dir) {
23
+ const ig = (0, ignore_1.default)();
24
+ ig.add(ALWAYS_EXCLUDE);
25
+ const gitignorePath = node_path_1.default.join(dir, ".gitignore");
26
+ if (node_fs_1.default.existsSync(gitignorePath)) {
27
+ const content = node_fs_1.default.readFileSync(gitignorePath, "utf8");
28
+ ig.add(content);
29
+ }
30
+ const entries = collectEntries(dir, dir, ig);
31
+ const files = entries.map((e) => e.name);
32
+ const gzipped = await (0, nanotar_1.createTarGzip)(entries);
33
+ return { buffer: Buffer.from(gzipped), files };
34
+ }
35
+ function collectEntries(baseDir, currentDir, ig) {
36
+ const entries = [];
37
+ const items = node_fs_1.default.readdirSync(currentDir, { withFileTypes: true });
38
+ for (const item of items) {
39
+ const fullPath = node_path_1.default.join(currentDir, item.name);
40
+ const relativePath = node_path_1.default.relative(baseDir, fullPath);
41
+ if (ALWAYS_EXCLUDE_EXTENSIONS.some((ext) => item.name.endsWith(ext))) {
42
+ continue;
43
+ }
44
+ const testPath = item.isDirectory() ? `${relativePath}/` : relativePath;
45
+ if (ig.ignores(testPath)) {
46
+ continue;
47
+ }
48
+ if (item.isDirectory()) {
49
+ entries.push(...collectEntries(baseDir, fullPath, ig));
50
+ }
51
+ else {
52
+ entries.push({
53
+ name: relativePath,
54
+ data: new Uint8Array(node_fs_1.default.readFileSync(fullPath)),
55
+ });
56
+ }
57
+ }
58
+ return entries;
59
+ }
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@openserv-labs/client",
3
- "version": "2.4.8",
3
+ "version": "2.5.1",
4
4
  "description": "OpenServ Platform Client - Manage agents, workflows, tasks, and triggers via the OpenServ API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "serv": "./dist/cli.js"
9
+ },
7
10
  "scripts": {
8
11
  "build": "tsc",
9
12
  "dev": "tsc --watch",
@@ -15,7 +18,8 @@
15
18
  "format": "prettier --write \"**/*.{ts,json,md}\"",
16
19
  "format:check": "prettier --check \"**/*.{ts,json,md}\"",
17
20
  "test": "node --import tsx --test test/**/*.test.ts",
18
- "test:unit": "node --import tsx --test test/unit/**/*.test.ts"
21
+ "test:unit": "node --import tsx --test test/unit/**/*.test.ts",
22
+ "deploy": "tsx src/cli.ts deploy"
19
23
  },
20
24
  "repository": {
21
25
  "type": "git",
@@ -38,6 +42,9 @@
38
42
  "license": "MIT",
39
43
  "dependencies": {
40
44
  "axios": "^1.6.8",
45
+ "dotenv": "^16.4.5",
46
+ "ignore": "^7.0.3",
47
+ "nanotar": "^0.2.0",
41
48
  "pinata": "^2.5.1",
42
49
  "viem": "^2.45.1"
43
50
  },
@@ -45,7 +52,6 @@
45
52
  "@eslint/js": "^9.39.2",
46
53
  "@tsconfig/strictest": "^2.0.3",
47
54
  "@types/node": "^22.10.2",
48
- "dotenv": "^16.4.5",
49
55
  "eslint": "^9.39.2",
50
56
  "eslint-config-prettier": "^9.1.0",
51
57
  "eslint-plugin-prettier": "^5.1.3",