@andreprado/agentkit 0.1.0-alpha.10
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 +69 -0
- package/bin/agentkit.mjs +23 -0
- package/docs/guides/add-channel.md +114 -0
- package/docs/guides/add-knowledge.md +134 -0
- package/docs/guides/add-tool.md +342 -0
- package/docs/guides/agentkit-skills-architecture.md +471 -0
- package/docs/guides/channel-security.md +81 -0
- package/docs/guides/channels-implementation-map.md +243 -0
- package/docs/guides/channels-production-handoff.md +102 -0
- package/docs/guides/connect-telegram.md +110 -0
- package/docs/guides/connect-whatsapp-zapster.md +119 -0
- package/docs/guides/create-agent.md +220 -0
- package/docs/guides/prepare-deploy.md +209 -0
- package/docs/guides/run-evals.md +179 -0
- package/docs/guides/security-rules.md +156 -0
- package/docs/guides/use-provider.md +140 -0
- package/docs/llms-full.txt +876 -0
- package/docs/llms.txt +83 -0
- package/docs/portable-deploy-release-checklist.md +41 -0
- package/package.json +47 -0
- package/src/cli/args.ts +36 -0
- package/src/cli/cloud-client.ts +265 -0
- package/src/cli/commands/channels.ts +810 -0
- package/src/cli/commands/knowledge.ts +136 -0
- package/src/cli/constants.ts +4 -0
- package/src/cli/deploy-chat-ui.ts +392 -0
- package/src/cli/deploy-readiness.ts +348 -0
- package/src/cli/flags.ts +162 -0
- package/src/cli/help.ts +184 -0
- package/src/cli/index.ts +1276 -0
- package/src/cli/process.ts +31 -0
- package/src/cloud/artifact.ts +139 -0
- package/src/cloud/client.ts +79 -0
- package/src/cloud/contracts.ts +63 -0
- package/src/cloud/index.ts +3 -0
- package/src/create-project.ts +177 -0
- package/src/index.ts +408 -0
- package/src/providers/index.ts +25 -0
- package/src/providers/pi.ts +286 -0
- package/src/providers/test.ts +133 -0
- package/src/providers/types.ts +34 -0
- package/src/runtime/build.ts +43 -0
- package/src/runtime/channel-buffer.ts +30 -0
- package/src/runtime/channel-test-harness.ts +112 -0
- package/src/runtime/channels/telegram.ts +360 -0
- package/src/runtime/channels/website.ts +132 -0
- package/src/runtime/channels/whatsapp-meta.ts +71 -0
- package/src/runtime/channels/whatsapp-zapster.ts +278 -0
- package/src/runtime/channels.ts +138 -0
- package/src/runtime/chat.ts +218 -0
- package/src/runtime/config.ts +684 -0
- package/src/runtime/conversations.ts +38 -0
- package/src/runtime/core/deploy-state.ts +54 -0
- package/src/runtime/core/manifest.ts +213 -0
- package/src/runtime/core/targets.ts +133 -0
- package/src/runtime/database.ts +256 -0
- package/src/runtime/db-commands.ts +167 -0
- package/src/runtime/deploy-readiness.ts +105 -0
- package/src/runtime/deploy.ts +1 -0
- package/src/runtime/dev-server.ts +1247 -0
- package/src/runtime/docs.ts +36 -0
- package/src/runtime/env.ts +152 -0
- package/src/runtime/errors.ts +13 -0
- package/src/runtime/evals.ts +509 -0
- package/src/runtime/inspect.ts +203 -0
- package/src/runtime/knowledge/chunk.ts +333 -0
- package/src/runtime/knowledge/config.ts +135 -0
- package/src/runtime/knowledge/embeddings.ts +133 -0
- package/src/runtime/knowledge/ingest.ts +521 -0
- package/src/runtime/knowledge/prompt-policy.ts +30 -0
- package/src/runtime/knowledge/retrieve.ts +283 -0
- package/src/runtime/knowledge/schema.ts +56 -0
- package/src/runtime/knowledge/tool.ts +64 -0
- package/src/runtime/knowledge/vector.ts +258 -0
- package/src/runtime/runtime-contract.ts +93 -0
- package/src/runtime/spec.ts +152 -0
- package/src/runtime/sync.ts +144 -0
- package/src/runtime/targets/cloudflare/build.ts +2517 -0
- package/src/runtime/targets/container/build.ts +146 -0
- package/src/runtime/targets/container/server.ts +33 -0
- package/src/runtime/targets/vps/deploy.ts +206 -0
- package/src/runtime/tool-runner.ts +65 -0
- package/src/runtime/tools.ts +470 -0
- package/src/runtime/traces.ts +41 -0
- package/src/storage/sqlite.ts +1118 -0
- package/src/templates/blank.ts +394 -0
- package/src/templates/dentista.ts +1003 -0
- package/src/templates/index.ts +33 -0
- package/src/templates/skills/agentkit-build-agent/SKILL.md +51 -0
- package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +20 -0
- package/src/templates/skills/agentkit-build-agent/templates/sales-qualifier.instructions.md +17 -0
- package/src/templates/skills/agentkit-build-agent/templates/support-agent.instructions.md +16 -0
- package/src/templates/skills/agentkit-capsule/SKILL.md +62 -0
- package/src/templates/skills/agentkit-capsule/references/docs-router.md +15 -0
- package/src/templates/skills/agentkit-channels/SKILL.md +62 -0
- package/src/templates/skills/agentkit-channels/references/channel-buffering.md +58 -0
- package/src/templates/skills/agentkit-channels/references/channel-debugging.md +41 -0
- package/src/templates/skills/agentkit-channels/references/telegram.md +38 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +44 -0
- package/src/templates/skills/agentkit-database/SKILL.md +45 -0
- package/src/templates/skills/agentkit-database/templates/appointments.schema.sql +15 -0
- package/src/templates/skills/agentkit-database/templates/leads.schema.sql +17 -0
- package/src/templates/skills/agentkit-deploy/SKILL.md +44 -0
- package/src/templates/skills/agentkit-evals/SKILL.md +60 -0
- package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +22 -0
- package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +14 -0
- package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +14 -0
- package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +18 -0
- package/src/templates/skills/agentkit-knowledge/SKILL.md +40 -0
- package/src/templates/skills/agentkit-knowledge/templates/faq.md +14 -0
- package/src/templates/skills/agentkit-knowledge/templates/policies.md +14 -0
- package/src/templates/skills/agentkit-knowledge/templates/prices.csv +3 -0
- package/src/templates/skills/agentkit-prompts/SKILL.md +45 -0
- package/src/templates/skills/agentkit-prompts/templates/knowledge-grounded-faq.instructions.md +11 -0
- package/src/templates/skills/agentkit-provider/SKILL.md +57 -0
- package/src/templates/skills/agentkit-security/SKILL.md +55 -0
- package/src/templates/skills/agentkit-tools/SKILL.md +36 -0
- package/src/templates/skills/agentkit-tools/examples/database-write.tool.md +35 -0
- package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +37 -0
- package/src/templates/skills/agentkit-tools/examples/lookup-order.tool.md +46 -0
- package/src/templates/skills/agentkit-troubleshooting/SKILL.md +52 -0
- package/src/templates/support.ts +401 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
export function runInheritedCommand(command: string, args: string[], cwd: string): Promise<number> {
|
|
4
|
+
return new Promise((resolvePromise, reject) => {
|
|
5
|
+
const child = spawn(command, args, {
|
|
6
|
+
cwd,
|
|
7
|
+
stdio: "inherit",
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
child.once("error", reject);
|
|
11
|
+
child.once("close", (code) => resolvePromise(code ?? 1));
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function waitForShutdown(stop: () => Promise<void>): Promise<void> {
|
|
16
|
+
await new Promise<void>((resolve) => {
|
|
17
|
+
let stopped = false;
|
|
18
|
+
|
|
19
|
+
const shutdown = () => {
|
|
20
|
+
if (stopped) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
stopped = true;
|
|
25
|
+
void stop().finally(resolve);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
process.once("SIGINT", shutdown);
|
|
29
|
+
process.once("SIGTERM", shutdown);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { extname, isAbsolute, relative, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
import type { AgentBuildResult, AgentManifestWithBundle } from "../runtime/targets/cloudflare/build";
|
|
6
|
+
import { AgentKitError } from "../runtime/errors";
|
|
7
|
+
import type { HostedDeployArtifact } from "./contracts";
|
|
8
|
+
|
|
9
|
+
const HOSTED_KNOWLEDGE_MAX_SOURCE_BYTES = 5 * 1024 * 1024;
|
|
10
|
+
const HOSTED_KNOWLEDGE_MAX_TOTAL_BYTES = 9 * 1024 * 1024;
|
|
11
|
+
const HOSTED_KNOWLEDGE_SUPPORTED_EXTENSIONS = new Set([".md", ".markdown", ".txt", ".csv"]);
|
|
12
|
+
|
|
13
|
+
export async function createHostedDeployArtifact(build: AgentBuildResult): Promise<HostedDeployArtifact> {
|
|
14
|
+
const [manifestSource, workerSource, wranglerSource] = await Promise.all([
|
|
15
|
+
readFile(build.manifestPath, "utf8"),
|
|
16
|
+
readFile(build.workerPath, "utf8"),
|
|
17
|
+
readFile(build.wranglerPath, "utf8"),
|
|
18
|
+
]);
|
|
19
|
+
const manifest = JSON.parse(manifestSource) as AgentManifestWithBundle;
|
|
20
|
+
const workerSha256 = sha256(workerSource);
|
|
21
|
+
|
|
22
|
+
if (workerSha256 !== build.bundleSha256 || workerSha256 !== manifest.bundle.sha256) {
|
|
23
|
+
throw new AgentKitError("artifact_integrity_error", "Worker SHA-256 does not match the build manifest.");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const artifact: HostedDeployArtifact = {
|
|
27
|
+
manifest,
|
|
28
|
+
worker: {
|
|
29
|
+
content_type: "application/javascript",
|
|
30
|
+
source: workerSource,
|
|
31
|
+
sha256: workerSha256,
|
|
32
|
+
},
|
|
33
|
+
wrangler: {
|
|
34
|
+
content_type: "application/json",
|
|
35
|
+
source: wranglerSource,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
if (build.tursoSchemaPath) {
|
|
40
|
+
const source = await readFile(build.tursoSchemaPath, "utf8");
|
|
41
|
+
artifact.turso_schema = {
|
|
42
|
+
content_type: "text/sql",
|
|
43
|
+
source,
|
|
44
|
+
sha256: sha256(source),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (manifest.knowledge.enabled) {
|
|
49
|
+
artifact.knowledge_sources = await createHostedKnowledgeSources(build.root, manifest);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return artifact;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function createHostedKnowledgeSources(
|
|
56
|
+
root: string,
|
|
57
|
+
manifest: AgentManifestWithBundle,
|
|
58
|
+
): Promise<NonNullable<HostedDeployArtifact["knowledge_sources"]>> {
|
|
59
|
+
const sources: NonNullable<HostedDeployArtifact["knowledge_sources"]>["sources"] = [];
|
|
60
|
+
let totalBytes = 0;
|
|
61
|
+
|
|
62
|
+
for (const source of manifest.knowledge.sources) {
|
|
63
|
+
if (source.kind === "url") {
|
|
64
|
+
throw new AgentKitError(
|
|
65
|
+
"artifact_knowledge_source_unsupported",
|
|
66
|
+
"Hosted deploy can automatically sync local Knowledge files only. URL Knowledge sources require a hosted ingestion connector.",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const absolutePath = resolve(root, source.value);
|
|
71
|
+
const sourceKey = capsuleRelativePath(root, absolutePath);
|
|
72
|
+
const extension = extname(sourceKey).toLowerCase();
|
|
73
|
+
|
|
74
|
+
if (!HOSTED_KNOWLEDGE_SUPPORTED_EXTENSIONS.has(extension)) {
|
|
75
|
+
throw new AgentKitError(
|
|
76
|
+
"artifact_knowledge_source_unsupported",
|
|
77
|
+
`Knowledge source "${sourceKey}" is not supported for hosted deploy. Use .md, .txt, or .csv.`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const stats = await stat(absolutePath).catch((error) => {
|
|
82
|
+
throw new AgentKitError("artifact_knowledge_source_missing", `Knowledge source "${sourceKey}" was not found.`, {
|
|
83
|
+
cause: error,
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (!stats.isFile()) {
|
|
88
|
+
throw new AgentKitError("artifact_knowledge_source_invalid", `Knowledge source "${sourceKey}" must be a file.`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (stats.size > HOSTED_KNOWLEDGE_MAX_SOURCE_BYTES) {
|
|
92
|
+
throw new AgentKitError(
|
|
93
|
+
"artifact_knowledge_source_too_large",
|
|
94
|
+
`Knowledge source "${sourceKey}" is too large for automatic deploy sync. Keep each source under 5MB.`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
totalBytes += stats.size;
|
|
99
|
+
|
|
100
|
+
if (totalBytes > HOSTED_KNOWLEDGE_MAX_TOTAL_BYTES) {
|
|
101
|
+
throw new AgentKitError(
|
|
102
|
+
"artifact_knowledge_sources_too_large",
|
|
103
|
+
"Knowledge sources are too large for automatic deploy sync. Keep total packaged Knowledge under 9MB.",
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const content = await readFile(absolutePath, "utf8");
|
|
108
|
+
sources.push({
|
|
109
|
+
kind: "file",
|
|
110
|
+
source_key: sourceKey,
|
|
111
|
+
title: source.title,
|
|
112
|
+
content,
|
|
113
|
+
sha256: sha256(content),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
content_type: "application/json",
|
|
119
|
+
sources,
|
|
120
|
+
sha256: sha256(JSON.stringify(sources)),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function capsuleRelativePath(root: string, absolutePath: string): string {
|
|
125
|
+
const relativePath = relative(root, absolutePath);
|
|
126
|
+
|
|
127
|
+
if (!relativePath || relativePath.startsWith("..") || isAbsolute(relativePath)) {
|
|
128
|
+
throw new AgentKitError(
|
|
129
|
+
"artifact_knowledge_source_outside_capsule",
|
|
130
|
+
"Knowledge source paths must stay inside the Agent Capsule.",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return relativePath.split("\\").join("/");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function sha256(source: string): string {
|
|
138
|
+
return createHash("sha256").update(source).digest("hex");
|
|
139
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { AgentBuildResult } from "../runtime/targets/cloudflare/build";
|
|
5
|
+
import type { LocalDeployState } from "../runtime/core/deploy-state";
|
|
6
|
+
import { AgentKitError } from "../runtime/errors";
|
|
7
|
+
import { createHostedDeployArtifact } from "./artifact";
|
|
8
|
+
import type { AgentKitCloudDeployOptions, CreateDeployRequest, CreateDeployResponse } from "./contracts";
|
|
9
|
+
|
|
10
|
+
export async function deployBuildToAgentKitCloud(
|
|
11
|
+
build: AgentBuildResult,
|
|
12
|
+
options: AgentKitCloudDeployOptions,
|
|
13
|
+
): Promise<CreateDeployResponse> {
|
|
14
|
+
const artifact = await createHostedDeployArtifact(build);
|
|
15
|
+
const headers = new Headers({
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
});
|
|
18
|
+
const apiToken = options.apiToken ?? process.env.AGENTKIT_CLOUD_API_TOKEN;
|
|
19
|
+
|
|
20
|
+
if (apiToken) {
|
|
21
|
+
headers.set("Authorization", `Bearer ${apiToken}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const response = await fetch(new URL("/v1/deploys", normalizeApiUrl(options.apiUrl)), {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers,
|
|
27
|
+
body: JSON.stringify({
|
|
28
|
+
project_name: artifact.manifest.name,
|
|
29
|
+
anonymous: options.anonymous ?? !apiToken,
|
|
30
|
+
artifact,
|
|
31
|
+
} satisfies CreateDeployRequest),
|
|
32
|
+
});
|
|
33
|
+
const payload = await response.json().catch(() => null);
|
|
34
|
+
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
const code =
|
|
37
|
+
payload && typeof payload === "object" && "error" in payload
|
|
38
|
+
? String((payload as { error?: { code?: unknown } }).error?.code ?? "cloud_deploy_failed")
|
|
39
|
+
: "cloud_deploy_failed";
|
|
40
|
+
const message =
|
|
41
|
+
payload && typeof payload === "object" && "error" in payload
|
|
42
|
+
? String((payload as { error?: { message?: unknown } }).error?.message ?? "AgentKit Cloud deploy failed.")
|
|
43
|
+
: `AgentKit Cloud deploy failed with HTTP ${response.status}.`;
|
|
44
|
+
throw new AgentKitError(code, message);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return payload as CreateDeployResponse;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function writeLocalDeployState(root: string, deploy: CreateDeployResponse): Promise<string> {
|
|
51
|
+
const statePath = join(root, ".agentkit/deploy.json");
|
|
52
|
+
const state: LocalDeployState = {
|
|
53
|
+
project_id: deploy.project.id,
|
|
54
|
+
deploy_id: deploy.deploy.id,
|
|
55
|
+
url: deploy.deploy.url,
|
|
56
|
+
target: "cloudflare",
|
|
57
|
+
runtime: "edge",
|
|
58
|
+
resources: {
|
|
59
|
+
cloudflare: {
|
|
60
|
+
worker_url: deploy.deploy.url,
|
|
61
|
+
durable_object_class: "AgentKitConversationStore",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
owner_state: deploy.project.owner_state,
|
|
65
|
+
access: {
|
|
66
|
+
mode: deploy.deploy.access.mode,
|
|
67
|
+
private_token: deploy.deploy.access.private_token ?? undefined,
|
|
68
|
+
},
|
|
69
|
+
created_at: new Date().toISOString(),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
await mkdir(join(root, ".agentkit"), { recursive: true });
|
|
73
|
+
await writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`);
|
|
74
|
+
return statePath;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function normalizeApiUrl(value: string): string {
|
|
78
|
+
return value.endsWith("/") ? value : `${value}/`;
|
|
79
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { AgentManifestWithBundle } from "../runtime/targets/cloudflare/build";
|
|
2
|
+
|
|
3
|
+
export type HostedDeployArtifact = {
|
|
4
|
+
manifest: AgentManifestWithBundle;
|
|
5
|
+
worker: {
|
|
6
|
+
content_type: "application/javascript";
|
|
7
|
+
source: string;
|
|
8
|
+
sha256: string;
|
|
9
|
+
};
|
|
10
|
+
wrangler: {
|
|
11
|
+
content_type: "application/json";
|
|
12
|
+
source: string;
|
|
13
|
+
};
|
|
14
|
+
turso_schema?: {
|
|
15
|
+
content_type: "text/sql";
|
|
16
|
+
source: string;
|
|
17
|
+
sha256: string;
|
|
18
|
+
};
|
|
19
|
+
knowledge_sources?: {
|
|
20
|
+
content_type: "application/json";
|
|
21
|
+
sources: Array<{
|
|
22
|
+
kind: "file";
|
|
23
|
+
source_key: string;
|
|
24
|
+
title: string | null;
|
|
25
|
+
content: string;
|
|
26
|
+
sha256: string;
|
|
27
|
+
}>;
|
|
28
|
+
sha256: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type CreateDeployRequest = {
|
|
33
|
+
project_name: string;
|
|
34
|
+
anonymous: boolean;
|
|
35
|
+
artifact: HostedDeployArtifact;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type CreateDeployResponse = {
|
|
39
|
+
project: {
|
|
40
|
+
id: string;
|
|
41
|
+
owner_state: "anonymous" | "claimed";
|
|
42
|
+
};
|
|
43
|
+
deploy: {
|
|
44
|
+
id: string;
|
|
45
|
+
status: "ready" | "failed";
|
|
46
|
+
url: string;
|
|
47
|
+
access: {
|
|
48
|
+
mode: string;
|
|
49
|
+
private_token?: string;
|
|
50
|
+
};
|
|
51
|
+
knowledge?: {
|
|
52
|
+
enabled: boolean;
|
|
53
|
+
sources: number;
|
|
54
|
+
chunks: number;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type AgentKitCloudDeployOptions = {
|
|
60
|
+
apiUrl: string;
|
|
61
|
+
apiToken?: string;
|
|
62
|
+
anonymous?: boolean;
|
|
63
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
import { getTemplate, type TemplateFile } from "./templates";
|
|
8
|
+
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
export type CreateProjectOptions = {
|
|
13
|
+
name: string;
|
|
14
|
+
template: string;
|
|
15
|
+
cwd: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type CreateProjectResult = {
|
|
19
|
+
name: string;
|
|
20
|
+
path: string;
|
|
21
|
+
files: string[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export async function createProject(options: CreateProjectOptions): Promise<CreateProjectResult> {
|
|
25
|
+
const target = resolveProjectTarget(options);
|
|
26
|
+
validateProjectName(target.name);
|
|
27
|
+
|
|
28
|
+
const template = getTemplate(options.template);
|
|
29
|
+
const projectPath = target.path;
|
|
30
|
+
const files = [
|
|
31
|
+
...template.files(target.name, {
|
|
32
|
+
agentkitDependency: await resolveAgentKitDependency(),
|
|
33
|
+
}),
|
|
34
|
+
...(await loadDefaultSkillFiles()),
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
if (target.currentDirectory) {
|
|
38
|
+
await ensureNoFileCollisions(projectPath, files.map((file) => file.path));
|
|
39
|
+
} else {
|
|
40
|
+
await mkdir(projectPath, { recursive: false });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const file of files) {
|
|
44
|
+
const absolutePath = join(projectPath, file.path);
|
|
45
|
+
const directory = absolutePath.slice(0, absolutePath.lastIndexOf("/"));
|
|
46
|
+
|
|
47
|
+
await mkdir(directory, { recursive: true });
|
|
48
|
+
await writeFile(absolutePath, file.contents);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await initializeGitRepository(projectPath);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
name: target.name,
|
|
55
|
+
path: projectPath,
|
|
56
|
+
files: files.map((file) => file.path),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveProjectTarget(options: CreateProjectOptions): { name: string; path: string; currentDirectory: boolean } {
|
|
61
|
+
if (options.name === ".") {
|
|
62
|
+
const path = resolve(options.cwd);
|
|
63
|
+
return {
|
|
64
|
+
name: basename(path),
|
|
65
|
+
path,
|
|
66
|
+
currentDirectory: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
name: options.name,
|
|
72
|
+
path: join(options.cwd, options.name),
|
|
73
|
+
currentDirectory: false,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function validateProjectName(name: string) {
|
|
78
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9-_]*$/.test(name)) {
|
|
79
|
+
throw new Error("Project name must start with a letter or number and contain only letters, numbers, hyphens, and underscores.");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function ensureNoFileCollisions(projectPath: string, files: string[]): Promise<void> {
|
|
84
|
+
const collisions: string[] = [];
|
|
85
|
+
|
|
86
|
+
for (const file of files) {
|
|
87
|
+
if (await pathExists(join(projectPath, file))) {
|
|
88
|
+
collisions.push(file);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (collisions.length > 0) {
|
|
93
|
+
throw new Error(`Cannot create Agent Capsule in current directory because these files already exist: ${collisions.join(", ")}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function initializeGitRepository(projectPath: string): Promise<void> {
|
|
98
|
+
if (await directoryExists(join(projectPath, ".git"))) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
await execFileAsync("git", ["init"], { cwd: projectPath });
|
|
104
|
+
} catch (error) {
|
|
105
|
+
const stderr = error && typeof error === "object" && "stderr" in error ? String(error.stderr) : "";
|
|
106
|
+
throw new Error(`Could not initialize git repository: ${stderr.trim() || "git init failed"}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function resolveAgentKitDependency(): Promise<string> {
|
|
111
|
+
if (process.env.AGENTKIT_TEMPLATE_DEPENDENCY) {
|
|
112
|
+
return process.env.AGENTKIT_TEMPLATE_DEPENDENCY;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const packageRoot = resolve(moduleDir, "..");
|
|
116
|
+
|
|
117
|
+
if (await directoryExists(resolve(packageRoot, "../..", ".git"))) {
|
|
118
|
+
return `file:${packageRoot}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const packageJson = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8")) as {
|
|
122
|
+
version?: unknown;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (typeof packageJson.version !== "string" || packageJson.version.length === 0) {
|
|
126
|
+
throw new Error("Could not determine @andreprado/agentkit package version for the generated capsule.");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return packageJson.version;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function loadDefaultSkillFiles(): Promise<TemplateFile[]> {
|
|
133
|
+
const sourceRoot = join(moduleDir, "templates", "skills");
|
|
134
|
+
const sourceFiles = await listFiles(sourceRoot);
|
|
135
|
+
|
|
136
|
+
return Promise.all(
|
|
137
|
+
sourceFiles.map(async (sourcePath) => ({
|
|
138
|
+
path: join("skills", relative(sourceRoot, sourcePath)).replaceAll("\\", "/"),
|
|
139
|
+
contents: await readFile(sourcePath, "utf8"),
|
|
140
|
+
})),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function listFiles(directory: string): Promise<string[]> {
|
|
145
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
146
|
+
const files = await Promise.all(
|
|
147
|
+
entries.map(async (entry) => {
|
|
148
|
+
const path = join(directory, entry.name);
|
|
149
|
+
|
|
150
|
+
if (entry.isDirectory()) {
|
|
151
|
+
return listFiles(path);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return entry.isFile() ? [path] : [];
|
|
155
|
+
}),
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
return files.flat().sort();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function directoryExists(path: string): Promise<boolean> {
|
|
162
|
+
try {
|
|
163
|
+
const result = await stat(path);
|
|
164
|
+
return result.isDirectory();
|
|
165
|
+
} catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function pathExists(path: string): Promise<boolean> {
|
|
171
|
+
try {
|
|
172
|
+
await stat(path);
|
|
173
|
+
return true;
|
|
174
|
+
} catch {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
}
|