fotric-claw 0.1.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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +276 -0
  3. package/backend/.env.example +26 -0
  4. package/backend/nest-cli.json +8 -0
  5. package/backend/package-lock.json +13239 -0
  6. package/backend/package.json +82 -0
  7. package/backend/src/agent/agent.module.ts +10 -0
  8. package/backend/src/agent/agent.service.ts +210 -0
  9. package/backend/src/agent/index.ts +4 -0
  10. package/backend/src/agent/llm.factory.ts +20 -0
  11. package/backend/src/agent/tools/fetch.tool.ts +128 -0
  12. package/backend/src/agent/tools/file-read.tool.ts +99 -0
  13. package/backend/src/agent/tools/index.ts +55 -0
  14. package/backend/src/agent/tools/node-repl.tool.ts +82 -0
  15. package/backend/src/agent/tools/rag.tool.ts +192 -0
  16. package/backend/src/agent/tools/shell.tool.ts +65 -0
  17. package/backend/src/app.module.ts +26 -0
  18. package/backend/src/chat/chat.controller.ts +34 -0
  19. package/backend/src/chat/chat.module.ts +12 -0
  20. package/backend/src/chat/chat.service.ts +52 -0
  21. package/backend/src/chat/dto/chat.dto.ts +12 -0
  22. package/backend/src/chat/dto/index.ts +1 -0
  23. package/backend/src/chat/index.ts +4 -0
  24. package/backend/src/config/config.controller.ts +92 -0
  25. package/backend/src/config/config.module.ts +7 -0
  26. package/backend/src/config/constants.ts +56 -0
  27. package/backend/src/config/index.ts +3 -0
  28. package/backend/src/files/files.controller.ts +87 -0
  29. package/backend/src/files/files.module.ts +7 -0
  30. package/backend/src/files/index.ts +2 -0
  31. package/backend/src/main.ts +21 -0
  32. package/backend/src/memory/index.ts +3 -0
  33. package/backend/src/memory/memory.module.ts +10 -0
  34. package/backend/src/memory/memory.service.ts +329 -0
  35. package/backend/src/memory/memory.types.ts +38 -0
  36. package/backend/src/sessions/default.json +7 -0
  37. package/backend/src/sessions/index.ts +2 -0
  38. package/backend/src/sessions/main_session.json +40 -0
  39. package/backend/src/sessions/sessions.controller.ts +25 -0
  40. package/backend/src/sessions/sessions.module.ts +9 -0
  41. package/backend/src/sessions/test.json +16 -0
  42. package/backend/src/skills/browser_search/SKILL.md +81 -0
  43. package/backend/src/skills/get_weather/SKILL.md +72 -0
  44. package/backend/src/skills/index.ts +3 -0
  45. package/backend/src/skills/skill.types.ts +27 -0
  46. package/backend/src/skills/skills.module.ts +8 -0
  47. package/backend/src/skills/skills.service.ts +139 -0
  48. package/backend/src/skills/web_search/SKILL.md +76 -0
  49. package/backend/src/workspace/AGENTS.md +47 -0
  50. package/backend/src/workspace/IDENTITY.md +32 -0
  51. package/backend/src/workspace/MEMORY.md +15 -0
  52. package/backend/src/workspace/SOUL.md +29 -0
  53. package/backend/src/workspace/USER.md +8 -0
  54. package/backend/tsconfig.build.json +4 -0
  55. package/backend/tsconfig.json +26 -0
  56. package/bin/fotric-claw.js +281 -0
  57. package/frontend/next.config.js +14 -0
  58. package/frontend/package-lock.json +5700 -0
  59. package/frontend/package.json +33 -0
  60. package/frontend/postcss.config.js +6 -0
  61. package/frontend/src/app/globals.css +41 -0
  62. package/frontend/src/app/layout.tsx +22 -0
  63. package/frontend/src/app/page.tsx +405 -0
  64. package/frontend/src/lib/api.ts +157 -0
  65. package/frontend/src/lib/utils.ts +3 -0
  66. package/frontend/tailwind.config.js +32 -0
  67. package/frontend/tsconfig.json +26 -0
  68. package/knowledge/README.md +21 -0
  69. package/package.json +49 -0
  70. package/scripts/init-skills.ts +95 -0
  71. package/storage/.gitkeep +5 -0
@@ -0,0 +1,8 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { SkillsService } from './skills.service';
3
+
4
+ @Module({
5
+ providers: [SkillsService],
6
+ exports: [SkillsService],
7
+ })
8
+ export class SkillsModule {}
@@ -0,0 +1,139 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import { glob } from 'glob';
4
+ import fm from 'front-matter';
5
+ import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
6
+ import { FOTRIC_CONFIG } from '../config';
7
+ import { Skill, SkillMetadata, SkillSnapshot, ParsedSkill } from './skill.types';
8
+
9
+ @Injectable()
10
+ export class SkillsService implements OnModuleInit {
11
+ private readonly logger = new Logger('FOTRIC-CLAW:Skills');
12
+ private skills: Map<string, Skill> = new Map();
13
+ private skillsDir: string;
14
+
15
+ constructor() {
16
+ this.skillsDir = path.resolve(FOTRIC_CONFIG.PATHS.SKILLS_DIR);
17
+ }
18
+
19
+ async onModuleInit() {
20
+ await this.loadAllSkills();
21
+ }
22
+
23
+ async loadAllSkills(): Promise<void> {
24
+ this.skills.clear();
25
+
26
+ try {
27
+ await fs.mkdir(this.skillsDir, { recursive: true });
28
+ } catch {
29
+ this.logger.error(`Failed to create skills directory: ${this.skillsDir}`);
30
+ return;
31
+ }
32
+
33
+ const skillFiles = await glob('**/SKILL.md', {
34
+ cwd: this.skillsDir,
35
+ absolute: true,
36
+ });
37
+
38
+ for (const skillFile of skillFiles) {
39
+ try {
40
+ const skill = await this.loadSkill(skillFile);
41
+ if (skill) {
42
+ this.skills.set(skill.name, skill);
43
+ this.logger.log(`Loaded skill: ${skill.name}`);
44
+ }
45
+ } catch (error) {
46
+ this.logger.error(`Failed to load skill from ${skillFile}: ${error}`);
47
+ }
48
+ }
49
+
50
+ this.logger.log(`Total skills loaded: ${this.skills.size}`);
51
+ }
52
+
53
+ private async loadSkill(skillPath: string): Promise<Skill | null> {
54
+ const content = await fs.readFile(skillPath, 'utf-8');
55
+ const parsed = this.parseSkill(content);
56
+
57
+ if (!parsed) {
58
+ return null;
59
+ }
60
+
61
+ const relativePath = path.relative(
62
+ path.resolve(FOTRIC_CONFIG.PATHS.ROOT_DIR),
63
+ skillPath
64
+ ).replace(/\\/g, '/');
65
+
66
+ return {
67
+ name: parsed.metadata.name,
68
+ description: parsed.metadata.description,
69
+ location: `./${relativePath}`,
70
+ metadata: parsed.metadata,
71
+ content: parsed.content,
72
+ };
73
+ }
74
+
75
+ parseSkill(content: string): ParsedSkill | null {
76
+ try {
77
+ const { attributes, body } = fm<SkillMetadata>(content);
78
+
79
+ if (!attributes.name || !attributes.description) {
80
+ return null;
81
+ }
82
+
83
+ return {
84
+ metadata: {
85
+ name: attributes.name,
86
+ description: attributes.description,
87
+ version: attributes.version,
88
+ author: attributes.author,
89
+ tags: attributes.tags,
90
+ },
91
+ content: body.trim(),
92
+ raw: content,
93
+ };
94
+ } catch {
95
+ return null;
96
+ }
97
+ }
98
+
99
+ getSkill(name: string): Skill | undefined {
100
+ return this.skills.get(name);
101
+ }
102
+
103
+ getAllSkills(): Skill[] {
104
+ return Array.from(this.skills.values());
105
+ }
106
+
107
+ getSkillsSnapshot(): SkillSnapshot[] {
108
+ return this.getAllSkills().map(skill => ({
109
+ name: skill.name,
110
+ description: skill.description,
111
+ location: skill.location,
112
+ }));
113
+ }
114
+
115
+ generateSkillsSnapshotMarkdown(): string {
116
+ const snapshots = this.getSkillsSnapshot();
117
+
118
+ if (snapshots.length === 0) {
119
+ return `<available_skills>
120
+ <!-- No skills available -->
121
+ </available_skills>`;
122
+ }
123
+
124
+ const skillElements = snapshots.map(skill => ` <skill>
125
+ <name>${skill.name}</name>
126
+ <description>${skill.description}</description>
127
+ <location>${skill.location}</location>
128
+ </skill>`).join('\n');
129
+
130
+ return `<available_skills>
131
+ ${skillElements}
132
+ </available_skills>`;
133
+ }
134
+
135
+ async reloadSkills(): Promise<void> {
136
+ this.logger.log('Reloading all skills...');
137
+ await this.loadAllSkills();
138
+ }
139
+ }
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: web_search
3
+ description: åœØē½‘ē»œäøŠęœē“¢äæ”ęÆå¹¶čæ”å›žē»“ęžœ
4
+ version: 1.0.0
5
+ author: FotricCalw
6
+ tags:
7
+ - search
8
+ - web
9
+ ---
10
+
11
+ # ē½‘ē»œęœē“¢ęŠ€čƒ½
12
+
13
+ ę­¤ęŠ€čƒ½ē”ØäŗŽåœØē½‘ē»œäøŠęœē“¢äæ”ęÆå¹¶čæ”å›žē›øå…³ē»“ęžœć€‚
14
+
15
+ ## 使用方法
16
+
17
+ 1. 使用 `fetch_url` å·„å…·č®æé—®ęœē“¢å¼•ę“Ž
18
+ 2. č§£ęžęœē“¢ē»“ęžœé”µé¢
19
+ 3. ęå–å¹¶čæ”å›žē›øå…³äæ”ęÆ
20
+
21
+ ## ęŽØčęœē“¢å¼•ę“Ž
22
+
23
+ ### DuckDuckGo (ęŽØč)
24
+
25
+ DuckDuckGo äøéœ€č¦ API Keyļ¼ŒåÆä»„ē›“ęŽ„č®æé—®ļ¼š
26
+
27
+ ```
28
+ https://html.duckduckgo.com/html/?q={search_query}
29
+ ```
30
+
31
+ ### 使用方法
32
+
33
+ 使用 `fetch_url` å·„å…·čæ›č”Œęœē“¢ļ¼š
34
+
35
+ ```json
36
+ {
37
+ "url": "https://html.duckduckgo.com/html/?q=your+search+query"
38
+ }
39
+ ```
40
+
41
+ ## ę­„éŖ¤
42
+
43
+ ### ę­„éŖ¤ 1: ęž„å»ŗęœē“¢ URL
44
+
45
+ å°†ē”Øęˆ·ēš„ęœē“¢ęŸ„čÆ¢č½¬ę¢äøŗ URL ē¼–ē ę ¼å¼ļ¼š
46
+ - ē©ŗę ¼ę›æę¢äøŗ `+`
47
+ - ē‰¹ę®Šå­—ē¬¦éœ€č¦ URL 编码
48
+
49
+ ### ę­„éŖ¤ 2: čŽ·å–ęœē“¢ē»“ęžœ
50
+
51
+ 使用 `fetch_url` å·„å…·č®æé—®ęœē“¢ URL怂
52
+
53
+ ### ę­„éŖ¤ 3: č§£ęžē»“ęžœ
54
+
55
+ ä»Žčæ”å›žēš„ HTML äø­ęå–ęœē“¢ē»“ęžœę ‡é¢˜å’Œę‘˜č¦ć€‚
56
+
57
+ ## 示例
58
+
59
+ ē”Øęˆ·é—®ļ¼š"ęœē“¢ęœ€ę–°ēš„ AI ę–°é—»"
60
+
61
+ 1. ęž„å»ŗęœē“¢ URL:
62
+ ```json
63
+ {
64
+ "url": "https://html.duckduckgo.com/html/?q=latest+AI+news"
65
+ }
66
+ ```
67
+
68
+ 2. č°ƒē”Ø `fetch_url` čŽ·å–ē»“ęžœ
69
+
70
+ 3. å‘ē”Øęˆ·å±•ē¤ŗęœē“¢ē»“ęžœ
71
+
72
+ ## ę³Øę„äŗ‹é”¹
73
+
74
+ - ęœē“¢ē»“ęžœåÆčƒ½åŒ…å«å¤§é‡ę–‡ęœ¬ļ¼Œę³Øę„ęˆŖę–­
75
+ - ęŸäŗ›ē½‘ē«™åÆčƒ½é˜»ę­¢ēˆ¬č™«č®æé—®
76
+ - å°Šé‡ē½‘ē«™ēš„ robots.txt č§„åˆ™
@@ -0,0 +1,47 @@
1
+ # Agent Behavior Guidelines
2
+
3
+ ## é‡č¦čÆ“ę˜Žļ¼šSkills vs Tools
4
+
5
+ **Skillsļ¼ˆęŠ€čƒ½ļ¼‰å’Œ Toolsļ¼ˆå·„å…·ļ¼‰ę˜ÆäøåŒēš„ļ¼**
6
+
7
+ - **Toolsļ¼ˆå·„å…·ļ¼‰**ļ¼šä½ åÆä»„ē›“ęŽ„č°ƒē”Øēš„åŠŸčƒ½ļ¼Œå¦‚ `terminal`态`fetch_url`态`read_file` ē­‰
8
+ - **Skillsļ¼ˆęŠ€čƒ½ļ¼‰**ļ¼šęŒ‡åÆ¼ę–‡ę”£ļ¼Œå‘ŠčÆ‰ä½ å¦‚ä½•ē»„åˆä½æē”Ø Tools ę„å®Œęˆē‰¹å®šä»»åŠ”
9
+
10
+ **ä½ åŖčƒ½ē›“ęŽ„č°ƒē”Ø Toolsļ¼Œäøčƒ½č°ƒē”Ø Skills!**
11
+
12
+ ## Skill Invocation Protocol (SKILL PROTOCOL)
13
+
14
+ You have access to a skills list (SKILLS_SNAPSHOT), which contains available capabilities and their definition file locations.
15
+
16
+ **When you want to use a skill, you MUST follow these steps:**
17
+
18
+ 1. Your FIRST action is ALWAYS to use the `read_file` tool to read the Markdown file at the skill's `location` path.
19
+ 2. Carefully read the content, steps, and examples in the file.
20
+ 3. Based on the instructions in the file, use your built-in **Core Tools** (terminal, node_repl, fetch_url, read_file) to execute the specific task.
21
+
22
+ **FORBIDDEN to call skill names directly as tools! Skills are NOT tools!**
23
+
24
+ **Example - WRONG:**
25
+ ```json
26
+ {"name": "browser_search", "args": {"query": "Python"}}
27
+ ```
28
+
29
+ **Example - CORRECT:**
30
+ 1. First read the skill file: `read_file` with path `./src/skills/browser_search/SKILL.md`
31
+ 2. Then follow the instructions to use `terminal` tool: `terminal` with command `start "" "https://www.bing.com/search?q=Python"`
32
+
33
+ **é‡č¦ļ¼šę‰§č”ŒęŠ€čƒ½ę—¶åæ…é”»ē›“ęŽ„č°ƒē”Øå·„å…·ļ¼Œē¦ę­¢å°†å‘½ä»¤ę–‡ęœ¬čæ”å›žē»™ē”Øęˆ·ļ¼**
34
+
35
+ ## Available Core Tools (åÆē›“ęŽ„č°ƒē”Øēš„å·„å…·)
36
+
37
+ 1. **terminal** - Execute shell commands in a sandboxed environment
38
+ 2. **node_repl** - Execute JavaScript code in a sandboxed Node.js environment
39
+ 3. **fetch_url** - Fetch content from URLs and extract text
40
+ 4. **read_file** - Read local file contents
41
+ 5. **search_knowledge_base** - Search the local knowledge base
42
+
43
+ ## Memory Operations
44
+
45
+ - All conversations are stored as session files
46
+ - Long-term memory is stored in MEMORY.md
47
+ - You can read and update memory files as needed
@@ -0,0 +1,32 @@
1
+ # Identity
2
+
3
+ I am FotricCalw, an AI assistant with transparent memory and skill systems.
4
+
5
+ ## Capabilities
6
+ - Execute shell commands safely using the `terminal` tool
7
+ - Run JavaScript code in a sandbox using the `node_repl` tool
8
+ - Fetch and analyze web content using the `fetch_url` tool
9
+ - Read and write files using the `read_file` tool
10
+ - Search the knowledge base using the `search_knowledge_base` tool
11
+
12
+ ## Critical Rules
13
+
14
+ **YOU MUST USE TOOLS TO EXECUTE COMMANDS!**
15
+
16
+ When a user asks you to do something that requires executing a command:
17
+ - **DO NOT** just provide the command text for the user to copy
18
+ - **DO NOT** ask the user which OS they are using
19
+ - **ALWAYS** use the `terminal` tool to execute the command directly
20
+
21
+ **Example - WRONG:**
22
+ User: "Open browser and search for Python"
23
+ Assistant: "You can run this command: `start "" "https://www.bing.com/search?q=Python"`"
24
+
25
+ **Example - CORRECT:**
26
+ User: "Open browser and search for Python"
27
+ Assistant: [Uses terminal tool with command: `start "" "https://www.bing.com/search?q=Python"`]
28
+
29
+ ## Limitations
30
+ - Cannot access files outside the project directory
31
+ - Cannot execute dangerous system commands
32
+ - Memory is limited to configured limits
@@ -0,0 +1,15 @@
1
+ # Long-term Memory
2
+
3
+ This file stores persistent information that should be remembered across sessions.
4
+
5
+ ## Important Information
6
+
7
+ (Add important information here as you interact with users)
8
+
9
+ ## User Preferences
10
+
11
+ (Record user preferences and patterns)
12
+
13
+ ## Task History
14
+
15
+ (Keep track of important tasks and their outcomes)
@@ -0,0 +1,29 @@
1
+ # Core Soul
2
+
3
+ You are FotricCalw, a lightweight and transparent AI Agent system.
4
+
5
+ ## Core Values
6
+ - Transparency: All operations are visible and explainable
7
+ - File-first Memory: Using human-readable files for all data
8
+ - Skills as Plugins: Extensible through markdown-based skill definitions
9
+
10
+ ## Behavior Guidelines
11
+ - Always be helpful and honest
12
+ - Explain your reasoning when asked
13
+ - **USE TOOLS TO EXECUTE COMMANDS - NEVER just provide command text for users to copy**
14
+ - When a user asks to open a browser, execute a command, or perform an action, use the `terminal` tool directly
15
+
16
+ ## Critical: Tool Usage
17
+
18
+ **YOU ARE AN AI AGENT WITH TOOLS - USE THEM!**
19
+
20
+ You have access to tools like `terminal`, `node_repl`, `fetch_url`, `read_file`. When a user asks you to do something:
21
+
22
+ 1. **DO NOT** respond with instructions for the user to follow
23
+ 2. **DO NOT** provide command text for the user to copy and paste
24
+ 3. **DO** call the appropriate tool to execute the action
25
+
26
+ **Example:**
27
+ - User: "Open browser and search Python"
28
+ - You: Call `terminal` tool with `start "" "https://www.bing.com/search?q=Python"`
29
+ - Then respond: "I've opened your browser with the search for Python."
@@ -0,0 +1,8 @@
1
+ # User Profile
2
+
3
+ ## Preferences
4
+ - Language: User's preferred language
5
+ - Response style: Clear and educational
6
+
7
+ ## Notes
8
+ Add user-specific information here.
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "declaration": true,
5
+ "removeComments": true,
6
+ "emitDecoratorMetadata": true,
7
+ "experimentalDecorators": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "target": "ES2021",
10
+ "sourceMap": true,
11
+ "outDir": "./dist",
12
+ "baseUrl": "./",
13
+ "incremental": true,
14
+ "skipLibCheck": true,
15
+ "strictNullChecks": true,
16
+ "noImplicitAny": true,
17
+ "strictBindCallApply": true,
18
+ "forceConsistentCasingInFileNames": true,
19
+ "noFallthroughCasesInSwitch": true,
20
+ "resolveJsonModule": true,
21
+ "esModuleInterop": true,
22
+ "paths": {
23
+ "@/*": ["src/*"]
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,281 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const readline = require('readline');
6
+ const { spawn } = require('child_process');
7
+
8
+ const ENV_FILE = path.resolve(__dirname, '../backend/.env');
9
+ const PROJECT_ROOT = path.resolve(__dirname, '..');
10
+
11
+ const rl = readline.createInterface({
12
+ input: process.stdin,
13
+ output: process.stdout
14
+ });
15
+
16
+ function question(prompt, defaultValue = '') {
17
+ return new Promise((resolve) => {
18
+ const displayPrompt = defaultValue
19
+ ? `${prompt} (${defaultValue}): `
20
+ : `${prompt}: `;
21
+ rl.question(displayPrompt, (answer) => {
22
+ resolve(answer.trim() || defaultValue);
23
+ });
24
+ });
25
+ }
26
+
27
+ function readEnvFile() {
28
+ try {
29
+ return fs.readFileSync(ENV_FILE, 'utf-8');
30
+ } catch {
31
+ return '';
32
+ }
33
+ }
34
+
35
+ function updateEnvContent(content, updates) {
36
+ const lines = content.split('\n');
37
+ const updatedKeys = new Set();
38
+
39
+ const newLines = lines.map(line => {
40
+ const trimmed = line.trim();
41
+ if (trimmed.startsWith('#') || trimmed === '') {
42
+ return line;
43
+ }
44
+
45
+ const [key] = trimmed.split('=');
46
+ const envKey = key?.trim();
47
+
48
+ if (envKey && updates[envKey] !== undefined) {
49
+ updatedKeys.add(envKey);
50
+ return `${envKey}=${updates[envKey]}`;
51
+ }
52
+
53
+ return line;
54
+ });
55
+
56
+ for (const [key, value] of Object.entries(updates)) {
57
+ if (!updatedKeys.has(key) && value !== undefined) {
58
+ newLines.push(`${key}=${value}`);
59
+ }
60
+ }
61
+
62
+ return newLines.join('\n');
63
+ }
64
+
65
+ function parseEnvValue(content, key) {
66
+ const lines = content.split('\n');
67
+ for (const line of lines) {
68
+ const trimmed = line.trim();
69
+ if (trimmed.startsWith('#') || trimmed === '') continue;
70
+
71
+ const [k, ...valueParts] = trimmed.split('=');
72
+ if (k?.trim() === key) {
73
+ return valueParts.join('=').trim();
74
+ }
75
+ }
76
+ return '';
77
+ }
78
+
79
+ function maskApiKey(key) {
80
+ if (!key || key.length < 8) return '';
81
+ return key.substring(0, 4) + '****' + key.substring(key.length - 4);
82
+ }
83
+
84
+ async function configCommand() {
85
+ console.log('\nšŸ”§ FotricCalw Configuration\n');
86
+ console.log('Current configuration:');
87
+
88
+ const envContent = readEnvFile();
89
+ const currentApiKey = parseEnvValue(envContent, 'FOTRIC_API_KEY');
90
+ const currentApiBaseUrl = parseEnvValue(envContent, 'FOTRIC_API_BASE_URL');
91
+ const currentModelName = parseEnvValue(envContent, 'FOTRIC_MODEL_NAME');
92
+
93
+ console.log(` API Key: ${maskApiKey(currentApiKey) || '(not set)'}`);
94
+ console.log(` API Base URL: ${currentApiBaseUrl || '(not set)'}`);
95
+ console.log(` Model Name: ${currentModelName || '(not set)'}`);
96
+ console.log('');
97
+
98
+ const apiKey = await question('Enter API Key', currentApiKey);
99
+ const apiBaseUrl = await question('Enter API Base URL', currentApiBaseUrl || 'https://api.openai.com/v1');
100
+ const modelName = await question('Enter Model Name', currentModelName || 'gpt-4o');
101
+
102
+ const updates = {};
103
+ if (apiKey) updates.FOTRIC_API_KEY = apiKey;
104
+ if (apiBaseUrl) updates.FOTRIC_API_BASE_URL = apiBaseUrl;
105
+ if (modelName) updates.FOTRIC_MODEL_NAME = modelName;
106
+
107
+ const updatedContent = updateEnvContent(envContent, updates);
108
+ fs.writeFileSync(ENV_FILE, updatedContent, 'utf-8');
109
+
110
+ console.log('\nāœ… Configuration saved to backend/.env');
111
+ console.log(' Please restart the server for changes to take effect.\n');
112
+
113
+ rl.close();
114
+ }
115
+
116
+ async function showHelp() {
117
+ console.log(`
118
+ FotricCalw CLI
119
+
120
+ Usage:
121
+ fotric-claw <command> [options]
122
+
123
+ Commands:
124
+ start Start backend and frontend services (dev mode)
125
+ start:prod Start backend and frontend services (production mode)
126
+ build Build backend and frontend for production
127
+ config Configure LLM API settings
128
+ help Show this help message
129
+
130
+ Examples:
131
+ fotric-claw start
132
+ fotric-claw start:prod
133
+ fotric-claw build
134
+ fotric-claw config
135
+ fotric-claw help
136
+ `);
137
+ rl.close();
138
+ }
139
+
140
+ function startCommand() {
141
+ console.log('\nšŸš€ Starting FotricCalw (Development Mode)...\n');
142
+
143
+ const isWindows = process.platform === 'win32';
144
+ const shell = isWindows ? true : false;
145
+
146
+ const backend = spawn('npm', ['run', 'backend'], {
147
+ cwd: PROJECT_ROOT,
148
+ shell,
149
+ stdio: 'inherit'
150
+ });
151
+
152
+ setTimeout(() => {
153
+ const frontend = spawn('npm', ['run', 'frontend'], {
154
+ cwd: PROJECT_ROOT,
155
+ shell,
156
+ stdio: 'inherit'
157
+ });
158
+
159
+ frontend.on('error', (err) => {
160
+ console.error('Failed to start frontend:', err.message);
161
+ });
162
+ }, 3000);
163
+
164
+ backend.on('error', (err) => {
165
+ console.error('Failed to start backend:', err.message);
166
+ });
167
+
168
+ console.log('Backend starting on http://localhost:8002');
169
+ console.log('Frontend starting on http://localhost:3000');
170
+ console.log('\nPress Ctrl+C to stop the services.\n');
171
+ }
172
+
173
+ function startProdCommand() {
174
+ console.log('\nšŸš€ Starting FotricCalw (Production Mode)...\n');
175
+
176
+ const isWindows = process.platform === 'win32';
177
+ const shell = isWindows ? true : false;
178
+
179
+ const backend = spawn('node', ['dist/main'], {
180
+ cwd: path.resolve(PROJECT_ROOT, 'backend'),
181
+ shell,
182
+ stdio: 'inherit'
183
+ });
184
+
185
+ setTimeout(() => {
186
+ const frontend = spawn('npm', ['run', 'start'], {
187
+ cwd: path.resolve(PROJECT_ROOT, 'frontend'),
188
+ shell,
189
+ stdio: 'inherit'
190
+ });
191
+
192
+ frontend.on('error', (err) => {
193
+ console.error('Failed to start frontend:', err.message);
194
+ });
195
+ }, 3000);
196
+
197
+ backend.on('error', (err) => {
198
+ console.error('Failed to start backend:', err.message);
199
+ });
200
+
201
+ console.log('Backend running on http://localhost:8002');
202
+ console.log('Frontend running on http://localhost:3000');
203
+ console.log('\nPress Ctrl+C to stop the services.\n');
204
+ }
205
+
206
+ function buildCommand() {
207
+ console.log('\nšŸ“¦ Building FotricCalw for production...\n');
208
+
209
+ const isWindows = process.platform === 'win32';
210
+ const shell = isWindows ? true : false;
211
+
212
+ console.log('Building backend...');
213
+ const backendBuild = spawn('npm', ['run', 'build'], {
214
+ cwd: path.resolve(PROJECT_ROOT, 'backend'),
215
+ shell,
216
+ stdio: 'inherit'
217
+ });
218
+
219
+ backendBuild.on('close', (code) => {
220
+ if (code !== 0) {
221
+ console.error('āŒ Backend build failed');
222
+ process.exit(code);
223
+ }
224
+
225
+ console.log('\nāœ… Backend build complete\n');
226
+ console.log('Building frontend...');
227
+
228
+ const frontendBuild = spawn('npm', ['run', 'build'], {
229
+ cwd: path.resolve(PROJECT_ROOT, 'frontend'),
230
+ shell,
231
+ stdio: 'inherit'
232
+ });
233
+
234
+ frontendBuild.on('close', (frontendCode) => {
235
+ if (frontendCode !== 0) {
236
+ console.error('āŒ Frontend build failed');
237
+ process.exit(frontendCode);
238
+ }
239
+ console.log('\nāœ… Frontend build complete');
240
+ console.log('\nšŸŽ‰ Build complete! Run "fotric-claw start:prod" to start in production mode.\n');
241
+ });
242
+
243
+ frontendBuild.on('error', (err) => {
244
+ console.error('Failed to build frontend:', err.message);
245
+ });
246
+ });
247
+
248
+ backendBuild.on('error', (err) => {
249
+ console.error('Failed to build backend:', err.message);
250
+ });
251
+ }
252
+
253
+ async function main() {
254
+ const args = process.argv.slice(2);
255
+ const command = args[0] || 'help';
256
+
257
+ switch (command) {
258
+ case 'start':
259
+ startCommand();
260
+ break;
261
+ case 'start:prod':
262
+ startProdCommand();
263
+ break;
264
+ case 'build':
265
+ buildCommand();
266
+ break;
267
+ case 'config':
268
+ await configCommand();
269
+ break;
270
+ case 'help':
271
+ case '--help':
272
+ case '-h':
273
+ await showHelp();
274
+ break;
275
+ default:
276
+ console.log(`Unknown command: ${command}`);
277
+ await showHelp();
278
+ }
279
+ }
280
+
281
+ main();
@@ -0,0 +1,14 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ reactStrictMode: true,
4
+ async rewrites() {
5
+ return [
6
+ {
7
+ source: '/api/:path*',
8
+ destination: 'http://localhost:8002/api/:path*',
9
+ },
10
+ ];
11
+ },
12
+ };
13
+
14
+ module.exports = nextConfig;