fleetfizz 0.14.1 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/fleetfizz.mjs +360 -91
  2. package/package.json +1 -1
@@ -16,7 +16,7 @@ import * as readline from "node:readline";
16
16
  import process$1, { cwd, env } from "node:process";
17
17
  import tty from "node:tty";
18
18
  import { EventEmitter } from "node:events";
19
- import { types } from "node:util";
19
+ import { stripVTControlCharacters, types } from "node:util";
20
20
  //#region \0rolldown/runtime.js
21
21
  var __create = Object.create;
22
22
  var __defProp = Object.defineProperty;
@@ -3791,7 +3791,7 @@ const ServiceProto = {
3791
3791
  .../*#__PURE__*/ Prototype({
3792
3792
  label: "Service",
3793
3793
  evaluate(fiber) {
3794
- return exitSucceed(get$3(fiber.context, this));
3794
+ return exitSucceed(get$4(fiber.context, this));
3795
3795
  }
3796
3796
  }),
3797
3797
  toJSON() {
@@ -3807,10 +3807,10 @@ const ServiceProto = {
3807
3807
  return make$32(this, self);
3808
3808
  },
3809
3809
  use(f) {
3810
- return withFiber$1((fiber) => f(get$3(fiber.context, this)));
3810
+ return withFiber$1((fiber) => f(get$4(fiber.context, this)));
3811
3811
  },
3812
3812
  useSync(f) {
3813
- return withFiber$1((fiber) => exitSucceed(f(get$3(fiber.context, this))));
3813
+ return withFiber$1((fiber) => exitSucceed(f(get$4(fiber.context, this))));
3814
3814
  }
3815
3815
  };
3816
3816
  const cacheKeys = /*#__PURE__*/ new Set();
@@ -4170,7 +4170,7 @@ const getUnsafe = /*#__PURE__*/ dual(2, (self, service) => {
4170
4170
  * @category getters
4171
4171
  * @since 2.0.0
4172
4172
  */
4173
- const get$3 = getUnsafe;
4173
+ const get$4 = getUnsafe;
4174
4174
  const defaultValueCacheKey = "~effect/Context/defaultValue";
4175
4175
  const getDefaultValue = (ref) => {
4176
4176
  if (defaultValueCacheKey in ref) return ref[defaultValueCacheKey];
@@ -6908,7 +6908,7 @@ var FiberImpl = class {
6908
6908
  return this._dispatcher ??= this.cache.scheduler.makeDispatcher();
6909
6909
  }
6910
6910
  getRef(ref) {
6911
- return get$3(this.context, ref);
6911
+ return get$4(this.context, ref);
6912
6912
  }
6913
6913
  addObserver(cb) {
6914
6914
  if (this._exit) {
@@ -7066,17 +7066,17 @@ var FiberImpl = class {
7066
7066
  const makeFiberContextCache = (context) => {
7067
7067
  const currentTracer = getOrUndefinedUnsafe(context, TracerKey);
7068
7068
  return {
7069
- scheduler: get$3(context, Scheduler),
7069
+ scheduler: get$4(context, Scheduler),
7070
7070
  tracer: currentTracer,
7071
7071
  tracerContext: currentTracer ? currentTracer["context"] : void 0,
7072
- tracerEnabled: get$3(context, TracerEnabled),
7072
+ tracerEnabled: get$4(context, TracerEnabled),
7073
7073
  span: getOrUndefinedUnsafe(context, ParentSpanKey),
7074
- logLevel: get$3(context, CurrentLogLevel),
7075
- minimumLogLevel: get$3(context, MinimumLogLevel$1),
7076
- stackFrame: get$3(context, CurrentStackFrame),
7074
+ logLevel: get$4(context, CurrentLogLevel),
7075
+ minimumLogLevel: get$4(context, MinimumLogLevel$1),
7076
+ stackFrame: get$4(context, CurrentStackFrame),
7077
7077
  runtimeMetrics: getOrUndefinedUnsafe(context, FiberRuntimeMetricsKey),
7078
- maxOpsBeforeYield: get$3(context, MaxOpsBeforeYield),
7079
- preventYield: get$3(context, PreventSchedulerYield)
7078
+ maxOpsBeforeYield: get$4(context, MaxOpsBeforeYield),
7079
+ preventYield: get$4(context, PreventSchedulerYield)
7080
7080
  };
7081
7081
  };
7082
7082
  const deferredInterruptCont = {
@@ -8434,11 +8434,11 @@ const NoopSpanProto = {
8434
8434
  const noopSpan = (options) => Object.assign(Object.create(NoopSpanProto), options);
8435
8435
  const filterDisablePropagation = (span) => {
8436
8436
  if (!span) return none();
8437
- return get$3(span.annotations, DisablePropagation) ? span._tag === "Span" ? filterDisablePropagation(getOrUndefined$1(span.parent)) : none() : some(span);
8437
+ return get$4(span.annotations, DisablePropagation) ? span._tag === "Span" ? filterDisablePropagation(getOrUndefined$1(span.parent)) : none() : some(span);
8438
8438
  };
8439
8439
  /** @internal */
8440
8440
  const makeSpanUnsafe = (fiber, name, options) => {
8441
- const disablePropagation = !fiber.cache.tracerEnabled || options?.annotations && get$3(options.annotations, DisablePropagation);
8441
+ const disablePropagation = !fiber.cache.tracerEnabled || options?.annotations && get$4(options.annotations, DisablePropagation);
8442
8442
  const parent = options?.parent !== void 0 ? some(options.parent) : options?.root ? none() : filterDisablePropagation(fiber.cache.span);
8443
8443
  let span;
8444
8444
  if (disablePropagation) span = noopSpan({
@@ -18071,7 +18071,7 @@ var RcRefImpl = class {
18071
18071
  /** @internal */
18072
18072
  const make$24 = (options) => withFiber((fiber) => {
18073
18073
  const context = fiber.context;
18074
- const scope = get$3(context, Scope);
18074
+ const scope = get$4(context, Scope);
18075
18075
  const ref = new RcRefImpl(options.acquire, context, scope, options.idleTimeToLive !== void 0 ? fromInputUnsafe$1(options.idleTimeToLive) : void 0);
18076
18076
  return as(addFinalizerExit(scope, () => {
18077
18077
  const state = ref.state;
@@ -18134,7 +18134,7 @@ const awaitAcquiring = (self, acquiring, restore) => onExit$1(restore(join(acqui
18134
18134
  return interrupt(acquiring.fiber);
18135
18135
  });
18136
18136
  /** @internal */
18137
- const get$2 = (self_) => {
18137
+ const get$3 = (self_) => {
18138
18138
  const self = self_;
18139
18139
  return uninterruptibleMask((restore) => flatMap$1(scope, (scope) => flatMap$1(getState(self, restore), (state) => as(addFinalizerExit(scope, () => release(self, state)), state.value))));
18140
18140
  };
@@ -18252,7 +18252,7 @@ const make$23 = make$24;
18252
18252
  * @category combinators
18253
18253
  * @since 3.5.0
18254
18254
  */
18255
- const get$1 = get$2;
18255
+ const get$2 = get$3;
18256
18256
  //#endregion
18257
18257
  //#region ../../node_modules/.pnpm/effect@4.0.2/node_modules/effect/dist/Stream.js
18258
18258
  /**
@@ -24846,7 +24846,7 @@ const TypeId$15 = /*#__PURE__*/ Symbol.for("~effect/http/Headers");
24846
24846
  const Proto$7 = /*#__PURE__*/ Object.defineProperties(/*#__PURE__*/ Object.create(null), {
24847
24847
  [TypeId$15]: { value: TypeId$15 },
24848
24848
  [symbolRedactable]: { value(context) {
24849
- return redact(this, get$3(context, CurrentRedactedNames));
24849
+ return redact(this, get$4(context, CurrentRedactedNames));
24850
24850
  } },
24851
24851
  toJSON: { value() {
24852
24852
  return redact$1(this);
@@ -28692,13 +28692,13 @@ const layer$5 = /*#__PURE__*/ effect(Terminal, /*#__PURE__*/ (/* @__PURE__ */ fn
28692
28692
  stdin.on("keypress", handleKeypress);
28693
28693
  if (inputEnded) handleEnd();
28694
28694
  else {
28695
- yield* get$1(rlRef);
28695
+ yield* get$2(rlRef);
28696
28696
  stdin.once("end", handleEnd);
28697
28697
  }
28698
28698
  return queue;
28699
28699
  });
28700
28700
  const readLine = suspend$2(() => poll(lines).pipe(flatMap$1(match$5({
28701
- onNone: () => scoped(andThen(get$1(rlRef), take(lines))),
28701
+ onNone: () => scoped(andThen(get$2(rlRef), take(lines))),
28702
28702
  onSome: succeed$3
28703
28703
  })), mapError(() => new QuitError({}))));
28704
28704
  const display = (prompt) => uninterruptible(callback$1((resume) => {
@@ -35654,7 +35654,7 @@ const runWith = (command, config) => {
35654
35654
  };
35655
35655
  //#endregion
35656
35656
  //#region package.json
35657
- var version = "0.14.1";
35657
+ var version = "0.15.0";
35658
35658
  //#endregion
35659
35659
  //#region ../../node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
35660
35660
  /**
@@ -38460,7 +38460,8 @@ const ReposSection = section({ cloneRoot: optionalKey(variants(withShorthand(Str
38460
38460
  encode: (root) => root.path
38461
38461
  }))).annotateKey({ description: `Where a Machine keeps its Managed Clones: a path, or a list of \`{ path, platforms?, machines? }\` where a Machine gets the first that targets it. \`~\` expands on that Machine. Default: \`${DEFAULT_CLONE_ROOT}\`.` }) }, Record(NonEmptyString, variants(TrackedRepo)), `Tracked Repos, keyed by name, which names the Managed Clone and the \`repos:<name>\` Item. Each is \`{ origin, ref, install, ... }\`, ${VARIANTS}.`);
38462
38462
  const ITEM_NAME = /^[A-Za-z0-9._-]+$/;
38463
- const badName = (names) => names.find((n) => !ITEM_NAME.test(n) || n === "." || n === "..");
38463
+ const isItemName = (name) => ITEM_NAME.test(name) && name !== "." && name !== "..";
38464
+ const badName = (names) => names.find((name) => !isItemName(name));
38464
38465
  const HomePath = NonEmptyString.check(isPattern(/^(\/|~\/)./, {
38465
38466
  title: "path",
38466
38467
  description: "an absolute path, or one under ~"
@@ -38562,6 +38563,11 @@ var ConfigNotFound = class extends TaggedError()("ConfigNotFound", { path: Strin
38562
38563
  return `no config at ${this.path}; run fizz init to write one, or pass --config`;
38563
38564
  }
38564
38565
  };
38566
+ var ConfigIsLink = class extends TaggedError()("ConfigIsLink", { path: String$6 }) {
38567
+ get message() {
38568
+ return `${this.path} is a symlink. fleetfizz copies the config to every Machine itself, so it must be a regular file; to keep its history, put its directory under git instead`;
38569
+ }
38570
+ };
38565
38571
  const lineOf = (text, offset) => text.slice(0, offset).split("\n").length;
38566
38572
  const repeatedKeys = (text) => {
38567
38573
  const problems = [];
@@ -38637,10 +38643,19 @@ const parseConfig = fn("parseConfig")(function* (path, text) {
38637
38643
  })));
38638
38644
  });
38639
38645
  const formatIssue = makeFormatterDefault();
38640
- const loadConfig = fn("loadConfig")(function* (path) {
38646
+ const refuseLink = fn("refuseLink")(function* (path) {
38641
38647
  const fs = yield* FileSystem;
38642
- if (!(yield* fs.exists(path))) return yield* new ConfigNotFound({ path });
38643
- const text = yield* fs.readFileString(path);
38648
+ if (isSome(yield* option(fs.readLink(path)))) return yield* new ConfigIsLink({ path });
38649
+ });
38650
+ const readConfigText = fn("readConfigText")(function* (path) {
38651
+ yield* refuseLink(path);
38652
+ const fs = yield* FileSystem;
38653
+ if (!(yield* fs.exists(path))) return void 0;
38654
+ return yield* fs.readFileString(path);
38655
+ });
38656
+ const loadConfig = fn("loadConfig")(function* (path) {
38657
+ const text = yield* readConfigText(path);
38658
+ if (text === void 0) return yield* new ConfigNotFound({ path });
38644
38659
  return yield* parseConfig(path, text);
38645
38660
  });
38646
38661
  //#endregion
@@ -38766,9 +38781,11 @@ const insertSpread = (text, path, value) => {
38766
38781
  const result = insertText(text, path, value);
38767
38782
  if (!("text" in result)) return result;
38768
38783
  const at = firstDifference(text, result.text);
38769
- if (lineLength(result.text, at) <= WIDTH) return result;
38784
+ const tooLong = lineLength(result.text, at) > WIDTH;
38785
+ const added = result.text.slice(at, at + result.text.length - text.length);
38786
+ if (!tooLong && !added.includes("\n")) return result;
38770
38787
  const container = oneLineAround(text, parse$2(text), Math.min(at, text.length - 1));
38771
- if (container === void 0) return result;
38788
+ if (container === void 0 || !tooLong && at >= container.offset + container.length - 1) return result;
38772
38789
  const spreadText = spread(text, container);
38773
38790
  return sameComments(text, spreadText) ? insertSpread(spreadText, path, value) : result;
38774
38791
  };
@@ -38863,12 +38880,12 @@ const targetingLists = (text, machine) => {
38863
38880
  };
38864
38881
  const saveConfig = fn("saveConfig")(function* (path, text) {
38865
38882
  const config = yield* parseConfig(path, text);
38883
+ yield* refuseLink(path);
38866
38884
  const fs = yield* FileSystem;
38867
- const target = (yield* fs.exists(path)) ? yield* fs.realPath(path) : path;
38868
- yield* fs.makeDirectory((yield* Path$1).dirname(target), { recursive: true });
38869
- yield* fs.writeFileString(`${target}.tmp`, text);
38870
- if (yield* fs.exists(target)) yield* fs.chmod(`${target}.tmp`, (yield* fs.stat(target)).mode & 4095);
38871
- yield* fs.rename(`${target}.tmp`, target);
38885
+ yield* fs.makeDirectory((yield* Path$1).dirname(path), { recursive: true });
38886
+ yield* fs.writeFileString(`${path}.tmp`, text);
38887
+ if (yield* fs.exists(path)) yield* fs.chmod(`${path}.tmp`, (yield* fs.stat(path)).mode & 4095);
38888
+ yield* fs.rename(`${path}.tmp`, path);
38872
38889
  return config;
38873
38890
  });
38874
38891
  //#endregion
@@ -39196,6 +39213,10 @@ const EngineFact = Union([
39196
39213
  sha: optionalKey(String$6),
39197
39214
  base64: optionalKey(String$6)
39198
39215
  }),
39216
+ Struct({
39217
+ fact: Literal("configLink"),
39218
+ path: String$6
39219
+ }),
39199
39220
  Struct({
39200
39221
  fact: Literal("configRecord"),
39201
39222
  record: Unknown
@@ -39250,6 +39271,12 @@ const assembleState = (facts) => {
39250
39271
  }
39251
39272
  };
39252
39273
  break;
39274
+ case "configLink":
39275
+ state = {
39276
+ ...state,
39277
+ configLink: f.path
39278
+ };
39279
+ break;
39253
39280
  case "configRecord":
39254
39281
  state = {
39255
39282
  ...state,
@@ -39341,7 +39368,7 @@ const checkScript = (input) => {
39341
39368
  lines.push(provider.observe(items, input.scope));
39342
39369
  if (provider.rule === "record") for (const item of items) lines.push(...recordFact(provider.name, item.name));
39343
39370
  }
39344
- lines.push(`ff_c=${CONFIG_DIR_SH}/${quote(CONFIG_FILE)}`, `if [ -f "$ff_c" ]; then ff_b=$(base64 < "$ff_c" | tr -d '\\n'); ff_state "{\\"fact\\":\\"config\\",\\"present\\":true,\\"sha\\":\\"$(printf '%s' "$ff_b" | base64 -d | ff_sha)\\",\\"base64\\":\\"$ff_b\\"}"; else ff_state '{"fact":"config","present":false}'; fi`, `ff_r=${quotePath(CONFIG_RECORD)}`, `if [ -f "$ff_r" ]; then ff_state "{\\"fact\\":\\"configRecord\\",\\"record\\":$(tr -d '\\n' < "$ff_r")}"; fi`);
39371
+ lines.push(`ff_c=${CONFIG_DIR_SH}/${quote(CONFIG_FILE)}`, `if [ -L "$ff_c" ]; then ff_state "{\\"fact\\":\\"configLink\\",\\"path\\":\\"$(ff_json "$ff_c")\\"}"; elif [ -f "$ff_c" ]; then ff_b=$(base64 < "$ff_c" | tr -d '\\n'); ff_state "{\\"fact\\":\\"config\\",\\"present\\":true,\\"sha\\":\\"$(printf '%s' "$ff_b" | base64 -d | ff_sha)\\",\\"base64\\":\\"$ff_b\\"}"; else ff_state '{"fact":"config","present":false}'; fi`, `ff_r=${quotePath(CONFIG_RECORD)}`, `if [ -f "$ff_r" ]; then ff_state "{\\"fact\\":\\"configRecord\\",\\"record\\":$(tr -d '\\n' < "$ff_r")}"; fi`);
39345
39372
  lines.push(`ff_state '{"fact":"end"}'`);
39346
39373
  return lines.join("\n");
39347
39374
  };
@@ -39479,7 +39506,7 @@ const applyScript = (plan, configRecord, init) => {
39479
39506
  `IFS= read -r ff_schema || ff_schema=""`,
39480
39507
  ...inputsOf(plan).map((_, i) => `IFS= read -r ff_in_${i} || exit 1`),
39481
39508
  `ff_git() { if [ -n "$ff_token" ]; then FF_TOKEN="$ff_token" git -c credential.helper= -c 'credential.helper=!f() { echo username=x-access-token; echo "password=$FF_TOKEN"; }; f' "$@"; else git "$@"; fi; }`,
39482
- `ff_skills() { if [ -n "$ff_token" ]; then GH_TOKEN="$ff_token" GIT_CONFIG_COUNT=2 GIT_CONFIG_KEY_0=credential.helper GIT_CONFIG_VALUE_0= GIT_CONFIG_KEY_1=credential.helper GIT_CONFIG_VALUE_1='!f() { echo username=x-access-token; echo "password=$GH_TOKEN"; }; f' DISABLE_TELEMETRY=1 skills "$@"; else DISABLE_TELEMETRY=1 skills "$@"; fi; }`,
39509
+ `ff_skills() { if [ -n "$ff_token" ]; then GH_TOKEN="$ff_token" GIT_CONFIG_COUNT=3 GIT_CONFIG_KEY_0=core.autocrlf GIT_CONFIG_VALUE_0=false GIT_CONFIG_KEY_1=credential.helper GIT_CONFIG_VALUE_1= GIT_CONFIG_KEY_2=credential.helper GIT_CONFIG_VALUE_2='!f() { echo username=x-access-token; echo "password=$GH_TOKEN"; }; f' DISABLE_TELEMETRY=1 skills "$@"; else GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=core.autocrlf GIT_CONFIG_VALUE_0=false DISABLE_TELEMETRY=1 skills "$@"; fi; }`,
39483
39510
  ...configStep(configRecord),
39484
39511
  ...plan.providers.flatMap((p) => providerSteps(p, offsets)),
39485
39512
  doneLine("end", true)
@@ -40091,7 +40118,7 @@ const decideFile = (item, observed, resolved, ctx) => {
40091
40118
  };
40092
40119
  const recorded = record === void 0 ? void 0 : fingerprint(recordEntries(record));
40093
40120
  const same = ctx.machine.name === from || current !== void 0 && fingerprint(current) === ours;
40094
- const rerun = same && item.after !== void 0 ? ctx.rebuild(subject) ? "--rebuild" : ctx.machine.name !== from && recorded !== ours ? "its after has not finished" : void 0 : void 0;
40121
+ const rerun = same && item.after !== void 0 ? ctx.rebuild(subject) ? "--rebuild" : ctx.machine.name === from || recorded === ours ? void 0 : recorded === void 0 ? "its after has not succeeded here" : "its after has not finished" : void 0;
40095
40122
  if (rerun !== void 0) return {
40096
40123
  kind: "change",
40097
40124
  item: item.name,
@@ -41311,7 +41338,7 @@ const make$2 = (method) => (url, options) => modify(empty, {
41311
41338
  * @category constructors
41312
41339
  * @since 4.0.0
41313
41340
  */
41314
- const get = /*#__PURE__*/ make$2("GET");
41341
+ const get$1 = /*#__PURE__*/ make$2("GET");
41315
41342
  /**
41316
41343
  * Applies request options to an `HttpClientRequest`, returning a new request.
41317
41344
  *
@@ -41705,6 +41732,14 @@ const accessor = (method) => (...args) => flatMap$1(HttpClient, (client) => clie
41705
41732
  */
41706
41733
  const execute = /*#__PURE__*/ accessor("execute");
41707
41734
  /**
41735
+ * Executes a `GET` request using the `HttpClient` service from the environment.
41736
+ *
41737
+ * @stability unstable
41738
+ * @category accessors
41739
+ * @since 4.0.0
41740
+ */
41741
+ const get = /*#__PURE__*/ accessor("get");
41742
+ /**
41708
41743
  * Transforms a client by applying an effectful transformation to each response effect.
41709
41744
  *
41710
41745
  * @stability unstable
@@ -42101,7 +42136,8 @@ const treeHash = (dir) => {
42101
42136
  try {
42102
42137
  execFileSync("git", ["init", "-q", "--bare", git]);
42103
42138
  const env = { ...process.env, GIT_DIR: git, GIT_INDEX_FILE: join(git, "index") };
42104
- execFileSync("git", ["--work-tree", dir, "add", "-A", "-f", "."], { cwd: dir, env, stdio: "ignore" });
42139
+ // As ff_skills clones: no line-ending conversion, so the hash is GitHub's.
42140
+ execFileSync("git", ["-c", "core.autocrlf=false", "--work-tree", dir, "add", "-A", "-f", "."], { cwd: dir, env, stdio: "ignore" });
42105
42141
  return execFileSync("git", ["write-tree"], { env, encoding: "utf8" }).trim();
42106
42142
  } catch {
42107
42143
  return undefined;
@@ -42200,16 +42236,18 @@ const TreeResponse = Struct({
42200
42236
  path: String$6,
42201
42237
  type: String$6,
42202
42238
  sha: String$6
42203
- }))
42239
+ })),
42240
+ truncated: Boolean$4
42204
42241
  });
42205
42242
  const fetchFolders = fn("fetchFolders")(function* (repo, ref, token) {
42206
- const response = yield* execute(get(`https://api.github.com/repos/${repo}/git/trees/${encodeURIComponent(ref ?? "HEAD")}?recursive=1`).pipe(setHeaders({
42243
+ const response = yield* execute(get$1(`https://api.github.com/repos/${repo}/git/trees/${encodeURIComponent(ref ?? "HEAD")}?recursive=1`).pipe(setHeaders({
42207
42244
  Accept: "application/vnd.github+json",
42208
42245
  "User-Agent": "fleetfizz"
42209
42246
  }), (request) => token === void 0 ? request : bearerToken(request, token)));
42210
42247
  if (response.status === 404) return { error: `no ${ref ?? "default branch"} in ${repo}, or no access to it` };
42211
42248
  if (response.status !== 200) return { error: `GitHub answered ${response.status}` };
42212
42249
  const body = yield* schemaBodyJson(TreeResponse)(response);
42250
+ if (body.truncated) return { error: "the repo is too large for GitHub to list its folders" };
42213
42251
  return { folders: new Map([["", body.sha], ...body.tree.flatMap((e) => e.type === "tree" ? [[e.path, e.sha]] : [])]) };
42214
42252
  }, timeout("15 seconds"), orElseSucceed(() => ({ error: "could not reach GitHub" })), provide(layer$2));
42215
42253
  const skillQuestion = (subject, found, problem) => ({
@@ -73448,6 +73486,72 @@ const changeText = (path, before, after, summary) => toText(/* @__PURE__ */ (0,
73448
73486
  ]
73449
73487
  }));
73450
73488
  //#endregion
73489
+ //#region src/localSkills.ts
73490
+ const decodeLock = decodeUnknownOption(fromJsonString(Struct({
73491
+ version: Number$2,
73492
+ skills: Record(String$6, Struct({
73493
+ source: String$6,
73494
+ sourceType: String$6,
73495
+ ref: optionalKey(String$6)
73496
+ }))
73497
+ })));
73498
+ /**
73499
+ * The Skills in the CLI's lock, read where the CLI keeps it, that every
73500
+ * Machine can install: a GitHub source and a name the config can hold. One
73501
+ * from a path here, or a folder the CLI did not install, has no source another
73502
+ * Machine has. None when the lock is missing or from an older CLI.
73503
+ */
73504
+ const localSkills = fn("localSkills")(function* () {
73505
+ const fs = yield* FileSystem;
73506
+ const state = process.env.XDG_STATE_HOME;
73507
+ const path = state === void 0 || state === "" ? expandHome("~/.agents/.skill-lock.json") : `${state}/skills/.skill-lock.json`;
73508
+ const lock = decodeLock(yield* fs.readFileString(path).pipe(orElseSucceed(() => "")));
73509
+ if (lock._tag === "None" || lock.value.version < 3) return [];
73510
+ return Object.entries(lock.value.skills).filter(([name, entry]) => entry.sourceType === "github" && isItemName(name)).map(([name, entry]) => ({
73511
+ name,
73512
+ source: entry.source,
73513
+ ...entry.ref === void 0 ? {} : { ref: entry.ref }
73514
+ })).sort((a, b) => a.name.localeCompare(b.name));
73515
+ });
73516
+ const NO_TELEMETRY = { DISABLE_TELEMETRY: "1" };
73517
+ const decodeList = decodeUnknownOption(fromJsonString(ArraySchema(Struct({ agents: ArraySchema(String$6) }))));
73518
+ /**
73519
+ * The Agents the skills CLI sees Skills installed for here, as the ids
73520
+ * `skills add -a` takes, or none when the CLI is not here. `skills ls --json`
73521
+ * names them for display ("Claude Code" is `claude-code`), the way most ids
73522
+ * are written; the few that are not (`roo` is "Roo Code") the CLI rejects,
73523
+ * and they are dropped.
73524
+ */
73525
+ const localAgents = fn("localAgents")(function* () {
73526
+ const listed = yield* runLocal("skills", [
73527
+ "ls",
73528
+ "-g",
73529
+ "--json"
73530
+ ], NO_TELEMETRY);
73531
+ const list = listed.code === 0 ? decodeList(listed.stdout) : void 0;
73532
+ const ids = list === void 0 || list._tag === "None" ? [] : [...new Set(list.value.flatMap((skill) => skill.agents.map((agent) => agent.toLowerCase().replace(/\s+/g, "-"))))];
73533
+ if (ids.length === 0) return ids;
73534
+ const validated = yield* runLocal("skills", [
73535
+ "ls",
73536
+ "-g",
73537
+ "-a",
73538
+ ...ids,
73539
+ "--json"
73540
+ ], NO_TELEMETRY);
73541
+ const invalid = validated.code === 0 ? [] : /Invalid agents: (.*)/.exec(stripVTControlCharacters(validated.stdout))?.[1]?.split(", ") ?? ids;
73542
+ return ids.filter((id) => !invalid.includes(id));
73543
+ });
73544
+ /** The skills section's `agents`, picked from `agents`, all picked to start. */
73545
+ const pickAgents = (agents) => MultiSelect({
73546
+ message: "Which agents are skills installed for? (skills CLI ids)",
73547
+ choices: agents.map((agent) => ({
73548
+ title: agent,
73549
+ value: agent,
73550
+ selected: true
73551
+ })),
73552
+ min: 1
73553
+ });
73554
+ //#endregion
73451
73555
  //#region src/configAdd.tsx
73452
73556
  var ConfigAddError = class extends TaggedError()("ConfigAddError", { reason: String$6 }) {
73453
73557
  get message() {
@@ -73468,6 +73572,13 @@ const fileEntry = (path, exclude, targeting) => untargeted(targeting) && exclude
73468
73572
  ...exclude.length > 0 ? { exclude } : {},
73469
73573
  ...targeting
73470
73574
  };
73575
+ /** A Skill's entry: its source alone, or `{ source, ref, agents, ... }` when it has more. */
73576
+ const skillEntry = (source, ref, agents, targeting) => ref === void 0 && agents.length === 0 && untargeted(targeting) ? source : {
73577
+ source,
73578
+ ...ref === void 0 ? {} : { ref },
73579
+ ...agents.length > 0 ? { agents } : {},
73580
+ ...targeting
73581
+ };
73471
73582
  const parseSpec = (spec) => {
73472
73583
  const at = spec.lastIndexOf("@");
73473
73584
  return at > 0 ? {
@@ -73478,9 +73589,8 @@ const parseSpec = (spec) => {
73478
73589
  const fileName = (path) => (path.replace(/\/+$/, "").split("/").at(-1) ?? "").replace(/^\.+/, "").replace(/[^A-Za-z0-9._-]/g, "-");
73479
73590
  const openConfig = fn("openConfig")(function* (flag) {
73480
73591
  const path = flag ?? configPath(process.env);
73481
- const fs = yield* FileSystem;
73482
- if (!(yield* fs.exists(path))) return yield* new ConfigAddError({ reason: `no config at ${homeRelative(path)}; run fizz init to write one, or pass --config` });
73483
- const text = yield* fs.readFileString(path);
73592
+ const text = yield* readConfigText(path);
73593
+ if (text === void 0) return yield* new ConfigAddError({ reason: `no config at ${homeRelative(path)}; run fizz init to write one, or pass --config` });
73484
73594
  return {
73485
73595
  path,
73486
73596
  text,
@@ -73503,18 +73613,24 @@ const addItem = fn("addItem")(function* (input) {
73503
73613
  if (unknown.length > 0) return yield* new ConfigAddError({ reason: `not in machines: ${unknown.join(", ")} (add it with fizz config add machines)` });
73504
73614
  const tailnet = yield* readTailnet();
73505
73615
  const linux = config.machines.some((m) => tailnet.fleet.find((f) => f.name === m)?.platform === "linux");
73506
- const start = input.provider === "cask" && linux && config.cask?.platforms === void 0 ? insertAt(text, ["cask", "platforms"], ["darwin"]) : { text };
73507
- if ("reason" in start) return yield* new ConfigAddError({ reason: start.reason });
73508
- const at = [
73509
- input.provider,
73510
- "items",
73511
- input.name
73616
+ const edits = [
73617
+ ...input.provider === "cask" && linux && config.cask?.platforms === void 0 ? [[["cask", "platforms"], ["darwin"]]] : [],
73618
+ ...input.section ?? [],
73619
+ [[
73620
+ input.provider,
73621
+ "items",
73622
+ input.name
73623
+ ], input.entry]
73512
73624
  ];
73513
- const result = insertAt(start.text, at, input.entry);
73514
- if ("reason" in result) return yield* new ConfigAddError({ reason: result.reason });
73515
- const saved = yield* saveConfig(path, result.text);
73625
+ let next = text;
73626
+ for (const [at, value] of edits) {
73627
+ const edited = insertAt(next, at, value);
73628
+ if ("reason" in edited) return yield* new ConfigAddError({ reason: edited.reason });
73629
+ next = edited.text;
73630
+ }
73631
+ const saved = yield* saveConfig(path, next);
73516
73632
  const to = reach(saved, tailnet, input.provider, input.targeting);
73517
- yield* log(changeText(path, text, result.text, /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
73633
+ yield* log(changeText(path, text, next, /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
73518
73634
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(T, {
73519
73635
  tone: "ok",
73520
73636
  children: "added"
@@ -73575,6 +73691,40 @@ const addHomebrew = fn("addHomebrew")(function* (input) {
73575
73691
  notes
73576
73692
  });
73577
73693
  });
73694
+ /** The skills section's first `agents`, picked from the Agents the skills CLI sees here. */
73695
+ const pickHere = fn("pickHere")(function* () {
73696
+ const here = yield* localAgents();
73697
+ if (here.length === 0) return yield* new ConfigAddError({ reason: "the skills CLI sees no agents here; pass --agent" });
73698
+ return yield* pickAgents(here);
73699
+ });
73700
+ /**
73701
+ * `fizz config add skills <name>`. The source and ref default to the ones the
73702
+ * skills CLI installed it from here. The first Skill also writes the section's
73703
+ * `agents`, from --agent or a pick of the Agents here; after that, --agent is
73704
+ * the Skill's own.
73705
+ */
73706
+ const addSkill = fn("addSkill")(function* (input) {
73707
+ const { config } = yield* openConfig(input.config);
73708
+ const ask = interactive();
73709
+ const here = input.source === void 0 ? (yield* localSkills()).find((skill) => skill.name === input.name) : void 0;
73710
+ const source = input.source ?? here?.source ?? (ask ? yield* String$4({
73711
+ message: "Source (`owner/repo` on GitHub, or any source skills add takes)",
73712
+ validate: (value) => value.trim() === "" ? fail$3("a source is needed") : succeed$3(value.trim())
73713
+ }) : yield* needs("--source"));
73714
+ const ref = input.ref ?? here?.ref;
73715
+ const agents = [...new Set(input.agents)];
73716
+ const section = config.skills !== void 0 ? [] : [[["skills", "agents"], agents.length > 0 ? agents : !ask ? yield* needs("--agent") : yield* pickHere()]];
73717
+ const targeting = targetingOf(input.targeting);
73718
+ yield* addItem({
73719
+ config: input.config,
73720
+ provider: "skills",
73721
+ name: input.name,
73722
+ entry: skillEntry(source, ref, section.length > 0 ? [] : agents, targeting),
73723
+ targeting,
73724
+ section,
73725
+ notes: PACKAGE_MANAGERS.some((pm) => config[pm]?.items?.skills !== void 0) ? [] : [`No package manager in the config installs the skills CLI; add it with fizz config add ${PACKAGE_MANAGERS.join("|")} skills`]
73726
+ });
73727
+ });
73578
73728
  const defaultBranch = fn("defaultBranch")(function* (origin) {
73579
73729
  const remote = yield* runLocal("git", [
73580
73730
  "ls-remote",
@@ -74391,6 +74541,12 @@ const check = fn("check")(function* (target, script, ledger) {
74391
74541
  yield* ledger.fail(target.name, ["checking did not finish", ...output]);
74392
74542
  return { kind: "failed" };
74393
74543
  }
74544
+ if (state.configLink !== void 0) {
74545
+ const { configLink: link, home } = state;
74546
+ const path = home !== void 0 && link.startsWith(`${home}/`) ? `~${link.slice(home.length)}` : link;
74547
+ yield* ledger.fail(target.name, [new ConfigIsLink({ path }).message]);
74548
+ return { kind: "failed" };
74549
+ }
74394
74550
  return {
74395
74551
  kind: "checked",
74396
74552
  state
@@ -74946,33 +75102,74 @@ const DOTFILES = [
74946
75102
  ];
74947
75103
  const draftText = (picks) => {
74948
75104
  const edits = [
74949
- ...picks.globals.map((g) => [[
74950
- g.pm,
74951
- "items",
74952
- g.name
74953
- ], `^${g.version}`]),
74954
- ...picks.formulae.map((f) => [[
74955
- "brew",
74956
- "items",
75105
+ ...picks.machines.map((m, i) => [
75106
+ m,
75107
+ ["machines", i],
75108
+ m
75109
+ ]),
75110
+ ...picks.globals.map((g) => [
75111
+ g.name,
75112
+ [
75113
+ g.pm,
75114
+ "items",
75115
+ g.name
75116
+ ],
75117
+ `^${g.version}`
75118
+ ]),
75119
+ ...picks.formulae.map((f) => [
75120
+ f.name,
75121
+ [
75122
+ "brew",
75123
+ "items",
75124
+ f.name
75125
+ ],
75126
+ rangeEntry("*", picks.linux && f.macos ? { platforms: ["darwin"] } : {})
75127
+ ]),
75128
+ ...picks.casks.length > 0 && picks.linux ? [[
75129
+ "the cask platforms",
75130
+ ["cask", "platforms"],
75131
+ ["darwin"]
75132
+ ]] : [],
75133
+ ...picks.casks.map((c) => [
75134
+ c,
75135
+ [
75136
+ "cask",
75137
+ "items",
75138
+ c
75139
+ ],
75140
+ "*"
75141
+ ]),
75142
+ ...picks.skills.length > 0 ? [[
75143
+ "the skills agents",
75144
+ ["skills", "agents"],
75145
+ picks.agents
75146
+ ]] : [],
75147
+ ...picks.skills.map((skill) => [
75148
+ skill.name,
75149
+ [
75150
+ "skills",
75151
+ "items",
75152
+ skill.name
75153
+ ],
75154
+ skillEntry(skill.source, skill.ref, [], {})
75155
+ ]),
75156
+ ...picks.files.map((f) => [
75157
+ f,
75158
+ [
75159
+ "files",
75160
+ "items",
75161
+ fileName(f)
75162
+ ],
74957
75163
  f
74958
- ], "*"]),
74959
- ...picks.casks.length > 0 && picks.linux ? [[["cask", "platforms"], ["darwin"]]] : [],
74960
- ...picks.casks.map((c) => [[
74961
- "cask",
74962
- "items",
74963
- c
74964
- ], "*"]),
74965
- ...picks.files.map((f) => [[
74966
- "files",
74967
- "items",
74968
- fileName(f)
74969
- ], f])
75164
+ ])
74970
75165
  ];
74971
- const start = `{\n "$schema": "./${SCHEMA_FILE}",\n "version": ${JSON.stringify(picks.version)},\n "machines": [],\n}\n`;
74972
- return [...picks.machines.map((m, i) => [["machines", i], m]), ...edits].reduce((text, [path, value]) => {
75166
+ let text = `{\n "$schema": "./${SCHEMA_FILE}",\n "version": ${JSON.stringify(picks.version)},\n "machines": [],\n}\n`;
75167
+ for (const [pick, path, value] of edits) {
74973
75168
  const result = insertAt(text, path, value);
74974
- return "text" in result ? result.text : text;
74975
- }, start);
75169
+ if ("reason" in result) return { reason: `fizz init could not add ${pick}: ${result.reason}` };
75170
+ text = result.text;
75171
+ }
75172
+ return { text };
74976
75173
  };
74977
75174
  const CheckLine = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(T, {
74978
75175
  tone: props.check.ok ? "ok" : props.check.required ? "fail" : "skip",
@@ -75002,6 +75199,39 @@ const lines = fn("lines")(function* (command, args) {
75002
75199
  const ran = yield* runLocal(command, args);
75003
75200
  return ran.code === 0 ? ran.stdout.split("\n").filter((l) => l.trim() !== "") : [];
75004
75201
  });
75202
+ const Requirements = ArraySchema(Struct({ name: String$6 }));
75203
+ const FormulaInfo = Struct({
75204
+ requirements: Requirements,
75205
+ variations: optionalKey(NullOr(Record(String$6, Struct({ requirements: optionalKey(Requirements) }))))
75206
+ });
75207
+ const macosOnly = (f) => {
75208
+ const linux = Object.entries(f.variations ?? {}).flatMap(([tag, v]) => tag.endsWith("_linux") ? [v.requirements ?? f.requirements] : []);
75209
+ return (linux.length > 0 ? linux : [f.requirements]).every((rs) => rs.some((r) => r.name === "macos"));
75210
+ };
75211
+ const BrewInfo = fromJsonString(Struct({ formulae: ArraySchema(Struct({
75212
+ ...FormulaInfo.fields,
75213
+ full_name: String$6
75214
+ })) }));
75215
+ const coreMacosOnly = fn("coreMacosOnly")(function* (name) {
75216
+ const response = yield* get(`https://formulae.brew.sh/api/formula/${name}.json`);
75217
+ return macosOnly(yield* schemaBodyJson(FormulaInfo)(response));
75218
+ }, timeout("10 seconds"), orElseSucceed(() => false), provide(layer$2));
75219
+ const tappedMacosOnly = fn("tappedMacosOnly")(function* (names) {
75220
+ if (names.length === 0) return [];
75221
+ const ran = yield* runLocal("brew", [
75222
+ "info",
75223
+ "--json=v2",
75224
+ "--variations",
75225
+ "--formula",
75226
+ ...names
75227
+ ]);
75228
+ return (yield* decodeEffect(BrewInfo)(ran.stdout)).formulae.filter(macosOnly).map((f) => f.full_name);
75229
+ }, orElseSucceed(() => []));
75230
+ const macosFormulae = fn("macosFormulae")(function* (names) {
75231
+ const tapped = yield* tappedMacosOnly(names.filter((n) => n.includes("/")));
75232
+ const core = yield* filter(names.filter((n) => !n.includes("/")), coreMacosOnly, { concurrency: 8 });
75233
+ return /* @__PURE__ */ new Set([...tapped, ...core]);
75234
+ });
75005
75235
  const pickSome = (message, choices) => choices.length === 0 ? succeed$3([]) : MultiSelect({
75006
75236
  message,
75007
75237
  choices,
@@ -75032,8 +75262,9 @@ const init = fn("init")(function* (flags) {
75032
75262
  return;
75033
75263
  }
75034
75264
  const tailnet = yield* readTailnet();
75035
- if (yield* fs.exists(path)) {
75036
- const config = yield* loadConfig(path);
75265
+ const existing = yield* readConfigText(path);
75266
+ if (existing !== void 0) {
75267
+ const config = yield* parseConfig(path, existing);
75037
75268
  yield* log(`\n${configText({
75038
75269
  path,
75039
75270
  config,
@@ -75051,35 +75282,57 @@ const init = fn("init")(function* (flags) {
75051
75282
  name,
75052
75283
  version
75053
75284
  })))))).flat().filter((g) => g.name !== SELF_PACKAGE);
75054
- const globals = yield* pickSome(`Which Globals from this Machine? (${installed.length} installed)`, installed.map((g) => ({
75285
+ const pickedGlobals = yield* pickSome(`Which Globals from this Machine? (${installed.length} installed)`, installed.map((g) => ({
75055
75286
  title: `${g.name} ${g.version} ${g.pm}`,
75056
75287
  value: g
75057
75288
  })));
75289
+ const linux = machines.some((m) => tailnet.fleet.find((f) => f.name === m)?.platform === "linux");
75058
75290
  const leaves = yield* lines("brew", ["leaves", "--installed-on-request"]);
75059
- const formulae = yield* pickSome(`Which Formulae from this Machine? (${leaves.length} installed on request)`, leaves.map((f) => ({
75291
+ const pickedFormulae = yield* pickSome(`Which Formulae from this Machine? (${leaves.length} installed on request)`, leaves.map((f) => ({
75060
75292
  title: f,
75061
75293
  value: f
75062
75294
  })));
75295
+ const macos = linux ? yield* macosFormulae(pickedFormulae) : /* @__PURE__ */ new Set();
75296
+ const formulae = pickedFormulae.map((name) => ({
75297
+ name,
75298
+ macos: macos.has(name)
75299
+ }));
75063
75300
  const installedCasks = process.platform === "darwin" ? yield* lines("brew", ["list", "--cask"]) : [];
75064
75301
  const casks = yield* pickSome(`Which Casks from this Machine? (${installedCasks.length} installed)`, installedCasks.map((c) => ({
75065
75302
  title: c,
75066
75303
  value: c
75067
75304
  })));
75305
+ const agentsHere = yield* localAgents();
75306
+ const fromGitHub = agentsHere.length === 0 ? [] : yield* localSkills();
75307
+ const skills = yield* pickSome(`Which skills from this Machine? (${fromGitHub.length} installed from GitHub)`, fromGitHub.map((skill) => ({
75308
+ title: `${skill.name} ${skill.source}${skill.ref === void 0 ? "" : `#${skill.ref}`}`,
75309
+ value: skill,
75310
+ selected: true
75311
+ })));
75312
+ const agents = skills.length === 0 ? [] : yield* pickAgents(agentsHere);
75313
+ const cli = installed.find((global) => global.name === "skills");
75314
+ const addCli = cli !== void 0 && skills.length > 0 && !pickedGlobals.some((global) => global.name === "skills");
75315
+ const globals = addCli ? [...pickedGlobals, cli] : pickedGlobals;
75316
+ if (addCli) yield* log(`The skills CLI is in the config too, as a ${cli.pm} Global: a Ship installs skills with it.`);
75317
+ if (cli === void 0 && skills.length > 0) yield* log(`No package manager here has the skills CLI, so a Ship cannot install skills; add it with fizz config add ${PACKAGE_MANAGERS.join("|")} skills.`);
75068
75318
  const present = yield* filter(DOTFILES, (f) => fs.exists(expandHome(f)));
75069
75319
  const files = yield* pickSome("Which Shared Files? A Ship copies each from the Machine it starts on.", present.map((f) => ({
75070
75320
  title: f,
75071
75321
  value: f
75072
75322
  })));
75073
- const text = draftText({
75323
+ const draft = draftText({
75074
75324
  version: flags.version,
75075
75325
  machines,
75076
75326
  globals,
75077
75327
  formulae,
75078
75328
  casks,
75329
+ skills,
75330
+ agents,
75079
75331
  files,
75080
- linux: machines.some((m) => tailnet.fleet.find((f) => f.name === m)?.platform === "linux")
75332
+ linux
75081
75333
  });
75082
- const config = yield* saveConfig(path, text);
75334
+ if ("reason" in draft) return yield* new ConfigAddError({ reason: draft.reason });
75335
+ const config = yield* saveConfig(path, draft.text);
75083
75336
  const paths = yield* Path$1;
75084
75337
  yield* fs.writeFileString(paths.join(paths.dirname(path), SCHEMA_FILE), fleetConfigSchemaText());
75085
75338
  yield* log(`\n${toText(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(T, {
@@ -75090,7 +75343,7 @@ const init = fn("init")(function* (flags) {
75090
75343
  path,
75091
75344
  config,
75092
75345
  tailnet,
75093
- footer: [`Add more with fizz config add repos|files|${PACKAGE_MANAGERS.join("|")}|brew|cask.`]
75346
+ footer: [`Add more with fizz config add repos|files|${PACKAGE_MANAGERS.join("|")}|brew|cask|skills.`]
75094
75347
  }));
75095
75348
  if (yield* Confirm({
75096
75349
  message: "Run fizz ship --dry-run now?",
@@ -75347,10 +75600,10 @@ const install = fn("install")(function* (version, why) {
75347
75600
  if ((yield* runHere("sh", ["-c", installWith(installer, [spec])])) !== 0) return yield* new VersionError({ reason: `could not install ${spec} with ${installer}; run ${MANAGERS[installer].install} ${spec}` });
75348
75601
  });
75349
75602
  const atConfigVersion = (flag, running, effect) => gen(function* () {
75350
- const fs = yield* FileSystem;
75351
75603
  const path = flag ?? configPath(process.env);
75352
- if (!(yield* fs.exists(path))) return yield* effect;
75353
- const wanted = readVersion(yield* fs.readFileString(path));
75604
+ const text = yield* readConfigText(path);
75605
+ if (text === void 0) return yield* effect;
75606
+ const wanted = readVersion(text);
75354
75607
  if (wanted === void 0 || wanted === running) return yield* effect;
75355
75608
  if (process.env[SWITCHED] !== void 0) return yield* new VersionError({ reason: `${homeRelative(path)} needs fleetfizz ${wanted}, but the fizz on PATH is still ${running} after installing it; see which fizz comes first (command -v fizz)` });
75356
75609
  yield* install(wanted, `${homeRelative(path)} needs fleetfizz ${wanted}, not ${running}`);
@@ -75367,12 +75620,11 @@ const latest = fn("latest")(function* () {
75367
75620
  return version;
75368
75621
  });
75369
75622
  const selfUpdate = fn("selfUpdate")(function* (input) {
75370
- const fs = yield* FileSystem;
75371
75623
  const path = input.config ?? configPath(process.env);
75372
- if (!(yield* fs.exists(path))) return yield* new VersionError({ reason: `no config at ${homeRelative(path)}; run fizz init to write one, or pass --config` });
75624
+ const text = yield* readConfigText(path);
75625
+ if (text === void 0) return yield* new VersionError({ reason: `no config at ${homeRelative(path)}; run fizz init to write one, or pass --config` });
75373
75626
  const version = input.version ?? (yield* latest());
75374
75627
  if (predatesVersion(version)) return yield* new VersionError({ reason: `fleetfizz ${version} cannot read this config, which needs 0.14.0 or newer` });
75375
- const text = yield* fs.readFileString(path);
75376
75628
  const named = readVersion(text) === version;
75377
75629
  if (named && version === input.running) {
75378
75630
  yield* log(`fleetfizz ${version} already`);
@@ -75475,6 +75727,23 @@ const addCommand = make$4("add").pipe(withDescription("Add an Item or a Machine
75475
75727
  ...PACKAGE_MANAGERS.map(globalCommand),
75476
75728
  homebrewCommand("brew"),
75477
75729
  homebrewCommand("cask"),
75730
+ make$4("skills", {
75731
+ name: String$2("name").pipe(withDescription$2("The skill, by the name its source gives it.")),
75732
+ source: String$1("source").pipe(optional, withDescription$1("Where the skills CLI installs it from: `owner/repo` on GitHub, or any source `skills add` takes. Default: the GitHub repo the CLI installed it from here. Asked for when missing.")),
75733
+ ref: String$1("ref").pipe(optional, withDescription$1("A branch, tag, or full commit SHA. Default: the repo's default branch, or without `--source`, the ref the CLI installed it at here.")),
75734
+ agent: String$1("agent").pipe(atLeast(0), withMetavar("id"), withDescription$1("An agent to install it for, as a skills CLI id (`claude-code`). Repeat the flag for more. For the first skill, they are the section's `agents`, picked from this Machine's when missing; after that, the skill's own.")),
75735
+ ...targetingFlags
75736
+ }).pipe(withDescription("Add an agent skill."), withHandler((flags) => withConfig((config) => addSkill({
75737
+ config,
75738
+ name: flags.name,
75739
+ source: getOrUndefined$1(flags.source),
75740
+ ref: getOrUndefined$1(flags.ref),
75741
+ agents: flags.agent,
75742
+ targeting: {
75743
+ platforms: flags.platform,
75744
+ machines: flags.machine
75745
+ }
75746
+ })).pipe(printProblems))),
75478
75747
  make$4("repos", {
75479
75748
  origin: String$2("origin").pipe(withDescription$2("The GitHub `owner/repo`.")),
75480
75749
  name: String$1("name").pipe(optional, withDescription$1("The Item's name. Default: the repo's name.")),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fleetfizz",
3
- "version": "0.14.1",
3
+ "version": "0.15.0",
4
4
  "description": "Keep every Machine on your tailnet in line with one config.",
5
5
  "keywords": [
6
6
  "agent-skills",