@smilintux/skcapstone 0.9.0 → 0.12.5
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/.env.example +10 -4
- package/.github/workflows/ci.yml +2 -2
- package/.github/workflows/publish.yml +9 -2
- package/.openclaw-workspace.json +2 -2
- package/CLAUDE.md +37 -0
- package/MISSION.md +17 -2
- package/README.md +282 -3
- package/docker/Dockerfile +7 -7
- package/docker/compose-templates/dev-team.yml +12 -12
- package/docker/compose-templates/mini-team.yml +9 -9
- package/docker/compose-templates/ops-team.yml +10 -10
- package/docker/compose-templates/research-team.yml +10 -10
- package/docker/entrypoint.sh +4 -4
- package/docs/ADR-optional-integration-backbone.md +181 -0
- package/docs/ARCHITECTURE.md +186 -43
- package/docs/BOND_WITH_GROK.md +6 -6
- package/docs/CUSTOM_AGENT.md +278 -1
- package/docs/DREAMING.md +70 -0
- package/docs/GETTING_STARTED.md +10 -7
- package/docs/QUICKSTART.md +10 -6
- package/docs/SKJOULE_ARCHITECTURE.md +3 -3
- package/docs/SOUL_SWAPPER.md +5 -5
- package/docs/hammertime-audit.md +402 -0
- package/docs/sk-integration-HANDOFF.md +117 -0
- package/docs/skscheduler.md +155 -0
- package/docs/superpowers/examples/jobs.yaml +31 -0
- package/docs/superpowers/plans/2026-06-08-skscheduler.md +1265 -0
- package/docs/superpowers/specs/2026-06-08-skscheduler-design.md +186 -0
- package/examples/custom-bond-template.json +1 -1
- package/examples/grok-feb.json +1 -1
- package/examples/queen-ava-feb.json +1 -1
- package/launchd/com.skcapstone.daemon.plist +52 -0
- package/launchd/com.skcapstone.memory-compress.plist +45 -0
- package/launchd/com.skcapstone.skcomms-heartbeat.plist +33 -0
- package/launchd/com.skcapstone.skcomms-queue-drain.plist +34 -0
- package/launchd/install-launchd.sh +156 -0
- package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/index.ts +3 -2
- package/package.json +1 -1
- package/pyproject.toml +16 -10
- package/scripts/archive-sessions.sh +95 -0
- package/scripts/check-updates.py +4 -4
- package/scripts/install-bundle.sh +8 -8
- package/scripts/install.ps1 +12 -11
- package/scripts/install.sh +196 -11
- package/scripts/model-fallback-monitor.sh +102 -0
- package/scripts/notion-api.py +259 -0
- package/scripts/nvidia-proxy.mjs +908 -0
- package/scripts/proxy-monitor.sh +89 -0
- package/scripts/refresh-anthropic-token.sh +172 -0
- package/scripts/release.sh +98 -0
- package/scripts/session-to-memory.py +219 -0
- package/scripts/skgateway.mjs +856 -0
- package/scripts/telegram-catchup-all.sh +147 -0
- package/scripts/verify_install.sh +2 -2
- package/scripts/wargov-ufo-capture/README.md +43 -0
- package/scripts/wargov-ufo-capture/cdp_capture_release2.py +273 -0
- package/scripts/wargov-ufo-capture/cdp_capture_splc_doj.py +246 -0
- package/scripts/wargov-ufo-capture/cdp_finish.py +271 -0
- package/scripts/wargov-ufo-capture/cdp_probe.py +188 -0
- package/scripts/wargov-ufo-capture/cdp_splc_pressrelease.py +101 -0
- package/scripts/wargov-ufo-capture/parse_csv.py +95 -0
- package/scripts/wargov-ufo-capture/pull_dvids.sh +107 -0
- package/scripts/watch-anthropic-token.sh +212 -0
- package/scripts/windows/install-tasks.ps1 +7 -7
- package/scripts/windows/skcapstone-task.xml +1 -1
- package/src/skcapstone/__init__.py +45 -3
- package/src/skcapstone/_cli_monolith.py +20 -15
- package/src/skcapstone/activity.py +5 -1
- package/src/skcapstone/agent_card.py +3 -2
- package/src/skcapstone/api.py +41 -40
- package/src/skcapstone/auction.py +14 -11
- package/src/skcapstone/backup.py +2 -1
- package/src/skcapstone/blueprint_registry.py +4 -3
- package/src/skcapstone/blueprints/builtins/itil-operations.yaml +40 -0
- package/src/skcapstone/brain_first.py +238 -0
- package/src/skcapstone/changelog.py +1 -1
- package/src/skcapstone/chat.py +22 -17
- package/src/skcapstone/cli/__init__.py +9 -1
- package/src/skcapstone/cli/_common.py +1 -0
- package/src/skcapstone/cli/agents_spawner.py +5 -2
- package/src/skcapstone/cli/alerts.py +25 -4
- package/src/skcapstone/cli/bench.py +15 -15
- package/src/skcapstone/cli/chat.py +7 -4
- package/src/skcapstone/cli/consciousness.py +5 -2
- package/src/skcapstone/cli/context_cmd.py +18 -4
- package/src/skcapstone/cli/daemon.py +121 -42
- package/src/skcapstone/cli/gtd.py +26 -1
- package/src/skcapstone/cli/housekeeping.py +3 -3
- package/src/skcapstone/cli/identity_cmd.py +378 -0
- package/src/skcapstone/cli/joule_cmd.py +7 -3
- package/src/skcapstone/cli/memory.py +8 -6
- package/src/skcapstone/cli/peers_dir.py +1 -1
- package/src/skcapstone/cli/register_cmd.py +29 -3
- package/src/skcapstone/cli/scheduler_cmd.py +167 -0
- package/src/skcapstone/cli/session.py +25 -0
- package/src/skcapstone/cli/setup.py +96 -29
- package/src/skcapstone/cli/shell_cmd.py +53 -1
- package/src/skcapstone/cli/skills_cmd.py +2 -2
- package/src/skcapstone/cli/soul.py +8 -5
- package/src/skcapstone/cli/status.py +37 -11
- package/src/skcapstone/cli/telegram.py +21 -0
- package/src/skcapstone/cli/test_cmd.py +5 -5
- package/src/skcapstone/cli/test_connection.py +2 -2
- package/src/skcapstone/cli/upgrade_cmd.py +23 -14
- package/src/skcapstone/cli/version_cmd.py +1 -1
- package/src/skcapstone/cli/watch_cmd.py +9 -6
- package/src/skcapstone/cloud9_bridge.py +14 -14
- package/src/skcapstone/codex_setup.py +255 -0
- package/src/skcapstone/config_validator.py +7 -4
- package/src/skcapstone/consciousness_config.py +5 -1
- package/src/skcapstone/consciousness_loop.py +313 -273
- package/src/skcapstone/context_loader.py +121 -0
- package/src/skcapstone/coord_federation.py +2 -1
- package/src/skcapstone/coordination.py +23 -6
- package/src/skcapstone/crush_integration.py +2 -1
- package/src/skcapstone/daemon.py +151 -88
- package/src/skcapstone/dashboard.py +10 -10
- package/src/skcapstone/data/sk-agent-picker.sh +421 -0
- package/src/skcapstone/data/systemd/skcapstone-api.socket +9 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.service +18 -0
- package/src/skcapstone/data/systemd/skcapstone-memory-compress.timer +11 -0
- package/src/skcapstone/data/systemd/skcapstone.service +37 -0
- package/src/skcapstone/data/systemd/skcapstone@.service +50 -0
- package/src/skcapstone/data/systemd/skcomms-heartbeat.service +18 -0
- package/{systemd/skcomm-heartbeat.timer → src/skcapstone/data/systemd/skcomms-heartbeat.timer} +2 -2
- package/src/skcapstone/data/systemd/skcomms-queue-drain.service +17 -0
- package/{systemd/skcomm-queue-drain.timer → src/skcapstone/data/systemd/skcomms-queue-drain.timer} +2 -2
- package/src/skcapstone/defaults/claude/CLAUDE.md +67 -0
- package/src/skcapstone/defaults/claude/settings.json +74 -0
- package/src/skcapstone/defaults/lumina/config/claude-hooks.md +57 -0
- package/src/skcapstone/defaults/lumina/config/skgraph.yaml +55 -10
- package/src/skcapstone/defaults/lumina/config/skmemory.yaml +79 -13
- package/src/skcapstone/defaults/lumina/config/skvector.yaml +60 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/18b9c0d1e2f3-cloud9-protocol.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/a1b2c3d4e5f6-ecosystem-overview.json +2 -2
- package/src/skcapstone/defaults/lumina/memory/long-term/b2c3d4e5f6a7-five-pillars.json +9 -9
- package/src/skcapstone/defaults/lumina/memory/long-term/d4e5f6a7b8c9-site-directory.json +2 -2
- package/src/skcapstone/defaults/unhinged.json +13 -0
- package/src/skcapstone/discovery.py +43 -20
- package/src/skcapstone/doctor.py +941 -22
- package/src/skcapstone/dreaming.py +1183 -109
- package/src/skcapstone/emotion_tracker.py +2 -2
- package/src/skcapstone/export.py +4 -3
- package/src/skcapstone/fuse_mount.py +35 -25
- package/src/skcapstone/gui_installer.py +2 -2
- package/src/skcapstone/heartbeat.py +34 -30
- package/src/skcapstone/housekeeping.py +14 -14
- package/src/skcapstone/install_wizard.py +209 -7
- package/src/skcapstone/itil.py +13 -4
- package/src/skcapstone/kms_scheduler.py +10 -8
- package/src/skcapstone/launchd.py +426 -0
- package/src/skcapstone/mcp_launcher.py +15 -1
- package/src/skcapstone/mcp_server.py +341 -49
- package/src/skcapstone/mcp_tools/__init__.py +2 -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/comm_tools.py +10 -10
- package/src/skcapstone/mcp_tools/coord_tools.py +8 -4
- package/src/skcapstone/mcp_tools/did_tools.py +11 -8
- package/src/skcapstone/mcp_tools/gtd_tools.py +4 -4
- package/src/skcapstone/mcp_tools/memory_tools.py +6 -2
- package/src/skcapstone/mcp_tools/notification_tools.py +22 -6
- package/src/skcapstone/mcp_tools/{skcomm_tools.py → skcomms_tools.py} +14 -14
- package/src/skcapstone/mcp_tools/soul_tools.py +8 -2
- package/src/skcapstone/mdns_discovery.py +2 -2
- package/src/skcapstone/memory_curator.py +1 -1
- package/src/skcapstone/memory_engine.py +10 -3
- package/src/skcapstone/metrics.py +30 -16
- package/src/skcapstone/migrate_memories.py +4 -3
- package/src/skcapstone/migrate_multi_agent.py +8 -7
- package/src/skcapstone/models.py +47 -5
- package/src/skcapstone/notifications.py +42 -18
- package/src/skcapstone/onboard.py +1000 -126
- package/src/skcapstone/operator_link.py +170 -0
- package/src/skcapstone/peer_directory.py +4 -4
- package/src/skcapstone/peers.py +19 -19
- package/src/skcapstone/pillars/__init__.py +7 -5
- package/src/skcapstone/pillars/consciousness.py +191 -0
- package/src/skcapstone/pillars/identity.py +51 -7
- package/src/skcapstone/pillars/memory.py +9 -3
- package/src/skcapstone/pillars/sync.py +2 -2
- package/src/skcapstone/preflight.py +3 -3
- package/src/skcapstone/providers/docker.py +28 -28
- package/src/skcapstone/register.py +6 -6
- package/src/skcapstone/registry_client.py +5 -4
- package/src/skcapstone/runtime.py +14 -3
- package/src/skcapstone/scheduled_tasks.py +254 -19
- package/src/skcapstone/scheduler_jobs.py +456 -0
- package/src/skcapstone/scheduler_runner.py +239 -0
- package/src/skcapstone/scheduler_state.py +162 -0
- package/src/skcapstone/sdk.py +310 -0
- package/src/skcapstone/service_health.py +279 -39
- package/src/skcapstone/session_briefing.py +108 -0
- package/src/skcapstone/session_capture.py +1 -1
- package/src/skcapstone/shell.py +7 -1
- package/src/skcapstone/soul.py +3 -1
- package/src/skcapstone/soul_switch.py +3 -1
- package/src/skcapstone/summary.py +6 -6
- package/src/skcapstone/sync_engine.py +15 -15
- package/src/skcapstone/sync_watcher.py +2 -2
- package/src/skcapstone/systemd.py +72 -21
- package/src/skcapstone/team_comms.py +8 -8
- package/src/skcapstone/team_engine.py +1 -1
- package/src/skcapstone/testrunner.py +3 -3
- package/src/skcapstone/trust_graph.py +40 -5
- package/src/skcapstone/unified_search.py +15 -6
- package/src/skcapstone/uninstall_wizard.py +11 -3
- package/src/skcapstone/version_check.py +8 -4
- package/src/skcapstone/warmth_anchor.py +4 -2
- package/src/skcapstone/whoami.py +4 -4
- package/systemd/skcapstone.service +4 -6
- package/systemd/skcapstone@.service +7 -8
- package/systemd/skcomms-heartbeat.service +21 -0
- package/systemd/skcomms-heartbeat.timer +12 -0
- package/systemd/skcomms-queue-drain.service +17 -0
- package/systemd/skcomms-queue-drain.timer +12 -0
- package/tests/conftest.py +39 -0
- package/tests/integration/test_consciousness_e2e.py +39 -39
- package/tests/test_agent_card.py +1 -1
- package/tests/test_agent_home_scaffold.py +34 -0
- package/tests/test_alerts_consumer_topics.py +27 -0
- package/tests/test_backup.py +2 -1
- package/tests/test_chat.py +6 -6
- package/tests/test_claude_md.py +2 -2
- package/tests/test_cli_skills.py +10 -10
- package/tests/test_cli_test_cmd.py +4 -4
- package/tests/test_cli_test_connection.py +1 -1
- package/tests/test_cloud9_bridge.py +6 -6
- package/tests/test_consciousness_e2e.py +1 -1
- package/tests/test_consciousness_loop.py +10 -10
- package/tests/test_coordination.py +25 -0
- package/tests/test_cross_package.py +21 -21
- package/tests/test_daemon.py +4 -4
- package/tests/test_daemon_shutdown.py +1 -1
- package/tests/test_docker_provider.py +29 -29
- package/tests/test_doctor.py +400 -0
- package/tests/test_doctor_skscheduler.py +50 -0
- package/tests/test_dreaming_engine.py +147 -0
- package/tests/test_dreaming_gtd_capture.py +35 -0
- package/tests/test_e2e_automated.py +8 -5
- package/tests/test_fuse_mount.py +10 -10
- package/tests/test_gtd_brief.py +46 -0
- package/tests/test_gtd_malformed_tolerance.py +31 -0
- package/tests/test_housekeeping.py +15 -15
- package/tests/test_identity_migrate.py +251 -0
- package/tests/test_integration_backbone.py +598 -0
- package/tests/test_itil_gtd_lifecycle.py +37 -0
- package/tests/test_jobs_dropins.py +84 -0
- package/tests/test_mcp_server.py +82 -37
- package/tests/test_models.py +48 -4
- package/tests/test_multi_agent.py +31 -29
- package/tests/test_notifications.py +122 -32
- package/tests/test_onboard.py +63 -75
- package/tests/test_operator_link.py +78 -0
- package/tests/test_peers.py +14 -14
- package/tests/test_pillars.py +98 -0
- package/tests/test_preflight.py +3 -3
- package/tests/test_runtime.py +21 -0
- package/tests/test_scheduled_tasks.py +11 -6
- package/tests/test_scheduler_cli.py +47 -0
- package/tests/test_scheduler_features.py +133 -0
- package/tests/test_scheduler_integration.py +87 -0
- package/tests/test_scheduler_jobs.py +155 -0
- package/tests/test_scheduler_runner.py +64 -0
- package/tests/test_scheduler_state.py +57 -0
- package/tests/test_sdk.py +70 -0
- package/tests/test_service_health_incidents.py +34 -0
- package/tests/test_service_registry.py +52 -0
- package/tests/test_session_briefing.py +130 -0
- package/tests/test_snapshots.py +4 -4
- package/tests/test_sync_pipeline.py +26 -26
- package/tests/test_team_comms.py +2 -2
- package/tests/test_testrunner.py +2 -2
- package/tests/test_trust_graph.py +18 -0
- package/tests/test_unified_search.py +2 -2
- package/tests/test_version_check.py +10 -0
- package/tests/test_version_cmd.py +8 -8
- package/tests/test_whoami.py +1 -1
- package/systemd/skcomm-heartbeat.service +0 -18
- package/systemd/skcomm-queue-drain.service +0 -17
- /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/package.json +0 -0
- /package/{openclaw-plugin → openclaw-plugin.archived-2026-04-23}/src/openclaw.plugin.json +0 -0
|
@@ -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()
|