yourskills 0.3.0 → 0.4.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 +345 -64
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -611,6 +611,7 @@ async function localAccount(server) {
611
611
  return {
612
612
  signedIn: Boolean(config.token),
613
613
  email: config.userEmail ?? null,
614
+ organization: config.orgName ?? null,
614
615
  server: serverUrl(config, server)
615
616
  };
616
617
  }
@@ -924,15 +925,24 @@ function flattenCommands(groups) {
924
925
  }
925
926
  var POINTER = 2;
926
927
  var GAP2 = 2;
928
+ var LABEL_CAP = 22;
929
+ function valueColumn(items) {
930
+ return Math.min(items.reduce((wide, item) => Math.max(wide, item.label.length), 0), LABEL_CAP);
931
+ }
927
932
  function CommandRow({
928
933
  item,
929
934
  width,
935
+ column,
930
936
  active
931
937
  }) {
932
938
  const hint = item.hint ?? "";
933
939
  const room = width - POINTER - (hint ? hint.length + GAP2 : 0);
934
940
  const label = clip(item.label, Math.max(room, 0));
935
- const gap = Math.max(width - POINTER - label.length - hint.length, hint ? 1 : 0);
941
+ const shows = item.shows ?? "";
942
+ const pad = Math.max(column - label.length, 0) + GAP2;
943
+ const value = shows && label.length + pad + shows.length <= room ? shows : "";
944
+ const left = label.length + (value ? pad + value.length : 0);
945
+ const gap = Math.max(width - POINTER - left - hint.length, hint ? 1 : 0);
936
946
  return /* @__PURE__ */ jsxs5(Text4, {
937
947
  children: [
938
948
  /* @__PURE__ */ jsx5(Text4, {
@@ -944,6 +954,9 @@ function CommandRow({
944
954
  color: active ? tone.primary : undefined,
945
955
  children: label
946
956
  }),
957
+ value ? /* @__PURE__ */ jsx5(Text4, {
958
+ children: `${" ".repeat(pad)}${value}`
959
+ }) : null,
947
960
  /* @__PURE__ */ jsx5(Text4, {
948
961
  dimColor: true,
949
962
  children: `${" ".repeat(gap)}${hint}`
@@ -963,6 +976,7 @@ function CommandPalette({
963
976
  const rows = flattenCommands(visible);
964
977
  const current = rows[cursor];
965
978
  const interior = width - FRAME_CHROME;
979
+ const column = valueColumn(rows);
966
980
  return /* @__PURE__ */ jsxs5(Box3, {
967
981
  flexDirection: "column",
968
982
  children: [
@@ -997,6 +1011,7 @@ function CommandPalette({
997
1011
  group.items.map((item) => /* @__PURE__ */ jsx5(CommandRow, {
998
1012
  item,
999
1013
  width: interior,
1014
+ column,
1000
1015
  active: item.value === current?.value
1001
1016
  }, item.value))
1002
1017
  ]
@@ -5753,7 +5768,7 @@ import { realpathSync } from "node:fs";
5753
5768
  // package.json
5754
5769
  var package_default = {
5755
5770
  name: "yourskills",
5756
- version: "0.3.0",
5771
+ version: "0.4.0",
5757
5772
  description: "Browse, install and keep up to date the agent skills and bundles your team publishes on yourskills.",
5758
5773
  license: "UNLICENSED",
5759
5774
  homepage: "https://yourskills.store",
@@ -6198,8 +6213,41 @@ async function runSessionStart() {
6198
6213
  const outcome = await sessionStart();
6199
6214
  emit(outcome, await upgradeCheck());
6200
6215
  }
6201
- // src/org/org.ts
6216
+ // src/org/wire.ts
6202
6217
  var authBase2 = (base) => `${base.replace(/\/$/, "")}/api/auth`;
6218
+ async function activeOrganizationId(base, token) {
6219
+ const res = await fetch(`${authBase2(base)}/get-session`, {
6220
+ headers: { authorization: `Bearer ${token}` }
6221
+ });
6222
+ if (!res.ok)
6223
+ return null;
6224
+ const body = await res.json();
6225
+ return body?.session?.activeOrganizationId ?? null;
6226
+ }
6227
+ async function listOrganizations(base, token) {
6228
+ let res;
6229
+ try {
6230
+ res = await fetch(`${authBase2(base)}/organization/list`, {
6231
+ headers: { authorization: `Bearer ${token}` }
6232
+ });
6233
+ } catch (error) {
6234
+ throw new Error(`Could not reach ${base}: ${error instanceof Error ? error.message : String(error)}`);
6235
+ }
6236
+ if (!res.ok) {
6237
+ throw new Error(`Could not list organizations: ${res.status} ${res.statusText}`);
6238
+ }
6239
+ return await res.json();
6240
+ }
6241
+ async function rememberOrganization(name) {
6242
+ try {
6243
+ const config = await readConfig();
6244
+ if ((config.orgName ?? null) === name)
6245
+ return;
6246
+ await writeConfig(name === null ? { ...config, orgName: undefined } : { ...config, orgName: name });
6247
+ } catch {}
6248
+ }
6249
+
6250
+ // src/org/org.ts
6203
6251
  var NO_ACTIVE_ORGANIZATION = "none: choose one with `yourskills org <name>`, or in the web app";
6204
6252
  async function namedOrganization(server) {
6205
6253
  const client = await createClient(server);
@@ -6208,11 +6256,9 @@ async function namedOrganization(server) {
6208
6256
  }
6209
6257
  async function whoamiReport(server) {
6210
6258
  const who = await whoami(server);
6211
- return drawWhoami({
6212
- user: who.user,
6213
- server: who.server,
6214
- organization: who.org ? await namedOrganization(server) : null
6215
- });
6259
+ const organization = who.org ? await namedOrganization(server) : null;
6260
+ await rememberOrganization(organization?.name ?? null);
6261
+ return drawWhoami({ user: who.user, server: who.server, organization });
6216
6262
  }
6217
6263
  function drawWhoami({ user, server, organization }) {
6218
6264
  const label = (text) => text.padEnd(7);
@@ -6230,29 +6276,6 @@ function drawWhoami({ user, server, organization }) {
6230
6276
  json: { user, server, organization }
6231
6277
  };
6232
6278
  }
6233
- async function activeOrganizationId(base, token) {
6234
- const res = await fetch(`${authBase2(base)}/get-session`, {
6235
- headers: { authorization: `Bearer ${token}` }
6236
- });
6237
- if (!res.ok)
6238
- return null;
6239
- const body = await res.json();
6240
- return body?.session?.activeOrganizationId ?? null;
6241
- }
6242
- async function listOrganizations(base, token) {
6243
- let res;
6244
- try {
6245
- res = await fetch(`${authBase2(base)}/organization/list`, {
6246
- headers: { authorization: `Bearer ${token}` }
6247
- });
6248
- } catch (error) {
6249
- throw new Error(`Could not reach ${base}: ${error instanceof Error ? error.message : String(error)}`);
6250
- }
6251
- if (!res.ok) {
6252
- throw new Error(`Could not list organizations: ${res.status} ${res.statusText}`);
6253
- }
6254
- return await res.json();
6255
- }
6256
6279
  function rows(orgs, active) {
6257
6280
  if (orgs.length === 0) {
6258
6281
  return [
@@ -6269,12 +6292,40 @@ function rows(orgs, active) {
6269
6292
  note: org.slug ?? org.id
6270
6293
  }));
6271
6294
  }
6295
+ async function learnActiveOrganization(server) {
6296
+ try {
6297
+ const { token, base } = await requireToken(server);
6298
+ const [orgs, active] = await Promise.all([
6299
+ listOrganizations(base, token),
6300
+ activeOrganizationId(base, token)
6301
+ ]);
6302
+ const name = orgs.find((o) => o.id === active)?.name ?? null;
6303
+ await rememberOrganization(name);
6304
+ return name;
6305
+ } catch {
6306
+ return null;
6307
+ }
6308
+ }
6309
+ async function organizationChoices(server) {
6310
+ const { token, base } = await requireToken(server);
6311
+ const [orgs, active] = await Promise.all([
6312
+ listOrganizations(base, token),
6313
+ activeOrganizationId(base, token)
6314
+ ]);
6315
+ await rememberOrganization(orgs.find((o) => o.id === active)?.name ?? null);
6316
+ return orgs.map((org) => ({
6317
+ name: org.name,
6318
+ slug: org.slug ?? null,
6319
+ active: org.id === active
6320
+ }));
6321
+ }
6272
6322
  async function orgListReport(server) {
6273
6323
  const { token, base } = await requireToken(server);
6274
6324
  const [orgs, active] = await Promise.all([
6275
6325
  listOrganizations(base, token),
6276
6326
  activeOrganizationId(base, token)
6277
6327
  ]);
6328
+ await rememberOrganization(orgs.find((o) => o.id === active)?.name ?? null);
6278
6329
  return {
6279
6330
  title: "org",
6280
6331
  lines: rows(orgs, active),
@@ -6331,6 +6382,7 @@ async function orgSwitchReport(name, server) {
6331
6382
  failed: true
6332
6383
  };
6333
6384
  }
6385
+ await rememberOrganization(match.name);
6334
6386
  return {
6335
6387
  title: "org",
6336
6388
  lines: [{ mark: "ok", text: `Active organization: ${match.name}` }],
@@ -6896,6 +6948,7 @@ function noSuchPass(id, passes) {
6896
6948
  };
6897
6949
  }
6898
6950
  // src/commands/facts.ts
6951
+ var NO_FEED = {};
6899
6952
  function count2(n, one, many = `${one}s`) {
6900
6953
  return `${n} ${n === 1 ? one : many}`;
6901
6954
  }
@@ -6988,6 +7041,7 @@ var TABLE = {
6988
7041
  value: "catalog.bundles",
6989
7042
  label: "browse bundles",
6990
7043
  hint: "browse",
7044
+ shows: (facts) => facts.feed.bundles === undefined ? "" : count2(facts.feed.bundles, "bundle"),
6991
7045
  when: (facts) => facts.signedIn,
6992
7046
  opens: { kind: "browse" }
6993
7047
  }
@@ -7094,8 +7148,9 @@ var TABLE = {
7094
7148
  value: "account.org",
7095
7149
  label: "switch org",
7096
7150
  hint: "org <name>",
7151
+ shows: (facts) => facts.feed.organizations === undefined ? "" : facts.feed.organizations <= 1 ? "only this one" : count2(facts.feed.organizations - 1, "other"),
7097
7152
  when: (facts) => facts.signedIn,
7098
- opens: { kind: "org" }
7153
+ says: (facts) => facts.organization ? `switch this machine out of ${facts.organization}` : "choose which organization this machine installs from"
7099
7154
  }
7100
7155
  ],
7101
7156
  invitesHooks: false,
@@ -7118,6 +7173,7 @@ var TABLE = {
7118
7173
  value: "catalog.skills",
7119
7174
  label: "browse skills",
7120
7175
  hint: "list",
7176
+ shows: (facts) => facts.feed.skills === undefined ? "" : count2(facts.feed.skills, "skill"),
7121
7177
  when: (facts) => facts.signedIn,
7122
7178
  opens: { kind: "list" }
7123
7179
  }
@@ -7295,6 +7351,7 @@ var TABLE = {
7295
7351
  value: "installed.update",
7296
7352
  label: "update all",
7297
7353
  hint: "update --all",
7354
+ shows: (facts) => updateScope(facts.bundles, facts.loose),
7298
7355
  when: (facts) => facts.skills > 0 && facts.signedIn,
7299
7356
  opens: { kind: "update", all: true }
7300
7357
  }
@@ -7384,6 +7441,7 @@ var TABLE = {
7384
7441
  value: "installed.list",
7385
7442
  label: "list installed",
7386
7443
  hint: "installed",
7444
+ shows: (facts) => count2(facts.skills, "skill"),
7387
7445
  when: (facts) => facts.skills > 0,
7388
7446
  opens: { kind: "installed" }
7389
7447
  }
@@ -7413,6 +7471,7 @@ var TABLE = {
7413
7471
  value: "installed.outdated",
7414
7472
  label: "outdated",
7415
7473
  hint: "outdated",
7474
+ shows: (facts) => facts.feed.outdated === undefined ? "" : facts.feed.outdated === 0 ? "all current" : `${facts.feed.outdated} behind`,
7416
7475
  when: (facts) => facts.skills > 0 && facts.signedIn,
7417
7476
  opens: { kind: "outdated" }
7418
7477
  }
@@ -7447,6 +7506,7 @@ var TABLE = {
7447
7506
  value: "catalog.pending",
7448
7507
  label: "waiting for a review",
7449
7508
  hint: "pending",
7509
+ shows: (facts) => facts.feed.pending === undefined ? "" : facts.feed.pending === 0 ? "none open" : `${facts.feed.pending} open`,
7450
7510
  when: (facts) => facts.signedIn,
7451
7511
  opens: { kind: "pending" }
7452
7512
  }
@@ -7553,20 +7613,21 @@ var TABLE = {
7553
7613
  value: "setup.scope",
7554
7614
  label: "scope",
7555
7615
  hint: "config set scope project",
7556
- opens: { kind: "config", action: "get", key: "scope" },
7616
+ shows: (facts) => facts.scope,
7557
7617
  says: (facts) => `where installs land: ${facts.scope} right now`
7558
7618
  },
7559
7619
  {
7560
7620
  value: "setup.agents",
7561
7621
  label: "agents",
7562
7622
  hint: "config set agents <name>",
7563
- opens: { kind: "config", action: "get", key: "agents" },
7564
- says: () => "which agents a skill is written for when it is installed"
7623
+ shows: (facts) => facts.agents.length > 0 ? count2(facts.agents.length, "agent") : "unset",
7624
+ says: (facts) => facts.agents.length > 0 ? `which agents an install writes for: ${facts.agents.join(", ")}` : "which agents an install writes for; nothing pinned, so `skills` looks"
7565
7625
  },
7566
7626
  {
7567
7627
  value: "setup.explain",
7568
7628
  label: "explain",
7569
7629
  hint: "config get --explain",
7630
+ shows: (facts) => `${count2(facts.settings, "setting")} set`,
7570
7631
  opens: { kind: "config", action: "get", explain: true }
7571
7632
  }
7572
7633
  ],
@@ -7644,6 +7705,7 @@ var TABLE = {
7644
7705
  value: "setup.upgrade",
7645
7706
  label: "upgrade",
7646
7707
  hint: "upgrade",
7708
+ shows: (facts) => facts.feed.upgrade === undefined ? "" : facts.feed.upgrade === null ? "up to date" : `${facts.feed.upgrade} is out`,
7647
7709
  opens: { kind: "upgrade" }
7648
7710
  }
7649
7711
  ],
@@ -7740,6 +7802,9 @@ function refuseCommand(command) {
7740
7802
  code: EXIT.usage
7741
7803
  });
7742
7804
  }
7805
+ function doorShows(door, facts) {
7806
+ return door.shows?.(facts) ?? "";
7807
+ }
7743
7808
  function doorSays(door, facts) {
7744
7809
  if (door.opens === undefined)
7745
7810
  return door.says(facts);
@@ -8041,13 +8106,19 @@ var PALETTE = [
8041
8106
  ]
8042
8107
  }
8043
8108
  ];
8044
- function item(door) {
8045
- return { value: door.value, label: door.label, hint: door.hint };
8109
+ function item(door, facts) {
8110
+ const shows = doorShows(door, facts);
8111
+ return {
8112
+ value: door.value,
8113
+ label: door.label,
8114
+ hint: door.hint,
8115
+ ...shows ? { shows } : {}
8116
+ };
8046
8117
  }
8047
8118
  function paletteGroups(facts) {
8048
8119
  const groups = [];
8049
8120
  for (const place of PALETTE) {
8050
- const items = place.doors.map((value) => DOORS[value]).filter((door) => door.when?.(facts) ?? true).map(item);
8121
+ const items = place.doors.map((value) => DOORS[value]).filter((door) => door.when?.(facts) ?? true).map((door) => item(door, facts));
8051
8122
  if (items.length > 0)
8052
8123
  groups.push({ group: place.group, items });
8053
8124
  }
@@ -8056,29 +8127,78 @@ function paletteGroups(facts) {
8056
8127
  // src/shell/facts.ts
8057
8128
  import { existsSync as existsSync7 } from "node:fs";
8058
8129
  async function paletteFacts(scope, server) {
8059
- const [account, nearest, config] = await Promise.all([
8130
+ const [account, nearest, config, agents] = await Promise.all([
8060
8131
  localAccount(server),
8061
8132
  nearestManifest(scope),
8062
- readConfig()
8133
+ readConfig(),
8134
+ resolveAgents()
8063
8135
  ]);
8064
8136
  const skills = Object.keys(nearest.manifest.skills).length;
8065
8137
  return {
8066
8138
  signedIn: account.signedIn,
8067
8139
  email: account.email,
8140
+ organization: account.organization,
8068
8141
  server: account.server,
8069
8142
  firstRun: !account.signedIn && skills === 0 && !config.welcomed,
8070
8143
  agentsOnDisk: HOOK_ADAPTERS.filter((adapter) => existsSync7(adapter.home())).map((adapter) => adapter.agent),
8144
+ agents,
8071
8145
  scope: nearest.scope,
8072
8146
  manifest: nearest.path,
8073
8147
  skills,
8074
8148
  bundles: Object.keys(nearest.manifest.bundles).length,
8075
- loose: looseSkills(nearest.manifest).length
8149
+ loose: looseSkills(nearest.manifest).length,
8150
+ settings: CONFIG_KEYS.filter((key) => configValue(config, key) !== UNSET).length,
8151
+ feed: NO_FEED
8076
8152
  };
8077
8153
  }
8078
8154
  // src/shell/screens/home.tsx
8079
8155
  import { Box as Box14, Text as Text16, useApp as useApp7, useInput as useInput8 } from "ink";
8080
8156
  import { useCallback as useCallback3, useEffect as useEffect9, useMemo as useMemo4, useRef, useState as useState11 } from "react";
8081
8157
 
8158
+ // src/shell/feed.ts
8159
+ async function feedFacts(client, where, server) {
8160
+ const installed = Object.keys(where.manifest.skills).length > 0;
8161
+ const [skills, bundles, passes, drift, orgs] = await Promise.allSettled([
8162
+ client.skills.list.query({}),
8163
+ client.bundles.list.query({}),
8164
+ client.mirror.passes.query(),
8165
+ installed ? driftNow(client, where) : Promise.resolve(null),
8166
+ organizationChoices(server)
8167
+ ]);
8168
+ return {
8169
+ ...skills.status === "fulfilled" ? { skills: skills.value.length } : {},
8170
+ ...bundles.status === "fulfilled" ? { bundles: bundles.value.length } : {},
8171
+ ...passes.status === "fulfilled" ? { pending: passes.value.length } : {},
8172
+ ...drift.status === "fulfilled" && drift.value ? { outdated: drift.value.moved.length } : {},
8173
+ ...orgs.status === "fulfilled" ? { organizations: orgs.value.length } : {}
8174
+ };
8175
+ }
8176
+
8177
+ // src/shell/probe.ts
8178
+ var importProbe = async (where) => {
8179
+ const found = await listInstalled(where.scope);
8180
+ const recorded = new Set(Object.keys(where.manifest.skills));
8181
+ const news = found.filter((skill) => !recorded.has(skill.name)).length;
8182
+ if (found.length === 0)
8183
+ return "nothing on this machine to adopt";
8184
+ return news === 0 ? `${count2(found.length, "skill")} on this machine, all recorded here already` : `${count2(found.length, "skill")} on this machine, ${news} not recorded here`;
8185
+ };
8186
+ var doctorProbe = async (where, server) => {
8187
+ const checks = await diagnose(where, {
8188
+ local: true,
8189
+ ...server ? { server } : {}
8190
+ });
8191
+ const bad = checks.filter((check) => !check.ok);
8192
+ return bad.length === 0 ? `${count2(checks.length, "local check")}, all passing` : `${bad.length} of ${checks.length} local checks failing: ${bad.map((check) => check.name).join(", ")}`;
8193
+ };
8194
+ var PROBES = {
8195
+ "setup.import": importProbe,
8196
+ "setup.doctor": doctorProbe
8197
+ };
8198
+ function probeFor(value) {
8199
+ return value ? PROBES[value] : undefined;
8200
+ }
8201
+
8082
8202
  // src/args.ts
8083
8203
  function configCommand2(rest, explain) {
8084
8204
  const [action, key, ...value] = rest;
@@ -8406,13 +8526,16 @@ function PickScreen({
8406
8526
  rows,
8407
8527
  empty,
8408
8528
  verb,
8529
+ single = false,
8530
+ marked,
8531
+ preset,
8409
8532
  onPick,
8410
8533
  onBack
8411
8534
  }) {
8412
8535
  const width = useFrameWidth();
8413
8536
  const [query, setQuery] = useState9("");
8414
8537
  const [cursor, setCursor] = useState9(0);
8415
- const [picked, setPicked] = useState9(new Set);
8538
+ const [picked, setPicked] = useState9(() => new Set(preset ?? []));
8416
8539
  const visible = useMemo3(() => {
8417
8540
  if (query.length === 0)
8418
8541
  return rows;
@@ -8439,7 +8562,7 @@ function PickScreen({
8439
8562
  } else if (key.backspace || key.delete) {
8440
8563
  setQuery((q) => q.slice(0, -1));
8441
8564
  setCursor(0);
8442
- } else if (input === " ") {
8565
+ } else if (input === " " && !single) {
8443
8566
  const row = visible[cursor];
8444
8567
  if (!row)
8445
8568
  return;
@@ -8490,21 +8613,21 @@ function PickScreen({
8490
8613
  names,
8491
8614
  selectable: true,
8492
8615
  cursor: i === cursor,
8493
- selected: picked.has(row.name)
8616
+ selected: single ? row.name === marked : picked.has(row.name)
8494
8617
  }, row.name)),
8495
8618
  /* @__PURE__ */ jsx16(Divider, {
8496
8619
  width
8497
8620
  }),
8498
8621
  /* @__PURE__ */ jsxs14(Box12, {
8499
8622
  children: [
8500
- picked.size > 0 ? /* @__PURE__ */ jsx16(Text14, {
8623
+ picked.size > 0 && !single ? /* @__PURE__ */ jsx16(Text14, {
8501
8624
  color: tone.notice,
8502
8625
  children: `${mark.selected} ${picked.size} marked `
8503
8626
  }) : null,
8504
8627
  /* @__PURE__ */ jsx16(Hints, {
8505
8628
  hints: [
8506
8629
  ["^v", "move"],
8507
- ["space", "mark"],
8630
+ ...single ? [] : [["space", "mark"]],
8508
8631
  ["enter", verb],
8509
8632
  ["esc", query ? "clear" : "back"]
8510
8633
  ]
@@ -8620,12 +8743,35 @@ function failure(title, error) {
8620
8743
  json: { error: message }
8621
8744
  };
8622
8745
  }
8746
+ var HEAD_FLOOR = 14;
8747
+ var AGENTS_FLOOR = 9;
8748
+ function agentsIn(agents, room) {
8749
+ if (agents.length === 0)
8750
+ return "agents unset";
8751
+ const full = agents.join(",");
8752
+ if (full.length <= room)
8753
+ return full;
8754
+ for (let keep = agents.length - 1;keep > 0; keep--) {
8755
+ const text = `${agents.slice(0, keep).join(",")} +${agents.length - keep}`;
8756
+ if (text.length <= room)
8757
+ return text;
8758
+ }
8759
+ return `${agents.length} agents`;
8760
+ }
8623
8761
  function AccountLine({ facts, width }) {
8624
- const right = `${facts.scope} ${facts.skills} installed`;
8625
8762
  const head = facts.signedIn ? facts.email ?? "signed in" : "not signed in";
8626
- const room = Math.max(width - FRAME_CHROME - right.length - 2, 12);
8763
+ const stat = `${facts.scope} ${facts.skills} installed`;
8764
+ const inner = width - FRAME_CHROME;
8765
+ const spent = Math.min(head.length, 24) + 2 + stat.length + 4;
8766
+ const forAgents = inner - spent;
8767
+ const agents = forAgents >= AGENTS_FLOOR ? agentsIn(facts.agents, forAgents) : "";
8768
+ const right = agents ? `${agents} ${stat}` : stat;
8769
+ const room = Math.max(inner - right.length - 2, HEAD_FLOOR);
8627
8770
  const tail = facts.signedIn ? ` ${hostOf(facts.server)}` : " — the catalog needs a session";
8771
+ const org = facts.signedIn && facts.organization ? ` ${facts.organization}` : "";
8628
8772
  const spare = room - 2 - head.length;
8773
+ const showOrg = spare >= org.length;
8774
+ const showTail = spare - (showOrg ? org.length : 0) >= tail.length;
8629
8775
  return /* @__PURE__ */ jsxs16(Box14, {
8630
8776
  width: width - FRAME_CHROME,
8631
8777
  justifyContent: "space-between",
@@ -8640,7 +8786,11 @@ function AccountLine({ facts, width }) {
8640
8786
  color: facts.signedIn ? undefined : tone.notice,
8641
8787
  children: ` ${clip(head, room - 2)}`
8642
8788
  }),
8643
- spare >= tail.length ? /* @__PURE__ */ jsx18(Text16, {
8789
+ showOrg ? /* @__PURE__ */ jsx18(Text16, {
8790
+ color: tone.primary,
8791
+ children: org
8792
+ }) : null,
8793
+ showTail ? /* @__PURE__ */ jsx18(Text16, {
8644
8794
  dimColor: true,
8645
8795
  children: tail
8646
8796
  }) : null
@@ -8648,7 +8798,7 @@ function AccountLine({ facts, width }) {
8648
8798
  }),
8649
8799
  /* @__PURE__ */ jsx18(Text16, {
8650
8800
  dimColor: true,
8651
- children: right
8801
+ children: clip(right, inner - room)
8652
8802
  })
8653
8803
  ]
8654
8804
  });
@@ -8664,7 +8814,9 @@ function StaleLine({ stale, width }) {
8664
8814
  });
8665
8815
  }
8666
8816
  var RUN = "run";
8817
+ var REST_MS = 250;
8667
8818
  var ASK_NPM = () => upgradeCheck();
8819
+ var ASK_REGISTRY = feedFacts;
8668
8820
  var NO_EXIT_CODE = () => {
8669
8821
  return;
8670
8822
  };
@@ -8680,19 +8832,20 @@ function HomeScreen({
8680
8832
  onWelcomed = async () => {
8681
8833
  return;
8682
8834
  },
8683
- checkUpgrade = ASK_NPM
8835
+ checkUpgrade = ASK_NPM,
8836
+ loadFeed = ASK_REGISTRY,
8837
+ probes: probeSet = probeFor
8684
8838
  }) {
8685
8839
  const { exit } = useApp7();
8686
8840
  const width = useFrameWidth();
8687
8841
  const [facts, setFacts] = useState11(initial);
8688
8842
  const [where, setWhere] = useState11(initialWhere);
8689
- const groups = useMemo4(() => paletteGroups(facts), [facts]);
8690
8843
  const [query, setQuery] = useState11("");
8691
8844
  const [cursor, setCursor] = useState11(0);
8692
8845
  const [route, setRoute] = useState11(null);
8693
8846
  const [welcome, setWelcome] = useState11(initial.firstRun);
8694
8847
  const [wizard, setWizard] = useState11(false);
8695
- const [stale, setStale] = useState11(null);
8848
+ const [stale, setStale] = useState11(undefined);
8696
8849
  useEffect9(() => {
8697
8850
  let live = true;
8698
8851
  checkUpgrade().then((found) => {
@@ -8705,6 +8858,17 @@ function HomeScreen({
8705
8858
  live = false;
8706
8859
  };
8707
8860
  }, [checkUpgrade]);
8861
+ const [feed, setFeed] = useState11(NO_FEED);
8862
+ const [probes, setProbes] = useState11({});
8863
+ const [probing, setProbing] = useState11(null);
8864
+ const shown = useMemo4(() => ({
8865
+ ...facts,
8866
+ feed: {
8867
+ ...feed,
8868
+ ...stale === undefined ? {} : { upgrade: stale ? stale.latest : null }
8869
+ }
8870
+ }), [facts, feed, stale]);
8871
+ const groups = useMemo4(() => paletteGroups(shown), [shown]);
8708
8872
  const typed = useMemo4(() => typedCommand(query), [query]);
8709
8873
  const visible = useMemo4(() => {
8710
8874
  const filtered = filterCommands(groups, query);
@@ -8727,6 +8891,20 @@ function HomeScreen({
8727
8891
  held.current ??= await openClient();
8728
8892
  return held.current;
8729
8893
  }, [openClient]);
8894
+ useEffect9(() => {
8895
+ if (!facts.signedIn)
8896
+ return;
8897
+ let live = true;
8898
+ connect().then((client) => loadFeed(client, where, server)).then((found) => {
8899
+ if (live)
8900
+ setFeed(found);
8901
+ }).catch(() => {
8902
+ return;
8903
+ });
8904
+ return () => {
8905
+ live = false;
8906
+ };
8907
+ }, [facts.signedIn, connect, loadFeed, where, server]);
8730
8908
  const checkSession = useCallback3(async () => facts.signedIn, [facts.signedIn]);
8731
8909
  const answering = useCallback3((title, working, load) => ({
8732
8910
  kind: "report",
@@ -8806,20 +8984,87 @@ function HomeScreen({
8806
8984
  held.current = null;
8807
8985
  return routeFrom(answer, say(command, facts));
8808
8986
  }, [catalogDoor, ctx, facts, routeFrom]);
8809
- const ownRoute = useCallback3((value) => {
8987
+ const ownRoute = useCallback3(async (value) => {
8810
8988
  switch (value) {
8811
8989
  case "catalog.search":
8812
8990
  return { kind: "browse", tab: "skills" };
8813
8991
  case "installed.remove":
8814
8992
  return {
8815
8993
  kind: "pick",
8994
+ title: "remove",
8816
8995
  rows: installedRows(where).map((row) => ({
8817
8996
  name: row.name,
8818
8997
  description: `${row.bundle ?? "loose"} ${shortRef(row.ref)}`
8819
- }))
8998
+ })),
8999
+ empty: "Nothing installed here.",
9000
+ verb: "remove",
9001
+ command: (names) => ({ kind: "remove", names })
9002
+ };
9003
+ case "setup.scope":
9004
+ return {
9005
+ kind: "pick",
9006
+ title: "scope",
9007
+ rows: [
9008
+ {
9009
+ name: "global",
9010
+ description: "installs land in your home directory, where every project sees them"
9011
+ },
9012
+ {
9013
+ name: "project",
9014
+ description: "installs land in this project's yourskills.json, and travel with the repo"
9015
+ }
9016
+ ],
9017
+ empty: "",
9018
+ verb: "set",
9019
+ single: true,
9020
+ marked: facts.scope,
9021
+ command: (names) => ({
9022
+ kind: "config",
9023
+ action: "set",
9024
+ key: "scope",
9025
+ value: names[0] ?? ""
9026
+ })
9027
+ };
9028
+ case "setup.agents": {
9029
+ const valid = await knownAgents() ?? [];
9030
+ const onDisk = new Set(facts.agentsOnDisk);
9031
+ return {
9032
+ kind: "pick",
9033
+ title: "agents",
9034
+ rows: valid.map((name) => ({
9035
+ name,
9036
+ description: onDisk.has(name) ? "on this machine" : ""
9037
+ })),
9038
+ empty: "Could not ask the `skills` CLI which agent names it accepts. Run `yourskills doctor`.",
9039
+ verb: "set",
9040
+ preset: facts.agents,
9041
+ command: (names) => ({
9042
+ kind: "config",
9043
+ action: "set",
9044
+ key: "agents",
9045
+ value: names.join(",")
9046
+ })
9047
+ };
9048
+ }
9049
+ case "account.org": {
9050
+ const orgs = await organizationChoices(server);
9051
+ const active = orgs.find((org) => org.active)?.name;
9052
+ return {
9053
+ kind: "pick",
9054
+ title: "org",
9055
+ rows: orgs.map((org) => ({
9056
+ name: org.name,
9057
+ description: org.slug ?? ""
9058
+ })),
9059
+ empty: "You are not a member of any organization yet.",
9060
+ verb: "switch",
9061
+ single: true,
9062
+ ...active ? { marked: active } : {},
9063
+ command: (names) => ({ kind: "org", name: names[0] ?? "" })
8820
9064
  };
9065
+ }
8821
9066
  }
8822
- }, [where]);
9067
+ }, [where, server, facts]);
8823
9068
  const routeForDoor = useCallback3(async (door) => door.opens === undefined ? ownRoute(door.value) : routeForCommand(door.opens), [ownRoute, routeForCommand]);
8824
9069
  const open = useCallback3((pending, title) => {
8825
9070
  pending.then((next) => {
@@ -8836,6 +9081,29 @@ function HomeScreen({
8836
9081
  open(routeForCommand(parsed.command), parsed.text);
8837
9082
  }, [open, routeForCommand]);
8838
9083
  const current = rows[cursor];
9084
+ useEffect9(() => {
9085
+ const value = current?.value;
9086
+ const probe = probeSet(value);
9087
+ if (!value || !probe || probes[value] !== undefined)
9088
+ return;
9089
+ let live = true;
9090
+ const timer = setTimeout(() => {
9091
+ setProbing(value);
9092
+ probe(where, server).then((text) => {
9093
+ if (live)
9094
+ setProbes((was) => ({ ...was, [value]: text }));
9095
+ }).catch(() => {
9096
+ return;
9097
+ }).finally(() => {
9098
+ if (live)
9099
+ setProbing(null);
9100
+ });
9101
+ }, REST_MS);
9102
+ return () => {
9103
+ live = false;
9104
+ clearTimeout(timer);
9105
+ };
9106
+ }, [current?.value, probes, probeSet, where, server]);
8839
9107
  const activate = useCallback3(() => {
8840
9108
  const item = rows[cursor];
8841
9109
  if (!item)
@@ -8862,6 +9130,7 @@ function HomeScreen({
8862
9130
  setRoute(null);
8863
9131
  setQuery("");
8864
9132
  setCursor(0);
9133
+ setProbes({});
8865
9134
  reload().then(setFacts).catch(() => {
8866
9135
  return;
8867
9136
  });
@@ -8870,6 +9139,9 @@ function HomeScreen({
8870
9139
  });
8871
9140
  }, [reload, resite]);
8872
9141
  const afterLogin = useCallback3(() => {
9142
+ learnActiveOrganization(server).then(() => reload()).then(setFacts).catch(() => {
9143
+ return;
9144
+ });
8873
9145
  if (!wizard) {
8874
9146
  back();
8875
9147
  return;
@@ -8879,7 +9151,7 @@ function HomeScreen({
8879
9151
  setFacts(next);
8880
9152
  setRoute({ kind: "browse" });
8881
9153
  }).catch(() => setRoute(null));
8882
- }, [wizard, back, reload]);
9154
+ }, [wizard, back, reload, server]);
8883
9155
  const leaveWelcome = useCallback3((start) => {
8884
9156
  setWelcome(false);
8885
9157
  onWelcomed().catch(() => {
@@ -8966,13 +9238,17 @@ function HomeScreen({
8966
9238
  });
8967
9239
  }
8968
9240
  if (route?.kind === "pick") {
9241
+ const picked = route;
8969
9242
  return /* @__PURE__ */ jsx18(PickScreen, {
8970
- title: "remove",
8971
- rows: route.rows,
8972
- empty: "Nothing installed here.",
8973
- verb: "remove",
9243
+ title: picked.title,
9244
+ rows: picked.rows,
9245
+ empty: picked.empty,
9246
+ verb: picked.verb,
9247
+ ...picked.single ? { single: true } : {},
9248
+ ...picked.marked ? { marked: picked.marked } : {},
9249
+ ...picked.preset ? { preset: picked.preset } : {},
8974
9250
  onPick: (names) => {
8975
- open(routeForCommand({ kind: "remove", names }), "remove");
9251
+ open(routeForCommand(picked.command(names)), picked.title);
8976
9252
  },
8977
9253
  onBack: back
8978
9254
  });
@@ -9003,14 +9279,15 @@ function HomeScreen({
9003
9279
  });
9004
9280
  }
9005
9281
  const highlighted = current ? doorFor(current.value) : undefined;
9006
- const description = current?.value === RUN && runnable(typed) ? say(typed.command, facts) : highlighted ? doorSays(highlighted, facts) : typed ? say(typed.command, facts) : "";
9282
+ const description = current?.value === RUN && runnable(typed) ? say(typed.command, shown) : highlighted ? doorSays(highlighted, shown) : typed ? say(typed.command, shown) : "";
9283
+ const found = current ? probes[current.value] ?? (probing === current.value ? "looking..." : "") : "";
9007
9284
  const empty = typed?.command.kind === "error" && typed.command.next ? `${mark.fail} ${typed.command.message} try: ${typed.command.next.replace(/^yourskills /, "")}` : "No matching commands.";
9008
9285
  return /* @__PURE__ */ jsxs16(Frame, {
9009
9286
  title: "yourskills",
9010
9287
  width,
9011
9288
  children: [
9012
9289
  /* @__PURE__ */ jsx18(AccountLine, {
9013
- facts,
9290
+ facts: shown,
9014
9291
  width
9015
9292
  }),
9016
9293
  stale ? /* @__PURE__ */ jsx18(StaleLine, {
@@ -9034,6 +9311,10 @@ function HomeScreen({
9034
9311
  dimColor: true,
9035
9312
  children: clip(description, width - FRAME_CHROME)
9036
9313
  }),
9314
+ /* @__PURE__ */ jsx18(Text16, {
9315
+ dimColor: true,
9316
+ children: found ? clip(` ${found}`, width - FRAME_CHROME) : " "
9317
+ }),
9037
9318
  /* @__PURE__ */ jsx18(Hints, {
9038
9319
  hints: [
9039
9320
  ["^v", "move"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yourskills",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Browse, install and keep up to date the agent skills and bundles your team publishes on yourskills.",
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://yourskills.store",