@kohala/devkit 0.1.0 → 0.1.2
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/CHANGELOG.md +58 -0
- package/CODE_OF_CONDUCT.md +37 -0
- package/CONTRIBUTING.md +53 -0
- package/README.md +26 -3
- package/SECURITY.md +28 -0
- package/dist/cli/index.js +248 -9
- package/dist/cli/index.js.map +1 -1
- package/docs/DEPLOY.md +48 -0
- package/docs/EMULATOR.md +93 -0
- package/docs/MANIFEST.md +84 -0
- package/docs/MEMORY.md +81 -0
- package/docs/QUICKSTART.md +70 -0
- package/examples/llm-notes/README.md +18 -0
- package/examples/llm-notes/kohala.json +16 -0
- package/examples/llm-notes/skills/task.md +7 -0
- package/examples/rss-digest/README.md +13 -0
- package/examples/rss-digest/kohala.json +17 -0
- package/examples/rss-digest/skills/_tools.py +125 -0
- package/examples/rss-digest/skills/main.py +49 -0
- package/examples/weather-logger/README.md +12 -0
- package/examples/weather-logger/kohala.json +19 -0
- package/examples/weather-logger/skills/_tools.py +125 -0
- package/examples/weather-logger/skills/main.py +39 -0
- package/package.json +7 -2
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Kohala tool SDK (local twin).
|
|
2
|
+
|
|
3
|
+
Your skill script talks to the Kohala runtime through these helpers. Locally
|
|
4
|
+
they call the emulator over a loopback RPC endpoint; on the hosted platform
|
|
5
|
+
the same functions talk to the real runtime. Your script does not change.
|
|
6
|
+
|
|
7
|
+
Uses only the Python standard library — no pip installs needed.
|
|
8
|
+
|
|
9
|
+
Available tools (each must also be listed in kohala.json -> toolAllowlist):
|
|
10
|
+
|
|
11
|
+
s3_put(key, body, category=None) store text in agent memory
|
|
12
|
+
s3_get(key_or_id) fetch a memory asset
|
|
13
|
+
s3_list(prefix=None, limit=None) list active memory assets
|
|
14
|
+
s3_delete(key_or_id) remove + deactivate an asset
|
|
15
|
+
http_post_json(url, body, headers=None) POST JSON to an external API
|
|
16
|
+
llm_complete(prompt, model=None) complete text with YOUR OWN LLM key
|
|
17
|
+
notify_send(channel, message) send a notification (trace, locally)
|
|
18
|
+
metrics_record(name, value, tags=None) record a metric (trace, locally)
|
|
19
|
+
|
|
20
|
+
Every helper raises KohalaToolError on failure — including TOOL_DENIED when
|
|
21
|
+
the tool is not in your allowlist, and PER_RUN_TOKEN_CAP when an LLM call
|
|
22
|
+
would cross your per-run token cap. Errors are loud on purpose.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import json
|
|
26
|
+
import os
|
|
27
|
+
import urllib.request
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class KohalaToolError(Exception):
|
|
31
|
+
"""A tool call failed. `code` is the platform's machine-readable code."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, code, message):
|
|
34
|
+
super().__init__(f"{code}: {message}")
|
|
35
|
+
self.code = code
|
|
36
|
+
self.message = message
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _rpc(tool, args):
|
|
40
|
+
rpc_url = os.environ.get("KOHALA_RPC_URL")
|
|
41
|
+
if not rpc_url:
|
|
42
|
+
raise KohalaToolError(
|
|
43
|
+
"NO_RUNTIME",
|
|
44
|
+
"KOHALA_RPC_URL is not set. Run this script via `kohala run <agent> --local`, "
|
|
45
|
+
"not directly with python.",
|
|
46
|
+
)
|
|
47
|
+
payload = json.dumps({"tool": tool, "args": args}).encode("utf-8")
|
|
48
|
+
request = urllib.request.Request(
|
|
49
|
+
rpc_url,
|
|
50
|
+
data=payload,
|
|
51
|
+
headers={"Content-Type": "application/json"},
|
|
52
|
+
method="POST",
|
|
53
|
+
)
|
|
54
|
+
with urllib.request.urlopen(request) as response:
|
|
55
|
+
body = json.loads(response.read().decode("utf-8"))
|
|
56
|
+
if not body.get("ok"):
|
|
57
|
+
error = body.get("error") or {}
|
|
58
|
+
raise KohalaToolError(error.get("code", "UNKNOWN"), error.get("message", "tool call failed"))
|
|
59
|
+
return body.get("result")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def s3_put(key, body, category=None):
|
|
63
|
+
args = {"key": key, "body": body}
|
|
64
|
+
if category is not None:
|
|
65
|
+
args["category"] = category
|
|
66
|
+
return _rpc("s3.put", args)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def s3_get(key_or_id):
|
|
70
|
+
return _rpc("s3.get", {"keyOrId": key_or_id})
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def s3_list(prefix=None, limit=None):
|
|
74
|
+
args = {}
|
|
75
|
+
if prefix is not None:
|
|
76
|
+
args["prefix"] = prefix
|
|
77
|
+
if limit is not None:
|
|
78
|
+
args["limit"] = limit
|
|
79
|
+
return _rpc("s3.list", args)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def s3_delete(key_or_id):
|
|
83
|
+
return _rpc("s3.delete", {"keyOrId": key_or_id})
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def http_post_json(url, body, headers=None):
|
|
87
|
+
args = {"url": url, "body": body}
|
|
88
|
+
if headers is not None:
|
|
89
|
+
args["headers"] = headers
|
|
90
|
+
return _rpc("http.post_json", args)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def llm_complete(prompt, model=None):
|
|
94
|
+
args = {"prompt": prompt}
|
|
95
|
+
if model is not None:
|
|
96
|
+
args["model"] = model
|
|
97
|
+
return _rpc("llm.complete", args)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def notify_send(channel, message):
|
|
101
|
+
return _rpc("notify.send", {"channel": channel, "message": message})
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def metrics_record(name, value, tags=None):
|
|
105
|
+
args = {"name": name, "value": value}
|
|
106
|
+
if tags is not None:
|
|
107
|
+
args["tags"] = tags
|
|
108
|
+
return _rpc("metrics.record", args)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def run_context():
|
|
112
|
+
"""Info about the current shift, including repair-loop state.
|
|
113
|
+
|
|
114
|
+
Returns a dict with:
|
|
115
|
+
agent the agent name
|
|
116
|
+
run_id unique id of this shift
|
|
117
|
+
repair_attempt 0 on the first try, 1..2 on repair attempts
|
|
118
|
+
validator_feedback why validators failed last attempt (empty on first try)
|
|
119
|
+
"""
|
|
120
|
+
return {
|
|
121
|
+
"agent": os.environ.get("KOHALA_AGENT", ""),
|
|
122
|
+
"run_id": os.environ.get("KOHALA_RUN_ID", ""),
|
|
123
|
+
"repair_attempt": int(os.environ.get("KOHALA_REPAIR_ATTEMPT", "0")),
|
|
124
|
+
"validator_feedback": os.environ.get("KOHALA_VALIDATOR_FEEDBACK", ""),
|
|
125
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""weather-logger — fetch current conditions and store them in memory.
|
|
2
|
+
|
|
3
|
+
Demonstrates: http.post_json, s3.put, a freshness validator, and an
|
|
4
|
+
invariant validator (the output must mention "temperature").
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from _tools import s3_put, http_post_json, metrics_record, KohalaToolError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main():
|
|
14
|
+
# Open-Meteo is free and needs no API key. (POST works for parity with
|
|
15
|
+
# the http.post_json tool; the API ignores the empty body.)
|
|
16
|
+
response = http_post_json(
|
|
17
|
+
"https://api.open-meteo.com/v1/forecast"
|
|
18
|
+
"?latitude=21.31&longitude=-157.86¤t=temperature_2m,weather_code",
|
|
19
|
+
{},
|
|
20
|
+
)
|
|
21
|
+
if not response["ok"]:
|
|
22
|
+
print(f"weather API returned {response['status']}", file=sys.stderr)
|
|
23
|
+
raise SystemExit(1)
|
|
24
|
+
|
|
25
|
+
current = (response["json"] or {}).get("current", {})
|
|
26
|
+
temperature = current.get("temperature_2m")
|
|
27
|
+
if temperature is None:
|
|
28
|
+
print("weather API response had no temperature", file=sys.stderr)
|
|
29
|
+
raise SystemExit(1)
|
|
30
|
+
|
|
31
|
+
report = json.dumps({"temperature_c": temperature, "raw": current})
|
|
32
|
+
s3_put("weather/latest", report)
|
|
33
|
+
metrics_record("temperature_c", float(temperature))
|
|
34
|
+
|
|
35
|
+
print(f"stored weather/latest: temperature {temperature}°C")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
main()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kohala/devkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Open-source CLI, local agent emulator, and open MCP memory server for Kohala agents. Build and run agents entirely on your own machine — then push the same agent to Kohala when you want it hosted.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -11,9 +11,14 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
13
|
"templates",
|
|
14
|
+
"docs",
|
|
15
|
+
"examples",
|
|
14
16
|
"README.md",
|
|
15
17
|
"LICENSE",
|
|
16
|
-
"CHANGELOG.md"
|
|
18
|
+
"CHANGELOG.md",
|
|
19
|
+
"CONTRIBUTING.md",
|
|
20
|
+
"CODE_OF_CONDUCT.md",
|
|
21
|
+
"SECURITY.md"
|
|
17
22
|
],
|
|
18
23
|
"engines": {
|
|
19
24
|
"node": ">=20"
|