Skip to content

Commit 1d809ec

Browse files
committed
Add plot agent generate command
1 parent 0ac9a95 commit 1d809ec

7 files changed

Lines changed: 1012 additions & 106 deletions

File tree

.changeset/bold-humans-throw.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@plotday/sdk": minor
3+
---
4+
5+
Add plot agent generate command

sdk/cli/commands/deploy.ts

Lines changed: 91 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import { bundleAgent } from "../utils/bundle";
99

1010
interface DeployOptions {
1111
dir: string;
12-
spec?: string;
1312
id?: string;
1413
deployToken?: string;
1514
apiUrl: string;
1615
name?: string;
1716
description?: string;
1817
environment?: string;
18+
dryRun?: boolean;
1919
}
2020

2121
interface PackageJson {
@@ -32,40 +32,19 @@ interface PackageJson {
3232
env?: Record<string, any>;
3333
}
3434

35+
interface AgentSource {
36+
dependencies: Record<string, string>;
37+
files: Record<string, string>;
38+
}
39+
3540
export async function deployCommand(options: DeployOptions) {
3641
const agentPath = path.resolve(process.cwd(), options.dir);
3742

38-
// Read spec file if provided
39-
let specContent: string | undefined;
40-
if (options.spec) {
41-
const specPath = path.resolve(process.cwd(), options.spec);
42-
if (!fs.existsSync(specPath)) {
43-
out.error(`Spec file not found: ${options.spec}`);
44-
process.exit(1);
45-
}
46-
try {
47-
specContent = fs.readFileSync(specPath, "utf-8");
48-
} catch (error) {
49-
out.error("Failed to read spec file", String(error));
50-
process.exit(1);
51-
}
52-
}
53-
54-
// Verify we're in an agent directory by checking for package.json
55-
// Package.json is required when deploying source code, optional for spec
43+
// Check for package.json
5644
const packageJsonPath = path.join(agentPath, "package.json");
5745
let packageJson: PackageJson | undefined;
5846

59-
if (!options.spec) {
60-
// Source code deployment requires package.json
61-
if (!fs.existsSync(packageJsonPath)) {
62-
out.error(
63-
"package.json not found. Are you in an agent directory?",
64-
"Run this command from your agent's root directory, or use --spec to deploy from a spec file"
65-
);
66-
process.exit(1);
67-
}
68-
47+
if (fs.existsSync(packageJsonPath)) {
6948
// Read and validate package.json
7049
try {
7150
const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8");
@@ -74,13 +53,41 @@ export async function deployCommand(options: DeployOptions) {
7453
out.error("Failed to parse package.json", String(error));
7554
process.exit(1);
7655
}
77-
} else if (fs.existsSync(packageJsonPath)) {
78-
// Optional: read package.json for defaults when using spec
79-
try {
80-
const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8");
81-
packageJson = JSON.parse(packageJsonContent);
82-
} catch {
83-
// Ignore errors, package.json is optional for spec deployments
56+
} else {
57+
// No package.json - check for plot-agent.md as fallback
58+
const specPath = path.join(agentPath, "plot-agent.md");
59+
if (fs.existsSync(specPath)) {
60+
out.info(
61+
"No package.json found, but plot-agent.md exists",
62+
["Generating agent from spec first..."]
63+
);
64+
65+
// Import and run generate command
66+
const { generateCommand } = await import("./generate");
67+
await generateCommand({
68+
dir: options.dir,
69+
id: options.id,
70+
deployToken: options.deployToken,
71+
apiUrl: options.apiUrl,
72+
});
73+
74+
// Re-read the generated package.json
75+
try {
76+
const packageJsonContent = fs.readFileSync(packageJsonPath, "utf-8");
77+
packageJson = JSON.parse(packageJsonContent);
78+
} catch (error) {
79+
out.error("Failed to read generated package.json", String(error));
80+
process.exit(1);
81+
}
82+
83+
out.blank();
84+
out.progress("Continuing with deployment...");
85+
} else {
86+
out.error(
87+
"Neither package.json nor plot-agent.md found",
88+
"Run 'plot agent create' to create a new agent, or create a plot-agent.md spec file"
89+
);
90+
process.exit(1);
8491
}
8592
}
8693

@@ -91,26 +98,8 @@ export async function deployCommand(options: DeployOptions) {
9198

9299
const environment = options.environment || "personal";
93100

94-
// For spec deployments without package.json, require CLI options
95-
if (options.spec && !packageJson) {
96-
if (!options.id) {
97-
out.error(
98-
"Agent ID is required when deploying spec without package.json",
99-
"Provide --id flag"
100-
);
101-
process.exit(1);
102-
}
103-
if (!options.name) {
104-
out.error(
105-
"Agent name is required when deploying spec without package.json",
106-
"Provide --name flag"
107-
);
108-
process.exit(1);
109-
}
110-
}
111-
112-
// Validate required fields for source code deployments
113-
if (!options.spec && !agentName) {
101+
// Validate required fields
102+
if (!agentName) {
114103
out.error(
115104
"package.json is missing displayName",
116105
'Add "displayName": "Your Agent Name" to package.json'
@@ -200,55 +189,47 @@ export async function deployCommand(options: DeployOptions) {
200189
}
201190
}
202191

203-
// Prepare request body based on spec or source code
192+
// Build the agent
204193
let requestBody: {
205-
module?: string;
206-
spec?: string;
194+
module: string;
207195
name: string;
208196
description?: string;
209197
environment: string;
198+
dryRun?: boolean;
210199
};
211200

212201
try {
213-
if (options.spec && specContent) {
214-
// Deploying from spec
215-
out.progress(`Deploying ${deploymentName} from spec...`);
216-
217-
requestBody = {
218-
spec: specContent,
219-
name: deploymentName!,
220-
description: deploymentDescription,
221-
environment: environment,
222-
};
223-
} else {
224-
// Deploying from source code - build the agent
225-
out.progress(`Building ${deploymentName}...`);
202+
out.progress(
203+
options.dryRun
204+
? `Validating ${deploymentName}...`
205+
: `Building ${deploymentName}...`
206+
);
226207

227-
const result = await bundleAgent(agentPath, {
228-
minify: false,
229-
sourcemap: true,
230-
});
208+
const result = await bundleAgent(agentPath, {
209+
minify: false,
210+
sourcemap: true,
211+
});
231212

232-
const moduleContent = result.code;
213+
const moduleContent = result.code;
233214

234-
if (result.warnings.length > 0) {
235-
out.warning("Build completed with warnings");
236-
for (const warning of result.warnings.slice(0, 5)) {
237-
console.warn(` ${warning}`);
238-
}
239-
if (result.warnings.length > 5) {
240-
console.warn(` ... and ${result.warnings.length - 5} more warnings`);
241-
}
215+
if (result.warnings.length > 0) {
216+
out.warning("Build completed with warnings");
217+
for (const warning of result.warnings.slice(0, 5)) {
218+
console.warn(` ${warning}`);
219+
}
220+
if (result.warnings.length > 5) {
221+
console.warn(` ... and ${result.warnings.length - 5} more warnings`);
242222
}
243-
244-
requestBody = {
245-
module: moduleContent,
246-
name: deploymentName!,
247-
description: deploymentDescription,
248-
environment: environment,
249-
};
250223
}
251224

225+
requestBody = {
226+
module: moduleContent,
227+
name: deploymentName!,
228+
description: deploymentDescription,
229+
environment: environment,
230+
dryRun: options.dryRun,
231+
};
232+
252233
// Validate all required deployment fields
253234
if (!deploymentName) {
254235
out.error(
@@ -291,6 +272,24 @@ export async function deployCommand(options: DeployOptions) {
291272

292273
const result = (await response.json()) as any;
293274

275+
// Handle dryRun response
276+
if (options.dryRun) {
277+
if (result.errors && result.errors.length > 0) {
278+
out.error("Validation failed");
279+
for (const error of result.errors) {
280+
console.error(` ${error}`);
281+
}
282+
process.exit(1);
283+
} else {
284+
out.success("Validation passed - agent is ready to deploy");
285+
out.info(
286+
"Run without --dry-run to deploy",
287+
[`plot agent deploy`]
288+
);
289+
}
290+
return;
291+
}
292+
294293
// Show dependencies from API response
295294
const dependencies = result.dependencies;
296295
if (dependencies && dependencies.length > 0) {

0 commit comments

Comments
 (0)