@smilintux/skcapstone 0.4.6 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +8 -1
- package/docs/CUSTOM_AGENT.md +184 -0
- package/docs/GETTING_STARTED.md +3 -0
- package/launchd/com.skcapstone.daemon.plist +52 -0
- package/launchd/com.skcapstone.memory-compress.plist +45 -0
- package/launchd/com.skcapstone.skcomm-heartbeat.plist +33 -0
- package/launchd/com.skcapstone.skcomm-queue-drain.plist +34 -0
- package/launchd/install-launchd.sh +156 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/archive-sessions.sh +88 -0
- package/scripts/install.sh +39 -8
- package/scripts/notion-api.py +259 -0
- package/scripts/nvidia-proxy.mjs +878 -0
- package/scripts/proxy-monitor.sh +89 -0
- package/scripts/refresh-anthropic-token.sh +94 -0
- package/scripts/skgateway.mjs +856 -0
- package/scripts/telegram-catchup-all.sh +136 -0
- package/scripts/watch-anthropic-token.sh +117 -0
- package/src/skcapstone/__init__.py +1 -1
- package/src/skcapstone/_cli_monolith.py +4 -4
- package/src/skcapstone/api.py +36 -35
- package/src/skcapstone/auction.py +8 -8
- package/src/skcapstone/blueprint_registry.py +2 -2
- package/src/skcapstone/blueprints/builtins/itil-operations.yaml +40 -0
- package/src/skcapstone/brain_first.py +238 -0
- package/src/skcapstone/chat.py +4 -4
- package/src/skcapstone/cli/__init__.py +2 -0
- package/src/skcapstone/cli/agents_spawner.py +5 -2
- package/src/skcapstone/cli/chat.py +5 -2
- package/src/skcapstone/cli/consciousness.py +5 -2
- package/src/skcapstone/cli/daemon.py +116 -41
- package/src/skcapstone/cli/itil.py +434 -0
- package/src/skcapstone/cli/memory.py +4 -4
- package/src/skcapstone/cli/skills_cmd.py +2 -2
- package/src/skcapstone/cli/soul.py +5 -2
- package/src/skcapstone/cli/status.py +11 -8
- package/src/skcapstone/cli/upgrade_cmd.py +7 -4
- package/src/skcapstone/cli/watch_cmd.py +9 -6
- package/src/skcapstone/config_validator.py +7 -4
- package/src/skcapstone/consciousness_config.py +27 -0
- package/src/skcapstone/consciousness_loop.py +20 -18
- package/src/skcapstone/coordination.py +6 -2
- package/src/skcapstone/daemon.py +51 -42
- package/src/skcapstone/dashboard.py +8 -8
- package/src/skcapstone/defaults/lumina/config/claude-hooks.md +42 -0
- package/src/skcapstone/doctor.py +5 -2
- package/src/skcapstone/dreaming.py +1440 -0
- package/src/skcapstone/emotion_tracker.py +2 -2
- package/src/skcapstone/export.py +2 -2
- package/src/skcapstone/fuse_mount.py +21 -13
- package/src/skcapstone/heartbeat.py +33 -29
- package/src/skcapstone/itil.py +1104 -0
- package/src/skcapstone/launchd.py +426 -0
- package/src/skcapstone/mcp_server.py +306 -4
- package/src/skcapstone/mcp_tools/__init__.py +4 -0
- package/src/skcapstone/mcp_tools/_helpers.py +2 -2
- package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
- package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
- package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
- package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
- package/src/skcapstone/mcp_tools/did_tools.py +9 -6
- package/src/skcapstone/mcp_tools/gtd_tools.py +1 -1
- package/src/skcapstone/mcp_tools/itil_tools.py +657 -0
- package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
- package/src/skcapstone/mcp_tools/soul_tools.py +6 -2
- package/src/skcapstone/mdns_discovery.py +2 -2
- package/src/skcapstone/metrics.py +8 -8
- package/src/skcapstone/migrate_memories.py +2 -2
- package/src/skcapstone/models.py +14 -0
- package/src/skcapstone/onboard.py +137 -14
- package/src/skcapstone/peer_directory.py +2 -2
- package/src/skcapstone/providers/docker.py +2 -2
- package/src/skcapstone/scheduled_tasks.py +107 -0
- package/src/skcapstone/service_health.py +83 -4
- package/src/skcapstone/sync_watcher.py +2 -2
- package/src/skcapstone/systemd.py +17 -0
package/scripts/install.sh
CHANGED
|
@@ -110,15 +110,16 @@ PARENT="$(dirname "$REPO_ROOT")"
|
|
|
110
110
|
PILLAR="$PARENT/pillar-repos"
|
|
111
111
|
|
|
112
112
|
# Core packages (in dependency order)
|
|
113
|
-
install_pkg "capauth" "all"
|
|
114
|
-
install_pkg "
|
|
113
|
+
install_pkg "capauth" "all" "$PILLAR/capauth $PARENT/capauth"
|
|
114
|
+
install_pkg "cloud9-protocol" "" "$PILLAR/cloud9 $PARENT/cloud9"
|
|
115
|
+
install_pkg "skmemory" "" "$PILLAR/skmemory $PARENT/skmemory"
|
|
115
116
|
install_pkg "skcomm" "cli,crypto,discovery,api" "$PILLAR/skcomm $PARENT/skcomm"
|
|
116
|
-
install_pkg "skcapstone" ""
|
|
117
|
-
install_pkg "skchat-sovereign" "all"
|
|
118
|
-
install_pkg "skseal" ""
|
|
119
|
-
install_pkg "skskills" ""
|
|
120
|
-
install_pkg "sksecurity" ""
|
|
121
|
-
install_pkg "skseed" ""
|
|
117
|
+
install_pkg "skcapstone" "" "$REPO_ROOT"
|
|
118
|
+
install_pkg "skchat-sovereign" "all" "$PARENT/skchat"
|
|
119
|
+
install_pkg "skseal" "" "$PARENT/skseal"
|
|
120
|
+
install_pkg "skskills" "" "$PARENT/skskills"
|
|
121
|
+
install_pkg "sksecurity" "" "$PARENT/sksecurity $PILLAR/SKSecurity $PARENT/SKSecurity"
|
|
122
|
+
install_pkg "skseed" "" "$PILLAR/skseed $PARENT/skseed"
|
|
122
123
|
|
|
123
124
|
# ---------------------------------------------------------------------------
|
|
124
125
|
# Step 4: Dev tools (optional)
|
|
@@ -184,3 +185,33 @@ echo ""
|
|
|
184
185
|
echo "Commands available: skcomm, skcapstone, capauth, skchat, skseal, skmemory, skskills, sksecurity, skseed"
|
|
185
186
|
echo "Venv location: $SKENV"
|
|
186
187
|
echo "To activate: source $SKENV/bin/activate"
|
|
188
|
+
|
|
189
|
+
# ---------------------------------------------------------------------------
|
|
190
|
+
# macOS: Offer launchd service installation
|
|
191
|
+
# ---------------------------------------------------------------------------
|
|
192
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
193
|
+
echo ""
|
|
194
|
+
echo "=== macOS Auto-Start Services ==="
|
|
195
|
+
echo ""
|
|
196
|
+
echo "SKCapstone can install launchd services so your agent starts"
|
|
197
|
+
echo "automatically at login. You can choose which services to install."
|
|
198
|
+
echo ""
|
|
199
|
+
read -r -p "Install launchd auto-start services? [Y/n] " _LAUNCHD_ANSWER
|
|
200
|
+
_LAUNCHD_ANSWER="${_LAUNCHD_ANSWER:-Y}"
|
|
201
|
+
|
|
202
|
+
if [[ "$_LAUNCHD_ANSWER" =~ ^[Yy] ]]; then
|
|
203
|
+
# Ask for agent name
|
|
204
|
+
_DEFAULT_AGENT="${SKCAPSTONE_AGENT:-sovereign}"
|
|
205
|
+
read -r -p "Agent name [$_DEFAULT_AGENT]: " _AGENT_NAME
|
|
206
|
+
_AGENT_NAME="${_AGENT_NAME:-$_DEFAULT_AGENT}"
|
|
207
|
+
|
|
208
|
+
read -r -p "Start services now? [y/N] " _START_NOW
|
|
209
|
+
if [[ "$_START_NOW" =~ ^[Yy] ]]; then
|
|
210
|
+
"$SKENV/bin/skcapstone" daemon install --agent "$_AGENT_NAME" --start
|
|
211
|
+
else
|
|
212
|
+
"$SKENV/bin/skcapstone" daemon install --agent "$_AGENT_NAME"
|
|
213
|
+
fi
|
|
214
|
+
else
|
|
215
|
+
echo "Skipped. Install later: skcapstone daemon install --agent <name>"
|
|
216
|
+
fi
|
|
217
|
+
fi
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
notion-api — Simple Notion API wrapper for AI agents.
|
|
4
|
+
|
|
5
|
+
Lets agents read and update Notion pages via CLI without needing
|
|
6
|
+
to construct raw curl/JSON. Uses NOTION_API_KEY from environment.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
notion-api read <page-id>
|
|
10
|
+
notion-api append <page-id> <markdown-text>
|
|
11
|
+
notion-api replace <page-id> <markdown-text>
|
|
12
|
+
notion-api add-todo <page-id> <text> [--checked]
|
|
13
|
+
notion-api list-blocks <page-id>
|
|
14
|
+
|
|
15
|
+
Examples:
|
|
16
|
+
notion-api read 31e2be82-a3a1-8178-820c-e6eeb11b15c1
|
|
17
|
+
notion-api append 31e2be82-a3a1-8178-820c-e6eeb11b15c1 "## New Section\nContent here"
|
|
18
|
+
notion-api add-todo 31e2be82-a3a1-8178-820c-e6eeb11b15c1 "Follow up with John"
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import sys
|
|
26
|
+
import urllib.request
|
|
27
|
+
import urllib.error
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
API_KEY = os.environ.get("NOTION_API_KEY", "")
|
|
31
|
+
API_VERSION = "2022-06-28"
|
|
32
|
+
BASE_URL = "https://api.notion.com/v1"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _request(method: str, path: str, body: dict | None = None) -> dict:
|
|
36
|
+
"""Make an authenticated Notion API request."""
|
|
37
|
+
if not API_KEY:
|
|
38
|
+
print("Error: NOTION_API_KEY not set in environment", file=sys.stderr)
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
|
|
41
|
+
url = f"{BASE_URL}{path}"
|
|
42
|
+
data = json.dumps(body).encode() if body else None
|
|
43
|
+
req = urllib.request.Request(url, data=data, method=method)
|
|
44
|
+
req.add_header("Authorization", f"Bearer {API_KEY}")
|
|
45
|
+
req.add_header("Notion-Version", API_VERSION)
|
|
46
|
+
if data:
|
|
47
|
+
req.add_header("Content-Type", "application/json")
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
resp = urllib.request.urlopen(req)
|
|
51
|
+
return json.loads(resp.read())
|
|
52
|
+
except urllib.error.HTTPError as e:
|
|
53
|
+
err = e.read().decode()
|
|
54
|
+
print(f"Notion API error {e.code}: {err}", file=sys.stderr)
|
|
55
|
+
sys.exit(1)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _rich_text(text: str, bold: bool = False) -> list[dict]:
|
|
59
|
+
"""Create a rich_text array from plain text."""
|
|
60
|
+
rt: dict[str, Any] = {"type": "text", "text": {"content": text}}
|
|
61
|
+
if bold:
|
|
62
|
+
rt["annotations"] = {"bold": True}
|
|
63
|
+
return [rt]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _md_to_blocks(markdown: str) -> list[dict]:
|
|
67
|
+
"""Convert simple markdown to Notion blocks.
|
|
68
|
+
|
|
69
|
+
Supports: ## headings, - bullets, [ ] todos, [x] todos, plain paragraphs.
|
|
70
|
+
"""
|
|
71
|
+
blocks: list[dict] = []
|
|
72
|
+
for line in markdown.strip().split("\n"):
|
|
73
|
+
stripped = line.strip()
|
|
74
|
+
if not stripped:
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
if stripped.startswith("### "):
|
|
78
|
+
blocks.append({
|
|
79
|
+
"object": "block",
|
|
80
|
+
"type": "heading_3",
|
|
81
|
+
"heading_3": {"rich_text": _rich_text(stripped[4:])}
|
|
82
|
+
})
|
|
83
|
+
elif stripped.startswith("## "):
|
|
84
|
+
blocks.append({
|
|
85
|
+
"object": "block",
|
|
86
|
+
"type": "heading_2",
|
|
87
|
+
"heading_2": {"rich_text": _rich_text(stripped[3:])}
|
|
88
|
+
})
|
|
89
|
+
elif stripped.startswith("# "):
|
|
90
|
+
blocks.append({
|
|
91
|
+
"object": "block",
|
|
92
|
+
"type": "heading_1",
|
|
93
|
+
"heading_1": {"rich_text": _rich_text(stripped[2:])}
|
|
94
|
+
})
|
|
95
|
+
elif stripped.startswith("- [x] ") or stripped.startswith("- [X] "):
|
|
96
|
+
blocks.append({
|
|
97
|
+
"object": "block",
|
|
98
|
+
"type": "to_do",
|
|
99
|
+
"to_do": {"rich_text": _rich_text(stripped[6:]), "checked": True}
|
|
100
|
+
})
|
|
101
|
+
elif stripped.startswith("- [ ] "):
|
|
102
|
+
blocks.append({
|
|
103
|
+
"object": "block",
|
|
104
|
+
"type": "to_do",
|
|
105
|
+
"to_do": {"rich_text": _rich_text(stripped[6:]), "checked": False}
|
|
106
|
+
})
|
|
107
|
+
elif stripped.startswith("- ") or stripped.startswith("* "):
|
|
108
|
+
blocks.append({
|
|
109
|
+
"object": "block",
|
|
110
|
+
"type": "bulleted_list_item",
|
|
111
|
+
"bulleted_list_item": {"rich_text": _rich_text(stripped[2:])}
|
|
112
|
+
})
|
|
113
|
+
elif stripped.startswith("---"):
|
|
114
|
+
blocks.append({"object": "block", "type": "divider", "divider": {}})
|
|
115
|
+
else:
|
|
116
|
+
blocks.append({
|
|
117
|
+
"object": "block",
|
|
118
|
+
"type": "paragraph",
|
|
119
|
+
"paragraph": {"rich_text": _rich_text(stripped)}
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
return blocks
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def cmd_read(page_id: str) -> None:
|
|
126
|
+
"""Read a page's properties and content."""
|
|
127
|
+
page = _request("GET", f"/pages/{page_id}")
|
|
128
|
+
title = ""
|
|
129
|
+
for prop in page.get("properties", {}).values():
|
|
130
|
+
if prop.get("type") == "title":
|
|
131
|
+
title = "".join(t.get("plain_text", "") for t in prop.get("title", []))
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
print(f"Page: {title}")
|
|
135
|
+
print(f"URL: {page.get('url', '')}")
|
|
136
|
+
print(f"Last edited: {page.get('last_edited_time', '')}")
|
|
137
|
+
print()
|
|
138
|
+
|
|
139
|
+
# Get blocks
|
|
140
|
+
blocks = _request("GET", f"/blocks/{page_id}/children?page_size=100")
|
|
141
|
+
for block in blocks.get("results", []):
|
|
142
|
+
btype = block["type"]
|
|
143
|
+
if btype == "divider":
|
|
144
|
+
print("---")
|
|
145
|
+
elif btype == "child_database":
|
|
146
|
+
print(f"[Database: {block['child_database'].get('title', '')}]")
|
|
147
|
+
else:
|
|
148
|
+
content = block.get(btype, {})
|
|
149
|
+
rt = content.get("rich_text", [])
|
|
150
|
+
text = "".join(t.get("plain_text", "") for t in rt)
|
|
151
|
+
prefix = ""
|
|
152
|
+
if btype == "heading_1":
|
|
153
|
+
prefix = "# "
|
|
154
|
+
elif btype == "heading_2":
|
|
155
|
+
prefix = "## "
|
|
156
|
+
elif btype == "heading_3":
|
|
157
|
+
prefix = "### "
|
|
158
|
+
elif btype == "bulleted_list_item":
|
|
159
|
+
prefix = "- "
|
|
160
|
+
elif btype == "numbered_list_item":
|
|
161
|
+
prefix = "1. "
|
|
162
|
+
elif btype == "to_do":
|
|
163
|
+
checked = content.get("checked", False)
|
|
164
|
+
prefix = "[x] " if checked else "[ ] "
|
|
165
|
+
elif btype == "callout":
|
|
166
|
+
icon = content.get("icon", {}).get("emoji", "")
|
|
167
|
+
prefix = f"{icon} " if icon else "> "
|
|
168
|
+
print(f"{prefix}{text}")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def cmd_list_blocks(page_id: str) -> None:
|
|
172
|
+
"""List all block IDs and types on a page."""
|
|
173
|
+
blocks = _request("GET", f"/blocks/{page_id}/children?page_size=100")
|
|
174
|
+
for block in blocks.get("results", []):
|
|
175
|
+
btype = block["type"]
|
|
176
|
+
rt = block.get(btype, {}).get("rich_text", [])
|
|
177
|
+
preview = "".join(t.get("plain_text", "") for t in rt)[:60]
|
|
178
|
+
print(f"{block['id']} {btype:20s} {preview}")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def cmd_append(page_id: str, markdown: str) -> None:
|
|
182
|
+
"""Append markdown content as new blocks to a page."""
|
|
183
|
+
blocks = _md_to_blocks(markdown)
|
|
184
|
+
if not blocks:
|
|
185
|
+
print("No content to append.")
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
result = _request("PATCH", f"/blocks/{page_id}/children", {"children": blocks})
|
|
189
|
+
count = len(result.get("results", []))
|
|
190
|
+
print(f"Appended {count} blocks to page.")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def cmd_replace(page_id: str, markdown: str) -> None:
|
|
194
|
+
"""Replace all content on a page with new markdown."""
|
|
195
|
+
# Delete existing blocks
|
|
196
|
+
existing = _request("GET", f"/blocks/{page_id}/children?page_size=100")
|
|
197
|
+
for block in existing.get("results", []):
|
|
198
|
+
if block["type"] != "child_database": # Don't delete databases
|
|
199
|
+
try:
|
|
200
|
+
_request("DELETE", f"/blocks/{block['id']}")
|
|
201
|
+
except SystemExit:
|
|
202
|
+
pass # Skip blocks that can't be deleted
|
|
203
|
+
|
|
204
|
+
# Append new content
|
|
205
|
+
cmd_append(page_id, markdown)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def cmd_add_todo(page_id: str, text: str, checked: bool = False) -> None:
|
|
209
|
+
"""Add a single todo item to a page."""
|
|
210
|
+
blocks = [{
|
|
211
|
+
"object": "block",
|
|
212
|
+
"type": "to_do",
|
|
213
|
+
"to_do": {"rich_text": _rich_text(text), "checked": checked}
|
|
214
|
+
}]
|
|
215
|
+
result = _request("PATCH", f"/blocks/{page_id}/children", {"children": blocks})
|
|
216
|
+
status = "checked" if checked else "unchecked"
|
|
217
|
+
print(f"Added todo ({status}): {text}")
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def main() -> None:
|
|
221
|
+
if len(sys.argv) < 3:
|
|
222
|
+
print(__doc__)
|
|
223
|
+
sys.exit(1)
|
|
224
|
+
|
|
225
|
+
cmd = sys.argv[1]
|
|
226
|
+
page_id = sys.argv[2]
|
|
227
|
+
|
|
228
|
+
if cmd == "read":
|
|
229
|
+
cmd_read(page_id)
|
|
230
|
+
elif cmd == "list-blocks":
|
|
231
|
+
cmd_list_blocks(page_id)
|
|
232
|
+
elif cmd == "append":
|
|
233
|
+
if len(sys.argv) < 4:
|
|
234
|
+
# Read from stdin
|
|
235
|
+
text = sys.stdin.read()
|
|
236
|
+
else:
|
|
237
|
+
text = sys.argv[3]
|
|
238
|
+
cmd_append(page_id, text)
|
|
239
|
+
elif cmd == "replace":
|
|
240
|
+
if len(sys.argv) < 4:
|
|
241
|
+
text = sys.stdin.read()
|
|
242
|
+
else:
|
|
243
|
+
text = sys.argv[3]
|
|
244
|
+
cmd_replace(page_id, text)
|
|
245
|
+
elif cmd == "add-todo":
|
|
246
|
+
if len(sys.argv) < 4:
|
|
247
|
+
print("Usage: notion-api add-todo <page-id> <text> [--checked]")
|
|
248
|
+
sys.exit(1)
|
|
249
|
+
text = sys.argv[3]
|
|
250
|
+
checked = "--checked" in sys.argv
|
|
251
|
+
cmd_add_todo(page_id, text, checked)
|
|
252
|
+
else:
|
|
253
|
+
print(f"Unknown command: {cmd}")
|
|
254
|
+
print(__doc__)
|
|
255
|
+
sys.exit(1)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
if __name__ == "__main__":
|
|
259
|
+
main()
|