@alejandroroman/agent-kit 0.1.4 → 0.2.0
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/dist/_memory/dist/server.js +0 -0
- package/dist/_memory/server.js +0 -0
- package/dist/agent/loop.js +210 -111
- package/dist/api/errors.d.ts +3 -0
- package/dist/api/errors.js +37 -0
- package/dist/api/events.d.ts +5 -0
- package/dist/api/events.js +28 -0
- package/dist/api/router.js +10 -0
- package/dist/api/traces.d.ts +3 -0
- package/dist/api/traces.js +35 -0
- package/dist/api/types.d.ts +2 -0
- package/dist/bootstrap.d.ts +3 -1
- package/dist/bootstrap.js +26 -7
- package/dist/cli/chat.js +3 -1
- package/dist/cli/claude-md-template.d.ts +5 -0
- package/dist/cli/claude-md-template.js +220 -0
- package/dist/cli/config-writer.js +3 -0
- package/dist/cli/env.d.ts +14 -0
- package/dist/cli/env.js +68 -0
- package/dist/cli/init.js +10 -0
- package/dist/cli/slack-setup.d.ts +6 -0
- package/dist/cli/slack-setup.js +234 -0
- package/dist/cli/start.js +65 -16
- package/dist/cli/ui.d.ts +2 -0
- package/dist/cli/ui.js +4 -1
- package/dist/cli/whats-new.d.ts +1 -0
- package/dist/cli/whats-new.js +69 -0
- package/dist/cli.js +14 -0
- package/dist/config/resolve.d.ts +1 -0
- package/dist/config/resolve.js +1 -0
- package/dist/config/schema.d.ts +2 -0
- package/dist/config/schema.js +1 -0
- package/dist/config/writer.d.ts +18 -0
- package/dist/config/writer.js +85 -0
- package/dist/cron/scheduler.d.ts +4 -1
- package/dist/cron/scheduler.js +99 -52
- package/dist/gateways/slack/client.d.ts +1 -0
- package/dist/gateways/slack/client.js +9 -0
- package/dist/gateways/slack/handler.js +2 -1
- package/dist/gateways/slack/index.js +75 -29
- package/dist/gateways/slack/listener.d.ts +8 -1
- package/dist/gateways/slack/listener.js +36 -10
- package/dist/heartbeat/runner.js +99 -82
- package/dist/llm/anthropic.d.ts +1 -0
- package/dist/llm/anthropic.js +11 -2
- package/dist/llm/fallback.js +34 -2
- package/dist/llm/openai.d.ts +2 -0
- package/dist/llm/openai.js +33 -2
- package/dist/llm/types.d.ts +16 -2
- package/dist/llm/types.js +9 -0
- package/dist/logger.js +8 -0
- package/dist/media/sanitize.d.ts +5 -0
- package/dist/media/sanitize.js +53 -0
- package/dist/multi/spawn.js +29 -10
- package/dist/session/compaction.js +3 -1
- package/dist/session/prune-images.d.ts +9 -0
- package/dist/session/prune-images.js +42 -0
- package/dist/skills/activate.d.ts +6 -0
- package/dist/skills/activate.js +72 -27
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/telemetry/db.d.ts +63 -0
- package/dist/telemetry/db.js +193 -0
- package/dist/telemetry/index.d.ts +17 -0
- package/dist/telemetry/index.js +82 -0
- package/dist/telemetry/sanitize.d.ts +6 -0
- package/dist/telemetry/sanitize.js +48 -0
- package/dist/telemetry/sqlite-processor.d.ts +11 -0
- package/dist/telemetry/sqlite-processor.js +108 -0
- package/dist/telemetry/types.d.ts +30 -0
- package/dist/telemetry/types.js +31 -0
- package/dist/tools/builtin/index.d.ts +2 -0
- package/dist/tools/builtin/index.js +2 -0
- package/dist/tools/builtin/self-config.d.ts +4 -0
- package/dist/tools/builtin/self-config.js +182 -0
- package/package.json +25 -18
package/dist/cli/chat.js
CHANGED
|
@@ -2,6 +2,7 @@ import { loadConfig } from "../config/index.js";
|
|
|
2
2
|
import { buildAgentRuntime } from "../bootstrap.js";
|
|
3
3
|
import { ensureOllama } from "./ollama.js";
|
|
4
4
|
import { resolveApiKey } from "./ui.js";
|
|
5
|
+
import { showWhatsNew } from "./whats-new.js";
|
|
5
6
|
import { CONFIG_PATH, DATA_DIR, SKILLS_DIR } from "./paths.js";
|
|
6
7
|
import { startRepl } from "./repl.js";
|
|
7
8
|
export async function chat(agentName) {
|
|
@@ -19,6 +20,7 @@ export async function chat(agentName) {
|
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
await resolveApiKey({ save: false });
|
|
23
|
+
showWhatsNew();
|
|
22
24
|
const config = loadConfig(CONFIG_PATH);
|
|
23
25
|
if (!config.agents[agentName]) {
|
|
24
26
|
const names = Object.keys(config.agents).join(", ");
|
|
@@ -31,7 +33,7 @@ export async function chat(agentName) {
|
|
|
31
33
|
console.log(" (Ollama not running — memory tools unavailable)\n");
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
|
-
const runtime = buildAgentRuntime(agentName, config, {
|
|
36
|
+
const runtime = await buildAgentRuntime(agentName, config, {
|
|
35
37
|
dataDir: DATA_DIR,
|
|
36
38
|
skillsDir: SKILLS_DIR,
|
|
37
39
|
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLAUDE.md template generated by `agent-kit init`.
|
|
3
|
+
* Gives any Claude Code session full context for working on this project.
|
|
4
|
+
*/
|
|
5
|
+
export declare const CLAUDE_MD_TEMPLATE = "# Agent Kit Project\n\nPersonal AI agent framework \u2014 multi-model agent loop with Slack gateway, cron scheduling, skills, and tool sandboxing.\n\nPowered by [`@alejandroroman/agent-kit`](https://www.npmjs.com/package/@alejandroroman/agent-kit).\n\n## Run Commands\n\n- `pnpm start` \u2014 start all services (cron, Slack, heartbeat, REPL)\n- `pnpm chat` \u2014 lightweight single-agent chat\n- `pnpm create` \u2014 AI-powered agent creation wizard\n\n## Project Layout\n\n```\nagent-kit.json # Main config \u2014 agents, cron jobs, models, defaults\n.env # Secrets (API keys, Slack tokens)\ndata/ # Runtime data (per-agent sessions, SOUL.md files, DBs)\n agents/<name>/\n SOUL.md # Agent personality / system prompt\n sessions/ # JSONL conversation logs\nskills/ # Custom skill definitions\n <skill-name>/\n skill.json # Manifest (name, description, tools list)\n SKILL.md # Instructions injected when skill is activated\n tools/ # Tool implementations (TypeScript)\n```\n\n## Configuration (agent-kit.json)\n\n### Models\n\n```json\n\"models\": [\n { \"model\": \"anthropic:claude-sonnet-4-6\", \"alias\": \"sonnet\" },\n { \"model\": \"anthropic:claude-haiku-4-5-20251001\", \"alias\": \"haiku\" }\n]\n```\n\nAgents reference models by alias. The framework handles automatic fallback between models on rate limits/server errors.\n\n### Agent Definition\n\n```json\n\"agents\": {\n \"my-agent\": {\n \"displayName\": \"My Agent\",\n \"emoji\": \"\uD83E\uDD16\",\n \"model\": \"sonnet\",\n \"tools\": [\"read_file\", \"write_file\", \"web_search\"],\n \"skills\": [\"my-skill\"],\n \"sandbox\": { \"allowedPaths\": [\"data/agents/my-agent/\"] },\n \"slack\": { \"channelId\": \"C0XXX\", \"channelName\": \"#my-channel\" },\n \"heartbeat\": { \"enabled\": true, \"intervalMinutes\": 60, \"model\": \"haiku\" },\n \"can_spawn\": [{ \"agent\": \"helper\", \"tool\": \"delegate\", \"description\": \"...\" }]\n }\n}\n```\n\n**Key fields:**\n- `tools` \u2014 builtin tools the agent can use (see list below)\n- `skills` \u2014 skill directories to make available via `activate_skill`\n- `sandbox` \u2014 restrict file access (`allowedPaths`) or commands (`allowedCommands`)\n- `slack` \u2014 bind agent to a Slack channel for inbound/outbound messages\n- `heartbeat` \u2014 periodic autonomous check-ins (posts to Slack if something noteworthy)\n- `spawn_only: true` \u2014 agent can only be invoked by other agents, not directly\n\n### Cron Jobs\n\n```json\n\"cron\": [\n {\n \"id\": \"daily-task\",\n \"agent\": \"my-agent\",\n \"schedule\": \"0 8 * * *\",\n \"prompt\": \"Activate the my-skill skill. Do the daily task.\",\n \"enabled\": true\n }\n]\n```\n\nSchedule uses standard cron syntax. Agents must activate their skill first in the prompt.\n\n### Defaults\n\n```json\n\"defaults\": {\n \"model\": \"sonnet\",\n \"maxTokens\": 4096,\n \"maxIterations\": 20,\n \"memory\": {\n \"dbPath\": \"./data/memory.db\",\n \"ollamaEndpoint\": \"http://localhost:11434\",\n \"ollamaModel\": \"all-minilm:l6-v2\"\n }\n}\n```\n\n## Built-in Tools\n\n| Tool | Description |\n|------|-------------|\n| `read_file` | Read a file (respects sandbox allowedPaths) |\n| `write_file` | Write a file (respects sandbox allowedPaths) |\n| `run_command` | Execute a shell command (respects sandbox allowedCommands) |\n| `web_search` | Search the web (Brave or Grok provider) |\n| `store_memory` | Store a memory with content and metadata |\n| `search_memory` | Semantic search across stored memories |\n| `get_memory` | Retrieve a specific memory by ID |\n| `update_memory` | Update an existing memory |\n| `forget_memory` | Delete a memory |\n| `list_memories` | List all memories with optional filtering |\n| `update_agent_config` | Agent updates its own config (allowlisted fields) |\n| `manage_cron` | Agent manages its own cron jobs (CRUD) |\n\nMemory tools require Ollama running with `all-minilm:l6-v2` model.\n\n## Creating Skills\n\nA skill is a directory in `skills/` with:\n\n1. **`skill.json`** \u2014 manifest\n```json\n{\n \"name\": \"my-skill\",\n \"description\": \"What this skill does\",\n \"tools\": [\"my_tool\"],\n \"prompt\": \"SKILL.md\"\n}\n```\n\n2. **`SKILL.md`** \u2014 instructions injected into agent context when activated\n\n3. **`tools/<name>.ts`** \u2014 tool implementations\n```typescript\nimport type { Tool } from \"@alejandroroman/agent-kit/dist/tools/types.js\";\n\nconst tool: Tool = {\n name: \"my_tool\",\n description: \"What this tool does\",\n parameters: {\n type: \"object\",\n properties: {\n input: { type: \"string\", description: \"The input\" }\n },\n required: [\"input\"]\n },\n execute: async (args) => {\n return \"result\";\n },\n};\n\nexport default tool;\n```\n\n## Environment Variables\n\n| Variable | Required | Description |\n|----------|----------|-------------|\n| `ANTHROPIC_API_KEY` | Yes | Anthropic API key for Claude models |\n| `OPENAI_API_KEY` | No | OpenAI API key (for fallback models) |\n| `SLACK_BOT_TOKEN` | No | Slack bot token (xoxb-...) for gateway |\n| `SLACK_APP_TOKEN` | No | Slack app token (xapp-...) for Socket Mode |\n| `BRAVE_SEARCH_API_KEY` | No | Brave Search API key for web_search |\n| `XAI_API_KEY` | No | xAI/Grok API key for web_search |\n\n## Slack Setup\n\n1. Create a Slack app at api.slack.com/apps \u2192 From Manifest (YAML):\n```yaml\ndisplay_information:\n name: Agent Kit\nsettings:\n socket_mode_enabled: true\nfeatures:\n bot_user:\n display_name: Agent Kit\n always_online: true\n event_subscriptions:\n bot_events:\n - message.channels\n - message.groups\noauth_config:\n scopes:\n bot:\n - chat:write\n - channels:history\n - channels:read\n - groups:history\n - groups:read\n```\n2. Generate an App-Level Token (Settings \u2192 Basic Information) with `connections:write` scope \u2192 `SLACK_APP_TOKEN`\n3. Install to workspace \u2192 copy Bot Token \u2192 `SLACK_BOT_TOKEN`\n4. Create channels, invite the bot (`/invite @Agent Kit`), copy channel IDs\n5. Add `slack` binding to each agent in `agent-kit.json`\n\n## Ollama (for Memory)\n\nMemory tools use Ollama for local embeddings:\n```bash\n# Install: https://ollama.com\nollama pull all-minilm:l6-v2\n# Ollama must be running when agent-kit starts\n```\n\nIf Ollama isn't running, everything else works \u2014 memory tools are just unavailable.\n\n## pnpm v10 Note\n\nIf using pnpm v10+, native dependencies need explicit approval:\n```json\n// In package.json:\n\"pnpm\": { \"onlyBuiltDependencies\": [\"better-sqlite3\"] }\n```\nThen `pnpm install` to rebuild native bindings.\n";
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLAUDE.md template generated by `agent-kit init`.
|
|
3
|
+
* Gives any Claude Code session full context for working on this project.
|
|
4
|
+
*/
|
|
5
|
+
export const CLAUDE_MD_TEMPLATE = `# Agent Kit Project
|
|
6
|
+
|
|
7
|
+
Personal AI agent framework — multi-model agent loop with Slack gateway, cron scheduling, skills, and tool sandboxing.
|
|
8
|
+
|
|
9
|
+
Powered by [\`@alejandroroman/agent-kit\`](https://www.npmjs.com/package/@alejandroroman/agent-kit).
|
|
10
|
+
|
|
11
|
+
## Run Commands
|
|
12
|
+
|
|
13
|
+
- \`pnpm start\` — start all services (cron, Slack, heartbeat, REPL)
|
|
14
|
+
- \`pnpm chat\` — lightweight single-agent chat
|
|
15
|
+
- \`pnpm create\` — AI-powered agent creation wizard
|
|
16
|
+
|
|
17
|
+
## Project Layout
|
|
18
|
+
|
|
19
|
+
\`\`\`
|
|
20
|
+
agent-kit.json # Main config — agents, cron jobs, models, defaults
|
|
21
|
+
.env # Secrets (API keys, Slack tokens)
|
|
22
|
+
data/ # Runtime data (per-agent sessions, SOUL.md files, DBs)
|
|
23
|
+
agents/<name>/
|
|
24
|
+
SOUL.md # Agent personality / system prompt
|
|
25
|
+
sessions/ # JSONL conversation logs
|
|
26
|
+
skills/ # Custom skill definitions
|
|
27
|
+
<skill-name>/
|
|
28
|
+
skill.json # Manifest (name, description, tools list)
|
|
29
|
+
SKILL.md # Instructions injected when skill is activated
|
|
30
|
+
tools/ # Tool implementations (TypeScript)
|
|
31
|
+
\`\`\`
|
|
32
|
+
|
|
33
|
+
## Configuration (agent-kit.json)
|
|
34
|
+
|
|
35
|
+
### Models
|
|
36
|
+
|
|
37
|
+
\`\`\`json
|
|
38
|
+
"models": [
|
|
39
|
+
{ "model": "anthropic:claude-sonnet-4-6", "alias": "sonnet" },
|
|
40
|
+
{ "model": "anthropic:claude-haiku-4-5-20251001", "alias": "haiku" }
|
|
41
|
+
]
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
Agents reference models by alias. The framework handles automatic fallback between models on rate limits/server errors.
|
|
45
|
+
|
|
46
|
+
### Agent Definition
|
|
47
|
+
|
|
48
|
+
\`\`\`json
|
|
49
|
+
"agents": {
|
|
50
|
+
"my-agent": {
|
|
51
|
+
"displayName": "My Agent",
|
|
52
|
+
"emoji": "🤖",
|
|
53
|
+
"model": "sonnet",
|
|
54
|
+
"tools": ["read_file", "write_file", "web_search"],
|
|
55
|
+
"skills": ["my-skill"],
|
|
56
|
+
"sandbox": { "allowedPaths": ["data/agents/my-agent/"] },
|
|
57
|
+
"slack": { "channelId": "C0XXX", "channelName": "#my-channel" },
|
|
58
|
+
"heartbeat": { "enabled": true, "intervalMinutes": 60, "model": "haiku" },
|
|
59
|
+
"can_spawn": [{ "agent": "helper", "tool": "delegate", "description": "..." }]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
\`\`\`
|
|
63
|
+
|
|
64
|
+
**Key fields:**
|
|
65
|
+
- \`tools\` — builtin tools the agent can use (see list below)
|
|
66
|
+
- \`skills\` — skill directories to make available via \`activate_skill\`
|
|
67
|
+
- \`sandbox\` — restrict file access (\`allowedPaths\`) or commands (\`allowedCommands\`)
|
|
68
|
+
- \`slack\` — bind agent to a Slack channel for inbound/outbound messages
|
|
69
|
+
- \`heartbeat\` — periodic autonomous check-ins (posts to Slack if something noteworthy)
|
|
70
|
+
- \`spawn_only: true\` — agent can only be invoked by other agents, not directly
|
|
71
|
+
|
|
72
|
+
### Cron Jobs
|
|
73
|
+
|
|
74
|
+
\`\`\`json
|
|
75
|
+
"cron": [
|
|
76
|
+
{
|
|
77
|
+
"id": "daily-task",
|
|
78
|
+
"agent": "my-agent",
|
|
79
|
+
"schedule": "0 8 * * *",
|
|
80
|
+
"prompt": "Activate the my-skill skill. Do the daily task.",
|
|
81
|
+
"enabled": true
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
\`\`\`
|
|
85
|
+
|
|
86
|
+
Schedule uses standard cron syntax. Agents must activate their skill first in the prompt.
|
|
87
|
+
|
|
88
|
+
### Defaults
|
|
89
|
+
|
|
90
|
+
\`\`\`json
|
|
91
|
+
"defaults": {
|
|
92
|
+
"model": "sonnet",
|
|
93
|
+
"maxTokens": 4096,
|
|
94
|
+
"maxIterations": 20,
|
|
95
|
+
"memory": {
|
|
96
|
+
"dbPath": "./data/memory.db",
|
|
97
|
+
"ollamaEndpoint": "http://localhost:11434",
|
|
98
|
+
"ollamaModel": "all-minilm:l6-v2"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
\`\`\`
|
|
102
|
+
|
|
103
|
+
## Built-in Tools
|
|
104
|
+
|
|
105
|
+
| Tool | Description |
|
|
106
|
+
|------|-------------|
|
|
107
|
+
| \`read_file\` | Read a file (respects sandbox allowedPaths) |
|
|
108
|
+
| \`write_file\` | Write a file (respects sandbox allowedPaths) |
|
|
109
|
+
| \`run_command\` | Execute a shell command (respects sandbox allowedCommands) |
|
|
110
|
+
| \`web_search\` | Search the web (Brave or Grok provider) |
|
|
111
|
+
| \`store_memory\` | Store a memory with content and metadata |
|
|
112
|
+
| \`search_memory\` | Semantic search across stored memories |
|
|
113
|
+
| \`get_memory\` | Retrieve a specific memory by ID |
|
|
114
|
+
| \`update_memory\` | Update an existing memory |
|
|
115
|
+
| \`forget_memory\` | Delete a memory |
|
|
116
|
+
| \`list_memories\` | List all memories with optional filtering |
|
|
117
|
+
| \`update_agent_config\` | Agent updates its own config (allowlisted fields) |
|
|
118
|
+
| \`manage_cron\` | Agent manages its own cron jobs (CRUD) |
|
|
119
|
+
|
|
120
|
+
Memory tools require Ollama running with \`all-minilm:l6-v2\` model.
|
|
121
|
+
|
|
122
|
+
## Creating Skills
|
|
123
|
+
|
|
124
|
+
A skill is a directory in \`skills/\` with:
|
|
125
|
+
|
|
126
|
+
1. **\`skill.json\`** — manifest
|
|
127
|
+
\`\`\`json
|
|
128
|
+
{
|
|
129
|
+
"name": "my-skill",
|
|
130
|
+
"description": "What this skill does",
|
|
131
|
+
"tools": ["my_tool"],
|
|
132
|
+
"prompt": "SKILL.md"
|
|
133
|
+
}
|
|
134
|
+
\`\`\`
|
|
135
|
+
|
|
136
|
+
2. **\`SKILL.md\`** — instructions injected into agent context when activated
|
|
137
|
+
|
|
138
|
+
3. **\`tools/<name>.ts\`** — tool implementations
|
|
139
|
+
\`\`\`typescript
|
|
140
|
+
import type { Tool } from "@alejandroroman/agent-kit/dist/tools/types.js";
|
|
141
|
+
|
|
142
|
+
const tool: Tool = {
|
|
143
|
+
name: "my_tool",
|
|
144
|
+
description: "What this tool does",
|
|
145
|
+
parameters: {
|
|
146
|
+
type: "object",
|
|
147
|
+
properties: {
|
|
148
|
+
input: { type: "string", description: "The input" }
|
|
149
|
+
},
|
|
150
|
+
required: ["input"]
|
|
151
|
+
},
|
|
152
|
+
execute: async (args) => {
|
|
153
|
+
return "result";
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export default tool;
|
|
158
|
+
\`\`\`
|
|
159
|
+
|
|
160
|
+
## Environment Variables
|
|
161
|
+
|
|
162
|
+
| Variable | Required | Description |
|
|
163
|
+
|----------|----------|-------------|
|
|
164
|
+
| \`ANTHROPIC_API_KEY\` | Yes | Anthropic API key for Claude models |
|
|
165
|
+
| \`OPENAI_API_KEY\` | No | OpenAI API key (for fallback models) |
|
|
166
|
+
| \`SLACK_BOT_TOKEN\` | No | Slack bot token (xoxb-...) for gateway |
|
|
167
|
+
| \`SLACK_APP_TOKEN\` | No | Slack app token (xapp-...) for Socket Mode |
|
|
168
|
+
| \`BRAVE_SEARCH_API_KEY\` | No | Brave Search API key for web_search |
|
|
169
|
+
| \`XAI_API_KEY\` | No | xAI/Grok API key for web_search |
|
|
170
|
+
|
|
171
|
+
## Slack Setup
|
|
172
|
+
|
|
173
|
+
1. Create a Slack app at api.slack.com/apps → From Manifest (YAML):
|
|
174
|
+
\`\`\`yaml
|
|
175
|
+
display_information:
|
|
176
|
+
name: Agent Kit
|
|
177
|
+
settings:
|
|
178
|
+
socket_mode_enabled: true
|
|
179
|
+
features:
|
|
180
|
+
bot_user:
|
|
181
|
+
display_name: Agent Kit
|
|
182
|
+
always_online: true
|
|
183
|
+
event_subscriptions:
|
|
184
|
+
bot_events:
|
|
185
|
+
- message.channels
|
|
186
|
+
- message.groups
|
|
187
|
+
oauth_config:
|
|
188
|
+
scopes:
|
|
189
|
+
bot:
|
|
190
|
+
- chat:write
|
|
191
|
+
- channels:history
|
|
192
|
+
- channels:read
|
|
193
|
+
- groups:history
|
|
194
|
+
- groups:read
|
|
195
|
+
\`\`\`
|
|
196
|
+
2. Generate an App-Level Token (Settings → Basic Information) with \`connections:write\` scope → \`SLACK_APP_TOKEN\`
|
|
197
|
+
3. Install to workspace → copy Bot Token → \`SLACK_BOT_TOKEN\`
|
|
198
|
+
4. Create channels, invite the bot (\`/invite @Agent Kit\`), copy channel IDs
|
|
199
|
+
5. Add \`slack\` binding to each agent in \`agent-kit.json\`
|
|
200
|
+
|
|
201
|
+
## Ollama (for Memory)
|
|
202
|
+
|
|
203
|
+
Memory tools use Ollama for local embeddings:
|
|
204
|
+
\`\`\`bash
|
|
205
|
+
# Install: https://ollama.com
|
|
206
|
+
ollama pull all-minilm:l6-v2
|
|
207
|
+
# Ollama must be running when agent-kit starts
|
|
208
|
+
\`\`\`
|
|
209
|
+
|
|
210
|
+
If Ollama isn't running, everything else works — memory tools are just unavailable.
|
|
211
|
+
|
|
212
|
+
## pnpm v10 Note
|
|
213
|
+
|
|
214
|
+
If using pnpm v10+, native dependencies need explicit approval:
|
|
215
|
+
\`\`\`json
|
|
216
|
+
// In package.json:
|
|
217
|
+
"pnpm": { "onlyBuiltDependencies": ["better-sqlite3"] }
|
|
218
|
+
\`\`\`
|
|
219
|
+
Then \`pnpm install\` to rebuild native bindings.
|
|
220
|
+
`;
|
|
@@ -103,6 +103,9 @@ export function scaffoldProjectPackageJson(projectDir, projectName) {
|
|
|
103
103
|
dependencies: {
|
|
104
104
|
"@alejandroroman/agent-kit": "^0.1.0",
|
|
105
105
|
},
|
|
106
|
+
pnpm: {
|
|
107
|
+
onlyBuiltDependencies: ["better-sqlite3"],
|
|
108
|
+
},
|
|
106
109
|
};
|
|
107
110
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
108
111
|
// Create .gitignore if missing
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a .env file into a key-value map.
|
|
3
|
+
* Strips quotes, ignores comments and blank lines.
|
|
4
|
+
*/
|
|
5
|
+
export declare function loadEnvFile(envPath: string): Record<string, string>;
|
|
6
|
+
/**
|
|
7
|
+
* Upsert a key in a .env file. Replaces if exists, appends if not.
|
|
8
|
+
* Values are always quoted to prevent shell issues.
|
|
9
|
+
*/
|
|
10
|
+
export declare function upsertEnvKey(envPath: string, key: string, value: string): void;
|
|
11
|
+
/**
|
|
12
|
+
* Load specific keys from .env into process.env (if not already set).
|
|
13
|
+
*/
|
|
14
|
+
export declare function loadEnvIntoProcess(envPath: string, keys: string[]): void;
|
package/dist/cli/env.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
/**
|
|
3
|
+
* Parse a .env file into a key-value map.
|
|
4
|
+
* Strips quotes, ignores comments and blank lines.
|
|
5
|
+
*/
|
|
6
|
+
export function loadEnvFile(envPath) {
|
|
7
|
+
if (!fs.existsSync(envPath))
|
|
8
|
+
return {};
|
|
9
|
+
const result = {};
|
|
10
|
+
const content = fs.readFileSync(envPath, "utf-8");
|
|
11
|
+
for (const line of content.split("\n")) {
|
|
12
|
+
const trimmed = line.trim();
|
|
13
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
14
|
+
continue;
|
|
15
|
+
const eqIndex = trimmed.indexOf("=");
|
|
16
|
+
if (eqIndex === -1)
|
|
17
|
+
continue;
|
|
18
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
19
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
20
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
21
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
22
|
+
value = value.slice(1, -1);
|
|
23
|
+
}
|
|
24
|
+
result[key] = value;
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Upsert a key in a .env file. Replaces if exists, appends if not.
|
|
30
|
+
* Values are always quoted to prevent shell issues.
|
|
31
|
+
*/
|
|
32
|
+
export function upsertEnvKey(envPath, key, value) {
|
|
33
|
+
const quotedLine = `${key}="${value}"`;
|
|
34
|
+
if (!fs.existsSync(envPath)) {
|
|
35
|
+
fs.writeFileSync(envPath, quotedLine + "\n", { mode: 0o600 });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const content = fs.readFileSync(envPath, "utf-8");
|
|
39
|
+
const lines = content.split("\n");
|
|
40
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
41
|
+
const pattern = new RegExp(`^${escaped}\\s*=`);
|
|
42
|
+
let replaced = false;
|
|
43
|
+
const updated = lines.map((line) => {
|
|
44
|
+
if (pattern.test(line.trim())) {
|
|
45
|
+
replaced = true;
|
|
46
|
+
return quotedLine;
|
|
47
|
+
}
|
|
48
|
+
return line;
|
|
49
|
+
});
|
|
50
|
+
if (!replaced) {
|
|
51
|
+
const needsNewline = content.length > 0 && !content.endsWith("\n");
|
|
52
|
+
fs.writeFileSync(envPath, content + (needsNewline ? "\n" : "") + quotedLine + "\n", { mode: 0o600 });
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
fs.writeFileSync(envPath, updated.join("\n"), { mode: 0o600 });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Load specific keys from .env into process.env (if not already set).
|
|
60
|
+
*/
|
|
61
|
+
export function loadEnvIntoProcess(envPath, keys) {
|
|
62
|
+
const env = loadEnvFile(envPath);
|
|
63
|
+
for (const key of keys) {
|
|
64
|
+
if (!process.env[key] && env[key]) {
|
|
65
|
+
process.env[key] = env[key];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
package/dist/cli/init.js
CHANGED
|
@@ -5,7 +5,10 @@ import { banner, isCancel, done, resolveApiKey } from "./ui.js";
|
|
|
5
5
|
import { checkOllama } from "./ollama.js";
|
|
6
6
|
import { createFreshConfig, scaffoldProjectPackageJson } from "./config-writer.js";
|
|
7
7
|
import { runSetupAgent } from "./setup-agent/index.js";
|
|
8
|
+
import { setupSlack } from "./slack-setup.js";
|
|
8
9
|
import { CONFIG_PATH, DATA_DIR, SKILLS_DIR } from "./paths.js";
|
|
10
|
+
import { CLAUDE_MD_TEMPLATE } from "./claude-md-template.js";
|
|
11
|
+
import * as path from "path";
|
|
9
12
|
function detectPackageManager() {
|
|
10
13
|
try {
|
|
11
14
|
execSync("pnpm --version", { stdio: "ignore" });
|
|
@@ -40,6 +43,11 @@ export async function init() {
|
|
|
40
43
|
createFreshConfig({ configPath: CONFIG_PATH });
|
|
41
44
|
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
42
45
|
fs.mkdirSync(SKILLS_DIR, { recursive: true });
|
|
46
|
+
// Generate CLAUDE.md for Claude Code context
|
|
47
|
+
const claudeMdPath = path.join(process.cwd(), "CLAUDE.md");
|
|
48
|
+
if (!fs.existsSync(claudeMdPath)) {
|
|
49
|
+
fs.writeFileSync(claudeMdPath, CLAUDE_MD_TEMPLATE);
|
|
50
|
+
}
|
|
43
51
|
}
|
|
44
52
|
catch (err) {
|
|
45
53
|
s.stop(`Failed to create project: ${err instanceof Error ? err.message : err}`);
|
|
@@ -79,4 +87,6 @@ export async function init() {
|
|
|
79
87
|
});
|
|
80
88
|
console.log();
|
|
81
89
|
done(summary);
|
|
90
|
+
// Offer Slack setup after agent creation
|
|
91
|
+
await setupSlack({ configPath: CONFIG_PATH, fromInit: true });
|
|
82
92
|
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import { execFileSync } from "child_process";
|
|
3
|
+
import * as p from "@clack/prompts";
|
|
4
|
+
import { isCancel } from "./ui.js";
|
|
5
|
+
import { upsertEnvKey, loadEnvFile } from "./env.js";
|
|
6
|
+
import { readRawConfig, writeConfig } from "./config-writer.js";
|
|
7
|
+
import { loadConfig } from "../config/index.js";
|
|
8
|
+
const SLACK_MANIFEST = `display_information:
|
|
9
|
+
name: Agent Kit
|
|
10
|
+
settings:
|
|
11
|
+
socket_mode_enabled: true
|
|
12
|
+
features:
|
|
13
|
+
bot_user:
|
|
14
|
+
display_name: Agent Kit
|
|
15
|
+
always_online: true
|
|
16
|
+
event_subscriptions:
|
|
17
|
+
bot_events:
|
|
18
|
+
- message.channels
|
|
19
|
+
- message.groups
|
|
20
|
+
oauth_config:
|
|
21
|
+
scopes:
|
|
22
|
+
bot:
|
|
23
|
+
- chat:write
|
|
24
|
+
- channels:history
|
|
25
|
+
- channels:read
|
|
26
|
+
- groups:history
|
|
27
|
+
- groups:read`;
|
|
28
|
+
function openBrowser(url) {
|
|
29
|
+
try {
|
|
30
|
+
const cmd = process.platform === "darwin" ? "open"
|
|
31
|
+
: process.platform === "win32" ? "start"
|
|
32
|
+
: "xdg-open";
|
|
33
|
+
execFileSync(cmd, [url], { stdio: "ignore" });
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
p.log.warn(`Could not open browser. Visit: ${url}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function setupSlack(opts) {
|
|
40
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
41
|
+
// 1. Opt-in (only during init)
|
|
42
|
+
if (opts.fromInit) {
|
|
43
|
+
const wantSlack = await p.confirm({
|
|
44
|
+
message: "Would you like to connect Slack?",
|
|
45
|
+
});
|
|
46
|
+
if (isCancel(wantSlack) || !wantSlack)
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// Check for existing tokens
|
|
50
|
+
const existingEnv = loadEnvFile(envPath);
|
|
51
|
+
const hasAppToken = !!existingEnv.SLACK_APP_TOKEN;
|
|
52
|
+
const hasBotToken = !!existingEnv.SLACK_BOT_TOKEN;
|
|
53
|
+
let skipTokens = false;
|
|
54
|
+
if (hasAppToken && hasBotToken) {
|
|
55
|
+
const keep = await p.confirm({
|
|
56
|
+
message: "Slack tokens already exist in .env. Keep them?",
|
|
57
|
+
});
|
|
58
|
+
if (isCancel(keep))
|
|
59
|
+
return;
|
|
60
|
+
if (keep) {
|
|
61
|
+
skipTokens = true;
|
|
62
|
+
process.env.SLACK_APP_TOKEN ??= existingEnv.SLACK_APP_TOKEN;
|
|
63
|
+
process.env.SLACK_BOT_TOKEN ??= existingEnv.SLACK_BOT_TOKEN;
|
|
64
|
+
p.log.info("Using existing tokens from .env");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// 2. Instructions — Create App
|
|
68
|
+
if (!skipTokens) {
|
|
69
|
+
p.log.step("Step 1: Create Slack App");
|
|
70
|
+
console.log();
|
|
71
|
+
console.log(" Go to api.slack.com/apps → Create New App → From a manifest");
|
|
72
|
+
console.log();
|
|
73
|
+
const openPage = await p.confirm({
|
|
74
|
+
message: "Open api.slack.com in your browser?",
|
|
75
|
+
});
|
|
76
|
+
if (!isCancel(openPage) && openPage) {
|
|
77
|
+
openBrowser("https://api.slack.com/apps");
|
|
78
|
+
}
|
|
79
|
+
console.log();
|
|
80
|
+
console.log(" Select your workspace, then paste this manifest:");
|
|
81
|
+
console.log();
|
|
82
|
+
for (const line of SLACK_MANIFEST.split("\n")) {
|
|
83
|
+
console.log(` ${line}`);
|
|
84
|
+
}
|
|
85
|
+
console.log();
|
|
86
|
+
console.log(" Click Create.");
|
|
87
|
+
console.log();
|
|
88
|
+
// 3. Instructions — App Token
|
|
89
|
+
p.log.step("Step 2: Generate App-Level Token");
|
|
90
|
+
console.log();
|
|
91
|
+
console.log(" Settings → Basic Information → App-Level Tokens");
|
|
92
|
+
console.log(" Click 'Generate Token and Scopes'");
|
|
93
|
+
console.log(" Name: socket-mode | Scope: connections:write");
|
|
94
|
+
console.log(" Copy the xapp-... token");
|
|
95
|
+
console.log();
|
|
96
|
+
// 4. Collect App Token
|
|
97
|
+
const appToken = await p.password({
|
|
98
|
+
message: "Paste App Token (xapp-...):",
|
|
99
|
+
validate: (val) => {
|
|
100
|
+
if (!val?.trim())
|
|
101
|
+
return "App token is required";
|
|
102
|
+
if (!val.startsWith("xapp-"))
|
|
103
|
+
return "Token should start with xapp-";
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
if (isCancel(appToken))
|
|
107
|
+
return;
|
|
108
|
+
upsertEnvKey(envPath, "SLACK_APP_TOKEN", appToken.trim());
|
|
109
|
+
process.env.SLACK_APP_TOKEN = appToken.trim();
|
|
110
|
+
p.log.success("App token saved to .env");
|
|
111
|
+
// 5. Instructions — Bot Token
|
|
112
|
+
p.log.step("Step 3: Install to Workspace");
|
|
113
|
+
console.log();
|
|
114
|
+
console.log(" Settings → Install App → Install to Workspace");
|
|
115
|
+
console.log(" Authorize, then copy the Bot User OAuth Token (xoxb-...)");
|
|
116
|
+
console.log();
|
|
117
|
+
// 6. Collect Bot Token
|
|
118
|
+
const botToken = await p.password({
|
|
119
|
+
message: "Paste Bot Token (xoxb-...):",
|
|
120
|
+
validate: (val) => {
|
|
121
|
+
if (!val?.trim())
|
|
122
|
+
return "Bot token is required";
|
|
123
|
+
if (!val.startsWith("xoxb-"))
|
|
124
|
+
return "Token should start with xoxb-";
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
if (isCancel(botToken))
|
|
128
|
+
return;
|
|
129
|
+
upsertEnvKey(envPath, "SLACK_BOT_TOKEN", botToken.trim());
|
|
130
|
+
process.env.SLACK_BOT_TOKEN = botToken.trim();
|
|
131
|
+
p.log.success("Bot token saved to .env");
|
|
132
|
+
} // end if (!skipTokens)
|
|
133
|
+
// 7. Channel Binding
|
|
134
|
+
let config;
|
|
135
|
+
try {
|
|
136
|
+
config = loadConfig(opts.configPath);
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
p.log.error(`Could not load config: ${err instanceof Error ? err.message : err}`);
|
|
140
|
+
p.log.info("Slack tokens were saved to .env. Run `agent-kit slack-setup` again after fixing your config.");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const agentNames = Object.keys(config.agents);
|
|
144
|
+
if (agentNames.length === 0) {
|
|
145
|
+
p.log.info("No agents configured yet. Run `agent-kit create` first, then `agent-kit slack-setup` to bind channels.");
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
p.log.step("Step 4: Connect agents to Slack channels");
|
|
149
|
+
console.log();
|
|
150
|
+
console.log(" Create channels in Slack, then invite the bot: /invite @Agent Kit");
|
|
151
|
+
console.log(" Get channel IDs: right-click channel → View channel details → copy ID (starts with C)");
|
|
152
|
+
console.log();
|
|
153
|
+
const raw = readRawConfig(opts.configPath);
|
|
154
|
+
if (!raw) {
|
|
155
|
+
p.log.error("Could not read agent-kit.json");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const agents = raw.agents;
|
|
159
|
+
if (!agents) {
|
|
160
|
+
p.log.error("No agents section in config");
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const usedChannels = new Map(); // channelId → agentName
|
|
164
|
+
// Collect already-bound channels
|
|
165
|
+
for (const [name, def] of Object.entries(agents)) {
|
|
166
|
+
const slack = def.slack;
|
|
167
|
+
if (slack?.channelId) {
|
|
168
|
+
usedChannels.set(slack.channelId, name);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
for (const name of agentNames) {
|
|
172
|
+
const agentDef = config.agents[name];
|
|
173
|
+
const emoji = agentDef.emoji ?? " ";
|
|
174
|
+
const display = agentDef.displayName ?? name;
|
|
175
|
+
// Skip if already bound
|
|
176
|
+
if (agentDef.slack) {
|
|
177
|
+
p.log.info(`${emoji} ${display} — already bound to ${agentDef.slack.channelName}`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const bind = await p.confirm({
|
|
181
|
+
message: `Connect ${emoji} ${display} to a Slack channel?`,
|
|
182
|
+
});
|
|
183
|
+
if (isCancel(bind))
|
|
184
|
+
return;
|
|
185
|
+
if (!bind)
|
|
186
|
+
continue;
|
|
187
|
+
const channelId = await p.text({
|
|
188
|
+
message: "Channel ID (e.g. C0123456789):",
|
|
189
|
+
validate: (val) => {
|
|
190
|
+
if (!val?.trim())
|
|
191
|
+
return "Channel ID is required";
|
|
192
|
+
if (!/^[CG][A-Z0-9]{8,}$/.test(val.trim())) {
|
|
193
|
+
return "Channel ID should start with C or G followed by uppercase letters/numbers";
|
|
194
|
+
}
|
|
195
|
+
const existing = usedChannels.get(val.trim());
|
|
196
|
+
if (existing) {
|
|
197
|
+
return `Channel already bound to agent "${existing}"`;
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
if (isCancel(channelId))
|
|
202
|
+
return;
|
|
203
|
+
const channelName = await p.text({
|
|
204
|
+
message: "Channel name (e.g. #finances):",
|
|
205
|
+
validate: (val) => {
|
|
206
|
+
if (!val?.trim())
|
|
207
|
+
return "Channel name is required";
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
if (isCancel(channelName))
|
|
211
|
+
return;
|
|
212
|
+
const id = channelId.trim();
|
|
213
|
+
const cName = channelName.trim();
|
|
214
|
+
// Update raw config
|
|
215
|
+
agents[name] = {
|
|
216
|
+
...agents[name],
|
|
217
|
+
slack: { channelId: id, channelName: cName },
|
|
218
|
+
};
|
|
219
|
+
usedChannels.set(id, name);
|
|
220
|
+
p.log.success(`${emoji} ${display} → ${cName}`);
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
writeConfig(opts.configPath, raw);
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
p.log.error(`Could not save config: ${err instanceof Error ? err.message : err}`);
|
|
227
|
+
p.log.info("Slack tokens were saved, but channel bindings were not persisted.");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// 8. Done
|
|
232
|
+
console.log();
|
|
233
|
+
p.log.success("Slack configured! Start with `pnpm start` to connect.");
|
|
234
|
+
}
|