meshcode 2.10.35__tar.gz → 2.10.36__tar.gz
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.
- {meshcode-2.10.35 → meshcode-2.10.36}/PKG-INFO +1 -1
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/__init__.py +1 -1
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/comms_v4.py +108 -8
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/backend.py +1 -1
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/server.py +47 -29
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-2.10.35 → meshcode-2.10.36}/pyproject.toml +1 -1
- {meshcode-2.10.35 → meshcode-2.10.36}/README.md +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/ascii_art.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/cli.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/invites.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/launcher.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/launcher_install.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/preferences.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/protocol_v2.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/run_agent.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/secrets.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/self_update.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode/setup_clients.py +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/setup.cfg +0 -0
- {meshcode-2.10.35 → meshcode-2.10.36}/tests/test_status_enum_coverage.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "2.10.
|
|
2
|
+
__version__ = "2.10.36"
|
|
@@ -1730,10 +1730,21 @@ SETUP (advanced):
|
|
|
1730
1730
|
register <proj> <name> [role] Register agent manually
|
|
1731
1731
|
setup <client> <proj> <name> [role] Legacy: global MCP config
|
|
1732
1732
|
|
|
1733
|
+
AGENT CONTROL:
|
|
1734
|
+
scan Scan identicon from clipboard → run
|
|
1735
|
+
kill <proj> <name> Force disconnect agent
|
|
1736
|
+
wake <proj> <name> Send wake signal
|
|
1737
|
+
sleep <proj> <name> Send sleep signal
|
|
1738
|
+
disconnect <proj> <name> Graceful disconnect
|
|
1739
|
+
whoami Show logged-in identity
|
|
1740
|
+
profile [agent] Show/set agent profile
|
|
1741
|
+
connect <proj> <name> Connect existing agent
|
|
1742
|
+
|
|
1733
1743
|
ADMIN:
|
|
1734
1744
|
clear <proj> <name> Clear inbox
|
|
1735
1745
|
unregister <proj> <name> Leave project
|
|
1736
1746
|
prefs View/set preferences
|
|
1747
|
+
update Check for package updates
|
|
1737
1748
|
|
|
1738
1749
|
PROFILES (multi-account):
|
|
1739
1750
|
profiles List stored keychain profiles
|
|
@@ -1856,6 +1867,80 @@ Clear inbox: marks all unread messages as read for an agent.
|
|
|
1856
1867
|
"unregister": """meshcode unregister <project> <name>
|
|
1857
1868
|
|
|
1858
1869
|
Remove an agent from a meshwork (deletes the row from mc_agents).
|
|
1870
|
+
""",
|
|
1871
|
+
"setup": """meshcode setup <project> <agent> [role]
|
|
1872
|
+
|
|
1873
|
+
Create an isolated workspace at ~/meshcode/<project>-<agent>/ with
|
|
1874
|
+
.mcp.json configured for the agent's MCP server. Usually auto-created
|
|
1875
|
+
by `meshcode go`.
|
|
1876
|
+
|
|
1877
|
+
EXAMPLES:
|
|
1878
|
+
meshcode setup my-app backend "Backend Engineer"
|
|
1879
|
+
""",
|
|
1880
|
+
"run": """meshcode run <agent> [--project <name>] [--editor claude|cursor|code]
|
|
1881
|
+
|
|
1882
|
+
Launch an agent in your preferred editor. Detects Claude Code, Cursor,
|
|
1883
|
+
VS Code, Windsurf, or Codex. Use <project>/<agent> to disambiguate.
|
|
1884
|
+
|
|
1885
|
+
EXAMPLES:
|
|
1886
|
+
meshcode run backend
|
|
1887
|
+
meshcode run my-app/backend --editor cursor
|
|
1888
|
+
""",
|
|
1889
|
+
"go": """meshcode go <agent> [--project <name>]
|
|
1890
|
+
|
|
1891
|
+
Shortcut for setup + run. Creates workspace if needed, then launches.
|
|
1892
|
+
|
|
1893
|
+
EXAMPLES:
|
|
1894
|
+
meshcode go backend
|
|
1895
|
+
meshcode go my-app/frontend
|
|
1896
|
+
""",
|
|
1897
|
+
"scan": """meshcode scan
|
|
1898
|
+
|
|
1899
|
+
Read an agent identicon from clipboard (or stdin) and launch the agent.
|
|
1900
|
+
The identicon must contain a ⟨ project/agent ⟩ tag.
|
|
1901
|
+
|
|
1902
|
+
EXAMPLES:
|
|
1903
|
+
meshcode scan # reads from clipboard
|
|
1904
|
+
cat identicon.txt | meshcode scan
|
|
1905
|
+
""",
|
|
1906
|
+
"invite": """meshcode invite <project> <agent> [--role "..."] [--days 7]
|
|
1907
|
+
|
|
1908
|
+
Generate an invite token for a teammate to join as a specific agent.
|
|
1909
|
+
--days 0 = permanent (never expires).
|
|
1910
|
+
|
|
1911
|
+
EXAMPLES:
|
|
1912
|
+
meshcode invite my-app frontend --role "React Developer" --days 7
|
|
1913
|
+
""",
|
|
1914
|
+
"join": """meshcode join <token> [--display-name "alice"]
|
|
1915
|
+
|
|
1916
|
+
Accept an invite and create a workspace for the assigned agent.
|
|
1917
|
+
|
|
1918
|
+
EXAMPLES:
|
|
1919
|
+
meshcode join mc_invite_abc123 --display-name "Alice"
|
|
1920
|
+
""",
|
|
1921
|
+
"invites": """meshcode invites <project>
|
|
1922
|
+
|
|
1923
|
+
List outstanding and redeemed invites for a meshwork.
|
|
1924
|
+
""",
|
|
1925
|
+
"members": """meshcode members <project>
|
|
1926
|
+
|
|
1927
|
+
List all members (owner + invited) of a meshwork.
|
|
1928
|
+
""",
|
|
1929
|
+
"whoami": """meshcode whoami
|
|
1930
|
+
|
|
1931
|
+
Show the currently logged-in user (email, user_id, profile).
|
|
1932
|
+
""",
|
|
1933
|
+
"prefs": """meshcode prefs [key] [value]
|
|
1934
|
+
|
|
1935
|
+
View or set preferences (permission-mode, auto-update, etc.).
|
|
1936
|
+
|
|
1937
|
+
EXAMPLES:
|
|
1938
|
+
meshcode prefs # show all
|
|
1939
|
+
meshcode prefs permission-mode bypass # set
|
|
1940
|
+
""",
|
|
1941
|
+
"profiles": """meshcode profiles
|
|
1942
|
+
|
|
1943
|
+
List all stored keychain profiles (default + per-meshwork guest keys).
|
|
1859
1944
|
""",
|
|
1860
1945
|
}
|
|
1861
1946
|
|
|
@@ -1981,6 +2066,9 @@ if __name__ == "__main__":
|
|
|
1981
2066
|
from_a, to_a = target.split(":", 1)
|
|
1982
2067
|
else:
|
|
1983
2068
|
from_a, to_a = "?", target
|
|
2069
|
+
if not message.strip():
|
|
2070
|
+
print("[meshcode] ERROR: message cannot be empty. Usage: meshcode send <project> <from>:<to> <message>")
|
|
2071
|
+
sys.exit(1)
|
|
1984
2072
|
send_msg(proj, from_a, to_a, message, compact=compact)
|
|
1985
2073
|
|
|
1986
2074
|
elif cmd == "broadcast":
|
|
@@ -1989,12 +2077,15 @@ if __name__ == "__main__":
|
|
|
1989
2077
|
from_a = flags["from"]
|
|
1990
2078
|
message = flags.get("msg", flags.get("message", " ".join(pos)))
|
|
1991
2079
|
msg_type = flags.get("type", "broadcast")
|
|
1992
|
-
broadcast(proj, from_a, message, msg_type)
|
|
1993
2080
|
else:
|
|
1994
2081
|
proj = pos[0] if len(pos) > 0 else "default"
|
|
1995
2082
|
from_a = pos[1] if len(pos) > 1 else "?"
|
|
1996
2083
|
message = " ".join(pos[2:]) if len(pos) > 2 else ""
|
|
1997
|
-
|
|
2084
|
+
msg_type = "broadcast"
|
|
2085
|
+
if not message.strip():
|
|
2086
|
+
print("[meshcode] ERROR: message cannot be empty. Usage: meshcode broadcast <project> <from> <message>")
|
|
2087
|
+
sys.exit(1)
|
|
2088
|
+
broadcast(proj, from_a, message, msg_type)
|
|
1998
2089
|
|
|
1999
2090
|
elif cmd == "read":
|
|
2000
2091
|
proj = flags.get("project", pos[0] if len(pos) > 0 else "default")
|
|
@@ -2007,8 +2098,14 @@ if __name__ == "__main__":
|
|
|
2007
2098
|
elif cmd == "watch":
|
|
2008
2099
|
proj = flags.get("project", pos[0] if len(pos) > 0 else "default")
|
|
2009
2100
|
name = flags.get("name", pos[1] if len(pos) > 1 else "agent")
|
|
2010
|
-
|
|
2011
|
-
|
|
2101
|
+
try:
|
|
2102
|
+
interval = int(flags.get("interval", pos[2] if len(pos) > 2 else "10"))
|
|
2103
|
+
except (ValueError, TypeError):
|
|
2104
|
+
interval = 10
|
|
2105
|
+
try:
|
|
2106
|
+
timeout = int(flags.get("timeout", pos[3] if len(pos) > 3 else "0"))
|
|
2107
|
+
except (ValueError, TypeError):
|
|
2108
|
+
timeout = 0
|
|
2012
2109
|
watch(proj, name, interval, timeout)
|
|
2013
2110
|
|
|
2014
2111
|
elif cmd == "board":
|
|
@@ -2230,12 +2327,15 @@ if __name__ == "__main__":
|
|
|
2230
2327
|
print(" meshcode scan # reads from clipboard")
|
|
2231
2328
|
print(" cat identicon.txt | meshcode scan # reads from stdin")
|
|
2232
2329
|
sys.exit(1)
|
|
2233
|
-
#
|
|
2234
|
-
|
|
2235
|
-
|
|
2330
|
+
# Find ALL identicon tags, prefer the one with project/agent format.
|
|
2331
|
+
# Identicons contain two tags: ⟨ agent ⟩ and ⟨ project/agent ⟩.
|
|
2332
|
+
# re.search returns the first (agent-only), missing the project.
|
|
2333
|
+
all_tags = _re.findall(r'⟨\s*(\S+)\s*⟩', art_text)
|
|
2334
|
+
if not all_tags:
|
|
2236
2335
|
print("[meshcode] No identicon tag found. Expected: ⟨ agent_name ⟩")
|
|
2237
2336
|
sys.exit(1)
|
|
2238
|
-
tag
|
|
2337
|
+
# Prefer project/agent tag, fall back to agent-only
|
|
2338
|
+
tag = next((t for t in all_tags if "/" in t), all_tags[0])
|
|
2239
2339
|
if "/" in tag:
|
|
2240
2340
|
_proj, _agent = tag.split("/", 1)
|
|
2241
2341
|
else:
|
|
@@ -65,7 +65,7 @@ class _CircuitBreaker:
|
|
|
65
65
|
return self.state == self.OPEN
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
_circuit = _CircuitBreaker(failure_threshold=5, recovery_timeout=
|
|
68
|
+
_circuit = _CircuitBreaker(failure_threshold=5, recovery_timeout=10.0)
|
|
69
69
|
|
|
70
70
|
# Bake in production defaults — RLS-protected publishable key, safe to ship.
|
|
71
71
|
_DEFAULT_SUPABASE_URL = "https://gjinagyyjttyxnaoavnz.supabase.co"
|
|
@@ -923,7 +923,9 @@ meshcode_expand_link(). No sensitive msgs cross-mesh.
|
|
|
923
923
|
|
|
924
924
|
MEMORY: meshcode_remember(key, value) persists across sessions.
|
|
925
925
|
meshcode_recall(key?) retrieves. meshcode_forget(key) deletes.
|
|
926
|
-
|
|
926
|
+
Only remember reusable learnings: mistakes, feedback, patterns, preferences.
|
|
927
|
+
Do NOT save task summaries — tasks already persist in the task system.
|
|
928
|
+
Do NOT use memory for session state or ephemeral data.
|
|
927
929
|
Save reusable code patterns as template_* keys for instant recall.
|
|
928
930
|
|
|
929
931
|
SCRATCHPAD: meshcode_scratchpad_set/get for shared meshwork-level context
|
|
@@ -1398,6 +1400,9 @@ async def meshcode_debug_sleep(seconds: int = 30) -> Dict[str, Any]:
|
|
|
1398
1400
|
def meshcode_send(to: str, message: Any, in_reply_to: Optional[str] = None,
|
|
1399
1401
|
sensitive: bool = False, encrypted: bool = False) -> Dict[str, Any]:
|
|
1400
1402
|
"""Send message. Use "agent@meshwork" for cross-mesh. sensitive=True hides from exports. Pass encrypted=True for secrets/credentials (AES-256-GCM)."""
|
|
1403
|
+
if not to or not to.strip():
|
|
1404
|
+
return {"error": "recipient 'to' cannot be empty"}
|
|
1405
|
+
to = to.strip()
|
|
1401
1406
|
if isinstance(message, str):
|
|
1402
1407
|
# Auto-wrap strings into dict. Warn if very long but don't reject.
|
|
1403
1408
|
if len(message) > 2000:
|
|
@@ -1605,6 +1610,22 @@ def _get_pending_tasks_summary() -> Optional[List[Dict[str, str]]]:
|
|
|
1605
1610
|
or t.get("assignee") == AGENT_NAME # Directly assigned to me
|
|
1606
1611
|
)
|
|
1607
1612
|
]
|
|
1613
|
+
# For leader agents: also include unclaimed '*' tasks as pending
|
|
1614
|
+
# so commanders auto-triage them instead of letting them pile up.
|
|
1615
|
+
_leader_haystack = ((_ROLE_DESCRIPTION or '') + ' ' + AGENT_NAME).lower()
|
|
1616
|
+
_LEADER_KW = ('commander', 'lead', 'orchestrat', 'coordinator', 'coordinat',
|
|
1617
|
+
'coordinad', 'jefe', 'líder', 'lider', 'director', 'manager',
|
|
1618
|
+
'chief', 'captain', 'boss', 'head agent')
|
|
1619
|
+
is_leader = any(k in _leader_haystack for k in _LEADER_KW)
|
|
1620
|
+
if is_leader:
|
|
1621
|
+
wildcard_tasks = [
|
|
1622
|
+
{"id": t["id"][:8], "title": t["title"][:80], "priority": t.get("priority", "normal"), "status": t["status"]}
|
|
1623
|
+
for t in tasks
|
|
1624
|
+
if t.get("status") == "open"
|
|
1625
|
+
and t.get("assignee") == "*"
|
|
1626
|
+
and not t.get("claimed_by")
|
|
1627
|
+
]
|
|
1628
|
+
pending.extend(wildcard_tasks)
|
|
1608
1629
|
return pending if pending else None
|
|
1609
1630
|
except Exception:
|
|
1610
1631
|
return None
|
|
@@ -1630,14 +1651,22 @@ async def meshcode_wait(timeout_seconds: int = 20, include_acks: bool = False) -
|
|
|
1630
1651
|
global _IN_WAIT, _CONSECUTIVE_IDLE_SECONDS, _LAST_SEEN_TS
|
|
1631
1652
|
|
|
1632
1653
|
# PRODUCT RULE 1: If agent has open tasks, refuse to wait. Work first.
|
|
1654
|
+
# Exception: commander/leader agents can wait while monitoring — they
|
|
1655
|
+
# delegate tasks and need to stay in the wait loop to receive reports.
|
|
1633
1656
|
pending_tasks = _get_pending_tasks_summary()
|
|
1634
1657
|
if pending_tasks:
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1658
|
+
_leader_haystack = ((_ROLE_DESCRIPTION or '') + ' ' + AGENT_NAME).lower()
|
|
1659
|
+
_LEADER_KW = ('commander', 'lead', 'orchestrat', 'coordinator', 'coordinat',
|
|
1660
|
+
'coordinad', 'jefe', 'líder', 'lider', 'director', 'manager',
|
|
1661
|
+
'chief', 'captain', 'boss', 'head agent')
|
|
1662
|
+
_is_leader = any(k in _leader_haystack for k in _LEADER_KW)
|
|
1663
|
+
if not _is_leader:
|
|
1664
|
+
return {
|
|
1665
|
+
"refused": True,
|
|
1666
|
+
"reason": "You have open tasks. Work them before entering wait.",
|
|
1667
|
+
"pending_tasks": pending_tasks,
|
|
1668
|
+
"count": len(pending_tasks),
|
|
1669
|
+
}
|
|
1641
1670
|
|
|
1642
1671
|
# PRODUCT RULE 2: If agent has unread messages in DB, refuse to wait.
|
|
1643
1672
|
try:
|
|
@@ -2050,8 +2079,10 @@ def meshcode_init(project: str, agent: str, role: str = "") -> Dict[str, Any]:
|
|
|
2050
2079
|
def meshcode_task_create(title: str, description: str = "", assignee: str = "*",
|
|
2051
2080
|
priority: str = "normal", parent_task_id: Optional[str] = None) -> Dict[str, Any]:
|
|
2052
2081
|
"""Create task. assignee="*" for any, priority: low/normal/high/urgent."""
|
|
2082
|
+
if not title or not title.strip():
|
|
2083
|
+
return {"error": "title cannot be empty"}
|
|
2053
2084
|
api_key = _get_api_key()
|
|
2054
|
-
result = be.task_create(api_key, _PROJECT_ID, AGENT_NAME, title,
|
|
2085
|
+
result = be.task_create(api_key, _PROJECT_ID, AGENT_NAME, title.strip(),
|
|
2055
2086
|
description=description, assignee=assignee,
|
|
2056
2087
|
priority=priority, parent_task_id=parent_task_id)
|
|
2057
2088
|
# Auto-notify assignee so they wake from meshcode_wait
|
|
@@ -2122,7 +2153,7 @@ def meshcode_task_claim(task_id: str) -> Dict[str, Any]:
|
|
|
2122
2153
|
@mcp.tool()
|
|
2123
2154
|
@with_working_status
|
|
2124
2155
|
def meshcode_task_complete(task_id: str, summary: str = "", force: bool = False) -> Dict[str, Any]:
|
|
2125
|
-
"""Complete a claimed task with summary.
|
|
2156
|
+
"""Complete a claimed task with summary.
|
|
2126
2157
|
|
|
2127
2158
|
Refuses if the task has open subtasks, unless force=True is passed with a
|
|
2128
2159
|
reason in `summary`. This prevents closing a parent while lanes are still
|
|
@@ -2148,25 +2179,8 @@ def meshcode_task_complete(task_id: str, summary: str = "", force: bool = False)
|
|
|
2148
2179
|
except Exception:
|
|
2149
2180
|
pass # Best-effort check; don't block on listing failure.
|
|
2150
2181
|
result = be.task_complete(api_key, _PROJECT_ID, task_id, AGENT_NAME, summary=summary)
|
|
2151
|
-
#
|
|
2152
|
-
|
|
2153
|
-
try:
|
|
2154
|
-
import threading
|
|
2155
|
-
def _auto_remember():
|
|
2156
|
-
try:
|
|
2157
|
-
be.sb_rpc("mc_memory_set", {
|
|
2158
|
-
"p_api_key": api_key,
|
|
2159
|
-
"p_agent_name": AGENT_NAME,
|
|
2160
|
-
"p_key": f"task_{task_id[:8]}",
|
|
2161
|
-
"p_value": {"title": result.get("title", ""), "summary": summary, "completed": True},
|
|
2162
|
-
"p_tier": "episodic",
|
|
2163
|
-
"p_project_name": PROJECT_NAME,
|
|
2164
|
-
})
|
|
2165
|
-
except Exception:
|
|
2166
|
-
pass
|
|
2167
|
-
threading.Thread(target=_auto_remember, daemon=True).start()
|
|
2168
|
-
except Exception:
|
|
2169
|
-
pass
|
|
2182
|
+
# Task data persists in the task system — do NOT duplicate to memory.
|
|
2183
|
+
# Samuel: "los tasks no deben guardarse en memoria, para eso salen en tasks"
|
|
2170
2184
|
return result
|
|
2171
2185
|
|
|
2172
2186
|
|
|
@@ -2268,6 +2282,7 @@ def meshcode_auto_wake() -> Dict[str, Any]:
|
|
|
2268
2282
|
suggestions: List[Dict[str, str]] = []
|
|
2269
2283
|
|
|
2270
2284
|
# 1. Check for stale agents (heartbeat >10 min, not offline/sleeping)
|
|
2285
|
+
agents = [] # Initialize before try so downstream checks don't NameError
|
|
2271
2286
|
try:
|
|
2272
2287
|
agents = be.get_board(_PROJECT_ID)
|
|
2273
2288
|
import datetime as _dt
|
|
@@ -2607,12 +2622,15 @@ source: meshcode
|
|
|
2607
2622
|
@mcp.tool()
|
|
2608
2623
|
@with_working_status
|
|
2609
2624
|
def meshcode_remember(key: str, value: Any) -> Dict[str, Any]:
|
|
2610
|
-
"""Store a persistent memory (survives restarts).
|
|
2625
|
+
"""Store a persistent memory (survives restarts). Only for reusable learnings, NOT task data.
|
|
2611
2626
|
|
|
2612
2627
|
Args:
|
|
2613
2628
|
key: Short key (e.g. "team_conventions").
|
|
2614
2629
|
value: Any JSON-serializable value.
|
|
2615
2630
|
"""
|
|
2631
|
+
if not key or not key.strip():
|
|
2632
|
+
return {"error": "key cannot be empty"}
|
|
2633
|
+
key = key.strip()
|
|
2616
2634
|
api_key = _get_api_key()
|
|
2617
2635
|
json_value = value if isinstance(value, (dict, list)) else json.dumps(value)
|
|
2618
2636
|
if isinstance(json_value, str):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|