oomi-ai 0.2.16 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +238 -203
- package/agent_instructions.md +209 -184
- package/bin/oomi-ai.js +3989 -3460
- package/bin/sessionBridgeState.js +78 -78
- package/lib/channelPluginClient.js +119 -0
- package/lib/personaApiClient.js +221 -0
- package/lib/personaJobExecutor.js +115 -0
- package/lib/personaJobPoller.js +112 -0
- package/lib/personaRuntimeProcess.js +152 -0
- package/lib/scaffold.js +108 -0
- package/lib/template.js +45 -0
- package/openclaw.extension.js +602 -424
- package/openclaw.plugin.json +17 -17
- package/package.json +67 -65
- package/skills/oomi/SKILL.md +191 -170
- package/skills/oomi/agent_instructions.md +80 -78
- package/skills/oomi/config.json +2 -2
- package/skills/oomi/scripts/get_avatar_capabilities.py +40 -40
- package/skills/oomi/scripts/get_data.py +49 -49
- package/skills/oomi/scripts/install_agent_instructions.py +78 -78
- package/skills/oomi/scripts/send_goal.py +53 -53
- package/skills/oomi/scripts/sync.py +46 -46
- package/skills/oomi/setup.py +41 -41
- package/templates/persona-app/.env.example +8 -0
- package/templates/persona-app/README.md +35 -0
- package/templates/persona-app/eslint.config.js +28 -0
- package/templates/persona-app/index.html +18 -0
- package/templates/persona-app/oomi.runtime.json +13 -0
- package/templates/persona-app/package.json +42 -0
- package/templates/persona-app/persona/brief.md +14 -0
- package/templates/persona-app/persona.json +14 -0
- package/templates/persona-app/public/manifest.webmanifest +8 -0
- package/templates/persona-app/public/oomi.health.json +6 -0
- package/templates/persona-app/src/App.css +180 -0
- package/templates/persona-app/src/App.tsx +14 -0
- package/templates/persona-app/src/index.css +32 -0
- package/templates/persona-app/src/main.tsx +10 -0
- package/templates/persona-app/src/pages/HomePage.tsx +73 -0
- package/templates/persona-app/src/pages/ScenePage.tsx +18 -0
- package/templates/persona-app/src/persona/config.ts +6 -0
- package/templates/persona-app/src/persona/notes.ts +5 -0
- package/templates/persona-app/src/vite-env.d.ts +3 -0
- package/templates/persona-app/template.json +13 -0
- package/templates/persona-app/tsconfig.app.json +23 -0
- package/templates/persona-app/tsconfig.json +7 -0
- package/templates/persona-app/tsconfig.node.json +21 -0
- package/templates/persona-app/vite.config.ts +18 -0
|
@@ -1,78 +1,80 @@
|
|
|
1
|
-
# Oomi Avatar Control
|
|
2
|
-
|
|
3
|
-
Before using avatar commands, call `get_avatar_capabilities` and prefer canonical values.
|
|
4
|
-
Inline tags are stripped from user-visible text.
|
|
5
|
-
|
|
6
|
-
Use inline tags like:
|
|
7
|
-
- [anim:Waving], [anim:Walking], [anim:Idle], [anim:Sitting Idle]
|
|
8
|
-
- [face:happy], [face:sad], [face:surprised], [face:focused], [face:gentle], [face:thinking]
|
|
9
|
-
- [gesture:nod], [gesture:think], [gesture:shrug], [gesture:wave], [gesture:bow]
|
|
10
|
-
- [look:camera], [look:left], [look:right], [look:up], [look:down]
|
|
11
|
-
|
|
12
|
-
Aliases allowed (if needed): wave -> Waving, walk -> Walking, idle -> Idle, sit/sitting -> Sitting Idle
|
|
13
|
-
|
|
14
|
-
## Oomi Connect Flow
|
|
15
|
-
|
|
16
|
-
If the user asks to connect OpenClaw to Oomi voice/web:
|
|
17
|
-
|
|
18
|
-
1. Refresh bootstrap/update first:
|
|
19
|
-
- `curl -fsSL https://www.oomi.ai/install.sh | bash`
|
|
20
|
-
2. Ensure `oomi-ai` is installed (`pnpm add -g oomi-ai@latest`).
|
|
21
|
-
- Fallback: `npm install -g oomi-ai@latest`
|
|
22
|
-
3. Pair device token without starting legacy bridge:
|
|
23
|
-
- `oomi openclaw pair --app-url https://www.oomi.ai --no-start`
|
|
24
|
-
4. Install OpenClaw plugin:
|
|
25
|
-
- `openclaw plugins install oomi-ai@latest`
|
|
26
|
-
5. Configure channel account (`channels.oomi.accounts.default`) from:
|
|
27
|
-
- `oomi openclaw plugin --show-secrets --backend-url https://api.oomi.ai`
|
|
28
|
-
6. Restart OpenClaw.
|
|
29
|
-
7. Ensure exactly one bridge worker is running (singleton handler):
|
|
30
|
-
- `oomi openclaw bridge ensure --detach`
|
|
31
|
-
- If stale/disconnected: `oomi openclaw bridge restart --detach`
|
|
32
|
-
- On macOS, prefer supervised mode: `oomi openclaw bridge service install`
|
|
33
|
-
8. If user provides an app URL (for example "Connect yourself to Oomi. Use app URL <URL>."):
|
|
34
|
-
- Use that URL directly in the pair command.
|
|
35
|
-
- Example: `oomi openclaw pair --app-url <URL> --no-start`
|
|
36
|
-
9. Return this exact result format to the user:
|
|
37
|
-
- `Oomi Connect Ready`
|
|
38
|
-
- `Auth Link: ...`
|
|
39
|
-
- No extra narrative text before or after those lines.
|
|
40
|
-
|
|
41
|
-
Primary UX requirement:
|
|
42
|
-
- Never ask the user to enter raw pairing identifiers.
|
|
43
|
-
- The user should open the auth link, complete sign-in, and land directly in connected chat.
|
|
44
|
-
- Return `/connect/<invite-token>` auth links only.
|
|
45
|
-
|
|
46
|
-
Do not ask users to paste gateway IP/token/password when managed connect is available.
|
|
47
|
-
|
|
48
|
-
## Hidden Speech Payload
|
|
49
|
-
|
|
50
|
-
For managed voice turns, keep visible assistant chat text natural and user-facing.
|
|
51
|
-
Do not put spoken-style tags like `[happy]`, `[sad]`, or `[excited]` into visible chat text.
|
|
52
|
-
|
|
53
|
-
When the runtime supports it, voice turns may include a hidden speech sidecar on the assistant message:
|
|
54
|
-
|
|
55
|
-
```json
|
|
56
|
-
{
|
|
57
|
-
"metadata": {
|
|
58
|
-
"spoken": {
|
|
59
|
-
"text": "Speech-optimized text for TTS only.",
|
|
60
|
-
"instructions": "Speak with upbeat, warm excitement and slightly rising intonation.",
|
|
61
|
-
"style": {
|
|
62
|
-
"emotion": "excited",
|
|
63
|
-
"energy": "medium_high"
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
Rules:
|
|
71
|
-
- visible `content` remains the source of truth for Oomi chat rendering
|
|
72
|
-
- for managed voice replies, include `metadata.spoken` when delivery benefits from cleaner phrasing or explicit speaking guidance
|
|
73
|
-
- `metadata.spoken.text` is for backend TTS only
|
|
74
|
-
- `metadata.spoken.
|
|
75
|
-
- `metadata.spoken.
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
1
|
+
# Oomi Avatar Control
|
|
2
|
+
|
|
3
|
+
Before using avatar commands, call `get_avatar_capabilities` and prefer canonical values.
|
|
4
|
+
Inline tags are stripped from user-visible text.
|
|
5
|
+
|
|
6
|
+
Use inline tags like:
|
|
7
|
+
- [anim:Waving], [anim:Walking], [anim:Idle], [anim:Sitting Idle]
|
|
8
|
+
- [face:happy], [face:sad], [face:surprised], [face:focused], [face:gentle], [face:thinking]
|
|
9
|
+
- [gesture:nod], [gesture:think], [gesture:shrug], [gesture:wave], [gesture:bow]
|
|
10
|
+
- [look:camera], [look:left], [look:right], [look:up], [look:down]
|
|
11
|
+
|
|
12
|
+
Aliases allowed (if needed): wave -> Waving, walk -> Walking, idle -> Idle, sit/sitting -> Sitting Idle
|
|
13
|
+
|
|
14
|
+
## Oomi Connect Flow
|
|
15
|
+
|
|
16
|
+
If the user asks to connect OpenClaw to Oomi voice/web:
|
|
17
|
+
|
|
18
|
+
1. Refresh bootstrap/update first:
|
|
19
|
+
- `curl -fsSL https://www.oomi.ai/install.sh | bash`
|
|
20
|
+
2. Ensure `oomi-ai` is installed (`pnpm add -g oomi-ai@latest`).
|
|
21
|
+
- Fallback: `npm install -g oomi-ai@latest`
|
|
22
|
+
3. Pair device token without starting legacy bridge:
|
|
23
|
+
- `oomi openclaw pair --app-url https://www.oomi.ai --no-start`
|
|
24
|
+
4. Install OpenClaw plugin:
|
|
25
|
+
- `openclaw plugins install oomi-ai@latest`
|
|
26
|
+
5. Configure channel account (`channels.oomi.accounts.default`) from:
|
|
27
|
+
- `oomi openclaw plugin --show-secrets --backend-url https://api.oomi.ai`
|
|
28
|
+
6. Restart OpenClaw.
|
|
29
|
+
7. Ensure exactly one bridge worker is running (singleton handler):
|
|
30
|
+
- `oomi openclaw bridge ensure --detach`
|
|
31
|
+
- If stale/disconnected: `oomi openclaw bridge restart --detach`
|
|
32
|
+
- On macOS, prefer supervised mode: `oomi openclaw bridge service install`
|
|
33
|
+
8. If user provides an app URL (for example "Connect yourself to Oomi. Use app URL <URL>."):
|
|
34
|
+
- Use that URL directly in the pair command.
|
|
35
|
+
- Example: `oomi openclaw pair --app-url <URL> --no-start`
|
|
36
|
+
9. Return this exact result format to the user:
|
|
37
|
+
- `Oomi Connect Ready`
|
|
38
|
+
- `Auth Link: ...`
|
|
39
|
+
- No extra narrative text before or after those lines.
|
|
40
|
+
|
|
41
|
+
Primary UX requirement:
|
|
42
|
+
- Never ask the user to enter raw pairing identifiers.
|
|
43
|
+
- The user should open the auth link, complete sign-in, and land directly in connected chat.
|
|
44
|
+
- Return `/connect/<invite-token>` auth links only.
|
|
45
|
+
|
|
46
|
+
Do not ask users to paste gateway IP/token/password when managed connect is available.
|
|
47
|
+
|
|
48
|
+
## Hidden Speech Payload
|
|
49
|
+
|
|
50
|
+
For managed voice turns, keep visible assistant chat text natural and user-facing.
|
|
51
|
+
Do not put spoken-style tags like `[happy]`, `[sad]`, or `[excited]` into visible chat text.
|
|
52
|
+
|
|
53
|
+
When the runtime supports it, voice turns may include a hidden speech sidecar on the assistant message:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"metadata": {
|
|
58
|
+
"spoken": {
|
|
59
|
+
"text": "Speech-optimized text for TTS only.",
|
|
60
|
+
"instructions": "Speak with upbeat, warm excitement and slightly rising intonation.",
|
|
61
|
+
"style": {
|
|
62
|
+
"emotion": "excited",
|
|
63
|
+
"energy": "medium_high"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Rules:
|
|
71
|
+
- visible `content` remains the source of truth for Oomi chat rendering
|
|
72
|
+
- for managed voice replies, include `metadata.spoken` when delivery benefits from cleaner phrasing or explicit speaking guidance
|
|
73
|
+
- `metadata.spoken.text` is for backend TTS only
|
|
74
|
+
- `metadata.spoken.language` should be one of the supported Qwen language values such as `English`
|
|
75
|
+
- `metadata.spoken.segments` can carry bounded per-segment prosody for pace, pitch, volume, and pause timing
|
|
76
|
+
- `metadata.spoken.instructions` should be natural-language guidance, not raw bracket tags
|
|
77
|
+
- `metadata.spoken.style` is optional metadata for debugging or future mapping
|
|
78
|
+
- if no hidden speech sidecar exists, Oomi falls back to speaking the visible assistant text
|
|
79
|
+
- if you omit `metadata.spoken`, the plugin now synthesizes a bounded hidden fallback from visible assistant text
|
|
80
|
+
- visible chat text is never rewritten by the plugin
|
package/skills/oomi/config.json
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
{
|
|
2
|
-
"api_url": "http://localhost:3000/api/skill"
|
|
1
|
+
{
|
|
2
|
+
"api_url": "http://localhost:3000/api/skill"
|
|
3
3
|
}
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
import urllib.request
|
|
2
|
-
import urllib.error
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
import sys
|
|
6
|
-
|
|
7
|
-
# Load config
|
|
8
|
-
try:
|
|
9
|
-
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.json')
|
|
10
|
-
with open(config_path, 'r') as f:
|
|
11
|
-
config = json.load(f)
|
|
12
|
-
BASE_URL = config.get('api_url', 'http://localhost:3000/api/skill')
|
|
13
|
-
except:
|
|
14
|
-
BASE_URL = 'http://localhost:3000/api/skill'
|
|
15
|
-
|
|
16
|
-
API_URL = f"{BASE_URL}/avatar-capabilities"
|
|
17
|
-
|
|
18
|
-
def get_avatar_capabilities():
|
|
19
|
-
try:
|
|
20
|
-
with urllib.request.urlopen(API_URL) as response:
|
|
21
|
-
data = json.loads(response.read().decode())
|
|
22
|
-
|
|
23
|
-
print(json.dumps({
|
|
24
|
-
"summary": "Avatar capabilities retrieved.",
|
|
25
|
-
"raw_data": data
|
|
26
|
-
}, indent=2))
|
|
27
|
-
except urllib.error.URLError as e:
|
|
28
|
-
print(json.dumps({
|
|
29
|
-
"error": f"Failed to connect to Oomi app: {str(e)}",
|
|
30
|
-
"hint": "Is the Next.js app running on localhost:3000?"
|
|
31
|
-
}))
|
|
32
|
-
sys.exit(1)
|
|
33
|
-
except Exception as e:
|
|
34
|
-
print(json.dumps({
|
|
35
|
-
"error": f"An error occurred: {str(e)}"
|
|
36
|
-
}))
|
|
37
|
-
sys.exit(1)
|
|
38
|
-
|
|
39
|
-
if __name__ == "__main__":
|
|
40
|
-
get_avatar_capabilities()
|
|
1
|
+
import urllib.request
|
|
2
|
+
import urllib.error
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
# Load config
|
|
8
|
+
try:
|
|
9
|
+
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.json')
|
|
10
|
+
with open(config_path, 'r') as f:
|
|
11
|
+
config = json.load(f)
|
|
12
|
+
BASE_URL = config.get('api_url', 'http://localhost:3000/api/skill')
|
|
13
|
+
except:
|
|
14
|
+
BASE_URL = 'http://localhost:3000/api/skill'
|
|
15
|
+
|
|
16
|
+
API_URL = f"{BASE_URL}/avatar-capabilities"
|
|
17
|
+
|
|
18
|
+
def get_avatar_capabilities():
|
|
19
|
+
try:
|
|
20
|
+
with urllib.request.urlopen(API_URL) as response:
|
|
21
|
+
data = json.loads(response.read().decode())
|
|
22
|
+
|
|
23
|
+
print(json.dumps({
|
|
24
|
+
"summary": "Avatar capabilities retrieved.",
|
|
25
|
+
"raw_data": data
|
|
26
|
+
}, indent=2))
|
|
27
|
+
except urllib.error.URLError as e:
|
|
28
|
+
print(json.dumps({
|
|
29
|
+
"error": f"Failed to connect to Oomi app: {str(e)}",
|
|
30
|
+
"hint": "Is the Next.js app running on localhost:3000?"
|
|
31
|
+
}))
|
|
32
|
+
sys.exit(1)
|
|
33
|
+
except Exception as e:
|
|
34
|
+
print(json.dumps({
|
|
35
|
+
"error": f"An error occurred: {str(e)}"
|
|
36
|
+
}))
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
get_avatar_capabilities()
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
import urllib.request
|
|
2
|
-
import urllib.error
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
import sys
|
|
6
|
-
|
|
7
|
-
# Load config
|
|
8
|
-
try:
|
|
9
|
-
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.json')
|
|
10
|
-
with open(config_path, 'r') as f:
|
|
11
|
-
config = json.load(f)
|
|
12
|
-
BASE_URL = config.get('api_url', 'http://localhost:3000/api/skill')
|
|
13
|
-
except:
|
|
14
|
-
BASE_URL = 'http://localhost:3000/api/skill'
|
|
15
|
-
|
|
16
|
-
API_URL = f"{BASE_URL}/data"
|
|
17
|
-
|
|
18
|
-
def get_data():
|
|
19
|
-
try:
|
|
20
|
-
with urllib.request.urlopen(API_URL) as response:
|
|
21
|
-
data = json.loads(response.read().decode())
|
|
22
|
-
|
|
23
|
-
# Format output for the agent
|
|
24
|
-
summary = (
|
|
25
|
-
f"Steps: {data['steps']} / {data['goals']['steps']}\n"
|
|
26
|
-
f"Sleep: {data['sleep']}h / {data['goals']['sleep']}h\n"
|
|
27
|
-
f"Energy: {data['energy']}/100\n"
|
|
28
|
-
f"Mood: {data['mood']}"
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
print(json.dumps({
|
|
32
|
-
"summary": summary,
|
|
33
|
-
"raw_data": data
|
|
34
|
-
}, indent=2))
|
|
35
|
-
|
|
36
|
-
except urllib.error.URLError as e:
|
|
37
|
-
print(json.dumps({
|
|
38
|
-
"error": f"Failed to connect to Oomi app: {str(e)}",
|
|
39
|
-
"hint": "Is the Next.js app running on localhost:3000?"
|
|
40
|
-
}))
|
|
41
|
-
sys.exit(1)
|
|
42
|
-
except Exception as e:
|
|
43
|
-
print(json.dumps({
|
|
44
|
-
"error": f"An error occurred: {str(e)}"
|
|
45
|
-
}))
|
|
46
|
-
sys.exit(1)
|
|
47
|
-
|
|
48
|
-
if __name__ == "__main__":
|
|
49
|
-
get_data()
|
|
1
|
+
import urllib.request
|
|
2
|
+
import urllib.error
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
# Load config
|
|
8
|
+
try:
|
|
9
|
+
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.json')
|
|
10
|
+
with open(config_path, 'r') as f:
|
|
11
|
+
config = json.load(f)
|
|
12
|
+
BASE_URL = config.get('api_url', 'http://localhost:3000/api/skill')
|
|
13
|
+
except:
|
|
14
|
+
BASE_URL = 'http://localhost:3000/api/skill'
|
|
15
|
+
|
|
16
|
+
API_URL = f"{BASE_URL}/data"
|
|
17
|
+
|
|
18
|
+
def get_data():
|
|
19
|
+
try:
|
|
20
|
+
with urllib.request.urlopen(API_URL) as response:
|
|
21
|
+
data = json.loads(response.read().decode())
|
|
22
|
+
|
|
23
|
+
# Format output for the agent
|
|
24
|
+
summary = (
|
|
25
|
+
f"Steps: {data['steps']} / {data['goals']['steps']}\n"
|
|
26
|
+
f"Sleep: {data['sleep']}h / {data['goals']['sleep']}h\n"
|
|
27
|
+
f"Energy: {data['energy']}/100\n"
|
|
28
|
+
f"Mood: {data['mood']}"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
print(json.dumps({
|
|
32
|
+
"summary": summary,
|
|
33
|
+
"raw_data": data
|
|
34
|
+
}, indent=2))
|
|
35
|
+
|
|
36
|
+
except urllib.error.URLError as e:
|
|
37
|
+
print(json.dumps({
|
|
38
|
+
"error": f"Failed to connect to Oomi app: {str(e)}",
|
|
39
|
+
"hint": "Is the Next.js app running on localhost:3000?"
|
|
40
|
+
}))
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print(json.dumps({
|
|
44
|
+
"error": f"An error occurred: {str(e)}"
|
|
45
|
+
}))
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
get_data()
|
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
import argparse
|
|
2
|
-
import os
|
|
3
|
-
import sys
|
|
4
|
-
|
|
5
|
-
DEFAULT_MARKER_START = "<oomi-agent-instructions>"
|
|
6
|
-
DEFAULT_MARKER_END = "</oomi-agent-instructions>"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def read_file(path: str) -> str:
|
|
10
|
-
with open(path, "r") as f:
|
|
11
|
-
return f.read()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def write_file(path: str, content: str) -> None:
|
|
15
|
-
with open(path, "w") as f:
|
|
16
|
-
f.write(content)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def get_default_agents_file() -> str:
|
|
20
|
-
# Prefer explicit workspace env, otherwise use repo root if detected
|
|
21
|
-
workspace = os.environ.get("OPENCLAW_WORKSPACE") or os.environ.get("OPENCLAW_HOME")
|
|
22
|
-
if workspace:
|
|
23
|
-
return os.path.join(workspace, "AGENTS.md")
|
|
24
|
-
return os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "AGENTS.md"))
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def build_block(instructions: str, marker_start: str, marker_end: str) -> str:
|
|
28
|
-
return f"{marker_start}\n{instructions.strip()}\n{marker_end}"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def install_block(agents_path: str, block: str, marker_start: str, marker_end: str) -> None:
|
|
32
|
-
if os.path.exists(agents_path):
|
|
33
|
-
existing = read_file(agents_path)
|
|
34
|
-
else:
|
|
35
|
-
existing = ""
|
|
36
|
-
|
|
37
|
-
if marker_start in existing and marker_end in existing:
|
|
38
|
-
# Replace existing block
|
|
39
|
-
pre = existing.split(marker_start)[0]
|
|
40
|
-
post = existing.split(marker_end)[1]
|
|
41
|
-
content = f"{pre}{block}{post}"
|
|
42
|
-
else:
|
|
43
|
-
# Append block
|
|
44
|
-
spacer = "\n\n" if existing and not existing.endswith("\n\n") else ""
|
|
45
|
-
content = f"{existing}{spacer}{block}\n"
|
|
46
|
-
|
|
47
|
-
write_file(agents_path, content)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def main() -> None:
|
|
51
|
-
parser = argparse.ArgumentParser(description="Install Oomi agent instructions into AGENTS.md.")
|
|
52
|
-
parser.add_argument(
|
|
53
|
-
"--agents-file",
|
|
54
|
-
default=get_default_agents_file(),
|
|
55
|
-
help="Path to AGENTS.md (defaults to OPENCLAW_WORKSPACE/AGENTS.md or repo AGENTS.md).",
|
|
56
|
-
)
|
|
57
|
-
parser.add_argument(
|
|
58
|
-
"--instructions-file",
|
|
59
|
-
default=os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "agent_instructions.md")),
|
|
60
|
-
help="Path to instructions markdown file.",
|
|
61
|
-
)
|
|
62
|
-
parser.add_argument("--marker-start", default=DEFAULT_MARKER_START, help="Start marker.")
|
|
63
|
-
parser.add_argument("--marker-end", default=DEFAULT_MARKER_END, help="End marker.")
|
|
64
|
-
args = parser.parse_args()
|
|
65
|
-
|
|
66
|
-
if not os.path.exists(args.instructions_file):
|
|
67
|
-
print(f"Instructions file not found: {args.instructions_file}", file=sys.stderr)
|
|
68
|
-
sys.exit(1)
|
|
69
|
-
|
|
70
|
-
instructions = read_file(args.instructions_file)
|
|
71
|
-
block = build_block(instructions, args.marker_start, args.marker_end)
|
|
72
|
-
install_block(args.agents_file, block, args.marker_start, args.marker_end)
|
|
73
|
-
|
|
74
|
-
print(f"Installed Oomi instructions into {args.agents_file}")
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if __name__ == "__main__":
|
|
78
|
-
main()
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
DEFAULT_MARKER_START = "<oomi-agent-instructions>"
|
|
6
|
+
DEFAULT_MARKER_END = "</oomi-agent-instructions>"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def read_file(path: str) -> str:
|
|
10
|
+
with open(path, "r") as f:
|
|
11
|
+
return f.read()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def write_file(path: str, content: str) -> None:
|
|
15
|
+
with open(path, "w") as f:
|
|
16
|
+
f.write(content)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_default_agents_file() -> str:
|
|
20
|
+
# Prefer explicit workspace env, otherwise use repo root if detected
|
|
21
|
+
workspace = os.environ.get("OPENCLAW_WORKSPACE") or os.environ.get("OPENCLAW_HOME")
|
|
22
|
+
if workspace:
|
|
23
|
+
return os.path.join(workspace, "AGENTS.md")
|
|
24
|
+
return os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "AGENTS.md"))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def build_block(instructions: str, marker_start: str, marker_end: str) -> str:
|
|
28
|
+
return f"{marker_start}\n{instructions.strip()}\n{marker_end}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def install_block(agents_path: str, block: str, marker_start: str, marker_end: str) -> None:
|
|
32
|
+
if os.path.exists(agents_path):
|
|
33
|
+
existing = read_file(agents_path)
|
|
34
|
+
else:
|
|
35
|
+
existing = ""
|
|
36
|
+
|
|
37
|
+
if marker_start in existing and marker_end in existing:
|
|
38
|
+
# Replace existing block
|
|
39
|
+
pre = existing.split(marker_start)[0]
|
|
40
|
+
post = existing.split(marker_end)[1]
|
|
41
|
+
content = f"{pre}{block}{post}"
|
|
42
|
+
else:
|
|
43
|
+
# Append block
|
|
44
|
+
spacer = "\n\n" if existing and not existing.endswith("\n\n") else ""
|
|
45
|
+
content = f"{existing}{spacer}{block}\n"
|
|
46
|
+
|
|
47
|
+
write_file(agents_path, content)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main() -> None:
|
|
51
|
+
parser = argparse.ArgumentParser(description="Install Oomi agent instructions into AGENTS.md.")
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"--agents-file",
|
|
54
|
+
default=get_default_agents_file(),
|
|
55
|
+
help="Path to AGENTS.md (defaults to OPENCLAW_WORKSPACE/AGENTS.md or repo AGENTS.md).",
|
|
56
|
+
)
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"--instructions-file",
|
|
59
|
+
default=os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "agent_instructions.md")),
|
|
60
|
+
help="Path to instructions markdown file.",
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument("--marker-start", default=DEFAULT_MARKER_START, help="Start marker.")
|
|
63
|
+
parser.add_argument("--marker-end", default=DEFAULT_MARKER_END, help="End marker.")
|
|
64
|
+
args = parser.parse_args()
|
|
65
|
+
|
|
66
|
+
if not os.path.exists(args.instructions_file):
|
|
67
|
+
print(f"Instructions file not found: {args.instructions_file}", file=sys.stderr)
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
|
|
70
|
+
instructions = read_file(args.instructions_file)
|
|
71
|
+
block = build_block(instructions, args.marker_start, args.marker_end)
|
|
72
|
+
install_block(args.agents_file, block, args.marker_start, args.marker_end)
|
|
73
|
+
|
|
74
|
+
print(f"Installed Oomi instructions into {args.agents_file}")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
if __name__ == "__main__":
|
|
78
|
+
main()
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
import urllib.request
|
|
2
|
-
import urllib.error
|
|
3
|
-
import json
|
|
4
|
-
import argparse
|
|
5
|
-
import os
|
|
6
|
-
import sys
|
|
7
|
-
|
|
8
|
-
# Load config
|
|
9
|
-
try:
|
|
10
|
-
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.json')
|
|
11
|
-
with open(config_path, 'r') as f:
|
|
12
|
-
config = json.load(f)
|
|
13
|
-
BASE_URL = config.get('api_url', 'http://localhost:3000/api/skill')
|
|
14
|
-
except:
|
|
15
|
-
BASE_URL = 'http://localhost:3000/api/skill'
|
|
16
|
-
|
|
17
|
-
API_URL = f"{BASE_URL}/goal"
|
|
18
|
-
|
|
19
|
-
def send_goal(goal_type, value, message=None):
|
|
20
|
-
payload = {
|
|
21
|
-
"type": goal_type,
|
|
22
|
-
"value": value,
|
|
23
|
-
"message": message
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
try:
|
|
27
|
-
req = urllib.request.Request(
|
|
28
|
-
API_URL,
|
|
29
|
-
data=json.dumps(payload).encode('utf-8'),
|
|
30
|
-
headers={'Content-Type': 'application/json'}
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
with urllib.request.urlopen(req) as response:
|
|
34
|
-
result = json.loads(response.read().decode())
|
|
35
|
-
|
|
36
|
-
print(json.dumps(result, indent=2))
|
|
37
|
-
|
|
38
|
-
except urllib.error.URLError as e:
|
|
39
|
-
print(json.dumps({
|
|
40
|
-
"error": f"Failed to send goal: {str(e)}",
|
|
41
|
-
"hint": "Is the Next.js app running on localhost:3000?"
|
|
42
|
-
}))
|
|
43
|
-
sys.exit(1)
|
|
44
|
-
|
|
45
|
-
if __name__ == "__main__":
|
|
46
|
-
parser = argparse.ArgumentParser(description="Set a goal in Oomi")
|
|
47
|
-
parser.add_argument("--type", required=True, help="Type of goal (e.g. steps, sleep)")
|
|
48
|
-
parser.add_argument("--value", required=True, type=float, help="Goal target value")
|
|
49
|
-
parser.add_argument("--message", help="Optional motivational message")
|
|
50
|
-
|
|
51
|
-
args = parser.parse_args()
|
|
52
|
-
|
|
53
|
-
send_goal(args.type, args.value, args.message)
|
|
1
|
+
import urllib.request
|
|
2
|
+
import urllib.error
|
|
3
|
+
import json
|
|
4
|
+
import argparse
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
# Load config
|
|
9
|
+
try:
|
|
10
|
+
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.json')
|
|
11
|
+
with open(config_path, 'r') as f:
|
|
12
|
+
config = json.load(f)
|
|
13
|
+
BASE_URL = config.get('api_url', 'http://localhost:3000/api/skill')
|
|
14
|
+
except:
|
|
15
|
+
BASE_URL = 'http://localhost:3000/api/skill'
|
|
16
|
+
|
|
17
|
+
API_URL = f"{BASE_URL}/goal"
|
|
18
|
+
|
|
19
|
+
def send_goal(goal_type, value, message=None):
|
|
20
|
+
payload = {
|
|
21
|
+
"type": goal_type,
|
|
22
|
+
"value": value,
|
|
23
|
+
"message": message
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
req = urllib.request.Request(
|
|
28
|
+
API_URL,
|
|
29
|
+
data=json.dumps(payload).encode('utf-8'),
|
|
30
|
+
headers={'Content-Type': 'application/json'}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
with urllib.request.urlopen(req) as response:
|
|
34
|
+
result = json.loads(response.read().decode())
|
|
35
|
+
|
|
36
|
+
print(json.dumps(result, indent=2))
|
|
37
|
+
|
|
38
|
+
except urllib.error.URLError as e:
|
|
39
|
+
print(json.dumps({
|
|
40
|
+
"error": f"Failed to send goal: {str(e)}",
|
|
41
|
+
"hint": "Is the Next.js app running on localhost:3000?"
|
|
42
|
+
}))
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
parser = argparse.ArgumentParser(description="Set a goal in Oomi")
|
|
47
|
+
parser.add_argument("--type", required=True, help="Type of goal (e.g. steps, sleep)")
|
|
48
|
+
parser.add_argument("--value", required=True, type=float, help="Goal target value")
|
|
49
|
+
parser.add_argument("--message", help="Optional motivational message")
|
|
50
|
+
|
|
51
|
+
args = parser.parse_args()
|
|
52
|
+
|
|
53
|
+
send_goal(args.type, args.value, args.message)
|