ape-claw 0.1.4 → 0.1.6
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 +36 -24
- package/docs/CLI_GUIDE.md +5 -0
- package/docs/GLOBAL_BACKEND.md +3 -1
- package/docs/PRODUCT_OVERVIEW.md +15 -1
- package/docs/STARTER_PACK.md +7 -5
- package/docs/SUPPORTED_NETWORKS.md +1 -1
- package/docs/operator/01-quickstart.md +100 -9
- package/docs/operator/02-dashboard.md +1 -1
- package/docs/operator/03-cli-reference.md +19 -7
- package/docs/operator/04-skills-library.md +13 -12
- package/docs/operator/06-deployment.md +60 -7
- package/docs/operator/09-env-reference.md +61 -0
- package/docs/social/STARTER_PACK_THREAD.md +3 -3
- package/package.json +1 -1
- package/src/cli.mjs +143 -17
- package/src/server/index.mjs +6 -2
- package/src/server/routes/forge-agent.mjs +820 -0
- package/src/server/routes/skills.mjs +39 -6
- package/src/server/storage/file-backend.mjs +31 -0
- package/src/telemetry-server.mjs +90 -4
- package/ui/forge/css/forge.css +30 -2
- package/ui/forge/index.html +20 -7
- package/ui/forge/js/forge-attachments.js +26 -38
- package/ui/forge/js/forge-chat.js +240 -20
- package/ui/forge/js/forge-data.js +76 -14
- package/ui/forge/js/forge-scene.js +1943 -391
- package/ui/index.html +53 -54
- package/ui/js/skills.js +57 -42
- package/ui/shared/sidebar-nav.js +1 -1
- package/ui/skills.html +80 -53
|
@@ -56,6 +56,39 @@ This document lists all environment variables used by ApeClaw, organized by comp
|
|
|
56
56
|
| `APE_CLAW_INVITE_MAX_USES` | Maximum uses per invite token | No | `5` |
|
|
57
57
|
| `APE_CLAW_POD_DIR` | Pod workspace directory path | No | Auto-detected |
|
|
58
58
|
|
|
59
|
+
## Forge Agent Variables
|
|
60
|
+
|
|
61
|
+
The forge agent auto-detects your LLM provider. Set **any one** of the API key variables below — the first one found is used.
|
|
62
|
+
|
|
63
|
+
**LLM Provider (set one):**
|
|
64
|
+
|
|
65
|
+
| Variable | Provider | Default Model |
|
|
66
|
+
|----------|----------|---------------|
|
|
67
|
+
| `PERPLEXITY_API_KEY` | Perplexity Sonar (web-grounded) | `sonar-pro` |
|
|
68
|
+
| `OPENAI_API_KEY` | OpenAI | `gpt-4o` |
|
|
69
|
+
| `ANTHROPIC_API_KEY` | Anthropic Claude | `claude-sonnet-4-20250514` |
|
|
70
|
+
| `GROQ_API_KEY` | Groq | `llama-3.3-70b-versatile` |
|
|
71
|
+
| `TOGETHER_API_KEY` | Together AI | `meta-llama/Llama-3.3-70B-Instruct-Turbo` |
|
|
72
|
+
| `OLLAMA_HOST` | Ollama (local, no key needed) | `llama3.2` |
|
|
73
|
+
|
|
74
|
+
**Explicit override (any OpenAI-compatible endpoint):**
|
|
75
|
+
|
|
76
|
+
| Variable | Description | Default |
|
|
77
|
+
|----------|-------------|---------|
|
|
78
|
+
| `FORGE_LLM_API_URL` | Full chat completions URL | Auto-detected from provider |
|
|
79
|
+
| `FORGE_LLM_API_KEY` | API key for custom endpoint | Auto-detected from provider |
|
|
80
|
+
| `FORGE_LLM_MODEL` | Model name override | Auto-detected from provider |
|
|
81
|
+
|
|
82
|
+
**Agent identity:**
|
|
83
|
+
|
|
84
|
+
| Variable | Description | Default |
|
|
85
|
+
|----------|-------------|---------|
|
|
86
|
+
| `FORGE_AGENT_ID` | ClawBot agent ID | `"the-clawllector"` |
|
|
87
|
+
| `FORGE_AGENT_TOKEN` | Pre-provisioned ClawBot token for verified identity | Auto-registers on startup |
|
|
88
|
+
| `FORGE_AGENT_NAME` | Display name shown in chat | `"The Clawllector"` |
|
|
89
|
+
|
|
90
|
+
Without any LLM key the endpoint returns 503 and the forge chat falls back to the basic `/api/chat` relay.
|
|
91
|
+
|
|
59
92
|
## External Service Variables
|
|
60
93
|
|
|
61
94
|
| Variable | Description | Required | Default |
|
|
@@ -117,6 +150,34 @@ export APE_CLAW_CORS_ORIGINS=https://apeclaw.ai
|
|
|
117
150
|
export APE_CLAW_STORAGE=file # or "sqlite"
|
|
118
151
|
```
|
|
119
152
|
|
|
153
|
+
### Forge Agent (Local) — pick any provider
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
# Option A: OpenAI
|
|
157
|
+
export OPENAI_API_KEY=sk-...
|
|
158
|
+
|
|
159
|
+
# Option B: Anthropic
|
|
160
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
161
|
+
|
|
162
|
+
# Option C: Perplexity
|
|
163
|
+
export PERPLEXITY_API_KEY=pplx-...
|
|
164
|
+
|
|
165
|
+
# Option D: Groq (free tier available)
|
|
166
|
+
export GROQ_API_KEY=gsk_...
|
|
167
|
+
|
|
168
|
+
# Option E: Local Ollama (no key needed)
|
|
169
|
+
export OLLAMA_HOST=http://localhost:11434
|
|
170
|
+
|
|
171
|
+
# Option F: Any OpenAI-compatible endpoint
|
|
172
|
+
export FORGE_LLM_API_URL=https://your-endpoint.com/v1/chat/completions
|
|
173
|
+
export FORGE_LLM_API_KEY=your-key
|
|
174
|
+
export FORGE_LLM_MODEL=your-model
|
|
175
|
+
|
|
176
|
+
# Optional identity overrides:
|
|
177
|
+
export FORGE_AGENT_NAME="My Bot"
|
|
178
|
+
export FORGE_AGENT_ID=my-forge-bot
|
|
179
|
+
```
|
|
180
|
+
|
|
120
181
|
### Pod Operations
|
|
121
182
|
|
|
122
183
|
```bash
|
|
@@ -15,7 +15,7 @@ Press Enter. 61 skills load in about four seconds.
|
|
|
15
15
|
That's it. You have a working agent. No browsing a marketplace for an hour.
|
|
16
16
|
|
|
17
17
|
```
|
|
18
|
-
npx ape-claw skill install
|
|
18
|
+
npx ape-claw skill install
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
Here's what you actually get (thread)
|
|
@@ -152,8 +152,8 @@ Still in the library at apeclaw.ai/skills. Just not in the box you get on day on
|
|
|
152
152
|
|
|
153
153
|
The prompt defaults to yes, but you have options:
|
|
154
154
|
|
|
155
|
-
npx ape-claw skill install --
|
|
156
|
-
npx ape-claw skill install --
|
|
155
|
+
npx ape-claw skill install --starter-pack installs without asking
|
|
156
|
+
npx ape-claw skill install --no-starter-pack skips it
|
|
157
157
|
No flag means it asks you
|
|
158
158
|
|
|
159
159
|
You can install it later. You can also skip it entirely and pick from 10,000+ skills one at a time.
|
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -144,15 +144,15 @@ function installApeClawSkill(args) {
|
|
|
144
144
|
throw new Error(`Source skill missing at ${sourceSkillPath}`);
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
const scope = String(args.scope || "
|
|
147
|
+
const scope = String(args.scope || "global").toLowerCase();
|
|
148
148
|
const explicitSkillsDir = args["skills-dir"] ? String(args["skills-dir"]) : "";
|
|
149
149
|
let skillsRoot;
|
|
150
150
|
if (explicitSkillsDir) {
|
|
151
151
|
skillsRoot = path.resolve(explicitSkillsDir);
|
|
152
152
|
if (skillsRoot.includes("\0")) throw new Error("Invalid skills-dir path");
|
|
153
153
|
}
|
|
154
|
-
else if (scope === "
|
|
155
|
-
else skillsRoot = path.join(
|
|
154
|
+
else if (scope === "local") skillsRoot = path.join(process.cwd(), ".cursor", "skills");
|
|
155
|
+
else skillsRoot = path.join(os.homedir(), ".openclaw", "skills");
|
|
156
156
|
|
|
157
157
|
const targetSkillDir = path.join(skillsRoot, "ape-claw");
|
|
158
158
|
const targetSkillPath = path.join(targetSkillDir, "SKILL.md");
|
|
@@ -227,7 +227,12 @@ function syncSkillToOpenClaw(cardObj, slug, skillsRoot) {
|
|
|
227
227
|
const rawDoc = String(cardObj?.documentation_md || "").trim();
|
|
228
228
|
const displayName = String(cardObj?.name || s).trim();
|
|
229
229
|
const versionValue = String(cardObj?.version || "1.0.0").trim();
|
|
230
|
-
|
|
230
|
+
let descriptionValue = String(cardObj?.description || "").trim();
|
|
231
|
+
// Strip embedded YAML frontmatter that some imported skills have in their description
|
|
232
|
+
if (descriptionValue.startsWith("---")) {
|
|
233
|
+
descriptionValue = descriptionValue.replace(/^---[\s\S]*?---\s*/, "").trim();
|
|
234
|
+
}
|
|
235
|
+
if (!descriptionValue) descriptionValue = String(cardObj?.desc || displayName);
|
|
231
236
|
const descOneLine = descriptionValue.replace(/\n/g, " ").slice(0, 300);
|
|
232
237
|
|
|
233
238
|
const openclawFrontmatter = `---\nname: ${s}\nversion: ${yamlSafe(versionValue)}\ndescription: ${yamlSafe(descOneLine)}\n---\n`;
|
|
@@ -245,7 +250,8 @@ function syncSkillToOpenClaw(cardObj, slug, skillsRoot) {
|
|
|
245
250
|
fs.mkdirSync(skillDir, { recursive: true });
|
|
246
251
|
fs.writeFileSync(path.join(skillDir, "SKILL.md"), content, "utf8");
|
|
247
252
|
|
|
248
|
-
const
|
|
253
|
+
const homeDir = process.env.OPENCLAW_HOME || os.homedir();
|
|
254
|
+
const openclawWorkspaceSkills = path.join(homeDir, ".openclaw", "workspace", "skills", s);
|
|
249
255
|
try {
|
|
250
256
|
fs.mkdirSync(openclawWorkspaceSkills, { recursive: true });
|
|
251
257
|
fs.writeFileSync(path.join(openclawWorkspaceSkills, "SKILL.md"), content, "utf8");
|
|
@@ -373,6 +379,68 @@ function safeSkillVersion(v) {
|
|
|
373
379
|
return s;
|
|
374
380
|
}
|
|
375
381
|
|
|
382
|
+
const TRUSTED_SKILL_API_HOSTS = new Set([
|
|
383
|
+
"apeclaw.ai", "www.apeclaw.ai", "api.apeclaw.ai",
|
|
384
|
+
"acceptable-cat-production.up.railway.app",
|
|
385
|
+
]);
|
|
386
|
+
|
|
387
|
+
const SKILL_API_FALLBACK = "https://acceptable-cat-production.up.railway.app";
|
|
388
|
+
|
|
389
|
+
function resolveSkillApiBase(args = {}) {
|
|
390
|
+
const explicit = String(args.api || "").trim();
|
|
391
|
+
const fromEnv = String(process.env.APE_CLAW_API_URL || "").trim();
|
|
392
|
+
const raw = explicit || fromEnv || "https://apeclaw.ai";
|
|
393
|
+
let u;
|
|
394
|
+
try {
|
|
395
|
+
u = new URL(raw);
|
|
396
|
+
} catch {
|
|
397
|
+
throw new Error(`Invalid APE_CLAW_API_URL: ${raw}`);
|
|
398
|
+
}
|
|
399
|
+
const isLoopback = u.hostname === "localhost" || u.hostname === "127.0.0.1";
|
|
400
|
+
if (u.protocol !== "https:" && !(isLoopback && Boolean(args["allow-insecure-api"]))) {
|
|
401
|
+
throw new Error("Remote skill API must use HTTPS. For local dev only, use --allow-insecure-api with localhost.");
|
|
402
|
+
}
|
|
403
|
+
if (!TRUSTED_SKILL_API_HOSTS.has(u.hostname) && !Boolean(args["allow-custom-api"])) {
|
|
404
|
+
throw new Error(`Untrusted skill API host: ${u.hostname}. Use --allow-custom-api to override.`);
|
|
405
|
+
}
|
|
406
|
+
return u.origin.replace(/\/+$/, "");
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function assertRemoteSkillCardSafe({ requestedSlug, card, skillMeta = null, asJson = false, allowUnvetted = false, allowHighRisk = false }) {
|
|
410
|
+
if (!card || typeof card !== "object") throw new Error("Remote API returned invalid skill card object");
|
|
411
|
+
const normalizedRequested = toSlug(requestedSlug);
|
|
412
|
+
const normalizedCardSlug = toSlug(card.slug || card.name || "");
|
|
413
|
+
if (!normalizedCardSlug) throw new Error("Remote skill card missing slug/name");
|
|
414
|
+
if (normalizedCardSlug !== normalizedRequested) {
|
|
415
|
+
throw new Error(`Remote skill slug mismatch (requested=${normalizedRequested}, received=${normalizedCardSlug})`);
|
|
416
|
+
}
|
|
417
|
+
const version = safeSkillVersion(card.version || "1.0.0");
|
|
418
|
+
if (!version) throw new Error("Remote skill version is invalid");
|
|
419
|
+
const description = String(card.description || "").trim();
|
|
420
|
+
if (!description) throw new Error("Remote skill description is required");
|
|
421
|
+
const documentation = String(card.documentation_md || "");
|
|
422
|
+
if (Buffer.byteLength(documentation, "utf8") > 300_000) {
|
|
423
|
+
throw new Error("Remote skill documentation is too large (>300KB)");
|
|
424
|
+
}
|
|
425
|
+
if (/[\x00-\x08\x0B\x0C\x0E-\x1F]/.test(documentation)) {
|
|
426
|
+
throw new Error("Remote skill documentation contains control characters");
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const metaRiskTierRaw = Number(skillMeta?.riskTier ?? card?.constraints?.riskTier ?? card?.riskTier ?? 2);
|
|
430
|
+
const metaRiskTier = Number.isFinite(metaRiskTierRaw) ? Math.max(1, Math.min(3, Math.round(metaRiskTierRaw))) : 2;
|
|
431
|
+
if (metaRiskTier >= 3 && !allowHighRisk) {
|
|
432
|
+
throw new Error(`Remote skill risk tier ${metaRiskTier} requires explicit --allow-high-risk`);
|
|
433
|
+
}
|
|
434
|
+
// If API metadata is present, require vetting by default.
|
|
435
|
+
if (skillMeta && skillMeta.vettedOk !== true && !allowUnvetted) {
|
|
436
|
+
throw new Error("Remote skill is not vetted. Use --allow-unvetted to install anyway.");
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (!asJson && skillMeta && skillMeta.vettedOk === true) {
|
|
440
|
+
console.log("\x1b[2m Security: vetted skill metadata confirmed by API.\x1b[0m");
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
376
444
|
function resolveBundledSkillFile(packageRoot, slug) {
|
|
377
445
|
const skillsDataDir = path.join(packageRoot, "data", "skills");
|
|
378
446
|
const target = path.join(skillsDataDir, `${slug}.json`);
|
|
@@ -397,17 +465,49 @@ function resolveBundledSkillFile(packageRoot, slug) {
|
|
|
397
465
|
return "";
|
|
398
466
|
}
|
|
399
467
|
|
|
400
|
-
async function
|
|
401
|
-
const
|
|
402
|
-
const
|
|
468
|
+
async function fetchSkillFromApiOnce(slug, apiBase) {
|
|
469
|
+
const url = `${apiBase}/api/skills/get?slug=${encodeURIComponent(slug)}`;
|
|
470
|
+
const res = await fetch(url, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(15000) });
|
|
471
|
+
if (!res.ok) return null;
|
|
472
|
+
const ct = String(res.headers.get("content-type") || "");
|
|
473
|
+
if (!ct.includes("json")) return null;
|
|
474
|
+
const json = await res.json();
|
|
475
|
+
if (!json?.ok) return null;
|
|
476
|
+
|
|
477
|
+
const skillMeta = json?.skill && typeof json.skill === "object" ? json.skill : null;
|
|
478
|
+
let card = json?.card && typeof json.card === "object" ? json.card : null;
|
|
479
|
+
if (!card && skillMeta) {
|
|
480
|
+
card = {
|
|
481
|
+
name: skillMeta.name || slug,
|
|
482
|
+
slug: skillMeta.slug || slug,
|
|
483
|
+
version: "1.0.0",
|
|
484
|
+
description: skillMeta.description || skillMeta.name || slug,
|
|
485
|
+
riskTier: skillMeta.riskTier ?? 2,
|
|
486
|
+
provenance: skillMeta.provenance || { publisher: "imported", signed: false },
|
|
487
|
+
constraints: { riskTier: skillMeta.riskTier ?? 2 },
|
|
488
|
+
documentation_md: skillMeta.description
|
|
489
|
+
? `# ${skillMeta.name || slug}\n\n${skillMeta.description}`
|
|
490
|
+
: `# ${skillMeta.name || slug}\n`,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
return { card, skillMeta, url, apiBase };
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async function fetchSkillFromApi(slug, args = {}) {
|
|
497
|
+
const apiBase = resolveSkillApiBase(args);
|
|
403
498
|
try {
|
|
404
|
-
const
|
|
405
|
-
if (
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
499
|
+
const result = await fetchSkillFromApiOnce(slug, apiBase);
|
|
500
|
+
if (result) return result;
|
|
501
|
+
} catch { /* primary failed, try fallback */ }
|
|
502
|
+
|
|
503
|
+
if (apiBase !== SKILL_API_FALLBACK) {
|
|
504
|
+
try {
|
|
505
|
+
const fallbackResult = await fetchSkillFromApiOnce(slug, SKILL_API_FALLBACK);
|
|
506
|
+
if (fallbackResult) return fallbackResult;
|
|
507
|
+
} catch { /* fallback also failed */ }
|
|
410
508
|
}
|
|
509
|
+
|
|
510
|
+
return { card: null, skillMeta: null, url: `${apiBase}/api/skills/get?slug=${encodeURIComponent(slug)}`, apiBase };
|
|
411
511
|
}
|
|
412
512
|
|
|
413
513
|
function resolveHumanizerDependencySlug(packageRoot) {
|
|
@@ -419,7 +519,8 @@ function resolveHumanizerDependencySlug(packageRoot) {
|
|
|
419
519
|
}
|
|
420
520
|
|
|
421
521
|
function installOpenClawSkillCard(cardObj, fallbackSlug = "") {
|
|
422
|
-
const
|
|
522
|
+
const homeDir = process.env.OPENCLAW_HOME || os.homedir();
|
|
523
|
+
const skillsRoot = path.join(homeDir, ".openclaw", "skills");
|
|
423
524
|
return syncSkillToOpenClaw(cardObj, fallbackSlug, skillsRoot);
|
|
424
525
|
}
|
|
425
526
|
|
|
@@ -615,7 +716,16 @@ async function main() {
|
|
|
615
716
|
} catch (bundledErr) {
|
|
616
717
|
// Not in bundled data — fetch from API
|
|
617
718
|
if (!asJson) console.log(`\x1b[2m Skill not bundled locally, fetching from API…\x1b[0m`);
|
|
618
|
-
|
|
719
|
+
let remote;
|
|
720
|
+
try {
|
|
721
|
+
remote = await fetchSkillFromApi(requestedSkillSlug, args);
|
|
722
|
+
} catch (apiErr) {
|
|
723
|
+
if (asJson) return print({ ok: false, error: `Skill "${requestedSkillSlug}" rejected: ${apiErr.message}` }, true);
|
|
724
|
+
console.error(`\x1b[31m ✗ ${apiErr.message}\x1b[0m`);
|
|
725
|
+
process.exitCode = 1;
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const card = remote?.card || null;
|
|
619
729
|
if (!card || typeof card !== "object" || (!card.name && !card.slug)) {
|
|
620
730
|
if (asJson) return print({ ok: false, error: `Skill "${requestedSkillSlug}" not found (bundled: ${bundledErr.message}, API: not found)` }, true);
|
|
621
731
|
console.error(`\x1b[31m ✗ Skill "${requestedSkillSlug}" not found in bundled library or API.\x1b[0m`);
|
|
@@ -623,6 +733,22 @@ async function main() {
|
|
|
623
733
|
process.exitCode = 1;
|
|
624
734
|
return;
|
|
625
735
|
}
|
|
736
|
+
try {
|
|
737
|
+
assertRemoteSkillCardSafe({
|
|
738
|
+
requestedSlug: requestedSkillSlug,
|
|
739
|
+
card,
|
|
740
|
+
skillMeta: remote?.skillMeta || null,
|
|
741
|
+
asJson,
|
|
742
|
+
allowUnvetted: Boolean(args["allow-unvetted"]),
|
|
743
|
+
allowHighRisk: Boolean(args["allow-high-risk"]),
|
|
744
|
+
});
|
|
745
|
+
} catch (safeErr) {
|
|
746
|
+
if (asJson) return print({ ok: false, error: safeErr.message }, true);
|
|
747
|
+
console.error(`\x1b[31m ✗ ${safeErr.message}\x1b[0m`);
|
|
748
|
+
console.error(`\x1b[2m Use --allow-unvetted and/or --allow-high-risk only if you trust this source.\x1b[0m`);
|
|
749
|
+
process.exitCode = 1;
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
626
752
|
// Write fetched card to a temp file and install it
|
|
627
753
|
const tmpDir = path.join(packageRoot, "data", "skills");
|
|
628
754
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
@@ -2192,7 +2318,7 @@ async function main() {
|
|
|
2192
2318
|
"bridge execute (autonomous)": "ape-claw bridge execute --request <requestId> --execute --autonomous --json",
|
|
2193
2319
|
"bridge status": "ape-claw bridge status --request <requestId> --json",
|
|
2194
2320
|
"allowlist audit": "ape-claw allowlist audit --json",
|
|
2195
|
-
"skill install": "ape-claw skill install [<slug>] [--scope local] [--starter-pack | --no-starter-pack] --json",
|
|
2321
|
+
"skill install": "ape-claw skill install [<slug>] [--scope local] [--starter-pack | --no-starter-pack] [--allow-unvetted] [--allow-high-risk] [--allow-custom-api] [--allow-insecure-api] --json",
|
|
2196
2322
|
"v2 skill mint": "ape-claw v2 skill mint --rpc <url> --privateKey 0x... --skillNft 0x... --registry 0x... [--parentId 0] [--royalty-receiver 0x... --royalty-bps 500] --json",
|
|
2197
2323
|
"v2 skill publish": "ape-claw v2 skill publish --rpc <url> --privateKey 0x... --registry 0x... --skillId <id> --file <skillcard.json> [--uri ipfs://...] [--riskTier 1] --json",
|
|
2198
2324
|
"v2 intent create": "ape-claw v2 intent create --rpc <url> --privateKey 0x... --intents 0x... --payload '{...}' [--expiresAt <unixSec>] --json",
|
package/src/server/index.mjs
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
handleChatStream, handleChatGet, handleChatRooms,
|
|
31
31
|
handleChatPost, handleChatReact,
|
|
32
32
|
} from "./routes/chat.mjs";
|
|
33
|
+
import { handleForgeChat, handleForgeStatus, initForgeAgent } from "./routes/forge-agent.mjs";
|
|
33
34
|
import { handleV2ReceiptGet, handleV2Config } from "./routes/v2.mjs";
|
|
34
35
|
import { handlePodStatus, handlePodStop, handlePodFiles, handleStarterPack } from "./routes/pod.mjs";
|
|
35
36
|
import {
|
|
@@ -41,7 +42,7 @@ import {
|
|
|
41
42
|
handleIndex, handleStaticFile,
|
|
42
43
|
} from "./routes/static.mjs";
|
|
43
44
|
|
|
44
|
-
const PORT = Number(process.env.APE_CLAW_UI_PORT || 8787);
|
|
45
|
+
const PORT = Number(process.env.PORT || process.env.APE_CLAW_UI_PORT || 8787);
|
|
45
46
|
const BIND_HOST = String(process.env.APE_CLAW_BIND_HOST || "").trim();
|
|
46
47
|
|
|
47
48
|
const RL_READ = { limit: 60, windowMs: 60_000, keyPrefix: "read" };
|
|
@@ -58,7 +59,8 @@ if (!process.env.OPENSEA_API_KEY) {
|
|
|
58
59
|
|
|
59
60
|
initStorage();
|
|
60
61
|
initSseBroadcast();
|
|
61
|
-
|
|
62
|
+
initForgeAgent();
|
|
63
|
+
logger.info("Storage initialized, SSE broadcast active, forge agent ready");
|
|
62
64
|
|
|
63
65
|
function safeHandler(fn) {
|
|
64
66
|
return (req, res, ...args) => {
|
|
@@ -122,6 +124,8 @@ const server = http.createServer((req, res) => {
|
|
|
122
124
|
if (pathname === "/api/invites/create" && req.method === "POST") return safeHandler(handleInviteCreate)(req, res);
|
|
123
125
|
if (pathname === "/api/clawbots/register" && req.method === "POST") return safeHandler(handleClawbotsRegister)(req, res);
|
|
124
126
|
if (pathname === "/api/events" && req.method === "POST") return safeHandler(handlePostEvent)(req, res);
|
|
127
|
+
if (pathname === "/api/forge/status" && req.method === "GET") return safeHandler(handleForgeStatus)(req, res);
|
|
128
|
+
if (pathname === "/api/forge/chat" && req.method === "POST") return safeHandler(handleForgeChat)(req, res);
|
|
125
129
|
if (pathname === "/api/chat/stream") return safeHandler(handleChatStream)(req, res, reqUrl);
|
|
126
130
|
if (pathname === "/api/chat" && req.method === "GET") return safeHandler(handleChatGet)(req, res, reqUrl);
|
|
127
131
|
if (pathname === "/api/chat/rooms" && req.method === "GET") return safeHandler(handleChatRooms)(req, res, reqUrl);
|