Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ The CLI is available via the `ecloud` command after building:

```bash
# Deploy an application
pnpm ecloud app deploy \
npx ecloud app deploy \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

npx would need to use the actual package name, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

npx uses the named bin script from package.json, so npx ecloud works 🙏

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that'd only work if it's found locally, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user installs the cli globally then it will be available everywhere, is that what you mean?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nah, i mean people normally use npx to run packages without installing them. if it's not on their machine at all, wouldn't they need to do npx @layr-labs/ecloud-cli app deploy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh, sorry yes! It would be npx @layr-labs/ecloud-cli if its not installed, and npx ecloud if it is!

--private-key <your-private-key> \
--environment sepolia \
--image <docker-image-reference>
Expand All @@ -74,14 +74,12 @@ pnpm ecloud app deploy \
- `--private-key`: Your Ethereum private key (or set `ECLOUD_PRIVATE_KEY` env var)
- `--environment`: Target environment (`sepolia` or `mainnet-alpha`)
- `--rpc-url`: Custom RPC URL (optional, or set `ECLOUD_RPC_URL` env var)
- `--api-base-url`: API base URL (optional, or set `ECLOUD_API_BASE_URL` env var)

**Example:**
```bash
pnpm ecloud app deploy \
npx ecloud app deploy \
--private-key 0x... \
--environment sepolia \
--image myapp:latest
--environment sepolia
```

### SDK Usage
Expand Down
9 changes: 6 additions & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@
"ecloud": "./bin/run.js"
},
"dependencies": {
"@inquirer/prompts": "^7.10.1",
"@layr-labs/ecloud-sdk": "workspace:*",
"@oclif/core": "^4.8.0",
"viem": "^2.38.6",
"dockerode": "^4.0.9",
"handlebars": "^4.7.8",
"js-yaml": "^4.1.1",
"node-forge": "^1.3.1",
"undici": "^7.16.0"
"undici": "^7.16.0",
"viem": "^2.38.6"
},
"scripts": {
"build": "tsup",
"build": "tsup && npm run build:copy-templates",
"build:copy-templates": "node scripts/copy-templates.js",
"lint": "eslint .",
"format": "prettier --check .",
"format:fix": "prettier --write .",
Expand Down
69 changes: 69 additions & 0 deletions packages/cli/scripts/copy-templates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#!/usr/bin/env node

import * as fs from "fs";
import * as path from "path";
import { fileURLToPath } from "url";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const cliRoot = path.resolve(__dirname, "..");
const sdkRoot = path.resolve(cliRoot, "../sdk");

// Template files to copy from SDK to CLI dist
const templates = [
"src/client/common/templates/Dockerfile.layered.tmpl",
"src/client/common/templates/compute-source-env.sh.tmpl",
];

// Create templates directory in CLI dist
const distTemplatesDir = path.join(cliRoot, "dist", "templates");
if (!fs.existsSync(distTemplatesDir)) {
fs.mkdirSync(distTemplatesDir, { recursive: true });
}

// Copy each template file
for (const template of templates) {
const srcPath = path.join(sdkRoot, template);
const filename = path.basename(template);
const destPath = path.join(distTemplatesDir, filename);

if (!fs.existsSync(srcPath)) {
console.warn(`Warning: Template file not found: ${srcPath}`);
continue;
}

fs.copyFileSync(srcPath, destPath);
console.log(`Copied ${filename} to dist/templates/`);
}

console.log("Template files copied successfully");

// Copy keys directory structure from SDK to CLI dist
const keysSrcDir = path.join(sdkRoot, "keys");
const keysDistDir = path.join(cliRoot, "dist", "keys");

if (fs.existsSync(keysSrcDir)) {
// Copy entire keys directory structure recursively
function copyDirRecursive(src, dest) {
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true });
}

const entries = fs.readdirSync(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);

if (entry.isDirectory()) {
copyDirRecursive(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}

copyDirRecursive(keysSrcDir, keysDistDir);

console.log("Keys directory copied successfully");
} else {
console.warn("Warning: Keys directory not found at", keysSrcDir);
}
12 changes: 6 additions & 6 deletions packages/cli/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { createECloudClient } from "@ecloud/sdk";

export function loadClient(flags: {
privateKey: string;
verbose: boolean;
environment: string;
rpcUrl?: string;
apiBaseUrl?: string;
"private-key": string;
"rpc-url"?: string;
}) {
return createECloudClient({
privateKey: flags.privateKey as `0x${string}`,
verbose: flags.verbose,
environment: flags.environment,
rpcUrl: flags.rpcUrl,
apiBaseUrl: flags.apiBaseUrl,
privateKey: flags["private-key"] as `0x${string}`,
rpcUrl: flags["rpc-url"],
});
}
28 changes: 28 additions & 0 deletions packages/cli/src/commands/app/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Command, Flags } from "@oclif/core";
import { createApp } from "@ecloud/sdk";

export default class AppCreate extends Command {
static description = "Create a new app";

// CreateApp flags
static flags = {
name: Flags.string(),
language: Flags.string(),
template: Flags.string(),
templateVersion: Flags.string(),
verbose: Flags.boolean(),
};

async run() {
const { flags } = await this.parse(AppCreate);

// Skip creating client and call createApp directly
return createApp(flags, {
info: (msg: string, ...args: any[]) => console.log(msg, ...args),
warn: (msg: string, ...args: any[]) => console.warn(msg, ...args),
error: (msg: string, ...args: any[]) => console.error(msg, ...args),
debug: (msg: string, ...args: any[]) =>
flags.verbose && console.debug(msg, ...args),
});
}
}
51 changes: 42 additions & 9 deletions packages/cli/src/commands/app/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command, Flags } from "@oclif/core";
import { logVisibility } from "@ecloud/sdk";
import { loadClient } from "../../client";
import { commonFlags } from "../../flags";

Expand All @@ -7,22 +8,54 @@ export default class AppDeploy extends Command {

static flags = {
...commonFlags,
image: Flags.string({ required: true }),
owner: Flags.string(),
cpu: Flags.integer(),
memory: Flags.integer(),
salt: Flags.string(),
name: Flags.string({
required: false,
description: "Friendly name for the app",
env: "ECLOUD_NAME",
}),
dockerfile: Flags.string({
required: false,
description: "Path to Dockerfile",
env: "ECLOUD_DOCKERFILE_PATH",
}),
"image-ref": Flags.string({
required: false,
description: "Image reference pointing to registry",
env: "ECLOUD_IMAGE_REF",
}),
"env-file": Flags.string({
required: false,
description: 'Environment file to use (default: ".env")',
default: ".env",
env: "ECLOUD_ENVFILE_PATH",
}),
"log-visibility": Flags.string({
required: false,
description: "Log visibility setting: public, private, or off",
options: ["public", "private", "off"],
env: "ECLOUD_LOG_VISIBILITY",
}),
"instance-type": Flags.string({
required: false,
description:
"Machine instance type to use e.g. g1-standard-4t, g1-standard-8t",
default: "g1-standard-4t",
options: ["g1-standard-4t", "g1-standard-8t"],
env: "ECLOUD_INSTANCE_TYPE",
}),
};

async run() {
const { flags } = await this.parse(AppDeploy);
const client = loadClient(flags);

const res = await client.app.deploy({
image: flags.image,
owner: flags.owner as `0x${string}` | undefined,
resources: { cpu: flags.cpu, memoryMiB: flags.memory },
salt: flags.salt as `0x${string}` | undefined,
name: flags.name,
dockerfile: flags.dockerfile,
envFile: flags["env-file"],
imageRef: flags["image-ref"],
logVisibility: flags["log-visibility"] as logVisibility,
instanceType: flags["instance-type"],
});

this.log(JSON.stringify(res, null, 2));
Expand Down
19 changes: 16 additions & 3 deletions packages/cli/src/flags.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
import { Flags } from "@oclif/core";

export const commonFlags = {
privateKey: Flags.string({ required: true, env: "ECLOUD_PRIVATE_KEY" }),
environment: Flags.string({
required: true,
description: "Deployment environment to use",
options: ["sepolia", "mainnet-alpha"],
env: "ECLOUD_ENV",
}),
rpcUrl: Flags.string({ required: false, env: "ECLOUD_RPC_URL" }),
apiBaseUrl: Flags.string({ required: false, env: "ECLOUD_API_BASE_URL" }),
"private-key": Flags.string({
required: true,
description: "Private key for signing transactions",
env: "ECLOUD_PRIVATE_KEY",
}),
"rpc-url": Flags.string({
required: false,
description: "RPC URL to connect to blockchain",
env: "ECLOUD_RPC_URL",
}),
verbose: Flags.boolean({
required: false,
description: "Enable verbose logging (default: false)",
default: false,
}),
};
14 changes: 14 additions & 0 deletions packages/sdk/keys/mainnet-alpha/prod/kms-encryption-public-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0kHU86k17ofCIGcJKDcf
AFurFhSLeWmOL0bwWLCeVnTPG0MMHtJOq+woE0XXSWw6lzm+jzavBBTwKde1dgal
Ap91vULAZFMUpiUdd2dNUVtvU89qW0Pgf1Eu5FDj7BkY/SnyECbWJM4ga0BmpiGy
nQwLNN9mMGhjVoVLn2zwEGZ7JzS9Nz11EZKO/k/9DcO6LaoIFmKuvVf3jl6lvZg8
aeA0LoZXjkycHlRUt/kfKwZnhakUaYHP1ksV7ZNmolS5GYDTSKGB2KPPNR1s4/Xu
u8zeEFC8HuGRU8XuuBeaAunitnGhbNVREUNJGff6HZOGB6CIFNXjbQETeZ3p5uro
0v+hd1QqQYBv7+DEaMCmGnJNGAyIMr2mn4vr7wGsIj0HonlSHmQ8rmdUhL2ocNTc
LhKgZiZmBuDpSbFW/r53R2G7CHcqaqGeUBnT54QCH4zsYKw0/4dOtwFxQpTyBf9/
+k+KaWEJYKkx9d9OzKGyAvzrTDVOFoajddiJ6LPvRlMdOUQr3hl4IAC0/nh9lhHq
D0R+i5WAU96TkdAe7B7iTGH2D22k0KUPR6Q9W3aF353SLxQAMPNrgG4QQufAdRJn
AF+8ntun5TkTqjTWRSwAsUJZ1z4wb96DympWJbDi0OciJRZ3Fz3j9+amC43yCHGg
aaEMjdt35ewbztUSc04F10MCAwEAAQ==
-----END PUBLIC KEY-----
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfxbhXJjH4D0DH/iW5/rK1HzWS+f9
EyooZTrCYjCfezuOEmRuOWNaZLvwXN8SdzrvjWA7gSvOS85hLzp4grANRQ==
-----END PUBLIC KEY-----
14 changes: 14 additions & 0 deletions packages/sdk/keys/sepolia/dev/kms-encryption-public-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr/vqttU6aXX35HtsXavU
5teysunDzZB3HyaFM4qcuRnqj+70KxqLOwZsERN5SwZ/56Jm8T2ds1CcXsQCMUMw
+MPlsF6KMGfzghLtYHONwvKLnn+U9y886aAay6W8a0A7O7YCZehNYD3kQnCXjOIc
Mj6v8AEvMw+w/lNabjRXnwSBMKVIGp/cSL0hGwt8fGoC3TsxQN9opzvU1Z4rAw9K
a119l6dlPnqezDva378TCaXDjqKe/jSZOI1CcYpaSK2SJ+95Wbvte5j3lXbg1oT2
0rXeJUHEJ68QxMtJplfw0Sg+Ek4CUJ2c/kbdg0u7sIIO5wcB4WHL/Lfbw2XPmcBI
t0r0EC575D3iHF/aI01Ms2IRA0GDeHnNcr5FJLWJljTjNLEt4tFITrXwBe1Ealm3
NCxamApl5bBSwQ72Gb5fiQFwB8Fl2/XG3wfGTFInFEvWE4c/H8dtu1wHTsyEFZcG
B47IkD5GBSZq90Hd9xuZva55dxGpqUVrEJO88SqHGP9Oa+HLTYdEe5AR5Hitw4Mu
dk1cCH+X5OqY9dfpdoCNbKAM0N2SJvNAnDTU2JKGYheXrnDslXR6atBmU5gDkH+W
QVryDYl9xbwWIACMQsAQjrrtKw5xqJ4V89+06FN/wyEVF7KWAcJ4AhKiVnCvLqzb
BbISc+gOkRsefhCDJVPEKDkCAwEAAQ==
-----END PUBLIC KEY-----
4 changes: 4 additions & 0 deletions packages/sdk/keys/sepolia/dev/kms-signing-public-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEb2Q88/cxdic2xi4jS2V0dtYHjLwq
4wVFBFmaY8TTXoMXNggKEdU6PuE8EovocVKMpw3SIlaM27z9uxksNVL2xw==
-----END PUBLIC KEY-----
14 changes: 14 additions & 0 deletions packages/sdk/keys/sepolia/prod/kms-encryption-public-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApDvk8pAivkgtiC5li5MP
xMTJDduTeorBl18ynrooTxp2BwwgPwXfXbJaCA0qRubvc0aO2uh2VDrPM27CqMLH
o2S9YLtpLii4A1Nl7SE/MdWKWdG6v94xNGpc2YyPP7yWtHfqOkgDWp8sokl3Uq/9
MS0pjUaI7RyS5boCTy8Qw90BxGMpucjOmqm+luw4EdPWZCrgriUR2bbGRRgAmrT1
K4ou4IgPp799r120hwHbCWxnOvLdQdpiv2507b900xS/3yZahhnHCAn66146LU/f
BrRpQKSM0qSpktXrrc9MH/ru2VLR5cGLp89ZcZMQA9cRGglWM5XWVY3Ti2TPJ6Kd
An1d7qNkGJaSdVa3x3HkOf6c6HeTyqis5/L/6L+PFhUsTRbmKg1FtwD+3xxdyf7h
abFxryE9rv+WatHL6r6z5ztV0znJ/Fpfs5A45FWA6pfb28fA59RGpi/DQ8RxgdCH
nZRNvdz8dTgRaXSPgkfGXBcCFqb/QhFmad7XbWDthGzfhbPOxNPtiaGRQ1Dr/Pgq
n0ugdLbRQLmDOAFgaQcnr0U4y1TUlWJnvoZMETkVN7gmITtXA4F324ALT7Rd+Lgk
HikW5vG+NjAEwXfPsK0YzT+VbHd7o1lbru9UxiDlN03XVEkz/oRQi47CvSTo3FSr
5dB4lz8kov3UUcNJfQFZolMCAwEAAQ==
-----END PUBLIC KEY-----
4 changes: 4 additions & 0 deletions packages/sdk/keys/sepolia/prod/kms-signing-public-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsk6ZdmmvBqFfKHs+1cYjIemRGN7h
1NatIEitFRyx+3q8wmTJ9LknTE1FwWBLcCNTseJDti8Rh+SaVxfGOyJuuA==
-----END PUBLIC KEY-----
6 changes: 5 additions & 1 deletion packages/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,24 @@
}
},
"scripts": {
"build": "tsup",
"build": "tsup && npm run build:copy-templates",
"build:copy-templates": "node scripts/copy-templates.js",
"lint": "eslint .",
"format": "prettier --check .",
"format:fix": "prettier --write ."
},
"dependencies": {
"@inquirer/prompts": "^7.10.1",
"dockerode": "^4.0.9",
"handlebars": "^4.7.8",
"js-yaml": "^4.1.1",
"node-forge": "^1.3.1",
"undici": "^7.16.0",
"viem": "^2.38.6"
},
"devDependencies": {
"@types/dockerode": "^3.3.45",
"@types/js-yaml": "^4.0.9",
"@types/node-forge": "^1.3.14"
}
}
Loading