@marshalliqiu/loupe 0.1.0 → 0.2.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 +12 -0
- package/dist/cli.mjs +141 -8
- package/package.json +1 -1
- package/skills/loupe/SKILL.md +2 -1
package/README.md
CHANGED
|
@@ -75,6 +75,18 @@ loupe spec spec/guest-checkout.html # on Execute: write the companion spec (gue
|
|
|
75
75
|
|
|
76
76
|
> Use `npx @marshalliqiu/loupe new <file>` to map out what we discussed before building it.
|
|
77
77
|
|
|
78
|
+
**Skill (slash command).** Install the Loupe skill in the [Agent Skills](https://github.com/anthropics/skills) format with `npx skills` — it teaches your agent the full grill → lock → before/after workflow and, in Claude Code, exposes it as `/loupe`:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
npx skills add jerry5789k1/loupe --skill loupe
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The skill runs the CLI on demand via `npx -y @marshalliqiu/loupe`, so nothing else needs to be installed. Then:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
/loupe let's inspect the guest-checkout change before building it
|
|
88
|
+
```
|
|
89
|
+
|
|
78
90
|
**Session hook.** Want Loupe's ambient context — including your live open sessions — fed into every agent session instead of loading on demand? Install globally and opt into the hook:
|
|
79
91
|
|
|
80
92
|
```sh
|
package/dist/cli.mjs
CHANGED
|
@@ -801,6 +801,7 @@ function defaultPort() {
|
|
|
801
801
|
|
|
802
802
|
// src/scaffold.js
|
|
803
803
|
var MERMAID_CDN = "https://cdn.jsdelivr.net/npm/mermaid@11.15.0/dist/mermaid.esm.min.mjs";
|
|
804
|
+
var SVG_PAN_ZOOM_CDN = "https://esm.sh/svg-pan-zoom@3.6.2";
|
|
804
805
|
function escapeHtml(value) {
|
|
805
806
|
return String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
806
807
|
}
|
|
@@ -1201,8 +1202,18 @@ ${decisionSection()}`;
|
|
|
1201
1202
|
|
|
1202
1203
|
.loupe-card { background: var(--surface); border: 1px solid var(--hair); border-radius: var(--radius); padding: 22px; }
|
|
1203
1204
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1205
|
+
/* A pannable/zoomable viewport: the diagram renders at full size inside a fixed
|
|
1206
|
+
frame so it never shrinks to fit the card. Drag to pan, \u2318/Ctrl+wheel to zoom. */
|
|
1207
|
+
.loupe-diagram { position: relative; height: clamp(320px, 52vh, 580px); min-width: 0; overflow: hidden; border: 1px solid var(--hair); border-radius: var(--radius); background: var(--surface); cursor: grab; user-select: none; }
|
|
1208
|
+
.loupe-diagram:active { cursor: grabbing; }
|
|
1209
|
+
/* gesture hint \u2014 purely informational, never intercepts clicks */
|
|
1210
|
+
.loupe-diagram-hint { position: absolute; top: 8px; right: 10px; z-index: 2; pointer-events: none; font-size: 11px; color: var(--ink-3); background: rgba(255,255,255,0.82); border: 1px solid var(--hair); border-radius: 999px; padding: 2px 9px; }
|
|
1211
|
+
/* margin:0 \u2014 <pre>'s default 1em top/bottom margin (~26px) otherwise overflows the
|
|
1212
|
+
fixed-height frame and trips the layout audit's clipped-text check. */
|
|
1213
|
+
.loupe-diagram .mermaid { width: 100%; height: 100%; min-width: 0; margin: 0; padding: 0; }
|
|
1214
|
+
/* display:block removes the inline-element baseline descender gap (~6px) that
|
|
1215
|
+
otherwise overflows the frame and re-trips the layout audit. */
|
|
1216
|
+
.loupe-diagram svg { display: block; width: 100% !important; height: 100% !important; max-width: none !important; }
|
|
1206
1217
|
.loupe-legend { display: flex; flex-wrap: wrap; gap: 16px; margin-top: 14px; font-size: 13px; color: var(--ink-2); }
|
|
1207
1218
|
.loupe-legend span { display: inline-flex; align-items: center; gap: 7px; }
|
|
1208
1219
|
.loupe-sw { width: 13px; height: 13px; border-radius: 4px; display: inline-block; }
|
|
@@ -1277,12 +1288,67 @@ ${lenses}
|
|
|
1277
1288
|
|
|
1278
1289
|
<script type="module">
|
|
1279
1290
|
import mermaid from "${MERMAID_CDN}";
|
|
1291
|
+
// useMaxWidth:true makes mermaid emit a viewBox, so each SVG scales to fit its frame
|
|
1292
|
+
// immediately (no transient natural-size overflow before svg-pan-zoom's async CDN
|
|
1293
|
+
// import resolves \u2014 that overflow tripped the layout audit). The tall .loupe-diagram
|
|
1294
|
+
// frame + svg-pan-zoom fit then enlarge the diagram to fill the frame, so it stays big.
|
|
1280
1295
|
mermaid.initialize({ startOnLoad: false, theme: "base", securityLevel: "loose", flowchart: { useMaxWidth: true } });
|
|
1281
1296
|
try {
|
|
1282
1297
|
await mermaid.run();
|
|
1283
1298
|
} catch (e) {
|
|
1284
1299
|
console.error("Loupe: mermaid render failed", e);
|
|
1285
1300
|
}
|
|
1301
|
+
// Make every rendered diagram pannable/zoomable. Loaded after mermaid so the SVGs
|
|
1302
|
+
// exist; isolated in its own try so a CDN/offline failure leaves diagrams static but intact.
|
|
1303
|
+
try {
|
|
1304
|
+
const svgPanZoom = (await import("${SVG_PAN_ZOOM_CDN}")).default;
|
|
1305
|
+
for (const svg of document.querySelectorAll(".loupe-diagram .mermaid svg")) {
|
|
1306
|
+
svg.style.maxWidth = "none";
|
|
1307
|
+
svg.style.width = "100%";
|
|
1308
|
+
svg.style.height = "100%";
|
|
1309
|
+
const pz = svgPanZoom(svg, {
|
|
1310
|
+
zoomEnabled: true,
|
|
1311
|
+
controlIconsEnabled: true, // discoverable + / \u2212 / reset(fit) buttons
|
|
1312
|
+
fit: true,
|
|
1313
|
+
center: true,
|
|
1314
|
+
minZoom: 0.2,
|
|
1315
|
+
maxZoom: 12,
|
|
1316
|
+
dblClickZoomEnabled: false, // leave double-click free; pan is drag, annotate is click
|
|
1317
|
+
// A plain wheel must scroll the PAGE, not zoom the diagram \u2014 otherwise the page
|
|
1318
|
+
// scroll gets hijacked whenever the cursor is over a diagram (the reported bug).
|
|
1319
|
+
mouseWheelZoomEnabled: false,
|
|
1320
|
+
// Do NOT preventDefault on mouse events: Loupe's annotation picker listens for
|
|
1321
|
+
// click (capture phase); the default true swallows that click. false keeps
|
|
1322
|
+
// drag-to-pan AND click-to-annotate working.
|
|
1323
|
+
preventMouseEventsDefault: false,
|
|
1324
|
+
});
|
|
1325
|
+
const frame = svg.closest(".loupe-diagram");
|
|
1326
|
+
// Zoom is intentional only: \u2318/Ctrl + wheel zooms at the cursor; a plain wheel is
|
|
1327
|
+
// ignored here so the page scrolls normally. (The +/\u2212/reset buttons also zoom.)
|
|
1328
|
+
frame.addEventListener(
|
|
1329
|
+
"wheel",
|
|
1330
|
+
(e) => {
|
|
1331
|
+
if (!(e.ctrlKey || e.metaKey)) return; // plain scroll \u2192 let the page handle it
|
|
1332
|
+
e.preventDefault();
|
|
1333
|
+
const rect = svg.getBoundingClientRect();
|
|
1334
|
+
// gentle step per wheel tick \u2014 wheels/trackpads fire many events, so a small
|
|
1335
|
+
// factor keeps zoom controllable instead of jumping.
|
|
1336
|
+
const step = 1.06;
|
|
1337
|
+
pz.zoomAtPointBy(e.deltaY < 0 ? step : 1 / step, { x: e.clientX - rect.left, y: e.clientY - rect.top });
|
|
1338
|
+
},
|
|
1339
|
+
{ passive: false },
|
|
1340
|
+
);
|
|
1341
|
+
// discoverability hint, pointer-events:none so it never blocks a click
|
|
1342
|
+
if (!frame.querySelector(".loupe-diagram-hint")) {
|
|
1343
|
+
const hint = document.createElement("div");
|
|
1344
|
+
hint.className = "loupe-diagram-hint";
|
|
1345
|
+
hint.textContent = "drag to pan \xB7 \u2318/Ctrl + scroll to zoom";
|
|
1346
|
+
frame.appendChild(hint);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
} catch (e) {
|
|
1350
|
+
console.error("Loupe: pan/zoom init failed (diagrams stay static)", e);
|
|
1351
|
+
}
|
|
1286
1352
|
</script>
|
|
1287
1353
|
|
|
1288
1354
|
<script>
|
|
@@ -1782,6 +1848,7 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
|
|
|
1782
1848
|
if (!(el instanceof Element) || isLavishUi(el)) return;
|
|
1783
1849
|
if (isIntentionalHorizontalScroller(el)) return;
|
|
1784
1850
|
elements.push(el);
|
|
1851
|
+
if (el.tagName && el.tagName.toLowerCase() === "svg") return;
|
|
1785
1852
|
for (const child of el.children) walk(child);
|
|
1786
1853
|
}
|
|
1787
1854
|
if (document.body) walk(document.body);
|
|
@@ -1981,13 +2048,13 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
|
|
|
1981
2048
|
document.documentElement.appendChild(host);
|
|
1982
2049
|
shadow = host.attachShadow({ mode: "open" });
|
|
1983
2050
|
const style = document.createElement("style");
|
|
1984
|
-
style.textContent = `:host{all:initial;position:fixed;z-index:2147483647;left:0;top:0;color-scheme:dark;--ink-900:#0f1115;--ink-800:#11141a;--ink-700:#171a21;--ink-600:#1c212b;--steel-700:#2a2f3a;--steel-600:#303745;--steel-500:#3c4557;--steel-400:#8c96aa;--steel-300:#aeb6c6;--steel-200:#b9c0cf;--steel-100:#d8deea;--cream-50:#fffbf3;--cream-100:#f7f3ea;--cream-200:#e8e1cf;--brass-500:#f4c95d;--brass-400:#ffd877;--brass-ink:#17130a;--bg:var(--ink-900);--bg-panel:var(--ink-800);--bg-elevated:var(--ink-600);--fg:var(--cream-100);--fg-faint:var(--steel-300);--border:var(--steel-600);--accent:#f4c95d;--accent-hover:#ffd877;--font-sans:Geist,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;--font-mono:"Geist Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;--radius-md:10px;--radius-xl:14px;--shadow-floating:0 20px 70px rgba(0,0,0,.35);font-family:var(--font-sans)}*{box-sizing:border-box}:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.lavish-text-highlight{position:fixed;pointer-events:none;background:rgba(244,201,93,.28);border-radius:2px;box-shadow:0 0 0 1px rgba(244,201,93,.45)}.lavish-annotation-card{position:fixed;width:min(320px,calc(100vw - 24px));padding:12px;border-radius:var(--radius-xl);background:var(--bg-panel);color:var(--fg);border:1px solid var(--accent);box-shadow:var(--shadow-floating);font:14px/1.4 var(--font-sans)}.lavish-heading{font-weight:700;margin-bottom:6px}.lavish-annotation-card textarea{width:100%;min-height:86px;resize:vertical;border-radius:var(--radius-md);border:1px solid var(--border);background:var(--bg);color:var(--fg);padding:9px;font:inherit;font-family:var(--font-sans)}.lavish-annotation-card textarea::placeholder{color:var(--fg-faint)}.lavish-annotation-card .lavish-hint{margin-top:6px;font-size:11px;color:var(--fg-faint)}.lavish-annotation-card .lavish-row{display:flex;gap:8px;justify-content:flex-end;margin-top:8px}.lavish-annotation-card button{border:0;border-radius:var(--radius-md);padding:8px 10px;font-family:var(--font-sans);font-size:13px;font-weight:700;cursor:pointer}.lavish-annotation-card button:active{opacity:.85}.lavish-annotation-card .lavish-send{background:var(--accent);color:var(--brass-ink)}.lavish-annotation-card .lavish-send:hover{background:var(--accent-hover)}.lavish-annotation-card .lavish-cancel{background:var(--steel-700);color:var(--fg)}`;
|
|
2051
|
+
style.textContent = `:host{all:initial;position:fixed;z-index:2147483647;left:0;top:0;color-scheme:dark;--ink-900:#0f1115;--ink-800:#11141a;--ink-700:#171a21;--ink-600:#1c212b;--steel-700:#2a2f3a;--steel-600:#303745;--steel-500:#3c4557;--steel-400:#8c96aa;--steel-300:#aeb6c6;--steel-200:#b9c0cf;--steel-100:#d8deea;--cream-50:#fffbf3;--cream-100:#f7f3ea;--cream-200:#e8e1cf;--brass-500:#f4c95d;--brass-400:#ffd877;--brass-ink:#17130a;--bg:var(--ink-900);--bg-panel:var(--ink-800);--bg-elevated:var(--ink-600);--fg:var(--cream-100);--fg-faint:var(--steel-300);--border:var(--steel-600);--accent:#f4c95d;--accent-hover:#ffd877;--font-sans:Geist,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;--font-mono:"Geist Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;--radius-md:10px;--radius-xl:14px;--shadow-floating:0 20px 70px rgba(0,0,0,.35);font-family:var(--font-sans)}*{box-sizing:border-box}:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.lavish-text-highlight{position:fixed;pointer-events:none;background:rgba(244,201,93,.28);border-radius:2px;box-shadow:0 0 0 1px rgba(244,201,93,.45)}.lavish-annotation-card{position:fixed;width:min(320px,calc(100vw - 24px));padding:12px;border-radius:var(--radius-xl);background:var(--bg-panel);color:var(--fg);border:1px solid var(--accent);box-shadow:var(--shadow-floating);font:14px/1.4 var(--font-sans)}.lavish-heading{font-weight:700;margin-bottom:6px}.lavish-annotation-card textarea{width:100%;min-height:86px;resize:vertical;border-radius:var(--radius-md);border:1px solid var(--border);background:var(--bg);color:var(--fg);padding:9px;font:inherit;font-family:var(--font-sans)}.lavish-annotation-card textarea::placeholder{color:var(--fg-faint)}.lavish-annotation-card .lavish-hint{margin-top:6px;font-size:11px;color:var(--fg-faint)}.lavish-annotation-card .lavish-row{display:flex;gap:8px;justify-content:flex-end;margin-top:8px}.lavish-annotation-card button{border:0;border-radius:var(--radius-md);padding:8px 10px;font-family:var(--font-sans);font-size:13px;font-weight:700;cursor:pointer}.lavish-annotation-card button:active{opacity:.85}.lavish-annotation-card .lavish-send{background:var(--accent);color:var(--brass-ink)}.lavish-annotation-card .lavish-send:hover{background:var(--accent-hover)}.lavish-annotation-card .lavish-cancel{background:var(--steel-700);color:var(--fg)}.lavish-select-pill{position:fixed;display:flex;align-items:center;gap:10px;max-width:min(340px,calc(100vw - 24px));padding:5px 6px 5px 12px;border-radius:999px;background:var(--bg-panel);color:var(--fg);border:1px solid var(--accent);box-shadow:var(--shadow-floating);font:13px/1.3 var(--font-sans)}.lavish-select-pill .lavish-pill-label{display:flex;align-items:center;gap:7px;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.lavish-select-pill .lavish-pill-tag{flex:none;font-family:var(--font-mono);font-size:11px;color:var(--accent)}.lavish-select-pill .lavish-pill-text{overflow:hidden;text-overflow:ellipsis;color:var(--fg);font-weight:700}.lavish-select-pill .lavish-pill-add{flex:none;border:0;border-radius:999px;padding:6px 12px;background:var(--accent);color:var(--brass-ink);font:inherit;font-weight:700;cursor:pointer}.lavish-select-pill .lavish-pill-add:hover{background:var(--accent-hover)}`;
|
|
1985
2052
|
shadow.appendChild(style);
|
|
1986
2053
|
return shadow;
|
|
1987
2054
|
}
|
|
1988
2055
|
function closeCard() {
|
|
1989
2056
|
if (shadow) {
|
|
1990
|
-
for (const el of [...shadow.querySelectorAll(".lavish-annotation-card")]) el.remove();
|
|
2057
|
+
for (const el of [...shadow.querySelectorAll(".lavish-annotation-card,.lavish-select-pill")]) el.remove();
|
|
1991
2058
|
}
|
|
1992
2059
|
clearHighlight(hovered);
|
|
1993
2060
|
clearHighlight(selected);
|
|
@@ -1995,6 +2062,49 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
|
|
|
1995
2062
|
clearTextHighlight();
|
|
1996
2063
|
selected = null;
|
|
1997
2064
|
}
|
|
2065
|
+
function describeTarget(el) {
|
|
2066
|
+
const c = context(el);
|
|
2067
|
+
let label = c.text;
|
|
2068
|
+
if (!label && el && el.closest) {
|
|
2069
|
+
const group = el.closest("g");
|
|
2070
|
+
if (group) label = (group.textContent || "").trim().replace(/\s+/g, " ");
|
|
2071
|
+
}
|
|
2072
|
+
return { tag: c.tag, label: (label || "").slice(0, 60) };
|
|
2073
|
+
}
|
|
2074
|
+
function positionFloating(el, rect) {
|
|
2075
|
+
const pad = 8;
|
|
2076
|
+
const w = el.offsetWidth;
|
|
2077
|
+
const h = el.offsetHeight;
|
|
2078
|
+
const left = Math.min(Math.max(12, rect.left), window.innerWidth - w - 12);
|
|
2079
|
+
let top = rect.top - h - pad;
|
|
2080
|
+
if (top < 12) top = Math.min(rect.bottom + pad, window.innerHeight - h - 12);
|
|
2081
|
+
el.style.left = left + "px";
|
|
2082
|
+
el.style.top = top + "px";
|
|
2083
|
+
}
|
|
2084
|
+
function selectElement(target) {
|
|
2085
|
+
const root = ensureShadow();
|
|
2086
|
+
closeCard();
|
|
2087
|
+
selected = target;
|
|
2088
|
+
highlightElement(selected);
|
|
2089
|
+
const d = describeTarget(target);
|
|
2090
|
+
const pill = document.createElement("div");
|
|
2091
|
+
pill.className = "lavish-select-pill";
|
|
2092
|
+
pill.innerHTML = '<span class="lavish-pill-label"><span class="lavish-pill-tag"></span><span class="lavish-pill-text"></span></span><button class="lavish-pill-add" type="button">\u270E Add note</button>';
|
|
2093
|
+
const tagEl = pill.querySelector(".lavish-pill-tag");
|
|
2094
|
+
if (tagEl) tagEl.textContent = "<" + d.tag + ">";
|
|
2095
|
+
const textEl = pill.querySelector(".lavish-pill-text");
|
|
2096
|
+
if (textEl) {
|
|
2097
|
+
if (d.label) textEl.textContent = '"' + d.label + '"';
|
|
2098
|
+
else textEl.remove();
|
|
2099
|
+
}
|
|
2100
|
+
root.appendChild(pill);
|
|
2101
|
+
positionFloating(pill, target.getBoundingClientRect());
|
|
2102
|
+
const addButton = (
|
|
2103
|
+
/** @type {HTMLButtonElement | null} */
|
|
2104
|
+
pill.querySelector(".lavish-pill-add")
|
|
2105
|
+
);
|
|
2106
|
+
if (addButton) addButton.onclick = () => showAnnotationCard(selected);
|
|
2107
|
+
}
|
|
1998
2108
|
function showAnnotationCard(target, options = {}) {
|
|
1999
2109
|
const root = ensureShadow();
|
|
2000
2110
|
closeCard();
|
|
@@ -2008,10 +2118,13 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
|
|
|
2008
2118
|
const rect = options.range ? options.range.getBoundingClientRect() : target.getBoundingClientRect();
|
|
2009
2119
|
const card = document.createElement("div");
|
|
2010
2120
|
card.className = "lavish-annotation-card";
|
|
2011
|
-
const
|
|
2121
|
+
const label = c.tag === "text" ? "" : describeTarget(target).label;
|
|
2122
|
+
const headingText = c.tag === "text" ? "Annotate text" : "Annotate <" + c.tag + ">" + (label ? ' \xB7 "' + label + '"' : "");
|
|
2012
2123
|
const placeholder = c.tag === "text" ? "Tell the agent what to change about this text..." : "Tell the agent what to change about this element...";
|
|
2013
|
-
card.innerHTML = '<div class="lavish-heading"
|
|
2124
|
+
card.innerHTML = '<div class="lavish-heading"></div><textarea placeholder="' + placeholder + '"></textarea><div class="lavish-hint">Enter to queue · ' + (/Mac|iP(hone|ad|od)/.test(navigator.platform) ? "\u2318" : "Ctrl") + '+Enter to send now</div><div class="lavish-row"><button class="lavish-cancel" type="button">Cancel</button><button class="lavish-send" type="button">Queue</button></div>';
|
|
2014
2125
|
root.appendChild(card);
|
|
2126
|
+
const headingEl = card.querySelector(".lavish-heading");
|
|
2127
|
+
if (headingEl) headingEl.textContent = headingText;
|
|
2015
2128
|
const left = Math.min(Math.max(12, rect.left), window.innerWidth - card.offsetWidth - 12);
|
|
2016
2129
|
const top = Math.min(Math.max(12, rect.bottom + 8), window.innerHeight - card.offsetHeight - 12);
|
|
2017
2130
|
card.style.left = left + "px";
|
|
@@ -2120,7 +2233,27 @@ function createArtifactSdk(deriveQueueKey, isNativeInteractive = isNativeInterac
|
|
|
2120
2233
|
ignoreNextClick = false;
|
|
2121
2234
|
return;
|
|
2122
2235
|
}
|
|
2123
|
-
|
|
2236
|
+
const t = event.target;
|
|
2237
|
+
if (t === document.body || t === document.documentElement) {
|
|
2238
|
+
closeCard();
|
|
2239
|
+
return;
|
|
2240
|
+
}
|
|
2241
|
+
selectElement(t);
|
|
2242
|
+
},
|
|
2243
|
+
true
|
|
2244
|
+
);
|
|
2245
|
+
document.addEventListener(
|
|
2246
|
+
"keydown",
|
|
2247
|
+
(event) => {
|
|
2248
|
+
if (!annotationMode || !shadow) return;
|
|
2249
|
+
if (event.key === "Escape") {
|
|
2250
|
+
if (shadow.querySelector(".lavish-select-pill,.lavish-annotation-card")) closeCard();
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
if (event.key === "Enter" && !event.isComposing && selected && shadow.querySelector(".lavish-select-pill")) {
|
|
2254
|
+
event.preventDefault();
|
|
2255
|
+
showAnnotationCard(selected);
|
|
2256
|
+
}
|
|
2124
2257
|
},
|
|
2125
2258
|
true
|
|
2126
2259
|
);
|
|
@@ -3001,7 +3134,7 @@ function initDefaultTelemetry(_init) {
|
|
|
3001
3134
|
// src/cli.js
|
|
3002
3135
|
var COMMANDS = /* @__PURE__ */ new Set(["open", "new", "spec", "poll", "end", "stop", "server", "playbook", "design", "setup"]);
|
|
3003
3136
|
var DESCRIPTION = "Loupe turns a proposed change into a structured visual review surface for the spec phase, so developers grasp a change by looking and clicking instead of reading walls of text. Every Loupe artifact has the same guaranteed shape: \xA7A Current World (a map of the relevant use cases with the blast radius highlighted), \xA7B Grill (interactive cards the developer answers by clicking, each with an open field), and \xA7C Goal Vision (a before -> after of the agreed change). Run `loupe new <html-file>` to scaffold that structure, fill the marked slots, then `loupe <html-file>` to open the review and `loupe poll <html-file>` to receive the developer's grill answers and annotations.";
|
|
3004
|
-
var VERSION = "0.
|
|
3137
|
+
var VERSION = "0.2.0";
|
|
3005
3138
|
async function run(argv) {
|
|
3006
3139
|
await ensureStateDir();
|
|
3007
3140
|
const normalizedArgv = normalizeArgv(argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marshalliqiu/loupe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"packageManager": "pnpm@11.1.1",
|
|
5
5
|
"description": "Inspect a change before you build it: Loupe turns a proposed change into a structured visual spec-review surface. Forked from lavish-axi.",
|
|
6
6
|
"type": "module",
|
package/skills/loupe/SKILL.md
CHANGED
|
@@ -14,7 +14,8 @@ metadata:
|
|
|
14
14
|
Loupe turns a proposed change into a structured visual review surface for the spec phase, so developers grasp a change by looking and clicking instead of reading walls of text. Every Loupe artifact has the same guaranteed shape: §A Current World (a map of the relevant use cases with the blast radius highlighted), §B Grill (interactive cards the developer answers by clicking, each with an open field), and §C Goal Vision (a before -> after of the agreed change). Run `loupe new <html-file>` to scaffold that structure, fill the marked slots, then `loupe <html-file>` to open the review and `loupe poll <html-file>` to receive the developer's grill answers and annotations.
|
|
15
15
|
|
|
16
16
|
Loupe runs through the `loupe` CLI (a fork of lavish-axi). The commands below assume `loupe`
|
|
17
|
-
is on your PATH; if it is not, run it
|
|
17
|
+
is on your PATH; if it is not, run it on demand with `npx -y @marshalliqiu/loupe ...` (no install
|
|
18
|
+
needed), or from a source checkout as `node bin/lavish-axi.js ...`.
|
|
18
19
|
|
|
19
20
|
## Request
|
|
20
21
|
|