memstack-skill-loader 4.0.3__tar.gz → 4.0.4__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.
- {memstack_skill_loader-4.0.3/src/memstack_skill_loader.egg-info → memstack_skill_loader-4.0.4}/PKG-INFO +1 -1
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/pyproject.toml +1 -1
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/__init__.py +1 -1
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/agent_runner.py +155 -51
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/dashboard.html +29 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/dashboard.py +2 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4/src/memstack_skill_loader.egg-info}/PKG-INFO +1 -1
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/MANIFEST.in +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/README.md +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/setup.cfg +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/__main__.py +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/categories.py +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/compression.py +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/config.py +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/indexer.py +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/license.py +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/memory_db.py +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/search.py +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/server.py +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/skill_config.py +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/stats.py +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/tfidf_search.py +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/version_check.py +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader.egg-info/SOURCES.txt +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader.egg-info/dependency_links.txt +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader.egg-info/entry_points.txt +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader.egg-info/requires.txt +0 -0
- {memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader.egg-info/top_level.txt +0 -0
|
@@ -12,6 +12,8 @@ import uuid
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
from typing import Optional
|
|
14
14
|
|
|
15
|
+
import httpx
|
|
16
|
+
|
|
15
17
|
from .stats import log_agent_invocation
|
|
16
18
|
|
|
17
19
|
|
|
@@ -26,6 +28,8 @@ AGENT_TIMEOUT = 3600 # seconds per --print invocation (default 60 minutes)
|
|
|
26
28
|
MAX_ITERATIONS = 2
|
|
27
29
|
|
|
28
30
|
ANTHROPIC_BASE_URL = os.environ.get("ANTHROPIC_BASE_URL", "")
|
|
31
|
+
API_DEFAULT_MODEL = "claude-sonnet-4-20250514"
|
|
32
|
+
API_MAX_TOKENS = 16000
|
|
29
33
|
|
|
30
34
|
SYSTEM_PROMPTS = {
|
|
31
35
|
"manager": (
|
|
@@ -188,6 +192,7 @@ def _extract_commit_from_reviewer(reviewer_output: str) -> str:
|
|
|
188
192
|
def _build_env() -> dict:
|
|
189
193
|
"""Build environment for subprocess, ensuring Anthropic vars are passed."""
|
|
190
194
|
env = os.environ.copy()
|
|
195
|
+
env.pop("MEMSTACK_ENABLE_TTS", None)
|
|
191
196
|
if ANTHROPIC_BASE_URL:
|
|
192
197
|
env["ANTHROPIC_BASE_URL"] = ANTHROPIC_BASE_URL
|
|
193
198
|
return env
|
|
@@ -254,8 +259,105 @@ def _parse_stream_json(raw: str) -> tuple[str, int, int, float, int]:
|
|
|
254
259
|
return text, input_tokens, output_tokens, cost_usd, context_tokens
|
|
255
260
|
|
|
256
261
|
|
|
262
|
+
# ---------------------------------------------------------------------------
|
|
263
|
+
# Direct API invocation (Manager / Reviewer — no file tools needed)
|
|
264
|
+
# ---------------------------------------------------------------------------
|
|
265
|
+
|
|
266
|
+
def _invoke_api_agent(name: str, prompt: str, system_prompt: str,
|
|
267
|
+
log_path: Optional[Path] = None, timeout: int = 600,
|
|
268
|
+
model: str = "", session_id: Optional[str] = None,
|
|
269
|
+
) -> tuple[str, int, int]:
|
|
270
|
+
"""Call the Anthropic Messages API directly via httpx.
|
|
271
|
+
|
|
272
|
+
Returns (text, input_tokens, output_tokens).
|
|
273
|
+
"""
|
|
274
|
+
api_key = os.environ.get("ANTHROPIC_API_KEY", "")
|
|
275
|
+
base_url = ANTHROPIC_BASE_URL or "https://api.anthropic.com"
|
|
276
|
+
|
|
277
|
+
if not api_key and not ANTHROPIC_BASE_URL:
|
|
278
|
+
raise RuntimeError(
|
|
279
|
+
f"{name}: ANTHROPIC_API_KEY not set and no ANTHROPIC_BASE_URL proxy configured"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
url = f"{base_url.rstrip('/')}/v1/messages"
|
|
283
|
+
headers = {
|
|
284
|
+
"content-type": "application/json",
|
|
285
|
+
"anthropic-version": "2023-06-01",
|
|
286
|
+
}
|
|
287
|
+
if api_key:
|
|
288
|
+
headers["x-api-key"] = api_key
|
|
289
|
+
|
|
290
|
+
body = {
|
|
291
|
+
"model": model or API_DEFAULT_MODEL,
|
|
292
|
+
"max_tokens": API_MAX_TOKENS,
|
|
293
|
+
"system": system_prompt,
|
|
294
|
+
"messages": [{"role": "user", "content": prompt}],
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if log_path:
|
|
298
|
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
299
|
+
ts = time.strftime("%H:%M:%S")
|
|
300
|
+
with open(log_path, "a", encoding="utf-8") as f:
|
|
301
|
+
f.write(f"[{ts}] === API call: {name} ===\n")
|
|
302
|
+
f.write(f"[{ts}] Model: {body['model']}\n")
|
|
303
|
+
f.write(f"[{ts}] Prompt length: {len(prompt)} chars\n")
|
|
304
|
+
|
|
305
|
+
try:
|
|
306
|
+
resp = httpx.post(url, json=body, headers=headers, timeout=timeout)
|
|
307
|
+
resp.raise_for_status()
|
|
308
|
+
except httpx.TimeoutException:
|
|
309
|
+
if log_path:
|
|
310
|
+
ts = time.strftime("%H:%M:%S")
|
|
311
|
+
with open(log_path, "a", encoding="utf-8") as f:
|
|
312
|
+
f.write(f"[{ts}] API timeout after {timeout}s\n")
|
|
313
|
+
raise subprocess.TimeoutExpired(f"api:{name}", timeout)
|
|
314
|
+
except httpx.HTTPStatusError as exc:
|
|
315
|
+
if log_path:
|
|
316
|
+
ts = time.strftime("%H:%M:%S")
|
|
317
|
+
with open(log_path, "a", encoding="utf-8") as f:
|
|
318
|
+
f.write(f"[{ts}] API error {exc.response.status_code}: {exc.response.text[:500]}\n")
|
|
319
|
+
raise RuntimeError(f"{name} API error {exc.response.status_code}: {exc.response.text[:200]}") from exc
|
|
320
|
+
except httpx.HTTPError as exc:
|
|
321
|
+
if log_path:
|
|
322
|
+
ts = time.strftime("%H:%M:%S")
|
|
323
|
+
with open(log_path, "a", encoding="utf-8") as f:
|
|
324
|
+
f.write(f"[{ts}] HTTP error: {exc}\n")
|
|
325
|
+
raise RuntimeError(f"{name} HTTP error: {exc}") from exc
|
|
326
|
+
|
|
327
|
+
data = resp.json()
|
|
328
|
+
text_parts = []
|
|
329
|
+
for block in data.get("content", []):
|
|
330
|
+
if block.get("type") == "text":
|
|
331
|
+
text_parts.append(block["text"])
|
|
332
|
+
output = "\n".join(text_parts).strip()
|
|
333
|
+
|
|
334
|
+
usage = data.get("usage", {})
|
|
335
|
+
input_tokens = usage.get("input_tokens", 0)
|
|
336
|
+
output_tokens = usage.get("output_tokens", 0)
|
|
337
|
+
|
|
338
|
+
if log_path:
|
|
339
|
+
ts = time.strftime("%H:%M:%S")
|
|
340
|
+
with open(log_path, "a", encoding="utf-8") as f:
|
|
341
|
+
f.write(f"[{ts}] Response: {len(output)} chars, "
|
|
342
|
+
f"in={input_tokens} out={output_tokens}\n")
|
|
343
|
+
|
|
344
|
+
try:
|
|
345
|
+
log_agent_invocation(
|
|
346
|
+
name, len(prompt), len(output), session_id, "",
|
|
347
|
+
input_tokens=input_tokens, output_tokens=output_tokens, cost_usd=0.0,
|
|
348
|
+
)
|
|
349
|
+
except Exception:
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
return output, input_tokens, output_tokens
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
# ---------------------------------------------------------------------------
|
|
356
|
+
# CC subprocess invocation (Builder — needs file tools)
|
|
357
|
+
# ---------------------------------------------------------------------------
|
|
358
|
+
|
|
257
359
|
def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[Path] = None,
|
|
258
|
-
skip_permissions: bool = False,
|
|
360
|
+
skip_permissions: bool = False,
|
|
259
361
|
session_id: Optional[str] = None, timeout: int = AGENT_TIMEOUT,
|
|
260
362
|
model: str = "") -> tuple[str, int, int, int]:
|
|
261
363
|
"""Run a single claude --print invocation and return the output."""
|
|
@@ -264,8 +366,6 @@ def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[P
|
|
|
264
366
|
raise FileNotFoundError("'claude' CLI not found on PATH")
|
|
265
367
|
|
|
266
368
|
cmd = [claude_bin, "--print", "--verbose", "--output-format", "stream-json"]
|
|
267
|
-
if bare and os.environ.get("ANTHROPIC_API_KEY"):
|
|
268
|
-
cmd.append("--bare")
|
|
269
369
|
if skip_permissions:
|
|
270
370
|
cmd.append("--dangerously-skip-permissions")
|
|
271
371
|
if model:
|
|
@@ -280,10 +380,16 @@ def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[P
|
|
|
280
380
|
f.write(f"[{ts}] === Invoking {name} ===\n")
|
|
281
381
|
f.write(f"[{ts}] Prompt length: {len(prompt)} chars\n")
|
|
282
382
|
|
|
383
|
+
prompt_dir = log_path.parent if log_path else Path.home() / ".memstack" / "agent-runner"
|
|
384
|
+
prompt_dir.mkdir(parents=True, exist_ok=True)
|
|
385
|
+
prompt_file = prompt_dir / f"{name}_prompt.txt"
|
|
386
|
+
prompt_file.write_text(prompt, encoding="utf-8")
|
|
387
|
+
|
|
388
|
+
stdin_fh = open(prompt_file, "r", encoding="utf-8") # noqa: SIM115
|
|
283
389
|
global _current_process
|
|
284
390
|
proc = subprocess.Popen(
|
|
285
391
|
cmd,
|
|
286
|
-
stdin=
|
|
392
|
+
stdin=stdin_fh,
|
|
287
393
|
stdout=subprocess.PIPE,
|
|
288
394
|
stderr=subprocess.PIPE,
|
|
289
395
|
cwd=working_dir,
|
|
@@ -291,54 +397,50 @@ def _invoke_agent(name: str, prompt: str, working_dir: str, log_path: Optional[P
|
|
|
291
397
|
encoding="utf-8",
|
|
292
398
|
errors="replace",
|
|
293
399
|
env=_build_env(),
|
|
294
|
-
creationflags=
|
|
400
|
+
creationflags=0,
|
|
295
401
|
)
|
|
402
|
+
stdin_fh.close()
|
|
296
403
|
with _lock:
|
|
297
404
|
_current_process = proc
|
|
298
405
|
|
|
299
|
-
|
|
300
|
-
|
|
406
|
+
if log_path:
|
|
407
|
+
with open(log_path, "a", encoding="utf-8") as f:
|
|
408
|
+
f.write(f"[{ts}] PID: {proc.pid}\n")
|
|
301
409
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
pass
|
|
410
|
+
killed_by_watchdog = threading.Event()
|
|
411
|
+
|
|
412
|
+
def _watchdog_kill() -> None:
|
|
413
|
+
killed_by_watchdog.set()
|
|
414
|
+
proc.kill()
|
|
308
415
|
|
|
309
|
-
|
|
310
|
-
|
|
416
|
+
watchdog = threading.Timer(timeout, _watchdog_kill)
|
|
417
|
+
watchdog.daemon = True
|
|
418
|
+
watchdog.start()
|
|
311
419
|
|
|
312
420
|
try:
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
while True:
|
|
324
|
-
t_out.join(timeout=30)
|
|
325
|
-
rc = proc.poll()
|
|
326
|
-
if rc is not None:
|
|
327
|
-
break
|
|
328
|
-
if time.monotonic() > deadline:
|
|
329
|
-
proc.kill()
|
|
330
|
-
t_out.join(5)
|
|
331
|
-
t_err.join(5)
|
|
332
|
-
raise subprocess.TimeoutExpired(cmd, timeout)
|
|
333
|
-
t_out.join(5)
|
|
334
|
-
t_err.join(5)
|
|
335
|
-
stdout_data = "".join(stdout_chunks)
|
|
336
|
-
stderr_data = "".join(stderr_chunks)
|
|
421
|
+
stdout_data, stderr_data = proc.communicate()
|
|
422
|
+
except Exception as e:
|
|
423
|
+
watchdog.cancel()
|
|
424
|
+
with _lock:
|
|
425
|
+
if _current_process is proc:
|
|
426
|
+
_current_process = None
|
|
427
|
+
if log_path:
|
|
428
|
+
with open(log_path, "a", encoding="utf-8") as f:
|
|
429
|
+
f.write(f"[{ts}] communicate() error: {e}\n")
|
|
430
|
+
raise RuntimeError(f"{name} communicate() failed: {e}") from e
|
|
337
431
|
finally:
|
|
432
|
+
watchdog.cancel()
|
|
338
433
|
with _lock:
|
|
339
434
|
if _current_process is proc:
|
|
340
435
|
_current_process = None
|
|
341
436
|
|
|
437
|
+
if killed_by_watchdog.is_set():
|
|
438
|
+
raise subprocess.TimeoutExpired(cmd, timeout)
|
|
439
|
+
|
|
440
|
+
if log_path:
|
|
441
|
+
with open(log_path, "a", encoding="utf-8") as f:
|
|
442
|
+
f.write(f"[{ts}] communicate() returned: stdout={len(stdout_data)} stderr={len(stderr_data)}\n")
|
|
443
|
+
|
|
342
444
|
raw_stdout = stdout_data or ""
|
|
343
445
|
stderr = stderr_data or ""
|
|
344
446
|
output, input_tokens, output_tokens, cost_usd, context_tokens = _parse_stream_json(raw_stdout)
|
|
@@ -515,17 +617,18 @@ def _orchestrate(session: Session) -> None:
|
|
|
515
617
|
if session.user_name:
|
|
516
618
|
manager_prompt = f"The user's name is {session.user_name}.\n\n" + manager_prompt
|
|
517
619
|
try:
|
|
518
|
-
manager_output, m_in, m_out
|
|
519
|
-
"manager", manager_prompt,
|
|
620
|
+
manager_output, m_in, m_out = _invoke_api_agent(
|
|
621
|
+
"manager", manager_prompt,
|
|
622
|
+
system_prompt=SYSTEM_PROMPTS["manager"],
|
|
520
623
|
log_path=session_log_dir / "manager.log",
|
|
521
|
-
|
|
522
|
-
timeout=min(180, session.timeout),
|
|
624
|
+
timeout=min(600, session.timeout),
|
|
523
625
|
model=session.models.get("manager", ""),
|
|
626
|
+
session_id=session.session_id,
|
|
524
627
|
)
|
|
525
628
|
except subprocess.TimeoutExpired:
|
|
526
629
|
session.agents["manager"]["status"] = "timeout"
|
|
527
630
|
session.status = "error"
|
|
528
|
-
session.result = "Manager timed out after
|
|
631
|
+
session.result = "Manager timed out after 10 minutes. Try a simpler task description or break the task into smaller pieces."
|
|
529
632
|
session._save_state()
|
|
530
633
|
return
|
|
531
634
|
except RuntimeError:
|
|
@@ -536,7 +639,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
536
639
|
return
|
|
537
640
|
session.agents["manager"]["input_tokens"] += m_in
|
|
538
641
|
session.agents["manager"]["output_tokens"] += m_out
|
|
539
|
-
session.agents["manager"]["context_tokens"] =
|
|
642
|
+
session.agents["manager"]["context_tokens"] = m_in
|
|
540
643
|
session.agents["manager"]["last_output"] = (manager_output or "")[:500]
|
|
541
644
|
|
|
542
645
|
session.agents["manager"]["status"] = "done"
|
|
@@ -578,7 +681,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
578
681
|
builder_output, b_in, b_out, b_ctx = _invoke_agent(
|
|
579
682
|
"builder", builder_prompt, session.working_dir,
|
|
580
683
|
log_path=session_log_dir / "builder.log",
|
|
581
|
-
skip_permissions=True,
|
|
684
|
+
skip_permissions=True, session_id=session.session_id,
|
|
582
685
|
timeout=session.timeout,
|
|
583
686
|
model=session.models.get("builder", ""),
|
|
584
687
|
)
|
|
@@ -638,12 +741,13 @@ def _orchestrate(session: Session) -> None:
|
|
|
638
741
|
+ f"\n\nBuilder output (iteration {iteration}):\n{builder_output}"
|
|
639
742
|
)
|
|
640
743
|
try:
|
|
641
|
-
reviewer_output, r_in, r_out
|
|
642
|
-
"reviewer", reviewer_prompt,
|
|
744
|
+
reviewer_output, r_in, r_out = _invoke_api_agent(
|
|
745
|
+
"reviewer", reviewer_prompt,
|
|
746
|
+
system_prompt=SYSTEM_PROMPTS["reviewer"],
|
|
643
747
|
log_path=session_log_dir / "reviewer.log",
|
|
644
|
-
bare=True, session_id=session.session_id,
|
|
645
748
|
timeout=session.timeout,
|
|
646
749
|
model=session.models.get("reviewer", ""),
|
|
750
|
+
session_id=session.session_id,
|
|
647
751
|
)
|
|
648
752
|
except subprocess.TimeoutExpired:
|
|
649
753
|
session.agents["reviewer"]["status"] = "timeout"
|
|
@@ -659,7 +763,7 @@ def _orchestrate(session: Session) -> None:
|
|
|
659
763
|
return
|
|
660
764
|
session.agents["reviewer"]["input_tokens"] += r_in
|
|
661
765
|
session.agents["reviewer"]["output_tokens"] += r_out
|
|
662
|
-
session.agents["reviewer"]["context_tokens"] =
|
|
766
|
+
session.agents["reviewer"]["context_tokens"] = r_in
|
|
663
767
|
session.agents["reviewer"]["last_output"] = (reviewer_output or "")[:500]
|
|
664
768
|
|
|
665
769
|
session.agents["reviewer"]["status"] = "done"
|
|
@@ -802,7 +906,7 @@ def start_run(task: str, working_dir: Optional[str] = None, context: Optional[st
|
|
|
802
906
|
session._save_state()
|
|
803
907
|
|
|
804
908
|
_orchestration_thread = threading.Thread(
|
|
805
|
-
target=_orchestrate, args=(session,), daemon=
|
|
909
|
+
target=_orchestrate, args=(session,), daemon=False, name="orchestrator"
|
|
806
910
|
)
|
|
807
911
|
_orchestration_thread.start()
|
|
808
912
|
|
{memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/dashboard.html
RENAMED
|
@@ -1644,6 +1644,20 @@
|
|
|
1644
1644
|
</div>
|
|
1645
1645
|
</div>
|
|
1646
1646
|
|
|
1647
|
+
<div class="panel">
|
|
1648
|
+
<h3>Agent Runner Setup</h3>
|
|
1649
|
+
<p style="color:#8b949e;font-size:0.82rem;margin:0 0 0.8rem;">
|
|
1650
|
+
The Agent Runner requires an Anthropic API key for the Manager and Reviewer agents.
|
|
1651
|
+
The Builder uses Claude Code directly. Headroom is optional but recommended for token compression.
|
|
1652
|
+
</p>
|
|
1653
|
+
<div style="display:grid;grid-template-columns:auto 1fr;gap:0.5rem 1rem;font-size:0.82rem;align-items:start;">
|
|
1654
|
+
<span style="color:#8b949e;">API Key</span>
|
|
1655
|
+
<span id="settings-api-key-status" style="color:#c9d1d9;">—</span>
|
|
1656
|
+
<span style="color:#8b949e;">Headroom Proxy</span>
|
|
1657
|
+
<span id="settings-proxy-status" style="color:#c9d1d9;">—</span>
|
|
1658
|
+
</div>
|
|
1659
|
+
</div>
|
|
1660
|
+
|
|
1647
1661
|
<div class="panel">
|
|
1648
1662
|
<h3>Dashboard Info</h3>
|
|
1649
1663
|
<div style="display:grid;grid-template-columns:auto 1fr;gap:0.4rem 1.2rem;font-size:0.82rem;">
|
|
@@ -3481,6 +3495,21 @@ async function loadSettings() {
|
|
|
3481
3495
|
document.getElementById('settings-pro-dir').textContent = d.pro_skills_dir || '—';
|
|
3482
3496
|
document.getElementById('settings-stats-db').textContent = d.stats_db || '—';
|
|
3483
3497
|
document.getElementById('settings-sessions-dir').textContent = d.sessions_dir || '—';
|
|
3498
|
+
|
|
3499
|
+
const apiKeyEl = document.getElementById('settings-api-key-status');
|
|
3500
|
+
if (d.api_key_set) {
|
|
3501
|
+
apiKeyEl.innerHTML = '<span style="color:#3fb950;">✓</span> API key detected';
|
|
3502
|
+
} else {
|
|
3503
|
+
apiKeyEl.innerHTML = '<span style="color:#f85149;">✗</span> Not set — run <code style="background:#161b22;padding:0.1rem 0.4rem;border-radius:3px;color:#c9d1d9;">set ANTHROPIC_API_KEY=sk-ant-...</code> before starting the dashboard';
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
const proxyEl = document.getElementById('settings-proxy-status');
|
|
3507
|
+
if (d.base_url) {
|
|
3508
|
+
proxyEl.innerHTML = '<span style="color:#3fb950;">✓</span> Headroom proxy active at <code style="background:#161b22;padding:0.1rem 0.4rem;border-radius:3px;color:#c9d1d9;">' + d.base_url.replace(/</g,'<') + '</code>';
|
|
3509
|
+
} else {
|
|
3510
|
+
proxyEl.innerHTML = 'Optional: Install <a href="https://github.com/chopratejas/headroom" target="_blank" style="color:#58a6ff;">Headroom</a> for ~34% token savings';
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3484
3513
|
loadModelPrefs();
|
|
3485
3514
|
await loadUserProfile();
|
|
3486
3515
|
settingsLoaded = true;
|
{memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/dashboard.py
RENAMED
|
@@ -511,6 +511,8 @@ class _Handler(BaseHTTPRequestHandler):
|
|
|
511
511
|
"pro_skills_dir": str(home / ".memstack" / "pro-skills"),
|
|
512
512
|
"stats_db": str(DB_PATH),
|
|
513
513
|
"sessions_dir": str(home / ".memstack" / "agent-runner" / "sessions"),
|
|
514
|
+
"api_key_set": bool(os.environ.get("ANTHROPIC_API_KEY")),
|
|
515
|
+
"base_url": os.environ.get("ANTHROPIC_BASE_URL", ""),
|
|
514
516
|
}
|
|
515
517
|
body = json.dumps(data).encode()
|
|
516
518
|
self._respond(200, "application/json", body)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/__main__.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/categories.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/compression.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/config.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/indexer.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/license.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/memory_db.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/search.py
RENAMED
|
File without changes
|
{memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/server.py
RENAMED
|
File without changes
|
|
File without changes
|
{memstack_skill_loader-4.0.3 → memstack_skill_loader-4.0.4}/src/memstack_skill_loader/stats.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|