Skip to content

Commit

Permalink
Add CI File with Basic Lint (i.e. Prettier) (#20)
Browse files Browse the repository at this point in the history
Introducing some moderate CI. This requires linting with prettier. In a
follow up I will, introduce eslint.
  • Loading branch information
bh2smith authored Nov 19, 2024
1 parent 3975c2e commit 5931ca6
Show file tree
Hide file tree
Showing 21 changed files with 793 additions and 617 deletions.
40 changes: 40 additions & 0 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Node.js CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
types:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Install Bun
run: |
curl -fsSL https://bun.sh/install | bash
export PATH="$HOME/.bun/bin:$PATH"
- name: Cache node modules
uses: actions/cache@v4
with:
path: |
node_modules
~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lockb') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install, Lint & Build
run: |
export PATH="$HOME/.bun/bin:$PATH"
bun install --frozen-lockfile
bun lint
bun run build
Binary file modified bun.lockb
Binary file not shown.
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
"dev": "bun run ./src/index.ts dev --port 3000",
"build": "bun build ./src/index.ts --outdir ./dist --target node",
"prepublishOnly": "bun run build",
"build-binary": "bun build ./src/index.ts --compile --outfile make-agent"
"build-binary": "bun build ./src/index.ts --compile --outfile make-agent",
"lint": "prettier --check 'src/**/*.{js,jsx,ts,tsx}'",
"fmt": "prettier --write 'src/**/*.{js,jsx,ts,tsx}'"
},
"keywords": [
"ai",
Expand All @@ -31,7 +33,8 @@
"license": "MIT",
"devDependencies": {
"@types/bun": "latest",
"@types/localtunnel": "^2.0.4"
"@types/localtunnel": "^2.0.4",
"prettier": "^3.3.3"
},
"peerDependencies": {
"typescript": "^5.0.0"
Expand Down
88 changes: 49 additions & 39 deletions src/commands/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,37 +28,40 @@ export const contractCommand = new Command()
}
});


function showLoadingMessage(message: string): void {
console.log(`${message}...`);
}

async function promptQuestions() {
const questions = [
{
type: 'input',
name: 'contract',
message: 'Enter the Near Protocol contract name:',
validate: (input: string) => input.length > 0 || 'Contract name is required'
type: "input",
name: "contract",
message: "Enter the Near Protocol contract name:",
validate: (input: string) =>
input.length > 0 || "Contract name is required",
},
{
type: 'input',
name: 'description',
message: 'Enter the contract description (agent instructions):',
validate: (input: string) => input.length > 0 || 'Contract description is required'
type: "input",
name: "description",
message: "Enter the contract description (agent instructions):",
validate: (input: string) =>
input.length > 0 || "Contract description is required",
},
{
type: 'input',
name: 'output',
message: 'Enter the output directory (press Enter for current directory):',
default: '.',
type: "input",
name: "output",
message:
"Enter the output directory (press Enter for current directory):",
default: ".",
},
{
type: 'input',
name: 'accountId',
message: 'Enter your near account ID to generate an API key:',
validate: (input: string) => input.length > 0 || 'Near account ID is required'
}
type: "input",
name: "accountId",
message: "Enter your near account ID to generate an API key:",
validate: (input: string) =>
input.length > 0 || "Near account ID is required",
},
];

return await inquirer.prompt<{
Expand All @@ -75,38 +78,46 @@ async function generateTypes(outputDir: string, contract: string) {
await execAsync(`npx near2ts ${contract}`);
}

async function generateAIAgent(answers: {
contract: string;
description: string;
accountId: string;
}, outputDir: string) {
async function generateAIAgent(
answers: {
contract: string;
description: string;
accountId: string;
},
outputDir: string,
) {
const apiUrl = "https://contract-to-agent.vercel.app/api/generate";

showLoadingMessage("Generating AI agent");

const typesContent = await fs.readFile(path.join(outputDir, `contract_types.ts`), 'utf-8');
const typesContent = await fs.readFile(
path.join(outputDir, `contract_types.ts`),
"utf-8",
);

const postData = {
contract: answers.contract,
contractDescription: answers.description,
accountId: answers.accountId,
types: typesContent
types: typesContent,
};

const response = await fetch(apiUrl, {
method: 'POST',
method: "POST",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
body: JSON.stringify(postData),
});

if (response.status === 429) {
throw new Error('You have reached the daily prompt limit. Please try again tomorrow.');
throw new Error(
"You have reached the daily prompt limit. Please try again tomorrow.",
);
}

if (!response.ok) {
console.error('Failed to generate AI agent');
console.error("Failed to generate AI agent");
throw new Error(`HTTP error! status: ${response.status}`);
}

Expand All @@ -117,7 +128,7 @@ async function generateAIAgent(answers: {
async function writeFiles(outputDir: string, code: string, contract: string) {
await fs.mkdir(outputDir, { recursive: true });

await fs.writeFile(path.join(outputDir, 'index.ts'), code);
await fs.writeFile(path.join(outputDir, "index.ts"), code);

const tsConfig = {
compilerOptions: {
Expand All @@ -134,7 +145,7 @@ async function writeFiles(outputDir: string, code: string, contract: string) {
};
await fs.writeFile(
path.join(outputDir, "tsconfig.json"),
JSON.stringify(tsConfig, null, 2)
JSON.stringify(tsConfig, null, 2),
);

const packageJson = {
Expand All @@ -150,15 +161,15 @@ async function writeFiles(outputDir: string, code: string, contract: string) {
express: "^4.17.1",
"@types/express": "^4.17.13",
"make-agent": "latest",
"dotenv": "^10.0.0",
dotenv: "^10.0.0",
},
devDependencies: {
typescript: "^4.5.4",
},
};
await fs.writeFile(
path.join(outputDir, "package.json"),
JSON.stringify(packageJson, null, 2)
JSON.stringify(packageJson, null, 2),
);
}

Expand All @@ -169,7 +180,7 @@ async function setupAndRunAgent(outputDir: string) {
await execAsync("npm install --legacy-peer-deps");

showLoadingMessage("Running server");
const serverProcess = spawn('npx', ['tsx', './index.ts']);
const serverProcess = spawn("npx", ["tsx", "./index.ts"]);

serverProcess.stdout.on("data", (data) => {
console.log(`Server: ${data}`);
Expand All @@ -181,15 +192,15 @@ async function setupAndRunAgent(outputDir: string) {

// Wait for the server to start
await new Promise((resolve) => {
serverProcess.stdout.on('data', (data) => {
if (data.toString().includes('Server is running')) {
serverProcess.stdout.on("data", (data) => {
if (data.toString().includes("Server is running")) {
resolve(true);
}
});
});

showLoadingMessage("Running agent");
const agentProcess = spawn('npx', ['make-agent', 'dev', '-p', '8080']);
const agentProcess = spawn("npx", ["make-agent", "dev", "-p", "8080"]);

agentProcess.stdout.on("data", (data) => {
console.log(`Agent: ${data}`);
Expand All @@ -201,10 +212,9 @@ async function setupAndRunAgent(outputDir: string) {

agentProcess.on("close", (code) => {
console.log(`make-agent process exited with code ${code}`);
serverProcess.kill();
serverProcess.kill();
});

// Keep the main process running
await new Promise((resolve) => {});
}

64 changes: 32 additions & 32 deletions src/commands/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,41 @@ import { deployedUrl } from "../utils/deployed-url";
import { getAuthentication } from "../services/signer-service";

export const deleteCommand = new Command()
.name('delete')
.description('Delete your AI agent plugin')
.option('-u, --url <url>', 'Specify the deployment URL')
.action(async (options) => {
const url = options.url || deployedUrl;
.name("delete")
.description("Delete your AI agent plugin")
.option("-u, --url <url>", "Specify the deployment URL")
.action(async (options) => {
const url = options.url || deployedUrl;

if (!url) {
console.error('Deployed URL could not be determined.');
return;
}
if (!url) {
console.error("Deployed URL could not be determined.");
return;
}

const pluginId = getHostname(url);
const specUrl = getSpecUrl(url);
const { isValid, accountId } = await validateAndParseOpenApiSpec(specUrl);
const pluginId = getHostname(url);
const specUrl = getSpecUrl(url);
const { isValid, accountId } = await validateAndParseOpenApiSpec(specUrl);

if (!isValid) {
console.error('OpenAPI specification validation failed.');
return;
}
if (!isValid) {
console.error("OpenAPI specification validation failed.");
return;
}

if (!accountId) {
console.error('Failed to parse account ID from OpenAPI specification.');
return;
}
if (!accountId) {
console.error("Failed to parse account ID from OpenAPI specification.");
return;
}

const authentication = await getAuthentication(accountId);
if (!authentication) {
console.error('Authentication failed. Unable to delete the plugin.');
return;
}
const authentication = await getAuthentication(accountId);
if (!authentication) {
console.error("Authentication failed. Unable to delete the plugin.");
return;
}

try {
await deletePlugin(pluginId);
console.log(`Plugin ${pluginId} deleted successfully.`);
} catch (error) {
console.error('Failed to delete the plugin:', error);
}
});
try {
await deletePlugin(pluginId);
console.log(`Plugin ${pluginId} deleted successfully.`);
} catch (error) {
console.error("Failed to delete the plugin:", error);
}
});
Loading

0 comments on commit 5931ca6

Please sign in to comment.