yarkr-cli 0.1.5 → 1.0.1

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 (3) hide show
  1. package/README.md +48 -8
  2. package/dist/yarkr.js +345 -173
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -4,20 +4,60 @@ Install skills, agents and MCP toolsets from [Yarkr](https://yarkr.com) into the
4
4
  Claude Code, Codex, opencode, Cursor and more.
5
5
 
6
6
  ```sh
7
- npx yarkr-cli add posterus-ltd/code-review-checklist # a public package, no account needed
8
- npx yarkr-cli add posterus-ltd/github --harness codex # pick the harnesses to write
7
+ npm install -g yarkr-cli # Node 22 or newer; or run any command with `npx yarkr-cli …`
8
+ yarkr add posterus-ltd/code-review-checklist # a public package, no account needed
9
+ yarkr add posterus-ltd/github --harness codex # pick the harnesses to write
9
10
  ```
10
11
 
11
- In a project, `yarkr init` creates `yarkr.json`; `yarkr add` and `yarkr sync` then pin every package in `yarkr.lock`:
12
+ ## Sync an ark to your machine
13
+
14
+ An ark is a curated set of skills, agents and toolsets hosted in the registry. `yarkr sync` writes each one into the
15
+ harness's own layout (`~/.claude/skills/…`, `~/.agents/…`, MCP configs and so on):
16
+
17
+ ```sh
18
+ yarkr arks # list public arks
19
+ yarkr sync acme/starter # install one into your home directory
20
+ yarkr sync acme/starter --harness claude-code # only write Claude Code's files
21
+ yarkr ark # what your arks give you, and which version
22
+ ```
23
+
24
+ Signed in, a bare `yarkr sync` installs every ark you're a member of, plus your personal ark. Run it again to pick up
25
+ whatever an admin added or removed; `yarkr sync --check` exits 1 when you're behind.
26
+
27
+ ## Set up an existing repo
28
+
29
+ In a project, `yarkr init` creates `yarkr.json` (and `.yarkrrc`), detecting the harnesses already used there.
30
+ `yarkr add` and `yarkr sync` then pin every package in `yarkr.lock`, so teammates and CI get the same releases:
12
31
 
13
32
  ```sh
14
- npm install -g yarkr-cli
15
- yarkr init
33
+ cd my-repo
34
+ yarkr init --ark acme/engineering # optional: install a team's ark into this project
16
35
  yarkr add posterus-ltd/playwright-test-authoring
17
- yarkr sync --check # in CI: exit 1 when anything is out of date
36
+ git add yarkr.json yarkr.lock .yarkrrc .claude AGENTS.md && git commit
37
+ yarkr sync --check # in CI: exit 1 when anything is out of date
38
+ ```
39
+
40
+ To publish from the repo instead, there's nothing to init: Yarkr finds `SKILL.md` folders, `agents/*.md` and
41
+ `.mcp.json` on its own. Anything else (a skill that's a single markdown file, or a folder with a README instead of
42
+ SKILL.md) goes in one `yarkr.index.yaml` at the root, and nothing has to move. Lint it before you push:
43
+
44
+ ```sh
45
+ yarkr lint # what will be published, and what the hub would refuse
46
+ yarkr lint --strict # in CI: fail on warnings too
47
+ yarkr guide index-repo -o ~/.claude/skills/index-repo/ # a skill your agent uses to write the index
48
+ ```
49
+
50
+ ## Sign in for private packages
51
+
52
+ Public packages and public arks work signed out. Your org's private packages and arks need a token:
53
+
54
+ ```sh
55
+ yarkr login # signs in in the browser; the token goes to your keychain (or ~/.yarkrrc)
56
+ yarkr login --no-browser # over SSH: open the printed link anywhere, then paste the token
57
+ yarkr whoami # check who you're signed in as
18
58
  ```
19
59
 
20
- `yarkr` uses the public hub at `https://yarkr.com` unless you point it at your own instance with
21
- `yarkr login --registry <url>`. Node 22 or newer.
60
+ In CI, store a read-only token as a secret and set `YARKR_TOKEN`. `yarkr` uses the public hub at `https://yarkr.com`
61
+ unless you point it at your own instance with `yarkr login --registry <url>`.
22
62
 
23
63
  Docs: [docs/cli.md](https://github.com/posterus-ltd/yarkr-public/blob/main/docs/cli.md) · Source: [github.com/posterus-ltd/yarkr-public](https://github.com/posterus-ltd/yarkr-public) · MIT
package/dist/yarkr.js CHANGED
@@ -1206,7 +1206,7 @@ var require_command = __commonJS({
1206
1206
  "use strict";
1207
1207
  var EventEmitter = __require("events").EventEmitter;
1208
1208
  var childProcess = __require("child_process");
1209
- var path17 = __require("path");
1209
+ var path18 = __require("path");
1210
1210
  var fs = __require("fs");
1211
1211
  var process2 = __require("process");
1212
1212
  var { Argument: Argument2, humanReadableArgName } = require_argument();
@@ -2219,9 +2219,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2219
2219
  let launchWithNode = false;
2220
2220
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2221
2221
  function findFile(baseDir, baseName) {
2222
- const localBin = path17.resolve(baseDir, baseName);
2222
+ const localBin = path18.resolve(baseDir, baseName);
2223
2223
  if (fs.existsSync(localBin)) return localBin;
2224
- if (sourceExt.includes(path17.extname(baseName))) return void 0;
2224
+ if (sourceExt.includes(path18.extname(baseName))) return void 0;
2225
2225
  const foundExt = sourceExt.find(
2226
2226
  (ext) => fs.existsSync(`${localBin}${ext}`)
2227
2227
  );
@@ -2239,17 +2239,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
2239
2239
  } catch {
2240
2240
  resolvedScriptPath = this._scriptPath;
2241
2241
  }
2242
- executableDir = path17.resolve(
2243
- path17.dirname(resolvedScriptPath),
2242
+ executableDir = path18.resolve(
2243
+ path18.dirname(resolvedScriptPath),
2244
2244
  executableDir
2245
2245
  );
2246
2246
  }
2247
2247
  if (executableDir) {
2248
2248
  let localFile = findFile(executableDir, executableFile);
2249
2249
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2250
- const legacyName = path17.basename(
2250
+ const legacyName = path18.basename(
2251
2251
  this._scriptPath,
2252
- path17.extname(this._scriptPath)
2252
+ path18.extname(this._scriptPath)
2253
2253
  );
2254
2254
  if (legacyName !== this._name) {
2255
2255
  localFile = findFile(
@@ -2260,7 +2260,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2260
2260
  }
2261
2261
  executableFile = localFile || executableFile;
2262
2262
  }
2263
- launchWithNode = sourceExt.includes(path17.extname(executableFile));
2263
+ launchWithNode = sourceExt.includes(path18.extname(executableFile));
2264
2264
  let proc;
2265
2265
  if (process2.platform !== "win32") {
2266
2266
  if (launchWithNode) {
@@ -3175,7 +3175,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3175
3175
  * @return {Command}
3176
3176
  */
3177
3177
  nameFromFilename(filename) {
3178
- this._name = path17.basename(filename, path17.extname(filename));
3178
+ this._name = path18.basename(filename, path18.extname(filename));
3179
3179
  return this;
3180
3180
  }
3181
3181
  /**
@@ -3189,9 +3189,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3189
3189
  * @param {string} [path]
3190
3190
  * @return {(string|null|Command)}
3191
3191
  */
3192
- executableDir(path18) {
3193
- if (path18 === void 0) return this._executableDir;
3194
- this._executableDir = path18;
3192
+ executableDir(path19) {
3193
+ if (path19 === void 0) return this._executableDir;
3194
+ this._executableDir = path19;
3195
3195
  return this;
3196
3196
  }
3197
3197
  /**
@@ -3547,17 +3547,17 @@ var require_visit = __commonJS({
3547
3547
  visit2.BREAK = BREAK;
3548
3548
  visit2.SKIP = SKIP2;
3549
3549
  visit2.REMOVE = REMOVE;
3550
- function visit_(key, node2, visitor, path17) {
3551
- const ctrl = callVisitor(key, node2, visitor, path17);
3550
+ function visit_(key, node2, visitor, path18) {
3551
+ const ctrl = callVisitor(key, node2, visitor, path18);
3552
3552
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
3553
- replaceNode(key, path17, ctrl);
3554
- return visit_(key, ctrl, visitor, path17);
3553
+ replaceNode(key, path18, ctrl);
3554
+ return visit_(key, ctrl, visitor, path18);
3555
3555
  }
3556
3556
  if (typeof ctrl !== "symbol") {
3557
3557
  if (identity.isCollection(node2)) {
3558
- path17 = Object.freeze(path17.concat(node2));
3558
+ path18 = Object.freeze(path18.concat(node2));
3559
3559
  for (let i = 0; i < node2.items.length; ++i) {
3560
- const ci = visit_(i, node2.items[i], visitor, path17);
3560
+ const ci = visit_(i, node2.items[i], visitor, path18);
3561
3561
  if (typeof ci === "number")
3562
3562
  i = ci - 1;
3563
3563
  else if (ci === BREAK)
@@ -3568,13 +3568,13 @@ var require_visit = __commonJS({
3568
3568
  }
3569
3569
  }
3570
3570
  } else if (identity.isPair(node2)) {
3571
- path17 = Object.freeze(path17.concat(node2));
3572
- const ck = visit_("key", node2.key, visitor, path17);
3571
+ path18 = Object.freeze(path18.concat(node2));
3572
+ const ck = visit_("key", node2.key, visitor, path18);
3573
3573
  if (ck === BREAK)
3574
3574
  return BREAK;
3575
3575
  else if (ck === REMOVE)
3576
3576
  node2.key = null;
3577
- const cv = visit_("value", node2.value, visitor, path17);
3577
+ const cv = visit_("value", node2.value, visitor, path18);
3578
3578
  if (cv === BREAK)
3579
3579
  return BREAK;
3580
3580
  else if (cv === REMOVE)
@@ -3595,17 +3595,17 @@ var require_visit = __commonJS({
3595
3595
  visitAsync.BREAK = BREAK;
3596
3596
  visitAsync.SKIP = SKIP2;
3597
3597
  visitAsync.REMOVE = REMOVE;
3598
- async function visitAsync_(key, node2, visitor, path17) {
3599
- const ctrl = await callVisitor(key, node2, visitor, path17);
3598
+ async function visitAsync_(key, node2, visitor, path18) {
3599
+ const ctrl = await callVisitor(key, node2, visitor, path18);
3600
3600
  if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
3601
- replaceNode(key, path17, ctrl);
3602
- return visitAsync_(key, ctrl, visitor, path17);
3601
+ replaceNode(key, path18, ctrl);
3602
+ return visitAsync_(key, ctrl, visitor, path18);
3603
3603
  }
3604
3604
  if (typeof ctrl !== "symbol") {
3605
3605
  if (identity.isCollection(node2)) {
3606
- path17 = Object.freeze(path17.concat(node2));
3606
+ path18 = Object.freeze(path18.concat(node2));
3607
3607
  for (let i = 0; i < node2.items.length; ++i) {
3608
- const ci = await visitAsync_(i, node2.items[i], visitor, path17);
3608
+ const ci = await visitAsync_(i, node2.items[i], visitor, path18);
3609
3609
  if (typeof ci === "number")
3610
3610
  i = ci - 1;
3611
3611
  else if (ci === BREAK)
@@ -3616,13 +3616,13 @@ var require_visit = __commonJS({
3616
3616
  }
3617
3617
  }
3618
3618
  } else if (identity.isPair(node2)) {
3619
- path17 = Object.freeze(path17.concat(node2));
3620
- const ck = await visitAsync_("key", node2.key, visitor, path17);
3619
+ path18 = Object.freeze(path18.concat(node2));
3620
+ const ck = await visitAsync_("key", node2.key, visitor, path18);
3621
3621
  if (ck === BREAK)
3622
3622
  return BREAK;
3623
3623
  else if (ck === REMOVE)
3624
3624
  node2.key = null;
3625
- const cv = await visitAsync_("value", node2.value, visitor, path17);
3625
+ const cv = await visitAsync_("value", node2.value, visitor, path18);
3626
3626
  if (cv === BREAK)
3627
3627
  return BREAK;
3628
3628
  else if (cv === REMOVE)
@@ -3649,23 +3649,23 @@ var require_visit = __commonJS({
3649
3649
  }
3650
3650
  return visitor;
3651
3651
  }
3652
- function callVisitor(key, node2, visitor, path17) {
3652
+ function callVisitor(key, node2, visitor, path18) {
3653
3653
  if (typeof visitor === "function")
3654
- return visitor(key, node2, path17);
3654
+ return visitor(key, node2, path18);
3655
3655
  if (identity.isMap(node2))
3656
- return visitor.Map?.(key, node2, path17);
3656
+ return visitor.Map?.(key, node2, path18);
3657
3657
  if (identity.isSeq(node2))
3658
- return visitor.Seq?.(key, node2, path17);
3658
+ return visitor.Seq?.(key, node2, path18);
3659
3659
  if (identity.isPair(node2))
3660
- return visitor.Pair?.(key, node2, path17);
3660
+ return visitor.Pair?.(key, node2, path18);
3661
3661
  if (identity.isScalar(node2))
3662
- return visitor.Scalar?.(key, node2, path17);
3662
+ return visitor.Scalar?.(key, node2, path18);
3663
3663
  if (identity.isAlias(node2))
3664
- return visitor.Alias?.(key, node2, path17);
3664
+ return visitor.Alias?.(key, node2, path18);
3665
3665
  return void 0;
3666
3666
  }
3667
- function replaceNode(key, path17, node2) {
3668
- const parent = path17[path17.length - 1];
3667
+ function replaceNode(key, path18, node2) {
3668
+ const parent = path18[path18.length - 1];
3669
3669
  if (identity.isCollection(parent)) {
3670
3670
  parent.items[key] = node2;
3671
3671
  } else if (identity.isPair(parent)) {
@@ -4277,10 +4277,10 @@ var require_Collection = __commonJS({
4277
4277
  var createNode = require_createNode();
4278
4278
  var identity = require_identity();
4279
4279
  var Node = require_Node();
4280
- function collectionFromPath(schema, path17, value) {
4280
+ function collectionFromPath(schema, path18, value) {
4281
4281
  let v = value;
4282
- for (let i = path17.length - 1; i >= 0; --i) {
4283
- const k = path17[i];
4282
+ for (let i = path18.length - 1; i >= 0; --i) {
4283
+ const k = path18[i];
4284
4284
  if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
4285
4285
  const a = [];
4286
4286
  a[k] = v;
@@ -4299,7 +4299,7 @@ var require_Collection = __commonJS({
4299
4299
  sourceObjects: /* @__PURE__ */ new Map()
4300
4300
  });
4301
4301
  }
4302
- var isEmptyPath = (path17) => path17 == null || typeof path17 === "object" && !!path17[Symbol.iterator]().next().done;
4302
+ var isEmptyPath = (path18) => path18 == null || typeof path18 === "object" && !!path18[Symbol.iterator]().next().done;
4303
4303
  var Collection = class extends Node.NodeBase {
4304
4304
  constructor(type, schema) {
4305
4305
  super(type);
@@ -4329,11 +4329,11 @@ var require_Collection = __commonJS({
4329
4329
  * be a Pair instance or a `{ key, value }` object, which may not have a key
4330
4330
  * that already exists in the map.
4331
4331
  */
4332
- addIn(path17, value) {
4333
- if (isEmptyPath(path17))
4332
+ addIn(path18, value) {
4333
+ if (isEmptyPath(path18))
4334
4334
  this.add(value);
4335
4335
  else {
4336
- const [key, ...rest] = path17;
4336
+ const [key, ...rest] = path18;
4337
4337
  const node2 = this.get(key, true);
4338
4338
  if (identity.isCollection(node2))
4339
4339
  node2.addIn(rest, value);
@@ -4347,8 +4347,8 @@ var require_Collection = __commonJS({
4347
4347
  * Removes a value from the collection.
4348
4348
  * @returns `true` if the item was found and removed.
4349
4349
  */
4350
- deleteIn(path17) {
4351
- const [key, ...rest] = path17;
4350
+ deleteIn(path18) {
4351
+ const [key, ...rest] = path18;
4352
4352
  if (rest.length === 0)
4353
4353
  return this.delete(key);
4354
4354
  const node2 = this.get(key, true);
@@ -4362,8 +4362,8 @@ var require_Collection = __commonJS({
4362
4362
  * scalar values from their surrounding node; to disable set `keepScalar` to
4363
4363
  * `true` (collections are always returned intact).
4364
4364
  */
4365
- getIn(path17, keepScalar) {
4366
- const [key, ...rest] = path17;
4365
+ getIn(path18, keepScalar) {
4366
+ const [key, ...rest] = path18;
4367
4367
  const node2 = this.get(key, true);
4368
4368
  if (rest.length === 0)
4369
4369
  return !keepScalar && identity.isScalar(node2) ? node2.value : node2;
@@ -4381,8 +4381,8 @@ var require_Collection = __commonJS({
4381
4381
  /**
4382
4382
  * Checks if the collection includes a value with the key `key`.
4383
4383
  */
4384
- hasIn(path17) {
4385
- const [key, ...rest] = path17;
4384
+ hasIn(path18) {
4385
+ const [key, ...rest] = path18;
4386
4386
  if (rest.length === 0)
4387
4387
  return this.has(key);
4388
4388
  const node2 = this.get(key, true);
@@ -4392,8 +4392,8 @@ var require_Collection = __commonJS({
4392
4392
  * Sets a value in this collection. For `!!set`, `value` needs to be a
4393
4393
  * boolean to add/remove the item from the set.
4394
4394
  */
4395
- setIn(path17, value) {
4396
- const [key, ...rest] = path17;
4395
+ setIn(path18, value) {
4396
+ const [key, ...rest] = path18;
4397
4397
  if (rest.length === 0) {
4398
4398
  this.set(key, value);
4399
4399
  } else {
@@ -6908,9 +6908,9 @@ var require_Document = __commonJS({
6908
6908
  this.contents.add(value);
6909
6909
  }
6910
6910
  /** Adds a value to the document. */
6911
- addIn(path17, value) {
6911
+ addIn(path18, value) {
6912
6912
  if (assertCollection(this.contents))
6913
- this.contents.addIn(path17, value);
6913
+ this.contents.addIn(path18, value);
6914
6914
  }
6915
6915
  /**
6916
6916
  * Create a new `Alias` node, ensuring that the target `node` has the required anchor.
@@ -6985,14 +6985,14 @@ var require_Document = __commonJS({
6985
6985
  * Removes a value from the document.
6986
6986
  * @returns `true` if the item was found and removed.
6987
6987
  */
6988
- deleteIn(path17) {
6989
- if (Collection.isEmptyPath(path17)) {
6988
+ deleteIn(path18) {
6989
+ if (Collection.isEmptyPath(path18)) {
6990
6990
  if (this.contents == null)
6991
6991
  return false;
6992
6992
  this.contents = null;
6993
6993
  return true;
6994
6994
  }
6995
- return assertCollection(this.contents) ? this.contents.deleteIn(path17) : false;
6995
+ return assertCollection(this.contents) ? this.contents.deleteIn(path18) : false;
6996
6996
  }
6997
6997
  /**
6998
6998
  * Returns item at `key`, or `undefined` if not found. By default unwraps
@@ -7007,10 +7007,10 @@ var require_Document = __commonJS({
7007
7007
  * scalar values from their surrounding node; to disable set `keepScalar` to
7008
7008
  * `true` (collections are always returned intact).
7009
7009
  */
7010
- getIn(path17, keepScalar) {
7011
- if (Collection.isEmptyPath(path17))
7010
+ getIn(path18, keepScalar) {
7011
+ if (Collection.isEmptyPath(path18))
7012
7012
  return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
7013
- return identity.isCollection(this.contents) ? this.contents.getIn(path17, keepScalar) : void 0;
7013
+ return identity.isCollection(this.contents) ? this.contents.getIn(path18, keepScalar) : void 0;
7014
7014
  }
7015
7015
  /**
7016
7016
  * Checks if the document includes a value with the key `key`.
@@ -7021,10 +7021,10 @@ var require_Document = __commonJS({
7021
7021
  /**
7022
7022
  * Checks if the document includes a value at `path`.
7023
7023
  */
7024
- hasIn(path17) {
7025
- if (Collection.isEmptyPath(path17))
7024
+ hasIn(path18) {
7025
+ if (Collection.isEmptyPath(path18))
7026
7026
  return this.contents !== void 0;
7027
- return identity.isCollection(this.contents) ? this.contents.hasIn(path17) : false;
7027
+ return identity.isCollection(this.contents) ? this.contents.hasIn(path18) : false;
7028
7028
  }
7029
7029
  /**
7030
7030
  * Sets a value in this document. For `!!set`, `value` needs to be a
@@ -7041,13 +7041,13 @@ var require_Document = __commonJS({
7041
7041
  * Sets a value in this document. For `!!set`, `value` needs to be a
7042
7042
  * boolean to add/remove the item from the set.
7043
7043
  */
7044
- setIn(path17, value) {
7045
- if (Collection.isEmptyPath(path17)) {
7044
+ setIn(path18, value) {
7045
+ if (Collection.isEmptyPath(path18)) {
7046
7046
  this.contents = value;
7047
7047
  } else if (this.contents == null) {
7048
- this.contents = Collection.collectionFromPath(this.schema, Array.from(path17), value);
7048
+ this.contents = Collection.collectionFromPath(this.schema, Array.from(path18), value);
7049
7049
  } else if (assertCollection(this.contents)) {
7050
- this.contents.setIn(path17, value);
7050
+ this.contents.setIn(path18, value);
7051
7051
  }
7052
7052
  }
7053
7053
  /**
@@ -9008,9 +9008,9 @@ var require_cst_visit = __commonJS({
9008
9008
  visit2.BREAK = BREAK;
9009
9009
  visit2.SKIP = SKIP2;
9010
9010
  visit2.REMOVE = REMOVE;
9011
- visit2.itemAtPath = (cst, path17) => {
9011
+ visit2.itemAtPath = (cst, path18) => {
9012
9012
  let item = cst;
9013
- for (const [field, index2] of path17) {
9013
+ for (const [field, index2] of path18) {
9014
9014
  const tok = item?.[field];
9015
9015
  if (tok && "items" in tok) {
9016
9016
  item = tok.items[index2];
@@ -9019,23 +9019,23 @@ var require_cst_visit = __commonJS({
9019
9019
  }
9020
9020
  return item;
9021
9021
  };
9022
- visit2.parentCollection = (cst, path17) => {
9023
- const parent = visit2.itemAtPath(cst, path17.slice(0, -1));
9024
- const field = path17[path17.length - 1][0];
9022
+ visit2.parentCollection = (cst, path18) => {
9023
+ const parent = visit2.itemAtPath(cst, path18.slice(0, -1));
9024
+ const field = path18[path18.length - 1][0];
9025
9025
  const coll = parent?.[field];
9026
9026
  if (coll && "items" in coll)
9027
9027
  return coll;
9028
9028
  throw new Error("Parent collection not found");
9029
9029
  };
9030
- function _visit(path17, item, visitor) {
9031
- let ctrl = visitor(item, path17);
9030
+ function _visit(path18, item, visitor) {
9031
+ let ctrl = visitor(item, path18);
9032
9032
  if (typeof ctrl === "symbol")
9033
9033
  return ctrl;
9034
9034
  for (const field of ["key", "value"]) {
9035
9035
  const token = item[field];
9036
9036
  if (token && "items" in token) {
9037
9037
  for (let i = 0; i < token.items.length; ++i) {
9038
- const ci = _visit(Object.freeze(path17.concat([[field, i]])), token.items[i], visitor);
9038
+ const ci = _visit(Object.freeze(path18.concat([[field, i]])), token.items[i], visitor);
9039
9039
  if (typeof ci === "number")
9040
9040
  i = ci - 1;
9041
9041
  else if (ci === BREAK)
@@ -9046,10 +9046,10 @@ var require_cst_visit = __commonJS({
9046
9046
  }
9047
9047
  }
9048
9048
  if (typeof ctrl === "function" && field === "key")
9049
- ctrl = ctrl(item, path17);
9049
+ ctrl = ctrl(item, path18);
9050
9050
  }
9051
9051
  }
9052
- return typeof ctrl === "function" ? ctrl(item, path17) : ctrl;
9052
+ return typeof ctrl === "function" ? ctrl(item, path18) : ctrl;
9053
9053
  }
9054
9054
  exports.visit = visit2;
9055
9055
  }
@@ -16023,8 +16023,8 @@ var require_utils = __commonJS({
16023
16023
  }
16024
16024
  return ind;
16025
16025
  }
16026
- function removeDotSegments(path17) {
16027
- let input2 = path17;
16026
+ function removeDotSegments(path18) {
16027
+ let input2 = path18;
16028
16028
  const output3 = [];
16029
16029
  let nextSlash = -1;
16030
16030
  let len = 0;
@@ -16433,8 +16433,8 @@ var require_schemes = __commonJS({
16433
16433
  }
16434
16434
  if (wsComponent.resourceName) {
16435
16435
  const queryIndex = wsComponent.resourceName.indexOf("?");
16436
- const path17 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
16437
- wsComponent.path = path17 && path17 !== "/" ? path17 : void 0;
16436
+ const path18 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
16437
+ wsComponent.path = path18 && path18 !== "/" ? path18 : void 0;
16438
16438
  wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
16439
16439
  wsComponent.resourceName = void 0;
16440
16440
  }
@@ -20476,10 +20476,10 @@ function mergeDefs(...defs) {
20476
20476
  function cloneDef(schema) {
20477
20477
  return mergeDefs(schema._zod.def);
20478
20478
  }
20479
- function getElementAtPath(obj, path17) {
20480
- if (!path17)
20479
+ function getElementAtPath(obj, path18) {
20480
+ if (!path18)
20481
20481
  return obj;
20482
- return path17.reduce((acc, key) => acc?.[key], obj);
20482
+ return path18.reduce((acc, key) => acc?.[key], obj);
20483
20483
  }
20484
20484
  function promiseAllObject(promisesObj) {
20485
20485
  const keys = Object.keys(promisesObj);
@@ -20819,11 +20819,11 @@ function explicitlyAborted(x, startIndex = 0) {
20819
20819
  }
20820
20820
  return false;
20821
20821
  }
20822
- function prefixIssues(path17, issues) {
20822
+ function prefixIssues(path18, issues) {
20823
20823
  return issues.map((iss) => {
20824
20824
  var _a3;
20825
20825
  (_a3 = iss).path ?? (_a3.path = []);
20826
- iss.path.unshift(path17);
20826
+ iss.path.unshift(path18);
20827
20827
  return iss;
20828
20828
  });
20829
20829
  }
@@ -21273,16 +21273,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
21273
21273
  }
21274
21274
  function formatError(error62, mapper = (issue2) => issue2.message) {
21275
21275
  const fieldErrors = { _errors: [] };
21276
- const processError = (error63, path17 = []) => {
21276
+ const processError = (error63, path18 = []) => {
21277
21277
  for (const issue2 of error63.issues) {
21278
21278
  if (issue2.code === "invalid_union" && issue2.errors.length) {
21279
- issue2.errors.map((issues) => processError({ issues }, [...path17, ...issue2.path]));
21279
+ issue2.errors.map((issues) => processError({ issues }, [...path18, ...issue2.path]));
21280
21280
  } else if (issue2.code === "invalid_key") {
21281
- processError({ issues: issue2.issues }, [...path17, ...issue2.path]);
21281
+ processError({ issues: issue2.issues }, [...path18, ...issue2.path]);
21282
21282
  } else if (issue2.code === "invalid_element") {
21283
- processError({ issues: issue2.issues }, [...path17, ...issue2.path]);
21283
+ processError({ issues: issue2.issues }, [...path18, ...issue2.path]);
21284
21284
  } else {
21285
- const fullpath = [...path17, ...issue2.path];
21285
+ const fullpath = [...path18, ...issue2.path];
21286
21286
  if (fullpath.length === 0) {
21287
21287
  fieldErrors._errors.push(mapper(issue2));
21288
21288
  } else {
@@ -21321,17 +21321,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
21321
21321
  }
21322
21322
  function treeifyError(error62, mapper = (issue2) => issue2.message) {
21323
21323
  const result = { errors: [] };
21324
- const processError = (error63, path17 = []) => {
21324
+ const processError = (error63, path18 = []) => {
21325
21325
  var _a3;
21326
21326
  for (const issue2 of error63.issues) {
21327
21327
  if (issue2.code === "invalid_union" && issue2.errors.length) {
21328
- issue2.errors.map((issues) => processError({ issues }, [...path17, ...issue2.path]));
21328
+ issue2.errors.map((issues) => processError({ issues }, [...path18, ...issue2.path]));
21329
21329
  } else if (issue2.code === "invalid_key") {
21330
- processError({ issues: issue2.issues }, [...path17, ...issue2.path]);
21330
+ processError({ issues: issue2.issues }, [...path18, ...issue2.path]);
21331
21331
  } else if (issue2.code === "invalid_element") {
21332
- processError({ issues: issue2.issues }, [...path17, ...issue2.path]);
21332
+ processError({ issues: issue2.issues }, [...path18, ...issue2.path]);
21333
21333
  } else {
21334
- const fullpath = [...path17, ...issue2.path];
21334
+ const fullpath = [...path18, ...issue2.path];
21335
21335
  if (fullpath.length === 0) {
21336
21336
  result.errors.push(mapper(issue2));
21337
21337
  continue;
@@ -21370,8 +21370,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
21370
21370
  }
21371
21371
  function toDotPath(_path) {
21372
21372
  const segs = [];
21373
- const path17 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
21374
- for (const seg of path17) {
21373
+ const path18 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
21374
+ for (const seg of path18) {
21375
21375
  if (typeof seg === "number")
21376
21376
  segs.push(`[${seg}]`);
21377
21377
  else if (typeof seg === "symbol")
@@ -38473,13 +38473,13 @@ function resolveRef(ref, ctx) {
38473
38473
  if (!ref.startsWith("#")) {
38474
38474
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
38475
38475
  }
38476
- const path17 = ref.slice(1).split("/").filter(Boolean);
38477
- if (path17.length === 0) {
38476
+ const path18 = ref.slice(1).split("/").filter(Boolean);
38477
+ if (path18.length === 0) {
38478
38478
  return ctx.rootSchema;
38479
38479
  }
38480
38480
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
38481
- if (path17[0] === defsKey) {
38482
- const key = path17[1] === void 0 ? void 0 : decodeJSONPointerSegment(path17[1]);
38481
+ if (path18[0] === defsKey) {
38482
+ const key = path18[1] === void 0 ? void 0 : decodeJSONPointerSegment(path18[1]);
38483
38483
  if (!key || !ctx.defs[key]) {
38484
38484
  throw new Error(`Reference not found: ${ref}`);
38485
38485
  }
@@ -39342,6 +39342,7 @@ var HARNESSES = {
39342
39342
 
39343
39343
  // ../../packages/schema/dist/manifest.js
39344
39344
  var MANIFEST_FILE = "yarkr.yaml";
39345
+ var INDEX_FILE = "yarkr.index.yaml";
39345
39346
  var PackageKind = external_exports.enum(["skill", "agent", "toolset"]);
39346
39347
  var Classification = external_exports.enum(["public", "internal", "restricted"]);
39347
39348
  var PackageName = external_exports.string().regex(/^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/, "Use namespace/name in lowercase, e.g. finance/invoice-extraction");
@@ -39573,7 +39574,7 @@ var EffectiveArkQuery = external_exports.object({
39573
39574
  /** Comma-separated ark refs to narrow to; omit for all. */
39574
39575
  arks: external_exports.string().optional()
39575
39576
  });
39576
- var PersonalItemBody = external_exports.object({ package: PackageName, range: external_exports.string().nullable().optional() });
39577
+ var PersonalItemBody = external_exports.object({ package: PackageName, range: external_exports.string().nullable().optional(), channel: external_exports.string().nullable().optional() });
39577
39578
  var ExclusionBody = external_exports.object({ ark: external_exports.string(), package: PackageName });
39578
39579
 
39579
39580
  // src/commands/auth.ts
@@ -39742,8 +39743,8 @@ var Api = class {
39742
39743
  this.conn = conn;
39743
39744
  }
39744
39745
  conn;
39745
- async request(method, path17, body, { allow404 = false } = {}) {
39746
- const url2 = `${this.conn.server}/api/v1${path17}`;
39746
+ async request(method, path18, body, { allow404 = false } = {}) {
39747
+ const url2 = `${this.conn.server}/api/v1${path18}`;
39747
39748
  let res;
39748
39749
  try {
39749
39750
  res = await fetch(url2, {
@@ -39765,7 +39766,7 @@ var Api = class {
39765
39766
  payload = await res.json();
39766
39767
  } catch {
39767
39768
  }
39768
- const msg = payload.message ?? `${method} ${path17} failed with HTTP ${res.status}.`;
39769
+ const msg = payload.message ?? `${method} ${path18} failed with HTTP ${res.status}.`;
39769
39770
  if (res.status === 401) {
39770
39771
  throw new CliError(
39771
39772
  msg,
@@ -39856,17 +39857,17 @@ function parseManifest(text) {
39856
39857
  }
39857
39858
  return manifest;
39858
39859
  }
39859
- function parseEvalSuite(text, path17 = "suite") {
39860
+ function parseEvalSuite(text, path18 = "suite") {
39860
39861
  let raw;
39861
39862
  try {
39862
39863
  raw = import_yaml.default.parse(text);
39863
39864
  } catch (err) {
39864
- throw new ManifestError(`${path17} is not valid YAML: ${err.message}`);
39865
+ throw new ManifestError(`${path18} is not valid YAML: ${err.message}`);
39865
39866
  }
39866
39867
  const res = EvalSuite.safeParse(raw);
39867
39868
  if (!res.success) {
39868
39869
  const issues = res.error.issues.map((i) => ({ path: i.path.join("."), message: i.message }));
39869
- throw new ManifestError(`${path17} is invalid: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`, issues);
39870
+ throw new ManifestError(`${path18} is invalid: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`, issues);
39870
39871
  }
39871
39872
  return res.data;
39872
39873
  }
@@ -39923,8 +39924,8 @@ function asText(content) {
39923
39924
  return null;
39924
39925
  return typeof content === "string" ? content : new TextDecoder().decode(content);
39925
39926
  }
39926
- function isYarkrOnly(path17) {
39927
- return path17 === MANIFEST_FILE || path17.startsWith("evals/") || path17.startsWith("fixtures/");
39927
+ function isYarkrOnly(path18) {
39928
+ return path18 === MANIFEST_FILE || path18 === INDEX_FILE || path18.startsWith("evals/") || path18.startsWith("fixtures/");
39928
39929
  }
39929
39930
  function skillEntryPath(m) {
39930
39931
  return m.entry ?? "SKILL.md";
@@ -39952,10 +39953,10 @@ ${body.replace(/^\n+/, "")}`);
39952
39953
  function skillFolderFiles(pkg, dir, folderName) {
39953
39954
  const entry = skillEntryPath(pkg.manifest);
39954
39955
  const files = [{ package: pkg.manifest.name, path: join(dir, folderName, "SKILL.md"), content: renderSkillMd(pkg, folderName) }];
39955
- for (const [path17, content] of Object.entries(pkg.files)) {
39956
- if (path17 === entry || path17 === "SKILL.md" || isYarkrOnly(path17))
39956
+ for (const [path18, content] of Object.entries(pkg.files)) {
39957
+ if (path18 === entry || path18 === "SKILL.md" || isYarkrOnly(path18))
39957
39958
  continue;
39958
- files.push({ package: pkg.manifest.name, path: join(dir, folderName, path17), content });
39959
+ files.push({ package: pkg.manifest.name, path: join(dir, folderName, path18), content });
39959
39960
  }
39960
39961
  return files;
39961
39962
  }
@@ -40049,13 +40050,13 @@ function nativeFolderHarness(o) {
40049
40050
  ...o.mcp ? {
40050
40051
  toolsets(servers, ctx) {
40051
40052
  const mcp = o.mcp;
40052
- const path17 = ctx.layout[slot];
40053
- if (!path17) {
40053
+ const path18 = ctx.layout[slot];
40054
+ if (!path18) {
40054
40055
  if (mcp.unavailable)
40055
40056
  ctx.out.notes.push(mcp.unavailable(servers));
40056
40057
  return;
40057
40058
  }
40058
- ctx.out.mcp.push({ path: path17, key: mcp.key, servers: Object.fromEntries(servers.map(({ pkg, server }) => [server.name, mcp.entry(pkg, server, ctx.out.notes)])) });
40059
+ ctx.out.mcp.push({ path: path18, key: mcp.key, servers: Object.fromEntries(servers.map(({ pkg, server }) => [server.name, mcp.entry(pkg, server, ctx.out.notes)])) });
40059
40060
  }
40060
40061
  } : {}
40061
40062
  });
@@ -40240,10 +40241,10 @@ var cursor = defineHarness({
40240
40241
  const entry = skillEntryPath(pkg.manifest);
40241
40242
  const { data, body } = splitFrontmatter(asText(pkg.files[entry]) ?? "");
40242
40243
  const extra = [];
40243
- for (const [path17, content] of Object.entries(pkg.files)) {
40244
- if (path17 === entry || isYarkrOnly(path17))
40244
+ for (const [path18, content] of Object.entries(pkg.files)) {
40245
+ if (path18 === entry || isYarkrOnly(path18))
40245
40246
  continue;
40246
- const p = join(l.assets, name, path17);
40247
+ const p = join(l.assets, name, path18);
40247
40248
  ctx.out.files.push({ package: pkg.manifest.name, path: p, content });
40248
40249
  extra.push(`- \`${p}\``);
40249
40250
  }
@@ -40314,9 +40315,9 @@ function folderExportHarness(id, rootDefault, opts = {}) {
40314
40315
  for (const pkg of byKind(input2, "agent")) {
40315
40316
  const name = names.get(pkg.manifest.name);
40316
40317
  const parts = agentParts(pkg, id, names, out);
40317
- for (const [path17, content] of Object.entries(pkg.files))
40318
- if (!isYarkrOnly(path17))
40319
- out.files.push({ package: pkg.manifest.name, path: join(root, "agents", name, path17), content });
40318
+ for (const [path18, content] of Object.entries(pkg.files))
40319
+ if (!isYarkrOnly(path18))
40320
+ out.files.push({ package: pkg.manifest.name, path: join(root, "agents", name, path18), content });
40320
40321
  index2.push({ kind: "agent", name, package: pkg.manifest.name, version: pkg.manifest.version, description: pkg.manifest.description, model: parts.model, tools: parts.tools, skills: parts.skills, subagents: parts.subagents, system_prompt: join("agents", name, pkg.manifest.agent?.system_prompt ?? "") });
40321
40322
  }
40322
40323
  for (const pkg of byKind(input2, "toolset")) {
@@ -41125,17 +41126,41 @@ var dirname = (p) => p.includes("/") ? p.slice(0, p.lastIndexOf("/")) : "";
41125
41126
  var basename = (p) => p.slice(p.lastIndexOf("/") + 1);
41126
41127
  var under = (dir, p) => dir ? p.startsWith(`${dir}/`) : true;
41127
41128
  var rel = (dir, p) => dir ? p.slice(dir.length + 1) : p;
41128
- function parseOverrides(text, path17, problems) {
41129
+ function parseOverrides(text, path18, problems) {
41129
41130
  if (text === null)
41130
41131
  return null;
41131
41132
  try {
41132
41133
  const raw = import_yaml3.default.parse(text);
41133
41134
  return raw && typeof raw === "object" ? raw : null;
41134
41135
  } catch (err) {
41135
- problems.push({ path: path17, message: `${MANIFEST_FILE} isn't valid YAML: ${err.message}` });
41136
+ problems.push({ path: path18, message: `${MANIFEST_FILE} isn't valid YAML: ${err.message}` });
41136
41137
  return null;
41137
41138
  }
41138
41139
  }
41140
+ function parseIndex(text, path18, problems) {
41141
+ let raw;
41142
+ try {
41143
+ raw = import_yaml3.default.parse(text ?? "");
41144
+ } catch (err) {
41145
+ problems.push({ path: path18, message: `isn't valid YAML: ${err.message}` });
41146
+ return null;
41147
+ }
41148
+ const entries = (key) => {
41149
+ const list = raw?.[key] ?? [];
41150
+ if (!Array.isArray(list)) {
41151
+ problems.push({ path: path18, message: `\`${key}\` must be a list.` });
41152
+ return [];
41153
+ }
41154
+ return list.flatMap((e, i) => {
41155
+ const entry = typeof e === "string" ? { path: e } : e;
41156
+ if (entry && typeof entry === "object" && typeof entry.path === "string" && entry.path.trim())
41157
+ return [{ ...entry, path: entry.path.replace(/^\.?\/+|\/+$/g, "") }];
41158
+ problems.push({ path: path18, message: `${key}[${i}] needs a \`path\`.` });
41159
+ return [];
41160
+ });
41161
+ };
41162
+ return { skills: entries("skills"), agents: entries("agents"), toolsets: entries("toolsets"), detect: raw?.detect !== false };
41163
+ }
41139
41164
  var asList = (v) => Array.isArray(v) ? v.map(String) : typeof v === "string" ? v.split(",").map((s) => s.trim()).filter(Boolean) : [];
41140
41165
  async function detectAssets(input2) {
41141
41166
  const root = (input2.root ?? "").replace(/^\/+|\/+$/g, "");
@@ -41146,7 +41171,7 @@ async function detectAssets(input2) {
41146
41171
  const claimed = [];
41147
41172
  const isClaimed = (p) => claimed.some((d) => d ? p === d || p.startsWith(`${d}/`) : true);
41148
41173
  const filesBelow = (dir) => new Map(paths.filter((p) => under(dir, p)).map((p) => [rel(dir, p), p]));
41149
- const make = (name, kind, path17, files, o, extra) => {
41174
+ const make = (name, kind, path18, files, o, extra) => {
41150
41175
  const suites = [...files.keys()].filter((p) => SUITE.test(p));
41151
41176
  const raw = {
41152
41177
  name: `${input2.namespace}/${slugify2(name)}`,
@@ -41165,19 +41190,109 @@ async function detectAssets(input2) {
41165
41190
  };
41166
41191
  const res = Manifest.safeParse(raw);
41167
41192
  if (!res.success) {
41168
- problems.push({ path: path17, message: res.error.issues.map((i) => `${i.path.join(".") || "manifest"}: ${i.message}`).join("; ") });
41193
+ problems.push({ path: path18, message: res.error.issues.map((i) => `${i.path.join(".") || "manifest"}: ${i.message}`).join("; ") });
41169
41194
  return null;
41170
41195
  }
41171
- return { name: slugify2(name), kind, path: path17, manifest: res.data, files };
41196
+ return { name: slugify2(name), kind, path: path18, manifest: res.data, files };
41172
41197
  };
41173
41198
  const manifestNear = async (dir, from) => {
41174
41199
  const mp = dir ? `${dir}/${MANIFEST_FILE}` : MANIFEST_FILE;
41175
41200
  return paths.includes(mp) ? parseOverrides(await input2.read(mp), from, problems) ?? {} : {};
41176
41201
  };
41202
+ const readFrontmatter = async (p) => {
41203
+ try {
41204
+ return splitFrontmatter(await input2.read(p) ?? "").data;
41205
+ } catch (err) {
41206
+ problems.push({ path: p, message: `frontmatter isn't valid YAML: ${err.message}` });
41207
+ return null;
41208
+ }
41209
+ };
41210
+ const agentAsset = (p, fm, e = {}) => {
41211
+ const name = e.name ?? (typeof fm.name === "string" && fm.name.trim() ? fm.name : basename(p).replace(/\.md$/, ""));
41212
+ const skills = asList(e.skills ?? fm.skills);
41213
+ const depends = skills.length ? { skills: Object.fromEntries(skills.map((s) => [s.includes("/") ? s : `${input2.namespace}/${slugify2(s)}`, "*"])) } : {};
41214
+ const model = e.model ?? fm.model;
41215
+ return make(name, "agent", p, /* @__PURE__ */ new Map([["AGENT.md", p]]), { targets: e.targets, tags: e.tags, evals: e.evals }, {
41216
+ description: e.description ?? String(fm.description),
41217
+ depends,
41218
+ agent: { system_prompt: "AGENT.md", model: typeof model === "string" ? model : void 0, tools: asList(e.tools ?? fm.tools), subagents: [] }
41219
+ });
41220
+ };
41221
+ const indexPath = root ? `${root}/${INDEX_FILE}` : INDEX_FILE;
41222
+ const index2 = paths.includes(indexPath) ? parseIndex(await input2.read(indexPath), indexPath, problems) : null;
41223
+ const listed = /* @__PURE__ */ new Set();
41224
+ const at = (e) => root ? `${root}/${e.path}` : e.path;
41225
+ const missing = (p, what) => problems.push({ path: indexPath, message: `${p} isn't in the repo (listed under ${what}).` });
41226
+ for (const e of index2?.skills ?? []) {
41227
+ const p = at(e);
41228
+ const isFile = paths.includes(p);
41229
+ if (!isFile && !paths.some((x) => x.startsWith(`${p}/`))) {
41230
+ missing(p, "skills");
41231
+ continue;
41232
+ }
41233
+ const entry = isFile ? basename(p) : e.entry ?? (paths.includes(`${p}/SKILL.md`) ? "SKILL.md" : "README.md");
41234
+ const entryPath = isFile ? p : `${p}/${entry}`;
41235
+ if (!paths.includes(entryPath)) {
41236
+ problems.push({ path: indexPath, message: `${p} has no ${entry}. Set \`entry\` to the file with the skill's instructions.` });
41237
+ continue;
41238
+ }
41239
+ const fm = await readFrontmatter(entryPath);
41240
+ if (!fm)
41241
+ continue;
41242
+ const description = e.description ?? (typeof fm.description === "string" ? fm.description : "");
41243
+ if (!description) {
41244
+ problems.push({ path: indexPath, message: `${p} needs a \`description\` (here or in its frontmatter): harnesses use it to decide when to load the skill.` });
41245
+ continue;
41246
+ }
41247
+ listed.add(entryPath);
41248
+ if (!isFile)
41249
+ claimed.push(p);
41250
+ const name = e.name ?? (typeof fm.name === "string" && fm.name.trim() ? fm.name : basename(p).replace(/\.mdc?$/, ""));
41251
+ const near = isFile ? {} : await manifestNear(p, entryPath);
41252
+ const o = { ...near, ...e.targets && { targets: e.targets }, ...e.tags && { tags: e.tags }, ...e.evals && { evals: e.evals }, ...entry !== "SKILL.md" && { entry } };
41253
+ const asset = make(name, "skill", p, isFile ? /* @__PURE__ */ new Map([[entry, p]]) : filesBelow(p), o, { description });
41254
+ if (asset)
41255
+ found.push(asset);
41256
+ }
41257
+ for (const e of index2?.agents ?? []) {
41258
+ const p = at(e);
41259
+ if (!paths.includes(p)) {
41260
+ missing(p, "agents");
41261
+ continue;
41262
+ }
41263
+ const fm = await readFrontmatter(p);
41264
+ if (!fm)
41265
+ continue;
41266
+ if (!e.description && typeof fm.description !== "string") {
41267
+ problems.push({ path: indexPath, message: `${p} needs a \`description\` (here or in its frontmatter): it says when to hand work to the agent.` });
41268
+ continue;
41269
+ }
41270
+ listed.add(p);
41271
+ const asset = agentAsset(p, fm, e);
41272
+ if (asset)
41273
+ found.push(asset);
41274
+ }
41275
+ for (const e of index2?.toolsets ?? []) {
41276
+ const p = at(e);
41277
+ if (!paths.includes(p)) {
41278
+ missing(p, "toolsets");
41279
+ continue;
41280
+ }
41281
+ listed.add(p);
41282
+ const servers = toolsetServers2(await input2.read(p), p, problems);
41283
+ if (!servers)
41284
+ continue;
41285
+ const description = e.description ?? `MCP servers: ${servers.map((s) => s.name).join(", ")}`;
41286
+ const o = { targets: e.targets, tags: e.tags, evals: e.evals };
41287
+ const asset = make(e.name ?? input2.repoName, "toolset", p, /* @__PURE__ */ new Map([[".mcp.json", p]]), o, { description, toolset: { servers, tool_schemas: [] } });
41288
+ if (asset)
41289
+ found.push(asset);
41290
+ }
41291
+ const detect = index2?.detect ?? true;
41177
41292
  const depth = (p) => p.split("/").length;
41178
- for (const p of paths.filter((x) => basename(x) === "SKILL.md").sort((a, b) => depth(a) - depth(b) || a.localeCompare(b))) {
41293
+ for (const p of paths.filter((x) => detect && basename(x) === "SKILL.md").sort((a, b) => depth(a) - depth(b) || a.localeCompare(b))) {
41179
41294
  const dir = dirname(p);
41180
- if (isClaimed(dir))
41295
+ if (isClaimed(dir) || listed.has(p))
41181
41296
  continue;
41182
41297
  let fm;
41183
41298
  try {
@@ -41197,7 +41312,7 @@ async function detectAssets(input2) {
41197
41312
  if (asset)
41198
41313
  found.push(asset);
41199
41314
  }
41200
- for (const p of paths.filter((x) => basename(x) === MANIFEST_FILE)) {
41315
+ for (const p of paths.filter((x) => detect && basename(x) === MANIFEST_FILE)) {
41201
41316
  const dir = dirname(p);
41202
41317
  if (isClaimed(dir))
41203
41318
  continue;
@@ -41210,34 +41325,15 @@ async function detectAssets(input2) {
41210
41325
  if (asset)
41211
41326
  found.push(asset);
41212
41327
  }
41213
- for (const p of paths.filter((x) => AGENT_FILE.test(x) && !isClaimed(dirname(x)))) {
41214
- const text = await input2.read(p) ?? "";
41215
- let fm;
41216
- try {
41217
- fm = splitFrontmatter(text).data;
41218
- } catch (err) {
41219
- problems.push({ path: p, message: `frontmatter isn't valid YAML: ${err.message}` });
41328
+ for (const p of paths.filter((x) => detect && AGENT_FILE.test(x) && !listed.has(x) && !isClaimed(dirname(x)))) {
41329
+ const fm = await readFrontmatter(p);
41330
+ if (!fm || typeof fm.description !== "string")
41220
41331
  continue;
41221
- }
41222
- if (typeof fm.description !== "string")
41223
- continue;
41224
- const name = typeof fm.name === "string" && fm.name.trim() ? fm.name : basename(p).replace(/\.md$/, "");
41225
- const skills = asList(fm.skills);
41226
- const depends = skills.length ? { skills: Object.fromEntries(skills.map((s) => [s.includes("/") ? s : `${input2.namespace}/${slugify2(s)}`, "*"])) } : {};
41227
- const asset = make(name, "agent", p, /* @__PURE__ */ new Map([["AGENT.md", p]]), {}, {
41228
- description: fm.description,
41229
- depends,
41230
- agent: {
41231
- system_prompt: "AGENT.md",
41232
- model: typeof fm.model === "string" ? fm.model : void 0,
41233
- tools: asList(fm.tools),
41234
- subagents: []
41235
- }
41236
- });
41332
+ const asset = agentAsset(p, fm);
41237
41333
  if (asset)
41238
41334
  found.push(asset);
41239
41335
  }
41240
- for (const p of paths.filter((x) => TOOLSET_FILE.test(rel(root, x)) && !isClaimed(dirname(x)))) {
41336
+ for (const p of paths.filter((x) => detect && TOOLSET_FILE.test(rel(root, x)) && !listed.has(x) && !isClaimed(dirname(x)))) {
41241
41337
  const dir = dirname(p);
41242
41338
  const name = TOOLSET_FILE.exec(rel(root, p))[1] ?? input2.repoName;
41243
41339
  const servers = toolsetServers2(await input2.read(p), p, problems);
@@ -41260,16 +41356,16 @@ async function detectAssets(input2) {
41260
41356
  }
41261
41357
  return { assets: [...byName.values()], problems };
41262
41358
  }
41263
- function toolsetServers2(text, path17, problems) {
41359
+ function toolsetServers2(text, path18, problems) {
41264
41360
  let doc;
41265
41361
  try {
41266
41362
  doc = JSON.parse(text ?? "");
41267
41363
  } catch (err) {
41268
- problems.push({ path: path17, message: `isn't valid JSON: ${err.message}` });
41364
+ problems.push({ path: path18, message: `isn't valid JSON: ${err.message}` });
41269
41365
  return null;
41270
41366
  }
41271
41367
  const out = [];
41272
- for (const [name, s] of Object.entries(doc.mcpServers ?? {})) {
41368
+ for (const [name, s] of Object.entries(doc.mcpServers ?? doc.servers ?? {})) {
41273
41369
  const http = s.type === "http" || s.type === "sse" || typeof s.url === "string" && !s.command;
41274
41370
  const refs = (field) => {
41275
41371
  const map2 = s[field] ?? {};
@@ -41278,7 +41374,7 @@ function toolsetServers2(text, path17, problems) {
41278
41374
  const ref = String(v).replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-[^}]*)?\}/g, "${env:$1}");
41279
41375
  const plain = ref.replace(/^Bearer /, "");
41280
41376
  if (!SECRET_REF.test(plain)) {
41281
- problems.push({ path: path17, message: `${name}.${field}.${k} is a literal value. Use \${${k.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}} or \${vault:path#key} so no secret lives in the repo.` });
41377
+ problems.push({ path: path18, message: `${name}.${field}.${k} is a literal value. Use \${${k.toUpperCase().replace(/[^A-Z0-9]+/g, "_")}} or \${vault:path#key} so no secret lives in the repo.` });
41282
41378
  return null;
41283
41379
  }
41284
41380
  res[k] = ref;
@@ -41298,7 +41394,7 @@ function toolsetServers2(text, path17, problems) {
41298
41394
  });
41299
41395
  }
41300
41396
  if (!out.length) {
41301
- problems.push({ path: path17, message: "has no mcpServers." });
41397
+ problems.push({ path: path18, message: "has no mcpServers." });
41302
41398
  return null;
41303
41399
  }
41304
41400
  return out;
@@ -41422,8 +41518,8 @@ function verifyBundle(b, key, requireSignature) {
41422
41518
  return { manifest: b.manifest, files, content_hash: b.content_hash };
41423
41519
  }
41424
41520
  var TEXT_EXT = /\.(md|mdc|mdx|markdown|txt|ya?ml|json|toml|py|sh|bash|js|mjs|cjs|ts|tsx|csv|html|css|xml|sql|rb|go|rs|java|ini|cfg|env)$/i;
41425
- function looksText(path17, bytes) {
41426
- if (TEXT_EXT.test(path17)) return true;
41521
+ function looksText(path18, bytes) {
41522
+ if (TEXT_EXT.test(path18)) return true;
41427
41523
  const n = Math.min(bytes.length, 4096);
41428
41524
  for (let i = 0; i < n; i++) if (bytes[i] === 0) return false;
41429
41525
  try {
@@ -41991,16 +42087,16 @@ var Diff = class {
41991
42087
  }
41992
42088
  }
41993
42089
  }
41994
- addToPath(path17, added, removed, oldPosInc, options) {
41995
- const last = path17.lastComponent;
42090
+ addToPath(path18, added, removed, oldPosInc, options) {
42091
+ const last = path18.lastComponent;
41996
42092
  if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
41997
42093
  return {
41998
- oldPos: path17.oldPos + oldPosInc,
42094
+ oldPos: path18.oldPos + oldPosInc,
41999
42095
  lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
42000
42096
  };
42001
42097
  } else {
42002
42098
  return {
42003
- oldPos: path17.oldPos + oldPosInc,
42099
+ oldPos: path18.oldPos + oldPosInc,
42004
42100
  lastComponent: { count: 1, added, removed, previousComponent: last }
42005
42101
  };
42006
42102
  }
@@ -42642,10 +42738,10 @@ async function loadSuites(candidate, candFiles, shared) {
42642
42738
  }
42643
42739
  return out;
42644
42740
  }
42645
- function parseSuite(bytes, ref, path17, source) {
42741
+ function parseSuite(bytes, ref, path18, source) {
42646
42742
  const text = bytes.toString("utf8");
42647
42743
  try {
42648
- return { suite: parseEvalSuite(text, path17), ref, path: path17, source, hash: sha256(text) };
42744
+ return { suite: parseEvalSuite(text, path18), ref, path: path18, source, hash: sha256(text) };
42649
42745
  } catch (err) {
42650
42746
  throw new SuiteError(err.message);
42651
42747
  }
@@ -44146,7 +44242,7 @@ async function registrySnapshot(api, name, opts = {}) {
44146
44242
  const rel2 = await api.resolve(name, { channel: opts.channel ?? "stable", range: opts.range });
44147
44243
  const bundle = await api.bundle(name, rel2.version);
44148
44244
  const files = new Map(bundle.files.map((f) => [f.path, Buffer.from(f.content, "base64")]));
44149
- const tree = buildTree([...files].map(([path17, bytes]) => ({ path: path17, bytes })));
44245
+ const tree = buildTree([...files].map(([path18, bytes]) => ({ path: path18, bytes })));
44150
44246
  return { snapshot: { name, label: bundle.version, contentHash: bundle.content_hash, manifest: bundle.manifest, tree }, files };
44151
44247
  }
44152
44248
  function registrySuiteResolver(getApi) {
@@ -44716,7 +44812,8 @@ import path16 from "path";
44716
44812
 
44717
44813
  // ../../packages/core/dist/guides/content.generated.js
44718
44814
  var GUIDE_CONTENT = {
44719
- "create-ark": '---\nname: create-ark\ndescription: Build a Yarkr ark (a set of skills, agents or MCP configs handed to a team) for a given topic and type. Use when asked to create, assemble or curate an ark, or to collect a topic\'s skills, agents or MCP servers for a team in Yarkr.\n---\n\n# Creating an ark from a topic and a type\n\nYou get two inputs and build one [ark](https://github.com/posterus-ltd/yarkr-public/blob/main/docs/arks.md) that hands out packages on that topic.\n\n| Input | Example | Meaning |\n| --- | --- | --- |\n| **topic** | `invoice processing`, `frontend testing` | what the ark is about |\n| **type** | `skill`, `agent` or `mcp` | the kind of package it holds. `mcp` is a **toolset** in Yarkr (`--kind toolset`, `kind: toolset`) |\n\nPlus, from the user or their config: the **org** (its slug is its GitHub login, lowercased), and whether the ark should\nbe **public**. Ask for the org if you can\'t tell; default to private.\n\nAn ark only points at packages. Packages come from GitHub ([github.md](https://github.com/posterus-ltd/yarkr-public/blob/main/docs/github.md)): there is no way to upload content to\nYarkr directly. So the work is: find what exists, write what\'s missing in a repo, then assemble the ark.\n\n```\n1. plan \u2500\u2500\u25B6 2. find existing \u2500\u2500\u25B6 3. author missing (PR) \u2500\u2500\u25B6 4. wait for index \u2500\u2500\u25B6 5. create ark + items \u2500\u2500\u25B6 6. verify\n```\n\n---\n\n## 1. Plan\n\n- **Ark name:** a slug, `^[a-z0-9][a-z0-9-]*$`, short and named after the topic: `invoice-processing`, not\n `ark-for-invoices`. It must be unique in the org.\n- **Description:** one sentence, at most 500 characters, saying who it\'s for and what they get.\n- **Scope:** aim for 3 to 8 packages. Every skill in an ark goes into every member\'s context; too many makes agents\n worse. Split a broad topic into several arks rather than one big one.\n- List the packages you expect to need, each with a one-line purpose, before searching.\n\n## 2. Find what already exists\n\nReuse beats writing. Search the registry for each planned package:\n\n```bash\nyarkr search "<topic words>" --kind <skill|agent|toolset> --json\nyarkr info <namespace>/<name> # source repo, targets, releases, eval results\n```\n\nOver MCP, `search_packages` with `scope: "all"`, then `get_skill` / `get_agent` / `get_toolset` to read one.\n\nKeep a package when:\n\n- its description and body actually cover the planned purpose (read it, don\'t trust the name);\n- the org can put it in an ark: **the org\'s own namespace, or any public package**;\n- for a public ark: **it is public**. A public ark can hold nothing else;\n- it isn\'t yanked or archived, and has evals that pass if any are shown.\n\n## 3. Author what\'s missing\n\nWrite each missing package in one of the org\'s **connected** repos (check **Settings \u2192 GitHub** / `/orgs/<org>/github`;\na repo that isn\'t connected is never indexed). For a public ark the repo must be public. Open **one pull request** with\nall new packages, and describe the ark they\'re for. Merging is publishing; don\'t push to the default branch yourself\nunless the user tells you to.\n\nPackage names come from the folder or `name:` frontmatter, slugified, and publish as `<org>/<name>`. Check that the name\ndoesn\'t already exist in the org.\n\n### Type `skill`\n\nA folder with `SKILL.md`. Everything in the folder ships with it. This guide is itself one.\n\n```\nskills/<name>/\n SKILL.md\n references/\u2026 # optional: long docs the skill loads on demand\n scripts/\u2026 # optional\n evals/core.yaml # optional but recommended, see https://github.com/posterus-ltd/yarkr-public/blob/main/docs/evals.md\n```\n\n```markdown\n---\nname: <name>\ndescription: <what it does and when to use it. Harnesses decide whether to load the skill from this line alone, so name the trigger situations.>\n---\n\n# <Title>\n\n<Steps the agent follows. Concrete and imperative. Point to references/ for anything long.>\n```\n\n`description` is required; without it the skill isn\'t indexed.\n\n### Type `agent`\n\nOne markdown file under `agents/` (or `.claude/agents/`).\n\n```markdown\n---\nname: <name>\ndescription: <when to hand work to this agent>\nmodel: <optional>\ntools: Read, Grep, Bash # optional\nskills: <skill-name>, other-org/skill # optional: dependencies, installed with the agent\n---\n\n<System prompt: role, what it does, how it decides, what it returns.>\n```\n\n- `description` is required; a file without it is treated as a plain doc.\n- A bare name in `skills:` means a skill in the same org. Use `ns/name` for another namespace. A skill the agent needs\n that doesn\'t exist yet is part of this same PR.\n- Add the agent\'s skills to the ark too, or leave them to the dependency: `yarkr sync` installs dependencies either way.\n\n### Type `mcp` (toolset)\n\nOne toolset per folder, in Claude Code\'s `.mcp.json` format:\n\n```\ntoolsets/<name>/\n .mcp.json\n README.md # first paragraph becomes the description\n```\n\n```json\n{\n "mcpServers": {\n "<server>": {\n "type": "http",\n "url": "https://mcp.example.com/mcp",\n "headers": { "Authorization": "Bearer ${EXAMPLE_TOKEN}" }\n }\n }\n}\n```\n\n- **No literal secrets.** Values in `env` and `headers` must be `${VAR}` (becomes `${env:VAR}`), `${VAR:-default}`\n or `${vault:path#key}`. A literal value is refused.\n- Prefer servers you can verify exist: the vendor\'s official server, or a well-known package for `stdio` (`npx -y \u2026`).\n Never invent a URL or package name. If you can\'t confirm one, stop and ask.\n- Document in the README which variables each user must set.\n\n### Optional `yarkr.yaml`\n\nNext to any asset, for what can\'t be inferred:\n\n```yaml\ntargets: [claude-code, codex] # default: every harness\ntags: [<topic>, \u2026] # helps search; use the topic\nevals: [evals/core.yaml]\n```\n\n## 4. Wait for the index\n\nAfter the PR merges, the push is indexed in the background, usually within a minute. Each package\'s first release is a\nsnapshot of the default branch (`0.0.0-main.\u2026`) unless a tag says otherwise.\n\n```bash\nyarkr info <org>/<name> # repeat until it shows a stable version\n```\n\nIf it never appears, check the repo\'s row on the org\'s GitHub page: detection problems (missing description, literal\nsecret, name clash) are listed there. Fix them in a new PR.\n\n## 5. Create the ark and add items\n\nArk changes need an **org admin** and a **read and write** token. There\'s no CLI command for this; use the web app\n(**Arks \u2192 your org**) or the API. The base URL is the registry (`yarkr whoami` prints it; the hub is\n`https://api.yarkr.com/functions/v1/api`).\n\n```bash\nAPI=https://api.yarkr.com/functions/v1/api/api/v1\nAUTH="Authorization: Bearer $YARKR_TOKEN"\n\n# Create\ncurl -sf -X POST "$API/arks/<org>" -H "$AUTH" -H \'content-type: application/json\' \\\n -d \'{"name":"<ark>","description":"<one sentence>"}\'\n\n# One call per package\ncurl -sf -X PUT "$API/arks/<org>/<ark>/items/<ns>/<name>" -H "$AUTH" -H \'content-type: application/json\' \\\n -d \'{"range":"^1","required":true}\'\n```\n\nItem body:\n\n| Field | Use |\n| --- | --- |\n| `range` | Semver range. Use `^<major>` for tagged packages. Omit it (and `channel`) to follow `stable`, which suits new snapshot-only packages |\n| `channel` | Instead of a range, e.g. `stable` |\n| `required` | `true` for the core of the topic; `false` for extras members may exclude |\n| `config` | Toolsets only: per-ark overrides, `{"servers": {"<server>": {"env": {\u2026}, "headers": {\u2026}, "args": [\u2026], "url": "\u2026"}}}`. Secret values must be `${env:\u2026}` or `${vault:\u2026}` |\n\nThe server rejects an item that the org can\'t use, a private package in a public ark, a bad range, a literal secret,\nor a range that conflicts with another ark sharing a member. Read the error and fix the cause; don\'t retry blindly.\n\nThen, only if asked:\n\n```bash\n# Make it public (public packages only)\ncurl -sf -X PATCH "$API/arks/<org>/<ark>" -H "$AUTH" -H \'content-type: application/json\' -d \'{"public":true}\'\n\n# Add members (they must already belong to the org)\ncurl -sf -X POST "$API/arks/<org>/<ark>/members" -H "$AUTH" -H \'content-type: application/json\' -d \'{"email":"\u2026"}\'\n```\n\n## 6. Verify\n\n```bash\ncurl -sf "$API/arks/<org>/<ark>" -H "$AUTH" # items, ranges, description\nyarkr ark lint <org>/<ark> --strict # fix every error and warning it reports\nyarkr sync <org>/<ark> --dry-run --harness claude-code # resolves every item without writing files\n```\n\nFor a public ark, also check `yarkr arks` lists it.\n\n## Report back\n\nTell the user:\n\n- the ark (`<org>/<ark>`), its description and whether it\'s public;\n- each item: reused or new, range, required or optional, one line on why it\'s there;\n- the PR link for new packages, and whether it\'s merged;\n- what\'s left for them: merging the PR, adding members, setting the toolset variables listed in the READMEs, and how\n people get it (`yarkr sync <org>/<ark>`, or `yarkr mcp add <org>/<ark>`).\n\n## Don\'ts\n\n- Don\'t put literal secrets anywhere: repo, ark config or PR description.\n- Don\'t add packages you haven\'t read.\n- Don\'t make an ark public, add members, or merge a PR unless the user asked.\n- Don\'t pad the ark to reach a number. Three good packages beat eight vague ones.\n'
44815
+ "create-ark": '---\nname: create-ark\ndescription: Build a Yarkr ark (a set of skills, agents or MCP configs handed to a team) for a given topic and type. Use when asked to create, assemble or curate an ark, or to collect a topic\'s skills, agents or MCP servers for a team in Yarkr.\n---\n\n# Creating an ark from a topic and a type\n\nYou get two inputs and build one [ark](https://github.com/posterus-ltd/yarkr-public/blob/main/docs/arks.md) that hands out packages on that topic.\n\n| Input | Example | Meaning |\n| --- | --- | --- |\n| **topic** | `invoice processing`, `frontend testing` | what the ark is about |\n| **type** | `skill`, `agent` or `mcp` | the kind of package it holds. `mcp` is a **toolset** in Yarkr (`--kind toolset`, `kind: toolset`) |\n\nPlus, from the user or their config: the **org** (its slug is its GitHub login, lowercased), and whether the ark should\nbe **public**. Ask for the org if you can\'t tell; default to private.\n\nAn ark only points at packages. Packages come from GitHub ([github.md](https://github.com/posterus-ltd/yarkr-public/blob/main/docs/github.md)): there is no way to upload content to\nYarkr directly. So the work is: find what exists, write what\'s missing in a repo, then assemble the ark.\n\n```\n1. plan \u2500\u2500\u25B6 2. find existing \u2500\u2500\u25B6 3. author missing (PR) \u2500\u2500\u25B6 4. wait for index \u2500\u2500\u25B6 5. create ark + items \u2500\u2500\u25B6 6. verify\n```\n\n---\n\n## 1. Plan\n\n- **Ark name:** a slug, `^[a-z0-9][a-z0-9-]*$`, short and named after the topic: `invoice-processing`, not\n `ark-for-invoices`. It must be unique in the org.\n- **Description:** one sentence, at most 500 characters, saying who it\'s for and what they get.\n- **Scope:** aim for 3 to 8 packages. Every skill in an ark goes into every member\'s context; too many makes agents\n worse. Split a broad topic into several arks rather than one big one.\n- List the packages you expect to need, each with a one-line purpose, before searching.\n\n## 2. Find what already exists\n\nReuse beats writing. Search the registry for each planned package:\n\n```bash\nyarkr search "<topic words>" --kind <skill|agent|toolset> --json\nyarkr info <namespace>/<name> # source repo, targets, releases, eval results\n```\n\nOver MCP, `search_packages` with `scope: "all"`, then `get_skill` / `get_agent` / `get_toolset` to read one.\n\nKeep a package when:\n\n- its description and body actually cover the planned purpose (read it, don\'t trust the name);\n- the org can put it in an ark: **the org\'s own namespace, or any public package**;\n- for a public ark: **it is public**. A public ark can hold nothing else;\n- it isn\'t yanked or archived, and has evals that pass if any are shown.\n\n## 3. Author what\'s missing\n\nWrite each missing package in one of the org\'s **connected** repos (check **Settings \u2192 GitHub** / `/orgs/<org>/github`;\na repo that isn\'t connected is never indexed). For a public ark the repo must be public. Open **one pull request** with\nall new packages, and describe the ark they\'re for. Merging is publishing; don\'t push to the default branch yourself\nunless the user tells you to.\n\nPackage names come from the folder or `name:` frontmatter, slugified, and publish as `<org>/<name>`. Check that the name\ndoesn\'t already exist in the org.\n\n### Type `skill`\n\nA folder with `SKILL.md`. Everything in the folder ships with it. This guide is itself one.\n\n```\nskills/<name>/\n SKILL.md\n references/\u2026 # optional: long docs the skill loads on demand\n scripts/\u2026 # optional\n evals/core.yaml # optional but recommended, see https://github.com/posterus-ltd/yarkr-public/blob/main/docs/evals.md\n```\n\n```markdown\n---\nname: <name>\ndescription: <what it does and when to use it. Harnesses decide whether to load the skill from this line alone, so name the trigger situations.>\n---\n\n# <Title>\n\n<Steps the agent follows. Concrete and imperative. Point to references/ for anything long.>\n```\n\n`description` is required; without it the skill isn\'t indexed.\n\n### Type `agent`\n\nOne markdown file under `agents/` (or `.claude/agents/`).\n\n```markdown\n---\nname: <name>\ndescription: <when to hand work to this agent>\nmodel: <optional>\ntools: Read, Grep, Bash # optional\nskills: <skill-name>, other-org/skill # optional: dependencies, installed with the agent\n---\n\n<System prompt: role, what it does, how it decides, what it returns.>\n```\n\n- `description` is required; a file without it is treated as a plain doc.\n- A bare name in `skills:` means a skill in the same org. Use `ns/name` for another namespace. A skill the agent needs\n that doesn\'t exist yet is part of this same PR.\n- Add the agent\'s skills to the ark too, or leave them to the dependency: `yarkr sync` installs dependencies either way.\n\n### Type `mcp` (toolset)\n\nOne toolset per folder, in Claude Code\'s `.mcp.json` format:\n\n```\ntoolsets/<name>/\n .mcp.json\n README.md # first paragraph becomes the description\n```\n\n```json\n{\n "mcpServers": {\n "<server>": {\n "type": "http",\n "url": "https://mcp.example.com/mcp",\n "headers": { "Authorization": "Bearer ${EXAMPLE_TOKEN}" }\n }\n }\n}\n```\n\n- **No literal secrets.** Values in `env` and `headers` must be `${VAR}` (becomes `${env:VAR}`), `${VAR:-default}`\n or `${vault:path#key}`. A literal value is refused.\n- Prefer servers you can verify exist: the vendor\'s official server, or a well-known package for `stdio` (`npx -y \u2026`).\n Never invent a URL or package name. If you can\'t confirm one, stop and ask.\n- Document in the README which variables each user must set.\n\n### Optional `yarkr.yaml`\n\nNext to any asset, for what can\'t be inferred:\n\n```yaml\ntargets: [claude-code, codex] # default: every harness\ntags: [<topic>, \u2026] # helps search; use the topic\nevals: [evals/core.yaml]\n```\n\n## 4. Wait for the index\n\nAfter the PR merges, the push is indexed in the background, usually within a minute. Each package\'s first release is a\nsnapshot of the default branch (`0.0.0-main.\u2026`) unless a tag says otherwise.\n\n```bash\nyarkr info <org>/<name> # repeat until it shows a stable version\n```\n\nIf it never appears, check the repo\'s row on the org\'s GitHub page: detection problems (missing description, literal\nsecret, name clash) are listed there. Fix them in a new PR.\n\n## 5. Create the ark and add items\n\nArk changes need an **org admin** and a **read and write** token. There\'s no CLI command for this; use the web app\n(**Arks \u2192 your org**) or the API. The base URL is the registry (`yarkr whoami` prints it; the hub is\n`https://api.yarkr.com/functions/v1/api`).\n\n```bash\nAPI=https://api.yarkr.com/functions/v1/api/api/v1\nAUTH="Authorization: Bearer $YARKR_TOKEN"\n\n# Create\ncurl -sf -X POST "$API/arks/<org>" -H "$AUTH" -H \'content-type: application/json\' \\\n -d \'{"name":"<ark>","description":"<one sentence>"}\'\n\n# One call per package\ncurl -sf -X PUT "$API/arks/<org>/<ark>/items/<ns>/<name>" -H "$AUTH" -H \'content-type: application/json\' \\\n -d \'{"range":"^1","required":true}\'\n```\n\nItem body:\n\n| Field | Use |\n| --- | --- |\n| `range` | Semver range. Use `^<major>` for tagged packages. Omit it (and `channel`) to follow `stable`, which suits new snapshot-only packages |\n| `channel` | Instead of a range, e.g. `stable` |\n| `required` | `true` for the core of the topic; `false` for extras members may exclude |\n| `config` | Toolsets only: per-ark overrides, `{"servers": {"<server>": {"env": {\u2026}, "headers": {\u2026}, "args": [\u2026], "url": "\u2026"}}}`. Secret values must be `${env:\u2026}` or `${vault:\u2026}` |\n\nThe server rejects an item that the org can\'t use, a private package in a public ark, a bad range, a literal secret,\nor a range that conflicts with another ark sharing a member. Read the error and fix the cause; don\'t retry blindly.\n\nThen, only if asked:\n\n```bash\n# Make it public (public packages only)\ncurl -sf -X PATCH "$API/arks/<org>/<ark>" -H "$AUTH" -H \'content-type: application/json\' -d \'{"public":true}\'\n\n# Add members (they must already belong to the org)\ncurl -sf -X POST "$API/arks/<org>/<ark>/members" -H "$AUTH" -H \'content-type: application/json\' -d \'{"email":"\u2026"}\'\n```\n\n## 6. Verify\n\n```bash\ncurl -sf "$API/arks/<org>/<ark>" -H "$AUTH" # items, ranges, description\nyarkr ark lint <org>/<ark> --strict # fix every error and warning it reports\nyarkr sync <org>/<ark> --dry-run --harness claude-code # resolves every item without writing files\n```\n\nFor a public ark, also check `yarkr arks` lists it.\n\n## Report back\n\nTell the user:\n\n- the ark (`<org>/<ark>`), its description and whether it\'s public;\n- each item: reused or new, range, required or optional, one line on why it\'s there;\n- the PR link for new packages, and whether it\'s merged;\n- what\'s left for them: merging the PR, adding members, setting the toolset variables listed in the READMEs, and how\n people get it (`yarkr sync <org>/<ark>`, or `yarkr mcp add <org>/<ark>`).\n\n## Don\'ts\n\n- Don\'t put literal secrets anywhere: repo, ark config or PR description.\n- Don\'t add packages you haven\'t read.\n- Don\'t make an ark public, add members, or merge a PR unless the user asked.\n- Don\'t pad the ark to reach a number. Three good packages beat eight vague ones.\n',
44816
+ "index-repo": "---\nname: index-repo\ndescription: Make an existing repo a Yarkr source by adding one yarkr.index.yaml that points at the skills, agents and MCP configs already in it, in whatever layout they have, without moving or rewriting files. Use when asked to publish a repo's skills to Yarkr, make a repo Yarkr-compatible or conforming, or when lint or the hub doesn't find skills that are in the repo.\n---\n\n# Making a repo a Yarkr source\n\nYarkr publishes skills, agents and MCP configs (toolsets) from GitHub repos\n([github.md](https://github.com/posterus-ltd/yarkr-public/blob/main/docs/github.md)). It already finds assets in their\nusual layouts. Skills kept any other way (a single markdown file, a folder with a README instead of `SKILL.md`, no\nfrontmatter) are listed in one file at the repo root, `yarkr.index.yaml`, which says where each one lives and adds the\nname and description the file itself lacks. Agents and MCP configs work the same way.\n\n**The goal is the smallest possible diff:** ideally one new file. Don't move, rename or reformat existing files, and\ndon't add frontmatter to them: the index carries it. The only edits to existing files you may need are removing literal\nsecrets from MCP configs (step 5).\n\n```\n1. lint \u2500\u2500\u25B6 2. survey \u2500\u2500\u25B6 3. choose \u2500\u2500\u25B6 4. write yarkr.index.yaml \u2500\u2500\u25B6 5. fix secrets \u2500\u2500\u25B6 6. lint clean \u2500\u2500\u25B6 7. PR\n```\n\n---\n\n## 1. See what Yarkr already finds\n\nFrom the repo root:\n\n```bash\nnpx yarkr-cli lint # or `yarkr lint`; --json for machine-readable output\n```\n\nIt lists every asset that would be published (name, kind, path, description), using the same detector as the hub,\nthen **errors** (what the hub would refuse) and **warnings** (unknown index keys, descriptions that don't say when to\nuse the asset, agent skills missing from the repo). These are found without an index:\n\n| Layout | Becomes |\n| --- | --- |\n| `<dir>/SKILL.md` with `description` frontmatter | a skill; the whole folder ships with it |\n| `agents/<name>.md`, `.claude/agents/<name>.md` with `description` frontmatter | an agent |\n| `.mcp.json` at the root, `toolsets/<name>/.mcp.json` | a toolset |\n\nAnything listed here needs no index entry. Fix what it reports in step 4 or 5.\n\n## 2. Find the skills Yarkr misses\n\nLook for skills, agents and MCP configs that step 1 didn't list. They're usually there, just in a different shape:\n\n| You find | It is | In the index |\n| --- | --- | --- |\n| A folder of instructions plus scripts or references, with `README.md` (or another `.md`) instead of `SKILL.md` | a skill | `path` to the folder, `entry` to its instructions file |\n| A single markdown file with a skill's instructions (`ai/skills/code-review.md`) | a skill | `path` to the file |\n| A `SKILL.md` without `description` frontmatter | a skill | `path` plus `description`, instead of editing the file |\n| Skills under a folder that Yarkr ignores, or nested inside another skill | skills | one entry each |\n| An agent definition outside `agents/` or `.claude/agents/`, or without `description` | an agent | `path`, `description`, and its `skills` |\n| An MCP config other than a root `.mcp.json` or `toolsets/<name>/.mcp.json` (`.cursor/mcp.json`, `.vscode/mcp.json`) | a toolset | `path` and `name` |\n\nSearch by content as well as by name: skills tend to have imperative steps for a task and a \"when to use\" line.\nIgnore `node_modules`, vendored code, test fixtures, generated files, and examples that show a format rather than\nbeing a skill.\n\n## 3. Choose what to publish\n\nPublish a skill only if **someone outside this repo would load it** for a recognisable task. For each one, decide:\n\n- **Kind.** Steps for a task are a **skill**. A role the main agent delegates to (with its own system prompt, tools,\n maybe model) is an **agent**. MCP server config is a **toolset**.\n- **Keep out:** `CLAUDE.md`, `AGENTS.md` and other files that only describe this codebase (its layout, commands,\n conventions). They're project context, not skills.\n- **Description.** One or two sentences: what it does, then *when to use it* (\"Use when \u2026\"). Harnesses decide whether\n to load a skill from this line alone, so name the trigger situations. Read the file; don't guess from its name.\n- **Name.** A short slug, unique in the repo, `<org>/<name>` once published. Defaults to the frontmatter `name`, else the\n file or folder name. Set `name` when the default is vague (`README`, `howto`).\n- **A file or a folder.** A lone markdown file is a skill on its own. If it relies on scripts, templates or references\n next to it, point at the folder instead, with `entry` naming the instructions file.\n\nIf unsure whether something belongs, ask the user with your shortlist rather than publishing everything.\n\n## 4. Write `yarkr.index.yaml`\n\nAt the repo root (or at the **root folder** set when the repo was connected). Paths are relative to the index file.\nList only what step 1 didn't already find.\n\n```yaml\n# Assets Yarkr publishes from this repo, in addition to the usual layouts.\n# https://github.com/posterus-ltd/yarkr-public/blob/main/docs/github.md#yarkrindexyaml\nskills:\n - path: ai/skills/code-review.md # one file: the whole skill\n description: Review a diff for bugs, security issues and missing tests. Use when asked to review a PR or a change.\n - path: ai/skills/pdf # a folder: everything in it ships\n entry: README.md # its instructions file (default SKILL.md, else README.md)\n name: pdf-forms\n description: Fill and read PDF forms with the bundled scripts. Use for any PDF form task.\n - ai/skills/release/howto.md # a bare path works when the file has description frontmatter\n\nagents:\n - path: ai/triage-agent.md\n description: Triages new GitHub issues into labels and owners. Hand it any untriaged issue.\n skills: [code-review] # skills it uses: a name here, or <org>/<name> elsewhere\n tools: [Read, Grep] # optional\n model: sonnet # optional\n\ntoolsets:\n - path: .cursor/mcp.json\n name: github\n description: GitHub's MCP server for issues and PRs. Needs GITHUB_TOKEN.\n```\n\nEvery entry takes `path` and, optionally, `name`, `description`, `tags`, `targets` (harness ids) and `evals` (suite\npaths). Values in the index win over the file's frontmatter.\n\nAdd `detect: false` at the top level when the repo has `SKILL.md` or `agents/*.md` files you don't want published\n(fixtures, examples, drafts): then only the index is published.\n\n## 5. Remove literal secrets from MCP configs\n\nA toolset with a literal value in `env` or `headers` is refused. Replace each with `${VAR}` (or `${vault:path#key}`)\nand tell the user which variables to set. This is the one case where you edit an existing file; mention it in the PR.\nIf the file is also used locally with real values, it's leaking a secret already: say so.\n\n## 6. Lint until clean\n\n```bash\nnpx yarkr-cli lint --strict\n```\n\nIt exits 1 on errors, and with `--strict` on warnings too. Fix warnings unless there's a reason not to (say, an agent's\nskill published from another repo). Check that every asset you chose appears once, with the name and description you\nmeant, and nothing you left out appears. Suggest adding `npx yarkr-cli lint` to CI so the index can't drift from the\nfiles it points at.\n\n## 7. Hand it over\n\n- Open a PR with `yarkr.index.yaml` (and any secret fixes). The description lists what will be published and why the\n rest was left out.\n- After merge, an org admin connects the repo in Yarkr (**the org's GitHub page \u2192 Connect**). Every push to the default\n branch publishes a new release of each asset whose files changed.\n- To change an asset later, edit its file where it is. Moving it means updating its `path` in the index.\n\n## Rules\n\n- Never invent an asset, a description that the file doesn't support, or an MCP server URL or package.\n- Never move, rename or rewrite existing files to fit Yarkr. The index exists so you don't have to.\n- One asset per entry; names unique. Don't list the same file twice or a file inside a listed folder.\n"
44720
44817
  };
44721
44818
 
44722
44819
  // ../../packages/core/dist/guides/index.js
@@ -44867,8 +44964,82 @@ async function arkLint(ref, opts) {
44867
44964
  return findings;
44868
44965
  }
44869
44966
 
44967
+ // src/commands/repo-lint.ts
44968
+ var import_yaml5 = __toESM(require_dist(), 1);
44969
+ import { readFile as readFile10 } from "fs/promises";
44970
+ import path17 from "path";
44971
+ var INDEX_KEYS = /* @__PURE__ */ new Set(["skills", "agents", "toolsets", "detect"]);
44972
+ var ENTRY_KEYS = {
44973
+ skills: /* @__PURE__ */ new Set(["path", "name", "description", "tags", "targets", "evals", "entry"]),
44974
+ agents: /* @__PURE__ */ new Set(["path", "name", "description", "tags", "targets", "evals", "skills", "tools", "model"]),
44975
+ toolsets: /* @__PURE__ */ new Set(["path", "name", "description", "tags", "targets", "evals"])
44976
+ };
44977
+ var TRIGGER = /\b(use (it )?(when|for|to|if)|when (the user|asked|you)|whenever|hand it|for any)\b/i;
44978
+ var MIN_DESCRIPTION = 40;
44979
+ async function lintRepo(dir, opts = {}) {
44980
+ const res = await scanFolder(dir, { namespace: opts.namespace, fallbackNamespace: await gitOwner(dir) ?? void 0, label: "working tree" });
44981
+ const findings = [];
44982
+ const add2 = (severity, rule, message, pkg) => findings.push({ severity, rule, message, ...pkg ? { package: pkg } : {} });
44983
+ for (const p of res.problems) add2("error", "detect", `${p.path}: ${p.message}`);
44984
+ const indexText = await readFile10(path17.join(res.root, INDEX_FILE), "utf8").catch(() => null);
44985
+ if (indexText !== null) {
44986
+ let raw = null;
44987
+ try {
44988
+ raw = import_yaml5.default.parse(indexText);
44989
+ } catch {
44990
+ }
44991
+ if (raw && typeof raw === "object") {
44992
+ for (const [key, list] of Object.entries(raw)) {
44993
+ if (!INDEX_KEYS.has(key)) add2("warning", "index-key", `${INDEX_FILE}: unknown key \`${key}\` is ignored. Expected ${[...INDEX_KEYS].join(", ")}.`);
44994
+ if (!ENTRY_KEYS[key] || !Array.isArray(list)) continue;
44995
+ list.forEach((e, i) => {
44996
+ if (!e || typeof e !== "object") return;
44997
+ for (const k of Object.keys(e)) if (!ENTRY_KEYS[key].has(k)) add2("warning", "index-key", `${INDEX_FILE}: ${key}[${i}] has unknown key \`${k}\`, which is ignored.`);
44998
+ const targets = e.targets;
44999
+ for (const t of Array.isArray(targets) ? targets : []) {
45000
+ if (!HARNESS_IDS.includes(String(t))) add2("warning", "target", `${INDEX_FILE}: ${key}[${i}] targets unknown harness \`${t}\`. Known: ${HARNESS_IDS.join(", ")}.`);
45001
+ }
45002
+ });
45003
+ }
45004
+ }
45005
+ }
45006
+ const assets = res.packages.map(({ asset }) => ({ name: asset.manifest.name, kind: asset.kind, path: asset.path, description: asset.manifest.description, manifest: asset.manifest }));
45007
+ if (!assets.length && !res.problems.length) add2("error", "empty", `Nothing to publish. List assets in ${INDEX_FILE}; \`yarkr guide index-repo\` explains how.`);
45008
+ const names = new Set(assets.map((a) => a.name));
45009
+ for (const a of assets) {
45010
+ if (a.kind !== "toolset" && a.description.length < MIN_DESCRIPTION) add2("warning", "description", `The description is very short. Say what it does and when to use it.`, a.name);
45011
+ else if (a.kind !== "toolset" && !TRIGGER.test(a.description)) add2("warning", "description", `The description doesn't say when to use it ("Use when \u2026"), so harnesses may not load it.`, a.name);
45012
+ if (a.description.length >= 500) add2("warning", "description", "The description is cut at 500 characters.", a.name);
45013
+ const ns = a.name.split("/")[0];
45014
+ for (const dep of Object.keys(a.manifest.depends.skills ?? {})) {
45015
+ if (dep.startsWith(`${ns}/`) && !names.has(dep)) add2("warning", "dependency", `Uses skill ${dep}, which isn't in this repo. Fine if another repo in the org publishes it.`, a.name);
45016
+ }
45017
+ }
45018
+ return { assets: assets.map(({ manifest: _, ...a }) => a), findings };
45019
+ }
45020
+ async function repoLint(dir, opts) {
45021
+ const { assets, findings } = await lintRepo(dir, opts);
45022
+ const errors = findings.filter((f) => f.severity === "error").length;
45023
+ const warnings = findings.length - errors;
45024
+ if (opts.json) console.log(JSON.stringify({ assets, errors, warnings, findings }, null, 2));
45025
+ else {
45026
+ if (assets.length) ui.table(assets.map((a) => [ui.bold(a.name), a.kind, a.path, a.description.slice(0, 60)]), ["package", "kind", "path", "description"]);
45027
+ for (const f of findings) {
45028
+ const line = `${f.package ? `${ui.bold(f.package)} ` : ""}${f.message} ${ui.dim(`[${f.rule}]`)}`;
45029
+ if (f.severity === "error") ui.error(line);
45030
+ else ui.warn(line);
45031
+ }
45032
+ if (!findings.length) ui.ok(`Ready: ${assets.length} asset(s) will be published.`);
45033
+ }
45034
+ if (errors || opts.strict && warnings) {
45035
+ throw new CliError(`${errors} error(s), ${warnings} warning(s).`, errors ? "Assets with errors aren't published until they're fixed." : "Warnings fail the lint because of --strict.");
45036
+ }
45037
+ if (warnings && !opts.json) ui.info(`${warnings} warning(s). Pass --strict to fail on them.`);
45038
+ return { assets, findings };
45039
+ }
45040
+
44870
45041
  // src/index.ts
44871
- var VERSION = true ? "0.1.5" : "0.0.0-dev";
45042
+ var VERSION = true ? "1.0.1" : "0.0.0-dev";
44872
45043
  var act = (fn) => async (...a) => {
44873
45044
  await fn(...a);
44874
45045
  };
@@ -44886,6 +45057,7 @@ function buildProgram() {
44886
45057
  const arkCmd = program2.command("ark").argument("[arks...]", "limit to these arks, e.g. acme/finance or @me").description("show what your arks give you").option("--json").action(act((refs, o) => showArk(refs, o)));
44887
45058
  arkCmd.command("lint").argument("<ark>", "an ark you can read, e.g. acme/finance").description("check an ark's items: missing or private packages, ranges that resolve to nothing, literal secrets, failing evals").option("--strict", "exit 1 on warnings too").option("--json").action(act((ref, o) => arkLint(ref, { ...o, json: o.json || !!arkCmd.opts().json })));
44888
45059
  program2.command("arks").description("list public arks: curated sets anyone can sync or connect over MCP").option("--json").action(act(publicArks));
45060
+ program2.command("lint").argument("[dir]", "a repo checkout", ".").description("check a repo is set up as a Yarkr source: what it publishes, problems the hub would refuse, and index typos or weak descriptions").option("--namespace <ns>", "namespace to show names in (default: the git owner)").option("--strict", "exit 1 on warnings too").option("--json").action(act(repoLint));
44889
45061
  program2.command("guide").argument("[name]", "a guide, e.g. create-ark; omit to list them").description("print instructions for your agent, e.g. how to build an ark for a topic").option("-o, --out <path>", "write to a file, or <dir>/SKILL.md for a directory (e.g. ~/.claude/skills/create-ark/)").option("--topic <topic>", "create-ark: what the ark is about").addOption(new Option("--type <type>", "create-ark: the kind of package it holds").choices(["skill", "agent", "mcp"])).action(act(guide));
44890
45062
  syncFlags(program2.command("add").argument("<packages...>", "namespace/name[@range]").description("add packages to yarkr.json (outside a project: to your personal ark, or your user config when signed out) and sync").option("--no-sync", "only edit yarkr.json")).action(act((pkgs, o) => add(pkgs, { ...o, noSync: o.sync === false })));
44891
45063
  syncFlags(program2.command("remove").argument("<packages...>", "packages, or arks listed in yarkr.json").description("remove packages or arks (or opt out of optional ark items) and delete their files").option("--no-sync", "only edit yarkr.json")).action(act((pkgs, o) => remove(pkgs, { ...o, noSync: o.sync === false })));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yarkr-cli",
3
- "version": "0.1.5",
3
+ "version": "1.0.1",
4
4
  "description": "Install skills, agents and MCP toolsets from Yarkr into Claude Code, Codex, opencode, Cursor and more",
5
5
  "license": "MIT",
6
6
  "homepage": "https://yarkr.com",
@@ -48,8 +48,8 @@
48
48
  "vitest": "^3.2.4",
49
49
  "yaml": "^2.8.1",
50
50
  "@yarkr/core": "0.1.0",
51
- "@yarkr/schema": "0.1.0",
52
- "@yarkr/evals": "0.1.0"
51
+ "@yarkr/evals": "0.1.0",
52
+ "@yarkr/schema": "0.1.0"
53
53
  },
54
54
  "scripts": {
55
55
  "build": "tsup",