@xynogen/pix-welcome 0.1.1 → 0.1.3
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/package.json +1 -1
- package/src/extension.ts +2 -1
- package/src/once.ts +16 -0
- package/src/welcome.test.ts +30 -0
- package/src/welcome.ts +34 -1
package/package.json
CHANGED
package/src/extension.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { once } from "./once.ts";
|
|
2
3
|
import registerWelcome from "./welcome.ts";
|
|
3
4
|
|
|
4
5
|
export default function (pi: ExtensionAPI): void {
|
|
5
|
-
registerWelcome(pi);
|
|
6
|
+
once("pix-welcome", () => registerWelcome(pi));
|
|
6
7
|
}
|
package/src/once.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idempotency guard for extension activation.
|
|
3
|
+
*
|
|
4
|
+
* pix-core (the meta-package) invokes this package's factory in addition to a
|
|
5
|
+
* possible direct install. Pi's loader uses jiti with `moduleCache: false`, so
|
|
6
|
+
* each load pass re-evaluates modules — a plain module-level flag would not be
|
|
7
|
+
* shared. The dedupe key therefore lives on `globalThis`, which persists for
|
|
8
|
+
* the lifetime of the process across all load passes.
|
|
9
|
+
*/
|
|
10
|
+
export function once(key: string, fn: () => void): void {
|
|
11
|
+
const g = globalThis as { __pixLoaded?: Set<string> };
|
|
12
|
+
const loaded = (g.__pixLoaded ??= new Set<string>());
|
|
13
|
+
if (loaded.has(key)) return;
|
|
14
|
+
loaded.add(key);
|
|
15
|
+
fn();
|
|
16
|
+
}
|
package/src/welcome.test.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
renderCheck,
|
|
8
8
|
shortCwd,
|
|
9
9
|
statusIcon,
|
|
10
|
+
summariseSkills,
|
|
10
11
|
summariseTools,
|
|
11
12
|
type Theme,
|
|
12
13
|
} from "./welcome.ts";
|
|
@@ -117,6 +118,35 @@ describe("summariseTools", () => {
|
|
|
117
118
|
});
|
|
118
119
|
});
|
|
119
120
|
|
|
121
|
+
describe("summariseSkills", () => {
|
|
122
|
+
it("warns when no skills loaded", () => {
|
|
123
|
+
const r = summariseSkills([]);
|
|
124
|
+
expect(r.status).toBe("warn");
|
|
125
|
+
expect(r.detail).toBe("none loaded");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("reports count when skills present", () => {
|
|
129
|
+
const r = summariseSkills([{}, {}, {}]);
|
|
130
|
+
expect(r.status).toBe("ok");
|
|
131
|
+
expect(r.detail).toBe("3 loaded");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("excludes skills with disableModelInvocation", () => {
|
|
135
|
+
const r = summariseSkills([{}, { disableModelInvocation: true }, {}]);
|
|
136
|
+
expect(r.status).toBe("ok");
|
|
137
|
+
expect(r.detail).toBe("2 loaded");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("warns when all skills are disabled", () => {
|
|
141
|
+
const r = summariseSkills([
|
|
142
|
+
{ disableModelInvocation: true },
|
|
143
|
+
{ disableModelInvocation: true },
|
|
144
|
+
]);
|
|
145
|
+
expect(r.status).toBe("warn");
|
|
146
|
+
expect(r.detail).toBe("none loaded");
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
120
150
|
describe("PI_IGNORE_RULES", () => {
|
|
121
151
|
it("includes both rules", () => {
|
|
122
152
|
expect(PI_IGNORE_RULES).toEqual([".pi/", ".pi-lens/"]);
|
package/src/welcome.ts
CHANGED
|
@@ -170,6 +170,17 @@ async function checkPiIgnore(
|
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
interface SkillInfo {
|
|
174
|
+
disableModelInvocation?: boolean;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function summariseSkills(skills: SkillInfo[]): CheckResult {
|
|
178
|
+
const count = skills.filter((s) => !s.disableModelInvocation).length;
|
|
179
|
+
return count > 0
|
|
180
|
+
? { label: "Skills", status: "ok", detail: `${count} loaded` }
|
|
181
|
+
: { label: "Skills", status: "warn", detail: "none loaded" };
|
|
182
|
+
}
|
|
183
|
+
|
|
173
184
|
interface ToolInfo {
|
|
174
185
|
sourceInfo?: { source?: string };
|
|
175
186
|
}
|
|
@@ -285,6 +296,7 @@ function buildCheckLines(theme: Theme, checks: CheckResult[]): string[] {
|
|
|
285
296
|
export default function (pi: ExtensionAPI) {
|
|
286
297
|
let dismissed = false;
|
|
287
298
|
let requestRender: (() => void) | null = null;
|
|
299
|
+
let _updateSkills: ((r: CheckResult) => void) | null = null;
|
|
288
300
|
|
|
289
301
|
const dismiss = (ctx: ExtensionContext) => {
|
|
290
302
|
if (dismissed) return;
|
|
@@ -306,6 +318,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
306
318
|
{ label: "Auth", status: "pending" },
|
|
307
319
|
{ label: "Models", status: "pending" },
|
|
308
320
|
{ label: "Tools", status: "pending" },
|
|
321
|
+
{ label: "Skills", status: "pending" },
|
|
309
322
|
{ label: "Ignore", status: "pending" },
|
|
310
323
|
];
|
|
311
324
|
|
|
@@ -344,8 +357,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
344
357
|
requestRender?.();
|
|
345
358
|
};
|
|
346
359
|
|
|
360
|
+
// Expose skills updater so the before_agent_start handler can fill it in.
|
|
361
|
+
_updateSkills = (r: CheckResult) => update(4, r);
|
|
362
|
+
|
|
347
363
|
void checkPiVersion(pi).then((r) => update(0, r));
|
|
348
|
-
void checkPiIgnore(pi, ctx.cwd).then((r) => update(
|
|
364
|
+
void checkPiIgnore(pi, ctx.cwd).then((r) => update(5, r));
|
|
349
365
|
// auth already filled synchronously above; no async needed
|
|
350
366
|
|
|
351
367
|
// Tools register during session_start (incl. other extensions); read on
|
|
@@ -360,6 +376,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
360
376
|
);
|
|
361
377
|
});
|
|
362
378
|
|
|
379
|
+
// Skills count is only available once resources_discover has run, which
|
|
380
|
+
// happens after session_start. Grab it from the first before_agent_start
|
|
381
|
+
// (guaranteed to have skills in systemPromptOptions) and update the banner.
|
|
382
|
+
let skillsChecked = false;
|
|
383
|
+
pi.on("before_agent_start", (event) => {
|
|
384
|
+
if (skillsChecked) return;
|
|
385
|
+
skillsChecked = true;
|
|
386
|
+
const skills = event.systemPromptOptions?.skills ?? [];
|
|
387
|
+
const result = summariseSkills(skills);
|
|
388
|
+
// Banner may already be dismissed by the time the first prompt fires —
|
|
389
|
+
// update anyway so it's accurate if still visible.
|
|
390
|
+
requestRender?.();
|
|
391
|
+
// Reach into CHECKS via the closure captured above per session.
|
|
392
|
+
// We store the updater so each session's banner is independent.
|
|
393
|
+
_updateSkills?.(result);
|
|
394
|
+
});
|
|
395
|
+
|
|
363
396
|
pi.on("turn_start", (_event, ctx) => {
|
|
364
397
|
dismiss(ctx);
|
|
365
398
|
});
|