@imfusion/web-ui 0.10.1-dev.41.g467e0e1a → 0.10.1-dev.46.g5fe8962a

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.
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { a as e, c as t, i as n, n as r, o as i, r as a, t as o } from "./paths-CZtwNknt.js";
3
- import { chmodSync as s, cpSync as c, existsSync as l, lstatSync as u, mkdirSync as d, readFileSync as f, readdirSync as p, readlinkSync as ee, realpathSync as m, rmSync as h, symlinkSync as te, writeFileSync as g } from "node:fs";
4
- import { dirname as ne, relative as _, resolve as v } from "node:path";
5
- import { Command as re, Option as ie } from "commander";
6
- import * as y from "@clack/prompts";
7
- var b = {
2
+ import { n as e, r as t, t as n } from "./paths-B5Ht3ETc.js";
3
+ import { chmodSync as r, cpSync as i, existsSync as a, lstatSync as o, mkdirSync as s, readFileSync as c, readdirSync as l, readlinkSync as u, realpathSync as d, rmSync as f, symlinkSync as p, writeFileSync as m } from "node:fs";
4
+ import { dirname as h, relative as g, resolve as _ } from "node:path";
5
+ import { Command as ee, Option as te } from "commander";
6
+ import * as v from "@clack/prompts";
7
+ var y = {
8
8
  harnesses: {
9
9
  claude: {
10
10
  label: "Claude Code",
@@ -24,91 +24,91 @@ var b = {
24
24
  skillsDir: ".agents/skills",
25
25
  linkDir: ".claude/skills",
26
26
  versionMarker: ".imf-web-ui-skill-version.json"
27
- }, ae = `<!-- ${b.namespace}:begin`, x = `<!-- ${b.namespace}:end -->`, oe = v(r, b.templatesDir, "AGENTS.md"), S = v(a, "AGENTS.md");
28
- function C(e) {
29
- let t = e.indexOf(ae), n = e.indexOf(x);
30
- return t === -1 || n === -1 ? null : e.slice(t, n + x.length);
27
+ }, ne = `<!-- ${y.namespace}:begin`, b = `<!-- ${y.namespace}:end -->`, re = _(e, y.templatesDir, "AGENTS.md"), x = _(t, "AGENTS.md");
28
+ function S(e) {
29
+ let t = e.indexOf(ne), n = e.indexOf(b);
30
+ return t === -1 || n === -1 ? null : e.slice(t, n + b.length);
31
31
  }
32
- function se() {
33
- if (!l(S)) return "absent";
34
- let e = C(f(oe, "utf-8"));
32
+ function ie() {
33
+ if (!a(x)) return "absent";
34
+ let e = S(c(re, "utf-8"));
35
35
  if (!e) return "absent";
36
- let t = f(S, "utf-8"), n = C(t), r = n ? t.replace(n, e) : `${t.trimEnd()}\n\n${e}\n`;
37
- return r === t ? "unchanged" : (g(S, r), n ? "updated" : "added");
36
+ let t = c(x, "utf-8"), n = S(t), r = n ? t.replace(n, e) : `${t.trimEnd()}\n\n${e}\n`;
37
+ return r === t ? "unchanged" : (m(x, r), n ? "updated" : "added");
38
38
  }
39
39
  //#endregion
40
40
  //#region src/cli/skills.ts
41
- var w = Object.keys(b.harnesses).filter((e) => e in b.harnesses), ce = [...w, "all"];
41
+ var C = Object.keys(y.harnesses).filter((e) => e in y.harnesses), w = [...C, "all"];
42
42
  function T(e) {
43
- return e === "all" ? w : [e];
43
+ return e === "all" ? C : [e];
44
44
  }
45
45
  function E(e) {
46
- return `./${_(a, e)}`;
46
+ return `./${g(t, e)}`;
47
47
  }
48
- var D = b.namespace, O = b.versionMarker, k = v(r, b.skillsSource), A = v(a, b.skillsDir), j = v(a, b.linkDir), M = class extends Error {}, N = JSON.parse(f(v(r, "package.json"), "utf-8"));
49
- if (typeof N != "object" || !N || !("version" in N) || typeof N.version != "string") throw Error(`${v(r, "package.json")} has no string "version" field`);
48
+ var D = y.namespace, O = y.versionMarker, k = _(e, y.skillsSource), A = _(t, y.skillsDir), j = _(t, y.linkDir), M = class extends Error {}, N = JSON.parse(c(_(e, "package.json"), "utf-8"));
49
+ if (typeof N != "object" || !N || !("version" in N) || typeof N.version != "string") throw Error(`${_(e, "package.json")} has no string "version" field`);
50
50
  var P = N.version;
51
51
  function F(e) {
52
52
  return e === D || e.startsWith(`${D}-`);
53
53
  }
54
54
  function I() {
55
- return l(k) ? p(k, { withFileTypes: !0 }).filter((e) => e.isDirectory() && F(e.name)).map((e) => e.name).sort() : [];
55
+ return a(k) ? l(k, { withFileTypes: !0 }).filter((e) => e.isDirectory() && F(e.name)).map((e) => e.name).sort() : [];
56
56
  }
57
- function le(e) {
58
- let t = v(A, e, O);
59
- if (!l(t)) return null;
57
+ function ae(e) {
58
+ let t = _(A, e, O);
59
+ if (!a(t)) return null;
60
60
  try {
61
- let e = JSON.parse(f(t, "utf-8"));
61
+ let e = JSON.parse(c(t, "utf-8"));
62
62
  if (typeof e != "object" || !e) return null;
63
63
  let n = "harnesses" in e && Array.isArray(e.harnesses) ? e.harnesses : null;
64
- return n ? w.filter((e) => n.includes(e)) : null;
64
+ return n ? C.filter((e) => n.includes(e)) : null;
65
65
  } catch {
66
66
  return null;
67
67
  }
68
68
  }
69
69
  function L(e) {
70
70
  for (let t of e) {
71
- let e = le(t);
71
+ let e = ae(t);
72
72
  if (e && e.length > 0) return e;
73
73
  }
74
74
  return null;
75
75
  }
76
76
  function R(e, t, n) {
77
- if (l(e)) for (let r of p(e).filter(F)) t.has(r) || (h(v(e, r), {
77
+ if (a(e)) for (let r of l(e).filter(F)) t.has(r) || (f(_(e, r), {
78
78
  recursive: !0,
79
79
  force: !0
80
- }), y.log.info(`Removed skill: ${r} (${n})`));
80
+ }), v.log.info(`Removed skill: ${r} (${n})`));
81
81
  }
82
- function ue() {
83
- return l(j) && l(A) && m(j) === m(A);
82
+ function oe() {
83
+ return a(j) && a(A) && d(j) === d(A);
84
84
  }
85
- function de(e) {
86
- let t = v(j, e), n = _(j, v(A, e)), r = u(t, { throwIfNoEntry: !1 });
87
- r?.isSymbolicLink() && ee(t) === n || (r && h(t, {
85
+ function z(e) {
86
+ let t = _(j, e), n = g(j, _(A, e)), r = o(t, { throwIfNoEntry: !1 });
87
+ r?.isSymbolicLink() && u(t) === n || (r && f(t, {
88
88
  recursive: !0,
89
89
  force: !0
90
- }), d(j, { recursive: !0 }), te(n, t));
90
+ }), s(j, { recursive: !0 }), p(n, t));
91
91
  }
92
- function fe(e, t) {
92
+ function se(e, t) {
93
93
  try {
94
- d(A, { recursive: !0 });
95
- let n = [];
96
- for (let r of e) {
97
- let e = v(A, r);
98
- h(e, {
94
+ s(A, { recursive: !0 });
95
+ let r = [];
96
+ for (let n of e) {
97
+ let e = _(A, n);
98
+ f(e, {
99
99
  recursive: !0,
100
100
  force: !0
101
- }), c(v(k, r), e, { recursive: !0 });
102
- let i = v(e, O);
103
- g(i, JSON.stringify({
101
+ }), i(_(k, n), e, { recursive: !0 });
102
+ let a = _(e, O);
103
+ m(a, JSON.stringify({
104
104
  version: P,
105
105
  harnesses: t
106
- }, null, 2) + "\n"), n.push(i);
106
+ }, null, 2) + "\n"), r.push(a);
107
107
  }
108
- if (o(n), R(A, new Set(e), "no longer in this bundle"), y.log.success(`Skills -> ${E(A)}/${D}*`), ue()) return;
108
+ if (n(r), R(A, new Set(e), "no longer in this bundle"), v.log.success(`Skills -> ${E(A)}/${D}*`), oe()) return;
109
109
  if (t.includes("claude")) {
110
- for (let t of e) de(t);
111
- R(j, new Set(e), "no longer in this bundle"), y.log.success(`Claude Code -> ${E(j)}/${D}* (symlinks -> ${E(A)})`);
110
+ for (let t of e) z(t);
111
+ R(j, new Set(e), "no longer in this bundle"), v.log.success(`Claude Code -> ${E(j)}/${D}* (symlinks -> ${E(A)})`);
112
112
  } else R(j, /* @__PURE__ */ new Set(), "claude not selected");
113
113
  } catch (e) {
114
114
  throw e instanceof Error ? new M(e.message) : e;
@@ -116,68 +116,68 @@ function fe(e, t) {
116
116
  }
117
117
  //#endregion
118
118
  //#region src/cli/hooks.ts
119
- var z = v(r, b.templatesDir), B = v(z, "hooks"), V = v(a, b.hooksDir), H = class extends Error {};
120
- function pe(e, t) {
121
- if (typeof e != "object" || !e || Array.isArray(e)) throw new H(`${t} is not a JSON object.`);
119
+ var B = _(e, y.templatesDir), V = _(B, "hooks"), H = _(t, y.hooksDir), U = class extends Error {};
120
+ function ce(e, t) {
121
+ if (typeof e != "object" || !e || Array.isArray(e)) throw new U(`${t} is not a JSON object.`);
122
122
  let n = { ...e };
123
123
  if ("hooks" in n) {
124
- if (typeof n.hooks != "object" || n.hooks === null || Array.isArray(n.hooks)) throw new H(`${t}: "hooks" is not a JSON object.`);
125
- for (let [e, r] of Object.entries(n.hooks)) if (!Array.isArray(r)) throw new H(`${t}: "hooks.${e}" is not a JSON array.`);
124
+ if (typeof n.hooks != "object" || n.hooks === null || Array.isArray(n.hooks)) throw new U(`${t}: "hooks" is not a JSON object.`);
125
+ for (let [e, r] of Object.entries(n.hooks)) if (!Array.isArray(r)) throw new U(`${t}: "hooks.${e}" is not a JSON array.`);
126
126
  }
127
127
  return n;
128
128
  }
129
- function U(e) {
129
+ function W(e) {
130
130
  let t;
131
131
  try {
132
- t = JSON.parse(f(e, "utf-8"));
132
+ t = JSON.parse(c(e, "utf-8"));
133
133
  } catch {
134
- throw new H(`${e} is not valid JSON.`);
134
+ throw new U(`${e} is not valid JSON.`);
135
135
  }
136
- return pe(t, e);
136
+ return ce(t, e);
137
137
  }
138
- function me() {
139
- let e = p(B).filter((e) => e.endsWith(".sh"));
140
- d(V, { recursive: !0 });
138
+ function le() {
139
+ let e = l(V).filter((e) => e.endsWith(".sh"));
140
+ s(H, { recursive: !0 });
141
141
  for (let t of e) {
142
- let e = v(V, t);
143
- c(v(B, t), e, { force: !0 }), s(e, 493);
142
+ let e = _(H, t);
143
+ i(_(V, t), e, { force: !0 }), r(e, 493);
144
144
  }
145
- let t = p(V).filter((t) => t.endsWith(".sh") && !e.includes(t));
146
- for (let e of t) h(v(V, e), { force: !0 });
145
+ let t = l(H).filter((t) => t.endsWith(".sh") && !e.includes(t));
146
+ for (let e of t) f(_(H, e), { force: !0 });
147
147
  return {
148
148
  installed: e,
149
149
  removed: t
150
150
  };
151
151
  }
152
- var W = `${b.hooksDir}/`;
153
- function G(e) {
152
+ var G = `${y.hooksDir}/`;
153
+ function K(e) {
154
154
  if (typeof e != "string") return null;
155
- let t = e.lastIndexOf(W);
155
+ let t = e.lastIndexOf(G);
156
156
  if (t === -1) return null;
157
- let n = /^([\w.-]+)["']?$/.exec(e.slice(t + W.length));
157
+ let n = /^([\w.-]+)["']?$/.exec(e.slice(t + G.length));
158
158
  return n ? n[1] : null;
159
159
  }
160
- function K(e) {
161
- return typeof e == "object" && !!e && "hooks" in e && Array.isArray(e.hooks);
162
- }
163
160
  function q(e) {
164
- return K(e) ? e.hooks : null;
161
+ return typeof e == "object" && !!e && "hooks" in e && Array.isArray(e.hooks);
165
162
  }
166
163
  function J(e) {
167
- return typeof e == "object" && e && "command" in e ? e.command : void 0;
164
+ return q(e) ? e.hooks : null;
168
165
  }
169
166
  function Y(e) {
170
- let t = q(e);
171
- return t ? t.map((e) => G(J(e))).filter((e) => e !== null) : [];
167
+ return typeof e == "object" && e && "command" in e ? e.command : void 0;
168
+ }
169
+ function X(e) {
170
+ let t = J(e);
171
+ return t ? t.map((e) => K(Y(e))).filter((e) => e !== null) : [];
172
172
  }
173
- function he(e, t, n) {
173
+ function ue(e, t, n) {
174
174
  e.hooks ??= {};
175
175
  let r = 0, i = 0, a = [];
176
176
  for (let [r, o] of Object.entries(e.hooks)) {
177
177
  let s = 0, c = o.map((e) => {
178
- if (!K(e)) return e;
178
+ if (!q(e)) return e;
179
179
  let t = e.hooks, r = t.filter((e) => {
180
- let t = G(J(e));
180
+ let t = K(Y(e));
181
181
  return t === null || n.includes(t);
182
182
  });
183
183
  return s += t.length - r.length, r.length === t.length ? e : {
@@ -185,7 +185,7 @@ function he(e, t, n) {
185
185
  hooks: r
186
186
  };
187
187
  }).filter((e) => {
188
- let t = q(e);
188
+ let t = J(e);
189
189
  return t === null || t.length > 0;
190
190
  });
191
191
  s !== 0 && (i += s, c.length === 0 && !(r in t) ? a.push(r) : e.hooks[r] = c);
@@ -194,8 +194,8 @@ function he(e, t, n) {
194
194
  for (let [n, i] of Object.entries(t)) {
195
195
  e.hooks[n] ??= [];
196
196
  for (let t of i) {
197
- let i = Y(t);
198
- e.hooks[n].some((e) => Y(e).some((e) => i.includes(e))) || (e.hooks[n].push(t), r++);
197
+ let i = X(t);
198
+ e.hooks[n].some((e) => X(e).some((e) => i.includes(e))) || (e.hooks[n].push(t), r++);
199
199
  }
200
200
  }
201
201
  return {
@@ -203,72 +203,72 @@ function he(e, t, n) {
203
203
  removed: i
204
204
  };
205
205
  }
206
- function ge(e) {
207
- let t = /* @__PURE__ */ new Map(), n = /* @__PURE__ */ new Map();
208
- for (let r of w) {
209
- let i = v(a, b.harnesses[r].hookSettings);
210
- if (l(i) && t.set(r, U(i)), e.includes(r)) {
211
- let e = v(z, b.harnesses[r].hookTemplate);
212
- n.set(r, U(e).hooks ?? {});
206
+ function de(e) {
207
+ let r = /* @__PURE__ */ new Map(), i = /* @__PURE__ */ new Map();
208
+ for (let n of C) {
209
+ let o = _(t, y.harnesses[n].hookSettings);
210
+ if (a(o) && r.set(n, W(o)), e.includes(n)) {
211
+ let e = _(B, y.harnesses[n].hookTemplate);
212
+ i.set(n, W(e).hooks ?? {});
213
213
  }
214
214
  }
215
- let { installed: r, removed: i } = me();
216
- y.log.success(`Agent hooks -> ${E(V)} (${String(r.length)} scripts)`), i.length > 0 && y.log.info(`Removed stale hook script(s): ${i.join(", ")} (no longer shipped)`);
217
- for (let i of w) {
218
- let s = e.includes(i);
219
- if (!s && !t.has(i)) continue;
220
- let c = v(a, b.harnesses[i].hookSettings), l = n.get(i) ?? {}, u = t.get(i) ?? {}, f = he(u, l, s ? r : []);
215
+ let { installed: o, removed: c } = le();
216
+ v.log.success(`Agent hooks -> ${E(H)} (${String(o.length)} scripts)`), c.length > 0 && v.log.info(`Removed stale hook script(s): ${c.join(", ")} (no longer shipped)`);
217
+ for (let a of C) {
218
+ let c = e.includes(a);
219
+ if (!c && !r.has(a)) continue;
220
+ let l = _(t, y.harnesses[a].hookSettings), u = i.get(a) ?? {}, d = r.get(a) ?? {}, f = ue(d, u, c ? o : []);
221
221
  if (f.added === 0 && f.removed === 0) {
222
- s && y.log.info(`Hook registrations already present in ${E(c)}`);
222
+ c && v.log.info(`Hook registrations already present in ${E(l)}`);
223
223
  continue;
224
224
  }
225
- d(ne(c), { recursive: !0 }), g(c, JSON.stringify(u, null, 2) + "\n"), o([c]);
225
+ s(h(l), { recursive: !0 }), m(l, JSON.stringify(d, null, 2) + "\n"), n([l]);
226
226
  let p = [f.added > 0 && `registered ${String(f.added)}`, f.removed > 0 && `removed ${String(f.removed)} stale`].filter(Boolean);
227
- y.log.success(`${p.join(", ")} hook registration(s) in ${E(c)}`);
227
+ v.log.success(`${p.join(", ")} hook registration(s) in ${E(l)}`);
228
228
  }
229
229
  }
230
230
  //#endregion
231
231
  //#region src/cli/index.ts
232
- function X() {
233
- return new ie("--harness <harness>", "claude, codex, or all").choices(ce);
232
+ function Z() {
233
+ return new te("--harness <harness>", "claude, codex, or all").choices(w);
234
234
  }
235
- async function Z(e, t, n) {
236
- if (t) return y.log.info(`Harness from --harness flag: ${t.join(", ")}`), t;
235
+ async function fe(e, t, n) {
236
+ if (t) return v.log.info(`Harness from --harness flag: ${t.join(", ")}`), t;
237
237
  let r = L(e);
238
- if (r && !n) return y.log.info(`Refreshing the existing install: ${r.join(", ")} — pass --reconfigure to choose differently.`), r;
238
+ if (r && !n) return v.log.info(`Refreshing the existing install: ${r.join(", ")} — pass --reconfigure to choose differently.`), r;
239
239
  if (!process.stdin.isTTY) return console.error("Pass --harness=claude|codex|all — no terminal to prompt in."), process.exitCode = 1, null;
240
- let i = await y.multiselect({
240
+ let i = await v.multiselect({
241
241
  message: "Install into which harness (or harnesses)?",
242
- options: w.map((e) => ({
242
+ options: C.map((e) => ({
243
243
  value: e,
244
- label: b.harnesses[e].label
244
+ label: y.harnesses[e].label
245
245
  })),
246
246
  required: !0
247
247
  });
248
- return y.isCancel(i) ? (y.cancel("Cancelled — nothing installed."), null) : i;
248
+ return v.isCancel(i) ? (v.cancel("Cancelled — nothing installed."), null) : i;
249
249
  }
250
- async function _e(e) {
250
+ async function pe(e) {
251
251
  let t = e.harness ? T(e.harness) : null, n = I();
252
- if (y.intro(`@imfusion/web-ui install skills — ${String(n.length)} skills`), n.length === 0) {
253
- y.log.error("No skills found. Reinstall @imfusion/web-ui and try again."), y.outro("Nothing installed."), process.exitCode = 1;
252
+ if (v.intro(`@imfusion/web-ui install skills — ${String(n.length)} skills`), n.length === 0) {
253
+ v.log.error("No skills found. Reinstall @imfusion/web-ui and try again."), v.outro("Nothing installed."), process.exitCode = 1;
254
254
  return;
255
255
  }
256
- let r = await Z(n, t, e.reconfigure === !0);
256
+ let r = await fe(n, t, e.reconfigure === !0);
257
257
  if (!r) return;
258
- y.log.message(`Skills in this bundle:\n${n.map((e) => ` - ${e}`).join("\n")}`);
258
+ v.log.message(`Skills in this bundle:\n${n.map((e) => ` - ${e}`).join("\n")}`);
259
259
  try {
260
- fe(n, r);
260
+ se(n, r);
261
261
  } catch (e) {
262
262
  if (e instanceof M) {
263
- y.log.error(e.message), y.outro("Install failed."), process.exitCode = 1;
263
+ v.log.error(e.message), v.outro("Install failed."), process.exitCode = 1;
264
264
  return;
265
265
  }
266
266
  throw e;
267
267
  }
268
- let i = se();
269
- (i === "updated" || i === "added") && y.log.success(`Refreshed the ${b.namespace} block in ./AGENTS.md`), y.outro(`Done — ${String(n.length)} skills installed.`);
268
+ let i = ie();
269
+ (i === "updated" || i === "added") && v.log.success(`Refreshed the ${y.namespace} block in ./AGENTS.md`), v.outro(`Done — ${String(n.length)} skills installed.`);
270
270
  }
271
- function ve(e) {
271
+ function me(e) {
272
272
  let t = e.harness ? T(e.harness) : null, n = L(I());
273
273
  if (!t && !n) {
274
274
  console.error("No harness recorded. Pass --harness=claude|codex|all."), process.exitCode = 1;
@@ -278,56 +278,24 @@ function ve(e) {
278
278
  if (i.length > 0) {
279
279
  for (let e of i) console.error(`No skills installed for ${e}. Run \`npx web-ui install skills --harness=${e}\` first.`);
280
280
  process.exitCode = 1;
281
- return;
282
- }
283
- y.intro("@imfusion/web-ui install hooks");
284
- try {
285
- ge(r);
286
- } catch (e) {
287
- if (e instanceof H) {
288
- console.error(e.message), process.exitCode = 1;
289
- return;
281
+ } else {
282
+ v.intro("@imfusion/web-ui install hooks");
283
+ try {
284
+ de(r);
285
+ } catch (e) {
286
+ if (e instanceof U) {
287
+ console.error(e.message), process.exitCode = 1;
288
+ return;
289
+ }
290
+ throw e;
290
291
  }
291
- throw e;
292
- }
293
- y.outro("Done — agent hooks installed.");
294
- }
295
- function ye(r) {
296
- let i = r ?? process.env.WEB_UI_CHECKOUT;
297
- if (!i) {
298
- console.error("Pass the checkout path, or set WEB_UI_CHECKOUT."), process.exitCode = 1;
299
- return;
300
- }
301
- let o = v(i);
302
- try {
303
- let t = e(a, o);
304
- y.log.success(`Linked ${o} into ${E(t)}`);
305
- } catch (e) {
306
- if (e instanceof n) {
307
- console.error(e.message), process.exitCode = 1;
308
- return;
309
- }
310
- throw e;
311
- }
312
- y.log.info("Syncing on every rebuild of dist/. Ctrl+C stops syncing; the app keeps the last synced build."), t(a, o, (e) => {
313
- y.log.info(e);
314
- });
315
- }
316
- function be() {
317
- try {
318
- for (let e of i(a)) y.log.message(e);
319
- } catch (e) {
320
- if (e instanceof n) {
321
- console.error(e.message), process.exitCode = 1;
322
- return;
323
- }
324
- throw e;
292
+ v.outro("Done — agent hooks installed.");
325
293
  }
326
294
  }
327
- var Q = new re();
295
+ var Q = new ee();
328
296
  Q.name("web-ui").description("Command-line tools for @imfusion/web-ui").version(P);
329
297
  var $ = Q.command("install").description("Install @imfusion/web-ui agent skills or hooks into a project");
330
- $.command("skills").description("Install skills for the chosen harness").addOption(X()).option("--reconfigure", "Re-open the harness selection prompt on an existing install").action(_e), $.command("hooks").description("Install agent lifecycle hooks (requires skills already installed for the harness)").addOption(X()).action(ve), Q.command("link").description("Serve a local @imfusion/web-ui checkout from this project and sync it on every rebuild").argument("[path]", "checkout directory (default: WEB_UI_CHECKOUT)").action(ye), Q.command("unlink").description("Restore the published @imfusion/web-ui files in this project").action(be), $.action(() => {
298
+ $.command("skills").description("Install skills for the chosen harness").addOption(Z()).option("--reconfigure", "Re-open the harness selection prompt on an existing install").action(pe), $.command("hooks").description("Install agent lifecycle hooks (requires skills already installed for the harness)").addOption(Z()).action(me), $.action(() => {
331
299
  $.help();
332
300
  }), Q.action(() => {
333
301
  Q.help();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imfusion/web-ui",
3
- "version": "0.10.1-dev.41.g467e0e1a",
3
+ "version": "0.10.1-dev.46.g5fe8962a",
4
4
  "description": "The official Web UI component library for ImFusion web apps",
5
5
  "author": "ImFusion GmbH",
6
6
  "homepage": "https://imfusion.com",
@@ -138,7 +138,7 @@
138
138
  "@types/react": "19.2.14",
139
139
  "@types/react-dom": "19.2.3",
140
140
  "@vitejs/plugin-react": "6.0.1",
141
- "@vitest/browser-playwright": "5.0.3",
141
+ "@vitest/browser-playwright": "5.0.4",
142
142
  "concurrently": "9.2.1",
143
143
  "culori": "4.0.2",
144
144
  "fast-xml-parser": "5.11.2",
@@ -148,19 +148,21 @@
148
148
  "mermaid": "12.0.0",
149
149
  "playwright": "1.60.0",
150
150
  "prettier": "3.8.3",
151
+ "react": "19.2.5",
151
152
  "react-docgen-typescript": "2.4.0",
153
+ "react-dom": "19.2.5",
152
154
  "remark-gfm": "4.0.1",
153
155
  "storybook": "10.6.2",
154
156
  "tsx": "4.21.0",
155
157
  "typescript": "6.0.3",
156
- "vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
158
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.2.0",
157
159
  "vite-plugin-dts": "5.0.0",
158
- "vite-plus": "1.1.0",
159
- "vitest": "5.0.3"
160
+ "vite-plus": "1.2.0",
161
+ "vitest": "5.0.4"
160
162
  },
161
163
  "overrides": {
162
- "vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
163
- "vitest": "5.0.3",
164
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.2.0",
165
+ "vitest": "5.0.4",
164
166
  "storybook": {
165
167
  ".": "$storybook",
166
168
  "vite-plus": "$vite-plus"
@@ -4,6 +4,8 @@
4
4
  # and warns on drift. Always exits 0 — staleness never blocks a commit.
5
5
  PKG=node_modules/@imfusion/web-ui/package.json
6
6
  [ -f "$PKG" ] || exit 0
7
+ # A symlinked checkout reports its own dev version.
8
+ [ -L node_modules/@imfusion/web-ui ] && exit 0
7
9
  CURRENT=$(node -p "require('./$PKG').version" 2>/dev/null) || exit 0
8
10
 
9
11
  # .claude/skills may be a symlink to .agents/skills; skip markers already read.
@@ -10,9 +10,12 @@ git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
10
10
  [ -n "$(git status --porcelain -- '*.ts' '*.tsx' '*.js' '*.jsx' '*.css' 2>/dev/null)" ] || exit 0
11
11
  # Gate only turns that actually edited files — a tree left dirty by an earlier
12
12
  # session must not block a question-answering turn. Edit evidence: Write/Edit
13
- # tool calls (Claude transcript) or apply_patch calls (Codex rollout).
13
+ # tool calls (Claude transcript) or apply_patch calls (Codex rollout), searched
14
+ # only after the turn's opening entry: the last real user prompt (not a tool
15
+ # result or injected meta message) or the last Codex task_started event.
14
16
  TRANSCRIPT=$(printf '%s' "$PAYLOAD" | sed -n 's/.*"transcript_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)
15
17
  [ -f "$TRANSCRIPT" ] || exit 0
16
- grep -q '"name":"Write"\|"name":"Edit"\|"name":"MultiEdit"\|"name":"NotebookEdit"\|apply_patch' "$TRANSCRIPT" || exit 0
18
+ TURN_START=$(grep -En '"type":"user"|"type":"task_started"' "$TRANSCRIPT" | grep -Ev '"type":"tool_result"|"isMeta":true' | tail -n 1 | cut -d: -f1)
19
+ tail -n +$(( ${TURN_START:-0} + 1 )) "$TRANSCRIPT" | grep -Eq '"name":"Write"|"name":"Edit"|"name":"MultiEdit"|"name":"NotebookEdit"|"name":"apply_patch"' || exit 0
17
20
  printf '{"decision":"block","reason":"web-ui stop gate: this turn changed source files. Run the project verification (verify:typecheck, verify:lint, and verify:format at minimum), fix what it reports, then finish. If it already ran after the last change, say so and finish."}\n'
18
21
  exit 0
@@ -3,8 +3,7 @@ name: imf-web-ui
3
3
  description:
4
4
  "Route UI work and library questions in a project that uses @imfusion/web-ui. Use this skill whenever a user adds, edits,
5
5
  styles, or reviews UI, or asks about library setup, brand assets, favicons, theming, or usage in a consumer project, even
6
- if they do not mention the library. Also use it to link, unlink, or use a live local Web UI checkout. Open only the
7
- matching packaged guide or companion skill."
6
+ if they do not mention the library. Open only the matching packaged guide or companion skill."
8
7
  ---
9
8
 
10
9
  # Route Web UI work
@@ -24,19 +23,18 @@ may not already have. Open a companion by invoking its skill; reading one of its
24
23
 
25
24
  ## 2. Route the task
26
25
 
27
- | Task | Open |
28
- | -------------------------------------------------------------------- | ---------------------------------------- |
29
- | Understand library setup, brand assets, theming, or usage | Packaged user guides below |
30
- | Look up a component, part, prop, default, or icon | `imf-web-ui-components` |
31
- | Choose components or shape a screen, form, or flow | `imf-web-ui-ux` |
32
- | Write or update documentation | `imf-web-ui-conventions` |
33
- | Write a wrapper, custom UI, CSS, data layer, validation, or tests | `imf-web-ui-conventions` |
34
- | Choose a routing, data, form, or validation library for the project | `imf-web-ui-conventions` |
35
- | Install the library or bootstrap project tooling | `imf-web-ui-setup` |
36
- | Diagnose a broken install: unstyled output, missing tokens, no theme | `imf-web-ui-setup` |
37
- | Inspect an existing project without changing it | `imf-web-ui-audit` |
38
- | Update the package, skills, or hooks | `imf-web-ui-update` |
39
- | Link, unlink, or use a live local Web UI checkout | `imf-web-ui-conventions` `tooling` topic |
26
+ | Task | Open |
27
+ | -------------------------------------------------------------------- | -------------------------- |
28
+ | Understand library setup, brand assets, theming, or usage | Packaged user guides below |
29
+ | Look up a component, part, prop, default, or icon | `imf-web-ui-components` |
30
+ | Choose components or shape a screen, form, or flow | `imf-web-ui-ux` |
31
+ | Write or update documentation | `imf-web-ui-conventions` |
32
+ | Write a wrapper, custom UI, CSS, data layer, validation, or tests | `imf-web-ui-conventions` |
33
+ | Choose a routing, data, form, or validation library for the project | `imf-web-ui-conventions` |
34
+ | Install the library or bootstrap project tooling | `imf-web-ui-setup` |
35
+ | Diagnose a broken install: unstyled output, missing tokens, no theme | `imf-web-ui-setup` |
36
+ | Inspect an existing project without changing it | `imf-web-ui-audit` |
37
+ | Update the package, skills, or hooks | `imf-web-ui-update` |
40
38
 
41
39
  A new form opens `imf-web-ui-ux`, even when its fields and action are already specified. A screen often needs both
42
40
  `imf-web-ui-ux` and `imf-web-ui-components`, in that order. Setup and audit are for project-wide questions, not every
@@ -61,7 +61,10 @@ Classify every result as one of these:
61
61
  Use the report contract's severity, `path:line` evidence, concrete impact, and smallest next action. Unverified entries use
62
62
  that same structure: unavailable runtime evidence, the impact of the unknown, and the check that resolves it. Do not promote
63
63
  an optional tool or a working alternative to a missing finding. A `vite.config.ts` without `localWebUILink()` registered from
64
- `@imfusion/web-ui/vite` is Missing. Its next action is the one-line add from the `tooling` topic.
64
+ `@imfusion/web-ui/vite` is Missing. A `tsconfig.json` without the `paths` block from the `tooling` topic is Missing too. Each
65
+ next action is the add from that topic. A major or minor mismatch between the project and Web UI on a package listed under
66
+ Aligned runtime packages in the `tooling` topic is a Deviation. Report both versions, with
67
+ `npm install <package>@<Web UI's version>` as the fix.
65
68
 
66
69
  ## Topics
67
70