@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.
Files changed (77) hide show
  1. package/.github/workflows/publish.yml +8 -1
  2. package/docs/CUSTOM_AGENT.md +184 -0
  3. package/docs/GETTING_STARTED.md +3 -0
  4. package/launchd/com.skcapstone.daemon.plist +52 -0
  5. package/launchd/com.skcapstone.memory-compress.plist +45 -0
  6. package/launchd/com.skcapstone.skcomm-heartbeat.plist +33 -0
  7. package/launchd/com.skcapstone.skcomm-queue-drain.plist +34 -0
  8. package/launchd/install-launchd.sh +156 -0
  9. package/package.json +1 -1
  10. package/pyproject.toml +1 -1
  11. package/scripts/archive-sessions.sh +88 -0
  12. package/scripts/install.sh +39 -8
  13. package/scripts/notion-api.py +259 -0
  14. package/scripts/nvidia-proxy.mjs +878 -0
  15. package/scripts/proxy-monitor.sh +89 -0
  16. package/scripts/refresh-anthropic-token.sh +94 -0
  17. package/scripts/skgateway.mjs +856 -0
  18. package/scripts/telegram-catchup-all.sh +136 -0
  19. package/scripts/watch-anthropic-token.sh +117 -0
  20. package/src/skcapstone/__init__.py +1 -1
  21. package/src/skcapstone/_cli_monolith.py +4 -4
  22. package/src/skcapstone/api.py +36 -35
  23. package/src/skcapstone/auction.py +8 -8
  24. package/src/skcapstone/blueprint_registry.py +2 -2
  25. package/src/skcapstone/blueprints/builtins/itil-operations.yaml +40 -0
  26. package/src/skcapstone/brain_first.py +238 -0
  27. package/src/skcapstone/chat.py +4 -4
  28. package/src/skcapstone/cli/__init__.py +2 -0
  29. package/src/skcapstone/cli/agents_spawner.py +5 -2
  30. package/src/skcapstone/cli/chat.py +5 -2
  31. package/src/skcapstone/cli/consciousness.py +5 -2
  32. package/src/skcapstone/cli/daemon.py +116 -41
  33. package/src/skcapstone/cli/itil.py +434 -0
  34. package/src/skcapstone/cli/memory.py +4 -4
  35. package/src/skcapstone/cli/skills_cmd.py +2 -2
  36. package/src/skcapstone/cli/soul.py +5 -2
  37. package/src/skcapstone/cli/status.py +11 -8
  38. package/src/skcapstone/cli/upgrade_cmd.py +7 -4
  39. package/src/skcapstone/cli/watch_cmd.py +9 -6
  40. package/src/skcapstone/config_validator.py +7 -4
  41. package/src/skcapstone/consciousness_config.py +27 -0
  42. package/src/skcapstone/consciousness_loop.py +20 -18
  43. package/src/skcapstone/coordination.py +6 -2
  44. package/src/skcapstone/daemon.py +51 -42
  45. package/src/skcapstone/dashboard.py +8 -8
  46. package/src/skcapstone/defaults/lumina/config/claude-hooks.md +42 -0
  47. package/src/skcapstone/doctor.py +5 -2
  48. package/src/skcapstone/dreaming.py +1440 -0
  49. package/src/skcapstone/emotion_tracker.py +2 -2
  50. package/src/skcapstone/export.py +2 -2
  51. package/src/skcapstone/fuse_mount.py +21 -13
  52. package/src/skcapstone/heartbeat.py +33 -29
  53. package/src/skcapstone/itil.py +1104 -0
  54. package/src/skcapstone/launchd.py +426 -0
  55. package/src/skcapstone/mcp_server.py +306 -4
  56. package/src/skcapstone/mcp_tools/__init__.py +4 -0
  57. package/src/skcapstone/mcp_tools/_helpers.py +2 -2
  58. package/src/skcapstone/mcp_tools/ansible_tools.py +7 -4
  59. package/src/skcapstone/mcp_tools/brain_first_tools.py +90 -0
  60. package/src/skcapstone/mcp_tools/capauth_tools.py +7 -4
  61. package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
  62. package/src/skcapstone/mcp_tools/did_tools.py +9 -6
  63. package/src/skcapstone/mcp_tools/gtd_tools.py +1 -1
  64. package/src/skcapstone/mcp_tools/itil_tools.py +657 -0
  65. package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
  66. package/src/skcapstone/mcp_tools/soul_tools.py +6 -2
  67. package/src/skcapstone/mdns_discovery.py +2 -2
  68. package/src/skcapstone/metrics.py +8 -8
  69. package/src/skcapstone/migrate_memories.py +2 -2
  70. package/src/skcapstone/models.py +14 -0
  71. package/src/skcapstone/onboard.py +137 -14
  72. package/src/skcapstone/peer_directory.py +2 -2
  73. package/src/skcapstone/providers/docker.py +2 -2
  74. package/src/skcapstone/scheduled_tasks.py +107 -0
  75. package/src/skcapstone/service_health.py +83 -4
  76. package/src/skcapstone/sync_watcher.py +2 -2
  77. package/src/skcapstone/systemd.py +17 -0
@@ -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" "$PILLAR/capauth $PARENT/capauth"
114
- install_pkg "skmemory" "" "$PILLAR/skmemory $PARENT/skmemory"
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" "" "$REPO_ROOT"
117
- install_pkg "skchat-sovereign" "all" "$PARENT/skchat"
118
- install_pkg "skseal" "" "$PARENT/skseal"
119
- install_pkg "skskills" "" "$PARENT/skskills"
120
- install_pkg "sksecurity" "" "$PARENT/sksecurity"
121
- install_pkg "skseed" "" "$PILLAR/skseed $PARENT/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()