yourskills 0.1.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.
Files changed (2) hide show
  1. package/dist/main.js +4313 -0
  2. package/package.json +58 -0
package/dist/main.js ADDED
@@ -0,0 +1,4313 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/main.tsx
4
+ import { realpathSync } from "fs";
5
+ import { fileURLToPath } from "url";
6
+ import { render } from "ink";
7
+
8
+ // src/auth/auth.ts
9
+ import { setTimeout as sleep } from "node:timers/promises";
10
+
11
+ // src/settings/store.ts
12
+ import { chmodSync, mkdirSync, rmSync } from "node:fs";
13
+ import { readFile, writeFile } from "node:fs/promises";
14
+ import { homedir } from "node:os";
15
+ import { join } from "node:path";
16
+ var DEFAULT_URL = "http://localhost:3000";
17
+ function configDir() {
18
+ const xdg = process.env.XDG_CONFIG_HOME;
19
+ return join(xdg && xdg.length > 0 ? xdg : join(homedir(), ".config"), "yourskills");
20
+ }
21
+ function configPath() {
22
+ return join(configDir(), "config.json");
23
+ }
24
+ async function readConfig() {
25
+ try {
26
+ return JSON.parse(await readFile(configPath(), "utf8"));
27
+ } catch {
28
+ return {};
29
+ }
30
+ }
31
+ async function writeConfig(config) {
32
+ mkdirSync(configDir(), { recursive: true, mode: 448 });
33
+ await writeFile(configPath(), `${JSON.stringify(config, null, 2)}
34
+ `);
35
+ chmodSync(configPath(), 384);
36
+ }
37
+ function clearConfig() {
38
+ rmSync(configPath(), { force: true });
39
+ }
40
+ function serverUrl(config = {}, override) {
41
+ return override || process.env.YOURSKILLS_URL || config.serverUrl || DEFAULT_URL;
42
+ }
43
+
44
+ // src/settings/agents.ts
45
+ async function resolveAgents(flagAgents) {
46
+ if (flagAgents && flagAgents.length > 0)
47
+ return flagAgents;
48
+ const config = await readConfig();
49
+ return config.agents ?? [];
50
+ }
51
+ // src/settings/keys.ts
52
+ var CONFIG_KEYS = ["scope", "agents", "server", "track"];
53
+ function isConfigKey(key) {
54
+ return CONFIG_KEYS.includes(key);
55
+ }
56
+ var REDACTED = "<redacted>";
57
+ var UNSET = "(unset)";
58
+ function configValue(config, key) {
59
+ switch (key) {
60
+ case "scope":
61
+ return config.defaultScope ?? UNSET;
62
+ case "agents":
63
+ return config.agents?.length ? config.agents.join(",") : UNSET;
64
+ case "server":
65
+ return config.serverUrl ?? UNSET;
66
+ case "track":
67
+ return config.track === undefined ? UNSET : String(config.track);
68
+ }
69
+ }
70
+ function setConfigValue(config, key, value) {
71
+ if (!isConfigKey(key)) {
72
+ return {
73
+ ok: false,
74
+ message: `Unknown config key: ${key}. Known keys: ${CONFIG_KEYS.join(", ")}.`
75
+ };
76
+ }
77
+ switch (key) {
78
+ case "scope": {
79
+ if (value !== "global" && value !== "project") {
80
+ return {
81
+ ok: false,
82
+ message: "scope must be `global` or `project`."
83
+ };
84
+ }
85
+ return {
86
+ ok: true,
87
+ config: { ...config, defaultScope: value },
88
+ shown: value
89
+ };
90
+ }
91
+ case "agents": {
92
+ const agents = value.split(",").map((a) => a.trim()).filter((a) => a.length > 0);
93
+ if (agents.length === 0) {
94
+ return {
95
+ ok: false,
96
+ message: "agents must be a comma separated list, for example `claude-code,codex`."
97
+ };
98
+ }
99
+ return {
100
+ ok: true,
101
+ config: { ...config, agents },
102
+ shown: agents.join(",")
103
+ };
104
+ }
105
+ case "server": {
106
+ let url;
107
+ try {
108
+ url = new URL(value);
109
+ } catch {
110
+ return {
111
+ ok: false,
112
+ message: `server must be a URL, for example \`https://skills.acme.dev\`. Got: ${value}`
113
+ };
114
+ }
115
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
116
+ return { ok: false, message: "server must be an http or https URL." };
117
+ }
118
+ const serverUrl2 = url.toString().replace(/\/$/, "");
119
+ return { ok: true, config: { ...config, serverUrl: serverUrl2 }, shown: serverUrl2 };
120
+ }
121
+ case "track": {
122
+ if (value !== "true" && value !== "false") {
123
+ return { ok: false, message: "track must be `true` or `false`." };
124
+ }
125
+ const track = value === "true";
126
+ return { ok: true, config: { ...config, track }, shown: String(track) };
127
+ }
128
+ }
129
+ }
130
+ function configLines(config) {
131
+ const width = Math.max(...CONFIG_KEYS.map((k) => k.length), "user".length);
132
+ const lines = CONFIG_KEYS.map((key) => ({
133
+ mark: "none",
134
+ text: `${key.padEnd(width)} ${configValue(config, key)}`
135
+ }));
136
+ lines.push({
137
+ mark: "none",
138
+ text: `${"user".padEnd(width)} ${config.userEmail ?? UNSET}`
139
+ });
140
+ lines.push({
141
+ mark: "none",
142
+ text: `${"token".padEnd(width)} ${config.token ? REDACTED : UNSET}`
143
+ });
144
+ return lines;
145
+ }
146
+ function configGetReport(config, key, path) {
147
+ if (key === undefined) {
148
+ return {
149
+ title: "config",
150
+ lines: configLines(config),
151
+ json: {
152
+ path,
153
+ ...Object.fromEntries(CONFIG_KEYS.map((k) => [k, configValue(config, k)])),
154
+ user: config.userEmail ?? null,
155
+ token: config.token ? REDACTED : null
156
+ }
157
+ };
158
+ }
159
+ if (!isConfigKey(key)) {
160
+ return {
161
+ title: "config",
162
+ lines: [
163
+ {
164
+ mark: "fail",
165
+ text: `Unknown config key: ${key}`,
166
+ note: `Known keys: ${CONFIG_KEYS.join(", ")}`
167
+ }
168
+ ],
169
+ json: { path, error: `unknown key: ${key}`, keys: CONFIG_KEYS },
170
+ failed: true
171
+ };
172
+ }
173
+ const value = configValue(config, key);
174
+ return {
175
+ title: "config",
176
+ lines: [{ mark: "none", text: value }],
177
+ json: { path, key, value }
178
+ };
179
+ }
180
+
181
+ // src/settings/command.ts
182
+ async function configGet(key) {
183
+ return configGetReport(await readConfig(), key, configPath());
184
+ }
185
+ async function configSet(key, value) {
186
+ const path = configPath();
187
+ const config = await readConfig();
188
+ const result = setConfigValue(config, key, value);
189
+ if (!result.ok) {
190
+ return {
191
+ title: "config",
192
+ lines: [{ mark: "fail", text: result.message }],
193
+ json: { path, error: result.message },
194
+ failed: true
195
+ };
196
+ }
197
+ await writeConfig(result.config);
198
+ return {
199
+ title: "config",
200
+ lines: [{ mark: "ok", text: `${key} = ${result.shown}`, note: path }],
201
+ json: { path, key, value: result.shown }
202
+ };
203
+ }
204
+ // src/settings/scope.ts
205
+ import { existsSync } from "node:fs";
206
+ import { readFile as readFile2 } from "node:fs/promises";
207
+ import { dirname, join as join2, resolve } from "node:path";
208
+ var PROJECT_MANIFEST = "yourskills.json";
209
+ function findProjectManifest(from) {
210
+ let dir = resolve(from ?? process.cwd());
211
+ for (;; ) {
212
+ const candidate = join2(dir, PROJECT_MANIFEST);
213
+ if (existsSync(candidate))
214
+ return candidate;
215
+ const parent = dirname(dir);
216
+ if (parent === dir)
217
+ return null;
218
+ dir = parent;
219
+ }
220
+ }
221
+ async function scopeInManifest(path) {
222
+ try {
223
+ const parsed = JSON.parse(await readFile2(path, "utf8"));
224
+ return parsed.scope === "global" || parsed.scope === "project" ? parsed.scope : null;
225
+ } catch {
226
+ return null;
227
+ }
228
+ }
229
+ async function resolveScope(opts = {}) {
230
+ const manifestPath = findProjectManifest(opts.cwd);
231
+ if (opts.flag)
232
+ return { scope: opts.flag, source: "flag", manifestPath };
233
+ if (manifestPath) {
234
+ const declared = await scopeInManifest(manifestPath);
235
+ if (declared)
236
+ return { scope: declared, source: "project", manifestPath };
237
+ }
238
+ const config = await readConfig();
239
+ if (config.defaultScope)
240
+ return { scope: config.defaultScope, source: "config", manifestPath };
241
+ return { scope: "global", source: "default", manifestPath };
242
+ }
243
+ // src/auth/auth.ts
244
+ var CLIENT_ID = "yourskills-cli";
245
+ var GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
246
+ var SLOW_DOWN_STEP_MS = 5000;
247
+ var authBase = (base) => `${base.replace(/\/$/, "")}/api/auth`;
248
+ async function post(url, body) {
249
+ const res = await fetch(url, {
250
+ method: "POST",
251
+ headers: { "content-type": "application/json" },
252
+ body: JSON.stringify(body)
253
+ });
254
+ const text = await res.text();
255
+ let parsed;
256
+ try {
257
+ parsed = JSON.parse(text);
258
+ } catch {
259
+ return {
260
+ error: "invalid_response",
261
+ error_description: text.slice(0, 200) || res.statusText
262
+ };
263
+ }
264
+ if (res.ok)
265
+ return { ok: true, data: parsed };
266
+ const err = parsed;
267
+ return {
268
+ error: err.error ?? `http_${res.status}`,
269
+ error_description: err.error_description
270
+ };
271
+ }
272
+ async function* login() {
273
+ const config = await readConfig();
274
+ const base = serverUrl(config);
275
+ const start = await post(`${authBase(base)}/device/code`, {
276
+ client_id: CLIENT_ID
277
+ });
278
+ if (!("ok" in start)) {
279
+ throw new Error(`Could not start device login: ${start.error_description ?? start.error}`);
280
+ }
281
+ const device = start.data;
282
+ const deadline = Date.now() + device.expires_in * 1000;
283
+ yield {
284
+ kind: "prompt",
285
+ userCode: device.user_code,
286
+ verificationUri: device.verification_uri_complete ?? device.verification_uri,
287
+ expiresAt: deadline
288
+ };
289
+ let intervalMs = Math.max(device.interval, 1) * 1000;
290
+ while (Date.now() < deadline) {
291
+ await sleep(intervalMs);
292
+ const poll = await post(`${authBase(base)}/device/token`, {
293
+ grant_type: GRANT_TYPE,
294
+ device_code: device.device_code,
295
+ client_id: CLIENT_ID
296
+ });
297
+ if ("ok" in poll) {
298
+ const token = poll.data.access_token;
299
+ const who = await fetchSession(base, token);
300
+ await writeConfig({
301
+ ...config,
302
+ token,
303
+ serverUrl: base,
304
+ userEmail: who?.user?.email
305
+ });
306
+ yield { kind: "done", email: who?.user?.email };
307
+ return;
308
+ }
309
+ switch (poll.error) {
310
+ case "authorization_pending":
311
+ yield { kind: "waiting" };
312
+ break;
313
+ case "slow_down":
314
+ intervalMs += SLOW_DOWN_STEP_MS;
315
+ yield { kind: "waiting" };
316
+ break;
317
+ case "access_denied":
318
+ throw new Error("Login was denied.");
319
+ case "expired_token":
320
+ throw new Error("The device code expired. Run `yourskills login` again.");
321
+ default:
322
+ throw new Error(`Login failed: ${poll.error_description ?? poll.error}`);
323
+ }
324
+ }
325
+ throw new Error("The device code expired. Run `yourskills login` again.");
326
+ }
327
+ async function logout() {
328
+ const config = await readConfig();
329
+ if (config.token) {
330
+ await fetch(`${authBase(serverUrl(config))}/sign-out`, {
331
+ method: "POST",
332
+ headers: { authorization: `Bearer ${config.token}` }
333
+ }).catch(() => {
334
+ return;
335
+ });
336
+ }
337
+ clearConfig();
338
+ }
339
+ async function fetchSession(base, token) {
340
+ const res = await fetch(`${authBase(base)}/get-session`, {
341
+ headers: { authorization: `Bearer ${token}` }
342
+ });
343
+ if (!res.ok)
344
+ return null;
345
+ return await res.json();
346
+ }
347
+ async function whoami(server) {
348
+ const config = await readConfig();
349
+ if (!config.token)
350
+ throw new Error("Not logged in. Run `yourskills login`.");
351
+ const base = serverUrl(config, server);
352
+ const session = await fetchSession(base, config.token);
353
+ if (!session?.user)
354
+ throw new Error("Session is no longer valid. Run `yourskills login`.");
355
+ return {
356
+ user: session.user.email ?? session.user.id,
357
+ org: session.session?.activeOrganizationId ?? null,
358
+ server: base
359
+ };
360
+ }
361
+ async function requireToken(server) {
362
+ const config = await readConfig();
363
+ if (!config.token)
364
+ throw new Error("Not logged in. Run `yourskills login`.");
365
+ return { token: config.token, base: serverUrl(config, server) };
366
+ }
367
+ async function hasSession() {
368
+ return Boolean((await readConfig()).token);
369
+ }
370
+ // src/auth/screens/gate.tsx
371
+ import { Box as Box5, Text as Text7, useApp as useApp2, useInput } from "ink";
372
+ import {
373
+ useCallback,
374
+ useEffect as useEffect3,
375
+ useMemo,
376
+ useState as useState3
377
+ } from "react";
378
+
379
+ // src/ui/command.tsx
380
+ import { Box as Box2, Text as Text3 } from "ink";
381
+
382
+ // ../../packages/ui/src/lib/ascii.ts
383
+ var DEFAULT_ASCII_CHARS = {
384
+ top: "-",
385
+ bottom: "-",
386
+ left: "|",
387
+ right: "|",
388
+ divider: "-",
389
+ junction: "+"
390
+ };
391
+ function fill(n, seq = "-") {
392
+ const count = Math.max(n, 0);
393
+ if (count === 0) {
394
+ return "";
395
+ }
396
+ const unit = seq.length > 0 ? seq : " ";
397
+ return unit.repeat(Math.ceil(count / unit.length)).slice(0, count);
398
+ }
399
+ function junctionGlyph(chars) {
400
+ return chars.junction.length > 0 ? chars.junction[0] : DEFAULT_ASCII_CHARS.junction;
401
+ }
402
+ function topBorder(width, title, chars = DEFAULT_ASCII_CHARS) {
403
+ const j = junctionGlyph(chars);
404
+ if (!title) {
405
+ return `${j}${fill(width - 2, chars.top)}${j}`;
406
+ }
407
+ const label = ` ${title} `;
408
+ const left = 2;
409
+ const right = Math.max(width - 2 - left - label.length, 1);
410
+ return `${j}${fill(left, chars.top)}${label}${fill(right, chars.top)}${j}`;
411
+ }
412
+
413
+ // src/ui/frame.tsx
414
+ import { Box, Text, useWindowSize } from "ink";
415
+
416
+ // src/ui/theme.ts
417
+ var tone = {
418
+ primary: "cyan",
419
+ notice: "yellow",
420
+ danger: "red"
421
+ };
422
+ var stateTone = {
423
+ "state-active": undefined,
424
+ "state-experimental": "yellow",
425
+ "state-deprecated": "red"
426
+ };
427
+ var mark = {
428
+ ok: "+",
429
+ fail: "x",
430
+ pending: ".",
431
+ cursor: ">",
432
+ selected: "*",
433
+ unselected: " "
434
+ };
435
+
436
+ // src/ui/frame.tsx
437
+ import { jsx, jsxs } from "react/jsx-runtime";
438
+ var MAX_WIDTH = 96;
439
+ var MIN_WIDTH = 44;
440
+ var FRAME_CHROME = 4;
441
+ function useFrameWidth() {
442
+ const { columns } = useWindowSize();
443
+ return Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, columns - 2));
444
+ }
445
+ var j = DEFAULT_ASCII_CHARS.junction;
446
+ var BORDER = {
447
+ topLeft: j,
448
+ top: DEFAULT_ASCII_CHARS.top,
449
+ topRight: j,
450
+ right: DEFAULT_ASCII_CHARS.right,
451
+ bottomRight: j,
452
+ bottom: DEFAULT_ASCII_CHARS.bottom,
453
+ bottomLeft: j,
454
+ left: DEFAULT_ASCII_CHARS.left
455
+ };
456
+ function Frame({
457
+ title,
458
+ width,
459
+ children
460
+ }) {
461
+ return /* @__PURE__ */ jsxs(Box, {
462
+ flexDirection: "column",
463
+ width,
464
+ children: [
465
+ /* @__PURE__ */ jsx(Text, {
466
+ color: tone.primary,
467
+ children: topBorder(width, title)
468
+ }),
469
+ /* @__PURE__ */ jsx(Box, {
470
+ flexDirection: "column",
471
+ width,
472
+ paddingLeft: 1,
473
+ paddingRight: 1,
474
+ borderStyle: BORDER,
475
+ borderColor: tone.primary,
476
+ borderTop: false,
477
+ children
478
+ })
479
+ ]
480
+ });
481
+ }
482
+ function Divider({ width }) {
483
+ return /* @__PURE__ */ jsx(Box, {
484
+ marginLeft: -1,
485
+ marginRight: -1,
486
+ children: /* @__PURE__ */ jsx(Text, {
487
+ color: tone.primary,
488
+ children: fill(width - 2, DEFAULT_ASCII_CHARS.divider)
489
+ })
490
+ });
491
+ }
492
+ function Blank() {
493
+ return /* @__PURE__ */ jsx(Text, {
494
+ children: " "
495
+ });
496
+ }
497
+ function clip(text, width) {
498
+ if (width <= 0)
499
+ return "";
500
+ if (text.length <= width)
501
+ return text;
502
+ return width <= 1 ? text.slice(0, width) : `${text.slice(0, width - 1)}~`;
503
+ }
504
+
505
+ // src/ui/text-input.tsx
506
+ import { Text as Text2 } from "ink";
507
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
508
+ function SearchInput({
509
+ value,
510
+ placeholder = "",
511
+ focus = true
512
+ }) {
513
+ return /* @__PURE__ */ jsxs2(Text2, {
514
+ children: [
515
+ value.length > 0 ? /* @__PURE__ */ jsx2(Text2, {
516
+ children: value
517
+ }) : /* @__PURE__ */ jsx2(Text2, {
518
+ dimColor: true,
519
+ children: placeholder
520
+ }),
521
+ focus ? /* @__PURE__ */ jsx2(Text2, {
522
+ color: tone.primary,
523
+ children: "_"
524
+ }) : null
525
+ ]
526
+ });
527
+ }
528
+
529
+ // src/ui/command.tsx
530
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
531
+ function filterCommands(groups, query) {
532
+ if (query.length === 0)
533
+ return groups;
534
+ const q = query.toLowerCase();
535
+ return groups.map((group) => ({
536
+ group: group.group,
537
+ items: group.items.filter((item) => item.label.toLowerCase().includes(q) || (item.hint ?? "").toLowerCase().includes(q))
538
+ })).filter((group) => group.items.length > 0);
539
+ }
540
+ function flattenCommands(groups) {
541
+ return groups.flatMap((group) => group.items);
542
+ }
543
+ var POINTER = 2;
544
+ var GAP = 2;
545
+ function CommandRow({
546
+ item,
547
+ width,
548
+ active
549
+ }) {
550
+ const hint = item.hint ?? "";
551
+ const room = width - POINTER - (hint ? hint.length + GAP : 0);
552
+ const label = clip(item.label, Math.max(room, 0));
553
+ const gap = Math.max(width - POINTER - label.length - hint.length, hint ? 1 : 0);
554
+ return /* @__PURE__ */ jsxs3(Text3, {
555
+ children: [
556
+ /* @__PURE__ */ jsx3(Text3, {
557
+ color: tone.primary,
558
+ children: `${active ? mark.cursor : " "} `
559
+ }),
560
+ /* @__PURE__ */ jsx3(Text3, {
561
+ bold: active,
562
+ color: active ? tone.primary : undefined,
563
+ children: label
564
+ }),
565
+ /* @__PURE__ */ jsx3(Text3, {
566
+ dimColor: true,
567
+ children: `${" ".repeat(gap)}${hint}`
568
+ })
569
+ ]
570
+ });
571
+ }
572
+ function CommandPalette({
573
+ groups,
574
+ query,
575
+ cursor,
576
+ width,
577
+ placeholder = "type a command or search",
578
+ emptyMessage = "No matching commands."
579
+ }) {
580
+ const visible = filterCommands(groups, query);
581
+ const rows = flattenCommands(visible);
582
+ const current = rows[cursor];
583
+ const interior = width - FRAME_CHROME;
584
+ return /* @__PURE__ */ jsxs3(Box2, {
585
+ flexDirection: "column",
586
+ children: [
587
+ /* @__PURE__ */ jsxs3(Box2, {
588
+ children: [
589
+ /* @__PURE__ */ jsx3(Text3, {
590
+ color: tone.primary,
591
+ children: "> "
592
+ }),
593
+ /* @__PURE__ */ jsx3(SearchInput, {
594
+ value: query,
595
+ placeholder
596
+ })
597
+ ]
598
+ }),
599
+ /* @__PURE__ */ jsx3(Divider, {
600
+ width
601
+ }),
602
+ rows.length === 0 ? /* @__PURE__ */ jsx3(Text3, {
603
+ dimColor: true,
604
+ children: emptyMessage
605
+ }) : visible.map((group, i) => /* @__PURE__ */ jsxs3(Box2, {
606
+ flexDirection: "column",
607
+ children: [
608
+ i > 0 ? /* @__PURE__ */ jsx3(Divider, {
609
+ width
610
+ }) : null,
611
+ /* @__PURE__ */ jsx3(Text3, {
612
+ dimColor: true,
613
+ children: group.group.toUpperCase()
614
+ }),
615
+ group.items.map((item) => /* @__PURE__ */ jsx3(CommandRow, {
616
+ item,
617
+ width: interior,
618
+ active: item.value === current?.value
619
+ }, item.value))
620
+ ]
621
+ }, group.group))
622
+ ]
623
+ });
624
+ }
625
+ // ../../packages/skills/src/lifecycle.ts
626
+ var LIFECYCLES = [
627
+ { status: "active", word: "active", glyph: "#", tone: "state-active" },
628
+ {
629
+ status: "experimental",
630
+ word: "experimental",
631
+ glyph: "?",
632
+ tone: "state-experimental"
633
+ },
634
+ {
635
+ status: "deprecated",
636
+ word: "deprecated",
637
+ glyph: "x",
638
+ tone: "state-deprecated"
639
+ }
640
+ ];
641
+ var LIFECYCLE_ORDER = LIFECYCLES.map((lifecycle) => lifecycle.status);
642
+ var BY_STATUS = new Map(LIFECYCLES.map((lifecycle) => [lifecycle.status, lifecycle]));
643
+ function lifecycleOf(status) {
644
+ const lifecycle = BY_STATUS.get(status);
645
+ if (!lifecycle) {
646
+ throw new Error(`No lifecycle vocabulary for "${status}". Add it to LIFECYCLES.`);
647
+ }
648
+ return lifecycle;
649
+ }
650
+ // src/ui/rows.tsx
651
+ import { Box as Box3, Text as Text4 } from "ink";
652
+ import { jsx as jsx4, jsxs as jsxs4, Fragment } from "react/jsx-runtime";
653
+ function lifecycleTag(status) {
654
+ if (!status || status === "active")
655
+ return "";
656
+ return `[${lifecycleOf(status).word}]`;
657
+ }
658
+ function lifecycleColor(status) {
659
+ return stateTone[lifecycleOf(status ?? "active").tone];
660
+ }
661
+ var GUTTER = 4;
662
+ var GAP2 = 2;
663
+ function nameWidth(rows, max = 28) {
664
+ return Math.min(max, Math.max(8, ...rows.map((r) => r.name.length)));
665
+ }
666
+ function SkillRow({
667
+ row,
668
+ width,
669
+ names,
670
+ cursor,
671
+ selected,
672
+ selectable = false
673
+ }) {
674
+ const tag = lifecycleTag(row.status);
675
+ const meta = row.count !== undefined ? `${row.count} skills` : tag;
676
+ const gutter = selectable ? GUTTER : 0;
677
+ const rest = width - gutter - names - GAP2 - (meta ? meta.length + GAP2 : 0);
678
+ return /* @__PURE__ */ jsxs4(Box3, {
679
+ children: [
680
+ selectable ? /* @__PURE__ */ jsx4(Text4, {
681
+ color: cursor ? "cyan" : undefined,
682
+ children: `${cursor ? mark.cursor : " "} ${selected ? mark.selected : mark.unselected} `
683
+ }) : null,
684
+ /* @__PURE__ */ jsx4(Text4, {
685
+ bold: cursor,
686
+ color: lifecycleColor(row.status),
687
+ children: clip(row.name, names).padEnd(names)
688
+ }),
689
+ /* @__PURE__ */ jsx4(Text4, {
690
+ children: " ".repeat(GAP2)
691
+ }),
692
+ /* @__PURE__ */ jsx4(Text4, {
693
+ dimColor: true,
694
+ children: clip(row.description ?? "", Math.max(rest, 0))
695
+ }),
696
+ meta ? /* @__PURE__ */ jsxs4(Fragment, {
697
+ children: [
698
+ /* @__PURE__ */ jsx4(Text4, {
699
+ children: " ".repeat(GAP2)
700
+ }),
701
+ /* @__PURE__ */ jsx4(Text4, {
702
+ color: lifecycleColor(row.status),
703
+ dimColor: !tag,
704
+ children: meta
705
+ })
706
+ ]
707
+ }) : null
708
+ ]
709
+ });
710
+ }
711
+ function Hints({ hints }) {
712
+ return /* @__PURE__ */ jsx4(Box3, {
713
+ children: hints.map(([key, label], i) => /* @__PURE__ */ jsxs4(Text4, {
714
+ children: [
715
+ i > 0 ? /* @__PURE__ */ jsx4(Text4, {
716
+ dimColor: true,
717
+ children: " "
718
+ }) : null,
719
+ /* @__PURE__ */ jsx4(Text4, {
720
+ color: "cyan",
721
+ children: key
722
+ }),
723
+ /* @__PURE__ */ jsx4(Text4, {
724
+ dimColor: true,
725
+ children: ` ${label}`
726
+ })
727
+ ]
728
+ }, key))
729
+ });
730
+ }
731
+ // src/ui/spinner.tsx
732
+ import { Text as Text5 } from "ink";
733
+ import { useEffect, useState } from "react";
734
+ import { jsx as jsx5 } from "react/jsx-runtime";
735
+ var FRAMES = ["-", "\\", "|", "/"];
736
+ var INTERVAL_MS = 90;
737
+ function Spinner() {
738
+ const [frame, setFrame] = useState(0);
739
+ useEffect(() => {
740
+ const timer = setInterval(() => setFrame((f) => (f + 1) % FRAMES.length), INTERVAL_MS);
741
+ return () => clearInterval(timer);
742
+ }, []);
743
+ return /* @__PURE__ */ jsx5(Text5, {
744
+ color: tone.primary,
745
+ children: FRAMES[frame]
746
+ });
747
+ }
748
+ // src/auth/screens/login.tsx
749
+ import { Box as Box4, Text as Text6, useApp } from "ink";
750
+ import { useEffect as useEffect2, useState as useState2 } from "react";
751
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
752
+ function minutesLeft(expiresAt) {
753
+ return Math.max(0, Math.ceil((expiresAt - Date.now()) / 60000));
754
+ }
755
+ function LoginScreen({
756
+ onError,
757
+ onDone
758
+ }) {
759
+ const { exit } = useApp();
760
+ const width = useFrameWidth();
761
+ const [state, setState] = useState2({ phase: "starting" });
762
+ useEffect2(() => {
763
+ let live = true;
764
+ (async () => {
765
+ try {
766
+ for await (const event of login()) {
767
+ if (!live)
768
+ return;
769
+ if (event.kind === "prompt") {
770
+ setState({ phase: "waiting", prompt: event });
771
+ } else if (event.kind === "done") {
772
+ setState({ phase: "done", email: event.email });
773
+ }
774
+ }
775
+ } catch (error) {
776
+ if (live)
777
+ onError(error);
778
+ return;
779
+ }
780
+ if (!live)
781
+ return;
782
+ if (onDone)
783
+ onDone();
784
+ else
785
+ exit();
786
+ })();
787
+ return () => {
788
+ live = false;
789
+ };
790
+ }, [exit, onError, onDone]);
791
+ if (state.phase === "done") {
792
+ return /* @__PURE__ */ jsx6(Frame, {
793
+ title: "login",
794
+ width,
795
+ children: /* @__PURE__ */ jsxs5(Text6, {
796
+ children: [
797
+ /* @__PURE__ */ jsx6(Text6, {
798
+ color: "green",
799
+ children: mark.ok
800
+ }),
801
+ /* @__PURE__ */ jsx6(Text6, {
802
+ children: ` Logged in${state.email ? ` as ${state.email}` : ""}.`
803
+ })
804
+ ]
805
+ })
806
+ });
807
+ }
808
+ if (state.phase === "starting") {
809
+ return /* @__PURE__ */ jsx6(Frame, {
810
+ title: "login",
811
+ width,
812
+ children: /* @__PURE__ */ jsxs5(Text6, {
813
+ children: [
814
+ /* @__PURE__ */ jsx6(Spinner, {}),
815
+ /* @__PURE__ */ jsx6(Text6, {
816
+ dimColor: true,
817
+ children: " Requesting a device code..."
818
+ })
819
+ ]
820
+ })
821
+ });
822
+ }
823
+ const { prompt } = state;
824
+ return /* @__PURE__ */ jsx6(Frame, {
825
+ title: "login",
826
+ width,
827
+ children: /* @__PURE__ */ jsxs5(Box4, {
828
+ flexDirection: "column",
829
+ children: [
830
+ /* @__PURE__ */ jsx6(Text6, {
831
+ dimColor: true,
832
+ children: "Open this URL in your browser:"
833
+ }),
834
+ /* @__PURE__ */ jsx6(Text6, {
835
+ color: tone.primary,
836
+ children: prompt.verificationUri
837
+ }),
838
+ /* @__PURE__ */ jsx6(Box4, {
839
+ height: 1
840
+ }),
841
+ /* @__PURE__ */ jsx6(Text6, {
842
+ dimColor: true,
843
+ children: "and enter the code:"
844
+ }),
845
+ /* @__PURE__ */ jsx6(Text6, {
846
+ bold: true,
847
+ color: tone.notice,
848
+ children: prompt.userCode
849
+ }),
850
+ /* @__PURE__ */ jsx6(Box4, {
851
+ height: 1
852
+ }),
853
+ /* @__PURE__ */ jsxs5(Text6, {
854
+ children: [
855
+ /* @__PURE__ */ jsx6(Spinner, {}),
856
+ /* @__PURE__ */ jsx6(Text6, {
857
+ dimColor: true,
858
+ children: ` Waiting for approval — expires in ${minutesLeft(prompt.expiresAt)} min`
859
+ })
860
+ ]
861
+ })
862
+ ]
863
+ })
864
+ });
865
+ }
866
+
867
+ // src/auth/screens/gate.tsx
868
+ import { jsx as jsx7, jsxs as jsxs6, Fragment as Fragment2 } from "react/jsx-runtime";
869
+ function AuthGate({
870
+ title,
871
+ build,
872
+ onError,
873
+ check = hasSession,
874
+ connect
875
+ }) {
876
+ const { exit } = useApp2();
877
+ const width = useFrameWidth();
878
+ const [phase, setPhase] = useState3({ kind: "checking" });
879
+ const open = useCallback(() => {
880
+ connect().then((client) => {
881
+ process.exitCode = 0;
882
+ setPhase({ kind: "ready", client });
883
+ }).catch((error) => onError(error));
884
+ }, [connect, onError]);
885
+ useEffect3(() => {
886
+ let live = true;
887
+ check().then((ok) => {
888
+ if (!live)
889
+ return;
890
+ if (ok)
891
+ return open();
892
+ process.exitCode = 1;
893
+ setPhase({ kind: "blocked" });
894
+ }).catch((error) => {
895
+ if (live)
896
+ onError(error);
897
+ });
898
+ return () => {
899
+ live = false;
900
+ };
901
+ }, [check, open, onError]);
902
+ useInput((input, key) => {
903
+ if (input === "l")
904
+ setPhase({ kind: "logging-in" });
905
+ if (input === "q" || key.escape)
906
+ exit();
907
+ }, { isActive: phase.kind === "blocked" });
908
+ const child = useMemo(() => phase.kind === "ready" ? build(phase.client) : null, [phase, build]);
909
+ if (phase.kind === "ready")
910
+ return /* @__PURE__ */ jsx7(Fragment2, {
911
+ children: child
912
+ });
913
+ if (phase.kind === "logging-in")
914
+ return /* @__PURE__ */ jsx7(LoginScreen, {
915
+ onError,
916
+ onDone: open
917
+ });
918
+ if (phase.kind === "checking")
919
+ return /* @__PURE__ */ jsx7(Frame, {
920
+ title,
921
+ width,
922
+ children: /* @__PURE__ */ jsxs6(Text7, {
923
+ children: [
924
+ /* @__PURE__ */ jsx7(Spinner, {}),
925
+ /* @__PURE__ */ jsx7(Text7, {
926
+ dimColor: true,
927
+ children: " Checking session..."
928
+ })
929
+ ]
930
+ })
931
+ });
932
+ return /* @__PURE__ */ jsx7(Frame, {
933
+ title,
934
+ width,
935
+ children: /* @__PURE__ */ jsxs6(Box5, {
936
+ flexDirection: "column",
937
+ children: [
938
+ /* @__PURE__ */ jsxs6(Text7, {
939
+ children: [
940
+ /* @__PURE__ */ jsx7(Text7, {
941
+ color: tone.notice,
942
+ children: mark.fail
943
+ }),
944
+ /* @__PURE__ */ jsx7(Text7, {
945
+ children: ` Not signed in — ${title} needs a session.`
946
+ })
947
+ ]
948
+ }),
949
+ /* @__PURE__ */ jsx7(Blank, {}),
950
+ /* @__PURE__ */ jsxs6(Text7, {
951
+ dimColor: true,
952
+ children: [
953
+ "press ",
954
+ /* @__PURE__ */ jsx7(Text7, {
955
+ color: tone.primary,
956
+ children: "l"
957
+ }),
958
+ " to log in, ",
959
+ /* @__PURE__ */ jsx7(Text7, {
960
+ color: tone.primary,
961
+ children: "q"
962
+ }),
963
+ " to quit"
964
+ ]
965
+ }),
966
+ /* @__PURE__ */ jsxs6(Text7, {
967
+ dimColor: true,
968
+ children: [
969
+ "or run ",
970
+ /* @__PURE__ */ jsx7(Text7, {
971
+ color: tone.primary,
972
+ children: "yourskills login"
973
+ }),
974
+ " elsewhere"
975
+ ]
976
+ })
977
+ ]
978
+ })
979
+ });
980
+ }
981
+ // src/catalog/screens/browser.tsx
982
+ import { Box as Box7, Text as Text9, useApp as useApp4, useInput as useInput2, useWindowSize as useWindowSize2 } from "ink";
983
+ import { useCallback as useCallback2, useEffect as useEffect5, useMemo as useMemo2, useState as useState5 } from "react";
984
+
985
+ // src/install/install.ts
986
+ import { spawn } from "node:child_process";
987
+ import { createRequire } from "node:module";
988
+ import { dirname as dirname3, join as join4 } from "node:path";
989
+
990
+ // src/manifest/manifest.ts
991
+ import { mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "node:fs";
992
+ import { dirname as dirname2, join as join3, resolve as resolve2 } from "node:path";
993
+ function emptyManifest(scope) {
994
+ return { version: 1, scope, agents: [], bundles: {}, skills: {} };
995
+ }
996
+ function manifestPathFor(scope, cwd) {
997
+ return scope === "global" ? join3(configDir(), "installed.json") : join3(resolve2(cwd ?? process.cwd()), PROJECT_MANIFEST);
998
+ }
999
+ function readManifest(path) {
1000
+ let parsed;
1001
+ try {
1002
+ parsed = JSON.parse(readFileSync(path, "utf8"));
1003
+ } catch {
1004
+ return emptyManifest(path.endsWith(PROJECT_MANIFEST) ? "project" : "global");
1005
+ }
1006
+ if (!parsed || typeof parsed !== "object") {
1007
+ return emptyManifest(path.endsWith(PROJECT_MANIFEST) ? "project" : "global");
1008
+ }
1009
+ const raw = parsed;
1010
+ const scope = raw.scope === "project" ? "project" : "global";
1011
+ return {
1012
+ version: 1,
1013
+ scope,
1014
+ agents: Array.isArray(raw.agents) ? raw.agents : [],
1015
+ bundles: raw.bundles && typeof raw.bundles === "object" ? raw.bundles : {},
1016
+ skills: raw.skills && typeof raw.skills === "object" ? raw.skills : {}
1017
+ };
1018
+ }
1019
+ function writeManifest(path, m) {
1020
+ mkdirSync2(dirname2(path), { recursive: true });
1021
+ writeFileSync(path, `${JSON.stringify(m, null, 2)}
1022
+ `);
1023
+ }
1024
+ function recordSkill(m, entry) {
1025
+ return { ...m, skills: { ...m.skills, [entry.name]: entry } };
1026
+ }
1027
+ function recordBundle(m, name, ref) {
1028
+ return { ...m, bundles: { ...m.bundles, [name]: ref } };
1029
+ }
1030
+ function forgetSkill(m, name) {
1031
+ const { [name]: _removed, ...rest } = m.skills;
1032
+ return { ...m, skills: rest };
1033
+ }
1034
+ // src/manifest/nearest.ts
1035
+ async function nearestManifest(flag, cwd) {
1036
+ const { scope, source, manifestPath } = await resolveScope({ flag, cwd });
1037
+ const path = scope === "project" && manifestPath ? manifestPath : manifestPathFor(scope, cwd);
1038
+ return { path, scope, source, manifest: readManifest(path) };
1039
+ }
1040
+ // src/install/install.ts
1041
+ function skillsAddArgv(location, opts = {}) {
1042
+ const path = location.path.replace(/^\/+|\/+$/g, "");
1043
+ const spec = `${location.source}${path ? `/${path}` : ""}#${location.ref}`;
1044
+ const argv = ["add", spec, "--yes"];
1045
+ if (opts.scope !== "project")
1046
+ argv.push("--global");
1047
+ for (const agent of opts.agents ?? [])
1048
+ argv.push("--agent", agent);
1049
+ return argv;
1050
+ }
1051
+ function childEnv(base = process.env) {
1052
+ const env = {};
1053
+ for (const [k, v] of Object.entries(base))
1054
+ if (v !== undefined)
1055
+ env[k] = v;
1056
+ env.DISABLE_TELEMETRY = "1";
1057
+ env.DO_NOT_TRACK = "1";
1058
+ return env;
1059
+ }
1060
+ var nodeRequire = createRequire(import.meta.url);
1061
+ function skillsBin() {
1062
+ let pkgPath;
1063
+ try {
1064
+ pkgPath = nodeRequire.resolve("skills/package.json");
1065
+ } catch {
1066
+ throw new Error("The bundled `skills` CLI could not be resolved. Re-install yourskills; its `skills` dependency is missing.");
1067
+ }
1068
+ const pkg = nodeRequire(pkgPath);
1069
+ const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.skills;
1070
+ if (!bin) {
1071
+ throw new Error("The bundled `skills` package declares no `skills` binary. Re-install yourskills.");
1072
+ }
1073
+ return join4(dirname3(pkgPath), bin);
1074
+ }
1075
+ function runSkills(argv) {
1076
+ const bin = skillsBin();
1077
+ return new Promise((settle) => {
1078
+ const child = spawn(process.execPath, [bin, ...argv], {
1079
+ env: childEnv(),
1080
+ stdio: ["ignore", "pipe", "pipe"]
1081
+ });
1082
+ let stdout = "";
1083
+ let stderr = "";
1084
+ child.stdout.setEncoding("utf8");
1085
+ child.stderr.setEncoding("utf8");
1086
+ child.stdout.on("data", (chunk) => {
1087
+ stdout += chunk;
1088
+ });
1089
+ child.stderr.on("data", (chunk) => {
1090
+ stderr += chunk;
1091
+ });
1092
+ const fail = (message) => settle({ ok: false, message, detail: `${stdout}${stderr}`.trim() });
1093
+ child.on("error", (error) => fail(`skills ${argv.join(" ")} could not be started: ${error.message}`));
1094
+ child.on("close", (code, signal) => {
1095
+ if (code === 0) {
1096
+ settle({ ok: true });
1097
+ return;
1098
+ }
1099
+ fail(`skills ${argv.join(" ")} exited ${code ?? `on ${signal}`}`);
1100
+ });
1101
+ });
1102
+ }
1103
+ function pendingMessage(pending) {
1104
+ const where = `${pending.repo}/${pending.path}`;
1105
+ return pending.prUrl ? `${pending.skillName} not in your registry yet — opened a pull request against ${where}
1106
+ ` + ` ${pending.prUrl}
1107
+ ` + " Merge it, then run this command again." : `${pending.skillName} not in your registry yet — written to ${where}. ` + "Re-run once your registry has indexed it.";
1108
+ }
1109
+ function manifestEntryFor(location, opts = {}, bundle) {
1110
+ const path = location.path.replace(/^\/+|\/+$/g, "");
1111
+ return {
1112
+ name: location.skillName,
1113
+ externalId: location.externalId,
1114
+ ref: location.ref,
1115
+ source: location.source,
1116
+ path,
1117
+ ...bundle ? { bundle } : {},
1118
+ agents: opts.agents ?? [],
1119
+ installedAt: new Date().toISOString()
1120
+ };
1121
+ }
1122
+ function record(opts, location, bundle) {
1123
+ const target = opts.manifest;
1124
+ if (!target)
1125
+ return;
1126
+ let manifest = readManifest(target.path);
1127
+ manifest = {
1128
+ ...manifest,
1129
+ scope: target.scope,
1130
+ agents: opts.agents?.length ? opts.agents : manifest.agents
1131
+ };
1132
+ if (bundle && !manifest.bundles[bundle]) {
1133
+ manifest = recordBundle(manifest, bundle, location.ref);
1134
+ }
1135
+ manifest = recordSkill(manifest, manifestEntryFor(location, opts, bundle));
1136
+ writeManifest(target.path, manifest);
1137
+ }
1138
+ async function* installTargets(targets, opts, bundle) {
1139
+ for (const location of targets) {
1140
+ if (location.deprecated)
1141
+ yield { kind: "deprecated", location };
1142
+ yield { kind: "installing", location };
1143
+ const result = await runSkills(skillsAddArgv(location, opts));
1144
+ if (result.ok)
1145
+ record(opts, location, bundle);
1146
+ yield result.ok ? { kind: "installed", location } : {
1147
+ kind: "failed",
1148
+ name: location.skillName,
1149
+ message: result.message,
1150
+ detail: result.detail
1151
+ };
1152
+ }
1153
+ }
1154
+ async function* install(client, names, opts = {}) {
1155
+ for (const name of names)
1156
+ yield { kind: "resolving", name };
1157
+ let resolution;
1158
+ try {
1159
+ resolution = await client.resolve.skill.mutate({ names });
1160
+ } catch (error) {
1161
+ const message = error instanceof Error ? error.message : String(error);
1162
+ for (const name of names)
1163
+ yield { kind: "failed", name, message };
1164
+ return;
1165
+ }
1166
+ yield* installResolution(resolution.entries, opts);
1167
+ }
1168
+ async function* installBundle(client, name, opts = {}) {
1169
+ yield { kind: "resolving", name };
1170
+ let resolution;
1171
+ try {
1172
+ resolution = await client.resolve.bundle.mutate({ name });
1173
+ } catch (error) {
1174
+ yield {
1175
+ kind: "failed",
1176
+ name,
1177
+ message: error instanceof Error ? error.message : String(error)
1178
+ };
1179
+ return;
1180
+ }
1181
+ yield* installResolution(resolution.entries, opts, name);
1182
+ }
1183
+ async function* installResolution(entries, opts, bundle) {
1184
+ for (const entry of entries) {
1185
+ if (entry.state === "failed") {
1186
+ yield { kind: "failed", name: entry.name, message: entry.message };
1187
+ continue;
1188
+ }
1189
+ if (entry.state === "vendoring") {
1190
+ yield { kind: "pending", vendor: entry.vendor };
1191
+ continue;
1192
+ }
1193
+ yield* installTargets(entry.locations, opts, bundle);
1194
+ }
1195
+ }
1196
+ // src/install/reinstall.ts
1197
+ function targetOf(entry) {
1198
+ return {
1199
+ skillName: entry.name,
1200
+ externalId: entry.externalId,
1201
+ source: entry.source,
1202
+ path: entry.path,
1203
+ ref: entry.ref,
1204
+ deprecated: false
1205
+ };
1206
+ }
1207
+ async function* reinstall(manifest, opts = {}) {
1208
+ const entries = Object.values(manifest.skills);
1209
+ for (const entry of entries) {
1210
+ yield* installTargets([targetOf(entry)], opts, entry.bundle);
1211
+ }
1212
+ }
1213
+ // src/install/remove.ts
1214
+ function skillsRemoveArgv(name, opts = {}) {
1215
+ const argv = ["remove", name, "--yes"];
1216
+ if (opts.scope !== "project")
1217
+ argv.push("--global");
1218
+ for (const agent of opts.agents ?? [])
1219
+ argv.push("--agent", agent);
1220
+ return argv;
1221
+ }
1222
+ async function removeSkills(names, manifest, manifestPath, opts = {}) {
1223
+ const lines = [];
1224
+ const removed = [];
1225
+ let next = manifest;
1226
+ let failed = false;
1227
+ for (const name of names) {
1228
+ const entry = next.skills[name];
1229
+ if (!entry) {
1230
+ failed = true;
1231
+ lines.push({
1232
+ mark: "fail",
1233
+ text: name,
1234
+ note: "not in the manifest — nothing to remove"
1235
+ });
1236
+ continue;
1237
+ }
1238
+ const result = await runSkills(skillsRemoveArgv(name, opts));
1239
+ if (!result.ok) {
1240
+ failed = true;
1241
+ lines.push({ mark: "fail", text: name, note: result.message });
1242
+ continue;
1243
+ }
1244
+ next = forgetSkill(next, name);
1245
+ removed.push(name);
1246
+ lines.push({ mark: "ok", text: name, note: "removed" });
1247
+ }
1248
+ if (removed.length > 0)
1249
+ writeManifest(manifestPath, next);
1250
+ return {
1251
+ title: "remove",
1252
+ lines,
1253
+ json: { manifest: manifestPath, removed, failed },
1254
+ failed
1255
+ };
1256
+ }
1257
+ // src/install/screens/installer.tsx
1258
+ import { Box as Box6, Text as Text8, useApp as useApp3 } from "ink";
1259
+ import { useEffect as useEffect4, useState as useState4 } from "react";
1260
+ import { jsx as jsx8, jsxs as jsxs7, Fragment as Fragment3 } from "react/jsx-runtime";
1261
+ function where(location) {
1262
+ return `${location.source}/${location.path}@${location.ref.slice(0, 8)}`;
1263
+ }
1264
+ function reduce(lines, event) {
1265
+ const replace = (name, next) => {
1266
+ const at = lines.findIndex((l) => l.name === name);
1267
+ if (at === -1)
1268
+ return [...lines, next];
1269
+ return lines.map((l, i) => i === at ? next : l);
1270
+ };
1271
+ switch (event.kind) {
1272
+ case "resolving":
1273
+ return replace(event.name, { state: "resolving", name: event.name });
1274
+ case "deprecated":
1275
+ return lines;
1276
+ case "installing":
1277
+ return replace(event.location.skillName, {
1278
+ state: "installing",
1279
+ name: event.location.skillName,
1280
+ where: where(event.location)
1281
+ });
1282
+ case "installed":
1283
+ return replace(event.location.skillName, {
1284
+ state: "installed",
1285
+ name: event.location.skillName,
1286
+ where: where(event.location),
1287
+ deprecated: event.location.deprecated
1288
+ });
1289
+ case "pending":
1290
+ return replace(event.vendor.skillName, {
1291
+ state: "pending",
1292
+ name: event.vendor.skillName,
1293
+ vendor: event.vendor
1294
+ });
1295
+ case "failed":
1296
+ return replace(event.name, {
1297
+ state: "failed",
1298
+ name: event.name,
1299
+ message: event.message,
1300
+ detail: event.detail
1301
+ });
1302
+ }
1303
+ }
1304
+ function InstallLine({ line, width }) {
1305
+ const names = 24;
1306
+ const name = clip(line.name, names).padEnd(names);
1307
+ switch (line.state) {
1308
+ case "resolving":
1309
+ return /* @__PURE__ */ jsxs7(Text8, {
1310
+ children: [
1311
+ /* @__PURE__ */ jsx8(Spinner, {}),
1312
+ /* @__PURE__ */ jsx8(Text8, {
1313
+ children: ` ${name}`
1314
+ }),
1315
+ /* @__PURE__ */ jsx8(Text8, {
1316
+ dimColor: true,
1317
+ children: "resolving..."
1318
+ })
1319
+ ]
1320
+ });
1321
+ case "installing":
1322
+ return /* @__PURE__ */ jsxs7(Text8, {
1323
+ children: [
1324
+ /* @__PURE__ */ jsx8(Spinner, {}),
1325
+ /* @__PURE__ */ jsx8(Text8, {
1326
+ children: ` ${name}`
1327
+ }),
1328
+ /* @__PURE__ */ jsx8(Text8, {
1329
+ dimColor: true,
1330
+ children: clip(line.where, width - names - 3)
1331
+ })
1332
+ ]
1333
+ });
1334
+ case "installed":
1335
+ return /* @__PURE__ */ jsxs7(Text8, {
1336
+ children: [
1337
+ /* @__PURE__ */ jsx8(Text8, {
1338
+ color: "green",
1339
+ children: mark.ok
1340
+ }),
1341
+ /* @__PURE__ */ jsx8(Text8, {
1342
+ children: ` ${name}`
1343
+ }),
1344
+ /* @__PURE__ */ jsx8(Text8, {
1345
+ dimColor: true,
1346
+ children: clip(line.where, width - names - 3)
1347
+ }),
1348
+ line.deprecated ? /* @__PURE__ */ jsx8(Text8, {
1349
+ color: lifecycleColor("deprecated"),
1350
+ children: ` ${lifecycleTag("deprecated")}`
1351
+ }) : null
1352
+ ]
1353
+ });
1354
+ case "pending":
1355
+ return /* @__PURE__ */ jsxs7(Box6, {
1356
+ flexDirection: "column",
1357
+ children: [
1358
+ /* @__PURE__ */ jsxs7(Text8, {
1359
+ children: [
1360
+ /* @__PURE__ */ jsx8(Text8, {
1361
+ color: tone.notice,
1362
+ children: mark.pending
1363
+ }),
1364
+ /* @__PURE__ */ jsx8(Text8, {
1365
+ children: ` ${name}`
1366
+ }),
1367
+ /* @__PURE__ */ jsx8(Text8, {
1368
+ color: tone.notice,
1369
+ children: "not in your registry yet"
1370
+ })
1371
+ ]
1372
+ }),
1373
+ line.vendor.prUrl ? /* @__PURE__ */ jsxs7(Text8, {
1374
+ children: [
1375
+ /* @__PURE__ */ jsx8(Text8, {
1376
+ children: " "
1377
+ }),
1378
+ /* @__PURE__ */ jsx8(Text8, {
1379
+ color: tone.primary,
1380
+ children: line.vendor.prUrl
1381
+ })
1382
+ ]
1383
+ }) : null
1384
+ ]
1385
+ });
1386
+ case "failed":
1387
+ return /* @__PURE__ */ jsxs7(Box6, {
1388
+ flexDirection: "column",
1389
+ children: [
1390
+ /* @__PURE__ */ jsxs7(Text8, {
1391
+ children: [
1392
+ /* @__PURE__ */ jsx8(Text8, {
1393
+ color: tone.danger,
1394
+ children: mark.fail
1395
+ }),
1396
+ /* @__PURE__ */ jsx8(Text8, {
1397
+ children: ` ${name}`
1398
+ }),
1399
+ /* @__PURE__ */ jsx8(Text8, {
1400
+ color: tone.danger,
1401
+ children: clip(line.message, width - names - 3)
1402
+ })
1403
+ ]
1404
+ }),
1405
+ line.detail ? line.detail.split(`
1406
+ `).slice(-3).map((d) => /* @__PURE__ */ jsx8(Text8, {
1407
+ dimColor: true,
1408
+ children: clip(` ${d}`, width)
1409
+ }, d)) : null
1410
+ ]
1411
+ });
1412
+ }
1413
+ }
1414
+ function Installer({
1415
+ title,
1416
+ stream,
1417
+ onDone,
1418
+ exitWhenDone = true
1419
+ }) {
1420
+ const { exit } = useApp3();
1421
+ const width = useFrameWidth();
1422
+ const [lines, setLines] = useState4([]);
1423
+ const [error, setError] = useState4(null);
1424
+ const [done, setDone] = useState4(false);
1425
+ useEffect4(() => {
1426
+ let live = true;
1427
+ (async () => {
1428
+ let current = [];
1429
+ try {
1430
+ for await (const event of stream()) {
1431
+ if (!live)
1432
+ return;
1433
+ current = reduce(current, event);
1434
+ setLines(current);
1435
+ }
1436
+ } catch (e) {
1437
+ if (live)
1438
+ setError(e instanceof Error ? e.message : String(e));
1439
+ }
1440
+ if (!live)
1441
+ return;
1442
+ setDone(true);
1443
+ onDone?.({
1444
+ pending: current.filter((l) => l.state === "pending").map((l) => l.vendor),
1445
+ failed: current.filter((l) => l.state === "failed").length
1446
+ });
1447
+ if (exitWhenDone)
1448
+ exit();
1449
+ })();
1450
+ return () => {
1451
+ live = false;
1452
+ };
1453
+ }, [stream, onDone, exit, exitWhenDone]);
1454
+ const pending = lines.filter((l) => l.state === "pending");
1455
+ return /* @__PURE__ */ jsx8(Frame, {
1456
+ title,
1457
+ width,
1458
+ children: /* @__PURE__ */ jsxs7(Box6, {
1459
+ flexDirection: "column",
1460
+ children: [
1461
+ lines.length === 0 && !error ? /* @__PURE__ */ jsxs7(Text8, {
1462
+ children: [
1463
+ /* @__PURE__ */ jsx8(Spinner, {}),
1464
+ /* @__PURE__ */ jsx8(Text8, {
1465
+ dimColor: true,
1466
+ children: " Resolving..."
1467
+ })
1468
+ ]
1469
+ }) : null,
1470
+ lines.map((line) => /* @__PURE__ */ jsx8(InstallLine, {
1471
+ line,
1472
+ width: width - 2
1473
+ }, line.name)),
1474
+ error ? /* @__PURE__ */ jsx8(Text8, {
1475
+ color: tone.danger,
1476
+ children: `${mark.fail} ${error}`
1477
+ }) : null,
1478
+ done && pending.length > 0 ? /* @__PURE__ */ jsxs7(Fragment3, {
1479
+ children: [
1480
+ /* @__PURE__ */ jsx8(Divider, {
1481
+ width
1482
+ }),
1483
+ /* @__PURE__ */ jsx8(Text8, {
1484
+ color: tone.notice,
1485
+ children: pending.length === 1 ? "1 skill is waiting on a pull request." : `${pending.length} skills are waiting on a pull request.`
1486
+ }),
1487
+ /* @__PURE__ */ jsx8(Text8, {
1488
+ dimColor: true,
1489
+ children: "Merge it, then run this command again."
1490
+ })
1491
+ ]
1492
+ }) : null
1493
+ ]
1494
+ })
1495
+ });
1496
+ }
1497
+ async function installPlain(stream) {
1498
+ const pending = [];
1499
+ let failed = 0;
1500
+ for await (const event of stream) {
1501
+ switch (event.kind) {
1502
+ case "resolving":
1503
+ break;
1504
+ case "deprecated":
1505
+ console.warn(`warning: "${event.location.skillName}" is deprecated — installing anyway.`);
1506
+ break;
1507
+ case "installing":
1508
+ console.log(`${event.location.skillName} ${where(event.location)}`);
1509
+ break;
1510
+ case "installed":
1511
+ break;
1512
+ case "pending":
1513
+ console.log(pendingMessage(event.vendor));
1514
+ pending.push(event.vendor);
1515
+ break;
1516
+ case "failed":
1517
+ failed += 1;
1518
+ console.error(`error: ${event.name}: ${event.message}`);
1519
+ if (event.detail)
1520
+ console.error(event.detail);
1521
+ break;
1522
+ }
1523
+ }
1524
+ return { pending, failed };
1525
+ }
1526
+ // src/install/update.ts
1527
+ function update(client, bundle, opts = {}) {
1528
+ return installBundle(client, bundle, opts);
1529
+ }
1530
+ function looseSkills(manifest) {
1531
+ return Object.values(manifest.skills).filter((entry) => !entry.bundle).map((entry) => entry.name);
1532
+ }
1533
+ async function* updateTracked(client, manifest, opts = {}, all = false) {
1534
+ for (const bundle of Object.keys(manifest.bundles)) {
1535
+ yield* installBundle(client, bundle, opts);
1536
+ }
1537
+ const loose = all ? looseSkills(manifest) : [];
1538
+ if (loose.length > 0)
1539
+ yield* install(client, loose, opts);
1540
+ }
1541
+ // src/catalog/screens/browser.tsx
1542
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
1543
+ var TABS = ["bundles", "skills"];
1544
+ function pageSize(rows) {
1545
+ return Math.max(3, Math.min(20, rows - 11));
1546
+ }
1547
+ function matches(row, query) {
1548
+ if (query.length === 0)
1549
+ return true;
1550
+ const q = query.toLowerCase();
1551
+ return row.name.toLowerCase().includes(q) || (row.description ?? "").toLowerCase().includes(q);
1552
+ }
1553
+ function TabBar({ active }) {
1554
+ return /* @__PURE__ */ jsx9(Box7, {
1555
+ children: TABS.map((tab, i) => /* @__PURE__ */ jsxs8(Text9, {
1556
+ children: [
1557
+ i > 0 ? /* @__PURE__ */ jsx9(Text9, {
1558
+ dimColor: true,
1559
+ children: " "
1560
+ }) : null,
1561
+ tab === active ? /* @__PURE__ */ jsx9(Text9, {
1562
+ bold: true,
1563
+ color: tone.primary,
1564
+ children: `[${tab}]`
1565
+ }) : /* @__PURE__ */ jsx9(Text9, {
1566
+ dimColor: true,
1567
+ children: ` ${tab} `
1568
+ })
1569
+ ]
1570
+ }, tab))
1571
+ });
1572
+ }
1573
+ function BrowserScreen({
1574
+ client,
1575
+ opts,
1576
+ onError,
1577
+ onSummary,
1578
+ onExit
1579
+ }) {
1580
+ const { exit } = useApp4();
1581
+ const width = useFrameWidth();
1582
+ const { rows: terminalRows } = useWindowSize2();
1583
+ const [catalog, setCatalog] = useState5(null);
1584
+ const [tab, setTab] = useState5("bundles");
1585
+ const [query, setQuery] = useState5("");
1586
+ const [cursor, setCursor] = useState5(0);
1587
+ const [picked, setPicked] = useState5({
1588
+ bundles: new Set,
1589
+ skills: new Set
1590
+ });
1591
+ const [running, setRunning] = useState5(null);
1592
+ useEffect5(() => {
1593
+ let live = true;
1594
+ Promise.all([client.skills.list.query({}), client.bundles.list.query()]).then(([skills, bundles]) => {
1595
+ if (!live)
1596
+ return;
1597
+ setCatalog({
1598
+ skills: skills.map((s) => ({
1599
+ name: s.name,
1600
+ description: s.description,
1601
+ status: s.status
1602
+ })),
1603
+ bundles: bundles.map((b) => ({
1604
+ name: b.name,
1605
+ description: b.description
1606
+ }))
1607
+ });
1608
+ }).catch((error) => {
1609
+ if (live)
1610
+ onError(error);
1611
+ });
1612
+ return () => {
1613
+ live = false;
1614
+ };
1615
+ }, [client, onError]);
1616
+ const visible = useMemo2(() => {
1617
+ if (!catalog)
1618
+ return [];
1619
+ return catalog[tab].filter((row) => matches(row, query));
1620
+ }, [catalog, tab, query]);
1621
+ useEffect5(() => {
1622
+ setCursor((c) => Math.max(0, Math.min(c, visible.length - 1)));
1623
+ }, [visible.length]);
1624
+ const selected = picked[tab];
1625
+ const start = useCallback2(() => {
1626
+ const names2 = selected.size > 0 ? [...selected] : visible[cursor] ? [visible[cursor].name] : [];
1627
+ if (names2.length === 0)
1628
+ return;
1629
+ setRunning({
1630
+ title: tab === "bundles" ? `bundle: ${names2[0]}` : "add",
1631
+ names: names2
1632
+ });
1633
+ }, [selected, visible, cursor, tab]);
1634
+ useInput2((input, key) => {
1635
+ if (key.upArrow) {
1636
+ setCursor((c) => Math.max(0, c - 1));
1637
+ } else if (key.downArrow) {
1638
+ setCursor((c) => Math.min(visible.length - 1, c + 1));
1639
+ } else if (key.pageUp) {
1640
+ setCursor((c) => Math.max(0, c - pageSize(terminalRows)));
1641
+ } else if (key.pageDown) {
1642
+ setCursor((c) => Math.min(visible.length - 1, c + pageSize(terminalRows)));
1643
+ } else if (key.tab) {
1644
+ setTab((t) => t === "bundles" ? "skills" : "bundles");
1645
+ setCursor(0);
1646
+ } else if (key.return) {
1647
+ start();
1648
+ } else if (key.escape) {
1649
+ if (query.length > 0)
1650
+ setQuery("");
1651
+ else
1652
+ (onExit ?? exit)();
1653
+ } else if (key.backspace || key.delete) {
1654
+ setQuery((q) => q.slice(0, -1));
1655
+ setCursor(0);
1656
+ } else if (input === " ") {
1657
+ const row = visible[cursor];
1658
+ if (!row)
1659
+ return;
1660
+ setPicked((current) => {
1661
+ const next = new Set(current[tab]);
1662
+ if (tab === "bundles")
1663
+ next.clear();
1664
+ if (current[tab].has(row.name))
1665
+ next.delete(row.name);
1666
+ else
1667
+ next.add(row.name);
1668
+ return { ...current, [tab]: next };
1669
+ });
1670
+ } else if (input && !key.ctrl && !key.meta) {
1671
+ setQuery((q) => q + input);
1672
+ setCursor(0);
1673
+ }
1674
+ }, { isActive: running === null });
1675
+ if (running) {
1676
+ const names2 = running.names;
1677
+ const stream = tab === "bundles" ? () => installBundle(client, names2[0], opts) : () => install(client, names2, opts);
1678
+ return /* @__PURE__ */ jsx9(Installer, {
1679
+ title: running.title,
1680
+ stream,
1681
+ exitWhenDone: true,
1682
+ onDone: onSummary
1683
+ });
1684
+ }
1685
+ if (!catalog) {
1686
+ return /* @__PURE__ */ jsx9(Frame, {
1687
+ title: "yourskills",
1688
+ width,
1689
+ children: /* @__PURE__ */ jsxs8(Text9, {
1690
+ children: [
1691
+ /* @__PURE__ */ jsx9(Spinner, {}),
1692
+ /* @__PURE__ */ jsx9(Text9, {
1693
+ dimColor: true,
1694
+ children: " Loading your registry..."
1695
+ })
1696
+ ]
1697
+ })
1698
+ });
1699
+ }
1700
+ const size = pageSize(terminalRows);
1701
+ const first = Math.max(0, Math.min(cursor - Math.floor(size / 2), visible.length - size));
1702
+ const page = visible.slice(Math.max(0, first), Math.max(0, first) + size);
1703
+ const names = nameWidth(visible.length > 0 ? visible : catalog[tab]);
1704
+ return /* @__PURE__ */ jsx9(Frame, {
1705
+ title: "yourskills",
1706
+ width,
1707
+ children: /* @__PURE__ */ jsxs8(Box7, {
1708
+ flexDirection: "column",
1709
+ children: [
1710
+ /* @__PURE__ */ jsx9(TabBar, {
1711
+ active: tab
1712
+ }),
1713
+ /* @__PURE__ */ jsx9(Divider, {
1714
+ width
1715
+ }),
1716
+ /* @__PURE__ */ jsxs8(Box7, {
1717
+ children: [
1718
+ /* @__PURE__ */ jsx9(Text9, {
1719
+ color: tone.primary,
1720
+ children: "search> "
1721
+ }),
1722
+ /* @__PURE__ */ jsx9(SearchInput, {
1723
+ value: query,
1724
+ placeholder: "type to filter"
1725
+ })
1726
+ ]
1727
+ }),
1728
+ /* @__PURE__ */ jsx9(Divider, {
1729
+ width
1730
+ }),
1731
+ page.length === 0 ? /* @__PURE__ */ jsx9(Text9, {
1732
+ dimColor: true,
1733
+ children: `No ${tab} match "${query}".`
1734
+ }) : page.map((row, i) => /* @__PURE__ */ jsx9(SkillRow, {
1735
+ row,
1736
+ width: width - 2,
1737
+ names,
1738
+ selectable: true,
1739
+ cursor: Math.max(0, first) + i === cursor,
1740
+ selected: selected.has(row.name)
1741
+ }, row.name)),
1742
+ /* @__PURE__ */ jsx9(Divider, {
1743
+ width
1744
+ }),
1745
+ /* @__PURE__ */ jsxs8(Box7, {
1746
+ children: [
1747
+ /* @__PURE__ */ jsx9(Text9, {
1748
+ dimColor: true,
1749
+ children: `${visible.length > 0 ? cursor + 1 : 0}/${visible.length}`
1750
+ }),
1751
+ selected.size > 0 ? /* @__PURE__ */ jsx9(Text9, {
1752
+ color: tone.notice,
1753
+ children: ` ${selected.size} selected`
1754
+ }) : null,
1755
+ /* @__PURE__ */ jsx9(Text9, {
1756
+ children: " "
1757
+ }),
1758
+ /* @__PURE__ */ jsx9(Hints, {
1759
+ hints: [
1760
+ ["^v", "move"],
1761
+ ["space", "select"],
1762
+ ["enter", "install"],
1763
+ ["tab", tab === "bundles" ? "skills" : "bundles"],
1764
+ ["esc", query ? "clear" : onExit ? "back" : "quit"]
1765
+ ]
1766
+ })
1767
+ ]
1768
+ })
1769
+ ]
1770
+ })
1771
+ });
1772
+ }
1773
+ // src/catalog/screens/catalog.tsx
1774
+ import { Box as Box8, Text as Text10, useApp as useApp5 } from "ink";
1775
+ import { useEffect as useEffect6, useState as useState6 } from "react";
1776
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
1777
+ function CatalogScreen({
1778
+ title,
1779
+ load,
1780
+ onError
1781
+ }) {
1782
+ const { exit } = useApp5();
1783
+ const width = useFrameWidth();
1784
+ const [rows, setRows] = useState6(null);
1785
+ useEffect6(() => {
1786
+ let live = true;
1787
+ load().then((result) => {
1788
+ if (!live)
1789
+ return;
1790
+ setRows(result);
1791
+ exit();
1792
+ }).catch((error) => {
1793
+ if (live)
1794
+ onError(error);
1795
+ });
1796
+ return () => {
1797
+ live = false;
1798
+ };
1799
+ }, [load, exit, onError]);
1800
+ if (rows === null) {
1801
+ return /* @__PURE__ */ jsx10(Frame, {
1802
+ title,
1803
+ width,
1804
+ children: /* @__PURE__ */ jsxs9(Text10, {
1805
+ children: [
1806
+ /* @__PURE__ */ jsx10(Spinner, {}),
1807
+ /* @__PURE__ */ jsx10(Text10, {
1808
+ dimColor: true,
1809
+ children: " Loading..."
1810
+ })
1811
+ ]
1812
+ })
1813
+ });
1814
+ }
1815
+ if (rows.length === 0) {
1816
+ return /* @__PURE__ */ jsx10(Frame, {
1817
+ title,
1818
+ width,
1819
+ children: /* @__PURE__ */ jsx10(Text10, {
1820
+ dimColor: true,
1821
+ children: "No skills found."
1822
+ })
1823
+ });
1824
+ }
1825
+ const names = nameWidth(rows);
1826
+ return /* @__PURE__ */ jsx10(Frame, {
1827
+ title,
1828
+ width,
1829
+ children: /* @__PURE__ */ jsx10(Box8, {
1830
+ flexDirection: "column",
1831
+ children: rows.map((row) => /* @__PURE__ */ jsx10(SkillRow, {
1832
+ row,
1833
+ width: width - 2,
1834
+ names
1835
+ }, row.name))
1836
+ })
1837
+ });
1838
+ }
1839
+ // src/doctor/doctor.ts
1840
+ import { spawn as spawn2 } from "node:child_process";
1841
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
1842
+ import { homedir as homedir2 } from "node:os";
1843
+ import { dirname as dirname4, join as join5 } from "node:path";
1844
+ var REACH_TIMEOUT_MS = 5000;
1845
+ async function reachable(base) {
1846
+ const url = `${base.replace(/\/$/, "")}/api/auth/ok`;
1847
+ try {
1848
+ const res = await fetch(url, {
1849
+ signal: AbortSignal.timeout(REACH_TIMEOUT_MS)
1850
+ });
1851
+ return {
1852
+ name: "server reachable",
1853
+ ok: true,
1854
+ detail: `${base} (${res.status})`
1855
+ };
1856
+ } catch (error) {
1857
+ return {
1858
+ name: "server reachable",
1859
+ ok: false,
1860
+ detail: `${base}: ${error instanceof Error ? error.message : String(error)}`
1861
+ };
1862
+ }
1863
+ }
1864
+ async function session(server) {
1865
+ try {
1866
+ const who = await whoami(server);
1867
+ return [
1868
+ { name: "session valid", ok: true, detail: who.user },
1869
+ {
1870
+ name: "active organization",
1871
+ ok: who.org !== null,
1872
+ detail: who.org ?? "none — pick one with `yourskills org <name>` or in the web UI"
1873
+ }
1874
+ ];
1875
+ } catch (error) {
1876
+ return [
1877
+ {
1878
+ name: "session valid",
1879
+ ok: false,
1880
+ detail: error instanceof Error ? error.message : String(error)
1881
+ },
1882
+ {
1883
+ name: "active organization",
1884
+ ok: false,
1885
+ detail: "unknown until you are logged in"
1886
+ }
1887
+ ];
1888
+ }
1889
+ }
1890
+ function skillsResolvable() {
1891
+ try {
1892
+ const bin = skillsBin();
1893
+ return existsSync2(bin) ? { name: "skills dependency", ok: true, detail: bin } : { name: "skills dependency", ok: false, detail: `missing: ${bin}` };
1894
+ } catch (error) {
1895
+ return {
1896
+ name: "skills dependency",
1897
+ ok: false,
1898
+ detail: error instanceof Error ? error.message : String(error)
1899
+ };
1900
+ }
1901
+ }
1902
+ function detectedAgents() {
1903
+ let bin;
1904
+ try {
1905
+ bin = skillsBin();
1906
+ } catch {
1907
+ return Promise.resolve([]);
1908
+ }
1909
+ return new Promise((settle) => {
1910
+ const child = spawn2(process.execPath, [bin, "list", "--json", "--global"], {
1911
+ env: childEnv(),
1912
+ stdio: ["ignore", "pipe", "ignore"]
1913
+ });
1914
+ let out = "";
1915
+ child.stdout.setEncoding("utf8");
1916
+ child.stdout.on("data", (chunk) => {
1917
+ out += chunk;
1918
+ });
1919
+ child.on("error", () => settle([]));
1920
+ child.on("close", () => {
1921
+ try {
1922
+ const found = new Set;
1923
+ for (const row of JSON.parse(out)) {
1924
+ for (const agent of row.agents ?? [])
1925
+ found.add(agent);
1926
+ }
1927
+ settle([...found]);
1928
+ } catch {
1929
+ settle([]);
1930
+ }
1931
+ });
1932
+ });
1933
+ }
1934
+ async function agents() {
1935
+ const pinned = await resolveAgents();
1936
+ const found = new Set(pinned);
1937
+ for (const agent of await detectedAgents())
1938
+ found.add(agent);
1939
+ const all = [...found];
1940
+ return {
1941
+ name: "agents",
1942
+ ok: all.length > 0,
1943
+ detail: all.length > 0 ? all.join(", ") : "none — pin one with `yourskills config set agents claude-code`"
1944
+ };
1945
+ }
1946
+ function manifestParses(nearest) {
1947
+ if (!existsSync2(nearest.path)) {
1948
+ return {
1949
+ name: "manifest",
1950
+ ok: true,
1951
+ detail: `${nearest.path} (not created yet)`
1952
+ };
1953
+ }
1954
+ try {
1955
+ const raw = JSON.parse(readFileSync2(nearest.path, "utf8"));
1956
+ return raw && typeof raw === "object" ? {
1957
+ name: "manifest",
1958
+ ok: true,
1959
+ detail: `${nearest.path} (${nearest.scope})`
1960
+ } : {
1961
+ name: "manifest",
1962
+ ok: false,
1963
+ detail: `${nearest.path}: not an object`
1964
+ };
1965
+ } catch (error) {
1966
+ return {
1967
+ name: "manifest",
1968
+ ok: false,
1969
+ detail: `${nearest.path}: ${error instanceof Error ? error.message : String(error)}`
1970
+ };
1971
+ }
1972
+ }
1973
+ function onDisk(nearest, name) {
1974
+ const root = nearest.scope === "project" ? dirname4(nearest.path) : homedir2();
1975
+ return existsSync2(join5(root, ".agents", "skills", name));
1976
+ }
1977
+ function manifestMatchesDisk(nearest) {
1978
+ const names = Object.keys(nearest.manifest.skills);
1979
+ const missing = names.filter((name) => !onDisk(nearest, name));
1980
+ return {
1981
+ name: "manifest matches disk",
1982
+ ok: missing.length === 0,
1983
+ detail: missing.length === 0 ? `${names.length} skill(s) recorded` : `missing: ${missing.join(", ")} — run \`yourskills install\``
1984
+ };
1985
+ }
1986
+ async function doctorReport(nearest, server) {
1987
+ const config = await readConfig();
1988
+ const base = serverUrl(config, server);
1989
+ const checks = [
1990
+ await reachable(base),
1991
+ ...await session(server),
1992
+ skillsResolvable(),
1993
+ await agents(),
1994
+ manifestParses(nearest),
1995
+ manifestMatchesDisk(nearest)
1996
+ ];
1997
+ const width = Math.max(...checks.map((check) => check.name.length));
1998
+ const lines = checks.map((check) => ({
1999
+ mark: check.ok ? "ok" : "fail",
2000
+ text: check.name.padEnd(width),
2001
+ note: check.detail
2002
+ }));
2003
+ return {
2004
+ title: "doctor",
2005
+ lines,
2006
+ json: { manifest: nearest.path, checks },
2007
+ failed: checks.some((check) => !check.ok)
2008
+ };
2009
+ }
2010
+ // src/hooks/adapters.ts
2011
+ import { homedir as homedir3 } from "node:os";
2012
+ import { join as join6 } from "node:path";
2013
+ var BINARY = "yourskills";
2014
+ function claudeHome() {
2015
+ const override = process.env.CLAUDE_CONFIG_DIR;
2016
+ return override && override.length > 0 ? override : join6(homedir3(), ".claude");
2017
+ }
2018
+ function opencodeHome() {
2019
+ const xdg = process.env.XDG_CONFIG_HOME;
2020
+ return join6(xdg && xdg.length > 0 ? xdg : join6(homedir3(), ".config"), "opencode");
2021
+ }
2022
+ var UPDATE_CHECK_ENTRY = {
2023
+ kind: "update-check",
2024
+ event: "SessionStart",
2025
+ matcher: "startup|resume",
2026
+ command: `${BINARY} hook session-start`
2027
+ };
2028
+ var USAGE_ENTRY = {
2029
+ kind: "usage",
2030
+ event: "PostToolUse",
2031
+ matcher: "Skill",
2032
+ command: `${BINARY} hook post-tool-use`
2033
+ };
2034
+ var HOOK_ADAPTERS = [
2035
+ {
2036
+ agent: "claude-code",
2037
+ supports: ["update-check", "usage"],
2038
+ home: claudeHome,
2039
+ settingsPath: () => join6(claudeHome(), "settings.json"),
2040
+ entries: [UPDATE_CHECK_ENTRY, USAGE_ENTRY]
2041
+ },
2042
+ {
2043
+ agent: "opencode",
2044
+ supports: ["update-check"],
2045
+ home: opencodeHome,
2046
+ settingsPath: () => null,
2047
+ entries: [],
2048
+ limitation: "opencode hooks are JavaScript plugin modules, not commands in a settings file — yourskills cannot register one yet"
2049
+ }
2050
+ ];
2051
+ function adapterFor(agent) {
2052
+ return HOOK_ADAPTERS.find((adapter) => adapter.agent === agent);
2053
+ }
2054
+ function agentsToReport(configured) {
2055
+ const names = new Set(HOOK_ADAPTERS.map((adapter) => adapter.agent));
2056
+ for (const agent of configured)
2057
+ names.add(agent);
2058
+ return [...names].sort();
2059
+ }
2060
+ function agentForEvent(kind, event) {
2061
+ const owner = HOOK_ADAPTERS.find((adapter) => adapter.entries.some((entry) => entry.kind === kind && entry.event === event));
2062
+ return owner?.agent ?? "unknown";
2063
+ }
2064
+ // src/hooks/ensure.ts
2065
+ import { existsSync as existsSync3 } from "node:fs";
2066
+
2067
+ // src/usage/queue.ts
2068
+ import { mkdirSync as mkdirSync3 } from "node:fs";
2069
+ import { appendFile, readFile as readFile3, rm, stat, writeFile as writeFile2 } from "node:fs/promises";
2070
+ import { join as join7 } from "node:path";
2071
+
2072
+ // src/client.ts
2073
+ import { createTRPCClient, httpBatchLink } from "@trpc/client";
2074
+ async function createClient(server) {
2075
+ const { token, base } = await requireToken(server);
2076
+ return createTRPCClient({
2077
+ links: [
2078
+ httpBatchLink({
2079
+ url: `${base.replace(/\/$/, "")}/trpc`,
2080
+ headers: () => ({ authorization: `Bearer ${token}` })
2081
+ })
2082
+ ]
2083
+ });
2084
+ }
2085
+
2086
+ // src/usage/queue.ts
2087
+ var USAGE_BATCH_MAX = 100;
2088
+ var QUEUE_MAX_EVENTS = 5000;
2089
+ var TRIM_ABOVE_BYTES = QUEUE_MAX_EVENTS * 80;
2090
+ function queuePath() {
2091
+ return join7(configDir(), "queue.jsonl");
2092
+ }
2093
+ function parseLine(line) {
2094
+ try {
2095
+ const parsed = JSON.parse(line);
2096
+ if (typeof parsed?.skillExternalId !== "string" || typeof parsed.ref !== "string" || typeof parsed.agent !== "string" || typeof parsed.occurredAt !== "string") {
2097
+ return null;
2098
+ }
2099
+ return {
2100
+ skillExternalId: parsed.skillExternalId,
2101
+ ref: parsed.ref,
2102
+ agent: parsed.agent,
2103
+ occurredAt: parsed.occurredAt
2104
+ };
2105
+ } catch {
2106
+ return null;
2107
+ }
2108
+ }
2109
+ async function readRaw(path) {
2110
+ try {
2111
+ return await readFile3(path, "utf8");
2112
+ } catch {
2113
+ return "";
2114
+ }
2115
+ }
2116
+ async function readQueue() {
2117
+ const events = [];
2118
+ for (const line of (await readRaw(queuePath())).split(`
2119
+ `)) {
2120
+ if (line.trim().length === 0)
2121
+ continue;
2122
+ const event = parseLine(line);
2123
+ if (event)
2124
+ events.push(event);
2125
+ }
2126
+ return events;
2127
+ }
2128
+ async function trim(path) {
2129
+ const events = await readQueue();
2130
+ if (events.length <= QUEUE_MAX_EVENTS)
2131
+ return;
2132
+ const kept = events.slice(events.length - QUEUE_MAX_EVENTS);
2133
+ await writeFile2(path, kept.map(line).join(""));
2134
+ }
2135
+ function line(event) {
2136
+ return `${JSON.stringify(event)}
2137
+ `;
2138
+ }
2139
+ async function appendEvent(event) {
2140
+ const path = queuePath();
2141
+ mkdirSync3(configDir(), { recursive: true, mode: 448 });
2142
+ await appendFile(path, line(event));
2143
+ try {
2144
+ const info = await stat(path);
2145
+ if (info.size > TRIM_ABOVE_BYTES)
2146
+ await trim(path);
2147
+ } catch {}
2148
+ }
2149
+ async function keepFrom(path, before, sent) {
2150
+ const lines = before.split(`
2151
+ `);
2152
+ let seen = 0;
2153
+ let cut = lines.length;
2154
+ for (let i = 0;i < lines.length; i++) {
2155
+ const raw = lines[i];
2156
+ if (raw.trim().length === 0)
2157
+ continue;
2158
+ if (parseLine(raw) === null)
2159
+ continue;
2160
+ seen++;
2161
+ if (seen === sent) {
2162
+ cut = i + 1;
2163
+ break;
2164
+ }
2165
+ }
2166
+ const remainder = lines.slice(cut).join(`
2167
+ `);
2168
+ const current = await readRaw(path);
2169
+ const appended = current.length > before.length ? current.slice(before.length) : "";
2170
+ const text = `${remainder}${appended}`;
2171
+ if (text.trim().length === 0) {
2172
+ await rm(path, { force: true });
2173
+ return;
2174
+ }
2175
+ await writeFile2(path, text.endsWith(`
2176
+ `) ? text : `${text}
2177
+ `);
2178
+ }
2179
+ async function flush(client) {
2180
+ const path = queuePath();
2181
+ const before = await readRaw(path);
2182
+ const events = [];
2183
+ for (const raw of before.split(`
2184
+ `)) {
2185
+ if (raw.trim().length === 0)
2186
+ continue;
2187
+ const event = parseLine(raw);
2188
+ if (event)
2189
+ events.push(event);
2190
+ }
2191
+ if (events.length === 0) {
2192
+ if (before.trim().length > 0)
2193
+ await rm(path, { force: true });
2194
+ return { sent: 0, remaining: 0 };
2195
+ }
2196
+ let sent = 0;
2197
+ try {
2198
+ for (let i = 0;i < events.length; i += USAGE_BATCH_MAX) {
2199
+ const batch = events.slice(i, i + USAGE_BATCH_MAX);
2200
+ await client.usage.record.mutate({ events: batch });
2201
+ sent += batch.length;
2202
+ }
2203
+ } catch {}
2204
+ if (sent > 0)
2205
+ await keepFrom(path, before, sent);
2206
+ return { sent, remaining: events.length - sent };
2207
+ }
2208
+ function trackingDisabledByEnv() {
2209
+ const value = process.env.YOURSKILLS_NO_TRACK;
2210
+ return value !== undefined && value !== "" && value !== "0";
2211
+ }
2212
+ async function trackingEnabled() {
2213
+ if (trackingDisabledByEnv())
2214
+ return false;
2215
+ return (await readConfig()).track === true;
2216
+ }
2217
+ async function flushQuietly(server) {
2218
+ try {
2219
+ if (!await trackingEnabled())
2220
+ return 0;
2221
+ if ((await readQueue()).length === 0)
2222
+ return 0;
2223
+ const { sent } = await flush(await createClient(server));
2224
+ return sent;
2225
+ } catch {
2226
+ return 0;
2227
+ }
2228
+ }
2229
+ // src/hooks/settings.ts
2230
+ import { mkdirSync as mkdirSync4 } from "node:fs";
2231
+ import { readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
2232
+ import { dirname as dirname5 } from "node:path";
2233
+ function binaryName(command) {
2234
+ const parts = command.split(/[\\/]/);
2235
+ return (parts[parts.length - 1] ?? "").replace(/\.(js|mjs|cjs|exe)$/i, "");
2236
+ }
2237
+ function isOurCommand(command) {
2238
+ if (typeof command !== "string")
2239
+ return false;
2240
+ const match = command.trim().match(/^(?:"([^"]+)"|'([^']+)'|(\S+))\s+hook\s+\S/);
2241
+ if (!match)
2242
+ return false;
2243
+ return binaryName(match[1] ?? match[2] ?? match[3] ?? "") === BINARY;
2244
+ }
2245
+ function groupIsOurs(group) {
2246
+ return (group.hooks ?? []).some((hook) => isOurCommand(hook.command));
2247
+ }
2248
+ function addHooks(settings, entries) {
2249
+ let next = { ...settings };
2250
+ for (const entry of entries) {
2251
+ const hooks = { ...next.hooks ?? {} };
2252
+ const groups = [...hooks[entry.event] ?? []];
2253
+ const ours = groups.findIndex((group) => group.matcher === entry.matcher && groupIsOurs(group));
2254
+ if (ours === -1) {
2255
+ groups.push({
2256
+ matcher: entry.matcher,
2257
+ hooks: [{ type: "command", command: entry.command }]
2258
+ });
2259
+ } else {
2260
+ const group = groups[ours];
2261
+ const commands = [...group.hooks ?? []];
2262
+ if (commands.some((hook) => hook.command === entry.command))
2263
+ continue;
2264
+ commands.push({ type: "command", command: entry.command });
2265
+ groups[ours] = { ...group, hooks: commands };
2266
+ }
2267
+ hooks[entry.event] = groups;
2268
+ next = { ...next, hooks };
2269
+ }
2270
+ return next;
2271
+ }
2272
+ function removeHooks(settings) {
2273
+ if (!settings.hooks || typeof settings.hooks !== "object")
2274
+ return { ...settings };
2275
+ const hooks = {};
2276
+ for (const [event, groups] of Object.entries(settings.hooks)) {
2277
+ if (!Array.isArray(groups)) {
2278
+ hooks[event] = groups;
2279
+ continue;
2280
+ }
2281
+ const kept = [];
2282
+ let changed = false;
2283
+ for (const group of groups) {
2284
+ const commands = group?.hooks;
2285
+ if (!Array.isArray(commands) || !groupIsOurs(group)) {
2286
+ kept.push(group);
2287
+ continue;
2288
+ }
2289
+ changed = true;
2290
+ const rest = commands.filter((hook) => !isOurCommand(hook.command));
2291
+ if (rest.length > 0)
2292
+ kept.push({ ...group, hooks: rest });
2293
+ }
2294
+ if (!changed) {
2295
+ hooks[event] = groups;
2296
+ continue;
2297
+ }
2298
+ if (kept.length > 0)
2299
+ hooks[event] = kept;
2300
+ }
2301
+ return { ...settings, hooks };
2302
+ }
2303
+ function registeredEntries(settings, entries) {
2304
+ return entries.filter((entry) => (settings.hooks?.[entry.event] ?? []).some((group) => group.matcher === entry.matcher && (group.hooks ?? []).some((hook) => hook.command === entry.command)));
2305
+ }
2306
+ function detectIndent(text) {
2307
+ const match = text.match(/\n([\t ]+)\S/);
2308
+ if (!match?.[1])
2309
+ return " ";
2310
+ return match[1].includes("\t") ? "\t" : match[1];
2311
+ }
2312
+ async function readSettingsFile(path) {
2313
+ let text;
2314
+ try {
2315
+ text = await readFile4(path, "utf8");
2316
+ } catch {
2317
+ return { ok: true, settings: {}, indent: " ", existed: false };
2318
+ }
2319
+ if (text.trim().length === 0) {
2320
+ return { ok: true, settings: {}, indent: " ", existed: true };
2321
+ }
2322
+ let parsed;
2323
+ try {
2324
+ parsed = JSON.parse(text);
2325
+ } catch (error) {
2326
+ return {
2327
+ ok: false,
2328
+ error: `${path}: not valid JSON (${error instanceof Error ? error.message : String(error)})`
2329
+ };
2330
+ }
2331
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
2332
+ return { ok: false, error: `${path}: not a JSON object` };
2333
+ }
2334
+ return {
2335
+ ok: true,
2336
+ settings: parsed,
2337
+ indent: detectIndent(text),
2338
+ existed: true
2339
+ };
2340
+ }
2341
+ function serialiseSettings(settings, indent) {
2342
+ return `${JSON.stringify(settings, null, indent)}
2343
+ `;
2344
+ }
2345
+ async function writeSettingsFile(path, settings, indent) {
2346
+ mkdirSync4(dirname5(path), { recursive: true });
2347
+ await writeFile3(path, serialiseSettings(settings, indent));
2348
+ }
2349
+
2350
+ // src/hooks/ensure.ts
2351
+ async function registerInto(adapter) {
2352
+ const wanted = adapter.entries.filter((entry) => entry.kind === "update-check");
2353
+ if (wanted.length === 0)
2354
+ return null;
2355
+ const path = adapter.settingsPath();
2356
+ if (!path)
2357
+ return null;
2358
+ if (!existsSync3(adapter.home()))
2359
+ return null;
2360
+ const file = await readSettingsFile(path);
2361
+ if (!file.ok)
2362
+ return null;
2363
+ const merged = addHooks(file.settings, wanted);
2364
+ if (serialiseSettings(merged, file.indent) === serialiseSettings(file.settings, file.indent)) {
2365
+ return null;
2366
+ }
2367
+ await writeSettingsFile(path, merged, file.indent);
2368
+ return { agent: adapter.agent, path, entries: wanted };
2369
+ }
2370
+ function linesFor(registered) {
2371
+ const lines = [
2372
+ "yourskills registered its update-check hook so your agent can tell you when an installed skill has moved:"
2373
+ ];
2374
+ for (const { agent, path, entries } of registered) {
2375
+ for (const entry of entries) {
2376
+ lines.push(` ${agent} ${entry.event}(${entry.matcher}) ${path}`);
2377
+ }
2378
+ }
2379
+ lines.push(" undo any time with `yourskills hooks uninstall`");
2380
+ if (!trackingDisabledByEnv()) {
2381
+ lines.push(" usage counting stays off; `yourskills hooks install --track` turns it on");
2382
+ }
2383
+ return lines;
2384
+ }
2385
+ async function ensureHooksRegistered() {
2386
+ try {
2387
+ const config = await readConfig();
2388
+ if (config.hooksOffered)
2389
+ return [];
2390
+ if (!config.token)
2391
+ return [];
2392
+ const registered = [];
2393
+ for (const adapter of HOOK_ADAPTERS) {
2394
+ try {
2395
+ const result = await registerInto(adapter);
2396
+ if (result)
2397
+ registered.push(result);
2398
+ } catch {}
2399
+ }
2400
+ await writeConfig({ ...config, hooksOffered: new Date().toISOString() });
2401
+ return registered.length > 0 ? linesFor(registered) : [];
2402
+ } catch {
2403
+ return [];
2404
+ }
2405
+ }
2406
+ // src/hooks/install.ts
2407
+ import { existsSync as existsSync4 } from "node:fs";
2408
+ var PAYLOAD_DISCLOSURE = [
2409
+ { mark: "none", text: "Usage tracking sends, once per skill invocation:" },
2410
+ { mark: "none", text: " skillExternalId", note: "the skill's registry id" },
2411
+ { mark: "none", text: " ref", note: "the commit you installed it at" },
2412
+ { mark: "none", text: " agent", note: "which agent ran it" },
2413
+ { mark: "none", text: " occurredAt", note: "when it ran" },
2414
+ {
2415
+ mark: "none",
2416
+ text: " (nothing else)",
2417
+ note: "the server adds a keyed hash of you; the CLI never sends a user id"
2418
+ }
2419
+ ];
2420
+ function trackNote(track) {
2421
+ if (trackingDisabledByEnv())
2422
+ return "off (YOURSKILLS_NO_TRACK is set)";
2423
+ return track ? "on" : "off — enable with `yourskills hooks install --track`";
2424
+ }
2425
+ function selectAdapters(agent) {
2426
+ if (agent) {
2427
+ const found = adapterFor(agent);
2428
+ return found ? { adapters: [found], unknown: [] } : { adapters: [], unknown: [agent] };
2429
+ }
2430
+ return {
2431
+ adapters: HOOK_ADAPTERS.filter((a) => existsSync4(a.home())),
2432
+ unknown: []
2433
+ };
2434
+ }
2435
+ async function pinnedAdapters() {
2436
+ const pinned = await resolveAgents();
2437
+ if (pinned.length === 0)
2438
+ return null;
2439
+ const adapters = pinned.map((name) => adapterFor(name)).filter((a) => a !== undefined);
2440
+ return adapters.length > 0 ? adapters : null;
2441
+ }
2442
+ async function installInto(adapter, optedIn) {
2443
+ const path = adapter.settingsPath();
2444
+ const result = {
2445
+ agent: adapter.agent,
2446
+ path,
2447
+ installed: [],
2448
+ already: [],
2449
+ skipped: []
2450
+ };
2451
+ if (!path) {
2452
+ for (const kind of adapter.supports) {
2453
+ result.skipped.push({
2454
+ kind,
2455
+ why: adapter.limitation ?? "not supported yet"
2456
+ });
2457
+ }
2458
+ return result;
2459
+ }
2460
+ const wanted = [];
2461
+ for (const entry of adapter.entries) {
2462
+ if (entry.kind === "usage" && !optedIn) {
2463
+ result.skipped.push({ kind: entry.kind, why: trackNote(false) });
2464
+ continue;
2465
+ }
2466
+ wanted.push(entry);
2467
+ }
2468
+ const file = await readSettingsFile(path);
2469
+ if (!file.ok) {
2470
+ result.error = file.error;
2471
+ return result;
2472
+ }
2473
+ const present = new Set(registeredEntries(file.settings, wanted).map((entry) => entry.kind));
2474
+ for (const entry of wanted) {
2475
+ if (present.has(entry.kind))
2476
+ result.already.push(entry.kind);
2477
+ else
2478
+ result.installed.push(entry.kind);
2479
+ }
2480
+ const merged = addHooks(file.settings, wanted);
2481
+ if (serialiseSettings(merged, file.indent) !== serialiseSettings(file.settings, file.indent)) {
2482
+ await writeSettingsFile(path, merged, file.indent);
2483
+ }
2484
+ return result;
2485
+ }
2486
+ function linesFor2(result) {
2487
+ const lines = [];
2488
+ if (result.error) {
2489
+ lines.push({
2490
+ mark: "fail",
2491
+ text: `${result.agent}: left untouched`,
2492
+ note: result.error
2493
+ });
2494
+ return lines;
2495
+ }
2496
+ for (const kind of result.installed) {
2497
+ lines.push({
2498
+ mark: "ok",
2499
+ text: `${result.agent} ${kind}`,
2500
+ note: result.path ?? ""
2501
+ });
2502
+ }
2503
+ for (const kind of result.already) {
2504
+ lines.push({
2505
+ mark: "ok",
2506
+ text: `${result.agent} ${kind}`,
2507
+ note: `already registered ${result.path ?? ""}`.trim()
2508
+ });
2509
+ }
2510
+ for (const skip of result.skipped) {
2511
+ lines.push({
2512
+ mark: "pending",
2513
+ text: `${result.agent} ${skip.kind}`,
2514
+ note: `skipped: ${skip.why}`
2515
+ });
2516
+ }
2517
+ return lines;
2518
+ }
2519
+ async function hooksInstall(opts = {}) {
2520
+ if (opts.track !== undefined) {
2521
+ const config2 = await readConfig();
2522
+ await writeConfig({ ...config2, track: opts.track });
2523
+ }
2524
+ const config = await readConfig();
2525
+ const optedIn = !trackingDisabledByEnv() && config.track === true;
2526
+ const { adapters: selected, unknown } = selectAdapters(opts.agent);
2527
+ const adapters = opts.agent === undefined ? await pinnedAdapters() ?? selected : selected;
2528
+ const lines = [...PAYLOAD_DISCLOSURE];
2529
+ lines.push({ mark: "none", text: "tracking", note: trackNote(optedIn) });
2530
+ for (const agent of unknown) {
2531
+ lines.push({
2532
+ mark: "fail",
2533
+ text: agent,
2534
+ note: "no yourskills hook support — see `yourskills hooks status`"
2535
+ });
2536
+ }
2537
+ const results = [];
2538
+ for (const adapter of adapters) {
2539
+ const result = await installInto(adapter, optedIn);
2540
+ results.push(result);
2541
+ lines.push(...linesFor2(result));
2542
+ }
2543
+ if (adapters.length === 0 && unknown.length === 0) {
2544
+ lines.push({
2545
+ mark: "pending",
2546
+ text: "No agent found that can carry yourskills hooks.",
2547
+ note: "pin one with `yourskills config set agents claude-code`"
2548
+ });
2549
+ }
2550
+ return {
2551
+ title: "hooks install",
2552
+ lines,
2553
+ json: { track: optedIn, agents: results, unknown },
2554
+ failed: unknown.length > 0 || results.some((r) => r.error !== undefined)
2555
+ };
2556
+ }
2557
+ async function hooksUninstall(opts = {}) {
2558
+ const { adapters: selected, unknown } = selectAdapters(opts.agent);
2559
+ const adapters = opts.agent ? selected : HOOK_ADAPTERS;
2560
+ const lines = [];
2561
+ const results = [];
2562
+ for (const agent of unknown) {
2563
+ lines.push({ mark: "fail", text: agent, note: "unknown agent" });
2564
+ }
2565
+ for (const adapter of adapters) {
2566
+ const path = adapter.settingsPath();
2567
+ if (!path)
2568
+ continue;
2569
+ const file = await readSettingsFile(path);
2570
+ if (!file.ok) {
2571
+ results.push({
2572
+ agent: adapter.agent,
2573
+ path,
2574
+ removed: false,
2575
+ error: file.error
2576
+ });
2577
+ lines.push({
2578
+ mark: "fail",
2579
+ text: `${adapter.agent}: left untouched`,
2580
+ note: file.error
2581
+ });
2582
+ continue;
2583
+ }
2584
+ const stripped = removeHooks(file.settings);
2585
+ const changed = serialiseSettings(stripped, file.indent) !== serialiseSettings(file.settings, file.indent);
2586
+ if (changed)
2587
+ await writeSettingsFile(path, stripped, file.indent);
2588
+ results.push({ agent: adapter.agent, path, removed: changed });
2589
+ lines.push({
2590
+ mark: changed ? "ok" : "none",
2591
+ text: `${adapter.agent} ${changed ? "removed" : "nothing registered"}`,
2592
+ note: path
2593
+ });
2594
+ }
2595
+ return {
2596
+ title: "hooks uninstall",
2597
+ lines,
2598
+ json: { agents: results, unknown },
2599
+ failed: unknown.length > 0 || results.some((r) => r.error !== undefined)
2600
+ };
2601
+ }
2602
+ async function hooksStatus() {
2603
+ const config = await readConfig();
2604
+ const optedIn = !trackingDisabledByEnv() && config.track === true;
2605
+ const lines = [
2606
+ { mark: "none", text: "tracking", note: trackNote(optedIn) }
2607
+ ];
2608
+ const agents2 = [];
2609
+ for (const agent of agentsToReport(config.agents ?? [])) {
2610
+ const adapter = adapterFor(agent);
2611
+ if (!adapter) {
2612
+ lines.push({
2613
+ mark: "none",
2614
+ text: agent,
2615
+ note: "no yourskills hook support"
2616
+ });
2617
+ agents2.push({ agent, supported: false });
2618
+ continue;
2619
+ }
2620
+ const path = adapter.settingsPath();
2621
+ if (!path) {
2622
+ lines.push({
2623
+ mark: "none",
2624
+ text: agent,
2625
+ note: adapter.limitation ?? "not supported yet"
2626
+ });
2627
+ agents2.push({
2628
+ agent,
2629
+ supported: false,
2630
+ supports: adapter.supports,
2631
+ limitation: adapter.limitation
2632
+ });
2633
+ continue;
2634
+ }
2635
+ const file = await readSettingsFile(path);
2636
+ if (!file.ok) {
2637
+ lines.push({ mark: "fail", text: agent, note: file.error });
2638
+ agents2.push({ agent, supported: true, path, error: file.error });
2639
+ continue;
2640
+ }
2641
+ const registered = new Set(registeredEntries(file.settings, adapter.entries).map((e) => e.kind));
2642
+ for (const entry of adapter.entries) {
2643
+ const on = registered.has(entry.kind);
2644
+ lines.push({
2645
+ mark: on ? "ok" : "pending",
2646
+ text: `${agent} ${entry.kind}`,
2647
+ note: on ? `${entry.event}(${entry.matcher}) ${path}` : "not registered — run `yourskills hooks install`"
2648
+ });
2649
+ }
2650
+ agents2.push({
2651
+ agent,
2652
+ supported: true,
2653
+ path,
2654
+ registered: [...registered],
2655
+ supports: adapter.supports
2656
+ });
2657
+ }
2658
+ return {
2659
+ title: "hooks status",
2660
+ lines,
2661
+ json: { track: optedIn, agents: agents2 }
2662
+ };
2663
+ }
2664
+ // ../../packages/bundles/src/drift.ts
2665
+ var SHORT_SHA = 7;
2666
+ function namesSameCommit(prefix, full) {
2667
+ const short = prefix.trim().toLowerCase();
2668
+ const long = full.trim().toLowerCase();
2669
+ if (short === long)
2670
+ return true;
2671
+ return short.length >= SHORT_SHA && long.startsWith(short);
2672
+ }
2673
+
2674
+ // src/installed/installed.ts
2675
+ var shortRef = (ref) => ref.slice(0, SHORT_SHA);
2676
+ function installedRows(nearest) {
2677
+ return Object.values(nearest.manifest.skills).map((entry) => ({
2678
+ name: entry.name,
2679
+ ref: entry.ref,
2680
+ bundle: entry.bundle ?? null,
2681
+ scope: nearest.manifest.scope
2682
+ })).sort((a, b) => a.name.localeCompare(b.name));
2683
+ }
2684
+ function columns(rows) {
2685
+ const names = Math.max(...rows.map((r) => r.name.length));
2686
+ const bundles = Math.max(...rows.map((r) => (r.bundle ?? "-").length));
2687
+ return rows.map((row) => ({
2688
+ mark: "none",
2689
+ text: `${row.name.padEnd(names)} ${shortRef(row.ref)}`,
2690
+ note: `${(row.bundle ?? "-").padEnd(bundles)} ${row.scope}`
2691
+ }));
2692
+ }
2693
+ function installedReport(nearest) {
2694
+ const rows = installedRows(nearest);
2695
+ const lines = rows.length === 0 ? [
2696
+ {
2697
+ mark: "none",
2698
+ text: "Nothing installed. Run `yourskills bundle <name>` or `yourskills add <skill>`.",
2699
+ note: nearest.path
2700
+ }
2701
+ ] : columns(rows);
2702
+ return {
2703
+ title: "installed",
2704
+ lines,
2705
+ json: {
2706
+ manifest: nearest.path,
2707
+ scope: nearest.manifest.scope,
2708
+ skills: rows
2709
+ }
2710
+ };
2711
+ }
2712
+ // src/installed/outdated.ts
2713
+ function hasMoved(installed, available) {
2714
+ return !(namesSameCommit(available, installed) || namesSameCommit(installed, available));
2715
+ }
2716
+ function drift(manifest, pins, catalog) {
2717
+ const byExternalId = new Map(catalog.map((row) => [row.externalId, row]));
2718
+ const byName = new Map(catalog.map((row) => [row.name, row]));
2719
+ const moved = [];
2720
+ const unknown = [];
2721
+ for (const entry of Object.values(manifest.skills)) {
2722
+ const row = byExternalId.get(entry.externalId) ?? byName.get(entry.name);
2723
+ if (!row) {
2724
+ unknown.push(entry.name);
2725
+ continue;
2726
+ }
2727
+ const pin = entry.bundle ? pins.find((p) => p.bundle === entry.bundle && p.skillId === row.id) : undefined;
2728
+ const available = pin?.pinnedSha ?? row.currentSha;
2729
+ if (!hasMoved(entry.ref, available))
2730
+ continue;
2731
+ moved.push({
2732
+ name: entry.name,
2733
+ bundle: entry.bundle ?? null,
2734
+ installed: entry.ref,
2735
+ available,
2736
+ deprecated: pin?.deprecated ?? row.status === "deprecated"
2737
+ });
2738
+ }
2739
+ moved.sort((a, b) => a.name.localeCompare(b.name));
2740
+ return { moved, unknown };
2741
+ }
2742
+ async function outdatedReport(client, nearest, quiet = false) {
2743
+ let pins;
2744
+ let catalog;
2745
+ try {
2746
+ [pins, catalog] = await Promise.all([
2747
+ client.bundles.pins.query(),
2748
+ client.skills.list.query({})
2749
+ ]);
2750
+ } catch (error) {
2751
+ const message = error instanceof Error ? error.message : String(error);
2752
+ return {
2753
+ title: "outdated",
2754
+ lines: [
2755
+ { mark: "fail", text: "Could not reach the registry.", note: message }
2756
+ ],
2757
+ json: { manifest: nearest.path, error: message },
2758
+ silent: quiet
2759
+ };
2760
+ }
2761
+ const report = drift(nearest.manifest, pins, catalog);
2762
+ const lines = report.moved.map((row) => ({
2763
+ mark: "pending",
2764
+ text: `${row.name} ${shortRef(row.installed)} -> ${shortRef(row.available)}`,
2765
+ note: [row.bundle ?? "-", row.deprecated ? "[deprecated]" : ""].filter(Boolean).join(" ")
2766
+ }));
2767
+ for (const name of report.unknown) {
2768
+ lines.push({
2769
+ mark: "fail",
2770
+ text: name,
2771
+ note: "no longer in your registry"
2772
+ });
2773
+ }
2774
+ const current = report.moved.length === 0 && report.unknown.length === 0;
2775
+ if (current && !quiet) {
2776
+ lines.push({
2777
+ mark: "ok",
2778
+ text: "Everything is at the ref your registry pins."
2779
+ });
2780
+ }
2781
+ return {
2782
+ title: "outdated",
2783
+ lines,
2784
+ json: {
2785
+ manifest: nearest.path,
2786
+ outdated: report.moved,
2787
+ unknown: report.unknown
2788
+ },
2789
+ silent: quiet && current
2790
+ };
2791
+ }
2792
+ // src/hooks/run.ts
2793
+ function isHookEventName(value) {
2794
+ return value === "session-start" || value === "post-tool-use";
2795
+ }
2796
+ var STDIN_BUDGET_MS = 2000;
2797
+ var SESSION_BUDGET_MS = 5000;
2798
+ var DAY_MS = 86400000;
2799
+ async function readStdin() {
2800
+ if (process.stdin.isTTY)
2801
+ return {};
2802
+ const text = await Promise.race([
2803
+ (async () => {
2804
+ let raw = "";
2805
+ process.stdin.setEncoding("utf8");
2806
+ for await (const chunk of process.stdin)
2807
+ raw += chunk;
2808
+ return raw;
2809
+ })(),
2810
+ new Promise((resolve3) => {
2811
+ const timer = setTimeout(() => resolve3(""), STDIN_BUDGET_MS);
2812
+ timer.unref?.();
2813
+ })
2814
+ ]);
2815
+ if (text.trim().length === 0)
2816
+ return {};
2817
+ try {
2818
+ return JSON.parse(text);
2819
+ } catch {
2820
+ return {};
2821
+ }
2822
+ }
2823
+ async function lookup(name, cwd) {
2824
+ const nearest = await nearestManifest(undefined, cwd);
2825
+ const found = nearest.manifest.skills[name];
2826
+ if (found)
2827
+ return found;
2828
+ if (nearest.scope === "global")
2829
+ return;
2830
+ const global = readManifest(manifestPathFor("global"));
2831
+ return global.skills[name];
2832
+ }
2833
+ async function queueInvocation(payload, now = new Date) {
2834
+ if (!await trackingEnabled())
2835
+ return null;
2836
+ const name = payload.tool_input?.skill;
2837
+ if (typeof name !== "string" || name.length === 0)
2838
+ return null;
2839
+ const entry = await lookup(name, payload.cwd);
2840
+ if (!entry)
2841
+ return null;
2842
+ const event = {
2843
+ skillExternalId: entry.externalId,
2844
+ ref: entry.ref,
2845
+ agent: agentForEvent("usage", "PostToolUse"),
2846
+ occurredAt: now.toISOString()
2847
+ };
2848
+ await appendEvent(event);
2849
+ return event;
2850
+ }
2851
+ function updateCheckDue(last, now = Date.now()) {
2852
+ if (!last)
2853
+ return true;
2854
+ const at = Date.parse(last);
2855
+ return Number.isNaN(at) || now - at >= DAY_MS;
2856
+ }
2857
+ async function sessionStart() {
2858
+ const config = await readConfig();
2859
+ if (!updateCheckDue(config.lastUpdateCheck))
2860
+ return;
2861
+ await writeConfig({ ...config, lastUpdateCheck: new Date().toISOString() });
2862
+ const client = await createClient();
2863
+ const nearest = await nearestManifest();
2864
+ const report = await outdatedReport(client, nearest, true);
2865
+ if (!report.silent && report.lines.length > 0) {
2866
+ const moved = report.json.outdated?.length ?? 0;
2867
+ if (moved > 0) {
2868
+ console.log(`yourskills: ${moved} skill(s) behind your registry — run \`yourskills update\``);
2869
+ }
2870
+ }
2871
+ if (await trackingEnabled())
2872
+ await flush(client);
2873
+ }
2874
+ async function runHook(event) {
2875
+ try {
2876
+ const payload = await readStdin();
2877
+ if (event === "post-tool-use")
2878
+ await queueInvocation(payload);
2879
+ else
2880
+ await withBudget(sessionStart(), SESSION_BUDGET_MS);
2881
+ } catch {}
2882
+ process.exitCode = 0;
2883
+ }
2884
+ function withBudget(work, ms) {
2885
+ return Promise.race([
2886
+ work,
2887
+ new Promise((resolve3) => {
2888
+ const timer = setTimeout(() => resolve3(undefined), ms);
2889
+ timer.unref?.();
2890
+ })
2891
+ ]);
2892
+ }
2893
+ // src/org/org.ts
2894
+ var authBase2 = (base) => `${base.replace(/\/$/, "")}/api/auth`;
2895
+ async function activeOrganizationId(base, token) {
2896
+ const res = await fetch(`${authBase2(base)}/get-session`, {
2897
+ headers: { authorization: `Bearer ${token}` }
2898
+ });
2899
+ if (!res.ok)
2900
+ return null;
2901
+ const body = await res.json();
2902
+ return body?.session?.activeOrganizationId ?? null;
2903
+ }
2904
+ async function listOrganizations(base, token) {
2905
+ let res;
2906
+ try {
2907
+ res = await fetch(`${authBase2(base)}/organization/list`, {
2908
+ headers: { authorization: `Bearer ${token}` }
2909
+ });
2910
+ } catch (error) {
2911
+ throw new Error(`Could not reach ${base}: ${error instanceof Error ? error.message : String(error)}`);
2912
+ }
2913
+ if (!res.ok) {
2914
+ throw new Error(`Could not list organizations: ${res.status} ${res.statusText}`);
2915
+ }
2916
+ return await res.json();
2917
+ }
2918
+ function rows(orgs, active) {
2919
+ if (orgs.length === 0) {
2920
+ return [
2921
+ {
2922
+ mark: "none",
2923
+ text: "You are not a member of any organization yet."
2924
+ }
2925
+ ];
2926
+ }
2927
+ const width = Math.max(...orgs.map((o) => o.name.length));
2928
+ return orgs.map((org) => ({
2929
+ mark: org.id === active ? "ok" : "none",
2930
+ text: `${org.id === active ? "" : " "}${org.name.padEnd(width)}`,
2931
+ note: org.slug ?? org.id
2932
+ }));
2933
+ }
2934
+ async function orgListReport(server) {
2935
+ const { token, base } = await requireToken(server);
2936
+ const [orgs, active] = await Promise.all([
2937
+ listOrganizations(base, token),
2938
+ activeOrganizationId(base, token)
2939
+ ]);
2940
+ return {
2941
+ title: "org",
2942
+ lines: rows(orgs, active),
2943
+ json: {
2944
+ active,
2945
+ organizations: orgs.map((o) => ({
2946
+ id: o.id,
2947
+ name: o.name,
2948
+ slug: o.slug ?? null,
2949
+ active: o.id === active
2950
+ }))
2951
+ }
2952
+ };
2953
+ }
2954
+ async function orgSwitchReport(name, server) {
2955
+ const { token, base } = await requireToken(server);
2956
+ const orgs = await listOrganizations(base, token);
2957
+ const wanted = name.toLowerCase();
2958
+ const match = orgs.find((o) => o.name.toLowerCase() === wanted || o.slug?.toLowerCase() === wanted);
2959
+ if (!match) {
2960
+ return {
2961
+ title: "org",
2962
+ lines: [
2963
+ {
2964
+ mark: "fail",
2965
+ text: `No organization called ${name}.`,
2966
+ note: orgs.map((o) => o.name).join(", ") || "you are in none"
2967
+ }
2968
+ ],
2969
+ json: { error: `unknown organization: ${name}` },
2970
+ failed: true
2971
+ };
2972
+ }
2973
+ const res = await fetch(`${authBase2(base)}/organization/set-active`, {
2974
+ method: "POST",
2975
+ headers: {
2976
+ "content-type": "application/json",
2977
+ authorization: `Bearer ${token}`
2978
+ },
2979
+ body: JSON.stringify({ organizationId: match.id })
2980
+ });
2981
+ if (!res.ok) {
2982
+ const detail = (await res.text()).slice(0, 200);
2983
+ return {
2984
+ title: "org",
2985
+ lines: [
2986
+ {
2987
+ mark: "fail",
2988
+ text: `Could not switch to ${match.name}.`,
2989
+ note: detail || `${res.status} ${res.statusText}`
2990
+ }
2991
+ ],
2992
+ json: { error: detail || res.statusText },
2993
+ failed: true
2994
+ };
2995
+ }
2996
+ return {
2997
+ title: "org",
2998
+ lines: [{ mark: "ok", text: `Active organization: ${match.name}` }],
2999
+ json: { active: match.id, name: match.name, slug: match.slug ?? null }
3000
+ };
3001
+ }
3002
+ // src/report.ts
3003
+ function plainLine(line2, glyph) {
3004
+ const head = line2.mark === "none" ? "" : `${glyph[line2.mark]} `;
3005
+ return `${head}${line2.text}${line2.note ? ` ${line2.note}` : ""}`;
3006
+ }
3007
+ // src/shell/commands.ts
3008
+ function paletteGroups() {
3009
+ return [
3010
+ {
3011
+ group: "Catalog",
3012
+ items: [
3013
+ { value: "catalog.bundles", label: "browse bundles", hint: "browse" },
3014
+ { value: "catalog.skills", label: "browse skills", hint: "list" },
3015
+ { value: "catalog.search", label: "search", hint: "search <query>" }
3016
+ ]
3017
+ },
3018
+ {
3019
+ group: "Installed",
3020
+ items: [
3021
+ { value: "installed.list", label: "list installed", hint: "installed" },
3022
+ {
3023
+ value: "installed.update",
3024
+ label: "update all",
3025
+ hint: "update --all"
3026
+ },
3027
+ { value: "installed.outdated", label: "outdated", hint: "outdated" },
3028
+ { value: "installed.remove", label: "remove", hint: "remove <skill>" }
3029
+ ]
3030
+ },
3031
+ {
3032
+ group: "Account",
3033
+ items: [
3034
+ { value: "account.login", label: "login", hint: "login" },
3035
+ { value: "account.logout", label: "logout", hint: "logout" },
3036
+ { value: "account.whoami", label: "whoami", hint: "whoami" },
3037
+ { value: "account.org", label: "switch org", hint: "org <name>" }
3038
+ ]
3039
+ },
3040
+ {
3041
+ group: "Setup",
3042
+ items: [
3043
+ {
3044
+ value: "setup.scope",
3045
+ label: "scope",
3046
+ hint: "config set scope project"
3047
+ },
3048
+ {
3049
+ value: "setup.agents",
3050
+ label: "agents",
3051
+ hint: "config set agents <name>"
3052
+ },
3053
+ { value: "setup.doctor", label: "doctor", hint: "doctor" }
3054
+ ]
3055
+ }
3056
+ ];
3057
+ }
3058
+ // src/shell/screens/home.tsx
3059
+ import { Box as Box10, Text as Text12, useApp as useApp7, useInput as useInput3 } from "ink";
3060
+ import { useCallback as useCallback3, useEffect as useEffect8, useMemo as useMemo3, useRef, useState as useState8 } from "react";
3061
+ // src/screens.tsx
3062
+ import { Box as Box9, Text as Text11, useApp as useApp6 } from "ink";
3063
+ import { useEffect as useEffect7, useState as useState7 } from "react";
3064
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
3065
+ var MARKS = {
3066
+ ok: { glyph: mark.ok, color: "green" },
3067
+ fail: { glyph: mark.fail, color: tone.danger },
3068
+ pending: { glyph: mark.pending, color: tone.notice },
3069
+ none: { glyph: "" }
3070
+ };
3071
+ function ReportRow({ line: line2, width }) {
3072
+ const { glyph, color } = MARKS[line2.mark];
3073
+ const head = line2.mark === "none" ? "" : `${glyph} `;
3074
+ const room = width - FRAME_CHROME - head.length;
3075
+ return /* @__PURE__ */ jsxs10(Text11, {
3076
+ children: [
3077
+ head ? /* @__PURE__ */ jsx11(Text11, {
3078
+ color,
3079
+ children: head
3080
+ }) : null,
3081
+ /* @__PURE__ */ jsx11(Text11, {
3082
+ children: clip(line2.text, room)
3083
+ }),
3084
+ line2.note ? /* @__PURE__ */ jsx11(Text11, {
3085
+ dimColor: true,
3086
+ children: clip(` ${line2.note}`, Math.max(room - line2.text.length, 0))
3087
+ }) : null
3088
+ ]
3089
+ });
3090
+ }
3091
+ function ReportScreen({
3092
+ title,
3093
+ load,
3094
+ onError,
3095
+ onReport,
3096
+ exitWhenDone = true
3097
+ }) {
3098
+ const { exit } = useApp6();
3099
+ const width = useFrameWidth();
3100
+ const [report, setReport] = useState7(null);
3101
+ useEffect7(() => {
3102
+ let live = true;
3103
+ load().then((result) => {
3104
+ if (!live)
3105
+ return;
3106
+ setReport(result);
3107
+ onReport?.(result);
3108
+ }).catch((error) => {
3109
+ if (live)
3110
+ onError(error);
3111
+ });
3112
+ return () => {
3113
+ live = false;
3114
+ };
3115
+ }, [load, onError, onReport]);
3116
+ useEffect7(() => {
3117
+ if (report && exitWhenDone)
3118
+ exit();
3119
+ }, [report, exit, exitWhenDone]);
3120
+ return /* @__PURE__ */ jsx11(Frame, {
3121
+ title,
3122
+ width,
3123
+ children: report ? /* @__PURE__ */ jsx11(Box9, {
3124
+ flexDirection: "column",
3125
+ children: report.lines.map((line2) => /* @__PURE__ */ jsx11(ReportRow, {
3126
+ line: line2,
3127
+ width
3128
+ }, `${line2.mark}:${line2.text}`))
3129
+ }) : /* @__PURE__ */ jsxs10(Text11, {
3130
+ children: [
3131
+ /* @__PURE__ */ jsx11(Spinner, {}),
3132
+ /* @__PURE__ */ jsx11(Text11, {
3133
+ dimColor: true,
3134
+ children: " Working..."
3135
+ })
3136
+ ]
3137
+ })
3138
+ });
3139
+ }
3140
+
3141
+ // src/shell/screens/home.tsx
3142
+ import { jsx as jsx12, jsxs as jsxs11 } from "react/jsx-runtime";
3143
+ function failure(title, error) {
3144
+ const message = error instanceof Error ? error.message : String(error);
3145
+ return {
3146
+ title,
3147
+ lines: [{ mark: "fail", text: message }],
3148
+ json: { error: message }
3149
+ };
3150
+ }
3151
+ function identityReport(who) {
3152
+ const label = (text) => text.padEnd(7);
3153
+ return {
3154
+ title: "whoami",
3155
+ lines: [
3156
+ { mark: "none", text: `${label("user")}${who.user}` },
3157
+ {
3158
+ mark: "none",
3159
+ text: `${label("org")}${who.org ?? "none — set an active organization in the web UI"}`
3160
+ },
3161
+ { mark: "none", text: `${label("server")}${who.server}` }
3162
+ ],
3163
+ json: { ...who }
3164
+ };
3165
+ }
3166
+ function HomeScreen({
3167
+ opts,
3168
+ server,
3169
+ openClient,
3170
+ onError,
3171
+ onSummary
3172
+ }) {
3173
+ const { exit } = useApp7();
3174
+ const width = useFrameWidth();
3175
+ const groups = useMemo3(() => paletteGroups(), []);
3176
+ const [query, setQuery] = useState8("");
3177
+ const [cursor, setCursor] = useState8(0);
3178
+ const [route, setRoute] = useState8(null);
3179
+ const [client, setClient] = useState8(null);
3180
+ const [refused, setRefused] = useState8(null);
3181
+ const rows2 = useMemo3(() => flattenCommands(filterCommands(groups, query)), [groups, query]);
3182
+ useEffect8(() => {
3183
+ setCursor((c) => Math.max(0, Math.min(c, rows2.length - 1)));
3184
+ }, [rows2.length]);
3185
+ const held = useRef(null);
3186
+ const connect = useCallback3(async () => {
3187
+ held.current ??= await openClient();
3188
+ return held.current;
3189
+ }, [openClient]);
3190
+ const answering = useCallback3((title, load) => ({
3191
+ kind: "report",
3192
+ title,
3193
+ load: async () => {
3194
+ try {
3195
+ return await load();
3196
+ } catch (error) {
3197
+ return failure(title, error);
3198
+ }
3199
+ }
3200
+ }), []);
3201
+ const routeFor = useCallback3((value) => {
3202
+ switch (value) {
3203
+ case "catalog.bundles":
3204
+ case "catalog.skills":
3205
+ case "catalog.search":
3206
+ return { kind: "browse" };
3207
+ case "installed.list":
3208
+ return answering("installed", async () => installedReport(await nearestManifest(opts.scope)));
3209
+ case "installed.update":
3210
+ return {
3211
+ kind: "install",
3212
+ title: "update",
3213
+ stream: async function* stream() {
3214
+ const nearest = await nearestManifest(opts.scope);
3215
+ yield* updateTracked(await connect(), nearest.manifest, opts, true);
3216
+ }
3217
+ };
3218
+ case "installed.outdated":
3219
+ return answering("outdated", async () => outdatedReport(await connect(), await nearestManifest(opts.scope)));
3220
+ case "installed.remove":
3221
+ return answering("remove", async () => {
3222
+ const report = installedReport(await nearestManifest(opts.scope));
3223
+ return {
3224
+ ...report,
3225
+ title: "remove",
3226
+ lines: [
3227
+ {
3228
+ mark: "none",
3229
+ text: "Run `yourskills remove <skill>` to delete one."
3230
+ },
3231
+ ...report.lines
3232
+ ]
3233
+ };
3234
+ });
3235
+ case "account.login":
3236
+ return { kind: "login" };
3237
+ case "account.logout":
3238
+ return answering("logout", async () => {
3239
+ await logout();
3240
+ held.current = null;
3241
+ setClient(null);
3242
+ return {
3243
+ title: "logout",
3244
+ lines: [{ mark: "ok", text: "Logged out." }],
3245
+ json: { ok: true }
3246
+ };
3247
+ });
3248
+ case "account.whoami":
3249
+ return answering("whoami", async () => identityReport(await whoami(server)));
3250
+ case "account.org":
3251
+ return answering("org", () => orgListReport(server));
3252
+ case "setup.scope":
3253
+ return answering("scope", () => configGet("scope"));
3254
+ case "setup.agents":
3255
+ return answering("agents", () => configGet("agents"));
3256
+ case "setup.doctor":
3257
+ return answering("doctor", async () => doctorReport(await nearestManifest(opts.scope), server));
3258
+ }
3259
+ }, [answering, connect, opts, server]);
3260
+ const activate = useCallback3(() => {
3261
+ const item = rows2[cursor];
3262
+ if (!item)
3263
+ return;
3264
+ item.onSelect?.();
3265
+ setRoute(routeFor(item.value));
3266
+ }, [rows2, cursor, routeFor]);
3267
+ const back = useCallback3(() => {
3268
+ setRoute(null);
3269
+ setRefused(null);
3270
+ }, []);
3271
+ useEffect8(() => {
3272
+ if (route?.kind !== "browse" || client || refused)
3273
+ return;
3274
+ let live = true;
3275
+ connect().then((opened) => {
3276
+ if (live)
3277
+ setClient(opened);
3278
+ }).catch((error) => {
3279
+ if (live)
3280
+ setRefused(error instanceof Error ? error.message : String(error));
3281
+ });
3282
+ return () => {
3283
+ live = false;
3284
+ };
3285
+ }, [route, client, refused, connect]);
3286
+ const browsing = route?.kind === "browse" && client !== null;
3287
+ useInput3((input, key) => {
3288
+ if (key.upArrow) {
3289
+ setCursor((c) => Math.max(0, c - 1));
3290
+ } else if (key.downArrow) {
3291
+ setCursor((c) => Math.min(rows2.length - 1, c + 1));
3292
+ } else if (key.return) {
3293
+ activate();
3294
+ } else if (key.escape) {
3295
+ if (query.length > 0)
3296
+ setQuery("");
3297
+ else
3298
+ exit();
3299
+ } else if (key.backspace || key.delete) {
3300
+ setQuery((q) => q.slice(0, -1));
3301
+ setCursor(0);
3302
+ } else if (input && !key.ctrl && !key.meta) {
3303
+ setQuery((q) => q + input);
3304
+ setCursor(0);
3305
+ }
3306
+ }, { isActive: route === null });
3307
+ useInput3((_input, key) => {
3308
+ if (key.escape)
3309
+ back();
3310
+ }, { isActive: route !== null && !browsing });
3311
+ if (route?.kind === "login") {
3312
+ return /* @__PURE__ */ jsx12(LoginScreen, {
3313
+ onError,
3314
+ onDone: back
3315
+ });
3316
+ }
3317
+ if (route?.kind === "report") {
3318
+ return /* @__PURE__ */ jsx12(ReportScreen, {
3319
+ title: route.title,
3320
+ load: route.load,
3321
+ onError,
3322
+ exitWhenDone: false
3323
+ });
3324
+ }
3325
+ if (route?.kind === "install") {
3326
+ return /* @__PURE__ */ jsx12(Installer, {
3327
+ title: route.title,
3328
+ stream: route.stream,
3329
+ exitWhenDone: false,
3330
+ onDone: onSummary
3331
+ });
3332
+ }
3333
+ if (route?.kind === "browse") {
3334
+ if (refused) {
3335
+ return /* @__PURE__ */ jsx12(Frame, {
3336
+ title: "browse",
3337
+ width,
3338
+ children: /* @__PURE__ */ jsxs11(Box10, {
3339
+ flexDirection: "column",
3340
+ children: [
3341
+ /* @__PURE__ */ jsxs11(Text12, {
3342
+ children: [
3343
+ /* @__PURE__ */ jsx12(Text12, {
3344
+ color: tone.danger,
3345
+ children: mark.fail
3346
+ }),
3347
+ /* @__PURE__ */ jsx12(Text12, {
3348
+ children: ` ${refused}`
3349
+ })
3350
+ ]
3351
+ }),
3352
+ /* @__PURE__ */ jsx12(Text12, {
3353
+ dimColor: true,
3354
+ children: "Pick `login` from the palette, then try again."
3355
+ })
3356
+ ]
3357
+ })
3358
+ });
3359
+ }
3360
+ if (!client) {
3361
+ return /* @__PURE__ */ jsx12(Frame, {
3362
+ title: "browse",
3363
+ width,
3364
+ children: /* @__PURE__ */ jsxs11(Text12, {
3365
+ children: [
3366
+ /* @__PURE__ */ jsx12(Spinner, {}),
3367
+ /* @__PURE__ */ jsx12(Text12, {
3368
+ dimColor: true,
3369
+ children: " Connecting..."
3370
+ })
3371
+ ]
3372
+ })
3373
+ });
3374
+ }
3375
+ return /* @__PURE__ */ jsx12(BrowserScreen, {
3376
+ client,
3377
+ opts,
3378
+ onError,
3379
+ onSummary,
3380
+ onExit: back
3381
+ });
3382
+ }
3383
+ return /* @__PURE__ */ jsxs11(Frame, {
3384
+ title: "yourskills",
3385
+ width,
3386
+ children: [
3387
+ /* @__PURE__ */ jsx12(CommandPalette, {
3388
+ groups,
3389
+ query,
3390
+ cursor,
3391
+ width
3392
+ }),
3393
+ /* @__PURE__ */ jsx12(Divider, {
3394
+ width
3395
+ }),
3396
+ /* @__PURE__ */ jsx12(Hints, {
3397
+ hints: [
3398
+ ["^v", "move"],
3399
+ ["enter", "open"],
3400
+ ["esc", query ? "clear" : "quit"]
3401
+ ]
3402
+ })
3403
+ ]
3404
+ });
3405
+ }
3406
+ // src/args.ts
3407
+ var HELP = `yourskills — install skills from your organization's registry
3408
+
3409
+ Usage: yourskills <command> [options]
3410
+
3411
+ Commands:
3412
+ (none) Open the command palette (every command, one keystroke away)
3413
+ browse Browse and install interactively
3414
+ login Device-flow login (RFC 8628)
3415
+ logout Forget the stored session token
3416
+ whoami Show the current user and active organization
3417
+ org List your organizations, marking the active one
3418
+ org <name> Switch the active organization
3419
+ list List every skill in your organization
3420
+ search <query> Search skills by name and description
3421
+ bundle <name> Install a role bundle (the default install path)
3422
+ add <skill...> Install individual skills
3423
+ install <skill...> The same thing, spelled the other way
3424
+ install Re-install everything the nearest manifest records
3425
+ remove <skill...> Delete skills from disk and from the manifest
3426
+ update <bundle> Re-install a bundle at its currently pinned refs
3427
+ update The same, for every bundle the manifest tracks
3428
+ installed List what the nearest manifest records
3429
+ outdated Report skills whose registry pin has moved
3430
+ doctor Check the server, the session, the agents, the manifest
3431
+ config get [key] Show the stored settings, or one of them
3432
+ config set <key> <val> Set one: scope, agents, server, track
3433
+ hooks status Show which agents carry our hooks, and where
3434
+ hooks install Register them: an update check, and usage if opted in
3435
+ hooks uninstall Remove ours, leaving every other hook alone
3436
+
3437
+ Options:
3438
+ -g, --global Install for the user (the default)
3439
+ -p, --project Install into this project, recorded in yourskills.json
3440
+ -a, --agent <name> Target agent (repeatable); omit to auto-detect
3441
+ --all update: refresh loose skills too, not only bundles
3442
+ -q, --quiet outdated: print nothing when everything is current
3443
+ --track hooks install: opt in to counting skill invocations
3444
+ --no-track hooks install: opt out again
3445
+ --server <url> Registry base URL for this invocation
3446
+ --plain Plain, greppable output; no frames, no colour
3447
+ --json Machine-readable output
3448
+ -v, --version Show the version
3449
+ -h, --help Show this help
3450
+
3451
+ Environment:
3452
+ YOURSKILLS_URL Registry base URL (default localhost:3000)
3453
+ YOURSKILLS_NO_TRACK Set to 1 to disable usage counting outright`;
3454
+ function configCommand(rest) {
3455
+ const [action, key, ...value] = rest;
3456
+ if (action === undefined || action === "get") {
3457
+ return { kind: "config", action: "get", ...key ? { key } : {} };
3458
+ }
3459
+ if (action !== "set") {
3460
+ return {
3461
+ kind: "error",
3462
+ message: `config takes get or set, not ${action}: yourskills config get [key]`
3463
+ };
3464
+ }
3465
+ if (!key) {
3466
+ return {
3467
+ kind: "error",
3468
+ message: "config set needs a key: yourskills config set <key> <value>"
3469
+ };
3470
+ }
3471
+ if (value.length === 0) {
3472
+ return {
3473
+ kind: "error",
3474
+ message: `config set needs a value: yourskills config set ${key} <value>`
3475
+ };
3476
+ }
3477
+ return { kind: "config", action: "set", key, value: value.join(" ") };
3478
+ }
3479
+ function hooksCommand(rest, agent, track) {
3480
+ const action = rest[0] ?? "status";
3481
+ if (action !== "install" && action !== "uninstall" && action !== "status") {
3482
+ return {
3483
+ kind: "error",
3484
+ message: `hooks takes install, uninstall or status, not ${action}`
3485
+ };
3486
+ }
3487
+ return {
3488
+ kind: "hooks",
3489
+ action,
3490
+ ...agent ? { agent } : {},
3491
+ ...track === undefined ? {} : { track }
3492
+ };
3493
+ }
3494
+ function parseArgv(args, interactive = true) {
3495
+ const opts = {};
3496
+ const positional = [];
3497
+ let plain = !interactive;
3498
+ let json = false;
3499
+ let all = false;
3500
+ let quiet = false;
3501
+ let server;
3502
+ let track;
3503
+ const done = (command2) => ({
3504
+ command: command2,
3505
+ output: { plain, json },
3506
+ ...server ? { server } : {}
3507
+ });
3508
+ for (let i = 0;i < args.length; i++) {
3509
+ const arg = args[i];
3510
+ if (arg === "-h" || arg === "--help")
3511
+ return done({ kind: "help" });
3512
+ if (arg === "-v" || arg === "--version")
3513
+ return done({ kind: "version" });
3514
+ if (arg === "--plain") {
3515
+ plain = true;
3516
+ } else if (arg === "--json") {
3517
+ json = true;
3518
+ plain = true;
3519
+ } else if (arg === "-g" || arg === "--global") {
3520
+ opts.scope = "global";
3521
+ } else if (arg === "-p" || arg === "--project") {
3522
+ opts.scope = "project";
3523
+ } else if (arg === "--all") {
3524
+ all = true;
3525
+ } else if (arg === "-q" || arg === "--quiet") {
3526
+ quiet = true;
3527
+ } else if (arg === "--track") {
3528
+ track = true;
3529
+ } else if (arg === "--no-track") {
3530
+ track = false;
3531
+ } else if (arg === "--server") {
3532
+ const value = args[++i];
3533
+ if (!value)
3534
+ return done({ kind: "error", message: "--server requires a URL" });
3535
+ server = value;
3536
+ } else if (arg === "-a" || arg === "--agent") {
3537
+ const value = args[++i];
3538
+ if (!value)
3539
+ return done({
3540
+ kind: "error",
3541
+ message: "--agent requires an agent name"
3542
+ });
3543
+ opts.agents = [...opts.agents ?? [], value];
3544
+ } else if (arg.startsWith("-")) {
3545
+ return done({ kind: "error", message: `Unknown option: ${arg}` });
3546
+ } else {
3547
+ positional.push(arg);
3548
+ }
3549
+ }
3550
+ const [command, ...rest] = positional;
3551
+ switch (command) {
3552
+ case undefined:
3553
+ return done(plain ? { kind: "help" } : { kind: "home" });
3554
+ case "help":
3555
+ return done({ kind: "help" });
3556
+ case "version":
3557
+ return done({ kind: "version" });
3558
+ case "browse":
3559
+ return done(plain ? {
3560
+ kind: "error",
3561
+ message: "browse needs a terminal; use `list` or `search` instead"
3562
+ } : { kind: "browse" });
3563
+ case "login":
3564
+ return done({ kind: "login" });
3565
+ case "logout":
3566
+ return done({ kind: "logout" });
3567
+ case "whoami":
3568
+ return done({ kind: "whoami" });
3569
+ case "list":
3570
+ return done({ kind: "list" });
3571
+ case "search":
3572
+ return done(rest.length > 0 ? { kind: "search", query: rest.join(" ") } : {
3573
+ kind: "error",
3574
+ message: "search needs a query: yourskills search <query>"
3575
+ });
3576
+ case "add":
3577
+ return done(rest.length > 0 ? { kind: "add", names: rest, opts } : {
3578
+ kind: "error",
3579
+ message: "add needs at least one skill: yourskills add <skill...>"
3580
+ });
3581
+ case "install":
3582
+ return done(rest.length > 0 ? { kind: "add", names: rest, opts } : { kind: "install", opts });
3583
+ case "remove":
3584
+ return done(rest.length > 0 ? { kind: "remove", names: rest, opts } : {
3585
+ kind: "error",
3586
+ message: "remove needs at least one skill: yourskills remove <skill...>"
3587
+ });
3588
+ case "installed":
3589
+ return done({ kind: "installed" });
3590
+ case "outdated":
3591
+ return done({ kind: "outdated", ...quiet ? { quiet: true } : {} });
3592
+ case "doctor":
3593
+ return done({ kind: "doctor" });
3594
+ case "org":
3595
+ return done(rest[0] ? { kind: "org", name: rest[0] } : { kind: "org" });
3596
+ case "config":
3597
+ return done(configCommand(rest));
3598
+ case "hooks":
3599
+ return done(hooksCommand(rest, opts.agents?.[0], track));
3600
+ case "hook": {
3601
+ const event = rest[0];
3602
+ return done(event && isHookEventName(event) ? { kind: "hook", event } : {
3603
+ kind: "error",
3604
+ message: "hook takes session-start or post-tool-use"
3605
+ });
3606
+ }
3607
+ case "bundle":
3608
+ return done(rest[0] ? { kind: "bundle", name: rest[0], opts } : {
3609
+ kind: "error",
3610
+ message: "bundle needs a name: yourskills bundle <name>"
3611
+ });
3612
+ case "update":
3613
+ return done({
3614
+ kind: "update",
3615
+ ...rest[0] ? { bundle: rest[0] } : {},
3616
+ ...all ? { all: true } : {},
3617
+ opts
3618
+ });
3619
+ default:
3620
+ return done({ kind: "error", message: `Unknown command: ${command}` });
3621
+ }
3622
+ }
3623
+
3624
+ // src/client.ts
3625
+ import { createTRPCClient as createTRPCClient2, httpBatchLink as httpBatchLink2 } from "@trpc/client";
3626
+ async function createClient2(server) {
3627
+ const { token, base } = await requireToken(server);
3628
+ return createTRPCClient2({
3629
+ links: [
3630
+ httpBatchLink2({
3631
+ url: `${base.replace(/\/$/, "")}/trpc`,
3632
+ headers: () => ({ authorization: `Bearer ${token}` })
3633
+ })
3634
+ ]
3635
+ });
3636
+ }
3637
+
3638
+ // src/screens.tsx
3639
+ import { Box as Box11, Text as Text13, useApp as useApp8 } from "ink";
3640
+ import { useEffect as useEffect9, useState as useState9 } from "react";
3641
+ import { jsx as jsx13, jsxs as jsxs12 } from "react/jsx-runtime";
3642
+ var HELP_COLUMN = 26;
3643
+ function WhoamiScreen({
3644
+ load,
3645
+ onError
3646
+ }) {
3647
+ const { exit } = useApp8();
3648
+ const width = useFrameWidth();
3649
+ const [who, setWho] = useState9(null);
3650
+ useEffect9(() => {
3651
+ let live = true;
3652
+ load().then((result) => {
3653
+ if (!live)
3654
+ return;
3655
+ setWho(result);
3656
+ exit();
3657
+ }).catch((error) => {
3658
+ if (live)
3659
+ onError(error);
3660
+ });
3661
+ return () => {
3662
+ live = false;
3663
+ };
3664
+ }, [load, exit, onError]);
3665
+ if (!who) {
3666
+ return /* @__PURE__ */ jsx13(Frame, {
3667
+ title: "whoami",
3668
+ width,
3669
+ children: /* @__PURE__ */ jsxs12(Text13, {
3670
+ children: [
3671
+ /* @__PURE__ */ jsx13(Spinner, {}),
3672
+ /* @__PURE__ */ jsx13(Text13, {
3673
+ dimColor: true,
3674
+ children: " Checking session..."
3675
+ })
3676
+ ]
3677
+ })
3678
+ });
3679
+ }
3680
+ return /* @__PURE__ */ jsx13(Frame, {
3681
+ title: "whoami",
3682
+ width,
3683
+ children: /* @__PURE__ */ jsxs12(Box11, {
3684
+ flexDirection: "column",
3685
+ children: [
3686
+ /* @__PURE__ */ jsxs12(Text13, {
3687
+ children: [
3688
+ /* @__PURE__ */ jsx13(Text13, {
3689
+ dimColor: true,
3690
+ children: "user "
3691
+ }),
3692
+ /* @__PURE__ */ jsx13(Text13, {
3693
+ children: who.user
3694
+ })
3695
+ ]
3696
+ }),
3697
+ /* @__PURE__ */ jsxs12(Text13, {
3698
+ children: [
3699
+ /* @__PURE__ */ jsx13(Text13, {
3700
+ dimColor: true,
3701
+ children: "org "
3702
+ }),
3703
+ who.org ? /* @__PURE__ */ jsx13(Text13, {
3704
+ children: who.org
3705
+ }) : /* @__PURE__ */ jsx13(Text13, {
3706
+ color: tone.notice,
3707
+ children: "none — set an active organization in the web UI"
3708
+ })
3709
+ ]
3710
+ }),
3711
+ /* @__PURE__ */ jsxs12(Text13, {
3712
+ children: [
3713
+ /* @__PURE__ */ jsx13(Text13, {
3714
+ dimColor: true,
3715
+ children: "server "
3716
+ }),
3717
+ /* @__PURE__ */ jsx13(Text13, {
3718
+ color: tone.primary,
3719
+ children: who.server
3720
+ })
3721
+ ]
3722
+ })
3723
+ ]
3724
+ })
3725
+ });
3726
+ }
3727
+ function NoticeScreen({
3728
+ title,
3729
+ message
3730
+ }) {
3731
+ const { exit } = useApp8();
3732
+ const width = useFrameWidth();
3733
+ useEffect9(exit, [exit]);
3734
+ return /* @__PURE__ */ jsx13(Frame, {
3735
+ title,
3736
+ width,
3737
+ children: /* @__PURE__ */ jsxs12(Text13, {
3738
+ children: [
3739
+ /* @__PURE__ */ jsx13(Text13, {
3740
+ color: "green",
3741
+ children: mark.ok
3742
+ }),
3743
+ /* @__PURE__ */ jsx13(Text13, {
3744
+ children: ` ${message}`
3745
+ })
3746
+ ]
3747
+ })
3748
+ });
3749
+ }
3750
+ var MARKS2 = {
3751
+ ok: { glyph: mark.ok, color: "green" },
3752
+ fail: { glyph: mark.fail, color: tone.danger },
3753
+ pending: { glyph: mark.pending, color: tone.notice },
3754
+ none: { glyph: "" }
3755
+ };
3756
+ function ReportRow2({ line: line2, width }) {
3757
+ const { glyph, color } = MARKS2[line2.mark];
3758
+ const head = line2.mark === "none" ? "" : `${glyph} `;
3759
+ const room = width - FRAME_CHROME - head.length;
3760
+ return /* @__PURE__ */ jsxs12(Text13, {
3761
+ children: [
3762
+ head ? /* @__PURE__ */ jsx13(Text13, {
3763
+ color,
3764
+ children: head
3765
+ }) : null,
3766
+ /* @__PURE__ */ jsx13(Text13, {
3767
+ children: clip(line2.text, room)
3768
+ }),
3769
+ line2.note ? /* @__PURE__ */ jsx13(Text13, {
3770
+ dimColor: true,
3771
+ children: clip(` ${line2.note}`, Math.max(room - line2.text.length, 0))
3772
+ }) : null
3773
+ ]
3774
+ });
3775
+ }
3776
+ function ReportScreen2({
3777
+ title,
3778
+ load,
3779
+ onError,
3780
+ onReport,
3781
+ exitWhenDone = true
3782
+ }) {
3783
+ const { exit } = useApp8();
3784
+ const width = useFrameWidth();
3785
+ const [report, setReport] = useState9(null);
3786
+ useEffect9(() => {
3787
+ let live = true;
3788
+ load().then((result) => {
3789
+ if (!live)
3790
+ return;
3791
+ setReport(result);
3792
+ onReport?.(result);
3793
+ }).catch((error) => {
3794
+ if (live)
3795
+ onError(error);
3796
+ });
3797
+ return () => {
3798
+ live = false;
3799
+ };
3800
+ }, [load, onError, onReport]);
3801
+ useEffect9(() => {
3802
+ if (report && exitWhenDone)
3803
+ exit();
3804
+ }, [report, exit, exitWhenDone]);
3805
+ return /* @__PURE__ */ jsx13(Frame, {
3806
+ title,
3807
+ width,
3808
+ children: report ? /* @__PURE__ */ jsx13(Box11, {
3809
+ flexDirection: "column",
3810
+ children: report.lines.map((line2) => /* @__PURE__ */ jsx13(ReportRow2, {
3811
+ line: line2,
3812
+ width
3813
+ }, `${line2.mark}:${line2.text}`))
3814
+ }) : /* @__PURE__ */ jsxs12(Text13, {
3815
+ children: [
3816
+ /* @__PURE__ */ jsx13(Spinner, {}),
3817
+ /* @__PURE__ */ jsx13(Text13, {
3818
+ dimColor: true,
3819
+ children: " Working..."
3820
+ })
3821
+ ]
3822
+ })
3823
+ });
3824
+ }
3825
+ function numberedLines(text) {
3826
+ return text.split(`
3827
+ `).map((line2, i) => ({ id: `${i}:${line2}`, line: line2 }));
3828
+ }
3829
+ function HelpScreen({ text }) {
3830
+ const { exit } = useApp8();
3831
+ const width = useFrameWidth();
3832
+ useEffect9(exit, [exit]);
3833
+ return /* @__PURE__ */ jsx13(Frame, {
3834
+ title: "yourskills",
3835
+ width,
3836
+ children: /* @__PURE__ */ jsx13(Box11, {
3837
+ flexDirection: "column",
3838
+ children: numberedLines(text).map(({ id, line: line2 }) => {
3839
+ if (line2.length === 0)
3840
+ return /* @__PURE__ */ jsx13(Blank, {}, id);
3841
+ if (!line2.startsWith(" "))
3842
+ return /* @__PURE__ */ jsx13(Text13, {
3843
+ children: clip(line2, width - FRAME_CHROME)
3844
+ }, id);
3845
+ return /* @__PURE__ */ jsxs12(Text13, {
3846
+ children: [
3847
+ /* @__PURE__ */ jsx13(Text13, {
3848
+ color: tone.primary,
3849
+ children: line2.slice(0, HELP_COLUMN)
3850
+ }),
3851
+ /* @__PURE__ */ jsx13(Text13, {
3852
+ dimColor: true,
3853
+ children: clip(line2.slice(HELP_COLUMN), width - FRAME_CHROME - HELP_COLUMN)
3854
+ })
3855
+ ]
3856
+ }, line2);
3857
+ })
3858
+ })
3859
+ });
3860
+ }
3861
+ // package.json
3862
+ var package_default = {
3863
+ name: "yourskills",
3864
+ version: "0.1.0",
3865
+ description: "Browse, install and keep up to date the agent skills and bundles your team publishes on yourskills.",
3866
+ license: "MIT",
3867
+ homepage: "https://github.com/xiduzo/yourskills/tree/main/apps/cli#readme",
3868
+ repository: {
3869
+ type: "git",
3870
+ url: "git+https://github.com/xiduzo/yourskills.git",
3871
+ directory: "apps/cli"
3872
+ },
3873
+ keywords: [
3874
+ "agent-skills",
3875
+ "skills",
3876
+ "cli",
3877
+ "claude-code",
3878
+ "codex",
3879
+ "cursor",
3880
+ "opencode",
3881
+ "ai"
3882
+ ],
3883
+ type: "module",
3884
+ bin: {
3885
+ yourskills: "dist/main.js"
3886
+ },
3887
+ files: [
3888
+ "dist"
3889
+ ],
3890
+ engines: {
3891
+ node: ">=22.20.0"
3892
+ },
3893
+ publishConfig: {
3894
+ access: "public"
3895
+ },
3896
+ scripts: {
3897
+ "check-types": "tsc --noEmit",
3898
+ build: "bun run build.ts",
3899
+ "build:binary": "bun run build.ts --binary",
3900
+ prepublishOnly: "bun run build"
3901
+ },
3902
+ dependencies: {
3903
+ "@trpc/client": "^11.18.0",
3904
+ "@trpc/server": "^11.18.0",
3905
+ ink: "^7.1.1",
3906
+ react: "^19.2.8",
3907
+ skills: "^1.5.25"
3908
+ },
3909
+ devDependencies: {
3910
+ "@types/bun": "catalog:",
3911
+ "@types/react": "catalog:",
3912
+ "@yourskills/api": "workspace:*",
3913
+ "@yourskills/bundles": "workspace:*",
3914
+ "@yourskills/config": "workspace:*",
3915
+ "@yourskills/ui": "workspace:*",
3916
+ "ink-testing-library": "^4.0.0",
3917
+ typescript: "catalog:"
3918
+ }
3919
+ };
3920
+
3921
+ // src/version.ts
3922
+ var VERSION = package_default.version ?? "0.0.0-dev";
3923
+
3924
+ // src/main.tsx
3925
+ import { jsx as jsx14 } from "react/jsx-runtime";
3926
+ function applySummary(summary) {
3927
+ if (summary.pending.length > 0 || summary.failed > 0)
3928
+ process.exitCode = 1;
3929
+ }
3930
+ var PLAIN_MARKS = {
3931
+ ok: mark.ok,
3932
+ fail: mark.fail,
3933
+ pending: mark.pending,
3934
+ none: ""
3935
+ };
3936
+ function emit(report, output) {
3937
+ if (report.failed)
3938
+ process.exitCode = 1;
3939
+ if (output.json) {
3940
+ console.log(JSON.stringify(report.json));
3941
+ return;
3942
+ }
3943
+ if (report.silent)
3944
+ return;
3945
+ for (const line2 of report.lines)
3946
+ console.log(plainLine(line2, PLAIN_MARKS));
3947
+ }
3948
+ function toRows(skills) {
3949
+ return skills.map((s) => ({
3950
+ name: s.name,
3951
+ description: s.description,
3952
+ status: s.status
3953
+ }));
3954
+ }
3955
+ function catalogReport(title, skills) {
3956
+ if (skills.length === 0) {
3957
+ return {
3958
+ title,
3959
+ lines: [{ mark: "none", text: "No skills found." }],
3960
+ json: { skills: [] }
3961
+ };
3962
+ }
3963
+ const width = Math.max(...skills.map((s) => s.name.length));
3964
+ return {
3965
+ title,
3966
+ lines: skills.map((skill) => {
3967
+ const tag = lifecycleTag(skill.status);
3968
+ return {
3969
+ mark: "none",
3970
+ text: `${skill.name.padEnd(width)}${tag ? ` ${tag}` : ""} ${skill.description ?? ""}`
3971
+ };
3972
+ }),
3973
+ json: { skills }
3974
+ };
3975
+ }
3976
+ async function resolveInstallOptions(opts) {
3977
+ const { scope, manifestPath } = await resolveScope({ flag: opts.scope });
3978
+ const path = scope === "project" && manifestPath ? manifestPath : manifestPathFor(scope);
3979
+ return {
3980
+ scope,
3981
+ agents: await resolveAgents(opts.agents),
3982
+ manifest: { path, scope }
3983
+ };
3984
+ }
3985
+ var INVITES_HOOKS = new Set([
3986
+ "login",
3987
+ "add",
3988
+ "bundle",
3989
+ "install",
3990
+ "update",
3991
+ "remove",
3992
+ "browse",
3993
+ "home"
3994
+ ]);
3995
+ async function prepare(command) {
3996
+ switch (command.kind) {
3997
+ case "add":
3998
+ case "bundle":
3999
+ case "update":
4000
+ case "install":
4001
+ case "remove":
4002
+ return { ...command, opts: await resolveInstallOptions(command.opts) };
4003
+ default:
4004
+ return command;
4005
+ }
4006
+ }
4007
+ function nearestFor(opts) {
4008
+ return nearestManifest(opts.scope);
4009
+ }
4010
+ function nothingTracked(nearest, kind, all = false) {
4011
+ const has = kind === "install" ? Object.keys(nearest.manifest.skills).length > 0 : Object.keys(nearest.manifest.bundles).length > 0 || all && looseSkills(nearest.manifest).length > 0;
4012
+ if (has)
4013
+ return null;
4014
+ return {
4015
+ title: kind,
4016
+ lines: [
4017
+ {
4018
+ mark: "none",
4019
+ text: kind === "install" ? "Nothing recorded to install. Run `yourskills bundle <name>` first." : "No bundles tracked here. Run `yourskills bundle <name>` first.",
4020
+ note: nearest.path
4021
+ }
4022
+ ],
4023
+ json: { manifest: nearest.path, skills: [], bundles: [] }
4024
+ };
4025
+ }
4026
+ async function runPlain(command, output, server) {
4027
+ switch (command.kind) {
4028
+ case "help":
4029
+ console.log(HELP);
4030
+ return;
4031
+ case "version":
4032
+ console.log(VERSION);
4033
+ return;
4034
+ case "home":
4035
+ case "browse":
4036
+ case "error":
4037
+ throw new Error(command.kind === "error" ? command.message : `${command.kind === "home" ? "the palette" : "browse"} needs a terminal`);
4038
+ case "login":
4039
+ throw new Error("login needs a terminal. Sign in on a machine that has one; the token is then copied from ~/.config/yourskills/config.json.");
4040
+ case "logout":
4041
+ await logout();
4042
+ console.log("Logged out.");
4043
+ return;
4044
+ case "whoami": {
4045
+ const who = await whoami(server);
4046
+ console.log(`${who.user} (${who.server})`);
4047
+ console.log(`org: ${who.org ?? "none \u2014 set an active organization in the web UI"}`);
4048
+ return;
4049
+ }
4050
+ case "list":
4051
+ case "search": {
4052
+ const client = await createClient2(server);
4053
+ const skills = command.kind === "list" ? await client.skills.list.query({}) : await client.skills.search.query({ q: command.query });
4054
+ emit(catalogReport(command.kind === "list" ? "skills" : `search: ${command.query}`, skills), output);
4055
+ return;
4056
+ }
4057
+ case "add":
4058
+ applySummary(await installPlain(install(await createClient2(server), command.names, command.opts)));
4059
+ return;
4060
+ case "bundle":
4061
+ applySummary(await installPlain(installBundle(await createClient2(server), command.name, command.opts)));
4062
+ return;
4063
+ case "install": {
4064
+ const nearest = await nearestFor(command.opts);
4065
+ const empty = nothingTracked(nearest, "install");
4066
+ if (empty) {
4067
+ emit(empty, output);
4068
+ return;
4069
+ }
4070
+ applySummary(await installPlain(reinstall(nearest.manifest, command.opts)));
4071
+ return;
4072
+ }
4073
+ case "update": {
4074
+ if (command.bundle) {
4075
+ applySummary(await installPlain(update(await createClient2(server), command.bundle, command.opts)));
4076
+ return;
4077
+ }
4078
+ const nearest = await nearestFor(command.opts);
4079
+ const empty = nothingTracked(nearest, "update", command.all ?? false);
4080
+ if (empty) {
4081
+ emit(empty, output);
4082
+ return;
4083
+ }
4084
+ applySummary(await installPlain(updateTracked(await createClient2(server), nearest.manifest, command.opts, command.all ?? false)));
4085
+ return;
4086
+ }
4087
+ case "remove": {
4088
+ const nearest = await nearestFor(command.opts);
4089
+ emit(await removeSkills(command.names, nearest.manifest, nearest.path, command.opts), output);
4090
+ return;
4091
+ }
4092
+ case "installed":
4093
+ emit(installedReport(await nearestManifest()), output);
4094
+ return;
4095
+ case "outdated":
4096
+ emit(await outdatedReport(await createClient2(server), await nearestManifest(), command.quiet ?? false), output);
4097
+ return;
4098
+ case "doctor":
4099
+ emit(await doctorReport(await nearestManifest(), server), output);
4100
+ return;
4101
+ case "org":
4102
+ emit(command.name ? await orgSwitchReport(command.name, server) : await orgListReport(server), output);
4103
+ return;
4104
+ case "config":
4105
+ emit(command.action === "get" ? await configGet(command.key) : await configSet(command.key, command.value), output);
4106
+ return;
4107
+ case "hooks":
4108
+ emit(await hooksReport(command), output);
4109
+ return;
4110
+ case "hook":
4111
+ await runHook(command.event);
4112
+ return;
4113
+ }
4114
+ }
4115
+ function hooksReport(command) {
4116
+ if (command.action === "install")
4117
+ return hooksInstall({
4118
+ ...command.agent ? { agent: command.agent } : {},
4119
+ ...command.track === undefined ? {} : { track: command.track }
4120
+ });
4121
+ if (command.action === "uninstall")
4122
+ return hooksUninstall(command.agent ? { agent: command.agent } : {});
4123
+ return hooksStatus();
4124
+ }
4125
+ async function screenFor(command, fail, summarise, report, server) {
4126
+ const gate = (title, build) => /* @__PURE__ */ jsx14(AuthGate, {
4127
+ title,
4128
+ build,
4129
+ connect: () => createClient2(server),
4130
+ onError: fail
4131
+ });
4132
+ const local = (title, load) => /* @__PURE__ */ jsx14(ReportScreen2, {
4133
+ title,
4134
+ load,
4135
+ onError: fail,
4136
+ onReport: report
4137
+ });
4138
+ switch (command.kind) {
4139
+ case "help":
4140
+ return /* @__PURE__ */ jsx14(HelpScreen, {
4141
+ text: HELP
4142
+ });
4143
+ case "version":
4144
+ return /* @__PURE__ */ jsx14(NoticeScreen, {
4145
+ title: "version",
4146
+ message: VERSION
4147
+ });
4148
+ case "error":
4149
+ throw new Error(command.message);
4150
+ case "login":
4151
+ return /* @__PURE__ */ jsx14(LoginScreen, {
4152
+ onError: fail
4153
+ });
4154
+ case "logout":
4155
+ await logout();
4156
+ return /* @__PURE__ */ jsx14(NoticeScreen, {
4157
+ title: "logout",
4158
+ message: "Logged out."
4159
+ });
4160
+ case "whoami":
4161
+ return gate("whoami", () => /* @__PURE__ */ jsx14(WhoamiScreen, {
4162
+ load: () => whoami(server),
4163
+ onError: fail
4164
+ }));
4165
+ case "list":
4166
+ return gate("skills", (client) => /* @__PURE__ */ jsx14(CatalogScreen, {
4167
+ title: "skills",
4168
+ load: async () => toRows(await client.skills.list.query({})),
4169
+ onError: fail
4170
+ }));
4171
+ case "search": {
4172
+ const q = command.query;
4173
+ return gate(`search: ${q}`, (client) => /* @__PURE__ */ jsx14(CatalogScreen, {
4174
+ title: `search: ${q}`,
4175
+ load: async () => toRows(await client.skills.search.query({ q })),
4176
+ onError: fail
4177
+ }));
4178
+ }
4179
+ case "home": {
4180
+ const opts = await resolveInstallOptions({});
4181
+ return /* @__PURE__ */ jsx14(HomeScreen, {
4182
+ opts,
4183
+ server,
4184
+ openClient: () => createClient2(server),
4185
+ onError: fail,
4186
+ onSummary: summarise
4187
+ });
4188
+ }
4189
+ case "browse": {
4190
+ const opts = await resolveInstallOptions({});
4191
+ return gate("browse", (client) => /* @__PURE__ */ jsx14(BrowserScreen, {
4192
+ client,
4193
+ opts,
4194
+ onError: fail,
4195
+ onSummary: summarise
4196
+ }));
4197
+ }
4198
+ case "add":
4199
+ return gate("add", (client) => /* @__PURE__ */ jsx14(Installer, {
4200
+ title: "add",
4201
+ stream: () => install(client, command.names, command.opts),
4202
+ onDone: summarise
4203
+ }));
4204
+ case "bundle":
4205
+ return gate(`bundle: ${command.name}`, (client) => /* @__PURE__ */ jsx14(Installer, {
4206
+ title: `bundle: ${command.name}`,
4207
+ stream: () => installBundle(client, command.name, command.opts),
4208
+ onDone: summarise
4209
+ }));
4210
+ case "install": {
4211
+ const nearest = await nearestFor(command.opts);
4212
+ const empty = nothingTracked(nearest, "install");
4213
+ if (empty)
4214
+ return local("install", async () => empty);
4215
+ return /* @__PURE__ */ jsx14(Installer, {
4216
+ title: "install",
4217
+ stream: () => reinstall(nearest.manifest, command.opts),
4218
+ onDone: summarise
4219
+ });
4220
+ }
4221
+ case "update": {
4222
+ if (command.bundle) {
4223
+ const name = command.bundle;
4224
+ return gate(`update: ${name}`, (client) => /* @__PURE__ */ jsx14(Installer, {
4225
+ title: `update: ${name}`,
4226
+ stream: () => update(client, name, command.opts),
4227
+ onDone: summarise
4228
+ }));
4229
+ }
4230
+ const nearest = await nearestFor(command.opts);
4231
+ const empty = nothingTracked(nearest, "update", command.all ?? false);
4232
+ if (empty)
4233
+ return local("update", async () => empty);
4234
+ return gate("update", (client) => /* @__PURE__ */ jsx14(Installer, {
4235
+ title: "update",
4236
+ stream: () => updateTracked(client, nearest.manifest, command.opts, command.all ?? false),
4237
+ onDone: summarise
4238
+ }));
4239
+ }
4240
+ case "remove": {
4241
+ const nearest = await nearestFor(command.opts);
4242
+ return local("remove", () => removeSkills(command.names, nearest.manifest, nearest.path, command.opts));
4243
+ }
4244
+ case "installed":
4245
+ return local("installed", async () => installedReport(await nearestManifest()));
4246
+ case "doctor":
4247
+ return local("doctor", async () => doctorReport(await nearestManifest(), server));
4248
+ case "config":
4249
+ return local("config", () => command.action === "get" ? configGet(command.key) : configSet(command.key, command.value));
4250
+ case "hooks":
4251
+ return local("hooks", () => hooksReport(command));
4252
+ case "hook":
4253
+ throw new Error("hooks are not interactive");
4254
+ case "outdated":
4255
+ return gate("outdated", (client) => /* @__PURE__ */ jsx14(ReportScreen2, {
4256
+ title: "outdated",
4257
+ load: async () => outdatedReport(client, await nearestManifest(), command.quiet),
4258
+ onError: fail,
4259
+ onReport: report
4260
+ }));
4261
+ case "org":
4262
+ return gate("org", () => /* @__PURE__ */ jsx14(ReportScreen2, {
4263
+ title: "org",
4264
+ load: () => command.name ? orgSwitchReport(command.name, server) : orgListReport(server),
4265
+ onError: fail,
4266
+ onReport: report
4267
+ }));
4268
+ }
4269
+ }
4270
+ async function runInk(command, server) {
4271
+ let failure2 = null;
4272
+ const node = await screenFor(command, (error) => {
4273
+ failure2 = error;
4274
+ instance?.unmount();
4275
+ }, applySummary, (report) => {
4276
+ if (report.failed)
4277
+ process.exitCode = 1;
4278
+ }, server);
4279
+ const instance = render(node);
4280
+ await instance.waitUntilExit();
4281
+ if (failure2)
4282
+ throw failure2;
4283
+ }
4284
+ function isEntrypoint() {
4285
+ const entry = process.argv[1];
4286
+ if (!entry)
4287
+ return false;
4288
+ try {
4289
+ return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));
4290
+ } catch {
4291
+ return false;
4292
+ }
4293
+ }
4294
+ if (isEntrypoint()) {
4295
+ const interactive = Boolean(process.stdout.isTTY);
4296
+ const parsed = parseArgv(process.argv.slice(2), interactive);
4297
+ const { output, server } = parsed;
4298
+ try {
4299
+ const command = await prepare(parsed.command);
4300
+ if (command.kind === "hook") {
4301
+ await runHook(command.event);
4302
+ } else {
4303
+ await (output.plain ? runPlain(command, output, server) : runInk(command, server));
4304
+ await flushQuietly(server);
4305
+ if (INVITES_HOOKS.has(command.kind))
4306
+ for (const line2 of await ensureHooksRegistered())
4307
+ console.log(line2);
4308
+ }
4309
+ } catch (error) {
4310
+ console.error(`error: ${error instanceof Error ? error.message : String(error)}`);
4311
+ process.exit(1);
4312
+ }
4313
+ }