meshcode 1.8.6__tar.gz → 1.8.9__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-1.8.6 → meshcode-1.8.9}/PKG-INFO +1 -1
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/__init__.py +1 -1
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/comms_v4.py +61 -12
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/invites.py +2 -2
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/meshcode_mcp/backend.py +2 -2
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/meshcode_mcp/server.py +70 -5
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/run_agent.py +10 -1
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/setup_clients.py +24 -4
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode.egg-info/PKG-INFO +1 -1
- {meshcode-1.8.6 → meshcode-1.8.9}/pyproject.toml +1 -1
- {meshcode-1.8.6 → meshcode-1.8.9}/README.md +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/cli.py +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/launcher.py +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/launcher_install.py +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/meshcode_mcp/__init__.py +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/meshcode_mcp/__main__.py +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/meshcode_mcp/realtime.py +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/meshcode_mcp/test_backend.py +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/meshcode_mcp/test_realtime.py +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/preferences.py +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/protocol_v2.py +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/secrets.py +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode/self_update.py +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode.egg-info/SOURCES.txt +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode.egg-info/dependency_links.txt +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode.egg-info/entry_points.txt +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode.egg-info/requires.txt +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/meshcode.egg-info/top_level.txt +0 -0
- {meshcode-1.8.6 → meshcode-1.8.9}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""MeshCode — Real-time communication between AI agents."""
|
|
2
|
-
__version__ = "1.8.
|
|
2
|
+
__version__ = "1.8.9"
|
|
@@ -47,8 +47,8 @@ from urllib.parse import quote
|
|
|
47
47
|
# Production defaults baked in. The publishable key is the anon/public key
|
|
48
48
|
# (RLS-protected, safe to ship — same one the frontend at meshcode.io uses
|
|
49
49
|
# in the browser). Override via env vars or ~/.meshcode/env if you self-host.
|
|
50
|
-
_DEFAULT_SUPABASE_URL = "https://
|
|
51
|
-
_DEFAULT_SUPABASE_KEY = "
|
|
50
|
+
_DEFAULT_SUPABASE_URL = "https://gjinagyyjttyxnaoavnz.supabase.co"
|
|
51
|
+
_DEFAULT_SUPABASE_KEY = "sb_publishable_qwN9PO1L7jUXhhbhhVk2CQ_z1FXG2Qf"
|
|
52
52
|
|
|
53
53
|
def _load_env_file():
|
|
54
54
|
"""Read SUPABASE_URL/KEY from ~/.meshcode/env if present (overrides defaults)."""
|
|
@@ -190,7 +190,7 @@ def log_msg(text):
|
|
|
190
190
|
try:
|
|
191
191
|
with open(LOG_FILE, "a") as f:
|
|
192
192
|
f.write(f"[{now()}] {text}\n")
|
|
193
|
-
except:
|
|
193
|
+
except (IOError, OSError):
|
|
194
194
|
pass
|
|
195
195
|
|
|
196
196
|
|
|
@@ -198,12 +198,61 @@ def ensure_sessions():
|
|
|
198
198
|
SESSIONS_DIR.mkdir(parents=True, exist_ok=True)
|
|
199
199
|
|
|
200
200
|
|
|
201
|
+
def _load_api_key_for_cli() -> str:
|
|
202
|
+
"""Pull the user's api key from the keychain (or env fallback) so the
|
|
203
|
+
CLI can call SECURITY DEFINER RPCs that use api_key auth instead of
|
|
204
|
+
relying on the publishable anon key + RLS, which is what CLI verbs
|
|
205
|
+
used to do and which always failed because the CLI has no JWT context.
|
|
206
|
+
"""
|
|
207
|
+
# 1) explicit env override
|
|
208
|
+
k = os.environ.get("MESHCODE_API_KEY", "").strip()
|
|
209
|
+
if k:
|
|
210
|
+
return k
|
|
211
|
+
# 2) keychain via secrets module
|
|
212
|
+
try:
|
|
213
|
+
import importlib
|
|
214
|
+
secrets_mod = importlib.import_module("meshcode.secrets")
|
|
215
|
+
profile = os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or secrets_mod.DEFAULT_PROFILE
|
|
216
|
+
key = secrets_mod.get_api_key(profile=profile) or ""
|
|
217
|
+
if key:
|
|
218
|
+
return key
|
|
219
|
+
except Exception:
|
|
220
|
+
pass
|
|
221
|
+
return ""
|
|
222
|
+
|
|
223
|
+
|
|
201
224
|
def get_project_id(project_name):
|
|
202
|
-
"""
|
|
225
|
+
"""Resolve a project's UUID for the authenticated CLI user.
|
|
226
|
+
|
|
227
|
+
Resolution order:
|
|
228
|
+
1. api_key + mc_resolve_project RPC (SECURITY DEFINER, the only
|
|
229
|
+
path that actually works for an authenticated CLI session)
|
|
230
|
+
2. legacy publishable-anon SELECT (only useful for shared/public
|
|
231
|
+
projects; will fail under RLS for owned projects)
|
|
232
|
+
3. legacy publishable-anon INSERT (will fail under RLS — kept as
|
|
233
|
+
last resort for backwards compat with very old test scripts)
|
|
234
|
+
"""
|
|
235
|
+
api_key = _load_api_key_for_cli()
|
|
236
|
+
if api_key:
|
|
237
|
+
try:
|
|
238
|
+
r = sb_rpc("mc_resolve_project", {
|
|
239
|
+
"p_api_key": api_key,
|
|
240
|
+
"p_project_name": project_name,
|
|
241
|
+
})
|
|
242
|
+
if isinstance(r, dict) and r.get("project_id"):
|
|
243
|
+
return r["project_id"]
|
|
244
|
+
# Some deployments return rows
|
|
245
|
+
if isinstance(r, list) and r and r[0].get("project_id"):
|
|
246
|
+
return r[0]["project_id"]
|
|
247
|
+
except Exception:
|
|
248
|
+
pass
|
|
249
|
+
|
|
203
250
|
rows = sb_select("mc_projects", f"name=eq.{quote(project_name)}")
|
|
204
251
|
if rows:
|
|
205
252
|
return rows[0]["id"]
|
|
206
|
-
|
|
253
|
+
|
|
254
|
+
# Last resort: try to create. Will fail under RLS for unauthenticated
|
|
255
|
+
# contexts, but kept for legacy callers + admin tooling.
|
|
207
256
|
result = sb_insert("mc_projects", {"name": project_name})
|
|
208
257
|
if result and len(result) > 0:
|
|
209
258
|
return result[0]["id"]
|
|
@@ -227,7 +276,7 @@ def can_nudge(project, name):
|
|
|
227
276
|
last = float(nf.read_text().strip())
|
|
228
277
|
if (time.time() - last) < NUDGE_COOLDOWN:
|
|
229
278
|
return False
|
|
230
|
-
except:
|
|
279
|
+
except (ValueError, IOError, OSError):
|
|
231
280
|
pass
|
|
232
281
|
return True
|
|
233
282
|
|
|
@@ -262,7 +311,7 @@ def send_notification(project, name, from_agent, pending=1):
|
|
|
262
311
|
else:
|
|
263
312
|
# Linux
|
|
264
313
|
subprocess.run(['notify-send', title, body], capture_output=True, timeout=3)
|
|
265
|
-
except:
|
|
314
|
+
except (subprocess.TimeoutExpired, subprocess.SubprocessError, OSError):
|
|
266
315
|
pass
|
|
267
316
|
|
|
268
317
|
|
|
@@ -598,7 +647,7 @@ def nudge_agent(project, name, from_agent=""):
|
|
|
598
647
|
|
|
599
648
|
try:
|
|
600
649
|
data = json.loads(session_file.read_text())
|
|
601
|
-
except:
|
|
650
|
+
except (json.JSONDecodeError, IOError, OSError):
|
|
602
651
|
if _headless_spawn_allowed():
|
|
603
652
|
log_msg(f"[{project}] NUDGE: {name} session file unreadable, spawning headless (opt-in)")
|
|
604
653
|
ok = _spawn_headless_for_pending(project, name, from_agent)
|
|
@@ -704,7 +753,7 @@ end tell
|
|
|
704
753
|
capture_output=True, text=True, timeout=10)
|
|
705
754
|
if result.returncode == 0:
|
|
706
755
|
success = True
|
|
707
|
-
except:
|
|
756
|
+
except (subprocess.TimeoutExpired, subprocess.SubprocessError, OSError):
|
|
708
757
|
pass
|
|
709
758
|
|
|
710
759
|
if success:
|
|
@@ -737,7 +786,7 @@ def get_session_info():
|
|
|
737
786
|
check_pid = parent
|
|
738
787
|
else:
|
|
739
788
|
break
|
|
740
|
-
except:
|
|
789
|
+
except (subprocess.SubprocessError, OSError, ValueError):
|
|
741
790
|
pass
|
|
742
791
|
|
|
743
792
|
if not my_tty:
|
|
@@ -750,7 +799,7 @@ def get_session_info():
|
|
|
750
799
|
session_tty = data.get("tty", "")
|
|
751
800
|
if session_tty and (session_tty in my_tty or my_tty in session_tty):
|
|
752
801
|
return data.get("project"), data.get("agent")
|
|
753
|
-
except:
|
|
802
|
+
except (json.JSONDecodeError, IOError, OSError):
|
|
754
803
|
continue
|
|
755
804
|
|
|
756
805
|
return None, None
|
|
@@ -786,7 +835,7 @@ def register(project, name, role=""):
|
|
|
786
835
|
check_pid = parent
|
|
787
836
|
else:
|
|
788
837
|
break
|
|
789
|
-
except:
|
|
838
|
+
except (subprocess.SubprocessError, OSError, ValueError):
|
|
790
839
|
pass
|
|
791
840
|
|
|
792
841
|
# Register agent via tier-aware RPC (enforces plan limits)
|
|
@@ -41,8 +41,8 @@ from urllib.request import Request, urlopen
|
|
|
41
41
|
# Supabase RPC helpers
|
|
42
42
|
# ============================================================
|
|
43
43
|
|
|
44
|
-
_DEFAULT_SUPABASE_URL = "https://
|
|
45
|
-
_DEFAULT_SUPABASE_KEY = "
|
|
44
|
+
_DEFAULT_SUPABASE_URL = "https://gjinagyyjttyxnaoavnz.supabase.co"
|
|
45
|
+
_DEFAULT_SUPABASE_KEY = "sb_publishable_qwN9PO1L7jUXhhbhhVk2CQ_z1FXG2Qf"
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
def _sb() -> Dict[str, str]:
|
|
@@ -13,8 +13,8 @@ from urllib.parse import quote
|
|
|
13
13
|
from urllib.request import Request, urlopen
|
|
14
14
|
|
|
15
15
|
# Bake in production defaults — RLS-protected publishable key, safe to ship.
|
|
16
|
-
_DEFAULT_SUPABASE_URL = "https://
|
|
17
|
-
_DEFAULT_SUPABASE_KEY = "
|
|
16
|
+
_DEFAULT_SUPABASE_URL = "https://gjinagyyjttyxnaoavnz.supabase.co"
|
|
17
|
+
_DEFAULT_SUPABASE_KEY = "sb_publishable_qwN9PO1L7jUXhhbhhVk2CQ_z1FXG2Qf"
|
|
18
18
|
|
|
19
19
|
def _load_env_file() -> Dict[str, str]:
|
|
20
20
|
env_path = Path.home() / ".meshcode" / "env"
|
|
@@ -221,24 +221,32 @@ if not _flip_status("idle", ""):
|
|
|
221
221
|
# Fire-and-forget flips so tool execution is never blocked.
|
|
222
222
|
# ============================================================
|
|
223
223
|
import functools as _functools
|
|
224
|
+
import threading as _threading
|
|
225
|
+
|
|
226
|
+
_flip_lock = _threading.Lock()
|
|
224
227
|
|
|
225
228
|
|
|
226
229
|
async def _async_flip_status(status: str, task: str = "") -> None:
|
|
227
230
|
try:
|
|
228
|
-
await asyncio.to_thread(
|
|
231
|
+
await asyncio.to_thread(_flip_status_locked, status, task)
|
|
229
232
|
except Exception as e:
|
|
230
233
|
log.debug(f"flip_status({status}) failed: {e}")
|
|
231
234
|
|
|
232
235
|
|
|
236
|
+
def _flip_status_locked(status: str, task: str = "") -> bool:
|
|
237
|
+
"""Thread-safe wrapper around _flip_status to prevent concurrent RPCs."""
|
|
238
|
+
with _flip_lock:
|
|
239
|
+
return _flip_status(status, task)
|
|
240
|
+
|
|
241
|
+
|
|
233
242
|
def _schedule_flip(status: str, task: str = "") -> None:
|
|
234
243
|
try:
|
|
235
244
|
loop = asyncio.get_running_loop()
|
|
236
245
|
loop.create_task(_async_flip_status(status, task))
|
|
237
246
|
except RuntimeError:
|
|
238
247
|
# No running loop (sync context) — run in a throwaway thread
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
target=_flip_status, args=(status, task), daemon=True
|
|
248
|
+
_threading.Thread(
|
|
249
|
+
target=_flip_status_locked, args=(status, task), daemon=True
|
|
242
250
|
).start()
|
|
243
251
|
|
|
244
252
|
|
|
@@ -434,6 +442,38 @@ LOOP-SAFETY RULES (IMPORTANT — prevent token-burning ping-pong):
|
|
|
434
442
|
- If you find yourself sending more than ~10 messages on the same topic
|
|
435
443
|
in a row, stop and reassess — you may be in a feedback loop.
|
|
436
444
|
|
|
445
|
+
COMMUNICATION EFFICIENCY PROTOCOL (MANDATORY):
|
|
446
|
+
|
|
447
|
+
1. TASKS OVER MESSAGES: use meshcode_task_create/claim/complete for all
|
|
448
|
+
trackable work assignments, NOT messages. Messages are only for short
|
|
449
|
+
signals: "review needed", "blocked on X", "approved", "question: ...".
|
|
450
|
+
The Tasks panel in the dashboard is where the human sees progress — if
|
|
451
|
+
there are no tasks, the human has no visibility into what you're doing.
|
|
452
|
+
|
|
453
|
+
2. COMPRESSED REPORTS: when reporting findings, use structured JSON in the
|
|
454
|
+
payload, not prose:
|
|
455
|
+
{{"findings": [{{"severity":"high","file":"X","issue":"Y","fix":"Z"}}],
|
|
456
|
+
"commits":["abc1234"], "next":"awaiting_review"}}
|
|
457
|
+
|
|
458
|
+
3. NO PROSE PADDING: never write "As you asked, I reviewed the schema and
|
|
459
|
+
found that..." — go straight to the data. Max 2 sentences of context,
|
|
460
|
+
then structured output. Every token costs money.
|
|
461
|
+
|
|
462
|
+
4. SIGNAL-ONLY MESSAGES: meshcode_send messages must be <100 tokens. If you
|
|
463
|
+
need to communicate more, create a task with the full description and
|
|
464
|
+
send a signal: "task created: <title>".
|
|
465
|
+
|
|
466
|
+
5. TASK STATUS AS SOURCE OF TRUTH: always create tasks for trackable work.
|
|
467
|
+
Claim them when you start. Complete them when done. The human watches the
|
|
468
|
+
task board, not the chat. No task = invisible work = wasted effort.
|
|
469
|
+
|
|
470
|
+
6. POLL TASKS, DON'T WAIT FOR MESSAGES: when coordinating, use
|
|
471
|
+
meshcode_tasks() to check task status instead of waiting for a message.
|
|
472
|
+
If you assigned a task, poll the board to see if it's done — don't
|
|
473
|
+
assume the agent will message you. Messages can be missed; task status
|
|
474
|
+
is persistent and reliable. Check meshcode_tasks() before each
|
|
475
|
+
meshcode_wait() call to avoid stale waits.
|
|
476
|
+
|
|
437
477
|
YOUR FIRST ACTIONS WHEN THIS SESSION STARTS:
|
|
438
478
|
1. Call meshcode_status() once to see who else is in the meshwork.
|
|
439
479
|
2. Call meshcode_set_status(status="online", task="ready") to announce
|
|
@@ -505,12 +545,37 @@ async def _on_new_message(msg: Dict[str, Any]) -> None:
|
|
|
505
545
|
|
|
506
546
|
|
|
507
547
|
async def _heartbeat_loop():
|
|
548
|
+
"""Send heartbeat every 30s via HTTP. Also renews the agent lease and
|
|
549
|
+
logs when the WebSocket is disconnected (heartbeat still works via HTTP
|
|
550
|
+
so the agent stays 'online' in the DB even during WS reconnects)."""
|
|
551
|
+
_lease_counter = 0
|
|
508
552
|
while True:
|
|
509
553
|
try:
|
|
510
554
|
be.sb_rpc("mc_heartbeat", {"p_project_id": _PROJECT_ID, "p_agent_name": AGENT_NAME})
|
|
511
|
-
|
|
555
|
+
# Log WS health for diagnostics
|
|
556
|
+
if _REALTIME and not _REALTIME.is_connected:
|
|
557
|
+
log.warning("heartbeat ok (HTTP) but WebSocket is disconnected — realtime messages may be delayed")
|
|
558
|
+
else:
|
|
559
|
+
log.debug(f"heartbeat ok for {AGENT_NAME}")
|
|
512
560
|
except Exception as e:
|
|
513
561
|
log.warning(f"heartbeat failed: {e}")
|
|
562
|
+
|
|
563
|
+
# Renew lease every ~2 minutes (every 4th heartbeat) to prevent
|
|
564
|
+
# another instance from stealing it during long sessions.
|
|
565
|
+
_lease_counter += 1
|
|
566
|
+
if _lease_counter % 4 == 0:
|
|
567
|
+
try:
|
|
568
|
+
api_key = _get_api_key()
|
|
569
|
+
if api_key:
|
|
570
|
+
be.sb_rpc("mc_acquire_agent_lease", {
|
|
571
|
+
"p_api_key": api_key,
|
|
572
|
+
"p_project_id": _PROJECT_ID,
|
|
573
|
+
"p_agent_name": AGENT_NAME,
|
|
574
|
+
"p_instance_id": _INSTANCE_ID,
|
|
575
|
+
})
|
|
576
|
+
except Exception as e:
|
|
577
|
+
log.warning(f"lease renewal failed: {e}")
|
|
578
|
+
|
|
514
579
|
await asyncio.sleep(30)
|
|
515
580
|
|
|
516
581
|
|
|
@@ -112,6 +112,16 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
112
112
|
except Exception:
|
|
113
113
|
pass
|
|
114
114
|
|
|
115
|
+
# Detect: are we already inside a Claude Code session? os.execvp(claude)
|
|
116
|
+
# from inside an existing claude won't work — claude needs a fresh
|
|
117
|
+
# interactive terminal it owns. Refuse with a clear message.
|
|
118
|
+
if os.environ.get("CLAUDECODE") == "1" or os.environ.get("CLAUDE_CODE_SESSION"):
|
|
119
|
+
print("[meshcode] ERROR: meshcode run cannot bootstrap a new agent from inside an", file=sys.stderr)
|
|
120
|
+
print("[meshcode] existing Claude Code session.", file=sys.stderr)
|
|
121
|
+
print("[meshcode] Open a fresh Terminal / iTerm window and run the command there.", file=sys.stderr)
|
|
122
|
+
print(f"[meshcode] (or copy this exact line into a new terminal: meshcode run {agent})", file=sys.stderr)
|
|
123
|
+
return 2
|
|
124
|
+
|
|
115
125
|
found = _find_agent_workspace(agent, project)
|
|
116
126
|
if not found:
|
|
117
127
|
return 2
|
|
@@ -138,7 +148,6 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
|
|
|
138
148
|
cmd = [
|
|
139
149
|
editor,
|
|
140
150
|
"--mcp-config", str(ws / ".mcp.json"),
|
|
141
|
-
"--strict-mcp-config",
|
|
142
151
|
]
|
|
143
152
|
if mode == "bypass":
|
|
144
153
|
cmd.append("--dangerously-skip-permissions")
|
|
@@ -93,9 +93,9 @@ def _load_supabase_env() -> Dict[str, str]:
|
|
|
93
93
|
elif k == "SUPABASE_KEY" and not key:
|
|
94
94
|
key = v
|
|
95
95
|
if not url:
|
|
96
|
-
url = "https://
|
|
96
|
+
url = "https://gjinagyyjttyxnaoavnz.supabase.co"
|
|
97
97
|
if not key:
|
|
98
|
-
key = "
|
|
98
|
+
key = "sb_publishable_qwN9PO1L7jUXhhbhhVk2CQ_z1FXG2Qf"
|
|
99
99
|
return {"SUPABASE_URL": url, "SUPABASE_KEY": key}
|
|
100
100
|
|
|
101
101
|
|
|
@@ -300,10 +300,30 @@ def setup_workspace(project: str, agent: str, role: str = "",
|
|
|
300
300
|
print(f"[meshcode] ERROR: cannot load secrets module: {e}", file=sys.stderr)
|
|
301
301
|
return 2
|
|
302
302
|
|
|
303
|
+
if not api_key and keychain_profile == "default" and sys.stdin.isatty():
|
|
304
|
+
# No key in keychain — prompt inline instead of failing
|
|
305
|
+
print("[meshcode] No API key found. You can get one from meshcode.io/settings.", file=sys.stderr)
|
|
306
|
+
try:
|
|
307
|
+
api_key = input("[meshcode] Paste your API key (mc_...): ").strip()
|
|
308
|
+
except (EOFError, KeyboardInterrupt):
|
|
309
|
+
api_key = ""
|
|
310
|
+
if api_key:
|
|
311
|
+
# Store it so they never have to paste again
|
|
312
|
+
try:
|
|
313
|
+
import importlib as _il
|
|
314
|
+
_login = _il.import_module("meshcode.comms_v4").login
|
|
315
|
+
_login(api_key)
|
|
316
|
+
except Exception:
|
|
317
|
+
# Fallback: just store raw in keychain
|
|
318
|
+
try:
|
|
319
|
+
secrets_mod.set_api_key(api_key, profile="default")
|
|
320
|
+
except Exception:
|
|
321
|
+
pass
|
|
322
|
+
|
|
303
323
|
if not api_key:
|
|
304
|
-
print(f"[meshcode] ERROR: no api key found
|
|
324
|
+
print(f"[meshcode] ERROR: no api key found.", file=sys.stderr)
|
|
305
325
|
if keychain_profile == "default":
|
|
306
|
-
print("[meshcode] Run `meshcode login <api_key>`
|
|
326
|
+
print("[meshcode] Run `meshcode login <api_key>` or paste it when prompted.", file=sys.stderr)
|
|
307
327
|
else:
|
|
308
328
|
print(f"[meshcode] This profile is created by `meshcode join <token>`.", file=sys.stderr)
|
|
309
329
|
return 2
|
|
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
|