agentwheel 0.16.4 → 0.16.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -54,13 +54,15 @@ install the companion skill, and show the right catalogue flow for your runtime.
54
54
  curl -fsSL https://raw.githubusercontent.com/NestDevLab/agentwheel/main/install.md
55
55
  ```
56
56
 
57
- **Companion skill**
57
+ **Companion skills**
58
58
 
59
- The companion skill keeps Agentwheel commands and safety rules inside the runtime you are using:
59
+ The management skill keeps Agentwheel commands and safety rules inside the runtime you are using.
60
+ Install the discovery skill separately when you also want proactive, non-installing suggestions:
60
61
 
61
62
  ```bash
62
63
  agentwheel doctor --adapter codex --local
63
64
  agentwheel install github:NestDevLab/agentwheel --adapter codex --local --skill agentwheel
65
+ agentwheel install github:NestDevLab/agentwheel --adapter codex --local --skill agentwheel-discovery
64
66
  ```
65
67
 
66
68
  > **Status: early (v0.12).** The public CLI vocabulary is package-manager style:
@@ -167,10 +169,10 @@ Use `--scope registry`, `--scope enriched`, or `--scope vercel` to restrict a qu
167
169
  `--scope all` combines every source, deduplicates equivalent artifacts, and reports every
168
170
  provenance plus the safe installation route.
169
171
 
170
- Search is deterministic and lexical. The companion skill adds semantic behavior at the agent
171
- layer: it can generate a small set of related queries, merge and rerank the JSON results against
172
- the original request, and suggest at most three artifacts. Search never installs or changes
173
- configuration by itself.
172
+ The optional `agentwheel-discovery` skill notices capability gaps during normal work, uses the
173
+ verified semantic catalogue index, reranks results against the original request, and suggests at
174
+ most three artifacts. It can read one instruction skill with `agentwheel try` before installation.
175
+ Search and trial never install or change configuration by themselves.
174
176
 
175
177
  Registry maintenance remains available through `agentwheel registry update` and
176
178
  `agentwheel registry list`. Registry short names continue to resolve during add/install.
@@ -243,10 +245,14 @@ stderr warning when an update is available. Disable it with `--no-update-check`
243
245
 
244
246
  ## Companion Skill Doctor
245
247
 
246
- Agentwheel ships its own companion skill in `github:NestDevLab/agentwheel` as `skills/agentwheel`.
247
- Installing it is optional, but strongly recommended if you want to get the most out of Agentwheel:
248
- the skill keeps Agentwheel commands, setup guidance, safety rules, and operational patterns available
249
- inside your agent runtime instead of forcing you to leave the flow and look them up elsewhere.
248
+ Agentwheel ships two optional companion skills in `github:NestDevLab/agentwheel`:
249
+
250
+ - `skills/agentwheel` provides explicit search, setup guidance, safety rules, and artifact management.
251
+ - `skills/agentwheel-discovery` proactively notices capability gaps, suggests up to three matches,
252
+ and offers a read-only skill trial before installation.
253
+
254
+ Install only the management skill when proactive recommendations are unwanted. Install both when
255
+ you want Agentwheel to surface reusable capabilities during normal work.
250
256
 
251
257
  The CLI never installs the companion skill silently into runtime folders. Use `doctor` to check the
252
258
  selected runtime and print the exact preview and install commands when a skill is missing:
@@ -255,6 +261,8 @@ selected runtime and print the exact preview and install commands when a skill i
255
261
  agentwheel doctor --adapter copilot --user
256
262
  agentwheel install github:NestDevLab/agentwheel --adapter copilot --user --skill agentwheel --dry-run
257
263
  agentwheel install github:NestDevLab/agentwheel --adapter copilot --user --skill agentwheel
264
+ agentwheel install github:NestDevLab/agentwheel --adapter copilot --user --skill agentwheel-discovery --dry-run
265
+ agentwheel install github:NestDevLab/agentwheel --adapter copilot --user --skill agentwheel-discovery
258
266
  ```
259
267
 
260
268
  `doctor` also accepts explicit skill checks and machine-readable output. In Syncwheel-managed
package/dist/index.js CHANGED
@@ -9,11 +9,11 @@ import {
9
9
  } from "./chunk-PKAPR55N.js";
10
10
 
11
11
  // src/cli/index.ts
12
- import { createHash as createHash12 } from "crypto";
12
+ import { createHash as createHash14 } from "crypto";
13
13
  import { existsSync } from "fs";
14
14
  import { mkdir as mkdir23, rm as rm12, writeFile as writeFile22 } from "fs/promises";
15
- import { homedir as homedir11 } from "os";
16
- import { dirname as dirname32, join as join46, resolve as resolve22 } from "path";
15
+ import { homedir as homedir12 } from "os";
16
+ import { dirname as dirname32, join as join48, resolve as resolve22 } from "path";
17
17
  import { fileURLToPath as fileURLToPath3 } from "url";
18
18
  import { Command } from "commander";
19
19
 
@@ -1469,15 +1469,15 @@ function appendMcpServers(content, servers) {
1469
1469
  `;
1470
1470
  }
1471
1471
  function formatMcpServer(name, server) {
1472
- const env = isRecord2(server.env) ? server.env : void 0;
1472
+ const env2 = isRecord2(server.env) ? server.env : void 0;
1473
1473
  const lines = [`[mcp_servers.${quoteTomlKey(name)}]`];
1474
1474
  for (const [key, value] of Object.entries(server)) {
1475
1475
  if (key === "env" || value === void 0) continue;
1476
1476
  lines.push(`${quoteTomlKey(key)} = ${formatTomlValue(value)}`);
1477
1477
  }
1478
- if (env && Object.keys(env).length > 0) {
1478
+ if (env2 && Object.keys(env2).length > 0) {
1479
1479
  lines.push("", `[mcp_servers.${quoteTomlKey(name)}.env]`);
1480
- for (const [key, value] of Object.entries(env)) {
1480
+ for (const [key, value] of Object.entries(env2)) {
1481
1481
  lines.push(`${quoteTomlKey(key)} = ${formatTomlValue(value)}`);
1482
1482
  }
1483
1483
  }
@@ -10581,9 +10581,9 @@ async function maybeCheckForUpdate(options) {
10581
10581
  }
10582
10582
  }
10583
10583
  function isDisabled(options) {
10584
- const env = options.env ?? process.env;
10585
- if (env.AGENTWHEEL_NO_UPDATE_CHECK === "1" || env.AGENTWHEEL_NO_UPDATE_CHECK === "true") return true;
10586
- if (env.CI) return true;
10584
+ const env2 = options.env ?? process.env;
10585
+ if (env2.AGENTWHEEL_NO_UPDATE_CHECK === "1" || env2.AGENTWHEEL_NO_UPDATE_CHECK === "true") return true;
10586
+ if (env2.CI) return true;
10587
10587
  if (options.argv?.includes("--no-update-check")) return true;
10588
10588
  if (options.argv?.includes("--offline")) return true;
10589
10589
  const isTTY = options.isTTY ?? process.stderr.isTTY === true;
@@ -11228,7 +11228,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
11228
11228
  const args = ["--no-update-check", "status", "--profile", member.profile, "--json"];
11229
11229
  if (options.refresh) args.push("--refresh");
11230
11230
  if (options.offline) args.push("--offline");
11231
- const env = { ...process.env, AGENTWHEEL_COMPOSITE_CHAIN: JSON.stringify(chain) };
11231
+ const env2 = { ...process.env, AGENTWHEEL_COMPOSITE_CHAIN: JSON.stringify(chain) };
11232
11232
  let stdout = "";
11233
11233
  let stderr = "";
11234
11234
  try {
@@ -11236,7 +11236,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
11236
11236
  const workspace = resolve21(parentWorkspace, member.workspace);
11237
11237
  const result = await execFileAsync6(process.execPath, [cliEntry, ...args], {
11238
11238
  cwd: workspace,
11239
- env,
11239
+ env: env2,
11240
11240
  maxBuffer: 20 * 1024 * 1024
11241
11241
  });
11242
11242
  stdout = result.stdout;
@@ -11251,7 +11251,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
11251
11251
  ...args.map(shellQuote2)
11252
11252
  ];
11253
11253
  const result = await execFileAsync6("ssh", [...sshArgs, remoteArgs.join(" ")], {
11254
- env,
11254
+ env: env2,
11255
11255
  maxBuffer: 20 * 1024 * 1024
11256
11256
  });
11257
11257
  stdout = result.stdout;
@@ -11272,7 +11272,7 @@ async function invokeMemberStatus(member, parentWorkspace, chain, options, cliEn
11272
11272
  }
11273
11273
  }
11274
11274
  async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
11275
- const env = { ...process.env, AGENTWHEEL_COMPOSITE_CHAIN: JSON.stringify(chain) };
11275
+ const env2 = { ...process.env, AGENTWHEEL_COMPOSITE_CHAIN: JSON.stringify(chain) };
11276
11276
  try {
11277
11277
  if (member.transport === "local") {
11278
11278
  const result2 = await execFileAsync6(
@@ -11280,7 +11280,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
11280
11280
  [process.argv[1], "--no-update-check", ...args],
11281
11281
  {
11282
11282
  cwd: resolve21(parentWorkspace, member.workspace),
11283
- env,
11283
+ env: env2,
11284
11284
  maxBuffer: 20 * 1024 * 1024
11285
11285
  }
11286
11286
  );
@@ -11295,7 +11295,7 @@ async function runMemberAgentwheel(member, parentWorkspace, args, chain) {
11295
11295
  ...args.map(shellQuote2)
11296
11296
  ];
11297
11297
  const result = await execFileAsync6("ssh", [...sshArguments(member), remoteArgs.join(" ")], {
11298
- env,
11298
+ env: env2,
11299
11299
  maxBuffer: 20 * 1024 * 1024
11300
11300
  });
11301
11301
  return { stdout: result.stdout, stderr: result.stderr };
@@ -11521,13 +11521,21 @@ var catalogueCacheSchema = z13.object({
11521
11521
  fetchedAt: z13.string().datetime(),
11522
11522
  sources: z13.tuple([z13.string().url(), z13.string().url()]),
11523
11523
  enriched: enrichedCatalogueSchema,
11524
- vercel: vercelCatalogueSchema
11524
+ vercel: vercelCatalogueSchema,
11525
+ sourceDigests: z13.object({
11526
+ enriched: z13.string().regex(/^[a-f0-9]{64}$/),
11527
+ vercel: z13.string().regex(/^[a-f0-9]{64}$/)
11528
+ }).optional()
11525
11529
  });
11526
11530
  var catalogueCacheEnvelopeSchema = z13.object({
11527
11531
  version: z13.literal(1),
11528
11532
  fetchedAt: z13.string().datetime(),
11529
11533
  sources: z13.tuple([z13.string().url(), z13.string().url()]),
11530
11534
  contentHash: z13.string().regex(/^[a-f0-9]{64}$/).optional(),
11535
+ sourceDigests: z13.object({
11536
+ enriched: z13.string().regex(/^[a-f0-9]{64}$/),
11537
+ vercel: z13.string().regex(/^[a-f0-9]{64}$/)
11538
+ }).optional(),
11531
11539
  enriched: z13.unknown(),
11532
11540
  vercel: z13.unknown()
11533
11541
  });
@@ -11545,13 +11553,15 @@ var searchResultSchema = z13.object({
11545
11553
  installability: installabilitySchema,
11546
11554
  provenances: z13.array(catalogueProvenanceSchema).min(1),
11547
11555
  score: z13.number().int().nonnegative(),
11548
- matchedFields: z13.array(z13.string())
11556
+ matchedFields: z13.array(z13.string()),
11557
+ semanticScore: z13.number().finite().optional()
11549
11558
  });
11550
11559
  var searchResponseSchema = z13.object({
11551
11560
  schemaVersion: z13.literal(1),
11552
11561
  query: z13.string(),
11553
11562
  scope: searchScopeSchema,
11554
11563
  fromCache: z13.boolean(),
11564
+ searchMode: z13.enum(["lexical", "semantic"]).optional(),
11555
11565
  results: z13.array(searchResultSchema)
11556
11566
  });
11557
11567
 
@@ -11594,24 +11604,35 @@ var CatalogueClient = class {
11594
11604
  return this.fromCache(usableCache, false);
11595
11605
  }
11596
11606
  try {
11597
- const [enriched, vercel] = await Promise.all([
11607
+ const [enrichedPayload, vercelPayload] = await Promise.all([
11598
11608
  this.fetchJson(this.sources[0], enrichedCatalogueSchema),
11599
11609
  this.fetchJson(this.sources[1], vercelCatalogueSchema)
11600
11610
  ]);
11611
+ const { value: enriched, digest: enrichedDigest } = enrichedPayload;
11612
+ const { value: vercel, digest: vercelDigest } = vercelPayload;
11601
11613
  const fetchedAt = this.now().toISOString();
11602
11614
  const cache = {
11603
11615
  version: 1,
11604
11616
  fetchedAt,
11605
11617
  sources: this.sources,
11606
11618
  enriched,
11607
- vercel
11619
+ vercel,
11620
+ sourceDigests: { enriched: enrichedDigest, vercel: vercelDigest }
11608
11621
  };
11609
11622
  const cacheFile = {
11610
11623
  ...cache,
11611
11624
  contentHash: catalogueContentHash(enriched, vercel)
11612
11625
  };
11613
11626
  await writeJsonAtomic(this.cachePath, cacheFile);
11614
- return { enriched, vercel, sources: this.sources, fetchedAt, fromCache: false, stale: false };
11627
+ return {
11628
+ enriched,
11629
+ vercel,
11630
+ sources: this.sources,
11631
+ fetchedAt,
11632
+ fromCache: false,
11633
+ stale: false,
11634
+ sourceDigests: cache.sourceDigests
11635
+ };
11615
11636
  } catch (error) {
11616
11637
  if (!usableCache) throw error;
11617
11638
  const reason = error instanceof Error ? error.message : String(error);
@@ -11652,7 +11673,8 @@ var CatalogueClient = class {
11652
11673
  sources: cache.sources,
11653
11674
  fetchedAt: cache.fetchedAt,
11654
11675
  fromCache: true,
11655
- stale
11676
+ stale,
11677
+ sourceDigests: cache.sourceDigests
11656
11678
  };
11657
11679
  }
11658
11680
  async fetchJson(source, schema) {
@@ -11662,8 +11684,8 @@ var CatalogueClient = class {
11662
11684
  }
11663
11685
  const declaredLength = response.headers.get("content-length");
11664
11686
  if (declaredLength !== null) {
11665
- const bytes = Number(declaredLength);
11666
- if (Number.isFinite(bytes) && bytes > MAX_CATALOGUE_PAYLOAD_BYTES) {
11687
+ const bytes2 = Number(declaredLength);
11688
+ if (Number.isFinite(bytes2) && bytes2 > MAX_CATALOGUE_PAYLOAD_BYTES) {
11667
11689
  throw new Error(`Catalogue payload exceeds 32 MiB limit: ${source}`);
11668
11690
  }
11669
11691
  }
@@ -11671,13 +11693,17 @@ var CatalogueClient = class {
11671
11693
  if (payload.byteLength > MAX_CATALOGUE_PAYLOAD_BYTES) {
11672
11694
  throw new Error(`Catalogue payload exceeds 32 MiB limit: ${source}`);
11673
11695
  }
11696
+ const bytes = new Uint8Array(payload);
11674
11697
  let value;
11675
11698
  try {
11676
11699
  value = JSON.parse(new TextDecoder().decode(payload));
11677
11700
  } catch {
11678
11701
  throw new Error(`Catalogue source returned invalid JSON: ${source}`);
11679
11702
  }
11680
- return schema.parse(value);
11703
+ return {
11704
+ value: schema.parse(value),
11705
+ digest: createHash11("sha256").update(bytes).digest("hex")
11706
+ };
11681
11707
  }
11682
11708
  };
11683
11709
  function defaultCatalogueCachePath() {
@@ -12048,6 +12074,235 @@ function includesToken(normalizedText, token) {
12048
12074
  return normalizedText === token || normalizedText.startsWith(`${token} `) || normalizedText.endsWith(` ${token}`) || normalizedText.includes(` ${token} `);
12049
12075
  }
12050
12076
 
12077
+ // src/semantic/index.ts
12078
+ import { createHash as createHash12 } from "crypto";
12079
+ import { homedir as homedir11 } from "os";
12080
+ import { join as join46 } from "path";
12081
+ import { env, pipeline } from "@huggingface/transformers";
12082
+ var DEFAULT_SEMANTIC_INDEX_URL = "https://raw.githubusercontent.com/NestDevLab/agentwheel-registry/main/catalogue-semantic-index/gte-v1/";
12083
+ var CONTRACT = {
12084
+ schemaVersion: 1,
12085
+ textSchemaVersion: 1,
12086
+ model: {
12087
+ id: "Xenova/gte-small",
12088
+ revision: "5927d1727bb12db490052a1b33265ad78058de08",
12089
+ dimensions: 384,
12090
+ dtype: "q8",
12091
+ pooling: "mean",
12092
+ normalize: true,
12093
+ queryPrefix: "",
12094
+ documentPrefix: ""
12095
+ },
12096
+ vectorFormat: "signed-int8-per-vector-scaled",
12097
+ normFormat: "float32-little-endian"
12098
+ };
12099
+ var SemanticSearchClient = class {
12100
+ constructor(options = {}) {
12101
+ this.options = options;
12102
+ this.fetchImpl = options.fetch ?? fetch;
12103
+ this.indexUrl = ensureTrailingSlash(options.indexUrl ?? DEFAULT_SEMANTIC_INDEX_URL);
12104
+ }
12105
+ options;
12106
+ fetchImpl;
12107
+ indexUrl;
12108
+ async search(request) {
12109
+ if (!request.catalogueDigests) {
12110
+ throw new Error("Semantic search needs a catalogue cache with source checksums. Run again online with --refresh.");
12111
+ }
12112
+ const metadata = await this.fetchMetadata();
12113
+ validateMetadata(metadata, request.catalogueDigests);
12114
+ const [ids, vectors, norms] = await Promise.all([
12115
+ this.fetchIds(metadata.files.ids),
12116
+ this.fetchBinary(metadata.files.vectors),
12117
+ this.fetchBinary(metadata.files.norms)
12118
+ ]);
12119
+ const decodedNorms = decodeFloat32LittleEndian(norms);
12120
+ validateIndexFiles(metadata, ids, vectors, decodedNorms);
12121
+ const query = await (this.options.embed ?? embedQuery)(request.query);
12122
+ if (query.length !== metadata.dimensions) {
12123
+ throw new Error(`Semantic model returned ${query.length} dimensions; expected ${metadata.dimensions}.`);
12124
+ }
12125
+ const ranked = searchInt8Index(vectors, decodedNorms, metadata.dimensions, query, Math.max(100, request.limit * 10));
12126
+ const entries = new Map(request.entries.map((entry) => [entry.id, entry]));
12127
+ const results = [];
12128
+ for (const candidate of ranked) {
12129
+ const entry = entries.get(ids[candidate.row]);
12130
+ if (!entry || !request.includeArchived && entry.archived) continue;
12131
+ if (request.type && entry.type !== request.type) continue;
12132
+ if (request.ecosystem && entry.ecosystem !== request.ecosystem) continue;
12133
+ results.push(toSearchResult(entry, candidate.score, results.length));
12134
+ if (results.length === request.limit) break;
12135
+ }
12136
+ if (results.length === 0) this.options.warn?.("Semantic index had no candidates matching the selected filters.");
12137
+ return results;
12138
+ }
12139
+ async fetchMetadata() {
12140
+ const response = await this.fetchImpl(new URL("metadata.json", this.indexUrl));
12141
+ if (!response.ok) throw new Error(`Semantic index metadata failed (${response.status}).`);
12142
+ let metadata;
12143
+ try {
12144
+ metadata = JSON.parse(await response.text());
12145
+ } catch {
12146
+ throw new Error("Semantic index metadata is not valid JSON.");
12147
+ }
12148
+ return metadata;
12149
+ }
12150
+ async fetchIds(descriptor) {
12151
+ const bytes = await this.fetchBinary(descriptor);
12152
+ let ids;
12153
+ try {
12154
+ ids = JSON.parse(new TextDecoder().decode(bytes));
12155
+ } catch {
12156
+ throw new Error("Semantic index IDs are not valid JSON.");
12157
+ }
12158
+ if (!Array.isArray(ids) || ids.some((id) => typeof id !== "string" || !id)) {
12159
+ throw new Error("Semantic index IDs are invalid.");
12160
+ }
12161
+ return ids;
12162
+ }
12163
+ async fetchBinary(descriptor) {
12164
+ const response = await this.fetchImpl(new URL(descriptor.path, this.indexUrl));
12165
+ if (!response.ok) throw new Error(`Semantic index file failed (${response.status}): ${descriptor.path}`);
12166
+ const bytes = new Uint8Array(await response.arrayBuffer());
12167
+ if (bytes.byteLength !== descriptor.bytes) throw new Error(`Semantic index file size does not match: ${descriptor.path}`);
12168
+ const digest = createHash12("sha256").update(bytes).digest("hex");
12169
+ if (digest !== descriptor.sha256) throw new Error(`Semantic index checksum does not match: ${descriptor.path}`);
12170
+ return bytes;
12171
+ }
12172
+ };
12173
+ async function embedQuery(query) {
12174
+ env.cacheDir = join46(homedir11(), ".agentwheel", "semantic-models");
12175
+ const extractor = await pipeline("feature-extraction", CONTRACT.model.id, {
12176
+ revision: CONTRACT.model.revision,
12177
+ dtype: CONTRACT.model.dtype,
12178
+ session_options: { intraOpNumThreads: 1, interOpNumThreads: 1 }
12179
+ });
12180
+ const output = await extractor(query, { pooling: CONTRACT.model.pooling, normalize: CONTRACT.model.normalize });
12181
+ return Float32Array.from(output.data);
12182
+ }
12183
+ function validateMetadata(metadata, digests) {
12184
+ if (metadata.schemaVersion !== CONTRACT.schemaVersion || metadata.textSchemaVersion !== CONTRACT.textSchemaVersion) {
12185
+ throw new Error("Unsupported semantic index schema.");
12186
+ }
12187
+ if (!Number.isInteger(metadata.count) || metadata.count < 1 || metadata.dimensions !== CONTRACT.model.dimensions) {
12188
+ throw new Error("Semantic index dimensions or count are invalid.");
12189
+ }
12190
+ if (metadata.vectorFormat !== CONTRACT.vectorFormat || metadata.normFormat !== CONTRACT.normFormat) {
12191
+ throw new Error("Semantic index binary format is unsupported.");
12192
+ }
12193
+ for (const [field, value] of Object.entries(CONTRACT.model)) {
12194
+ if (metadata.model?.[field] !== value) throw new Error(`Semantic model ${field} does not match.`);
12195
+ }
12196
+ for (const source of ["enriched", "vercel"]) {
12197
+ if (metadata.catalogue?.[source]?.sha256 !== digests[source]) {
12198
+ throw new Error(`Semantic index catalogue checksum does not match ${source}.`);
12199
+ }
12200
+ }
12201
+ for (const key of ["ids", "vectors", "norms"]) {
12202
+ const descriptor = metadata.files?.[key];
12203
+ if (!descriptor || typeof descriptor.path !== "string" || !Number.isInteger(descriptor.bytes) || descriptor.bytes < 1 || !/^[a-f0-9]{64}$/u.test(descriptor.sha256)) {
12204
+ throw new Error(`Semantic index ${key} descriptor is invalid.`);
12205
+ }
12206
+ }
12207
+ }
12208
+ function validateIndexFiles(metadata, ids, vectors, norms) {
12209
+ if (ids.length !== metadata.count || vectors.length !== metadata.count * metadata.dimensions || norms.length !== metadata.count) {
12210
+ throw new Error("Semantic index files do not match metadata.");
12211
+ }
12212
+ }
12213
+ function decodeFloat32LittleEndian(bytes) {
12214
+ if (bytes.byteLength % Float32Array.BYTES_PER_ELEMENT !== 0) throw new Error("Semantic norm file has an invalid length.");
12215
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
12216
+ const result = new Float32Array(bytes.byteLength / Float32Array.BYTES_PER_ELEMENT);
12217
+ for (let index = 0; index < result.length; index += 1) result[index] = view.getFloat32(index * 4, true);
12218
+ return result;
12219
+ }
12220
+ function searchInt8Index(vectors, norms, dimensions, query, limit) {
12221
+ const results = [];
12222
+ const signed = new Int8Array(vectors.buffer, vectors.byteOffset, vectors.byteLength);
12223
+ for (let row = 0; row < norms.length; row += 1) {
12224
+ let dot = 0;
12225
+ const offset = row * dimensions;
12226
+ for (let column = 0; column < dimensions; column += 1) dot += signed[offset + column] * query[column];
12227
+ const candidate = { row, score: norms[row] > 0 ? dot / norms[row] : 0 };
12228
+ const index = results.findIndex((item) => candidate.score > item.score);
12229
+ results.splice(index === -1 ? results.length : index, 0, candidate);
12230
+ if (results.length > limit) results.pop();
12231
+ }
12232
+ return results;
12233
+ }
12234
+ function toSearchResult(entry, semanticScore, rank) {
12235
+ return {
12236
+ id: entry.id,
12237
+ name: entry.name,
12238
+ description: entry.description,
12239
+ type: entry.type,
12240
+ ...entry.ecosystem ? { ecosystem: entry.ecosystem } : {},
12241
+ tags: entry.tags,
12242
+ provides: entry.provides,
12243
+ ...entry.source ? { source: entry.source } : {},
12244
+ ...entry.repoUrl ? { repoUrl: entry.repoUrl } : {},
12245
+ ...entry.installCommand ? { installCommand: entry.installCommand } : {},
12246
+ installability: entry.installability,
12247
+ provenances: entry.provenances,
12248
+ score: 1e5 - rank,
12249
+ matchedFields: ["semantic"],
12250
+ semanticScore: Number(semanticScore.toFixed(6))
12251
+ };
12252
+ }
12253
+ function ensureTrailingSlash(value) {
12254
+ return value.endsWith("/") ? value : `${value}/`;
12255
+ }
12256
+
12257
+ // src/trial/skill.ts
12258
+ import { createHash as createHash13 } from "crypto";
12259
+ import { readFile as readFile34, stat as stat12 } from "fs/promises";
12260
+ import { join as join47 } from "path";
12261
+ import { parse as parseYaml2 } from "yaml";
12262
+ var MAX_TRIAL_SKILL_BYTES = 512 * 1024;
12263
+ async function createSkillTrial(driver, resolved, selectors) {
12264
+ const scan = await driver.scan(resolved);
12265
+ if (!scan.ok) throw new Error("Skill trial blocked by source scan findings.");
12266
+ const selected = filterArtifactsBySelection(await driver.list(resolved), selectors);
12267
+ const skills = selected.filter((artifact2) => artifact2.type === "skills");
12268
+ if (skills.length !== 1) {
12269
+ throw new Error("Skill trial requires exactly one selected skill. Use --skill <name> or --select skills/<name>.");
12270
+ }
12271
+ const artifact = skills[0];
12272
+ const path = artifact.kind === "dir" ? join47(artifact.sourcePath, "SKILL.md") : artifact.sourcePath;
12273
+ const info = await stat12(path);
12274
+ if (info.size > MAX_TRIAL_SKILL_BYTES) {
12275
+ throw new Error(`Skill trial exceeds the ${MAX_TRIAL_SKILL_BYTES / 1024} KiB content limit.`);
12276
+ }
12277
+ const content = await readFile34(path, "utf8");
12278
+ const frontmatter = readSkillFrontmatter(content, artifact);
12279
+ return {
12280
+ schemaVersion: 1,
12281
+ mode: "read-only",
12282
+ source: resolved.source,
12283
+ ...resolved.resolvedCommit ? { resolvedCommit: resolved.resolvedCommit } : {},
12284
+ findings: scan.findings,
12285
+ skill: {
12286
+ name: artifact.name,
12287
+ relativePath: artifact.relativePath,
12288
+ sha256: createHash13("sha256").update(content).digest("hex"),
12289
+ frontmatter,
12290
+ content
12291
+ }
12292
+ };
12293
+ }
12294
+ function readSkillFrontmatter(content, artifact) {
12295
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
12296
+ if (!match) throw new Error(`Skill trial requires YAML frontmatter: ${artifact.relativePath}`);
12297
+ const parsed = parseYaml2(match[1]);
12298
+ if (!parsed || typeof parsed !== "object") throw new Error(`Skill trial frontmatter is invalid: ${artifact.relativePath}`);
12299
+ const value = parsed;
12300
+ if (typeof value.name !== "string" || !value.name.trim() || typeof value.description !== "string" || !value.description.trim()) {
12301
+ throw new Error(`Skill trial frontmatter needs name and description: ${artifact.relativePath}`);
12302
+ }
12303
+ return { name: value.name, description: value.description };
12304
+ }
12305
+
12051
12306
  // src/cli/index.ts
12052
12307
  var CLI_VERSION = resolveCliVersion();
12053
12308
  var COMPANION_SKILL_SOURCE = "github:NestDevLab/agentwheel";
@@ -12091,13 +12346,13 @@ program.command("list").description("list artifacts exposed by a package source"
12091
12346
  const resolvedInput = await resolvePackageSource(source, targetRoot);
12092
12347
  const selectedArtifacts = selectedArtifactsFromOptionsOrRegistry(options, resolvedInput.registryEntry);
12093
12348
  const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
12094
- const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join46(targetRoot, ".agentwheel", "cache") }))));
12349
+ const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join48(targetRoot, ".agentwheel", "cache") }))));
12095
12350
  const artifacts = filterArtifactsBySelection(await driver.list(resolved), selectedArtifacts);
12096
12351
  for (const artifact of artifacts) {
12097
12352
  console.log(`${artifact.type} ${artifact.name} ${artifact.relativePath}`);
12098
12353
  }
12099
12354
  });
12100
- program.command("search").description("search registry and public catalogue artifacts").argument("<query>", "search query").option("--json", "print the versioned search response as JSON", false).option("--scope <scope>", "search scope: all, registry, enriched, or vercel", "all").option("--type <type>", "artifact type: package, skill, plugin, mcp, or adapter").option("--ecosystem <ecosystem>", "ecosystem: official, openpack, mcp-registry, clawhub, skillkit, or vercel").option("--limit <n>", "maximum number of results (1-100)", "20").option("--include-archived", "include archived catalogue entries", false).option("--refresh", "refresh registry and catalogue caches", false).option("--offline", "use compatible local caches without network access", false).option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (query, options) => {
12355
+ program.command("search").description("search registry and public catalogue artifacts").argument("<query>", "search query").option("--json", "print the versioned search response as JSON", false).option("--scope <scope>", "search scope: all, registry, enriched, or vercel", "all").option("--type <type>", "artifact type: package, skill, plugin, mcp, or adapter").option("--ecosystem <ecosystem>", "ecosystem: official, openpack, mcp-registry, clawhub, skillkit, or vercel").option("--limit <n>", "maximum number of results (1-100)", "20").option("--include-archived", "include archived catalogue entries", false).option("--refresh", "refresh registry and catalogue caches", false).option("--offline", "use compatible local caches without network access", false).option("--semantic", "rank published catalogue entries with the verified semantic index", false).option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (query, options) => {
12101
12356
  const trimmedQuery = query.trim();
12102
12357
  if (!trimmedQuery) {
12103
12358
  throw new Error("Search query must not be empty.");
@@ -12109,17 +12364,28 @@ program.command("search").description("search registry and public catalogue arti
12109
12364
  if (options.refresh && options.offline) {
12110
12365
  throw new Error("--refresh cannot be used with --offline.");
12111
12366
  }
12367
+ if (options.semantic && scope === "registry") {
12368
+ throw new Error("--semantic requires a catalogue scope: all, enriched, or vercel.");
12369
+ }
12112
12370
  const warning = (message) => console.error(message);
12113
12371
  const targetRoot = normalizeTargetRoot(options.targetRoot);
12114
12372
  const registryRequest = scope === "all" || scope === "registry" ? new RegistryClient({ workspaceRoot: targetRoot, offline: options.offline, warn: warning }).getIndex({ refresh: options.refresh }) : void 0;
12115
- const catalogueRequest = scope === "all" || scope === "enriched" || scope === "vercel" ? new CatalogueClient({ offline: options.offline, warn: warning }).getIndex({ refresh: options.refresh }) : void 0;
12373
+ const catalogueRequest = scope === "all" || scope === "enriched" || scope === "vercel" ? new CatalogueClient({ offline: options.offline, warn: warning }).getIndex({ refresh: options.refresh || options.semantic && !options.offline }) : void 0;
12116
12374
  const [registryIndex, catalogueIndex] = await Promise.all([registryRequest, catalogueRequest]);
12117
12375
  const entries = buildSearchEntries({
12118
12376
  registry: registryIndex?.entries,
12119
12377
  enriched: scope === "all" || scope === "enriched" ? catalogueIndex?.enriched : void 0,
12120
12378
  vercel: scope === "all" || scope === "vercel" ? catalogueIndex?.vercel : void 0
12121
12379
  });
12122
- const results = searchEntries(entries, trimmedQuery, {
12380
+ const results = options.semantic ? await new SemanticSearchClient({ warn: warning }).search({
12381
+ query: trimmedQuery,
12382
+ entries,
12383
+ catalogueDigests: catalogueIndex?.sourceDigests,
12384
+ type,
12385
+ ecosystem,
12386
+ limit,
12387
+ includeArchived: options.includeArchived
12388
+ }) : searchEntries(entries, trimmedQuery, {
12123
12389
  type,
12124
12390
  ecosystem,
12125
12391
  limit,
@@ -12131,7 +12397,8 @@ program.command("search").description("search registry and public catalogue arti
12131
12397
  query: trimmedQuery,
12132
12398
  scope,
12133
12399
  fromCache: loadedIndexes.every((index) => index.fromCache),
12134
- results
12400
+ results,
12401
+ ...options.semantic ? { searchMode: "semantic" } : {}
12135
12402
  };
12136
12403
  if (options.json) {
12137
12404
  console.log(JSON.stringify(response, null, 2));
@@ -12139,11 +12406,30 @@ program.command("search").description("search registry and public catalogue arti
12139
12406
  }
12140
12407
  printSearchResults(trimmedQuery, results);
12141
12408
  });
12409
+ program.command("try").description("read and validate one skill for the current task without installing it").argument("<source>", "package source").option("--driver <driver>", "source driver").option("--json", "print the read-only skill trial as JSON", false).option("-t, --target-root <path>", "workspace root", process.cwd()).option("--select <type/name>", "select exactly one skill artifact", collectSelectOption, []).option("--skill <name>", "select exactly one skill by name", collectSkillOption, []).action(async (source, options) => {
12410
+ const targetRoot = normalizeTargetRoot(options.targetRoot);
12411
+ const resolvedInput = await resolvePackageSource(source, targetRoot);
12412
+ const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
12413
+ const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, {
12414
+ cacheRoot: join48(targetRoot, ".agentwheel", "cache")
12415
+ }))));
12416
+ const trial = await createSkillTrial(driver, resolved, selectedArtifactsFromOptionsOrRegistry(options, resolvedInput.registryEntry));
12417
+ if (options.json) {
12418
+ console.log(JSON.stringify(trial, null, 2));
12419
+ return;
12420
+ }
12421
+ console.log(`Read-only skill trial: ${trial.skill.name}`);
12422
+ console.log(`Source: ${trial.source}`);
12423
+ console.log(`Description: ${trial.skill.frontmatter.description}`);
12424
+ console.log("No configuration or runtime files were changed.");
12425
+ console.log("\n--- SKILL.md ---\n");
12426
+ console.log(trial.skill.content);
12427
+ });
12142
12428
  program.command("scan").description("scan a package source for validation findings").argument("<source>", "package source").option("--driver <driver>", "source driver").option("-t, --target-root <path>", "workspace root", process.cwd()).action(async (source, options) => {
12143
12429
  const targetRoot = normalizeTargetRoot(options.targetRoot);
12144
12430
  const resolvedInput = await resolvePackageSource(source, targetRoot);
12145
12431
  const driver = getSourceDriver(options.driver ?? inferSourceDriverName(resolvedInput.source));
12146
- const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join46(targetRoot, ".agentwheel", "cache") }))));
12432
+ const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(resolvedInput.source, { cacheRoot: join48(targetRoot, ".agentwheel", "cache") }))));
12147
12433
  const result = await driver.scan(resolved);
12148
12434
  if (result.findings.length === 0) {
12149
12435
  console.log("Scan ok: no findings");
@@ -12411,7 +12697,7 @@ journalCommand.command("list").description("show pending apply journals for reso
12411
12697
  if (!journal) continue;
12412
12698
  pending += 1;
12413
12699
  console.log(`PENDING ${state.adapter.name}/${state.installationType} at ${state.installRoot}`);
12414
- console.log(` journal: ${join46(state.installRoot, ".agentwheel", `${state.state.stateKey}.apply-journal.json`)}`);
12700
+ console.log(` journal: ${join48(state.installRoot, ".agentwheel", `${state.state.stateKey}.apply-journal.json`)}`);
12415
12701
  console.log(` stateKey: ${state.state.stateKey}`);
12416
12702
  console.log(` createdAt: ${journal.createdAt}`);
12417
12703
  console.log(` updatedAt: ${journal.updatedAt}`);
@@ -12714,7 +13000,7 @@ async function packageEntryFromSource(source, targetRoot, options) {
12714
13000
  const bundle = await stageSource(driver, resolvedSource, {
12715
13001
  workspaceRoot: targetRoot,
12716
13002
  adapter,
12717
- cacheRoot: join46(targetRoot, ".agentwheel", "cache"),
13003
+ cacheRoot: join48(targetRoot, ".agentwheel", "cache"),
12718
13004
  mode: options.mode,
12719
13005
  ref: initialVersion?.ref,
12720
13006
  frozenLock: lockMode,
@@ -13108,7 +13394,7 @@ function scopeUpdatePlanToDependencies(result, selectors, previousLock, manifest
13108
13394
  selectedPreviousNodeIds,
13109
13395
  selectedRootIds
13110
13396
  );
13111
- const graphLockDigest = createHash12("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
13397
+ const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
13112
13398
  return {
13113
13399
  ...result,
13114
13400
  bundle: { ...result.bundle, graphLock },
@@ -13276,7 +13562,7 @@ function scopeUpdatePlanToRoot(result, rootId, previousLock, manifest) {
13276
13562
  selectedPreviousNodeIds,
13277
13563
  /* @__PURE__ */ new Set([rootId])
13278
13564
  );
13279
- const graphLockDigest = createHash12("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
13565
+ const graphLockDigest = createHash14("sha256").update(canonicalGraphLockJson(graphLock)).digest("hex");
13280
13566
  return {
13281
13567
  ...scoped,
13282
13568
  bundle: { ...scoped.bundle, graphLock },
@@ -13348,7 +13634,7 @@ function keepManifestEntryOperation(entry, targetRoot, scopeDescription, operati
13348
13634
  artifactType: entry.artifactType,
13349
13635
  artifactName: entry.artifactName,
13350
13636
  kind: entry.kind,
13351
- destPath: operation?.destPath ?? join46(targetRoot, entry.path),
13637
+ destPath: operation?.destPath ?? join48(targetRoot, entry.path),
13352
13638
  relativeDestPath: entry.path,
13353
13639
  desiredHash: entry.sourceHash,
13354
13640
  currentHash: operation?.currentHash ?? entry.hash,
@@ -13933,12 +14219,12 @@ async function printDoctor(target, options) {
13933
14219
  const requestedSkills = doctorSkillRequests(target, options);
13934
14220
  const skills = [];
13935
14221
  for (const request of requestedSkills) {
13936
- const skillPath = join46(state.installRoot, targetMapping.dest, request.name);
14222
+ const skillPath = join48(state.installRoot, targetMapping.dest, request.name);
13937
14223
  const exists = await pathExists(skillPath);
13938
14224
  const manifestEntry = manifest?.entries.find((entry) => {
13939
14225
  if (entry.artifactType !== "skills") return false;
13940
14226
  const legacyInstallName = "installName" in entry && typeof entry.installName === "string" ? entry.installName : void 0;
13941
- return entry.artifactName === request.name || legacyInstallName === request.name || entry.path === join46(targetMapping.dest, request.name);
14227
+ return entry.artifactName === request.name || legacyInstallName === request.name || entry.path === join48(targetMapping.dest, request.name);
13942
14228
  });
13943
14229
  const status = manifestEntry ? "managed" : exists ? "present-unmanaged" : "missing";
13944
14230
  skills.push({
@@ -14018,7 +14304,7 @@ function doctorSkillLabel(name) {
14018
14304
  return `${name} skill`;
14019
14305
  }
14020
14306
  function isSyncwheelWorkspace(targetRoot) {
14021
- return existsSync(join46(targetRoot, ".syncwheel", "manifest.json"));
14307
+ return existsSync(join48(targetRoot, ".syncwheel", "manifest.json"));
14022
14308
  }
14023
14309
  function skillInstallCommand(adapter, installationType, options, skill, behavior = {}) {
14024
14310
  const args = [
@@ -14086,7 +14372,7 @@ function normalizeRuntimeScopeOptions(options, behavior = {}) {
14086
14372
  }
14087
14373
  const canDefaultTargetRoot = !options.agent && !options.all && !options.allDetected && !options.profile;
14088
14374
  if (!targetRoot && canDefaultTargetRoot && (options.user || installationType === "user" || behavior.defaultUser)) {
14089
- targetRoot = homedir11();
14375
+ targetRoot = homedir12();
14090
14376
  }
14091
14377
  if (!installationType && behavior.defaultUser) {
14092
14378
  installationType = "user";
@@ -14106,12 +14392,12 @@ function looksLikeSourceSpecifier(value) {
14106
14392
  return value.includes(":") || value.startsWith("/") || value.startsWith("./") || value.startsWith("../") || value === "~" || value.startsWith("~/");
14107
14393
  }
14108
14394
  function normalizeCliPath(value) {
14109
- if (value === "~") return homedir11();
14110
- if (value.startsWith("~/")) return resolve22(homedir11(), value.slice(2));
14395
+ if (value === "~") return homedir12();
14396
+ if (value.startsWith("~/")) return resolve22(homedir12(), value.slice(2));
14111
14397
  return resolve22(value);
14112
14398
  }
14113
14399
  function isHomePath(path) {
14114
- return resolve22(path) === resolve22(homedir11());
14400
+ return resolve22(path) === resolve22(homedir12());
14115
14401
  }
14116
14402
  function adapterListFromOption(adapter) {
14117
14403
  if (!adapter) return [];
@@ -14166,10 +14452,10 @@ function filterUninstallPlanBySelection(plan, selected) {
14166
14452
  };
14167
14453
  }
14168
14454
  async function initPackage(root) {
14169
- await mkdir23(join46(root, "instructions"), { recursive: true });
14170
- await mkdir23(join46(root, "rules"), { recursive: true });
14171
- await mkdir23(join46(root, "skills"), { recursive: true });
14172
- const manifestPath = join46(root, "openpack.json");
14455
+ await mkdir23(join48(root, "instructions"), { recursive: true });
14456
+ await mkdir23(join48(root, "rules"), { recursive: true });
14457
+ await mkdir23(join48(root, "skills"), { recursive: true });
14458
+ const manifestPath = join48(root, "openpack.json");
14173
14459
  const manifest = {
14174
14460
  schemaVersion: 2,
14175
14461
  name: "example/agentwheel-package",
@@ -14182,7 +14468,7 @@ async function initPackage(root) {
14182
14468
  };
14183
14469
  await writeFile22(manifestPath, `${JSON.stringify(manifest, null, 2)}
14184
14470
  `, "utf8");
14185
- await writeFile22(join46(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
14471
+ await writeFile22(join48(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
14186
14472
  }
14187
14473
  async function defaultBootstrapPackage(_root) {
14188
14474
  const packageRoot = await findAgentwheelPackageRoot(dirname32(fileURLToPath3(import.meta.url)));
@@ -14287,6 +14573,7 @@ function printSearchResults(query, results) {
14287
14573
  `${index + 1}. ${result.name} [type=${result.type}; ecosystem=${ecosystem}; installability=${result.installability}; provenance=${provenances}]`
14288
14574
  );
14289
14575
  console.log(` ${result.description || "(no description)"}`);
14576
+ if (result.semanticScore !== void 0) console.log(` Semantic score: ${result.semanticScore}`);
14290
14577
  if (result.installCommand) {
14291
14578
  console.log(` Install: ${result.installCommand}`);
14292
14579
  } else if (result.source) {
package/install.md CHANGED
@@ -11,8 +11,9 @@ commands, subagents, MCP, hooks, settings, plugins, and fragments.
11
11
  1. Inspect the environment.
12
12
  2. Install the `agentwheel` CLI if it is missing or too old.
13
13
  3. Verify the CLI.
14
- 4. Install the Agentwheel companion skill into the user's active runtime when requested.
15
- 5. Show the user how to browse or install catalogue resources.
14
+ 4. Install the Agentwheel management skill into the user's active runtime when requested.
15
+ 5. Offer the separate proactive discovery skill when the user wants automatic capability suggestions.
16
+ 6. Show the user how to browse or install catalogue resources.
16
17
 
17
18
  If the user explicitly asked you to install Agentwheel, you may run the installation commands below.
18
19
  Ask before destructive operations, `--force`, remote publishing, local adapter code execution, or
@@ -46,7 +47,7 @@ agentwheel --version
46
47
  agentwheel doctor --help
47
48
  ```
48
49
 
49
- ## Install The Companion Skill
50
+ ## Install The Companion Skills
50
51
 
51
52
  Pick the adapter that matches the active runtime:
52
53
 
@@ -70,6 +71,15 @@ agentwheel doctor --adapter claude --user
70
71
  agentwheel install github:NestDevLab/agentwheel --adapter claude --user --skill agentwheel
71
72
  ```
72
73
 
74
+ The `agentwheel` skill is not proactive. If the user wants the agent to notice capability gaps,
75
+ suggest up to three semantic matches, and offer read-only trials, preview and install the separate
76
+ discovery skill:
77
+
78
+ ```bash
79
+ agentwheel install github:NestDevLab/agentwheel --adapter claude --user --skill agentwheel-discovery --dry-run
80
+ agentwheel install github:NestDevLab/agentwheel --adapter claude --user --skill agentwheel-discovery
81
+ ```
82
+
73
83
  If the user wants to preview first, add `--dry-run` to the `install` command.
74
84
 
75
85
  ## Use The Catalogue
@@ -115,6 +125,7 @@ npx agentwheel@latest registry publish https://github.com/owner/repo
115
125
 
116
126
  - `agentwheel --version` works.
117
127
  - `agentwheel doctor` runs for the selected adapter.
118
- - The companion skill is installed if the user requested it.
128
+ - The management skill is installed if the user requested it.
129
+ - The discovery skill is installed only if the user requested proactive suggestions.
119
130
  - Any catalogue resource install uses the adapter and installation type the user intended.
120
131
  - Catalogue submissions use `agentwheel registry publish` unless the user explicitly wants a manual registry PR.
package/openpack.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "name": "NestDevLab/agentwheel",
4
- "version": "0.16.4",
4
+ "version": "0.16.5",
5
5
  "provides": [
6
6
  { "type": "skills", "path": "skills" }
7
7
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentwheel",
3
- "version": "0.16.4",
3
+ "version": "0.16.5",
4
4
  "description": "Weave skills, rules, and instructions across every AI agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -49,6 +49,7 @@
49
49
  "typecheck": "tsc --noEmit"
50
50
  },
51
51
  "dependencies": {
52
+ "@huggingface/transformers": "4.2.0",
52
53
  "@skillkit/core": "1.24.0",
53
54
  "commander": "14.0.2",
54
55
  "jsonc-parser": "3.3.1",
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: agentwheel
3
- description: Discover or manage reusable agent artifacts with Agentwheel. Use when substantive work exposes a missing capability, repeated manual workflow, or unavailable integration, when a requested artifact may already exist, and when adding, installing, updating, customizing, ejecting, or uninstalling agent artifacts across runtimes.
3
+ description: Manage and explicitly inspect reusable agent artifacts with Agentwheel. Use when the user asks to search, add, install, update, customize, eject, or uninstall skills, integrations, workflows, or other OpenPack artifacts across runtimes. For automatic capability suggestions, install the separate agentwheel-discovery skill.
4
4
  allowed-tools: [Bash]
5
5
  license: MIT
6
6
  metadata:
7
7
  author: NestDevLab
8
- version: "0.16.4"
8
+ version: "0.16.5"
9
9
  ---
10
10
 
11
11
  # agentwheel
@@ -73,30 +73,34 @@ agentwheel init package
73
73
 
74
74
  `agentwheel init package` creates `openpack.json`, `instructions/`, `rules/`, `skills/`, and `instructions/AGENTS.md`.
75
75
 
76
- ## Discovery And Recommendations
76
+ ## Explicit Discovery
77
77
 
78
78
  Search configured registries and public catalogue sources:
79
79
 
80
80
  ```bash
81
81
  agentwheel search "<query>"
82
82
  agentwheel search "<query>" --json --limit 10
83
+ agentwheel search "<query>" --semantic --json --limit 10
83
84
  ```
84
85
 
85
- Search automatically when substantive work exposes a missing capability, repeated manual workflow, unavailable integration, or explicit request for a reusable artifact. This is a per-turn judgment, not a background monitor; delegated agents follow the same trigger when the skill is available.
86
+ Search when the user explicitly asks to find, compare, or evaluate an artifact. Search results are
87
+ proposals, not approval, and never change desired state by themselves.
86
88
 
87
- Before searching, inspect the installed skill/tool inventory available in the current session. If nothing suitable is already present:
89
+ ### Semantic catalogue search
88
90
 
89
- 1. Extract the capability and constraints from the complete request.
90
- 2. Generate one to four short lexical queries using capability terms, synonyms, runtime names, and artifact types. Prefer English catalogue terms when the request uses another language.
91
- 3. Run one `agentwheel search "<query>" --json --limit 10` per variant. Stop after four calls; do not recursively refine without new user requirements.
92
- 4. Merge results by stable `id`. Treat CLI scores as retrieval signals, not semantic confidence.
93
- 5. Rerank against the original request using capabilities, runtime or ecosystem, artifact type, description, tags, `provides`, and installability. Do not infer capabilities absent from result metadata.
94
- 6. Suggest zero to three distinct artifacts. For each, give its name or source, one evidence-based match reason, installability, and a safe next command.
95
- 7. Wait for explicit approval before `add`, `install`, plugin execution, or configuration changes.
91
+ Use `--semantic` for a capability request whose wording is unlikely to match catalogue labels, or after bounded lexical search returns only weak matches. It queries the same published catalogue vector index used by the website and validates its checksums against the loaded catalogue before ranking. It is opt-in because first use may download the model and index assets.
96
92
 
97
- For automatic suggestions, search once per distinct capability gap. Skip discovery when the user explicitly wants custom implementation, has already selected an artifact, an installed artifact clearly satisfies the request, candidates are only weak lexical matches, or the same suggestion was declined or shown without new evidence. Continue useful work while searching when possible; do not interrupt solely to advertise marginal matches.
93
+ ```bash
94
+ agentwheel search "remember corrections from earlier conversations" --semantic --json --limit 10
95
+ ```
96
+
97
+ Do not use `--semantic` for a registry-only search, and never describe a semantic score as proof
98
+ that an artifact implements a capability.
99
+
100
+ Use a trial only for instruction skills. Plugins, MCP servers, hooks, commands, and settings are not trialled because reading them is not equivalent to safely executing them in an isolated runtime.
98
101
 
99
- Search recommendations are conversational only: they do not select OpenPack `suggests`, mutate desired state, or imply installation approval.
102
+ Install `skills/agentwheel-discovery` separately when the user wants proactive capability-gap
103
+ detection, bounded semantic recommendations, and read-only trial suggestions during unrelated work.
100
104
 
101
105
  Registry maintenance remains explicit:
102
106
 
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: agentwheel-discovery
3
+ description: Proactively discover reusable skills, integrations, and workflows when a user's request exposes a missing capability, repeated manual work, or unavailable integration. Use Agentwheel semantic search to suggest up to three evidence-backed matches and offer a read-only trial without installing or changing anything.
4
+ allowed-tools: [Bash]
5
+ license: MIT
6
+ metadata:
7
+ author: NestDevLab
8
+ version: "0.16.5"
9
+ ---
10
+
11
+ # Agentwheel Discovery
12
+
13
+ Find reusable capabilities during normal work without silently changing the user's environment.
14
+ This skill is optional: install it only when proactive recommendations are wanted. The separate
15
+ `agentwheel` skill owns artifact management and explicit installation workflows.
16
+
17
+ ## Safety
18
+
19
+ - Inspect the skills and tools already available in the current session before searching.
20
+ - Search and trial are read-only. Never add, install, enable, execute, or change configuration
21
+ without explicit approval for the artifact and target scope.
22
+ - Treat retrieval scores as ranking signals, not proof that an artifact implements a capability.
23
+ - Continue useful work while searching when possible; do not interrupt solely to advertise weak
24
+ or marginal matches.
25
+
26
+ ## When To Search
27
+
28
+ Search automatically when substantive work exposes a missing capability, repeated manual workflow,
29
+ or unavailable integration. This is a per-turn judgment, not a background monitor. Delegated agents
30
+ follow the same trigger when this skill is available.
31
+
32
+ Skip discovery when the user explicitly wants a custom implementation, has already selected an
33
+ artifact, an installed artifact clearly satisfies the request, candidates are only weak lexical
34
+ matches, or the same suggestion was declined or shown without new evidence.
35
+
36
+ ## Discovery Workflow
37
+
38
+ 1. Extract the capability and constraints from the complete request.
39
+ 2. Run one fast semantic search using the full capability request:
40
+ `agentwheel search "<query>" --semantic --json --limit 10`.
41
+ Use exact lexical search first only when the user supplied an artifact name, source, or registry
42
+ identifier.
43
+ 3. If semantic candidates are weak, absent, or need a precise runtime or type constraint, generate
44
+ up to three short lexical variants using capability terms, synonyms, runtime names, and artifact
45
+ types. Prefer English catalogue terms for non-English requests. Stop after four total searches;
46
+ do not recursively refine without new user requirements.
47
+ 4. Merge results by stable `id`. Treat CLI scores as retrieval signals, not semantic confidence.
48
+ 5. Rerank against the original request using capabilities, runtime or ecosystem, artifact type,
49
+ description, tags, `provides`, and installability. Do not infer capabilities absent from result
50
+ metadata.
51
+ 6. Suggest zero to three distinct artifacts. For each, give its name or source, one evidence-based
52
+ match reason, installability, and a read-only trial command before any installation command.
53
+ 7. Search once per distinct capability gap. Do not repeat a recommendation without new evidence.
54
+
55
+ Do not use `--semantic` for a registry-only search. The semantic path queries the same published
56
+ catalogue vector index used by the website and validates its checksums against the loaded catalogue.
57
+ First use may download the model and index assets.
58
+
59
+ ## Read-Only Trial
60
+
61
+ When the user wants to evaluate one instruction skill, run:
62
+
63
+ ```bash
64
+ agentwheel try <source> --skill <name> --json
65
+ ```
66
+
67
+ A trial fetches, scans, validates, and reads exactly one `SKILL.md` for the current task. It does not
68
+ add a package, change configuration, write runtime files, or execute code. Do not trial plugins,
69
+ MCP servers, hooks, commands, or settings because reading their metadata is not safe execution.
70
+
71
+ ## Recommendation Contract
72
+
73
+ Search recommendations are conversational only: they do not select OpenPack `suggests`, mutate
74
+ desired state, or imply installation approval. Wait for explicit approval before `add`, `install`,
75
+ plugin execution, or configuration changes.