@feastalytics/cli 0.1.15 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -88,6 +88,17 @@ npx skills add feastalytics/cli -g -a '*' -y
88
88
 
89
89
  To refresh from a local checkout instead of GitHub, run `npx skills add ./feast -g -a '*' -y` from the repo root.
90
90
 
91
+ ### Playbook skills from Feastalytics
92
+
93
+ Feastalytics also publishes playbook skills that build on this one. They are served by the API to logged-in users and installed by the CLI itself, so no GitHub access is needed:
94
+
95
+ ```bash
96
+ feast skill list # what is published, with versions
97
+ feast skill install feast-playbooks # writes ~/.claude/skills/feast-playbooks/
98
+ ```
99
+
100
+ `--dir <path>` installs somewhere else (zip that directory to upload it to claude.ai under Settings > Capabilities > Skills). A re-install replaces the previous copy; `--force` is required to replace a directory the CLI did not create. Skills belong to the organization you act on, so pass `--org` when you belong to several.
101
+
91
102
  ## Environment
92
103
 
93
104
  - `FEAST_API_URL` — override the API base URL (e.g. a local dev server)
package/dist/cli.js CHANGED
@@ -3221,8 +3221,8 @@ var require_utils = __commonJS({
3221
3221
  }
3222
3222
  return ind;
3223
3223
  }
3224
- function removeDotSegments(path3) {
3225
- let input = path3;
3224
+ function removeDotSegments(path5) {
3225
+ let input = path5;
3226
3226
  const output = [];
3227
3227
  let nextSlash = -1;
3228
3228
  let len = 0;
@@ -3474,8 +3474,8 @@ var require_schemes = __commonJS({
3474
3474
  wsComponent.secure = void 0;
3475
3475
  }
3476
3476
  if (wsComponent.resourceName) {
3477
- const [path3, query] = wsComponent.resourceName.split("?");
3478
- wsComponent.path = path3 && path3 !== "/" ? path3 : void 0;
3477
+ const [path5, query] = wsComponent.resourceName.split("?");
3478
+ wsComponent.path = path5 && path5 !== "/" ? path5 : void 0;
3479
3479
  wsComponent.query = query;
3480
3480
  wsComponent.resourceName = void 0;
3481
3481
  }
@@ -6836,22 +6836,22 @@ var TRPC_ERROR_CODES_BY_NUMBER = invert(TRPC_ERROR_CODES_BY_KEY);
6836
6836
  var TRPC_ERROR_CODES_BY_NUMBER2 = invert(TRPC_ERROR_CODES_BY_KEY);
6837
6837
  var noop = () => {
6838
6838
  };
6839
- function createInnerProxy(callback, path3) {
6839
+ function createInnerProxy(callback, path5) {
6840
6840
  const proxy = new Proxy(noop, {
6841
6841
  get(_obj, key) {
6842
6842
  if (typeof key !== "string" || key === "then") {
6843
6843
  return void 0;
6844
6844
  }
6845
6845
  return createInnerProxy(callback, [
6846
- ...path3,
6846
+ ...path5,
6847
6847
  key
6848
6848
  ]);
6849
6849
  },
6850
6850
  apply(_1, _2, args) {
6851
- const isApply = path3[path3.length - 1] === "apply";
6851
+ const isApply = path5[path5.length - 1] === "apply";
6852
6852
  return callback({
6853
6853
  args: isApply ? args.length >= 2 ? args[1] : [] : args,
6854
- path: isApply ? path3.slice(0, -1) : path3
6854
+ path: isApply ? path5.slice(0, -1) : path5
6855
6855
  });
6856
6856
  }
6857
6857
  });
@@ -7263,13 +7263,13 @@ function createHTTPBatchLink(requester) {
7263
7263
  if (maxURLLength === Infinity) {
7264
7264
  return true;
7265
7265
  }
7266
- const path3 = batchOps.map((op) => op.path).join(",");
7266
+ const path5 = batchOps.map((op) => op.path).join(",");
7267
7267
  const inputs = batchOps.map((op) => op.input);
7268
7268
  const url = getUrl({
7269
7269
  ...resolvedOpts,
7270
7270
  runtime,
7271
7271
  type,
7272
- path: path3,
7272
+ path: path5,
7273
7273
  inputs
7274
7274
  });
7275
7275
  return url.length <= maxURLLength;
@@ -7327,11 +7327,11 @@ function createHTTPBatchLink(requester) {
7327
7327
  }
7328
7328
  var batchRequester = (requesterOpts) => {
7329
7329
  return (batchOps) => {
7330
- const path3 = batchOps.map((op) => op.path).join(",");
7330
+ const path5 = batchOps.map((op) => op.path).join(",");
7331
7331
  const inputs = batchOps.map((op) => op.input);
7332
7332
  const { promise, cancel } = jsonHttpRequester({
7333
7333
  ...requesterOpts,
7334
- path: path3,
7334
+ path: path5,
7335
7335
  inputs,
7336
7336
  headers() {
7337
7337
  if (!requesterOpts.opts.headers) {
@@ -7365,12 +7365,12 @@ function httpLinkFactory(factoryOpts) {
7365
7365
  return (opts) => {
7366
7366
  const resolvedOpts = resolveHTTPLinkOptions(opts);
7367
7367
  return (runtime) => ({ op }) => observable((observer) => {
7368
- const { path: path3, input, type } = op;
7368
+ const { path: path5, input, type } = op;
7369
7369
  const { promise, cancel } = factoryOpts.requester({
7370
7370
  ...resolvedOpts,
7371
7371
  runtime,
7372
7372
  type,
7373
- path: path3,
7373
+ path: path5,
7374
7374
  input,
7375
7375
  headers() {
7376
7376
  if (!opts.headers) {
@@ -7416,13 +7416,13 @@ var httpLink = httpLinkFactory({
7416
7416
 
7417
7417
  // node_modules/@trpc/client/dist/index.mjs
7418
7418
  var TRPCUntypedClient = class {
7419
- $request({ type, input, path: path3, context = {} }) {
7419
+ $request({ type, input, path: path5, context = {} }) {
7420
7420
  const chain$ = createChain({
7421
7421
  links: this.links,
7422
7422
  op: {
7423
7423
  id: ++this.requestId,
7424
7424
  type,
7425
- path: path3,
7425
+ path: path5,
7426
7426
  input,
7427
7427
  context
7428
7428
  }
@@ -7442,28 +7442,28 @@ var TRPCUntypedClient = class {
7442
7442
  });
7443
7443
  return abortablePromise;
7444
7444
  }
7445
- query(path3, input, opts) {
7445
+ query(path5, input, opts) {
7446
7446
  return this.requestAsPromise({
7447
7447
  type: "query",
7448
- path: path3,
7448
+ path: path5,
7449
7449
  input,
7450
7450
  context: opts?.context,
7451
7451
  signal: opts?.signal
7452
7452
  });
7453
7453
  }
7454
- mutation(path3, input, opts) {
7454
+ mutation(path5, input, opts) {
7455
7455
  return this.requestAsPromise({
7456
7456
  type: "mutation",
7457
- path: path3,
7457
+ path: path5,
7458
7458
  input,
7459
7459
  context: opts?.context,
7460
7460
  signal: opts?.signal
7461
7461
  });
7462
7462
  }
7463
- subscription(path3, input, opts) {
7463
+ subscription(path5, input, opts) {
7464
7464
  const observable$ = this.$request({
7465
7465
  type: "subscription",
7466
- path: path3,
7466
+ path: path5,
7467
7467
  input,
7468
7468
  context: opts?.context
7469
7469
  });
@@ -7535,10 +7535,10 @@ function createTRPCClientProxy(client) {
7535
7535
  if (key === "__untypedClient") {
7536
7536
  return client;
7537
7537
  }
7538
- return createRecursiveProxy(({ path: path3, args }) => {
7538
+ return createRecursiveProxy(({ path: path5, args }) => {
7539
7539
  const pathCopy = [
7540
7540
  key,
7541
- ...path3
7541
+ ...path5
7542
7542
  ];
7543
7543
  const procedureType = clientCallTypeToProcedureType(pathCopy.pop());
7544
7544
  const fullPath = pathCopy.join(".");
@@ -7647,12 +7647,12 @@ var streamingJsonHttpRequester = (opts, onSingle) => {
7647
7647
  var streamRequester = (requesterOpts) => {
7648
7648
  const textDecoder = getTextDecoder(requesterOpts.opts.textDecoder);
7649
7649
  return (batchOps, unitResolver) => {
7650
- const path3 = batchOps.map((op) => op.path).join(",");
7650
+ const path5 = batchOps.map((op) => op.path).join(",");
7651
7651
  const inputs = batchOps.map((op) => op.input);
7652
7652
  const { cancel, promise } = streamingJsonHttpRequester({
7653
7653
  ...requesterOpts,
7654
7654
  textDecoder,
7655
- path: path3,
7655
+ path: path5,
7656
7656
  inputs,
7657
7657
  headers() {
7658
7658
  if (!requesterOpts.opts.headers) {
@@ -7866,7 +7866,7 @@ var isURL = (payload) => payload instanceof URL;
7866
7866
 
7867
7867
  // node_modules/superjson/dist/pathstringifier.js
7868
7868
  var escapeKey = (key) => key.replace(/\\/g, "\\\\").replace(/\./g, "\\.");
7869
- var stringifyPath = (path3) => path3.map(String).map(escapeKey).join(".");
7869
+ var stringifyPath = (path5) => path5.map(String).map(escapeKey).join(".");
7870
7870
  var parsePath = (string, legacyPaths) => {
7871
7871
  const result = [];
7872
7872
  let segment = "";
@@ -8113,26 +8113,26 @@ var getNthKey = (value, n) => {
8113
8113
  }
8114
8114
  return keys.next().value;
8115
8115
  };
8116
- function validatePath(path3) {
8117
- if (includes(path3, "__proto__")) {
8116
+ function validatePath(path5) {
8117
+ if (includes(path5, "__proto__")) {
8118
8118
  throw new Error("__proto__ is not allowed as a property");
8119
8119
  }
8120
- if (includes(path3, "prototype")) {
8120
+ if (includes(path5, "prototype")) {
8121
8121
  throw new Error("prototype is not allowed as a property");
8122
8122
  }
8123
- if (includes(path3, "constructor")) {
8123
+ if (includes(path5, "constructor")) {
8124
8124
  throw new Error("constructor is not allowed as a property");
8125
8125
  }
8126
8126
  }
8127
- var getDeep = (object, path3) => {
8128
- validatePath(path3);
8129
- for (let i = 0; i < path3.length; i++) {
8130
- const key = path3[i];
8127
+ var getDeep = (object, path5) => {
8128
+ validatePath(path5);
8129
+ for (let i = 0; i < path5.length; i++) {
8130
+ const key = path5[i];
8131
8131
  if (isSet(object)) {
8132
8132
  object = getNthKey(object, +key);
8133
8133
  } else if (isMap(object)) {
8134
8134
  const row = +key;
8135
- const type = +path3[++i] === 0 ? "key" : "value";
8135
+ const type = +path5[++i] === 0 ? "key" : "value";
8136
8136
  const keyOfRow = getNthKey(object, row);
8137
8137
  switch (type) {
8138
8138
  case "key":
@@ -8148,14 +8148,14 @@ var getDeep = (object, path3) => {
8148
8148
  }
8149
8149
  return object;
8150
8150
  };
8151
- var setDeep = (object, path3, mapper) => {
8152
- validatePath(path3);
8153
- if (path3.length === 0) {
8151
+ var setDeep = (object, path5, mapper) => {
8152
+ validatePath(path5);
8153
+ if (path5.length === 0) {
8154
8154
  return mapper(object);
8155
8155
  }
8156
8156
  let parent = object;
8157
- for (let i = 0; i < path3.length - 1; i++) {
8158
- const key = path3[i];
8157
+ for (let i = 0; i < path5.length - 1; i++) {
8158
+ const key = path5[i];
8159
8159
  if (isArray(parent)) {
8160
8160
  const index = +key;
8161
8161
  parent = parent[index];
@@ -8165,12 +8165,12 @@ var setDeep = (object, path3, mapper) => {
8165
8165
  const row = +key;
8166
8166
  parent = getNthKey(parent, row);
8167
8167
  } else if (isMap(parent)) {
8168
- const isEnd = i === path3.length - 2;
8168
+ const isEnd = i === path5.length - 2;
8169
8169
  if (isEnd) {
8170
8170
  break;
8171
8171
  }
8172
8172
  const row = +key;
8173
- const type = +path3[++i] === 0 ? "key" : "value";
8173
+ const type = +path5[++i] === 0 ? "key" : "value";
8174
8174
  const keyOfRow = getNthKey(parent, row);
8175
8175
  switch (type) {
8176
8176
  case "key":
@@ -8182,7 +8182,7 @@ var setDeep = (object, path3, mapper) => {
8182
8182
  }
8183
8183
  }
8184
8184
  }
8185
- const lastKey = path3[path3.length - 1];
8185
+ const lastKey = path5[path5.length - 1];
8186
8186
  if (isArray(parent)) {
8187
8187
  parent[+lastKey] = mapper(parent[+lastKey]);
8188
8188
  } else if (isPlainObject(parent)) {
@@ -8197,7 +8197,7 @@ var setDeep = (object, path3, mapper) => {
8197
8197
  }
8198
8198
  }
8199
8199
  if (isMap(parent)) {
8200
- const row = +path3[path3.length - 2];
8200
+ const row = +path5[path5.length - 2];
8201
8201
  const keyToRow = getNthKey(parent, row);
8202
8202
  const type = +lastKey === 0 ? "key" : "value";
8203
8203
  switch (type) {
@@ -8244,16 +8244,16 @@ function traverse(tree, walker2, version, origin = []) {
8244
8244
  walker2(nodeValue, origin);
8245
8245
  }
8246
8246
  function applyValueAnnotations(plain, annotations, version, superJson) {
8247
- traverse(annotations, (type, path3) => {
8248
- plain = setDeep(plain, path3, (v) => untransformValue(v, type, superJson));
8247
+ traverse(annotations, (type, path5) => {
8248
+ plain = setDeep(plain, path5, (v) => untransformValue(v, type, superJson));
8249
8249
  }, version);
8250
8250
  return plain;
8251
8251
  }
8252
8252
  function applyReferentialEqualityAnnotations(plain, annotations, version) {
8253
8253
  const legacyPaths = enableLegacyPaths(version);
8254
- function apply(identicalPaths, path3) {
8255
- const object = getDeep(plain, parsePath(path3, legacyPaths));
8256
- identicalPaths.map((path4) => parsePath(path4, legacyPaths)).forEach((identicalObjectPath) => {
8254
+ function apply(identicalPaths, path5) {
8255
+ const object = getDeep(plain, parsePath(path5, legacyPaths));
8256
+ identicalPaths.map((path6) => parsePath(path6, legacyPaths)).forEach((identicalObjectPath) => {
8257
8257
  plain = setDeep(plain, identicalObjectPath, () => object);
8258
8258
  });
8259
8259
  }
@@ -8271,12 +8271,12 @@ function applyReferentialEqualityAnnotations(plain, annotations, version) {
8271
8271
  return plain;
8272
8272
  }
8273
8273
  var isDeep = (object, superJson) => isPlainObject(object) || isArray(object) || isMap(object) || isSet(object) || isError(object) || isInstanceOfRegisteredClass(object, superJson);
8274
- function addIdentity(object, path3, identities) {
8274
+ function addIdentity(object, path5, identities) {
8275
8275
  const existingSet = identities.get(object);
8276
8276
  if (existingSet) {
8277
- existingSet.push(path3);
8277
+ existingSet.push(path5);
8278
8278
  } else {
8279
- identities.set(object, [path3]);
8279
+ identities.set(object, [path5]);
8280
8280
  }
8281
8281
  }
8282
8282
  function generateReferentialEqualityAnnotations(identitites, dedupe) {
@@ -8287,7 +8287,7 @@ function generateReferentialEqualityAnnotations(identitites, dedupe) {
8287
8287
  return;
8288
8288
  }
8289
8289
  if (!dedupe) {
8290
- paths = paths.map((path3) => path3.map(String)).sort((a, b) => a.length - b.length);
8290
+ paths = paths.map((path5) => path5.map(String)).sort((a, b) => a.length - b.length);
8291
8291
  }
8292
8292
  const [representativePath, ...identicalPaths] = paths;
8293
8293
  if (representativePath.length === 0) {
@@ -8306,10 +8306,10 @@ function generateReferentialEqualityAnnotations(identitites, dedupe) {
8306
8306
  return isEmptyObject(result) ? void 0 : result;
8307
8307
  }
8308
8308
  }
8309
- var walker = (object, identities, superJson, dedupe, path3 = [], objectsInThisPath = [], seenObjects = /* @__PURE__ */ new Map()) => {
8309
+ var walker = (object, identities, superJson, dedupe, path5 = [], objectsInThisPath = [], seenObjects = /* @__PURE__ */ new Map()) => {
8310
8310
  const primitive = isPrimitive(object);
8311
8311
  if (!primitive) {
8312
- addIdentity(object, path3, identities);
8312
+ addIdentity(object, path5, identities);
8313
8313
  const seen = seenObjects.get(object);
8314
8314
  if (seen) {
8315
8315
  return dedupe ? {
@@ -8343,7 +8343,7 @@ var walker = (object, identities, superJson, dedupe, path3 = [], objectsInThisPa
8343
8343
  if (index === "__proto__" || index === "constructor" || index === "prototype") {
8344
8344
  throw new Error(`Detected property ${index}. This is a prototype pollution risk, please remove it from your object.`);
8345
8345
  }
8346
- const recursiveResult = walker(value, identities, superJson, dedupe, [...path3, index], [...objectsInThisPath, object], seenObjects);
8346
+ const recursiveResult = walker(value, identities, superJson, dedupe, [...path5, index], [...objectsInThisPath, object], seenObjects);
8347
8347
  transformedValue[index] = recursiveResult.transformedValue;
8348
8348
  if (isArray(recursiveResult.annotations)) {
8349
8349
  innerAnnotations[escapeKey(index)] = recursiveResult.annotations;
@@ -8654,7 +8654,8 @@ function errorMessage(error) {
8654
8654
  }
8655
8655
 
8656
8656
  // src/main.ts
8657
- import fs2 from "fs";
8657
+ import fs3 from "fs";
8658
+ import path3 from "path";
8658
8659
 
8659
8660
  // src/generated/manifest.ts
8660
8661
  var CLI_MANIFEST = {
@@ -26494,6 +26495,90 @@ function validateToolInput(tool, input) {
26494
26495
  );
26495
26496
  }
26496
26497
 
26498
+ // src/skills.ts
26499
+ import fs2 from "fs";
26500
+ import os2 from "os";
26501
+ import path2 from "path";
26502
+ var MARKER_FILE = ".feast-skill.json";
26503
+ var SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
26504
+ function defaultSkillsDir() {
26505
+ return path2.join(os2.homedir(), ".claude", "skills");
26506
+ }
26507
+ async function createClient(orgFlag, roleFlag) {
26508
+ const acting = await resolveActingContext(orgFlag, roleFlag);
26509
+ return createHttpCaller({
26510
+ accessToken: acting.accessToken,
26511
+ preferredRole: acting.preferredRole
26512
+ });
26513
+ }
26514
+ async function listSkills(orgFlag, roleFlag) {
26515
+ const client = await createClient(orgFlag, roleFlag);
26516
+ const entries = await callProcedure(client, ["api", "skills", "list"], "query", void 0);
26517
+ return Array.isArray(entries) ? entries : [];
26518
+ }
26519
+ async function fetchSkill(name, orgFlag, roleFlag) {
26520
+ if (!SKILL_NAME_PATTERN.test(name)) {
26521
+ throw new Error(`Invalid skill name "${name}". Run: feast skill list`);
26522
+ }
26523
+ const client = await createClient(orgFlag, roleFlag);
26524
+ return callProcedure(client, ["api", "skills", "get"], "query", { name });
26525
+ }
26526
+ function assertInsideDir(root, relative) {
26527
+ const target = path2.resolve(root, relative);
26528
+ if (target !== root && !target.startsWith(`${root}${path2.sep}`)) {
26529
+ throw new Error(`Refusing to write outside the skill directory: ${relative}`);
26530
+ }
26531
+ return target;
26532
+ }
26533
+ function readMarker(dir) {
26534
+ const markerPath = path2.join(dir, MARKER_FILE);
26535
+ if (!fs2.existsSync(markerPath)) {
26536
+ return void 0;
26537
+ }
26538
+ try {
26539
+ return JSON.parse(fs2.readFileSync(markerPath, "utf-8"));
26540
+ } catch {
26541
+ return {};
26542
+ }
26543
+ }
26544
+ function writeSkill(bundle, targetDir, force) {
26545
+ const dir = path2.resolve(targetDir);
26546
+ const exists = fs2.existsSync(dir);
26547
+ const marker = exists ? readMarker(dir) : void 0;
26548
+ if (exists && marker == null && !force) {
26549
+ throw new Error(
26550
+ `${dir} exists and was not installed by feast. Pass --force to replace it.`
26551
+ );
26552
+ }
26553
+ if (exists) {
26554
+ fs2.rmSync(dir, { recursive: true, force: true });
26555
+ }
26556
+ fs2.mkdirSync(dir, { recursive: true });
26557
+ for (const file of bundle.files) {
26558
+ const target = assertInsideDir(dir, file.path);
26559
+ fs2.mkdirSync(path2.dirname(target), { recursive: true });
26560
+ fs2.writeFileSync(target, file.content);
26561
+ }
26562
+ fs2.writeFileSync(
26563
+ path2.join(dir, MARKER_FILE),
26564
+ `${JSON.stringify(
26565
+ {
26566
+ name: bundle.name,
26567
+ version: bundle.version,
26568
+ publishedAt: bundle.publishedAt,
26569
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
26570
+ },
26571
+ null,
26572
+ 2
26573
+ )}
26574
+ `
26575
+ );
26576
+ return { dir, written: bundle.files.length, replaced: exists };
26577
+ }
26578
+ function shortVersion(version) {
26579
+ return version.slice(0, 7);
26580
+ }
26581
+
26497
26582
  // src/main.ts
26498
26583
  var USAGE = `Usage: feast <command> [options]
26499
26584
 
@@ -26505,6 +26590,12 @@ Commands:
26505
26590
  tools [--domain <domain>] [--json] List available tools
26506
26591
  describe <tool> Show a tool's description and input JSON schema
26507
26592
  call <tool> [options] Invoke a tool
26593
+ skill list List the skills you can install
26594
+ skill install <name> [options] Install a skill into ~/.claude/skills/<name>
26595
+
26596
+ Skill install options:
26597
+ --dir <path> Install into this directory instead of ~/.claude/skills/<name>
26598
+ --force Replace a directory that feast did not install
26508
26599
 
26509
26600
  Call options:
26510
26601
  --org <organizationId> Organization to act on (required unless you belong to exactly one)
@@ -26526,7 +26617,14 @@ Non-interactive credentials (for sandboxed agents):
26526
26617
  function parseArgs(argv) {
26527
26618
  const positional = [];
26528
26619
  const flags = {};
26529
- const valueFlags = /* @__PURE__ */ new Set(["org", "role", "input", "input-file", "domain"]);
26620
+ const valueFlags = /* @__PURE__ */ new Set([
26621
+ "org",
26622
+ "role",
26623
+ "input",
26624
+ "input-file",
26625
+ "domain",
26626
+ "dir"
26627
+ ]);
26530
26628
  for (let i = 0; i < argv.length; i++) {
26531
26629
  const arg = argv[i];
26532
26630
  if (!arg.startsWith("--")) {
@@ -26674,7 +26772,7 @@ function readCallInput(args) {
26674
26772
  }
26675
26773
  let raw = inputJson;
26676
26774
  if (inputFile != null) {
26677
- raw = fs2.readFileSync(inputFile, "utf-8");
26775
+ raw = fs3.readFileSync(inputFile, "utf-8");
26678
26776
  }
26679
26777
  if (raw == null) {
26680
26778
  return {};
@@ -26742,6 +26840,41 @@ Run: feast describe ${tool.id}`
26742
26840
  const result = await callProcedure(client, tool.path, tool.type, input);
26743
26841
  console.info(JSON.stringify(result ?? null, null, 2));
26744
26842
  }
26843
+ async function commandSkill(args) {
26844
+ const [subcommand, name] = args.positional;
26845
+ const orgFlag = args.flags["org"];
26846
+ const roleFlag = args.flags["role"];
26847
+ if (subcommand === "list") {
26848
+ const entries = await listSkills(orgFlag, roleFlag);
26849
+ if (entries.length === 0) {
26850
+ console.info("No skills are published for your organization yet.");
26851
+ return;
26852
+ }
26853
+ for (const entry of entries) {
26854
+ console.info(
26855
+ `${entry.name} ${entry.displayTitle} (version ${shortVersion(entry.version)})`
26856
+ );
26857
+ console.info(` ${entry.description.split("\n")[0]}`);
26858
+ }
26859
+ return;
26860
+ }
26861
+ if (subcommand === "install") {
26862
+ if (name == null) {
26863
+ throw new Error("Usage: feast skill install <name> [--dir <path>] [--force]");
26864
+ }
26865
+ const bundle = await fetchSkill(name, orgFlag, roleFlag);
26866
+ const targetDir = args.flags["dir"] ?? path3.join(defaultSkillsDir(), bundle.name);
26867
+ const result = writeSkill(bundle, targetDir, args.flags["force"] === true);
26868
+ console.info(
26869
+ `${result.replaced ? "Updated" : "Installed"} ${bundle.name} (version ${shortVersion(bundle.version)}, ${result.written} files) in ${result.dir}`
26870
+ );
26871
+ console.info(
26872
+ "Claude Code picks it up on the next session. For claude.ai, zip that directory and upload it under Settings > Capabilities > Skills."
26873
+ );
26874
+ return;
26875
+ }
26876
+ throw new Error("Usage: feast skill <list|install <name>>");
26877
+ }
26745
26878
  async function runCli(argv) {
26746
26879
  const [command, ...rest] = argv;
26747
26880
  const args = parseArgs(rest);
@@ -26765,6 +26898,9 @@ async function runCli(argv) {
26765
26898
  case "call":
26766
26899
  await commandCall(args);
26767
26900
  break;
26901
+ case "skill":
26902
+ await commandSkill(args);
26903
+ break;
26768
26904
  default:
26769
26905
  console.info(USAGE);
26770
26906
  if (command != null && command !== "help") {
@@ -26774,16 +26910,16 @@ async function runCli(argv) {
26774
26910
  }
26775
26911
 
26776
26912
  // src/updateNotifier.ts
26777
- import fs3 from "fs";
26913
+ import fs4 from "fs";
26778
26914
  import { createRequire as createRequire2 } from "module";
26779
- import os2 from "os";
26780
- import path2 from "path";
26915
+ import os3 from "os";
26916
+ import path4 from "path";
26781
26917
  var PACKAGE_NAME = "@feastalytics/cli";
26782
26918
  var REGISTRY_URL = "https://registry.npmjs.org";
26783
26919
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
26784
26920
  var FETCH_TIMEOUT_MS = 1500;
26785
- var CACHE_DIR = path2.join(os2.homedir(), ".config", "feast-cli");
26786
- var CACHE_PATH = path2.join(CACHE_DIR, "update-check.json");
26921
+ var CACHE_DIR = path4.join(os3.homedir(), ".config", "feast-cli");
26922
+ var CACHE_PATH = path4.join(CACHE_DIR, "update-check.json");
26787
26923
  function readPackageVersion2() {
26788
26924
  try {
26789
26925
  const require2 = createRequire2(import.meta.url);
@@ -26820,7 +26956,7 @@ function isDisabled() {
26820
26956
  function readCache() {
26821
26957
  try {
26822
26958
  const parsed = JSON.parse(
26823
- fs3.readFileSync(CACHE_PATH, "utf-8")
26959
+ fs4.readFileSync(CACHE_PATH, "utf-8")
26824
26960
  );
26825
26961
  return typeof parsed.latest === "string" && typeof parsed.checkedAt === "number" ? parsed : void 0;
26826
26962
  } catch {
@@ -26829,8 +26965,8 @@ function readCache() {
26829
26965
  }
26830
26966
  function writeCache(cache) {
26831
26967
  try {
26832
- fs3.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
26833
- fs3.writeFileSync(CACHE_PATH, JSON.stringify(cache));
26968
+ fs4.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
26969
+ fs4.writeFileSync(CACHE_PATH, JSON.stringify(cache));
26834
26970
  } catch {
26835
26971
  return;
26836
26972
  }
@@ -44,6 +44,17 @@ If you've been invoking the CLI through `npx` rather than a global install, skip
44
44
 
45
45
  Update the skill whenever you update the CLI: the two ship from the same repo but on different triggers, so a new CLI version usually means this skill's guidance has moved too.
46
46
 
47
+ ## Playbook skills
48
+
49
+ Feastalytics publishes further skills that build on this one (campaign diagnosis and other playbooks). They come from the API, not from GitHub:
50
+
51
+ ```bash
52
+ feast skill list # what is published for your organization
53
+ feast skill install feast-playbooks # install or refresh one into ~/.claude/skills/
54
+ ```
55
+
56
+ Install what `feast skill list` offers when the user asks for a playbook this skill does not cover, and re-run the install when the CLI prints an update notice.
57
+
47
58
  ## Which organization
48
59
 
49
60
  Most tools act on one organization. A user often belongs to several, so which one you target matters and must be explicit.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feastalytics/cli",
3
- "version": "0.1.15",
3
+ "version": "0.1.16",
4
4
  "description": "Command-line client for the Feastalytics platform — list, create, and update campaigns, automations, offers, and members-program rewards from the terminal.",
5
5
  "type": "module",
6
6
  "bin": {