loki-mode 11.72.5 → 11.74.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/company-seats.json +82 -0
- package/autonomy/loki +14 -4
- package/autonomy/trigger-schedule.py +414 -6
- package/completions/loki.bash +5 -0
- package/dashboard/__init__.py +1 -1
- package/docs/CLI-REFERENCE.md +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/v10/OPERATING-MODEL.md +20 -8
- package/docs/v10/RETRO-2026.md +650 -0
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/tools/cockpit/SPEC.md +275 -0
- package/tools/cockpit/index.html +146 -0
- package/tools/cockpit/roi-basis.json +33 -0
- package/tools/cockpit/server.py +946 -0
- package/web-app/dist/assets/{AdminPage-Dm_5zuet.js → AdminPage-tOzodPzi.js} +1 -1
- package/web-app/dist/assets/{Avatar-BvqpB-F4.js → Avatar-gnHOTpn7.js} +1 -1
- package/web-app/dist/assets/{Badge-cA69Pepr.js → Badge-D-zPIsIE.js} +1 -1
- package/web-app/dist/assets/{Button-CDmFgrwq.js → Button-Ba3teZuX.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-Iqjcnh-c.js → CockpitPage-DnB6jCFt.js} +1 -1
- package/web-app/dist/assets/{ComparePage-D8RAxAOR.js → ComparePage-Dv5VR5dI.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-GijzDi6f.js → ErrorBoundary-D3PxG1EY.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-Bqw8-22j.js → EvidenceReceiptPanel-CrpPxnCl.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-Bzf04hzj.js → GitHubIssuesPanel-F7xTGZKO.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-DGgDjMUo.js → GitHubPRsPanel-BYXG9iC7.js} +1 -1
- package/web-app/dist/assets/{HomePage-DkYbylcL.js → HomePage-CO9c8oIn.js} +2 -2
- package/web-app/dist/assets/{LoginPage-DcKcxe5E.js → LoginPage-CmN5NyRe.js} +1 -1
- package/web-app/dist/assets/{MagicPage-DhJvyXuv.js → MagicPage-BPUMtPt8.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-XuL_5vIR.js → MetricsPage-Dm3GHAsm.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-DJ_xp0Pk.js → NotFoundPage-Boe4TCcQ.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-tQud0wez.js → ProjectPage-BFc_Uj5w.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-DH0qu3Pr.js → ProjectsPage-DMQ5nIgH.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-C6awIK-D.js → SettingsPage-DDSWgjSO.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-BZmouaYY.js → ShowcasePage-C1zCzPa1.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-DlzkIHxb.js → SystemSettingsPage-D0CAotwF.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-BE9A2S7z.js → TeamsPage-CPYm4yhm.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-C6xj7zEx.js → TemplatesPage-BdoBMm29.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-s7aXRB6h.js → TerminalOutput-CuKnS9oh.js} +1 -1
- package/web-app/dist/assets/{activity-DFWHHxU2.js → activity-Cs5LdWlh.js} +1 -1
- package/web-app/dist/assets/{bell-Bvmj6_8N.js → bell-IueQl5bE.js} +1 -1
- package/web-app/dist/assets/{bot-CccLnU-x.js → bot-Cpzd92QS.js} +1 -1
- package/web-app/dist/assets/{check-CZhlpbNc.js → check-xIB1DeBr.js} +1 -1
- package/web-app/dist/assets/{chevron-left-0jhI7cBO.js → chevron-left-B5b17KZc.js} +1 -1
- package/web-app/dist/assets/{circle-alert-D1L0kBVz.js → circle-alert-BfWbFPEh.js} +1 -1
- package/web-app/dist/assets/{clock-DGWLNKjf.js → clock-By9_QIi9.js} +1 -1
- package/web-app/dist/assets/{cloud-a1MQAGdL.js → cloud-DIoOyUKF.js} +1 -1
- package/web-app/dist/assets/{code-xml-DxVV6byE.js → code-xml-DpKRt91V.js} +1 -1
- package/web-app/dist/assets/{database-D3924vBP.js → database-pPFHh_ki.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-C8x-FGop.js → dollar-sign-Cpg8N8aR.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-CnlvfUOb.js → file-code-corner-B6KJxWC_.js} +1 -1
- package/web-app/dist/assets/{file-plus--tA113s3.js → file-plus-C2KqIxgl.js} +1 -1
- package/web-app/dist/assets/{globe-0CjDC5UM.js → globe-Dfp84gVc.js} +1 -1
- package/web-app/dist/assets/{hammer-CbqEYcGU.js → hammer-8Y1MYrke.js} +1 -1
- package/web-app/dist/assets/{index-CLoaV7sW.js → index-Diz9R0Ad.js} +3 -3
- package/web-app/dist/assets/{layers-9yjLnKlC.js → layers-Bl1-P7Mb.js} +1 -1
- package/web-app/dist/assets/{loader-circle-Ca13l3wn.js → loader-circle-BBLKWE-I.js} +1 -1
- package/web-app/dist/assets/{lock-B4iSBE00.js → lock-CjVUTsEb.js} +1 -1
- package/web-app/dist/assets/{package-C0yE2f0g.js → package-D80urViP.js} +1 -1
- package/web-app/dist/assets/{plus-CdRL6Oo5.js → plus-EBsGo-WE.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-BlIOCuip.js → refresh-cw-Cj4D7iA7.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-CGjJ0OjA.js → rotate-ccw-D4fUvKp7.js} +1 -1
- package/web-app/dist/assets/{scroll-text-BWKkwBJS.js → scroll-text-KPjNlar2.js} +1 -1
- package/web-app/dist/assets/{server-D6NDFit1.js → server-BxSS3qLC.js} +1 -1
- package/web-app/dist/assets/{shield-alert-CA9E0ro3.js → shield-alert-kmsOLKh_.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-pGSCT8yq.js → thumbs-up-BFA68HSV.js} +1 -1
- package/web-app/dist/assets/{trash-2--Vr-M4Ty.js → trash-2-DPSwmDYc.js} +1 -1
- package/web-app/dist/assets/{trending-down-Ci4vIc_h.js → trending-down-C8cVBNwP.js} +1 -1
- package/web-app/dist/assets/{trending-up-BGiZ0e9c.js → trending-up-BazAYgbt.js} +1 -1
- package/web-app/dist/assets/{upload-BAVIKX0b.js → upload-B14dm_MM.js} +1 -1
- package/web-app/dist/assets/{usePolling-COCviyJj.js → usePolling-ELsOlZfz.js} +1 -1
- package/web-app/dist/assets/{user-BASUHaOe.js → user-4bTC0Fne.js} +1 -1
- package/web-app/dist/index.html +1 -1
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var Fn0=Object.create;var{getPrototypeOf:On0,defineProperty:qG,getOwnPropertyNames:m80,getOwnPropertyDescriptor:Ln0}=Object,u80=Object.prototype.hasOwnProperty;function d80($){return this[$]}var jn0,An0,Cn0=($,Z,J)=>{var X=$!=null&&typeof $==="object";if(X){var Y=Z?jn0??=new WeakMap:An0??=new WeakMap,Q=Y.get($);if(Q)return Q}J=$!=null?Fn0(On0($)):{};let W=Z||!$||!$.__esModule?qG(J,"default",{value:$,enumerable:!0}):J;for(let z of m80($))if(!u80.call(W,z))qG(W,z,{get:d80.bind($,z),enumerable:!0});if(X)Y.set($,W);return W},Tn0=($)=>{var Z=(g80??=new WeakMap).get($),J;if(Z)return Z;if(Z=qG({},"__esModule",{value:!0}),$&&typeof $==="object"||typeof $==="function"){for(var X of m80($))if(!u80.call(Z,X))qG(Z,X,{get:d80.bind($,X),enumerable:!(J=Ln0($,X))||J.enumerable})}return g80.set($,Z),Z},g80,fO=($,Z)=>()=>(Z||$((Z={exports:{}}).exports,Z),Z.exports);var Pn0=($)=>$;function In0($,Z){this[$]=Pn0.bind(null,Z)}var P0=($,Z)=>{for(var J in Z)qG($,J,{get:Z[J],enumerable:!0,configurable:!0,set:In0.bind(Z,J)})};var w=($,Z)=>()=>($&&(Z=$($=0)),Z);var q6=import.meta.require;var p80={};P0(p80,{lokiDirFor:()=>gO,lokiDir:()=>k0,homeLokiDir:()=>K$,findRepoRootForVersion:()=>UG,REPO_ROOT:()=>R0});import{resolve as b1,dirname as ex}from"path";import{fileURLToPath as Rn0}from"url";import{existsSync as hO}from"fs";import{homedir as En0}from"os";function Dn0(){let $=c80;for(let Z=0;Z<6;Z++){if(hO(b1($,"VERSION"))&&hO(b1($,"autonomy/run.sh")))return $;let J=ex($);if(J===$)break;$=J}return b1(c80,"..","..","..")}function UG($){let Z=$;for(let J=0;J<6;J++){if(hO(b1(Z,"VERSION"))&&hO(b1(Z,"autonomy/run.sh")))return Z;let X=ex(Z);if(X===Z)break;Z=X}return b1($,"..","..","..")}function gO($){return process.env.LOKI_DIR??b1($,".loki")}function k0(){return gO(process.cwd())}function K$(){return b1(En0(),".loki")}var c80,R0;var X8=w(()=>{c80=ex(Rn0(import.meta.url));R0=Dn0()});import{readFileSync as wn0}from"fs";import{resolve as _n0,dirname as xn0}from"path";import{fileURLToPath as kn0}from"url";function y7(){if(iJ!==null)return iJ;let $="11.
|
|
2
|
+
var Fn0=Object.create;var{getPrototypeOf:On0,defineProperty:qG,getOwnPropertyNames:m80,getOwnPropertyDescriptor:Ln0}=Object,u80=Object.prototype.hasOwnProperty;function d80($){return this[$]}var jn0,An0,Cn0=($,Z,J)=>{var X=$!=null&&typeof $==="object";if(X){var Y=Z?jn0??=new WeakMap:An0??=new WeakMap,Q=Y.get($);if(Q)return Q}J=$!=null?Fn0(On0($)):{};let W=Z||!$||!$.__esModule?qG(J,"default",{value:$,enumerable:!0}):J;for(let z of m80($))if(!u80.call(W,z))qG(W,z,{get:d80.bind($,z),enumerable:!0});if(X)Y.set($,W);return W},Tn0=($)=>{var Z=(g80??=new WeakMap).get($),J;if(Z)return Z;if(Z=qG({},"__esModule",{value:!0}),$&&typeof $==="object"||typeof $==="function"){for(var X of m80($))if(!u80.call(Z,X))qG(Z,X,{get:d80.bind($,X),enumerable:!(J=Ln0($,X))||J.enumerable})}return g80.set($,Z),Z},g80,fO=($,Z)=>()=>(Z||$((Z={exports:{}}).exports,Z),Z.exports);var Pn0=($)=>$;function In0($,Z){this[$]=Pn0.bind(null,Z)}var P0=($,Z)=>{for(var J in Z)qG($,J,{get:Z[J],enumerable:!0,configurable:!0,set:In0.bind(Z,J)})};var w=($,Z)=>()=>($&&(Z=$($=0)),Z);var q6=import.meta.require;var p80={};P0(p80,{lokiDirFor:()=>gO,lokiDir:()=>k0,homeLokiDir:()=>K$,findRepoRootForVersion:()=>UG,REPO_ROOT:()=>R0});import{resolve as b1,dirname as ex}from"path";import{fileURLToPath as Rn0}from"url";import{existsSync as hO}from"fs";import{homedir as En0}from"os";function Dn0(){let $=c80;for(let Z=0;Z<6;Z++){if(hO(b1($,"VERSION"))&&hO(b1($,"autonomy/run.sh")))return $;let J=ex($);if(J===$)break;$=J}return b1(c80,"..","..","..")}function UG($){let Z=$;for(let J=0;J<6;J++){if(hO(b1(Z,"VERSION"))&&hO(b1(Z,"autonomy/run.sh")))return Z;let X=ex(Z);if(X===Z)break;Z=X}return b1($,"..","..","..")}function gO($){return process.env.LOKI_DIR??b1($,".loki")}function k0(){return gO(process.cwd())}function K$(){return b1(En0(),".loki")}var c80,R0;var X8=w(()=>{c80=ex(Rn0(import.meta.url));R0=Dn0()});import{readFileSync as wn0}from"fs";import{resolve as _n0,dirname as xn0}from"path";import{fileURLToPath as kn0}from"url";function y7(){if(iJ!==null)return iJ;let $="11.74.0";if(typeof $==="string"&&$.length>0)return iJ=$,iJ;try{let Z=xn0(kn0(import.meta.url)),J=UG(Z);iJ=wn0(_n0(J,"VERSION"),"utf-8").trim()}catch{iJ="unknown"}return iJ}var iJ=null;var y1=w(()=>{X8()});import{mkdirSync as Zk,readFileSync as mO,realpathSync as Sn0,statSync as bn0,writeFileSync as Jk}from"fs";import{dirname as kQ,join as $k,resolve as l80}from"path";function gn0(){return l80(K$(),"cache","update-check.json")}function v1($){let Z=/^(\d+)\.(\d+)\.(\d+)$/.exec($.trim());if(!Z)return null;return[Number(Z[1]),Number(Z[2]),Number(Z[3])]}function BG($,Z){let J=v1($),X=v1(Z);if(!J||!X)return!1;let[Y,Q,W]=J,[z,K,V]=X;if(Y!==z)return Y>z;if(Q!==K)return Q>K;return W>V}function i80(){let $=(process.env.LOKI_NO_UPDATE_CHECK??"").trim().toLowerCase();if($==="1"||$==="true"||$==="yes")return!0;if(process.env.CI)return!0;if(!process.stdout.isTTY)return!0;return!1}function a80($){try{let Z=mO($,"utf-8"),J=JSON.parse(Z);if(typeof J.checkedAt==="number"&&typeof J.latest==="string"&&v1(J.latest)!==null)return{checkedAt:J.checkedAt,latest:J.latest}}catch{}return null}function mn0($){try{let Z=JSON.parse(mO($,"utf-8")).failedAt;if(typeof Z==="number")return Z}catch{}return null}function un0($,Z){let J=a80($);try{Zk(kQ($),{recursive:!0}),Jk($,JSON.stringify({...J??{},failedAt:Z}),"utf-8")}catch{}}function dn0($,Z){try{Zk(kQ($),{recursive:!0}),Jk($,JSON.stringify(Z),"utf-8")}catch{}}async function cn0(){try{let $=await fetch(yn0,{signal:AbortSignal.timeout(fn0),headers:{accept:"application/json"}});if(!$.ok)return null;let Z=await $.json();if(typeof Z.version==="string"&&v1(Z.version)!==null)return Z.version}catch{}return null}async function pn0($=Date.now(),Z=cn0,J=gn0()){let X=a80(J);if(X&&$-X.checkedAt<vn0)return X.latest;let Y=mn0(J);if(Y!==null&&$-Y>=0&&$-Y<hn0)return null;let Q=await Z();if(Q===null)return un0(J,$),null;return dn0(J,{checkedAt:$,latest:Q}),Q}function ln0($,Z=process.env){if(v1($)===null)return null;let X=Z.PATH;if(!X)return null;let Y=new Set;for(let Q of X.split(":")){if(!Q)continue;let W=$k(Q,"loki"),z;try{bn0(W),z=Sn0(W)}catch{continue}if(Y.has(z))continue;Y.add(z);try{let K=$k(kQ(kQ(z)),"package.json"),V=JSON.parse(mO(K,"utf8")).version;if(typeof V!=="string")continue;if(BG(V,$))return{path:W,version:V}}catch{continue}}return null}function in0(){return l80(K$(),"cache","last-seen-version.json")}function an0($,Z={}){try{if(i80())return!1;if(v1($)===null)return!1;let J=Z.seenFile??in0(),X=null;try{let Q=JSON.parse(mO(J,"utf-8")).version;if(typeof Q==="string"&&v1(Q)!==null)X=Q}catch{}if(X!==null&&!BG($,X))return!1;try{Zk(kQ(J),{recursive:!0}),Jk(J,JSON.stringify({version:$}),"utf-8")}catch{return!1}if(X===null)return!1;return(Z.write??((Q)=>process.stderr.write(Q)))(`Loki Mode upgraded ${X} -> ${$}. What is new: https://github.com/asklokesh/loki-mode/releases/tag/v${$}
|
|
3
3
|
`),!0}catch{return!1}}function nn0($){let Z=$.replace(/\\/g,"/");if(Z.includes("/Cellar/"))return"brew upgrade loki-mode";if(/\/pnpm\//.test(Z)||Z.includes("/.pnpm/"))return"pnpm add -g loki-mode (or npm i -g loki-mode)";if(Z.includes("/yarn/global/")||Z.includes("/.yarn/"))return"yarn global add loki-mode (or npm i -g loki-mode)";if(Z.includes("/.bun/"))return"bun install -g loki-mode (or npm i -g loki-mode)";if(Z.includes("/lib/node_modules/"))return"npm i -g loki-mode (or bun install -g loki-mode)";if(Z.includes("/homebrew/"))return"brew upgrade loki-mode";return"bun install -g loki-mode (or npm i -g loki-mode)"}async function n80($,Z={}){try{if(i80())return;if(v1($)===null)return;let J=Z.write??((Q)=>process.stderr.write(Q));an0($,{write:J,...Z.cacheFile?{seenFile:$k(kQ(Z.cacheFile),"last-seen-version.json")}:{}});let X=ln0($,Z.env??process.env);if(X!==null){J(`Loki Mode ${X.version} is installed but not the one running (you are running ${$}).
|
|
4
4
|
Another loki earlier on PATH is winning. Newer copy: ${X.path}
|
|
5
5
|
Run \`which -a loki\` to see the order, then remove or re-point the earlier entry.
|
|
@@ -3307,4 +3307,4 @@ Unknown commands exit 2 with a suggestion; run 'loki help' for the full list.
|
|
|
3307
3307
|
`);{let{suggestCommand:X}=await Promise.resolve().then(() => (u5(),D90)),Y=Z===void 0?void 0:X(Z);if(Y)process.stderr.write(`Did you mean 'loki ${Y}'?
|
|
3308
3308
|
`)}return process.stderr.write(Gn0),2}}Ek0();var Hn0=()=>globalThis[Symbol.for("loki.ownsStopSignals")]===!0;process.on("SIGINT",()=>{if(!Hn0())process.exit(130)});process.on("SIGTERM",()=>{if(!Hn0())process.exit(143)});var Wb9=await Qb9(Bun.argv.slice(2));process.exit(Wb9);
|
|
3309
3309
|
|
|
3310
|
-
//# debugId=
|
|
3310
|
+
//# debugId=04D54A537C48D8D6742460A5C4362109
|
package/mcp/__init__.py
CHANGED
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": "11.
|
|
4
|
+
"version": "11.74.0",
|
|
5
5
|
"description": "An autonomous software factory that knows what it is supposed to deliver, and proves it did.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -160,5 +160,5 @@
|
|
|
160
160
|
"dependencies": {
|
|
161
161
|
"@resvg/resvg-wasm": "^2.6.2"
|
|
162
162
|
},
|
|
163
|
-
"gitHead": "
|
|
163
|
+
"gitHead": "530b63296b68d2ce8b00f5517a2d191a6c46c7a9"
|
|
164
164
|
}
|
|
@@ -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": "11.
|
|
5
|
+
"version": "11.74.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",
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# CEO cockpit: specification
|
|
2
|
+
|
|
3
|
+
Status: spec only (owner: platform-lead to build; chief-of-staff to accept).
|
|
4
|
+
Source of the ask: docs/v10/RETRO-2026.md section 6 item 6 (CEO console) and
|
|
5
|
+
item 7 (ROI ledger).
|
|
6
|
+
|
|
7
|
+
New-file justification (CLAUDE.md "extend, never duplicate"): no existing file
|
|
8
|
+
owns company operations for the founder. `dashboard/server.py` is the
|
|
9
|
+
user-facing per-run product dashboard and must stay free of company state;
|
|
10
|
+
`scripts/v10-pulse.sh`, `tools/cost-history.py` and `scripts/usage-governor.py`
|
|
11
|
+
already own pulse, run cost and usage, so the cockpit only invokes them and
|
|
12
|
+
renders their output. It re-implements none of their logic.
|
|
13
|
+
|
|
14
|
+
## 1. Goals and non-goals
|
|
15
|
+
Goals:
|
|
16
|
+
- One page where the CEO sees, within 5 seconds of opening, what the company
|
|
17
|
+
is doing, what shipped, what is blocked and what it costs against value.
|
|
18
|
+
- One write path: a founder command box that appends a row to
|
|
19
|
+
docs/v10/FOUNDER-QUEUE.md for the chief-of-staff to act on.
|
|
20
|
+
Non-goals:
|
|
21
|
+
- No direct control. Stop-all, dispatch and release actions are queue rows
|
|
22
|
+
("STOP ALL", "pause release") that the chief-of-staff executes by recorded
|
|
23
|
+
PID or through release-reliability-lead. The cockpit never signals a
|
|
24
|
+
process, edits BOARD.md, runs git writes or calls npm publish.
|
|
25
|
+
- No hosted mode, no auth provider, no telemetry.
|
|
26
|
+
|
|
27
|
+
## 2. Hard constraints
|
|
28
|
+
1. Server: Python 3 standard library only (`http.server`,
|
|
29
|
+
`ThreadingHTTPServer`, `json`, `subprocess`, `fcntl`, `secrets`). No pip
|
|
30
|
+
dependency.
|
|
31
|
+
2. Client: one static file `tools/cockpit/index.html` with inline CSS and JS.
|
|
32
|
+
No build step, no CDN, no framework. Works offline.
|
|
33
|
+
3. Bind 127.0.0.1 only. The `--host` flag accepts only `127.0.0.1` or `::1`;
|
|
34
|
+
any other value exits 64 with a message. Never `0.0.0.0`.
|
|
35
|
+
4. Read-only except `POST /api/queue`, which only appends to FOUNDER-QUEUE.md.
|
|
36
|
+
5. Testable headless: `--once --json` prints the full snapshot and exits; no
|
|
37
|
+
browser is ever opened (honour LOKI_NO_BROWSER; no `webbrowser` import).
|
|
38
|
+
6. Every shell-out has a timeout (default 20 s; pulse 60 s) and a cache, so a
|
|
39
|
+
hung `gh` or `npm` never blocks the page. A failed source renders
|
|
40
|
+
UNKNOWN with the error line, never a blank or a 0.
|
|
41
|
+
7. No emojis, no em or en dashes in code, UI text or output.
|
|
42
|
+
|
|
43
|
+
## 3. Files
|
|
44
|
+
- `tools/cockpit/server.py`: server, collectors, snapshot builder, CLI.
|
|
45
|
+
- `tools/cockpit/index.html`: the page.
|
|
46
|
+
- `tools/cockpit/roi-basis.json`: the salary basis and ROI assumptions (section
|
|
47
|
+
5.6). Tracked, edited by hand or by the chief-of-staff, versioned in git.
|
|
48
|
+
- `tests/test-cockpit.sh`: headless test, registered in
|
|
49
|
+
`tests/run-all-tests.sh` and the shard map (retro 3.3.21).
|
|
50
|
+
- Runtime only (untracked, under `.loki/`): `.loki/company/status/<slug>.json`
|
|
51
|
+
(written by each seat), `.loki/cockpit/cache/*.json`, `.loki/cockpit/token`.
|
|
52
|
+
|
|
53
|
+
## 4. CLI
|
|
54
|
+
```
|
|
55
|
+
python3 tools/cockpit/server.py [--port 8787] [--host 127.0.0.1]
|
|
56
|
+
[--repo-root PATH] [--once] [--json]
|
|
57
|
+
[--panel NAME]
|
|
58
|
+
```
|
|
59
|
+
- `--once --json`: build one snapshot, print it, exit 0 (exit 2 if any panel
|
|
60
|
+
is UNKNOWN, mirroring the pulse contract 0 clean, 1 violation, 2 unknown;
|
|
61
|
+
exit 1 if the pulse reports a VIOLATION).
|
|
62
|
+
- `--panel NAME`: restrict `--once` to one panel (pulse, releases, board,
|
|
63
|
+
agents, okrs, roi, queue).
|
|
64
|
+
- Env overrides (for tests and fixtures): `COCKPIT_NPM_CMD`, `COCKPIT_GH_CMD`,
|
|
65
|
+
`COCKPIT_PULSE_CMD`, `BOARD_MD`, `FOUNDER_QUEUE_MD`, `RETRO_MD`,
|
|
66
|
+
`USAGE_READINGS_TSV`, `COST_HISTORY_FILE`, `COCKPIT_STATUS_DIR`,
|
|
67
|
+
`COCKPIT_NOW` (ISO8601 UTC, for deterministic ages).
|
|
68
|
+
- On start the server prints `cockpit: http://127.0.0.1:<port>/` and its PID,
|
|
69
|
+
and writes the PID to `.loki/cockpit/server.pid` so it is stopped by
|
|
70
|
+
recorded PID only.
|
|
71
|
+
|
|
72
|
+
## 5. Panels and data sources
|
|
73
|
+
Each panel in the snapshot has the shape
|
|
74
|
+
`{status: OK|VIOLATION|UNKNOWN|UNMEASURED, as_of, source_cmd, data, error}`.
|
|
75
|
+
`source_cmd` is the exact command or file read, shown on the page under each
|
|
76
|
+
panel (evidence rule, retro 3.1.1).
|
|
77
|
+
|
|
78
|
+
### 5.1 Pulse
|
|
79
|
+
- Source: `bash scripts/v10-pulse.sh` (override `COCKPIT_PULSE_CMD`), run with
|
|
80
|
+
`PULSE_REPO_ROOT` set to the repo root. Cached 90 s; refreshed in a
|
|
81
|
+
background thread so a request never waits on it.
|
|
82
|
+
- Parse: every `Key: value` line into `metrics`; every line under
|
|
83
|
+
`=== VIOLATIONS ===` starting `VIOLATION:` into `violations`; the
|
|
84
|
+
`UNKNOWN metrics:` and `STALE metrics` lines into lists. Exit code 0, 1 or 2
|
|
85
|
+
maps to OK, VIOLATION, UNKNOWN.
|
|
86
|
+
- Render: violations first in a red band, top one largest; then the metric
|
|
87
|
+
table; STALE values shown with their age.
|
|
88
|
+
|
|
89
|
+
### 5.2 Releases
|
|
90
|
+
- npm: `npm view loki-mode time dist-tags --json` (override
|
|
91
|
+
`COCKPIT_NPM_CMD`). Show `dist-tags.latest`, `dist-tags.next`, the last 20
|
|
92
|
+
versions with publish time, releases in the last hour and 24 h, and minutes
|
|
93
|
+
since the last publish.
|
|
94
|
+
- Release runs: `gh run list --workflow release.yml --limit 20 --json
|
|
95
|
+
databaseId,status,conclusion,headSha,createdAt,displayTitle,url` (override
|
|
96
|
+
`COCKPIT_GH_CMD`). Show each run with conclusion and link.
|
|
97
|
+
- Local: `VERSION` file and `git -C <root> describe --tags --abbrev=0`.
|
|
98
|
+
- Derived flags: VERSION vs next vs latest divergence (retro 3.2.27); a
|
|
99
|
+
release run with conclusion failure on the newest VERSION SHA is a
|
|
100
|
+
VIOLATION. A cached packument is not proof: the panel labels npm data
|
|
101
|
+
"registry view" and never claims a tarball is fetchable (retro 3.2.7).
|
|
102
|
+
|
|
103
|
+
### 5.3 BOARD flow
|
|
104
|
+
- Source: `docs/v10/BOARD.md` (override `BOARD_MD`). Parse table rows with
|
|
105
|
+
columns `ID | Owner | Branch @ SHA | File set | Tier | Status | Notes`.
|
|
106
|
+
Status cell is `<token>@YYYY-MM-DDTHH:MMZ`; tokens: ready, building, review,
|
|
107
|
+
review-blocked, approved, merged, released, blocked, rejected, parked.
|
|
108
|
+
- Render: count per token (flow strip ready, building, review, approved,
|
|
109
|
+
merged, released), the active rows (building, review, review-blocked,
|
|
110
|
+
approved, blocked) with age in minutes against their tier budget (LOW 15,
|
|
111
|
+
MEDIUM 30, HIGH 60 per OPERATING-MODEL.md), and an "unparsed rows" list.
|
|
112
|
+
Ready count below 30 is flagged (D91).
|
|
113
|
+
|
|
114
|
+
### 5.4 Agents
|
|
115
|
+
- Source: `.loki/company/status/<slug>.json` for the 10 slugs in
|
|
116
|
+
`.claude/agents/` (list derived from that directory, never hard-coded).
|
|
117
|
+
Schema: `{seat, model, doing, slice, since, next, blocked_on, updated}`.
|
|
118
|
+
- Render one card per seat: model, what it is doing, slice ID linking to the
|
|
119
|
+
BOARD row, minutes since `updated`. A seat with no file is UNKNOWN; a file
|
|
120
|
+
older than 60 min is STALE; `blocked_on` non-empty is highlighted.
|
|
121
|
+
- Also show the seat's owned root causes, parsed from the "Root causes
|
|
122
|
+
owned" section of its agent file.
|
|
123
|
+
|
|
124
|
+
### 5.5 OKRs
|
|
125
|
+
- Source: `docs/v10/RETRO-2026.md` section 6 (override `RETRO_MD`): the 20
|
|
126
|
+
numbered items, each parsed into title (first sentence), owner (text after
|
|
127
|
+
`Owner:` up to the next period) and metric (text after `Metric:`).
|
|
128
|
+
- Current values: `.loki/company/okrs.json`, written by the owning seats,
|
|
129
|
+
shape `{"<item number>": {value, target, status, source_cmd, as_of}}`.
|
|
130
|
+
- Render the 20 rows ranked as in the retro. An item with no current value
|
|
131
|
+
shows UNMEASURED, never 0 and never green. A value without `source_cmd` is
|
|
132
|
+
shown as UNCITED and counts against verification-scientist.
|
|
133
|
+
|
|
134
|
+
### 5.6 ROI
|
|
135
|
+
Two sides, always labelled with their measurement state.
|
|
136
|
+
|
|
137
|
+
Agent cost (left side):
|
|
138
|
+
- Plan cost: `roi-basis.json` field `plan_usd_per_month` (default 200, the
|
|
139
|
+
founder's stated spend, retro RC9 evidence), prorated per day.
|
|
140
|
+
- Usage share: `docs/v10/usage-readings.tsv` (columns `utc_time
|
|
141
|
+
window_percent weekly_percent`) and `python3 scripts/usage-governor.py
|
|
142
|
+
--json`. These are plan-usage percentages, not tokens or dollars; the panel
|
|
143
|
+
says so and shows weekly percent used.
|
|
144
|
+
- Metered run cost: `python3 tools/cost-history.py report --json` (file
|
|
145
|
+
override `COST_HISTORY_FILE`). Shows measured runs, unmeasured runs (usd
|
|
146
|
+
null) as a separate count, and total measured USD. A missing history file
|
|
147
|
+
is UNMEASURED.
|
|
148
|
+
- Budget line: the $50 per day budget from FOUNDER-QUEUE row 3, as a
|
|
149
|
+
configurable `daily_budget_usd`.
|
|
150
|
+
- Cost is labelled ESTIMATE when derived from plan proration, MEASURED only
|
|
151
|
+
when it comes from cost-history measured rows.
|
|
152
|
+
|
|
153
|
+
Human-equivalent value (right side):
|
|
154
|
+
- Salary basis in `roi-basis.json`, versioned, every figure with source URL
|
|
155
|
+
and snapshot date:
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"source_url": "https://www.levels.fyi/companies/anthropic/salaries/software-engineer",
|
|
159
|
+
"snapshot_date": "2026-09-24",
|
|
160
|
+
"annual_total_comp_usd": {
|
|
161
|
+
"entry": 483000, "senior": 621000, "lead": 865000,
|
|
162
|
+
"staff": 1410000, "median": 883000
|
|
163
|
+
},
|
|
164
|
+
"research_scientist_median_usd": {"value": null, "status": "UNVERIFIED"},
|
|
165
|
+
"research_engineer_usd": {"value": null, "status": "NOT FOUND"},
|
|
166
|
+
"overhead_multiplier": {"value": 1.25, "status": "ASSUMPTION"},
|
|
167
|
+
"work_hours_per_year": {"value": 2000, "status": "ASSUMPTION"},
|
|
168
|
+
"seat_tier": {"chief-of-staff": "lead", "cto": "staff", "...": "..."},
|
|
169
|
+
"hours_per_outcome": {"verified_slice": {"value": null, "status": "ASSUMPTION, unset"}},
|
|
170
|
+
"plan_usd_per_month": 200,
|
|
171
|
+
"daily_budget_usd": 50
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
The figures match RETRO-2026.md 3.8.10 and were re-fetched from the URL on
|
|
175
|
+
2026-10-23. The basis is flagged STALE when `snapshot_date` is older than
|
|
176
|
+
90 days.
|
|
177
|
+
- Value formula, shown on the page: hourly rate = annual total comp x
|
|
178
|
+
overhead multiplier / work hours per year; human-equivalent value = sum over
|
|
179
|
+
released slices (BOARD released tokens in the window) of hours per outcome
|
|
180
|
+
x hourly rate for the owning seat's tier.
|
|
181
|
+
- Value is UNMEASURED while `hours_per_outcome` is unset. The page then shows
|
|
182
|
+
only "standing 10-seat payroll equivalent" (sum of tier comp x overhead for
|
|
183
|
+
10 seats, labelled ASSUMPTION) next to agent cost, and never prints an ROI
|
|
184
|
+
multiple.
|
|
185
|
+
- ROI multiple is shown only when both sides are non-null, with the label of
|
|
186
|
+
the weaker side (ESTIMATE or ASSUMPTION). Per verified outcome cost comes
|
|
187
|
+
from the outcome scoreboard (`.loki/company/scoreboard/*.json`) when it
|
|
188
|
+
exists, else UNMEASURED.
|
|
189
|
+
- Every assumption is editable only in `roi-basis.json`; the page shows its
|
|
190
|
+
path and git SHA, never an inline editor.
|
|
191
|
+
|
|
192
|
+
### 5.7 Founder command box
|
|
193
|
+
- UI: one textarea (max 2000 characters), one "Why only you" optional field,
|
|
194
|
+
one submit button. After submit, the page shows the appended row number
|
|
195
|
+
and the line written.
|
|
196
|
+
- `POST /api/queue`, body JSON `{item, why}`; returns `{row, line}`.
|
|
197
|
+
- Append rules:
|
|
198
|
+
1. Take an exclusive `fcntl.flock` on `FOUNDER-QUEUE.md.lock` (sidecar,
|
|
199
|
+
under `.loki/cockpit/`), with a 5 s timeout returning 503.
|
|
200
|
+
2. Read the file; allocate the next number as max existing `#` + 1.
|
|
201
|
+
3. Sanitize: strip control characters, replace newlines with spaces and
|
|
202
|
+
`|` with `/`, trim, reject empty (400) or over the cap (413).
|
|
203
|
+
4. Write one row in the existing header shape
|
|
204
|
+
`| # | Date | Item | Why only you | Loop does meanwhile |` with Date as
|
|
205
|
+
today UTC `YYYY-MM-DD`, Item prefixed `[cockpit]`, Why from the field or
|
|
206
|
+
`founder command`, Loop cell `chief-of-staff to triage`, plus trailing
|
|
207
|
+
status cell `open`.
|
|
208
|
+
5. Append only (`open(path, "a")`), then fsync, then re-read and verify the
|
|
209
|
+
file did not shrink and the new row is the last line (shrink guard,
|
|
210
|
+
retro 3.3.17). On failure return 500 and leave the file unchanged.
|
|
211
|
+
- The cockpit never commits the change; the chief-of-staff triages and
|
|
212
|
+
commits.
|
|
213
|
+
|
|
214
|
+
## 6. HTTP API
|
|
215
|
+
| Method | Path | Returns |
|
|
216
|
+
|---|---|---|
|
|
217
|
+
| GET | `/` | index.html |
|
|
218
|
+
| GET | `/api/snapshot` | all panels |
|
|
219
|
+
| GET | `/api/panel/<name>` | one panel |
|
|
220
|
+
| GET | `/healthz` | `{"ok": true, "pid": N}` |
|
|
221
|
+
| POST | `/api/queue` | append result |
|
|
222
|
+
Every other method or path returns 405 or 404.
|
|
223
|
+
|
|
224
|
+
Security:
|
|
225
|
+
- Reject any request whose `Host` header is not `127.0.0.1:<port>`,
|
|
226
|
+
`localhost:<port>` or `[::1]:<port>` (DNS rebinding).
|
|
227
|
+
- `POST /api/queue` requires header `X-Cockpit-Token` equal to a per-process
|
|
228
|
+
random token (`secrets.token_urlsafe(32)`), written to
|
|
229
|
+
`.loki/cockpit/token` with mode 600 and injected into the served page; and
|
|
230
|
+
an `Origin` equal to the served origin. Missing or wrong: 403.
|
|
231
|
+
- Request body capped at 8 KB. Responses set `Cache-Control: no-store`,
|
|
232
|
+
`X-Content-Type-Options: nosniff` and a CSP of `default-src 'self';
|
|
233
|
+
script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'`.
|
|
234
|
+
- Subprocesses run with an argument list (never `shell=True`) and an env built
|
|
235
|
+
deny-by-default (PATH, HOME, USER, LOGNAME, LANG, gh and npm config paths
|
|
236
|
+
only), per security-lead rule 3.9.2.
|
|
237
|
+
- No file path from a request is ever opened; all paths come from flags or
|
|
238
|
+
env.
|
|
239
|
+
|
|
240
|
+
## 7. Page behaviour
|
|
241
|
+
- Polls `/api/snapshot` every 15 s; panels update in place without layout
|
|
242
|
+
shift (fixed card heights, skeleton rows); a "last refreshed" time and the
|
|
243
|
+
age of each source are always visible.
|
|
244
|
+
- Order: pulse violations band, agents, BOARD flow, releases, OKRs, ROI,
|
|
245
|
+
command box.
|
|
246
|
+
- Light and dark from `prefers-color-scheme`; readable at 1280 px and 390 px.
|
|
247
|
+
- Every number has a hover or caption with its `source_cmd` and `as_of`.
|
|
248
|
+
|
|
249
|
+
## 8. Headless test (`tests/test-cockpit.sh`)
|
|
250
|
+
Uses `loki_run_tmp_create` and `loki_run_tmp_cleanup`, stops the server by its
|
|
251
|
+
recorded PID only, sets `LOKI_NO_BROWSER=1`. Fixtures under the run tmp dir:
|
|
252
|
+
- A BOARD.md with one row per token plus one unparsed row; assert counts.
|
|
253
|
+
- A FOUNDER-QUEUE.md copy; POST an item containing `|`, a newline and 2001
|
|
254
|
+
characters; assert 413, then a valid item, assert row number = max + 1, the
|
|
255
|
+
pipe replaced, the file only grew, and two concurrent POSTs get distinct
|
|
256
|
+
numbers.
|
|
257
|
+
- Stub `COCKPIT_NPM_CMD` and `COCKPIT_GH_CMD` scripts emitting fixed JSON,
|
|
258
|
+
and one that sleeps past the timeout; assert UNKNOWN, not a hang.
|
|
259
|
+
- Missing cost-history and empty status dir: assert ROI cost UNMEASURED and
|
|
260
|
+
agents UNKNOWN, and that no panel prints `0` for an unmeasured value.
|
|
261
|
+
- `--host 0.0.0.0` exits 64. POST without token: 403. Bad Host: 403.
|
|
262
|
+
- `--once --json` output parses and its exit code matches the pulse stub's
|
|
263
|
+
exit code mapping.
|
|
264
|
+
- Positive control for every absence assertion (retro 3.1.6): the same probe
|
|
265
|
+
run on a fixture that does contain the value must find it.
|
|
266
|
+
|
|
267
|
+
## 9. Acceptance (Wall checks)
|
|
268
|
+
1. `python3 tools/cockpit/server.py --once --json | python3 -m json.tool`
|
|
269
|
+
exits 0 or 2 on a clean checkout, with all 7 panels present.
|
|
270
|
+
2. `bash tests/test-cockpit.sh` passes, and fails when the append sanitizer
|
|
271
|
+
or the Host check is removed (mutation).
|
|
272
|
+
3. `lsof -nP -iTCP:<port> -sTCP:LISTEN` shows 127.0.0.1 only.
|
|
273
|
+
4. No file under the repo changes after an hour of page polling except
|
|
274
|
+
FOUNDER-QUEUE.md on submit (`git status --porcelain` diff).
|
|
275
|
+
5. Screenshot at 1280 px and 390 px reviewed by developer-experience-lead.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Loki Cockpit</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root{--bg:#f4f5f8;--card:#fff;--ink:#14161c;--mute:#5b6270;--line:#d9dde6;--ok:#17653a;--bad:#a4161a;--warn:#8a5a00;--unk:#4a4f6a;--badbg:#fde8e8;--accent:#2b4ed8}
|
|
9
|
+
@media (prefers-color-scheme:dark){:root{--bg:#0f1115;--card:#171a21;--ink:#e8eaf0;--mute:#9aa3b5;--line:#2a2f3a;--ok:#5fd08f;--bad:#ff8083;--warn:#f0b955;--unk:#9fa6c8;--badbg:#3a1618;--accent:#8aa2ff}}
|
|
10
|
+
*{box-sizing:border-box}
|
|
11
|
+
body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.45 system-ui,-apple-system,Segoe UI,sans-serif}
|
|
12
|
+
header{display:flex;flex-wrap:wrap;gap:8px 16px;align-items:baseline;padding:12px 16px;border-bottom:1px solid var(--line)}
|
|
13
|
+
header h1{font-size:16px;margin:0}
|
|
14
|
+
#refreshed{color:var(--mute);font-size:12px}
|
|
15
|
+
main{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,420px),1fr));gap:12px;padding:12px 16px}
|
|
16
|
+
section{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:12px;min-height:160px;overflow:hidden}
|
|
17
|
+
section.wide{grid-column:1/-1}
|
|
18
|
+
h2{font-size:13px;margin:0 0 8px;text-transform:uppercase;letter-spacing:.04em;display:flex;gap:8px;align-items:center}
|
|
19
|
+
.badge{font-size:11px;padding:1px 6px;border-radius:10px;border:1px solid currentColor}
|
|
20
|
+
.OK{color:var(--ok)}.VIOLATION{color:var(--bad)}.UNKNOWN,.UNMEASURED,.UNCITED{color:var(--unk)}.STALE{color:var(--warn)}
|
|
21
|
+
.src{margin-top:8px;color:var(--mute);font:11px ui-monospace,Menlo,monospace;word-break:break-all}
|
|
22
|
+
.err{color:var(--warn);font-size:12px;margin:4px 0}
|
|
23
|
+
.band{background:var(--badbg);color:var(--bad);border:1px solid var(--bad);border-radius:6px;padding:8px;margin-bottom:8px}
|
|
24
|
+
.band .top{font-size:16px;font-weight:600}
|
|
25
|
+
table{width:100%;border-collapse:collapse;font-size:12px}
|
|
26
|
+
td,th{text-align:left;padding:3px 6px 3px 0;border-bottom:1px solid var(--line);vertical-align:top;word-break:break-word}
|
|
27
|
+
.flow{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:8px}
|
|
28
|
+
.flow div{border:1px solid var(--line);border-radius:6px;padding:4px 8px;min-width:78px}
|
|
29
|
+
.flow b{display:block;font-size:18px}
|
|
30
|
+
.cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:8px}
|
|
31
|
+
.card{border:1px solid var(--line);border-radius:6px;padding:8px;font-size:12px}
|
|
32
|
+
.card.blocked{border-color:var(--bad)}
|
|
33
|
+
.mute{color:var(--mute)}
|
|
34
|
+
textarea,input[type=text]{width:100%;background:var(--bg);color:var(--ink);border:1px solid var(--line);border-radius:6px;padding:8px;font:inherit}
|
|
35
|
+
button{background:var(--accent);color:#fff;border:0;border-radius:6px;padding:8px 14px;font:inherit;cursor:pointer}
|
|
36
|
+
button:disabled{opacity:.5}
|
|
37
|
+
#qres{margin-top:8px;font-size:12px;min-height:18px;word-break:break-word}
|
|
38
|
+
</style>
|
|
39
|
+
</head>
|
|
40
|
+
<body>
|
|
41
|
+
<header><h1>Loki Cockpit</h1><span id="refreshed">loading</span><span class="mute" id="fail"></span></header>
|
|
42
|
+
<main>
|
|
43
|
+
<section class="wide" id="p-pulse"><h2>Pulse</h2><div class="body">loading</div></section>
|
|
44
|
+
<section class="wide" id="p-agents"><h2>Agents</h2><div class="body">loading</div></section>
|
|
45
|
+
<section id="p-board"><h2>BOARD flow</h2><div class="body">loading</div></section>
|
|
46
|
+
<section id="p-releases"><h2>Releases</h2><div class="body">loading</div></section>
|
|
47
|
+
<section class="wide" id="p-okrs"><h2>OKRs</h2><div class="body">loading</div></section>
|
|
48
|
+
<section class="wide" id="p-roi"><h2>ROI</h2><div class="body">loading</div></section>
|
|
49
|
+
<section class="wide" id="p-queue"><h2>Founder command box</h2><div class="body">loading</div>
|
|
50
|
+
<div>
|
|
51
|
+
<textarea id="item" rows="3" maxlength="2000" placeholder="Command or decision for the chief of staff (max 2000 characters)"></textarea>
|
|
52
|
+
<input type="text" id="why" maxlength="500" placeholder="Why only you (optional)">
|
|
53
|
+
<p><button id="send">Append to FOUNDER-QUEUE</button> <span class="mute" id="count">0/2000</span></p>
|
|
54
|
+
<div id="qres"></div>
|
|
55
|
+
</div>
|
|
56
|
+
</section>
|
|
57
|
+
</main>
|
|
58
|
+
<script>
|
|
59
|
+
(function(){
|
|
60
|
+
"use strict";
|
|
61
|
+
var TOKEN="__COCKPIT_TOKEN__";
|
|
62
|
+
function h(tag,attrs){var e=document.createElement(tag);if(attrs){for(var k in attrs){if(k==="class")e.className=attrs[k];else if(k==="title")e.title=attrs[k];else e.setAttribute(k,attrs[k]);}}
|
|
63
|
+
for(var i=2;i<arguments.length;i++){var c=arguments[i];if(c===null||c===undefined)continue;e.appendChild(typeof c==="object"?c:document.createTextNode(String(c)));}return e;}
|
|
64
|
+
function show(v){return v===null||v===undefined?"UNKNOWN":String(v);}
|
|
65
|
+
function badge(s){return h("span",{"class":"badge "+s},s);}
|
|
66
|
+
function src(p){return h("div",{"class":"src",title:"as of "+p.as_of},"source: "+p.source_cmd+" as of "+p.as_of+(p.cache_age_s!==undefined?" cached "+p.cache_age_s+"s":""));}
|
|
67
|
+
function err(p){return p.error?h("div",{"class":"err"},p.error):null;}
|
|
68
|
+
function table(rows,head){var t=h("table");if(head){var tr=h("tr");head.forEach(function(x){tr.appendChild(h("th",null,x));});t.appendChild(tr);}
|
|
69
|
+
rows.forEach(function(r){var tr=h("tr");r.forEach(function(c){tr.appendChild(h("td",null,c));});t.appendChild(tr);});return t;}
|
|
70
|
+
function fill(id,p,build){var s=document.getElementById("p-"+id);var b=s.querySelector(".body");var h2=s.querySelector("h2");
|
|
71
|
+
while(h2.childNodes.length>1)h2.removeChild(h2.lastChild);h2.appendChild(badge(p.status));
|
|
72
|
+
var nb=h("div",{"class":"body"});try{build(nb,p.data||{},p);}catch(e){nb.appendChild(h("div",{"class":"err"},"render error: "+e));}
|
|
73
|
+
nb.appendChild(err(p));nb.appendChild(src(p));s.replaceChild(nb,b);}
|
|
74
|
+
|
|
75
|
+
var R={};
|
|
76
|
+
R.pulse=function(b,d){
|
|
77
|
+
var v=d.violations||[];
|
|
78
|
+
if(v.length){var band=h("div",{"class":"band"});band.appendChild(h("div",{"class":"top"},v[0]));
|
|
79
|
+
v.slice(1).forEach(function(x){band.appendChild(h("div",null,x));});b.appendChild(band);}
|
|
80
|
+
else if(d.exit_code===0)b.appendChild(h("div",{"class":"OK"},"No violations."));
|
|
81
|
+
var m=d.metrics||{};b.appendChild(table(Object.keys(m).map(function(k){return [k,m[k]];})));
|
|
82
|
+
if((d.unknown_metrics||[]).length)b.appendChild(h("p",{"class":"mute"},"UNKNOWN metrics: "+d.unknown_metrics.join(", ")));
|
|
83
|
+
(d.stale||[]).forEach(function(x){b.appendChild(h("p",{"class":"STALE"},x));});};
|
|
84
|
+
R.agents=function(b,d){var c=h("div",{"class":"cards"});
|
|
85
|
+
(d.seats||[]).forEach(function(s){var card=h("div",{"class":"card"+(s.blocked?" blocked":"")});
|
|
86
|
+
card.appendChild(h("div",null,h("b",null,s.slug)," ",badge(s.state)));
|
|
87
|
+
if(s.state==="UNKNOWN"){card.appendChild(h("div",{"class":"mute"},s.error||"no status file"));}
|
|
88
|
+
else{card.appendChild(h("div",null,"model: "+show(s.model)));card.appendChild(h("div",null,"doing: "+show(s.doing)));
|
|
89
|
+
card.appendChild(h("div",null,"slice: "+show(s.slice)));card.appendChild(h("div",null,"updated: "+show(s.updated_min_ago)+" min ago"));
|
|
90
|
+
if(s.blocked_on)card.appendChild(h("div",{"class":"VIOLATION"},"blocked on: "+s.blocked_on));}
|
|
91
|
+
var tk=s.tick||{};card.appendChild(h("div",null,"last tick: "+show(tk.last_tick)+" ("+show(tk.outcome)+")"));
|
|
92
|
+
card.appendChild(h("div",null,"next due: "+show(tk.next_due)+(tk.state==="STALE"?" STALE (no tick in 2x "+tk.interval_min+" min)":"")));
|
|
93
|
+
(s.root_causes||[]).forEach(function(r){card.appendChild(h("div",{"class":"mute"},r));});c.appendChild(card);});
|
|
94
|
+
b.appendChild(c);};
|
|
95
|
+
R.board=function(b,d){var f=h("div",{"class":"flow"});var cs=d.counts||{};
|
|
96
|
+
["ready","building","review","approved","merged","released"].forEach(function(t){f.appendChild(h("div",null,h("b",null,show(cs[t])),t));});b.appendChild(f);
|
|
97
|
+
if(d.ready_below_30)b.appendChild(h("div",{"class":"err"},"ready queue below "+d.ready_target+" (D91)"));
|
|
98
|
+
b.appendChild(table((d.active||[]).map(function(r){return [r.id,r.owner,r.tier,r.token,show(r.age_min)+" / "+show(r.budget_min)+" min"+(r.over_budget?" OVER":"")];}),["ID","Owner","Tier","Status","Age / budget"]));
|
|
99
|
+
if((d.unparsed||[]).length)b.appendChild(h("p",{"class":"mute"},"Unparsed rows: "+d.unparsed.join(", ")));};
|
|
100
|
+
R.releases=function(b,d){
|
|
101
|
+
b.appendChild(table([["npm latest",show(d.latest)],["npm next",show(d.next)],["VERSION",show(d.version_file)],["last tag",show(d.last_tag)],
|
|
102
|
+
["minutes since last publish",show(d.last_publish_minutes_ago)],["releases last hour",show(d.releases_last_hour)],["releases last 24h",show(d.releases_last_24h)]]));
|
|
103
|
+
b.appendChild(h("p",{"class":"mute"},d.npm_label));
|
|
104
|
+
(d.flags||[]).forEach(function(x){b.appendChild(h("div",{"class":"VIOLATION"},x));});
|
|
105
|
+
b.appendChild(table((d.runs||[]).slice(0,8).map(function(r){var a=h("a",{href:r.url||"#",rel:"noreferrer",target:"_blank"},String(r.databaseId));return [a,show(r.conclusion||r.status),(r.headSha||"").slice(0,8)];}),["Run","Result","SHA"]));
|
|
106
|
+
b.appendChild(table((d.versions||[]).slice(0,10).map(function(v){return [v.version,v.published];}),["Version","Published"]));};
|
|
107
|
+
R.okrs=function(b,d){b.appendChild(table((d.items||[]).map(function(i){
|
|
108
|
+
var cur=i.current?(show(i.current.value)+(i.current.target!==undefined?" / "+i.current.target:"")):"";
|
|
109
|
+
return [i.n,i.title,show(i.owner),h("span",{"class":i.state},i.state),cur];}),["#","Item","Owner","State","Value / target"]));};
|
|
110
|
+
R.roi=function(b,d){var c=d.cost||{},v=d.value||{},r=d.roi||{},m=c.metered||{},u=c.usage||{};
|
|
111
|
+
b.appendChild(h("h3",null,"Agent cost ("+c.label+")"));
|
|
112
|
+
b.appendChild(table([["plan USD per month",show(c.plan_usd_per_month)],["plan USD per day (ESTIMATE)",show(c.plan_usd_per_day)],["daily budget USD",show(c.daily_budget_usd)],
|
|
113
|
+
["weekly plan-usage percent",show(u.weekly_percent)],["5h window percent",show(u.window_percent)],
|
|
114
|
+
["metered run cost",m.state==="MEASURED"?("USD "+m.total_usd+" over "+m.measured_runs+" runs ("+m.unmeasured_runs+" unmeasured)"):("UNMEASURED: "+show(m.reason))]]));
|
|
115
|
+
b.appendChild(h("p",{"class":"mute"},c.label_note+". Usage numbers are plan percentages, not tokens or dollars."));
|
|
116
|
+
b.appendChild(h("h3",null,"Human-equivalent value ("+show(v.state||"standing payroll only")+")"));
|
|
117
|
+
var pr=v.standing_payroll_equivalent;
|
|
118
|
+
b.appendChild(table([["standing payroll equivalent per day",pr?("USD "+pr.per_day_usd+" for "+pr.seats+" seats, "+pr.label):"UNKNOWN"],
|
|
119
|
+
["human-equivalent value",v.human_equivalent_usd===null||v.human_equivalent_usd===undefined?"UNMEASURED":"USD "+v.human_equivalent_usd],["ROI multiple",r.multiple===null?"not printed ("+r.note+")":r.multiple+"x ("+r.weaker_side_label+")"],
|
|
120
|
+
["cost per verified outcome",(d.scoreboard||{}).cost_per_verified_outcome_usd===null?"UNMEASURED":"USD "+d.scoreboard.cost_per_verified_outcome_usd]]));
|
|
121
|
+
b.appendChild(h("p",{"class":"mute"},"Formula: "+v.formula));
|
|
122
|
+
var bs=d.basis||{};b.appendChild(h("p",{"class":"mute"},"Basis: "+bs.path+" at "+show(bs.git_sha)+", snapshot "+show(bs.snapshot_date)+(bs.stale?" (STALE: "+bs.stale_note+")":"")+", source "+show(bs.source_url)));
|
|
123
|
+
(d.notes||[]).forEach(function(x){b.appendChild(h("div",{"class":"err"},x));});};
|
|
124
|
+
R.queue=function(b,d){b.appendChild(h("p",null,"Rows: "+show(d.rows_total)+", last number "+show(d.max_number)+", from cockpit "+show(d.cockpit_rows)));
|
|
125
|
+
b.appendChild(table((d.last||[]).map(function(r){return [r.n,r.date,r.item];}),["#","Date","Item"]));};
|
|
126
|
+
|
|
127
|
+
function render(snap){var ps=snap.panels||{};Object.keys(R).forEach(function(k){if(ps[k])fill(k,ps[k],R[k]);});
|
|
128
|
+
document.getElementById("refreshed").textContent="last refreshed "+new Date().toLocaleTimeString()+" (generated "+snap.generated_at+")";
|
|
129
|
+
document.getElementById("fail").textContent="";}
|
|
130
|
+
function poll(){fetch("/api/snapshot",{cache:"no-store"}).then(function(r){return r.json();}).then(render)
|
|
131
|
+
.catch(function(e){document.getElementById("fail").textContent="refresh failed: "+e;});}
|
|
132
|
+
poll();setInterval(poll,15000);
|
|
133
|
+
|
|
134
|
+
var item=document.getElementById("item");
|
|
135
|
+
item.addEventListener("input",function(){document.getElementById("count").textContent=item.value.length+"/2000";});
|
|
136
|
+
document.getElementById("send").addEventListener("click",function(){
|
|
137
|
+
var btn=this,res=document.getElementById("qres");btn.disabled=true;res.textContent="sending";
|
|
138
|
+
fetch("/api/queue",{method:"POST",headers:{"Content-Type":"application/json","X-Cockpit-Token":TOKEN},
|
|
139
|
+
body:JSON.stringify({item:item.value,why:document.getElementById("why").value})})
|
|
140
|
+
.then(function(r){return r.json().then(function(j){return {ok:r.ok,j:j};});})
|
|
141
|
+
.then(function(x){res.textContent=x.ok?("Appended row "+x.j.row+": "+x.j.line):("Rejected: "+x.j.error);if(x.ok){item.value="";poll();}})
|
|
142
|
+
.catch(function(e){res.textContent="Failed: "+e;}).then(function(){btn.disabled=false;});});
|
|
143
|
+
})();
|
|
144
|
+
</script>
|
|
145
|
+
</body>
|
|
146
|
+
</html>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source_url": "https://www.levels.fyi/companies/anthropic/salaries/software-engineer",
|
|
3
|
+
"snapshot_date": "2026-09-24",
|
|
4
|
+
"refetched_on": "2026-10-23",
|
|
5
|
+
"annual_total_comp_usd": {
|
|
6
|
+
"entry": 483000,
|
|
7
|
+
"senior": 621000,
|
|
8
|
+
"lead": 865000,
|
|
9
|
+
"staff": 1410000,
|
|
10
|
+
"median": 883000
|
|
11
|
+
},
|
|
12
|
+
"research_scientist_median_usd": {"value": null, "status": "UNVERIFIED"},
|
|
13
|
+
"research_engineer_usd": {"value": null, "status": "NOT FOUND"},
|
|
14
|
+
"overhead_multiplier": {"value": 1.25, "status": "ASSUMPTION"},
|
|
15
|
+
"work_hours_per_year": {"value": 2000, "status": "ASSUMPTION"},
|
|
16
|
+
"default_tier": "senior",
|
|
17
|
+
"seat_tier_status": "ASSUMPTION",
|
|
18
|
+
"seat_tier": {
|
|
19
|
+
"chief-of-staff": "lead",
|
|
20
|
+
"cto": "staff",
|
|
21
|
+
"developer-experience-lead": "lead",
|
|
22
|
+
"platform-lead": "lead",
|
|
23
|
+
"product-growth-lead": "lead",
|
|
24
|
+
"release-reliability-lead": "lead",
|
|
25
|
+
"research-harness-scientist": "lead",
|
|
26
|
+
"research-outcomes-scientist": "lead",
|
|
27
|
+
"security-lead": "lead",
|
|
28
|
+
"verification-scientist": "lead"
|
|
29
|
+
},
|
|
30
|
+
"hours_per_outcome": {"verified_slice": {"value": null, "status": "ASSUMPTION, unset"}},
|
|
31
|
+
"plan_usd_per_month": 200,
|
|
32
|
+
"daily_budget_usd": 50
|
|
33
|
+
}
|