loki-mode 7.106.0 → 7.107.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.
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/mcp-launch.sh +8 -1
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/mcp/server.py +100 -2
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 8 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v7.
|
|
6
|
+
# Loki Mode v7.107.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -408,4 +408,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
408
408
|
|
|
409
409
|
---
|
|
410
410
|
|
|
411
|
-
**v7.
|
|
411
|
+
**v7.107.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.107.0
|
package/autonomy/mcp-launch.sh
CHANGED
|
@@ -160,11 +160,18 @@ into it (with your consent). The exact pip command is printed before it runs.
|
|
|
160
160
|
It then launches the server using that venv's python.
|
|
161
161
|
|
|
162
162
|
Options:
|
|
163
|
-
--transport stdio|http Transport to use (default: stdio).
|
|
163
|
+
--transport stdio|http Transport to use (default: stdio). The http transport
|
|
164
|
+
binds 127.0.0.1 (loopback only) explicitly.
|
|
164
165
|
--port N Port for http transport (default: 8421).
|
|
165
166
|
--help, -h Show this help and exit.
|
|
166
167
|
|
|
167
168
|
Environment:
|
|
169
|
+
LOKI_MCP_AUTH_TOKEN=<tok> Optional. Only affects --transport http. When set,
|
|
170
|
+
every HTTP request must send
|
|
171
|
+
'Authorization: Bearer <tok>' or it is rejected
|
|
172
|
+
401. When unset, the http server is loopback-only
|
|
173
|
+
and unauthenticated (unchanged behavior). Has no
|
|
174
|
+
effect on the default stdio transport.
|
|
168
175
|
LOKI_MCP_VENV=/abs/path Use a custom venv location instead of .loki/mcp-venv.
|
|
169
176
|
LOKI_NO_INSTALL_OFFER=1 Never prompt to install; print the manual command.
|
|
170
177
|
Wins over LOKI_MCP_AUTO_BOOTSTRAP (explicit no beats
|
package/dashboard/__init__.py
CHANGED
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v7.
|
|
5
|
+
**Version:** v7.107.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var X8=Object.defineProperty;var K8=($)=>$;function q8($,Q){this[$]=K8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)X8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:q8.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var v1={};b(v1,{lokiDir:()=>P,homeLokiDir:()=>T$,findRepoRootForVersion:()=>e$,REPO_ROOT:()=>h});import{resolve as r,dirname as i$}from"path";import{fileURLToPath as J8}from"url";import{existsSync as x$}from"fs";import{homedir as V8}from"os";function W8(){let $=y1;for(let Q=0;Q<6;Q++){if(x$(r($,"VERSION"))&&x$(r($,"autonomy/run.sh")))return $;let Z=i$($);if(Z===$)break;$=Z}return r(y1,"..","..","..")}function e$($){let Q=$;for(let Z=0;Z<6;Z++){if(x$(r(Q,"VERSION"))&&x$(r(Q,"autonomy/run.sh")))return Q;let z=i$(Q);if(z===Q)break;Q=z}return r($,"..","..","..")}function P(){return process.env.LOKI_DIR??r(process.cwd(),".loki")}function T$(){return r(V8(),".loki")}var y1,h;var C=L(()=>{y1=i$(J8(import.meta.url));h=W8()});import{readFileSync as U8}from"fs";import{resolve as H8,dirname as G8}from"path";import{fileURLToPath as B8}from"url";function N$(){if(Q$!==null)return Q$;let $="7.
|
|
2
|
+
var X8=Object.defineProperty;var K8=($)=>$;function q8($,Q){this[$]=K8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)X8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:q8.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var v1={};b(v1,{lokiDir:()=>P,homeLokiDir:()=>T$,findRepoRootForVersion:()=>e$,REPO_ROOT:()=>h});import{resolve as r,dirname as i$}from"path";import{fileURLToPath as J8}from"url";import{existsSync as x$}from"fs";import{homedir as V8}from"os";function W8(){let $=y1;for(let Q=0;Q<6;Q++){if(x$(r($,"VERSION"))&&x$(r($,"autonomy/run.sh")))return $;let Z=i$($);if(Z===$)break;$=Z}return r(y1,"..","..","..")}function e$($){let Q=$;for(let Z=0;Z<6;Z++){if(x$(r(Q,"VERSION"))&&x$(r(Q,"autonomy/run.sh")))return Q;let z=i$(Q);if(z===Q)break;Q=z}return r($,"..","..","..")}function P(){return process.env.LOKI_DIR??r(process.cwd(),".loki")}function T$(){return r(V8(),".loki")}var y1,h;var C=L(()=>{y1=i$(J8(import.meta.url));h=W8()});import{readFileSync as U8}from"fs";import{resolve as H8,dirname as G8}from"path";import{fileURLToPath as B8}from"url";function N$(){if(Q$!==null)return Q$;let $="7.107.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=G8(B8(import.meta.url)),Z=e$(Q);Q$=U8(H8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var $1=L(()=>{C()});var c1={};b(c1,{runOrThrow:()=>x8,run:()=>F,readStreamCapped:()=>u1,commandVersion:()=>S8,commandExists:()=>f,ShellError:()=>Q1,MAX_STDOUT_BYTES:()=>f1});async function u1($,Q=f1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:W}=await Z.read();if(K)break;if(!W)continue;if(q+=W.byteLength,q>Q){let V=W.byteLength-(q-Q);X+=z.decode(W.subarray(0,V),{stream:!0});break}X+=z.decode(W,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function F($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,W]=await Promise.all([u1(Z.stdout),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:W}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function x8($,Q={}){let Z=await F($,Q);if(Z.exitCode!==0)throw new Q1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=N8($),Z=await F(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function N8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function S8($,Q="--version"){if(!await f($))return null;let z=await F([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var f1=16777216,Q1;var d=L(()=>{Q1=class Q1 extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function t($){return D8?"":$}var D8,T,S,_,nZ,I,k,y,J;var c=L(()=>{D8=(process.env.NO_COLOR??"").length>0;T=t("\x1B[0;31m"),S=t("\x1B[0;32m"),_=t("\x1B[1;33m"),nZ=t("\x1B[0;34m"),I=t("\x1B[0;36m"),k=t("\x1B[1m"),y=t("\x1B[2m"),J=t("\x1B[0m")});import{existsSync as c8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(c8($))return A$=$,$;let Q=await f("python3.12");if(Q)return A$=Q,Q;let Z=await f("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return F([Z,"-c",$],Q)}var A$;var V$=L(()=>{d()});var J0={};b(J0,{runStatus:()=>U3});import{existsSync as v,readFileSync as U$,readdirSync as e1,statSync as $0}from"fs";import{resolve as D,basename as Q3}from"path";import{homedir as Z3}from"os";function Q0($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function Z0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*D$/Q);if(X>D$)X=D$;let q=D$-X,K=S;if(z>=80)K=T;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=Q0($),U=Q0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${U})`}async function X3(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${J}
|
|
3
3
|
`),process.stdout.write(`Install with:
|
|
4
4
|
`),process.stdout.write(` brew install jq (macOS)
|
|
5
5
|
`),process.stdout.write(` apt install jq (Debian/Ubuntu)
|
|
@@ -805,4 +805,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
805
805
|
`),2}default:return process.stderr.write(`Unknown command: ${Q}
|
|
806
806
|
`),process.stderr.write(z8),2}}i1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var EZ=await RZ(Bun.argv.slice(2));process.exit(EZ);
|
|
807
807
|
|
|
808
|
-
//# debugId=
|
|
808
|
+
//# debugId=FBA0E62D8D186E7164756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/mcp/server.py
CHANGED
|
@@ -2601,6 +2601,98 @@ except Exception as _magic_err:
|
|
|
2601
2601
|
print(f"[warn] magic_tools registration skipped: {_magic_err}", file=_sys.stderr)
|
|
2602
2602
|
|
|
2603
2603
|
|
|
2604
|
+
# ============================================================
|
|
2605
|
+
# HTTP TRANSPORT (loopback bind + optional bearer-token auth)
|
|
2606
|
+
# ============================================================
|
|
2607
|
+
|
|
2608
|
+
def _run_http_transport(port):
|
|
2609
|
+
"""Run the MCP server over Streamable HTTP, bound explicitly to loopback.
|
|
2610
|
+
|
|
2611
|
+
Two hardening properties over a bare FastMCP HTTP launch:
|
|
2612
|
+
|
|
2613
|
+
1. Explicit loopback bind. We set host='127.0.0.1' ourselves and run the
|
|
2614
|
+
ASGI app via uvicorn rather than relying on any implicit default, so a
|
|
2615
|
+
reader of `lsof`/`ss` sees 127.0.0.1 unambiguously and a future SDK
|
|
2616
|
+
default change cannot silently widen the bind to 0.0.0.0.
|
|
2617
|
+
|
|
2618
|
+
2. Optional bearer-token auth. When LOKI_MCP_AUTH_TOKEN is set in the
|
|
2619
|
+
environment, a Starlette middleware requires every HTTP request to
|
|
2620
|
+
carry `Authorization: Bearer <token>` (rejecting mismatches with 401).
|
|
2621
|
+
When the env var is unset or empty, NO middleware is installed at all,
|
|
2622
|
+
so the request path is exactly the unauthenticated FastMCP behavior.
|
|
2623
|
+
|
|
2624
|
+
FastMCP's own run(transport='streamable-http') internally does the same
|
|
2625
|
+
thing (build the Streamable-HTTP ASGI app, then uvicorn.run over it); we
|
|
2626
|
+
inline that so we can inject the explicit host and the optional middleware.
|
|
2627
|
+
|
|
2628
|
+
hasattr-guarded against the whole supported mcp 1.x range (requirements.txt
|
|
2629
|
+
pins mcp>=1.0.0,<2.0.0): if the installed SDK lacks streamable_http_app or
|
|
2630
|
+
the host/port settings, we fail with a clear message rather than an
|
|
2631
|
+
AttributeError.
|
|
2632
|
+
"""
|
|
2633
|
+
if not hasattr(mcp, "streamable_http_app"):
|
|
2634
|
+
logger.error(
|
|
2635
|
+
"Installed MCP SDK does not support Streamable HTTP transport "
|
|
2636
|
+
"(no FastMCP.streamable_http_app). Upgrade the 'mcp' package "
|
|
2637
|
+
"(pip install -U mcp) or use the default stdio transport."
|
|
2638
|
+
)
|
|
2639
|
+
sys.exit(1)
|
|
2640
|
+
|
|
2641
|
+
host = "127.0.0.1"
|
|
2642
|
+
|
|
2643
|
+
# Pin host/port on the SDK settings too, so any code that reads them (and
|
|
2644
|
+
# the SDK's own transport-security allowed_hosts, which defaults to
|
|
2645
|
+
# 127.0.0.1/localhost) stays consistent with what uvicorn actually binds.
|
|
2646
|
+
try:
|
|
2647
|
+
if hasattr(mcp, "settings"):
|
|
2648
|
+
if hasattr(mcp.settings, "host"):
|
|
2649
|
+
mcp.settings.host = host
|
|
2650
|
+
if hasattr(mcp.settings, "port"):
|
|
2651
|
+
mcp.settings.port = port
|
|
2652
|
+
except Exception as _set_err: # pragma: no cover - defensive
|
|
2653
|
+
logger.warning("Could not pin MCP host/port settings: %s", _set_err)
|
|
2654
|
+
|
|
2655
|
+
app = mcp.streamable_http_app()
|
|
2656
|
+
|
|
2657
|
+
token = os.environ.get("LOKI_MCP_AUTH_TOKEN", "")
|
|
2658
|
+
if token:
|
|
2659
|
+
# Only import the middleware pieces when auth is actually requested, so
|
|
2660
|
+
# the unauthenticated path has zero new dependencies or behavior.
|
|
2661
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
2662
|
+
from starlette.responses import JSONResponse
|
|
2663
|
+
|
|
2664
|
+
expected_header = "Bearer " + token
|
|
2665
|
+
|
|
2666
|
+
async def _require_bearer(request, call_next):
|
|
2667
|
+
# BaseHTTPMiddleware auto-forwards non-http scopes (lifespan etc.),
|
|
2668
|
+
# so the StreamableHTTPSessionManager lifespan still starts.
|
|
2669
|
+
provided = request.headers.get("authorization", "")
|
|
2670
|
+
# Constant-time compare to avoid leaking the token via timing.
|
|
2671
|
+
import hmac
|
|
2672
|
+
if not hmac.compare_digest(provided, expected_header):
|
|
2673
|
+
return JSONResponse(
|
|
2674
|
+
{"error": "unauthorized"},
|
|
2675
|
+
status_code=401,
|
|
2676
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
2677
|
+
)
|
|
2678
|
+
return await call_next(request)
|
|
2679
|
+
|
|
2680
|
+
app.add_middleware(BaseHTTPMiddleware, dispatch=_require_bearer)
|
|
2681
|
+
logger.info(
|
|
2682
|
+
"MCP HTTP auth enabled: bearer token required "
|
|
2683
|
+
"(LOKI_MCP_AUTH_TOKEN is set)."
|
|
2684
|
+
)
|
|
2685
|
+
else:
|
|
2686
|
+
logger.info(
|
|
2687
|
+
"MCP HTTP auth disabled: no LOKI_MCP_AUTH_TOKEN set "
|
|
2688
|
+
"(loopback-only, unauthenticated)."
|
|
2689
|
+
)
|
|
2690
|
+
|
|
2691
|
+
import uvicorn
|
|
2692
|
+
logger.info("MCP Streamable HTTP listening on http://%s:%d/mcp", host, port)
|
|
2693
|
+
uvicorn.run(app, host=host, port=port, log_level="info")
|
|
2694
|
+
|
|
2695
|
+
|
|
2604
2696
|
# ============================================================
|
|
2605
2697
|
# MAIN
|
|
2606
2698
|
# ============================================================
|
|
@@ -2610,7 +2702,9 @@ def main():
|
|
|
2610
2702
|
import atexit
|
|
2611
2703
|
parser = argparse.ArgumentParser(description='Loki Mode MCP Server')
|
|
2612
2704
|
parser.add_argument('--transport', choices=['stdio', 'http'], default='stdio',
|
|
2613
|
-
help='Transport mechanism (default: stdio)'
|
|
2705
|
+
help=('Transport mechanism (default: stdio). "http" binds '
|
|
2706
|
+
'127.0.0.1 (loopback) explicitly; set env '
|
|
2707
|
+
'LOKI_MCP_AUTH_TOKEN to require a bearer token.'))
|
|
2614
2708
|
parser.add_argument('--port', type=int, default=8421,
|
|
2615
2709
|
help='Port for HTTP transport (default: 8421)')
|
|
2616
2710
|
parser.add_argument('--check-sdk', action='store_true',
|
|
@@ -2636,7 +2730,11 @@ def main():
|
|
|
2636
2730
|
logger.info(f"Starting Loki Mode MCP server (transport: {args.transport})")
|
|
2637
2731
|
|
|
2638
2732
|
if args.transport == 'http':
|
|
2639
|
-
|
|
2733
|
+
# Explicit loopback bind + optional bearer-token auth. Note: FastMCP has
|
|
2734
|
+
# no 'http' transport literal (it uses 'streamable-http') and run() takes
|
|
2735
|
+
# no port= kwarg, so the old mcp.run(transport='http', port=...) call
|
|
2736
|
+
# never worked; _run_http_transport is the supported path.
|
|
2737
|
+
_run_http_transport(args.port)
|
|
2640
2738
|
else:
|
|
2641
2739
|
mcp.run(transport='stdio')
|
|
2642
2740
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "7.
|
|
4
|
+
"version": "7.107.0",
|
|
5
5
|
"description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 8 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "loki-mode",
|
|
4
4
|
"displayName": "Loki Mode",
|
|
5
|
-
"version": "7.
|
|
5
|
+
"version": "7.107.0",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|