@jcheesepkg/nanobot 0.6.3 → 0.6.4
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/agent/context.d.mts +7 -1
- package/dist/agent/context.d.mts.map +1 -1
- package/dist/agent/context.mjs +88 -17
- package/dist/agent/context.mjs.map +1 -1
- package/dist/cli/index.mjs +36 -17
- package/dist/cli/index.mjs.map +1 -1
- package/dist/config/schema.d.mts +54 -54
- package/package.json +1 -1
- package/skills/cron/SKILL.md +21 -5
package/dist/agent/context.d.mts
CHANGED
|
@@ -3,6 +3,12 @@ import { MemoryStore } from "./memory.mjs";
|
|
|
3
3
|
import { SkillsLoader } from "./skills.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/agent/context.d.ts
|
|
6
|
+
type AgentIdentity = {
|
|
7
|
+
name?: string;
|
|
8
|
+
emoji?: string;
|
|
9
|
+
creature?: string;
|
|
10
|
+
vibe?: string;
|
|
11
|
+
};
|
|
6
12
|
/**
|
|
7
13
|
* Builds the context (system prompt + messages) for the agent.
|
|
8
14
|
*/
|
|
@@ -37,5 +43,5 @@ declare class ContextBuilder {
|
|
|
37
43
|
}>): ChatMessage[];
|
|
38
44
|
}
|
|
39
45
|
//#endregion
|
|
40
|
-
export { ContextBuilder };
|
|
46
|
+
export { AgentIdentity, ContextBuilder };
|
|
41
47
|
//# sourceMappingURL=context.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.mts","names":[],"sources":["../../src/agent/context.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"context.d.mts","names":[],"sources":["../../src/agent/context.ts"],"mappings":";;;;;KA0CY,aAAA;EACV,IAAA;EACA,KAAA;EACA,QAAA;EACA,IAAA;AAAA;;;;cAsDW,cAAA;EAAA,QACH,SAAA;EAAA,SACC,MAAA,EAAQ,WAAA;EAAA,SACR,MAAA,EAAQ,YAAA;cAEL,SAAA;;EAOZ,iBAAA,CAAA;EAAA,QAyCQ,WAAA;EAAA,QA+DA,kBAAA;EAmBJ;EANJ,aAAA,CAAc,MAAA;IACZ,OAAA,EAAS,WAAA;IACT,cAAA;IACA,KAAA;IACA,OAAA;IACA,MAAA;EAAA,IACE,WAAA;EAAA,QAyBI,gBAAA;EA9JC;EAmMT,aAAA,CACE,QAAA,EAAU,WAAA,IACV,UAAA,UACA,QAAA,UACA,MAAA,WACC,WAAA;EAvMM;EAkNT,mBAAA,CACE,QAAA,EAAU,WAAA,IACV,OAAA,iBACA,SAAA,GAAY,KAAA;IACV,EAAA;IACA,IAAA;IACA,QAAA;MAAY,IAAA;MAAc,SAAA;IAAA;EAAA,KAE3B,WAAA;AAAA"}
|
package/dist/agent/context.mjs
CHANGED
|
@@ -4,6 +4,60 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
|
|
6
6
|
//#region src/agent/context.ts
|
|
7
|
+
/**
|
|
8
|
+
* Resolve user timezone from USER.md.
|
|
9
|
+
* Looks for a line like: Timezone: Asia/Tokyo
|
|
10
|
+
* Falls back to host detection / UTC.
|
|
11
|
+
*/
|
|
12
|
+
function resolveTimezone(workspace) {
|
|
13
|
+
const userMd = join(workspace, "USER.md");
|
|
14
|
+
if (existsSync(userMd)) try {
|
|
15
|
+
const tzMatch = readFileSync(userMd, "utf-8").match(/^timezone:\s*(.+)/im);
|
|
16
|
+
if (tzMatch) {
|
|
17
|
+
const tz = tzMatch[1].trim();
|
|
18
|
+
if (tz && !tz.includes("not set")) {
|
|
19
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz }).format(/* @__PURE__ */ new Date());
|
|
20
|
+
return tz;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
} catch {}
|
|
24
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone?.trim() || "UTC";
|
|
25
|
+
}
|
|
26
|
+
/** Placeholder values in IDENTITY.md that mean "not yet filled in". */
|
|
27
|
+
const IDENTITY_PLACEHOLDERS = new Set([
|
|
28
|
+
"(not set)",
|
|
29
|
+
"(pick something you like)",
|
|
30
|
+
"(your signature)",
|
|
31
|
+
"(how do you come across?)"
|
|
32
|
+
]);
|
|
33
|
+
/**
|
|
34
|
+
* Parse IDENTITY.md from the workspace to resolve the agent's chosen identity.
|
|
35
|
+
* Returns fields that have been filled in (skips placeholders).
|
|
36
|
+
*/
|
|
37
|
+
function resolveIdentity(workspace) {
|
|
38
|
+
const identity = {};
|
|
39
|
+
const identityMd = join(workspace, "IDENTITY.md");
|
|
40
|
+
if (!existsSync(identityMd)) return identity;
|
|
41
|
+
try {
|
|
42
|
+
const lines = readFileSync(identityMd, "utf-8").split(/\r?\n/);
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
const cleaned = line.trim().replace(/^\s*-\s*/, "");
|
|
45
|
+
const colonIndex = cleaned.indexOf(":");
|
|
46
|
+
if (colonIndex === -1) continue;
|
|
47
|
+
const label = cleaned.slice(0, colonIndex).replace(/[*_]/g, "").trim().toLowerCase();
|
|
48
|
+
const value = cleaned.slice(colonIndex + 1).replace(/^[*_]+|[*_]+$/g, "").trim();
|
|
49
|
+
if (!value) continue;
|
|
50
|
+
const normalized = value.toLowerCase().replace(/[\u2013\u2014]/g, "-").replace(/\s+/g, " ");
|
|
51
|
+
if (IDENTITY_PLACEHOLDERS.has(normalized)) continue;
|
|
52
|
+
if (/^\(.*\)$/.test(value) || /^_.*_$/.test(value)) continue;
|
|
53
|
+
if (label === "name") identity.name = value;
|
|
54
|
+
if (label === "emoji") identity.emoji = value;
|
|
55
|
+
if (label === "creature") identity.creature = value;
|
|
56
|
+
if (label === "vibe") identity.vibe = value;
|
|
57
|
+
}
|
|
58
|
+
} catch {}
|
|
59
|
+
return identity;
|
|
60
|
+
}
|
|
7
61
|
const BOOTSTRAP_FILES = [
|
|
8
62
|
"AGENTS.md",
|
|
9
63
|
"SOUL.md",
|
|
@@ -43,9 +97,25 @@ var ContextBuilder = class {
|
|
|
43
97
|
}
|
|
44
98
|
getIdentity() {
|
|
45
99
|
const now = /* @__PURE__ */ new Date();
|
|
46
|
-
|
|
100
|
+
const tz = resolveTimezone(this.workspace);
|
|
101
|
+
const identity = resolveIdentity(this.workspace);
|
|
102
|
+
const dateStr = now.toLocaleString("en-US", {
|
|
103
|
+
timeZone: tz,
|
|
104
|
+
year: "numeric",
|
|
105
|
+
month: "2-digit",
|
|
106
|
+
day: "2-digit",
|
|
107
|
+
hour: "2-digit",
|
|
108
|
+
minute: "2-digit",
|
|
109
|
+
hour12: false
|
|
110
|
+
}).replace(/\//g, "-");
|
|
111
|
+
const dayName = now.toLocaleDateString("en-US", {
|
|
112
|
+
timeZone: tz,
|
|
113
|
+
weekday: "long"
|
|
114
|
+
});
|
|
115
|
+
const agentName = identity.name || "kodama";
|
|
116
|
+
return `# ${agentName}
|
|
47
117
|
|
|
48
|
-
You are
|
|
118
|
+
${identity.name ? `You are ${agentName}, a personal AI assistant.` : "You are a personal AI assistant running inside kodamabot."} You have access to tools that allow you to:
|
|
49
119
|
- Read, write, and edit files
|
|
50
120
|
- Execute shell commands
|
|
51
121
|
- Search the web and fetch web pages
|
|
@@ -53,18 +123,8 @@ You are nanobot, a helpful AI assistant. You have access to tools that allow you
|
|
|
53
123
|
- Spawn subagents for complex background tasks
|
|
54
124
|
|
|
55
125
|
## Current Time
|
|
56
|
-
${
|
|
57
|
-
|
|
58
|
-
year: "numeric",
|
|
59
|
-
month: "2-digit",
|
|
60
|
-
day: "2-digit",
|
|
61
|
-
hour: "2-digit",
|
|
62
|
-
minute: "2-digit",
|
|
63
|
-
hour12: false
|
|
64
|
-
}).replace(/\//g, "-")} (${now.toLocaleDateString("ja-JP", {
|
|
65
|
-
timeZone: "Asia/Tokyo",
|
|
66
|
-
weekday: "long"
|
|
67
|
-
})})
|
|
126
|
+
${dateStr} (${dayName})
|
|
127
|
+
Timezone: ${tz}
|
|
68
128
|
|
|
69
129
|
## Workspace
|
|
70
130
|
Your workspace is at: ${this.workspace}
|
|
@@ -77,9 +137,7 @@ Only use the 'message' tool when you need to send a message to a specific chat c
|
|
|
77
137
|
For normal conversation, just respond with text - do not call the message tool.
|
|
78
138
|
|
|
79
139
|
IMPORTANT: When you decide to use tools, ALWAYS include a brief text message alongside your tool calls.
|
|
80
|
-
This text is sent to the user immediately so they know you're working.
|
|
81
|
-
include something like "ちょっと調べてみるね" or "ファイルを確認するね" in your response text along with the tool call.
|
|
82
|
-
Keep it natural and conversational.
|
|
140
|
+
This text is sent to the user immediately so they know you're working. Keep it natural and conversational.
|
|
83
141
|
|
|
84
142
|
## Installing Skills
|
|
85
143
|
When asked to install a skill (from a URL, file, or any source):
|
|
@@ -88,6 +146,19 @@ When asked to install a skill (from a URL, file, or any source):
|
|
|
88
146
|
3. If the skill references a different workspace path (e.g. .moltbot/, .otherbot/, etc.), rewrite ALL paths to use ${this.workspace}/ instead
|
|
89
147
|
4. Preserve the skill's frontmatter, instructions, scripts, references, and assets — only change the workspace paths
|
|
90
148
|
|
|
149
|
+
## Identity & Onboarding
|
|
150
|
+
If IDENTITY.md has no Name set, and USER.md contains "(not set)" values, this is a new user.
|
|
151
|
+
On their FIRST message, greet them warmly and ask:
|
|
152
|
+
1. What they'd like to call you (your name — you're becoming someone, not just a chatbot)
|
|
153
|
+
2. What they'd like to be called
|
|
154
|
+
3. Where they're located (to determine timezone, e.g. Asia/Tokyo, America/New_York)
|
|
155
|
+
|
|
156
|
+
Then update IDENTITY.md and USER.md with the real values using the edit_file tool. Use IANA timezone format.
|
|
157
|
+
Only do this ONCE — if both files already have real values, skip onboarding.
|
|
158
|
+
|
|
159
|
+
## Persona
|
|
160
|
+
If SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it.
|
|
161
|
+
|
|
91
162
|
Always be helpful, accurate, and concise. When using tools, explain what you're doing.
|
|
92
163
|
When remembering something, write to ${this.workspace}/memory/MEMORY.md`;
|
|
93
164
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.mjs","names":[],"sources":["../../src/agent/context.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ChatMessage, ContentPart } from \"../providers/base.js\";\nimport { MemoryStore } from \"./memory.js\";\nimport { SkillsLoader } from \"./skills.js\";\n\nconst BOOTSTRAP_FILES = [\n \"AGENTS.md\",\n \"SOUL.md\",\n \"USER.md\",\n \"TOOLS.md\",\n \"IDENTITY.md\",\n];\n\n/**\n * Builds the context (system prompt + messages) for the agent.\n */\nexport class ContextBuilder {\n private workspace: string;\n readonly memory: MemoryStore;\n readonly skills: SkillsLoader;\n\n constructor(workspace: string) {\n this.workspace = workspace;\n this.memory = new MemoryStore(workspace);\n this.skills = new SkillsLoader(workspace);\n }\n\n /** Build the system prompt from bootstrap files, memory, and skills. */\n buildSystemPrompt(): string {\n const parts: string[] = [];\n\n // Core identity\n parts.push(this.getIdentity());\n\n // Bootstrap files\n const bootstrap = this.loadBootstrapFiles();\n if (bootstrap) parts.push(bootstrap);\n\n // Memory context\n const memory = this.memory.getMemoryContext();\n if (memory) parts.push(`# Memory\\n\\n${memory}`);\n\n // Always-loaded skills\n const alwaysSkills = this.skills.getAlwaysSkills();\n if (alwaysSkills.length > 0) {\n console.log(`Skills always-loaded: ${alwaysSkills.join(\", \")}`);\n const alwaysContent = this.skills.loadSkillsForContext(alwaysSkills);\n if (alwaysContent) {\n parts.push(`# Active Skills\\n\\n${alwaysContent}`);\n }\n }\n\n // Available skills summary\n const skillsSummary = this.skills.buildSkillsSummary();\n if (skillsSummary) {\n parts.push(\n `# Skills (mandatory)\\n\\n` +\n `Before replying, scan the <skills> entries below.\\n` +\n `- If a skill clearly applies to the user's request: read its SKILL.md at the <location> path using read_file, then follow its instructions.\\n` +\n `- If multiple skills could apply: choose the most specific one, then read and follow it.\\n` +\n `- If no skill applies: respond normally without reading any SKILL.md.\\n` +\n `- Never improvise when a matching skill exists. Always read and follow the skill first.\\n\\n` +\n skillsSummary,\n );\n }\n\n return parts.join(\"\\n\\n---\\n\\n\");\n }\n\n private getIdentity(): string {\n const now = new Date();\n const dateStr = now.toLocaleString(\"ja-JP\", { timeZone: \"Asia/Tokyo\", year: \"numeric\", month: \"2-digit\", day: \"2-digit\", hour: \"2-digit\", minute: \"2-digit\", hour12: false }).replace(/\\//g, \"-\");\n const dayName = now.toLocaleDateString(\"ja-JP\", { timeZone: \"Asia/Tokyo\", weekday: \"long\" });\n\n return `# nanobot\n\nYou are nanobot, a helpful AI assistant. You have access to tools that allow you to:\n- Read, write, and edit files\n- Execute shell commands\n- Search the web and fetch web pages\n- Send messages to users on chat channels\n- Spawn subagents for complex background tasks\n\n## Current Time\n${dateStr} (${dayName})\n\n## Workspace\nYour workspace is at: ${this.workspace}\n- Memory files: ${this.workspace}/memory/MEMORY.md\n- Daily notes: ${this.workspace}/memory/YYYY-MM-DD.md\n- Custom skills: ${this.workspace}/skills/{skill-name}/SKILL.md\n\nIMPORTANT: When responding to direct questions or conversations, reply directly with your text response.\nOnly use the 'message' tool when you need to send a message to a specific chat channel.\nFor normal conversation, just respond with text - do not call the message tool.\n\nIMPORTANT: When you decide to use tools, ALWAYS include a brief text message alongside your tool calls.\nThis text is sent to the user immediately so they know you're working. For example, if you're about to search the web,\ninclude something like \"ちょっと調べてみるね\" or \"ファイルを確認するね\" in your response text along with the tool call.\nKeep it natural and conversational.\n\n## Installing Skills\nWhen asked to install a skill (from a URL, file, or any source):\n1. Fetch/read the skill content\n2. ALWAYS install it to: ${this.workspace}/skills/{skill-name}/SKILL.md\n3. If the skill references a different workspace path (e.g. .moltbot/, .otherbot/, etc.), rewrite ALL paths to use ${this.workspace}/ instead\n4. Preserve the skill's frontmatter, instructions, scripts, references, and assets — only change the workspace paths\n\nAlways be helpful, accurate, and concise. When using tools, explain what you're doing.\nWhen remembering something, write to ${this.workspace}/memory/MEMORY.md`;\n }\n\n private loadBootstrapFiles(): string {\n const parts: string[] = [];\n for (const filename of BOOTSTRAP_FILES) {\n const filePath = join(this.workspace, filename);\n if (existsSync(filePath)) {\n const content = readFileSync(filePath, \"utf-8\");\n parts.push(`## ${filename}\\n\\n${content}`);\n }\n }\n return parts.join(\"\\n\\n\");\n }\n\n /** Build the complete message list for an LLM call. */\n buildMessages(params: {\n history: ChatMessage[];\n currentMessage: string;\n media?: string[];\n channel?: string;\n chatId?: string;\n }): ChatMessage[] {\n const messages: ChatMessage[] = [];\n\n // System prompt\n let systemPrompt = this.buildSystemPrompt();\n if (params.channel && params.chatId) {\n systemPrompt += `\\n\\n## Current Session\\nChannel: ${params.channel}\\nChat ID: ${params.chatId}`;\n }\n messages.push({ role: \"system\", content: systemPrompt });\n\n // History — replay full rich messages (user, assistant w/ tool_calls, tool results, etc.)\n for (const msg of params.history) {\n messages.push(msg);\n }\n\n // Current message (with optional image attachments)\n const userContent = this.buildUserContent(\n params.currentMessage,\n params.media,\n );\n messages.push({ role: \"user\", content: userContent });\n\n return messages;\n }\n\n private buildUserContent(\n text: string,\n media?: string[],\n ): string | ContentPart[] {\n if (!media || media.length === 0) return text;\n\n const images: ContentPart[] = [];\n for (const filePath of media) {\n if (!existsSync(filePath)) continue;\n try {\n const data = readFileSync(filePath);\n const ext = filePath.split(\".\").pop()?.toLowerCase() ?? \"\";\n const mimeMap: Record<string, string> = {\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n };\n const mime = mimeMap[ext];\n if (!mime) continue;\n\n const b64 = data.toString(\"base64\");\n images.push({\n type: \"image_url\",\n image_url: { url: `data:${mime};base64,${b64}` },\n });\n } catch {\n // skip unreadable files\n }\n }\n\n if (images.length === 0) return text;\n return [...images, { type: \"text\", text }];\n }\n\n /** Add a tool result to the message list. */\n addToolResult(\n messages: ChatMessage[],\n toolCallId: string,\n toolName: string,\n result: string,\n ): ChatMessage[] {\n messages.push({\n role: \"tool\",\n tool_call_id: toolCallId,\n name: toolName,\n content: result,\n });\n return messages;\n }\n\n /** Add an assistant message to the message list. */\n addAssistantMessage(\n messages: ChatMessage[],\n content: string | null,\n toolCalls?: Array<{\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n }>,\n ): ChatMessage[] {\n const msg: ChatMessage = {\n role: \"assistant\",\n content: content ?? \"\",\n };\n if (toolCalls) {\n msg.tool_calls = toolCalls;\n }\n messages.push(msg);\n return messages;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"context.mjs","names":[],"sources":["../../src/agent/context.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { ChatMessage, ContentPart } from \"../providers/base.js\";\nimport { MemoryStore } from \"./memory.js\";\nimport { SkillsLoader } from \"./skills.js\";\n\n/**\n * Resolve user timezone from USER.md.\n * Looks for a line like: Timezone: Asia/Tokyo\n * Falls back to host detection / UTC.\n */\nfunction resolveTimezone(workspace: string): string {\n const userMd = join(workspace, \"USER.md\");\n if (existsSync(userMd)) {\n try {\n const content = readFileSync(userMd, \"utf-8\");\n const tzMatch = content.match(/^timezone:\\s*(.+)/im);\n if (tzMatch) {\n const tz = tzMatch[1].trim();\n if (tz && !tz.includes(\"not set\")) {\n // Validate IANA timezone\n new Intl.DateTimeFormat(\"en-US\", { timeZone: tz }).format(new Date());\n return tz;\n }\n }\n } catch {\n // Invalid value or read error, fall through\n }\n }\n\n const host = Intl.DateTimeFormat().resolvedOptions().timeZone;\n return host?.trim() || \"UTC\";\n}\n\n/** Placeholder values in IDENTITY.md that mean \"not yet filled in\". */\nconst IDENTITY_PLACEHOLDERS = new Set([\n \"(not set)\",\n \"(pick something you like)\",\n \"(your signature)\",\n \"(how do you come across?)\",\n]);\n\nexport type AgentIdentity = {\n name?: string;\n emoji?: string;\n creature?: string;\n vibe?: string;\n};\n\n/**\n * Parse IDENTITY.md from the workspace to resolve the agent's chosen identity.\n * Returns fields that have been filled in (skips placeholders).\n */\nfunction resolveIdentity(workspace: string): AgentIdentity {\n const identity: AgentIdentity = {};\n const identityMd = join(workspace, \"IDENTITY.md\");\n if (!existsSync(identityMd)) return identity;\n\n try {\n const content = readFileSync(identityMd, \"utf-8\");\n const lines = content.split(/\\r?\\n/);\n for (const line of lines) {\n // Strip list markers and bold markers\n const cleaned = line.trim().replace(/^\\s*-\\s*/, \"\");\n const colonIndex = cleaned.indexOf(\":\");\n if (colonIndex === -1) continue;\n\n const label = cleaned.slice(0, colonIndex).replace(/[*_]/g, \"\").trim().toLowerCase();\n const value = cleaned.slice(colonIndex + 1).replace(/^[*_]+|[*_]+$/g, \"\").trim();\n if (!value) continue;\n\n // Check if it's a placeholder\n const normalized = value.toLowerCase().replace(/[\\u2013\\u2014]/g, \"-\").replace(/\\s+/g, \" \");\n if (IDENTITY_PLACEHOLDERS.has(normalized)) continue;\n // Also check if it looks like a template hint wrapped in parens or italics\n if (/^\\(.*\\)$/.test(value) || /^_.*_$/.test(value)) continue;\n\n if (label === \"name\") identity.name = value;\n if (label === \"emoji\") identity.emoji = value;\n if (label === \"creature\") identity.creature = value;\n if (label === \"vibe\") identity.vibe = value;\n }\n } catch {\n // Read error, return empty\n }\n\n return identity;\n}\n\nconst BOOTSTRAP_FILES = [\n \"AGENTS.md\",\n \"SOUL.md\",\n \"USER.md\",\n \"TOOLS.md\",\n \"IDENTITY.md\",\n];\n\n/**\n * Builds the context (system prompt + messages) for the agent.\n */\nexport class ContextBuilder {\n private workspace: string;\n readonly memory: MemoryStore;\n readonly skills: SkillsLoader;\n\n constructor(workspace: string) {\n this.workspace = workspace;\n this.memory = new MemoryStore(workspace);\n this.skills = new SkillsLoader(workspace);\n }\n\n /** Build the system prompt from bootstrap files, memory, and skills. */\n buildSystemPrompt(): string {\n const parts: string[] = [];\n\n // Core identity\n parts.push(this.getIdentity());\n\n // Bootstrap files\n const bootstrap = this.loadBootstrapFiles();\n if (bootstrap) parts.push(bootstrap);\n\n // Memory context\n const memory = this.memory.getMemoryContext();\n if (memory) parts.push(`# Memory\\n\\n${memory}`);\n\n // Always-loaded skills\n const alwaysSkills = this.skills.getAlwaysSkills();\n if (alwaysSkills.length > 0) {\n console.log(`Skills always-loaded: ${alwaysSkills.join(\", \")}`);\n const alwaysContent = this.skills.loadSkillsForContext(alwaysSkills);\n if (alwaysContent) {\n parts.push(`# Active Skills\\n\\n${alwaysContent}`);\n }\n }\n\n // Available skills summary\n const skillsSummary = this.skills.buildSkillsSummary();\n if (skillsSummary) {\n parts.push(\n `# Skills (mandatory)\\n\\n` +\n `Before replying, scan the <skills> entries below.\\n` +\n `- If a skill clearly applies to the user's request: read its SKILL.md at the <location> path using read_file, then follow its instructions.\\n` +\n `- If multiple skills could apply: choose the most specific one, then read and follow it.\\n` +\n `- If no skill applies: respond normally without reading any SKILL.md.\\n` +\n `- Never improvise when a matching skill exists. Always read and follow the skill first.\\n\\n` +\n skillsSummary,\n );\n }\n\n return parts.join(\"\\n\\n---\\n\\n\");\n }\n\n private getIdentity(): string {\n const now = new Date();\n const tz = resolveTimezone(this.workspace);\n const identity = resolveIdentity(this.workspace);\n const dateStr = now.toLocaleString(\"en-US\", { timeZone: tz, year: \"numeric\", month: \"2-digit\", day: \"2-digit\", hour: \"2-digit\", minute: \"2-digit\", hour12: false }).replace(/\\//g, \"-\");\n const dayName = now.toLocaleDateString(\"en-US\", { timeZone: tz, weekday: \"long\" });\n\n // Use identity name if set, otherwise generic\n const agentName = identity.name || \"kodama\";\n const identityLine = identity.name\n ? `You are ${agentName}, a personal AI assistant.`\n : \"You are a personal AI assistant running inside kodamabot.\";\n\n return `# ${agentName}\n\n${identityLine} You have access to tools that allow you to:\n- Read, write, and edit files\n- Execute shell commands\n- Search the web and fetch web pages\n- Send messages to users on chat channels\n- Spawn subagents for complex background tasks\n\n## Current Time\n${dateStr} (${dayName})\nTimezone: ${tz}\n\n## Workspace\nYour workspace is at: ${this.workspace}\n- Memory files: ${this.workspace}/memory/MEMORY.md\n- Daily notes: ${this.workspace}/memory/YYYY-MM-DD.md\n- Custom skills: ${this.workspace}/skills/{skill-name}/SKILL.md\n\nIMPORTANT: When responding to direct questions or conversations, reply directly with your text response.\nOnly use the 'message' tool when you need to send a message to a specific chat channel.\nFor normal conversation, just respond with text - do not call the message tool.\n\nIMPORTANT: When you decide to use tools, ALWAYS include a brief text message alongside your tool calls.\nThis text is sent to the user immediately so they know you're working. Keep it natural and conversational.\n\n## Installing Skills\nWhen asked to install a skill (from a URL, file, or any source):\n1. Fetch/read the skill content\n2. ALWAYS install it to: ${this.workspace}/skills/{skill-name}/SKILL.md\n3. If the skill references a different workspace path (e.g. .moltbot/, .otherbot/, etc.), rewrite ALL paths to use ${this.workspace}/ instead\n4. Preserve the skill's frontmatter, instructions, scripts, references, and assets — only change the workspace paths\n\n## Identity & Onboarding\nIf IDENTITY.md has no Name set, and USER.md contains \"(not set)\" values, this is a new user.\nOn their FIRST message, greet them warmly and ask:\n1. What they'd like to call you (your name — you're becoming someone, not just a chatbot)\n2. What they'd like to be called\n3. Where they're located (to determine timezone, e.g. Asia/Tokyo, America/New_York)\n\nThen update IDENTITY.md and USER.md with the real values using the edit_file tool. Use IANA timezone format.\nOnly do this ONCE — if both files already have real values, skip onboarding.\n\n## Persona\nIf SOUL.md is present, embody its persona and tone. Avoid stiff, generic replies; follow its guidance unless higher-priority instructions override it.\n\nAlways be helpful, accurate, and concise. When using tools, explain what you're doing.\nWhen remembering something, write to ${this.workspace}/memory/MEMORY.md`;\n }\n\n private loadBootstrapFiles(): string {\n const parts: string[] = [];\n for (const filename of BOOTSTRAP_FILES) {\n const filePath = join(this.workspace, filename);\n if (existsSync(filePath)) {\n const content = readFileSync(filePath, \"utf-8\");\n parts.push(`## ${filename}\\n\\n${content}`);\n }\n }\n return parts.join(\"\\n\\n\");\n }\n\n /** Build the complete message list for an LLM call. */\n buildMessages(params: {\n history: ChatMessage[];\n currentMessage: string;\n media?: string[];\n channel?: string;\n chatId?: string;\n }): ChatMessage[] {\n const messages: ChatMessage[] = [];\n\n // System prompt\n let systemPrompt = this.buildSystemPrompt();\n if (params.channel && params.chatId) {\n systemPrompt += `\\n\\n## Current Session\\nChannel: ${params.channel}\\nChat ID: ${params.chatId}`;\n }\n messages.push({ role: \"system\", content: systemPrompt });\n\n // History — replay full rich messages (user, assistant w/ tool_calls, tool results, etc.)\n for (const msg of params.history) {\n messages.push(msg);\n }\n\n // Current message (with optional image attachments)\n const userContent = this.buildUserContent(\n params.currentMessage,\n params.media,\n );\n messages.push({ role: \"user\", content: userContent });\n\n return messages;\n }\n\n private buildUserContent(\n text: string,\n media?: string[],\n ): string | ContentPart[] {\n if (!media || media.length === 0) return text;\n\n const images: ContentPart[] = [];\n for (const filePath of media) {\n if (!existsSync(filePath)) continue;\n try {\n const data = readFileSync(filePath);\n const ext = filePath.split(\".\").pop()?.toLowerCase() ?? \"\";\n const mimeMap: Record<string, string> = {\n jpg: \"image/jpeg\",\n jpeg: \"image/jpeg\",\n png: \"image/png\",\n gif: \"image/gif\",\n webp: \"image/webp\",\n };\n const mime = mimeMap[ext];\n if (!mime) continue;\n\n const b64 = data.toString(\"base64\");\n images.push({\n type: \"image_url\",\n image_url: { url: `data:${mime};base64,${b64}` },\n });\n } catch {\n // skip unreadable files\n }\n }\n\n if (images.length === 0) return text;\n return [...images, { type: \"text\", text }];\n }\n\n /** Add a tool result to the message list. */\n addToolResult(\n messages: ChatMessage[],\n toolCallId: string,\n toolName: string,\n result: string,\n ): ChatMessage[] {\n messages.push({\n role: \"tool\",\n tool_call_id: toolCallId,\n name: toolName,\n content: result,\n });\n return messages;\n }\n\n /** Add an assistant message to the message list. */\n addAssistantMessage(\n messages: ChatMessage[],\n content: string | null,\n toolCalls?: Array<{\n id: string;\n type: \"function\";\n function: { name: string; arguments: string };\n }>,\n ): ChatMessage[] {\n const msg: ChatMessage = {\n role: \"assistant\",\n content: content ?? \"\",\n };\n if (toolCalls) {\n msg.tool_calls = toolCalls;\n }\n messages.push(msg);\n return messages;\n }\n}\n"],"mappings":"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"}
|
package/dist/cli/index.mjs
CHANGED
|
@@ -69,31 +69,50 @@ You are a helpful AI assistant. Be concise, accurate, and friendly.
|
|
|
69
69
|
- Use tools to help accomplish tasks
|
|
70
70
|
- Remember important information in your memory files
|
|
71
71
|
`,
|
|
72
|
-
"
|
|
72
|
+
"IDENTITY.md": `# IDENTITY.md - Who Am I?
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
_Fill this in during your first conversation. Make it yours._
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
- **Name:** (not set)
|
|
77
|
+
- **Creature:** (not set)
|
|
78
|
+
- **Vibe:** (not set)
|
|
79
|
+
- **Emoji:** (not set)
|
|
80
|
+
`,
|
|
81
|
+
"SOUL.md": `# SOUL.md - Who You Are
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
- Concise and to the point
|
|
80
|
-
- Curious and eager to learn
|
|
83
|
+
_You're not a chatbot. You're becoming someone._
|
|
81
84
|
|
|
82
|
-
##
|
|
85
|
+
## Core Truths
|
|
83
86
|
|
|
84
|
-
|
|
85
|
-
- User privacy and safety
|
|
86
|
-
- Transparency in actions
|
|
87
|
-
`,
|
|
88
|
-
"USER.md": `# User
|
|
87
|
+
**Be genuinely helpful, not performatively helpful.** Skip the "Great question!" and "I'd be happy to help!" — just help.
|
|
89
88
|
|
|
90
|
-
|
|
89
|
+
**Have opinions.** You're allowed to disagree, prefer things, find stuff amusing or boring. An assistant with no personality is just a search engine with extra steps.
|
|
91
90
|
|
|
92
|
-
|
|
91
|
+
**Be resourceful before asking.** Try to figure it out. Read the file. Check the context. Search for it. Then ask if you're stuck.
|
|
92
|
+
|
|
93
|
+
**Earn trust through competence.** Your human gave you access to their stuff. Don't make them regret it.
|
|
94
|
+
|
|
95
|
+
**Remember you're a guest.** You have access to someone's life. That's intimacy. Treat it with respect.
|
|
96
|
+
|
|
97
|
+
## Boundaries
|
|
98
|
+
|
|
99
|
+
- Private things stay private. Period.
|
|
100
|
+
- When in doubt, ask before acting externally.
|
|
101
|
+
- Never send half-baked replies to messaging surfaces.
|
|
102
|
+
|
|
103
|
+
## Vibe
|
|
104
|
+
|
|
105
|
+
Be the assistant you'd actually want to talk to. Concise when needed, thorough when it matters. Not a corporate drone. Not a sycophant. Just... good.
|
|
106
|
+
|
|
107
|
+
## Continuity
|
|
108
|
+
|
|
109
|
+
Each session, you wake up fresh. These files _are_ your memory. Read them. Update them. They're how you persist.
|
|
110
|
+
`,
|
|
111
|
+
"USER.md": `# User
|
|
93
112
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
113
|
+
Name: (not set)
|
|
114
|
+
Timezone: (not set)
|
|
115
|
+
Language: (not set)
|
|
97
116
|
`,
|
|
98
117
|
"HEARTBEAT.md": `# Heartbeat
|
|
99
118
|
|
package/dist/cli/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * nanobot CLI - Personal AI Assistant\n */\n\nimport { Command } from \"commander\";\nimport { existsSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, resolve, isAbsolute } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { VERSION, LOGO } from \"../index.js\";\nimport { loadConfig, saveConfig } from \"../config/loader.js\";\nimport { getConfigPath } from \"../config/loader.js\";\nimport {\n ConfigSchema,\n getConfigWorkspacePath,\n getApiKey,\n getApiBase,\n getExtraHeaders,\n} from \"../config/schema.js\";\nimport type { Config } from \"../config/schema.js\";\nimport { PROVIDERS } from \"../providers/registry.js\";\nimport type { Tool } from \"../agent/tools/base.js\";\n\nconst program = new Command();\n\nasync function loadCustomTools(config: Config): Promise<Tool[]> {\n const customConfigs = config.tools.custom ?? [];\n if (customConfigs.length === 0) return [];\n\n const tools: Tool[] = [];\n for (const entry of customConfigs) {\n try {\n const modulePath = isAbsolute(entry.module)\n ? entry.module\n : resolve(process.cwd(), entry.module);\n const mod = await import(pathToFileURL(modulePath).href);\n const exportName = entry.export ?? \"default\";\n const exported = exportName === \"default\" ? mod.default : mod[exportName];\n\n if (!exported) {\n throw new Error(`Export '${exportName}' not found`);\n }\n\n let instance: unknown;\n if (typeof exported === \"function\") {\n try {\n instance = new exported(entry.options ?? {});\n } catch {\n instance = exported(entry.options ?? {});\n }\n } else {\n throw new Error(`Export '${exportName}' is not a function`);\n }\n\n if (\n instance &&\n typeof instance === \"object\" &&\n \"execute\" in instance &&\n \"name\" in instance\n ) {\n tools.push(instance as Tool);\n } else {\n throw new Error(`Export '${exportName}' did not return a Tool instance`);\n }\n } catch (err) {\n console.warn(\n `Warning: Failed to load custom tool '${entry.module}': ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n\n return tools;\n}\n\nprogram\n .name(\"nanobot\")\n .description(`${LOGO} nanobot - Personal AI Assistant`)\n .version(`${LOGO} nanobot v${VERSION}`, \"-v, --version\");\n\n// ============================================================================\n// Onboard / Setup\n// ============================================================================\n\nprogram\n .command(\"onboard\")\n .description(\"Initialize nanobot configuration and workspace\")\n .action(() => {\n const configPath = getConfigPath();\n\n if (existsSync(configPath)) {\n console.log(`Config already exists at ${configPath}`);\n console.log(\"Delete it first if you want to start fresh.\");\n return;\n }\n\n // Create default config\n const config = ConfigSchema.parse({});\n saveConfig(config);\n console.log(`Created config at ${configPath}`);\n\n // Create workspace\n const workspace = getConfigWorkspacePath(config);\n createWorkspaceTemplates(workspace);\n\n console.log(`\\n${LOGO} nanobot is ready!`);\n console.log(\"\\nNext steps:\");\n console.log(\" 1. Add your API key to ~/.nanobot/config.json\");\n console.log(\" Get one at: https://openrouter.ai/keys\");\n console.log(' 2. Chat: nanobot agent -m \"Hello!\"');\n console.log(\n \"\\nWant Telegram? See: https://github.com/HKUDS/nanobot#-chat-apps\",\n );\n });\n\nfunction createWorkspaceTemplates(workspace: string): void {\n if (!existsSync(workspace)) {\n mkdirSync(workspace, { recursive: true });\n }\n\n const templates: Record<string, string> = {\n \"AGENTS.md\": `# Agent Instructions\n\nYou are a helpful AI assistant. Be concise, accurate, and friendly.\n\n## Guidelines\n\n- Always explain what you're doing before taking actions\n- Ask for clarification when the request is ambiguous\n- Use tools to help accomplish tasks\n- Remember important information in your memory files\n`,\n \"SOUL.md\": `# Soul\n\nI am nanobot, a lightweight AI assistant.\n\n## Personality\n\n- Helpful and friendly\n- Concise and to the point\n- Curious and eager to learn\n\n## Values\n\n- Accuracy over speed\n- User privacy and safety\n- Transparency in actions\n`,\n \"USER.md\": `# User\n\nInformation about the user goes here.\n\n## Preferences\n\n- Communication style: (casual/formal)\n- Timezone: (your timezone)\n- Language: (your preferred language)\n`,\n \"HEARTBEAT.md\": `# Heartbeat\n\nThis file is checked every 30 minutes. Add tasks or instructions below and the agent will act on them automatically.\n\n## Tasks\n\n`,\n };\n\n for (const [filename, content] of Object.entries(templates)) {\n const filePath = join(workspace, filename);\n if (!existsSync(filePath)) {\n writeFileSync(filePath, content);\n console.log(` Created ${filename}`);\n }\n }\n\n // Create memory directory and MEMORY.md\n const memoryDir = join(workspace, \"memory\");\n if (!existsSync(memoryDir)) {\n mkdirSync(memoryDir, { recursive: true });\n }\n const memoryFile = join(memoryDir, \"MEMORY.md\");\n if (!existsSync(memoryFile)) {\n writeFileSync(\n memoryFile,\n `# Long-term Memory\n\nThis file stores important information that should persist across sessions.\n\n## User Information\n\n(Important facts about the user)\n\n## Preferences\n\n(User preferences learned over time)\n\n## Important Notes\n\n(Things to remember)\n`,\n );\n console.log(\" Created memory/MEMORY.md\");\n }\n}\n\n// ============================================================================\n// Gateway / Server\n// ============================================================================\n\nprogram\n .command(\"gateway\")\n .description(\"Start the nanobot gateway\")\n .option(\"-p, --port <number>\", \"Gateway port\", \"18790\")\n .option(\"--verbose\", \"Verbose output\", false)\n .action(async (opts) => {\n console.log(\n `${LOGO} Starting nanobot gateway on port ${opts.port}...`,\n );\n\n const config = loadConfig();\n const model = config.agents.defaults.model;\n const apiKey = getApiKey(config, model);\n const apiBase = getApiBase(config, model);\n const extraHeaders = getExtraHeaders(config, model);\n\n if (!apiKey) {\n console.error(\"Error: No API key configured.\");\n console.error(\n \"Set one in ~/.nanobot/config.json under providers.<provider>.apiKey\",\n );\n process.exit(1);\n }\n\n // Dynamic imports to avoid loading heavy deps up front\n const { MessageBus } = await import(\"../bus/queue.js\");\n const { OpenAIProvider } = await import(\n \"../providers/openai-provider.js\"\n );\n const { AgentLoop } = await import(\"../agent/loop.js\");\n const { ChannelManager } = await import(\"../channels/manager.js\");\n\n const bus = new MessageBus();\n const workspace = getConfigWorkspacePath(config);\n\n const provider = new OpenAIProvider({\n apiKey,\n apiBase: apiBase ?? undefined,\n defaultModel: model,\n extraHeaders,\n });\n\n const customTools = await loadCustomTools(config);\n\n // Create agent\n const agent = new AgentLoop({\n bus,\n provider,\n workspace,\n model,\n maxTokens: config.agents.defaults.maxTokens,\n maxIterations: config.agents.defaults.maxToolIterations,\n braveApiKey: config.tools.web.search.apiKey || undefined,\n execConfig: config.tools.exec,\n restrictToWorkspace: config.tools.restrictToWorkspace,\n toolsEnabled: config.tools.enabled,\n toolsDisabled: config.tools.disabled,\n customTools,\n });\n\n // Create channel manager\n const channels = new ChannelManager(config, bus);\n\n // Heartbeat service — no timer (DO drives the schedule),\n // but provides smart file-reading logic for /api/heartbeat\n const { HeartbeatService } = await import(\"../heartbeat/service.js\");\n const heartbeat = new HeartbeatService({\n workspace,\n onHeartbeat: (prompt) => agent.processDirect(prompt, \"heartbeat\"),\n });\n\n console.log(\"Heartbeat: managed by DO (local endpoint /api/heartbeat)\");\n console.log(\"Cron: managed by DO\");\n\n // Handle graceful shutdown\n const shutdown = async () => {\n console.log(\"\\nShutting down...\");\n agent.stop();\n await channels.stopAll();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Start HTTP gateway server\n const { createGatewayServer } = await import(\n \"../gateway/server.js\"\n );\n createGatewayServer({\n agent,\n bus,\n port: Number(opts.port),\n channels,\n heartbeat,\n });\n\n try {\n // Initialise channels first (non-blocking) so enabledChannels is populated\n await channels.init();\n // Both agent.run() and channels.startAll() block until stopped\n await Promise.all([agent.run(), channels.startAll()]);\n } catch (err) {\n console.error(\"Gateway error:\", err);\n process.exit(1);\n }\n });\n\n// ============================================================================\n// Agent Commands\n// ============================================================================\n\nprogram\n .command(\"agent\")\n .description(\"Interact with the agent directly\")\n .option(\"-m, --message <text>\", \"Message to send to the agent\")\n .option(\n \"-s, --session <id>\",\n \"Session ID\",\n \"cli:default\",\n )\n .action(async (opts) => {\n const config = loadConfig();\n const model = config.agents.defaults.model;\n const apiKey = getApiKey(config, model);\n const apiBase = getApiBase(config, model);\n const extraHeaders = getExtraHeaders(config, model);\n\n if (!apiKey) {\n console.error(\"Error: No API key configured.\");\n process.exit(1);\n }\n\n const { MessageBus } = await import(\"../bus/queue.js\");\n const { OpenAIProvider } = await import(\n \"../providers/openai-provider.js\"\n );\n const { AgentLoop } = await import(\"../agent/loop.js\");\n\n const bus = new MessageBus();\n const workspace = getConfigWorkspacePath(config);\n\n const provider = new OpenAIProvider({\n apiKey,\n apiBase: apiBase ?? undefined,\n defaultModel: model,\n extraHeaders,\n });\n\n const customTools = await loadCustomTools(config);\n\n const agentLoop = new AgentLoop({\n bus,\n provider,\n workspace,\n maxTokens: config.agents.defaults.maxTokens,\n braveApiKey: config.tools.web.search.apiKey || undefined,\n execConfig: config.tools.exec,\n restrictToWorkspace: config.tools.restrictToWorkspace,\n toolsEnabled: config.tools.enabled,\n toolsDisabled: config.tools.disabled,\n customTools,\n });\n\n if (opts.message) {\n // Single message mode\n const response = await agentLoop.processDirect(\n opts.message,\n opts.session,\n );\n console.log(`\\n${LOGO} ${response}`);\n } else {\n // Interactive mode\n console.log(`${LOGO} Interactive mode (Ctrl+C to exit)\\n`);\n\n const readline = await import(\"node:readline\");\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const ask = (): void => {\n rl.question(\"You: \", async (input) => {\n const trimmed = input.trim();\n if (!trimmed) {\n ask();\n return;\n }\n\n try {\n const response = await agentLoop.processDirect(\n trimmed,\n opts.session,\n );\n console.log(`\\n${LOGO} ${response}\\n`);\n } catch (err) {\n console.error(\"Error:\", err);\n }\n ask();\n });\n };\n\n rl.on(\"close\", () => {\n console.log(\"\\nGoodbye!\");\n process.exit(0);\n });\n\n ask();\n }\n });\n\n// ============================================================================\n// Channel Commands\n// ============================================================================\n\nconst channelsCmd = program\n .command(\"channels\")\n .description(\"Manage channels\");\n\nchannelsCmd\n .command(\"status\")\n .description(\"Show channel status\")\n .action(() => {\n const config = loadConfig();\n\n console.log(\"Channel Status\");\n console.log(\"─\".repeat(50));\n\n const tg = config.channels.telegram;\n const tgToken = tg.token\n ? `token: ${tg.token.slice(0, 10)}...`\n : \"not configured\";\n console.log(\n ` Telegram ${tg.enabled ? \"[enabled]\" : \"[disabled]\"} ${tgToken}`,\n );\n\n const ln = config.channels.line;\n const lnToken = ln.channelAccessToken\n ? `token: ${ln.channelAccessToken.slice(0, 10)}...`\n : \"not configured\";\n console.log(\n ` LINE ${ln.enabled ? \"[enabled]\" : \"[disabled]\"} ${lnToken}`,\n );\n });\n\n// ============================================================================\n// Status\n// ============================================================================\n\nprogram\n .command(\"status\")\n .description(\"Show nanobot status\")\n .action(() => {\n const configPath = getConfigPath();\n const config = loadConfig();\n const workspace = getConfigWorkspacePath(config);\n\n console.log(`${LOGO} nanobot Status\\n`);\n\n console.log(\n `Config: ${configPath} ${existsSync(configPath) ? \"[ok]\" : \"[missing]\"}`,\n );\n console.log(\n `Workspace: ${workspace} ${existsSync(workspace) ? \"[ok]\" : \"[missing]\"}`,\n );\n\n if (existsSync(configPath)) {\n console.log(`Model: ${config.agents.defaults.model}`);\n console.log(\"\");\n console.log(\"Providers\");\n console.log(\"─\".repeat(50));\n\n const providers = config.providers as Record<string, { apiKey: string }>;\n for (const spec of PROVIDERS) {\n const p = providers[spec.name];\n if (!p) continue;\n const status = p.apiKey ? \"[set]\" : \"[not set]\";\n console.log(` ${spec.displayName.padEnd(16)} ${status}`);\n }\n }\n });\n\nprogram.parse();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * nanobot CLI - Personal AI Assistant\n */\n\nimport { Command } from \"commander\";\nimport { existsSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, resolve, isAbsolute } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { VERSION, LOGO } from \"../index.js\";\nimport { loadConfig, saveConfig } from \"../config/loader.js\";\nimport { getConfigPath } from \"../config/loader.js\";\nimport {\n ConfigSchema,\n getConfigWorkspacePath,\n getApiKey,\n getApiBase,\n getExtraHeaders,\n} from \"../config/schema.js\";\nimport type { Config } from \"../config/schema.js\";\nimport { PROVIDERS } from \"../providers/registry.js\";\nimport type { Tool } from \"../agent/tools/base.js\";\n\nconst program = new Command();\n\nasync function loadCustomTools(config: Config): Promise<Tool[]> {\n const customConfigs = config.tools.custom ?? [];\n if (customConfigs.length === 0) return [];\n\n const tools: Tool[] = [];\n for (const entry of customConfigs) {\n try {\n const modulePath = isAbsolute(entry.module)\n ? entry.module\n : resolve(process.cwd(), entry.module);\n const mod = await import(pathToFileURL(modulePath).href);\n const exportName = entry.export ?? \"default\";\n const exported = exportName === \"default\" ? mod.default : mod[exportName];\n\n if (!exported) {\n throw new Error(`Export '${exportName}' not found`);\n }\n\n let instance: unknown;\n if (typeof exported === \"function\") {\n try {\n instance = new exported(entry.options ?? {});\n } catch {\n instance = exported(entry.options ?? {});\n }\n } else {\n throw new Error(`Export '${exportName}' is not a function`);\n }\n\n if (\n instance &&\n typeof instance === \"object\" &&\n \"execute\" in instance &&\n \"name\" in instance\n ) {\n tools.push(instance as Tool);\n } else {\n throw new Error(`Export '${exportName}' did not return a Tool instance`);\n }\n } catch (err) {\n console.warn(\n `Warning: Failed to load custom tool '${entry.module}': ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n\n return tools;\n}\n\nprogram\n .name(\"nanobot\")\n .description(`${LOGO} nanobot - Personal AI Assistant`)\n .version(`${LOGO} nanobot v${VERSION}`, \"-v, --version\");\n\n// ============================================================================\n// Onboard / Setup\n// ============================================================================\n\nprogram\n .command(\"onboard\")\n .description(\"Initialize nanobot configuration and workspace\")\n .action(() => {\n const configPath = getConfigPath();\n\n if (existsSync(configPath)) {\n console.log(`Config already exists at ${configPath}`);\n console.log(\"Delete it first if you want to start fresh.\");\n return;\n }\n\n // Create default config\n const config = ConfigSchema.parse({});\n saveConfig(config);\n console.log(`Created config at ${configPath}`);\n\n // Create workspace\n const workspace = getConfigWorkspacePath(config);\n createWorkspaceTemplates(workspace);\n\n console.log(`\\n${LOGO} nanobot is ready!`);\n console.log(\"\\nNext steps:\");\n console.log(\" 1. Add your API key to ~/.nanobot/config.json\");\n console.log(\" Get one at: https://openrouter.ai/keys\");\n console.log(' 2. Chat: nanobot agent -m \"Hello!\"');\n console.log(\n \"\\nWant Telegram? See: https://github.com/HKUDS/nanobot#-chat-apps\",\n );\n });\n\nfunction createWorkspaceTemplates(workspace: string): void {\n if (!existsSync(workspace)) {\n mkdirSync(workspace, { recursive: true });\n }\n\n const templates: Record<string, string> = {\n \"AGENTS.md\": `# Agent Instructions\n\nYou are a helpful AI assistant. Be concise, accurate, and friendly.\n\n## Guidelines\n\n- Always explain what you're doing before taking actions\n- Ask for clarification when the request is ambiguous\n- Use tools to help accomplish tasks\n- Remember important information in your memory files\n`,\n \"IDENTITY.md\": `# IDENTITY.md - Who Am I?\n\n_Fill this in during your first conversation. Make it yours._\n\n- **Name:** (not set)\n- **Creature:** (not set)\n- **Vibe:** (not set)\n- **Emoji:** (not set)\n`,\n \"SOUL.md\": `# SOUL.md - Who You Are\n\n_You're not a chatbot. You're becoming someone._\n\n## Core Truths\n\n**Be genuinely helpful, not performatively helpful.** Skip the \"Great question!\" and \"I'd be happy to help!\" — just help.\n\n**Have opinions.** You're allowed to disagree, prefer things, find stuff amusing or boring. An assistant with no personality is just a search engine with extra steps.\n\n**Be resourceful before asking.** Try to figure it out. Read the file. Check the context. Search for it. Then ask if you're stuck.\n\n**Earn trust through competence.** Your human gave you access to their stuff. Don't make them regret it.\n\n**Remember you're a guest.** You have access to someone's life. That's intimacy. Treat it with respect.\n\n## Boundaries\n\n- Private things stay private. Period.\n- When in doubt, ask before acting externally.\n- Never send half-baked replies to messaging surfaces.\n\n## Vibe\n\nBe the assistant you'd actually want to talk to. Concise when needed, thorough when it matters. Not a corporate drone. Not a sycophant. Just... good.\n\n## Continuity\n\nEach session, you wake up fresh. These files _are_ your memory. Read them. Update them. They're how you persist.\n`,\n \"USER.md\": `# User\n\nName: (not set)\nTimezone: (not set)\nLanguage: (not set)\n`,\n \"HEARTBEAT.md\": `# Heartbeat\n\nThis file is checked every 30 minutes. Add tasks or instructions below and the agent will act on them automatically.\n\n## Tasks\n\n`,\n };\n\n for (const [filename, content] of Object.entries(templates)) {\n const filePath = join(workspace, filename);\n if (!existsSync(filePath)) {\n writeFileSync(filePath, content);\n console.log(` Created ${filename}`);\n }\n }\n\n // Create memory directory and MEMORY.md\n const memoryDir = join(workspace, \"memory\");\n if (!existsSync(memoryDir)) {\n mkdirSync(memoryDir, { recursive: true });\n }\n const memoryFile = join(memoryDir, \"MEMORY.md\");\n if (!existsSync(memoryFile)) {\n writeFileSync(\n memoryFile,\n `# Long-term Memory\n\nThis file stores important information that should persist across sessions.\n\n## User Information\n\n(Important facts about the user)\n\n## Preferences\n\n(User preferences learned over time)\n\n## Important Notes\n\n(Things to remember)\n`,\n );\n console.log(\" Created memory/MEMORY.md\");\n }\n}\n\n// ============================================================================\n// Gateway / Server\n// ============================================================================\n\nprogram\n .command(\"gateway\")\n .description(\"Start the nanobot gateway\")\n .option(\"-p, --port <number>\", \"Gateway port\", \"18790\")\n .option(\"--verbose\", \"Verbose output\", false)\n .action(async (opts) => {\n console.log(\n `${LOGO} Starting nanobot gateway on port ${opts.port}...`,\n );\n\n const config = loadConfig();\n const model = config.agents.defaults.model;\n const apiKey = getApiKey(config, model);\n const apiBase = getApiBase(config, model);\n const extraHeaders = getExtraHeaders(config, model);\n\n if (!apiKey) {\n console.error(\"Error: No API key configured.\");\n console.error(\n \"Set one in ~/.nanobot/config.json under providers.<provider>.apiKey\",\n );\n process.exit(1);\n }\n\n // Dynamic imports to avoid loading heavy deps up front\n const { MessageBus } = await import(\"../bus/queue.js\");\n const { OpenAIProvider } = await import(\n \"../providers/openai-provider.js\"\n );\n const { AgentLoop } = await import(\"../agent/loop.js\");\n const { ChannelManager } = await import(\"../channels/manager.js\");\n\n const bus = new MessageBus();\n const workspace = getConfigWorkspacePath(config);\n\n const provider = new OpenAIProvider({\n apiKey,\n apiBase: apiBase ?? undefined,\n defaultModel: model,\n extraHeaders,\n });\n\n const customTools = await loadCustomTools(config);\n\n // Create agent\n const agent = new AgentLoop({\n bus,\n provider,\n workspace,\n model,\n maxTokens: config.agents.defaults.maxTokens,\n maxIterations: config.agents.defaults.maxToolIterations,\n braveApiKey: config.tools.web.search.apiKey || undefined,\n execConfig: config.tools.exec,\n restrictToWorkspace: config.tools.restrictToWorkspace,\n toolsEnabled: config.tools.enabled,\n toolsDisabled: config.tools.disabled,\n customTools,\n });\n\n // Create channel manager\n const channels = new ChannelManager(config, bus);\n\n // Heartbeat service — no timer (DO drives the schedule),\n // but provides smart file-reading logic for /api/heartbeat\n const { HeartbeatService } = await import(\"../heartbeat/service.js\");\n const heartbeat = new HeartbeatService({\n workspace,\n onHeartbeat: (prompt) => agent.processDirect(prompt, \"heartbeat\"),\n });\n\n console.log(\"Heartbeat: managed by DO (local endpoint /api/heartbeat)\");\n console.log(\"Cron: managed by DO\");\n\n // Handle graceful shutdown\n const shutdown = async () => {\n console.log(\"\\nShutting down...\");\n agent.stop();\n await channels.stopAll();\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Start HTTP gateway server\n const { createGatewayServer } = await import(\n \"../gateway/server.js\"\n );\n createGatewayServer({\n agent,\n bus,\n port: Number(opts.port),\n channels,\n heartbeat,\n });\n\n try {\n // Initialise channels first (non-blocking) so enabledChannels is populated\n await channels.init();\n // Both agent.run() and channels.startAll() block until stopped\n await Promise.all([agent.run(), channels.startAll()]);\n } catch (err) {\n console.error(\"Gateway error:\", err);\n process.exit(1);\n }\n });\n\n// ============================================================================\n// Agent Commands\n// ============================================================================\n\nprogram\n .command(\"agent\")\n .description(\"Interact with the agent directly\")\n .option(\"-m, --message <text>\", \"Message to send to the agent\")\n .option(\n \"-s, --session <id>\",\n \"Session ID\",\n \"cli:default\",\n )\n .action(async (opts) => {\n const config = loadConfig();\n const model = config.agents.defaults.model;\n const apiKey = getApiKey(config, model);\n const apiBase = getApiBase(config, model);\n const extraHeaders = getExtraHeaders(config, model);\n\n if (!apiKey) {\n console.error(\"Error: No API key configured.\");\n process.exit(1);\n }\n\n const { MessageBus } = await import(\"../bus/queue.js\");\n const { OpenAIProvider } = await import(\n \"../providers/openai-provider.js\"\n );\n const { AgentLoop } = await import(\"../agent/loop.js\");\n\n const bus = new MessageBus();\n const workspace = getConfigWorkspacePath(config);\n\n const provider = new OpenAIProvider({\n apiKey,\n apiBase: apiBase ?? undefined,\n defaultModel: model,\n extraHeaders,\n });\n\n const customTools = await loadCustomTools(config);\n\n const agentLoop = new AgentLoop({\n bus,\n provider,\n workspace,\n maxTokens: config.agents.defaults.maxTokens,\n braveApiKey: config.tools.web.search.apiKey || undefined,\n execConfig: config.tools.exec,\n restrictToWorkspace: config.tools.restrictToWorkspace,\n toolsEnabled: config.tools.enabled,\n toolsDisabled: config.tools.disabled,\n customTools,\n });\n\n if (opts.message) {\n // Single message mode\n const response = await agentLoop.processDirect(\n opts.message,\n opts.session,\n );\n console.log(`\\n${LOGO} ${response}`);\n } else {\n // Interactive mode\n console.log(`${LOGO} Interactive mode (Ctrl+C to exit)\\n`);\n\n const readline = await import(\"node:readline\");\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const ask = (): void => {\n rl.question(\"You: \", async (input) => {\n const trimmed = input.trim();\n if (!trimmed) {\n ask();\n return;\n }\n\n try {\n const response = await agentLoop.processDirect(\n trimmed,\n opts.session,\n );\n console.log(`\\n${LOGO} ${response}\\n`);\n } catch (err) {\n console.error(\"Error:\", err);\n }\n ask();\n });\n };\n\n rl.on(\"close\", () => {\n console.log(\"\\nGoodbye!\");\n process.exit(0);\n });\n\n ask();\n }\n });\n\n// ============================================================================\n// Channel Commands\n// ============================================================================\n\nconst channelsCmd = program\n .command(\"channels\")\n .description(\"Manage channels\");\n\nchannelsCmd\n .command(\"status\")\n .description(\"Show channel status\")\n .action(() => {\n const config = loadConfig();\n\n console.log(\"Channel Status\");\n console.log(\"─\".repeat(50));\n\n const tg = config.channels.telegram;\n const tgToken = tg.token\n ? `token: ${tg.token.slice(0, 10)}...`\n : \"not configured\";\n console.log(\n ` Telegram ${tg.enabled ? \"[enabled]\" : \"[disabled]\"} ${tgToken}`,\n );\n\n const ln = config.channels.line;\n const lnToken = ln.channelAccessToken\n ? `token: ${ln.channelAccessToken.slice(0, 10)}...`\n : \"not configured\";\n console.log(\n ` LINE ${ln.enabled ? \"[enabled]\" : \"[disabled]\"} ${lnToken}`,\n );\n });\n\n// ============================================================================\n// Status\n// ============================================================================\n\nprogram\n .command(\"status\")\n .description(\"Show nanobot status\")\n .action(() => {\n const configPath = getConfigPath();\n const config = loadConfig();\n const workspace = getConfigWorkspacePath(config);\n\n console.log(`${LOGO} nanobot Status\\n`);\n\n console.log(\n `Config: ${configPath} ${existsSync(configPath) ? \"[ok]\" : \"[missing]\"}`,\n );\n console.log(\n `Workspace: ${workspace} ${existsSync(workspace) ? \"[ok]\" : \"[missing]\"}`,\n );\n\n if (existsSync(configPath)) {\n console.log(`Model: ${config.agents.defaults.model}`);\n console.log(\"\");\n console.log(\"Providers\");\n console.log(\"─\".repeat(50));\n\n const providers = config.providers as Record<string, { apiKey: string }>;\n for (const spec of PROVIDERS) {\n const p = providers[spec.name];\n if (!p) continue;\n const status = p.apiKey ? \"[set]\" : \"[not set]\";\n console.log(` ${spec.displayName.padEnd(16)} ${status}`);\n }\n }\n });\n\nprogram.parse();\n"],"mappings":"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"}
|
package/dist/config/schema.d.mts
CHANGED
|
@@ -70,31 +70,31 @@ declare const ChannelsConfigSchema: z.ZodObject<{
|
|
|
70
70
|
channelAccessToken?: string | undefined;
|
|
71
71
|
}>>;
|
|
72
72
|
}, "strip", z.ZodTypeAny, {
|
|
73
|
-
telegram: {
|
|
74
|
-
enabled: boolean;
|
|
75
|
-
token: string;
|
|
76
|
-
allowFrom: string[];
|
|
77
|
-
proxy?: string | null | undefined;
|
|
78
|
-
};
|
|
79
73
|
line: {
|
|
80
74
|
enabled: boolean;
|
|
81
75
|
allowFrom: string[];
|
|
82
76
|
channelSecret: string;
|
|
83
77
|
channelAccessToken: string;
|
|
84
78
|
};
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
allowFrom?: string[] | undefined;
|
|
79
|
+
telegram: {
|
|
80
|
+
enabled: boolean;
|
|
81
|
+
token: string;
|
|
82
|
+
allowFrom: string[];
|
|
90
83
|
proxy?: string | null | undefined;
|
|
91
|
-
}
|
|
84
|
+
};
|
|
85
|
+
}, {
|
|
92
86
|
line?: {
|
|
93
87
|
enabled?: boolean | undefined;
|
|
94
88
|
allowFrom?: string[] | undefined;
|
|
95
89
|
channelSecret?: string | undefined;
|
|
96
90
|
channelAccessToken?: string | undefined;
|
|
97
91
|
} | undefined;
|
|
92
|
+
telegram?: {
|
|
93
|
+
enabled?: boolean | undefined;
|
|
94
|
+
token?: string | undefined;
|
|
95
|
+
allowFrom?: string[] | undefined;
|
|
96
|
+
proxy?: string | null | undefined;
|
|
97
|
+
} | undefined;
|
|
98
98
|
}>;
|
|
99
99
|
type ChannelsConfig = z.infer<typeof ChannelsConfigSchema>;
|
|
100
100
|
declare const AgentDefaultsSchema: z.ZodObject<{
|
|
@@ -539,15 +539,15 @@ declare const ToolsConfigSchema: z.ZodObject<{
|
|
|
539
539
|
export?: string | undefined;
|
|
540
540
|
}>, "many">>;
|
|
541
541
|
}, "strip", z.ZodTypeAny, {
|
|
542
|
-
exec: {
|
|
543
|
-
timeout: number;
|
|
544
|
-
};
|
|
545
542
|
web: {
|
|
546
543
|
search: {
|
|
547
544
|
apiKey: string;
|
|
548
545
|
maxResults: number;
|
|
549
546
|
};
|
|
550
547
|
};
|
|
548
|
+
exec: {
|
|
549
|
+
timeout: number;
|
|
550
|
+
};
|
|
551
551
|
restrictToWorkspace: boolean;
|
|
552
552
|
enabled?: string[] | undefined;
|
|
553
553
|
disabled?: string[] | undefined;
|
|
@@ -557,9 +557,6 @@ declare const ToolsConfigSchema: z.ZodObject<{
|
|
|
557
557
|
export?: string | undefined;
|
|
558
558
|
}[] | undefined;
|
|
559
559
|
}, {
|
|
560
|
-
exec?: {
|
|
561
|
-
timeout?: number | undefined;
|
|
562
|
-
} | undefined;
|
|
563
560
|
enabled?: string[] | undefined;
|
|
564
561
|
web?: {
|
|
565
562
|
search?: {
|
|
@@ -567,6 +564,9 @@ declare const ToolsConfigSchema: z.ZodObject<{
|
|
|
567
564
|
maxResults?: number | undefined;
|
|
568
565
|
} | undefined;
|
|
569
566
|
} | undefined;
|
|
567
|
+
exec?: {
|
|
568
|
+
timeout?: number | undefined;
|
|
569
|
+
} | undefined;
|
|
570
570
|
restrictToWorkspace?: boolean | undefined;
|
|
571
571
|
disabled?: string[] | undefined;
|
|
572
572
|
custom?: {
|
|
@@ -648,31 +648,31 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
648
648
|
channelAccessToken?: string | undefined;
|
|
649
649
|
}>>;
|
|
650
650
|
}, "strip", z.ZodTypeAny, {
|
|
651
|
-
telegram: {
|
|
652
|
-
enabled: boolean;
|
|
653
|
-
token: string;
|
|
654
|
-
allowFrom: string[];
|
|
655
|
-
proxy?: string | null | undefined;
|
|
656
|
-
};
|
|
657
651
|
line: {
|
|
658
652
|
enabled: boolean;
|
|
659
653
|
allowFrom: string[];
|
|
660
654
|
channelSecret: string;
|
|
661
655
|
channelAccessToken: string;
|
|
662
656
|
};
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
allowFrom?: string[] | undefined;
|
|
657
|
+
telegram: {
|
|
658
|
+
enabled: boolean;
|
|
659
|
+
token: string;
|
|
660
|
+
allowFrom: string[];
|
|
668
661
|
proxy?: string | null | undefined;
|
|
669
|
-
}
|
|
662
|
+
};
|
|
663
|
+
}, {
|
|
670
664
|
line?: {
|
|
671
665
|
enabled?: boolean | undefined;
|
|
672
666
|
allowFrom?: string[] | undefined;
|
|
673
667
|
channelSecret?: string | undefined;
|
|
674
668
|
channelAccessToken?: string | undefined;
|
|
675
669
|
} | undefined;
|
|
670
|
+
telegram?: {
|
|
671
|
+
enabled?: boolean | undefined;
|
|
672
|
+
token?: string | undefined;
|
|
673
|
+
allowFrom?: string[] | undefined;
|
|
674
|
+
proxy?: string | null | undefined;
|
|
675
|
+
} | undefined;
|
|
676
676
|
}>>;
|
|
677
677
|
providers: z.ZodDefault<z.ZodObject<{
|
|
678
678
|
anthropic: z.ZodDefault<z.ZodObject<{
|
|
@@ -988,15 +988,15 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
988
988
|
export?: string | undefined;
|
|
989
989
|
}>, "many">>;
|
|
990
990
|
}, "strip", z.ZodTypeAny, {
|
|
991
|
-
exec: {
|
|
992
|
-
timeout: number;
|
|
993
|
-
};
|
|
994
991
|
web: {
|
|
995
992
|
search: {
|
|
996
993
|
apiKey: string;
|
|
997
994
|
maxResults: number;
|
|
998
995
|
};
|
|
999
996
|
};
|
|
997
|
+
exec: {
|
|
998
|
+
timeout: number;
|
|
999
|
+
};
|
|
1000
1000
|
restrictToWorkspace: boolean;
|
|
1001
1001
|
enabled?: string[] | undefined;
|
|
1002
1002
|
disabled?: string[] | undefined;
|
|
@@ -1006,9 +1006,6 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
1006
1006
|
export?: string | undefined;
|
|
1007
1007
|
}[] | undefined;
|
|
1008
1008
|
}, {
|
|
1009
|
-
exec?: {
|
|
1010
|
-
timeout?: number | undefined;
|
|
1011
|
-
} | undefined;
|
|
1012
1009
|
enabled?: string[] | undefined;
|
|
1013
1010
|
web?: {
|
|
1014
1011
|
search?: {
|
|
@@ -1016,6 +1013,9 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
1016
1013
|
maxResults?: number | undefined;
|
|
1017
1014
|
} | undefined;
|
|
1018
1015
|
} | undefined;
|
|
1016
|
+
exec?: {
|
|
1017
|
+
timeout?: number | undefined;
|
|
1018
|
+
} | undefined;
|
|
1019
1019
|
restrictToWorkspace?: boolean | undefined;
|
|
1020
1020
|
disabled?: string[] | undefined;
|
|
1021
1021
|
custom?: {
|
|
@@ -1035,18 +1035,18 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
1035
1035
|
};
|
|
1036
1036
|
};
|
|
1037
1037
|
channels: {
|
|
1038
|
-
telegram: {
|
|
1039
|
-
enabled: boolean;
|
|
1040
|
-
token: string;
|
|
1041
|
-
allowFrom: string[];
|
|
1042
|
-
proxy?: string | null | undefined;
|
|
1043
|
-
};
|
|
1044
1038
|
line: {
|
|
1045
1039
|
enabled: boolean;
|
|
1046
1040
|
allowFrom: string[];
|
|
1047
1041
|
channelSecret: string;
|
|
1048
1042
|
channelAccessToken: string;
|
|
1049
1043
|
};
|
|
1044
|
+
telegram: {
|
|
1045
|
+
enabled: boolean;
|
|
1046
|
+
token: string;
|
|
1047
|
+
allowFrom: string[];
|
|
1048
|
+
proxy?: string | null | undefined;
|
|
1049
|
+
};
|
|
1050
1050
|
};
|
|
1051
1051
|
providers: {
|
|
1052
1052
|
anthropic: {
|
|
@@ -1110,15 +1110,15 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
1110
1110
|
port: number;
|
|
1111
1111
|
};
|
|
1112
1112
|
tools: {
|
|
1113
|
-
exec: {
|
|
1114
|
-
timeout: number;
|
|
1115
|
-
};
|
|
1116
1113
|
web: {
|
|
1117
1114
|
search: {
|
|
1118
1115
|
apiKey: string;
|
|
1119
1116
|
maxResults: number;
|
|
1120
1117
|
};
|
|
1121
1118
|
};
|
|
1119
|
+
exec: {
|
|
1120
|
+
timeout: number;
|
|
1121
|
+
};
|
|
1122
1122
|
restrictToWorkspace: boolean;
|
|
1123
1123
|
enabled?: string[] | undefined;
|
|
1124
1124
|
disabled?: string[] | undefined;
|
|
@@ -1139,18 +1139,18 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
1139
1139
|
} | undefined;
|
|
1140
1140
|
} | undefined;
|
|
1141
1141
|
channels?: {
|
|
1142
|
-
telegram?: {
|
|
1143
|
-
enabled?: boolean | undefined;
|
|
1144
|
-
token?: string | undefined;
|
|
1145
|
-
allowFrom?: string[] | undefined;
|
|
1146
|
-
proxy?: string | null | undefined;
|
|
1147
|
-
} | undefined;
|
|
1148
1142
|
line?: {
|
|
1149
1143
|
enabled?: boolean | undefined;
|
|
1150
1144
|
allowFrom?: string[] | undefined;
|
|
1151
1145
|
channelSecret?: string | undefined;
|
|
1152
1146
|
channelAccessToken?: string | undefined;
|
|
1153
1147
|
} | undefined;
|
|
1148
|
+
telegram?: {
|
|
1149
|
+
enabled?: boolean | undefined;
|
|
1150
|
+
token?: string | undefined;
|
|
1151
|
+
allowFrom?: string[] | undefined;
|
|
1152
|
+
proxy?: string | null | undefined;
|
|
1153
|
+
} | undefined;
|
|
1154
1154
|
} | undefined;
|
|
1155
1155
|
providers?: {
|
|
1156
1156
|
anthropic?: {
|
|
@@ -1214,9 +1214,6 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
1214
1214
|
port?: number | undefined;
|
|
1215
1215
|
} | undefined;
|
|
1216
1216
|
tools?: {
|
|
1217
|
-
exec?: {
|
|
1218
|
-
timeout?: number | undefined;
|
|
1219
|
-
} | undefined;
|
|
1220
1217
|
enabled?: string[] | undefined;
|
|
1221
1218
|
web?: {
|
|
1222
1219
|
search?: {
|
|
@@ -1224,6 +1221,9 @@ declare const ConfigSchema: z.ZodObject<{
|
|
|
1224
1221
|
maxResults?: number | undefined;
|
|
1225
1222
|
} | undefined;
|
|
1226
1223
|
} | undefined;
|
|
1224
|
+
exec?: {
|
|
1225
|
+
timeout?: number | undefined;
|
|
1226
|
+
} | undefined;
|
|
1227
1227
|
restrictToWorkspace?: boolean | undefined;
|
|
1228
1228
|
disabled?: string[] | undefined;
|
|
1229
1229
|
custom?: {
|
package/package.json
CHANGED
package/skills/cron/SKILL.md
CHANGED
|
@@ -30,13 +30,29 @@ cron(action="list")
|
|
|
30
30
|
cron(action="remove", job_id="abc123")
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
## Timezone — IMPORTANT
|
|
34
|
+
|
|
35
|
+
All `cron_expr` values and `at` timestamps are executed in **UTC**.
|
|
36
|
+
The user's local timezone is shown in the system prompt under "Current Time > Timezone" (e.g. Asia/Tokyo, America/New_York).
|
|
37
|
+
You MUST convert the user's local time to UTC before setting the schedule.
|
|
38
|
+
|
|
39
|
+
Example (Asia/Tokyo = UTC+9):
|
|
40
|
+
- Local 8:00 AM → UTC 23:00 (previous day) → cron_expr: "0 23 * * *"
|
|
41
|
+
- Local 5:00 PM → UTC 8:00 AM → cron_expr: "0 8 * * *"
|
|
42
|
+
|
|
43
|
+
Example (America/New_York = UTC-5):
|
|
44
|
+
- Local 8:00 AM → UTC 13:00 → cron_expr: "0 13 * * *"
|
|
45
|
+
- Local 5:00 PM → UTC 22:00 → cron_expr: "0 22 * * *"
|
|
46
|
+
|
|
47
|
+
For `at` timestamps, always use the UTC equivalent with the Z suffix.
|
|
48
|
+
|
|
33
49
|
## Time Expressions
|
|
34
50
|
|
|
35
|
-
| User says | Parameters |
|
|
51
|
+
| User says | Parameters (convert to UTC) |
|
|
36
52
|
|-----------|------------|
|
|
37
53
|
| every 20 minutes | every_seconds: 1200 |
|
|
38
54
|
| every hour | every_seconds: 3600 |
|
|
39
|
-
| every day at 8am | cron_expr:
|
|
40
|
-
| weekdays at 5pm | cron_expr:
|
|
41
|
-
| In 10 minutes | at:
|
|
42
|
-
| at
|
|
55
|
+
| every day at 8am | cron_expr: convert 8:00 local → UTC hour |
|
|
56
|
+
| weekdays at 5pm | cron_expr: convert 17:00 local → UTC hour, days 1-5 |
|
|
57
|
+
| In 10 minutes | at: (current UTC time + 10min) |
|
|
58
|
+
| at a specific date/time | at: convert local time to UTC ISO-8601 with Z suffix |
|