loki-mode 7.34.0 → 7.35.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/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/loki-mode?style=for-the-badge&logo=npm&logoColor=white&color=553DE9)](https://www.npmjs.com/package/loki-mode)
10
10
  [![npm downloads](https://img.shields.io/npm/dt/loki-mode?style=for-the-badge&logo=npm&logoColor=white&color=1FC5A8&label=downloads)](https://www.npmjs.com/package/loki-mode)
11
- [![GitHub stars](https://img.shields.io/github/stars/asklokesh/loki-mode?style=for-the-badge&logo=github&color=553DE9)](https://github.com/asklokesh/loki-mode)
11
+ [![GitHub stars](https://badgen.net/github/stars/asklokesh/loki-mode?label=Stars&color=553DE9&icon=github)](https://github.com/asklokesh/loki-mode/stargazers)
12
12
  [![Docker Pulls](https://img.shields.io/docker/pulls/asklokesh/loki-mode?style=for-the-badge&logo=docker&logoColor=white&color=2F71E3)](https://hub.docker.com/r/asklokesh/loki-mode)
13
13
  [![License](https://img.shields.io/badge/License-BUSL--1.1-36342E?style=for-the-badge)](LICENSE)
14
14
 
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, 11 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.34.0
6
+ # Loki Mode v7.35.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -398,4 +398,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
398
398
 
399
399
  ---
400
400
 
401
- **v7.34.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
401
+ **v7.35.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.34.0
1
+ 7.35.0
package/autonomy/loki CHANGED
@@ -8697,8 +8697,12 @@ cmd_api() {
8697
8697
 
8698
8698
  # Parse --host/--port (the old code ignored them, so `loki serve --port X`
8699
8699
  # silently bound 57374 and `--host 0.0.0.0` was dropped).
8700
+ # A trailing --help/-h on any subcommand (e.g. `loki api start --help`)
8701
+ # short-circuits to the help text instead of being swallowed and starting
8702
+ # the server (#574).
8700
8703
  while [ $# -gt 0 ]; do
8701
8704
  case "$1" in
8705
+ --help|-h) subcommand="help"; break ;;
8702
8706
  --port) port="${2:-$port}"; shift 2 ;;
8703
8707
  --port=*) port="${1#*=}"; shift ;;
8704
8708
  --host) host="${2:-$host}"; shift 2 ;;
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.34.0"
10
+ __version__ = "7.35.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -329,6 +329,27 @@
329
329
  text-overflow: ellipsis;
330
330
  white-space: nowrap;
331
331
  }
332
+ /* v7.35: the chip name is a switch-project affordance. Clickable names get
333
+ a pointer + hover accent; the active project's name is emphasized and
334
+ not clickable (already focused). */
335
+ .project-stop-row .project-stop-name.is-clickable {
336
+ cursor: pointer;
337
+ border-radius: 6px;
338
+ padding: 0 2px;
339
+ transition: color 0.12s ease, background 0.12s ease;
340
+ }
341
+ .project-stop-row .project-stop-name.is-clickable:hover {
342
+ color: var(--loki-accent, #553DE9);
343
+ background: var(--loki-bg-hover, rgba(85, 61, 233, 0.08));
344
+ }
345
+ .project-stop-row .project-stop-name.is-clickable:focus-visible {
346
+ outline: 2px solid var(--loki-accent, #553DE9);
347
+ outline-offset: 1px;
348
+ }
349
+ .project-stop-row .project-stop-name.is-active {
350
+ font-weight: 600;
351
+ color: var(--loki-accent, #553DE9);
352
+ }
332
353
  .project-stop-row button {
333
354
  padding: 2px 8px;
334
355
  background: transparent;
@@ -13799,6 +13820,16 @@ document.addEventListener('DOMContentLoaded', function() {
13799
13820
  var sel = document.getElementById('project-switcher');
13800
13821
  if (!sel) return;
13801
13822
  var stopList = document.getElementById('project-stop-list');
13823
+ // v7.35: focus a project by its working dir, then reload so every panel
13824
+ // re-fetches against it. The active section lives in the URL hash now, so
13825
+ // the reload lands the user on the SAME section (no reset to overview).
13826
+ // Shared by the dropdown <select> and the clickable project chips.
13827
+ function focusProject(dir) {
13828
+ var req = dir
13829
+ ? fetch('/api/focus', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ project_dir: dir }) })
13830
+ : fetch('/api/focus', { method: 'DELETE' });
13831
+ return req.then(function(){ window.location.reload(); }).catch(function(){ /* ignore */ });
13832
+ }
13802
13833
  // v7.7.30: build a per-row Stop control for each running project using
13803
13834
  // createElement + textContent only (never innerHTML for project-supplied
13804
13835
  // strings), so a project name can never inject markup.
@@ -13812,6 +13843,24 @@ document.addEventListener('DOMContentLoaded', function() {
13812
13843
  var name = document.createElement('span');
13813
13844
  name.className = 'project-stop-name';
13814
13845
  name.textContent = p.name || p.path || 'project';
13846
+ // v7.35: the chip name is now a clickable affordance that focuses
13847
+ // that project (same path as the dropdown). The Stop button keeps its
13848
+ // own handler; clicking the name never triggers Stop. is_active chips
13849
+ // are marked so the current project is visually obvious and its click
13850
+ // is a no-op (already focused).
13851
+ if (p.is_active) {
13852
+ name.classList.add('is-active');
13853
+ } else if (p.path) {
13854
+ name.classList.add('is-clickable');
13855
+ name.setAttribute('role', 'button');
13856
+ name.setAttribute('tabindex', '0');
13857
+ name.setAttribute('title', 'Switch to ' + (p.name || p.path));
13858
+ var go = function(ev){ if (ev) ev.stopPropagation(); focusProject(p.path); };
13859
+ name.addEventListener('click', go);
13860
+ name.addEventListener('keydown', function(ev){
13861
+ if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); go(ev); }
13862
+ });
13863
+ }
13815
13864
  var btn = document.createElement('button');
13816
13865
  btn.type = 'button';
13817
13866
  btn.textContent = 'Stop';
@@ -13858,14 +13907,7 @@ document.addEventListener('DOMContentLoaded', function() {
13858
13907
  .catch(function(){ /* offline / no endpoint: leave as-is */ });
13859
13908
  }
13860
13909
  sel.addEventListener('change', function(){
13861
- var dir = sel.value;
13862
- var req = dir
13863
- ? fetch('/api/focus', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ project_dir: dir }) })
13864
- : fetch('/api/focus', { method: 'DELETE' });
13865
- req.then(function(){
13866
- // Reload so every panel re-fetches against the newly focused project.
13867
- window.location.reload();
13868
- }).catch(function(){ /* ignore */ });
13910
+ focusProject(sel.value);
13869
13911
  });
13870
13912
  refresh();
13871
13913
  setInterval(refresh, 15000);
@@ -14091,6 +14133,17 @@ document.addEventListener('DOMContentLoaded', function() {
14091
14133
  // Scroll main content to top on section switch
14092
14134
  mainContent.scrollTop = 0;
14093
14135
  localStorage.setItem('loki-active-section', sectionId);
14136
+ // v7.35: reflect the active section in the URL hash so a refresh,
14137
+ // back-button, or a project switch (which reloads the page) lands the
14138
+ // user back on the SAME section instead of resetting to overview. The
14139
+ // hash is updated in place (replaceState) so it does not stack history
14140
+ // entries on every tab click.
14141
+ try {
14142
+ var nh = '#section=' + sectionId;
14143
+ if (window.location.hash !== nh) {
14144
+ history.replaceState(null, '', window.location.pathname + window.location.search + nh);
14145
+ }
14146
+ } catch (e) { /* file:// or sandboxed: hash routing best-effort */ }
14094
14147
  }
14095
14148
 
14096
14149
  navLinks.forEach(function(link) {
@@ -14103,8 +14156,35 @@ document.addEventListener('DOMContentLoaded', function() {
14103
14156
  });
14104
14157
  });
14105
14158
 
14106
- // Show the default section (overview) on load
14107
- switchSection('overview');
14159
+ // v7.35: restore the active section on load. Priority: URL hash
14160
+ // (#section=<id>, shareable + survives the project-switch reload) ->
14161
+ // localStorage (last-used on this machine) -> overview (default). Only
14162
+ // honor a section id that maps to a real nav link, so a stale/hand-edited
14163
+ // hash can never blank the page.
14164
+ function lokiInitialSection() {
14165
+ var fromHash = '';
14166
+ try {
14167
+ var m = (window.location.hash || '').match(/section=([A-Za-z0-9_-]+)/);
14168
+ if (m) fromHash = m[1];
14169
+ } catch (e) { /* ignore */ }
14170
+ var fromStore = '';
14171
+ try { fromStore = localStorage.getItem('loki-active-section') || ''; } catch (e) { /* ignore */ }
14172
+ var candidate = fromHash || fromStore || 'overview';
14173
+ if (!document.querySelector('.nav-link[data-section="' + candidate + '"]')) {
14174
+ candidate = 'overview';
14175
+ }
14176
+ return candidate;
14177
+ }
14178
+ switchSection(lokiInitialSection());
14179
+
14180
+ // Keep the view in sync if the hash changes (back/forward button, or a
14181
+ // shared link opened in the same tab).
14182
+ window.addEventListener('hashchange', function() {
14183
+ var m = (window.location.hash || '').match(/section=([A-Za-z0-9_-]+)/);
14184
+ if (m && document.querySelector('.nav-link[data-section="' + m[1] + '"]')) {
14185
+ switchSection(m[1]);
14186
+ }
14187
+ });
14108
14188
 
14109
14189
  // Keyboard shortcuts: Cmd/Ctrl + 1-7
14110
14190
  document.addEventListener('keydown', function(e) {
@@ -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.34.0
5
+ **Version:** v7.35.0
6
6
 
7
7
  ---
8
8
 
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var n6=Object.defineProperty;var a6=($)=>$;function s6($,Q){this[$]=a6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)n6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:s6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var S1={};h(S1,{lokiDir:()=>P,homeLokiDir:()=>o$,findRepoRootForVersion:()=>d$,REPO_ROOT:()=>m});import{resolve as n,dirname as l$}from"path";import{fileURLToPath as t6}from"url";import{existsSync as P$}from"fs";import{homedir as r6}from"os";function i6(){let $=N1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=l$($);if(Z===$)break;$=Z}return n(N1,"..","..","..")}function d$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=l$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o$(){return n(r6(),".loki")}var N1,m;var C=L(()=>{N1=l$(t6(import.meta.url));m=i6()});import{readFileSync as e6}from"fs";import{resolve as $Q,dirname as QQ}from"path";import{fileURLToPath as ZQ}from"url";function F$(){if($$!==null)return $$;let $="7.34.0";if(typeof $==="string"&&$.length>0)return $$=$,$$;try{let Q=QQ(ZQ(import.meta.url)),Z=d$(Q);$$=e6($Q(Z,"VERSION"),"utf-8").trim()}catch{$$="unknown"}return $$}var $$=null;var n$=L(()=>{C()});var C1={};h(C1,{runOrThrow:()=>zQ,run:()=>j,commandVersion:()=>KQ,commandExists:()=>f,ShellError:()=>a$});async function j($,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[W,K,U]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:W,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function zQ($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=XQ($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function XQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function KQ($,Q="--version"){if(!await f($))return null;let z=await j([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var a$;var d=L(()=>{a$=class a$ 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 a($){return WQ?"":$}var WQ,T,S,I,TZ,w,R,y,q;var c=L(()=>{WQ=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),S=a("\x1B[0;32m"),I=a("\x1B[1;33m"),TZ=a("\x1B[0;34m"),w=a("\x1B[0;36m"),R=a("\x1B[1m"),y=a("\x1B[2m"),q=a("\x1B[0m")});import{existsSync as TQ}from"fs";async function Q$(){if(B$!==void 0)return B$;let $="/opt/homebrew/bin/python3.12";if(TQ($))return B$=$,$;let Q=await f("python3.12");if(Q)return B$=Q,Q;let Z=await f("python3");return B$=Z,Z}async function Z$($,Q={}){let Z=await Q$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B$;var W$=L(()=>{d()});var t1={};h(t1,{runStatus:()=>gQ});import{existsSync as v,readFileSync as U$,readdirSync as l1,statSync as d1}from"fs";import{resolve as D,basename as xQ}from"path";import{homedir as NQ}from"os";async function DQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${q}
2
+ var n6=Object.defineProperty;var a6=($)=>$;function s6($,Q){this[$]=a6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)n6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:s6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var S1={};h(S1,{lokiDir:()=>P,homeLokiDir:()=>o$,findRepoRootForVersion:()=>d$,REPO_ROOT:()=>m});import{resolve as n,dirname as l$}from"path";import{fileURLToPath as t6}from"url";import{existsSync as P$}from"fs";import{homedir as r6}from"os";function i6(){let $=N1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=l$($);if(Z===$)break;$=Z}return n(N1,"..","..","..")}function d$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=l$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o$(){return n(r6(),".loki")}var N1,m;var C=L(()=>{N1=l$(t6(import.meta.url));m=i6()});import{readFileSync as e6}from"fs";import{resolve as $Q,dirname as QQ}from"path";import{fileURLToPath as ZQ}from"url";function F$(){if($$!==null)return $$;let $="7.35.0";if(typeof $==="string"&&$.length>0)return $$=$,$$;try{let Q=QQ(ZQ(import.meta.url)),Z=d$(Q);$$=e6($Q(Z,"VERSION"),"utf-8").trim()}catch{$$="unknown"}return $$}var $$=null;var n$=L(()=>{C()});var C1={};h(C1,{runOrThrow:()=>zQ,run:()=>j,commandVersion:()=>KQ,commandExists:()=>f,ShellError:()=>a$});async function j($,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[W,K,U]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:W,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function zQ($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=XQ($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function XQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function KQ($,Q="--version"){if(!await f($))return null;let z=await j([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var a$;var d=L(()=>{a$=class a$ 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 a($){return WQ?"":$}var WQ,T,S,I,TZ,w,R,y,q;var c=L(()=>{WQ=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),S=a("\x1B[0;32m"),I=a("\x1B[1;33m"),TZ=a("\x1B[0;34m"),w=a("\x1B[0;36m"),R=a("\x1B[1m"),y=a("\x1B[2m"),q=a("\x1B[0m")});import{existsSync as TQ}from"fs";async function Q$(){if(B$!==void 0)return B$;let $="/opt/homebrew/bin/python3.12";if(TQ($))return B$=$,$;let Q=await f("python3.12");if(Q)return B$=Q,Q;let Z=await f("python3");return B$=Z,Z}async function Z$($,Q={}){let Z=await Q$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B$;var W$=L(()=>{d()});var t1={};h(t1,{runStatus:()=>gQ});import{existsSync as v,readFileSync as U$,readdirSync as l1,statSync as d1}from"fs";import{resolve as D,basename as xQ}from"path";import{homedir as NQ}from"os";async function DQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${q}
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)
@@ -789,4 +789,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
789
789
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
790
790
  `),process.stderr.write(o6),2}}p1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var ZZ=await QZ(Bun.argv.slice(2));process.exit(ZZ);
791
791
 
792
- //# debugId=C89D42E5B326555264756E2164756E21
792
+ //# debugId=4A51F9C635720B1064756E2164756E21
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.34.0'
60
+ __version__ = '7.35.0'
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.34.0",
4
+ "version": "7.35.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 11 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
6
6
  "keywords": [
7
7
  "agent",