@feastalytics/cli 0.1.1 → 0.1.2

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
@@ -16,6 +16,21 @@ Or run without installing:
16
16
  npx @feastalytics/cli <command>
17
17
  ```
18
18
 
19
+ ### Staying current
20
+
21
+ Neither install path updates itself. Once a day the CLI checks npm for a newer
22
+ published version and, if there is one, prints a one-line notice to stderr after
23
+ the command finishes:
24
+
25
+ ```
26
+ Update available: feast 0.1.1 → 0.2.0
27
+ npx @feastalytics/cli@latest <command> (or: npm install -g @feastalytics/cli@latest)
28
+ ```
29
+
30
+ The check is skipped when stderr isn't a TTY, when `CI` is set, or when
31
+ `FEAST_NO_UPDATE_CHECK` or `NO_UPDATE_NOTIFIER` is set. It never blocks longer
32
+ than 1.5s and fails silently offline.
33
+
19
34
  ## Usage
20
35
 
21
36
  ```bash
package/dist/cli.js CHANGED
@@ -3221,8 +3221,8 @@ var require_utils = __commonJS({
3221
3221
  }
3222
3222
  return ind;
3223
3223
  }
3224
- function removeDotSegments(path2) {
3225
- let input = path2;
3224
+ function removeDotSegments(path3) {
3225
+ let input = path3;
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 [path2, query] = wsComponent.resourceName.split("?");
3478
- wsComponent.path = path2 && path2 !== "/" ? path2 : void 0;
3477
+ const [path3, query] = wsComponent.resourceName.split("?");
3478
+ wsComponent.path = path3 && path3 !== "/" ? path3 : void 0;
3479
3479
  wsComponent.query = query;
3480
3480
  wsComponent.resourceName = void 0;
3481
3481
  }
@@ -6597,6 +6597,9 @@ function getCredentialsPath() {
6597
6597
  return CREDENTIALS_PATH;
6598
6598
  }
6599
6599
 
6600
+ // src/http.ts
6601
+ import { createRequire } from "module";
6602
+
6600
6603
  // node_modules/@trpc/server/dist/observable-ade1bad8.mjs
6601
6604
  function identity(x) {
6602
6605
  return x;
@@ -6833,22 +6836,22 @@ var TRPC_ERROR_CODES_BY_NUMBER = invert(TRPC_ERROR_CODES_BY_KEY);
6833
6836
  var TRPC_ERROR_CODES_BY_NUMBER2 = invert(TRPC_ERROR_CODES_BY_KEY);
6834
6837
  var noop = () => {
6835
6838
  };
6836
- function createInnerProxy(callback, path2) {
6839
+ function createInnerProxy(callback, path3) {
6837
6840
  const proxy = new Proxy(noop, {
6838
6841
  get(_obj, key) {
6839
6842
  if (typeof key !== "string" || key === "then") {
6840
6843
  return void 0;
6841
6844
  }
6842
6845
  return createInnerProxy(callback, [
6843
- ...path2,
6846
+ ...path3,
6844
6847
  key
6845
6848
  ]);
6846
6849
  },
6847
6850
  apply(_1, _2, args) {
6848
- const isApply = path2[path2.length - 1] === "apply";
6851
+ const isApply = path3[path3.length - 1] === "apply";
6849
6852
  return callback({
6850
6853
  args: isApply ? args.length >= 2 ? args[1] : [] : args,
6851
- path: isApply ? path2.slice(0, -1) : path2
6854
+ path: isApply ? path3.slice(0, -1) : path3
6852
6855
  });
6853
6856
  }
6854
6857
  });
@@ -7260,18 +7263,18 @@ function createHTTPBatchLink(requester) {
7260
7263
  if (maxURLLength === Infinity) {
7261
7264
  return true;
7262
7265
  }
7263
- const path2 = batchOps.map((op) => op.path).join(",");
7266
+ const path3 = batchOps.map((op) => op.path).join(",");
7264
7267
  const inputs = batchOps.map((op) => op.input);
7265
7268
  const url = getUrl({
7266
7269
  ...resolvedOpts,
7267
7270
  runtime,
7268
7271
  type,
7269
- path: path2,
7272
+ path: path3,
7270
7273
  inputs
7271
7274
  });
7272
7275
  return url.length <= maxURLLength;
7273
7276
  };
7274
- const fetch = requester({
7277
+ const fetch2 = requester({
7275
7278
  ...resolvedOpts,
7276
7279
  runtime,
7277
7280
  type,
@@ -7279,7 +7282,7 @@ function createHTTPBatchLink(requester) {
7279
7282
  });
7280
7283
  return {
7281
7284
  validate,
7282
- fetch
7285
+ fetch: fetch2
7283
7286
  };
7284
7287
  };
7285
7288
  const query = dataLoader(batchLoader("query"));
@@ -7324,11 +7327,11 @@ function createHTTPBatchLink(requester) {
7324
7327
  }
7325
7328
  var batchRequester = (requesterOpts) => {
7326
7329
  return (batchOps) => {
7327
- const path2 = batchOps.map((op) => op.path).join(",");
7330
+ const path3 = batchOps.map((op) => op.path).join(",");
7328
7331
  const inputs = batchOps.map((op) => op.input);
7329
7332
  const { promise, cancel } = jsonHttpRequester({
7330
7333
  ...requesterOpts,
7331
- path: path2,
7334
+ path: path3,
7332
7335
  inputs,
7333
7336
  headers() {
7334
7337
  if (!requesterOpts.opts.headers) {
@@ -7362,12 +7365,12 @@ function httpLinkFactory(factoryOpts) {
7362
7365
  return (opts) => {
7363
7366
  const resolvedOpts = resolveHTTPLinkOptions(opts);
7364
7367
  return (runtime) => ({ op }) => observable((observer) => {
7365
- const { path: path2, input, type } = op;
7368
+ const { path: path3, input, type } = op;
7366
7369
  const { promise, cancel } = factoryOpts.requester({
7367
7370
  ...resolvedOpts,
7368
7371
  runtime,
7369
7372
  type,
7370
- path: path2,
7373
+ path: path3,
7371
7374
  input,
7372
7375
  headers() {
7373
7376
  if (!opts.headers) {
@@ -7413,13 +7416,13 @@ var httpLink = httpLinkFactory({
7413
7416
 
7414
7417
  // node_modules/@trpc/client/dist/index.mjs
7415
7418
  var TRPCUntypedClient = class {
7416
- $request({ type, input, path: path2, context = {} }) {
7419
+ $request({ type, input, path: path3, context = {} }) {
7417
7420
  const chain$ = createChain({
7418
7421
  links: this.links,
7419
7422
  op: {
7420
7423
  id: ++this.requestId,
7421
7424
  type,
7422
- path: path2,
7425
+ path: path3,
7423
7426
  input,
7424
7427
  context
7425
7428
  }
@@ -7439,28 +7442,28 @@ var TRPCUntypedClient = class {
7439
7442
  });
7440
7443
  return abortablePromise;
7441
7444
  }
7442
- query(path2, input, opts) {
7445
+ query(path3, input, opts) {
7443
7446
  return this.requestAsPromise({
7444
7447
  type: "query",
7445
- path: path2,
7448
+ path: path3,
7446
7449
  input,
7447
7450
  context: opts?.context,
7448
7451
  signal: opts?.signal
7449
7452
  });
7450
7453
  }
7451
- mutation(path2, input, opts) {
7454
+ mutation(path3, input, opts) {
7452
7455
  return this.requestAsPromise({
7453
7456
  type: "mutation",
7454
- path: path2,
7457
+ path: path3,
7455
7458
  input,
7456
7459
  context: opts?.context,
7457
7460
  signal: opts?.signal
7458
7461
  });
7459
7462
  }
7460
- subscription(path2, input, opts) {
7463
+ subscription(path3, input, opts) {
7461
7464
  const observable$ = this.$request({
7462
7465
  type: "subscription",
7463
- path: path2,
7466
+ path: path3,
7464
7467
  input,
7465
7468
  context: opts?.context
7466
7469
  });
@@ -7532,10 +7535,10 @@ function createTRPCClientProxy(client) {
7532
7535
  if (key === "__untypedClient") {
7533
7536
  return client;
7534
7537
  }
7535
- return createRecursiveProxy(({ path: path2, args }) => {
7538
+ return createRecursiveProxy(({ path: path3, args }) => {
7536
7539
  const pathCopy = [
7537
7540
  key,
7538
- ...path2
7541
+ ...path3
7539
7542
  ];
7540
7543
  const procedureType = clientCallTypeToProcedureType(pathCopy.pop());
7541
7544
  const fullPath = pathCopy.join(".");
@@ -7644,12 +7647,12 @@ var streamingJsonHttpRequester = (opts, onSingle) => {
7644
7647
  var streamRequester = (requesterOpts) => {
7645
7648
  const textDecoder = getTextDecoder(requesterOpts.opts.textDecoder);
7646
7649
  return (batchOps, unitResolver) => {
7647
- const path2 = batchOps.map((op) => op.path).join(",");
7650
+ const path3 = batchOps.map((op) => op.path).join(",");
7648
7651
  const inputs = batchOps.map((op) => op.input);
7649
7652
  const { cancel, promise } = streamingJsonHttpRequester({
7650
7653
  ...requesterOpts,
7651
7654
  textDecoder,
7652
- path: path2,
7655
+ path: path3,
7653
7656
  inputs,
7654
7657
  headers() {
7655
7658
  if (!requesterOpts.opts.headers) {
@@ -7863,7 +7866,7 @@ var isURL = (payload) => payload instanceof URL;
7863
7866
 
7864
7867
  // node_modules/superjson/dist/pathstringifier.js
7865
7868
  var escapeKey = (key) => key.replace(/\\/g, "\\\\").replace(/\./g, "\\.");
7866
- var stringifyPath = (path2) => path2.map(String).map(escapeKey).join(".");
7869
+ var stringifyPath = (path3) => path3.map(String).map(escapeKey).join(".");
7867
7870
  var parsePath = (string, legacyPaths) => {
7868
7871
  const result = [];
7869
7872
  let segment = "";
@@ -8110,26 +8113,26 @@ var getNthKey = (value, n) => {
8110
8113
  }
8111
8114
  return keys.next().value;
8112
8115
  };
8113
- function validatePath(path2) {
8114
- if (includes(path2, "__proto__")) {
8116
+ function validatePath(path3) {
8117
+ if (includes(path3, "__proto__")) {
8115
8118
  throw new Error("__proto__ is not allowed as a property");
8116
8119
  }
8117
- if (includes(path2, "prototype")) {
8120
+ if (includes(path3, "prototype")) {
8118
8121
  throw new Error("prototype is not allowed as a property");
8119
8122
  }
8120
- if (includes(path2, "constructor")) {
8123
+ if (includes(path3, "constructor")) {
8121
8124
  throw new Error("constructor is not allowed as a property");
8122
8125
  }
8123
8126
  }
8124
- var getDeep = (object, path2) => {
8125
- validatePath(path2);
8126
- for (let i = 0; i < path2.length; i++) {
8127
- const key = path2[i];
8127
+ var getDeep = (object, path3) => {
8128
+ validatePath(path3);
8129
+ for (let i = 0; i < path3.length; i++) {
8130
+ const key = path3[i];
8128
8131
  if (isSet(object)) {
8129
8132
  object = getNthKey(object, +key);
8130
8133
  } else if (isMap(object)) {
8131
8134
  const row = +key;
8132
- const type = +path2[++i] === 0 ? "key" : "value";
8135
+ const type = +path3[++i] === 0 ? "key" : "value";
8133
8136
  const keyOfRow = getNthKey(object, row);
8134
8137
  switch (type) {
8135
8138
  case "key":
@@ -8145,14 +8148,14 @@ var getDeep = (object, path2) => {
8145
8148
  }
8146
8149
  return object;
8147
8150
  };
8148
- var setDeep = (object, path2, mapper) => {
8149
- validatePath(path2);
8150
- if (path2.length === 0) {
8151
+ var setDeep = (object, path3, mapper) => {
8152
+ validatePath(path3);
8153
+ if (path3.length === 0) {
8151
8154
  return mapper(object);
8152
8155
  }
8153
8156
  let parent = object;
8154
- for (let i = 0; i < path2.length - 1; i++) {
8155
- const key = path2[i];
8157
+ for (let i = 0; i < path3.length - 1; i++) {
8158
+ const key = path3[i];
8156
8159
  if (isArray(parent)) {
8157
8160
  const index = +key;
8158
8161
  parent = parent[index];
@@ -8162,12 +8165,12 @@ var setDeep = (object, path2, mapper) => {
8162
8165
  const row = +key;
8163
8166
  parent = getNthKey(parent, row);
8164
8167
  } else if (isMap(parent)) {
8165
- const isEnd = i === path2.length - 2;
8168
+ const isEnd = i === path3.length - 2;
8166
8169
  if (isEnd) {
8167
8170
  break;
8168
8171
  }
8169
8172
  const row = +key;
8170
- const type = +path2[++i] === 0 ? "key" : "value";
8173
+ const type = +path3[++i] === 0 ? "key" : "value";
8171
8174
  const keyOfRow = getNthKey(parent, row);
8172
8175
  switch (type) {
8173
8176
  case "key":
@@ -8179,7 +8182,7 @@ var setDeep = (object, path2, mapper) => {
8179
8182
  }
8180
8183
  }
8181
8184
  }
8182
- const lastKey = path2[path2.length - 1];
8185
+ const lastKey = path3[path3.length - 1];
8183
8186
  if (isArray(parent)) {
8184
8187
  parent[+lastKey] = mapper(parent[+lastKey]);
8185
8188
  } else if (isPlainObject(parent)) {
@@ -8194,7 +8197,7 @@ var setDeep = (object, path2, mapper) => {
8194
8197
  }
8195
8198
  }
8196
8199
  if (isMap(parent)) {
8197
- const row = +path2[path2.length - 2];
8200
+ const row = +path3[path3.length - 2];
8198
8201
  const keyToRow = getNthKey(parent, row);
8199
8202
  const type = +lastKey === 0 ? "key" : "value";
8200
8203
  switch (type) {
@@ -8241,16 +8244,16 @@ function traverse(tree, walker2, version, origin = []) {
8241
8244
  walker2(nodeValue, origin);
8242
8245
  }
8243
8246
  function applyValueAnnotations(plain, annotations, version, superJson) {
8244
- traverse(annotations, (type, path2) => {
8245
- plain = setDeep(plain, path2, (v) => untransformValue(v, type, superJson));
8247
+ traverse(annotations, (type, path3) => {
8248
+ plain = setDeep(plain, path3, (v) => untransformValue(v, type, superJson));
8246
8249
  }, version);
8247
8250
  return plain;
8248
8251
  }
8249
8252
  function applyReferentialEqualityAnnotations(plain, annotations, version) {
8250
8253
  const legacyPaths = enableLegacyPaths(version);
8251
- function apply(identicalPaths, path2) {
8252
- const object = getDeep(plain, parsePath(path2, legacyPaths));
8253
- identicalPaths.map((path3) => parsePath(path3, legacyPaths)).forEach((identicalObjectPath) => {
8254
+ function apply(identicalPaths, path3) {
8255
+ const object = getDeep(plain, parsePath(path3, legacyPaths));
8256
+ identicalPaths.map((path4) => parsePath(path4, legacyPaths)).forEach((identicalObjectPath) => {
8254
8257
  plain = setDeep(plain, identicalObjectPath, () => object);
8255
8258
  });
8256
8259
  }
@@ -8268,12 +8271,12 @@ function applyReferentialEqualityAnnotations(plain, annotations, version) {
8268
8271
  return plain;
8269
8272
  }
8270
8273
  var isDeep = (object, superJson) => isPlainObject(object) || isArray(object) || isMap(object) || isSet(object) || isError(object) || isInstanceOfRegisteredClass(object, superJson);
8271
- function addIdentity(object, path2, identities) {
8274
+ function addIdentity(object, path3, identities) {
8272
8275
  const existingSet = identities.get(object);
8273
8276
  if (existingSet) {
8274
- existingSet.push(path2);
8277
+ existingSet.push(path3);
8275
8278
  } else {
8276
- identities.set(object, [path2]);
8279
+ identities.set(object, [path3]);
8277
8280
  }
8278
8281
  }
8279
8282
  function generateReferentialEqualityAnnotations(identitites, dedupe) {
@@ -8284,7 +8287,7 @@ function generateReferentialEqualityAnnotations(identitites, dedupe) {
8284
8287
  return;
8285
8288
  }
8286
8289
  if (!dedupe) {
8287
- paths = paths.map((path2) => path2.map(String)).sort((a, b) => a.length - b.length);
8290
+ paths = paths.map((path3) => path3.map(String)).sort((a, b) => a.length - b.length);
8288
8291
  }
8289
8292
  const [representativePath, ...identicalPaths] = paths;
8290
8293
  if (representativePath.length === 0) {
@@ -8303,10 +8306,10 @@ function generateReferentialEqualityAnnotations(identitites, dedupe) {
8303
8306
  return isEmptyObject(result) ? void 0 : result;
8304
8307
  }
8305
8308
  }
8306
- var walker = (object, identities, superJson, dedupe, path2 = [], objectsInThisPath = [], seenObjects = /* @__PURE__ */ new Map()) => {
8309
+ var walker = (object, identities, superJson, dedupe, path3 = [], objectsInThisPath = [], seenObjects = /* @__PURE__ */ new Map()) => {
8307
8310
  const primitive = isPrimitive(object);
8308
8311
  if (!primitive) {
8309
- addIdentity(object, path2, identities);
8312
+ addIdentity(object, path3, identities);
8310
8313
  const seen = seenObjects.get(object);
8311
8314
  if (seen) {
8312
8315
  return dedupe ? {
@@ -8340,7 +8343,7 @@ var walker = (object, identities, superJson, dedupe, path2 = [], objectsInThisPa
8340
8343
  if (index === "__proto__" || index === "constructor" || index === "prototype") {
8341
8344
  throw new Error(`Detected property ${index}. This is a prototype pollution risk, please remove it from your object.`);
8342
8345
  }
8343
- const recursiveResult = walker(value, identities, superJson, dedupe, [...path2, index], [...objectsInThisPath, object], seenObjects);
8346
+ const recursiveResult = walker(value, identities, superJson, dedupe, [...path3, index], [...objectsInThisPath, object], seenObjects);
8344
8347
  transformedValue[index] = recursiveResult.transformedValue;
8345
8348
  if (isArray(recursiveResult.annotations)) {
8346
8349
  innerAnnotations[escapeKey(index)] = recursiveResult.annotations;
@@ -8518,7 +8521,35 @@ function getApiKey() {
8518
8521
  return process.env.FEAST_API_KEY ?? PROD_API_KEY;
8519
8522
  }
8520
8523
 
8524
+ // src/jwt.ts
8525
+ function decodeJwtPayload(jwt) {
8526
+ const payload = jwt.split(".")[1];
8527
+ if (payload == null) {
8528
+ throw new Error("Malformed JWT");
8529
+ }
8530
+ return JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
8531
+ }
8532
+
8521
8533
  // src/http.ts
8534
+ function readPackageVersion() {
8535
+ try {
8536
+ const require2 = createRequire(import.meta.url);
8537
+ return require2("../package.json").version ?? "unknown";
8538
+ } catch {
8539
+ return "unknown";
8540
+ }
8541
+ }
8542
+ var CLI_VERSION = readPackageVersion();
8543
+ var CLI_CLIENT_ID = `feast-cli/${CLI_VERSION}`;
8544
+ var CLI_USER_AGENT = `${CLI_CLIENT_ID} node/${process.versions.node}`;
8545
+ function usernameFromToken(accessToken) {
8546
+ try {
8547
+ const payload = decodeJwtPayload(accessToken);
8548
+ return payload.username ?? payload.sub;
8549
+ } catch {
8550
+ return void 0;
8551
+ }
8552
+ }
8522
8553
  function createHttpCaller(auth) {
8523
8554
  return createTRPCProxyClient({
8524
8555
  transformer: dist_default,
@@ -8527,11 +8558,17 @@ function createHttpCaller(auth) {
8527
8558
  url: getApiUrl(),
8528
8559
  headers: () => {
8529
8560
  const headers = {
8530
- "x-api-key": getApiKey()
8561
+ "x-api-key": getApiKey(),
8562
+ "x-client": CLI_CLIENT_ID,
8563
+ "user-agent": CLI_USER_AGENT
8531
8564
  };
8532
8565
  if (auth.accessToken != null) {
8533
8566
  headers["x-access-token"] = `Bearer ${auth.accessToken}`;
8534
8567
  headers["x-timezone"] = Intl.DateTimeFormat().resolvedOptions().timeZone;
8568
+ const username = usernameFromToken(auth.accessToken);
8569
+ if (username != null) {
8570
+ headers["x-client-user"] = username;
8571
+ }
8535
8572
  }
8536
8573
  if (auth.preferredRole != null) {
8537
8574
  headers["x-preferred-role"] = auth.preferredRole;
@@ -8552,13 +8589,6 @@ function callProcedure(client, pathSegments, type, input) {
8552
8589
 
8553
8590
  // src/auth.ts
8554
8591
  var REFRESH_MARGIN_MS = 5 * 60 * 1e3;
8555
- function decodeJwtPayload(jwt) {
8556
- const payload = jwt.split(".")[1];
8557
- if (payload == null) {
8558
- throw new Error("Malformed JWT");
8559
- }
8560
- return JSON.parse(Buffer.from(payload, "base64url").toString("utf-8"));
8561
- }
8562
8592
  async function login(username, password) {
8563
8593
  const client = createHttpCaller({});
8564
8594
  const response = await client.api.auth.login.query({ username, password });
@@ -10828,6 +10858,81 @@ var CLI_MANIFEST = {
10828
10858
  "$schema": "http://json-schema.org/draft-07/schema#"
10829
10859
  }
10830
10860
  },
10861
+ {
10862
+ "id": "deleteMedia",
10863
+ "domain": "core",
10864
+ "description": "Delete an uploaded media file for the organization by its S3 key. The key must belong to the given scope (passBuilder, funnel, organizationFilePublic, organizationFilePrivate, or creativeLibrary).",
10865
+ "needsApproval": true,
10866
+ "type": "mutation",
10867
+ "path": [
10868
+ "api",
10869
+ "media",
10870
+ "delete"
10871
+ ],
10872
+ "inputJsonSchema": {
10873
+ "type": "object",
10874
+ "properties": {
10875
+ "scope": {
10876
+ "type": "string",
10877
+ "enum": [
10878
+ "passBuilder",
10879
+ "funnel",
10880
+ "organizationFilePublic",
10881
+ "organizationFilePrivate",
10882
+ "creativeLibrary"
10883
+ ]
10884
+ },
10885
+ "key": {
10886
+ "type": "string"
10887
+ }
10888
+ },
10889
+ "required": [
10890
+ "scope",
10891
+ "key"
10892
+ ],
10893
+ "additionalProperties": false,
10894
+ "$schema": "http://json-schema.org/draft-07/schema#"
10895
+ }
10896
+ },
10897
+ {
10898
+ "id": "describeData",
10899
+ "domain": "data",
10900
+ "description": "Describe the queryable data model, then use queryData to read it. Call with no arguments for an index of every queryable object type plus the full query grammar - start here. Call with schemaName for every object type in that schema with full column detail, schemaName plus objectTypeName for a single object type, or detail='full' for the entire model at once. Columns come back with their type, enum values, nullability and a description; links come back with the name you pass to a pivot or join command. Prefer the 'interface' schema, whose object types (order, orderItem, customer, location, ...) are POS-agnostic and return the same shape regardless of which POS the organization runs. Pass includeGrammar=false to omit the grammar once you already have it.",
10901
+ "needsApproval": false,
10902
+ "type": "query",
10903
+ "path": [
10904
+ "api",
10905
+ "data",
10906
+ "describe"
10907
+ ],
10908
+ "inputJsonSchema": {
10909
+ "type": "object",
10910
+ "properties": {
10911
+ "schemaName": {
10912
+ "type": "string",
10913
+ "description": 'Narrow to one schema, e.g. "interface", "core", "events", "texting". Omit for the index of everything.'
10914
+ },
10915
+ "objectTypeName": {
10916
+ "type": "string",
10917
+ "description": 'Narrow to one object type within schemaName, e.g. "order", "guest". Requires schemaName.'
10918
+ },
10919
+ "detail": {
10920
+ "type": "string",
10921
+ "enum": [
10922
+ "index",
10923
+ "full"
10924
+ ],
10925
+ "description": 'Only applies when schemaName is omitted. "index" (default) lists object types with a one-line description; "full" returns every column of every object type.'
10926
+ },
10927
+ "includeGrammar": {
10928
+ "type": "boolean",
10929
+ "description": "Whether to include the query grammar for queryData. Defaults to true; pass false once you already have it."
10930
+ }
10931
+ },
10932
+ "additionalProperties": false,
10933
+ "$schema": "http://json-schema.org/draft-07/schema#"
10934
+ }
10935
+ },
10831
10936
  {
10832
10937
  "id": "dfyCreateOffer",
10833
10938
  "domain": "offer",
@@ -11252,6 +11357,49 @@ var CLI_MANIFEST = {
11252
11357
  "$schema": "http://json-schema.org/draft-07/schema#"
11253
11358
  }
11254
11359
  },
11360
+ {
11361
+ "id": "getMediaUploadUrl",
11362
+ "domain": "core",
11363
+ "description": "Get a presigned S3 upload URL for a media file in a given scope (passBuilder, funnel, organizationFilePublic, organizationFilePrivate, creativeLibrary). Set cropped to store a cropped variant under the scope's cropped/ subfolder. PUT the file bytes to the returned presignedUrl, then reference the returned url/key.",
11364
+ "needsApproval": false,
11365
+ "type": "mutation",
11366
+ "path": [
11367
+ "api",
11368
+ "media",
11369
+ "getUploadUrl"
11370
+ ],
11371
+ "inputJsonSchema": {
11372
+ "type": "object",
11373
+ "properties": {
11374
+ "scope": {
11375
+ "type": "string",
11376
+ "enum": [
11377
+ "passBuilder",
11378
+ "funnel",
11379
+ "organizationFilePublic",
11380
+ "organizationFilePrivate",
11381
+ "creativeLibrary"
11382
+ ]
11383
+ },
11384
+ "fileName": {
11385
+ "type": "string"
11386
+ },
11387
+ "fileType": {
11388
+ "type": "string"
11389
+ },
11390
+ "cropped": {
11391
+ "type": "boolean"
11392
+ }
11393
+ },
11394
+ "required": [
11395
+ "scope",
11396
+ "fileName",
11397
+ "fileType"
11398
+ ],
11399
+ "additionalProperties": false,
11400
+ "$schema": "http://json-schema.org/draft-07/schema#"
11401
+ }
11402
+ },
11255
11403
  {
11256
11404
  "id": "listAutomationFlows",
11257
11405
  "domain": "automations",
@@ -11430,6 +11578,48 @@ var CLI_MANIFEST = {
11430
11578
  "$schema": "http://json-schema.org/draft-07/schema#"
11431
11579
  }
11432
11580
  },
11581
+ {
11582
+ "id": "listMedia",
11583
+ "domain": "core",
11584
+ "description": "List uploaded media files for the organization across one or more scopes (passBuilder, funnel, organizationFilePublic, organizationFilePrivate, creativeLibrary). Each file is tagged with its scope and a canDelete flag. Set mediaOnly to return only image/video files. Set cropped to list the cropped variants (each scope's cropped/ subfolder) instead of the base files.",
11585
+ "needsApproval": false,
11586
+ "type": "query",
11587
+ "path": [
11588
+ "api",
11589
+ "media",
11590
+ "list"
11591
+ ],
11592
+ "inputJsonSchema": {
11593
+ "type": "object",
11594
+ "properties": {
11595
+ "scopes": {
11596
+ "type": "array",
11597
+ "items": {
11598
+ "type": "string",
11599
+ "enum": [
11600
+ "passBuilder",
11601
+ "funnel",
11602
+ "organizationFilePublic",
11603
+ "organizationFilePrivate",
11604
+ "creativeLibrary"
11605
+ ]
11606
+ },
11607
+ "minItems": 1
11608
+ },
11609
+ "mediaOnly": {
11610
+ "type": "boolean"
11611
+ },
11612
+ "cropped": {
11613
+ "type": "boolean"
11614
+ }
11615
+ },
11616
+ "required": [
11617
+ "scopes"
11618
+ ],
11619
+ "additionalProperties": false,
11620
+ "$schema": "http://json-schema.org/draft-07/schema#"
11621
+ }
11622
+ },
11433
11623
  {
11434
11624
  "id": "loadCurrentOrganization",
11435
11625
  "domain": "core",
@@ -11544,6 +11734,184 @@ var CLI_MANIFEST = {
11544
11734
  "$schema": "http://json-schema.org/draft-07/schema#"
11545
11735
  }
11546
11736
  },
11737
+ {
11738
+ "id": "queryData",
11739
+ "domain": "data",
11740
+ "description": `Run a read-only query against the organization's data. Call describeData first for the object types and their exact column names - do not guess columns.
11741
+ Results are always scoped to the calling organization, so never filter on organizationId yourself.
11742
+
11743
+ Example - export opted-in members with more than 5 visits, newest first:
11744
+ {"schemaName":"core","objectTypeName":"guest","commands":[{"type":"filter","filter":{"type":"and","filters":[{"$optIn":{"boolean":true}},{"$testUser":{"boolean":false}},{"$progress":{"number":5,"match":"GT"}}]}}],"args":{"limit":500,"order":{"field":"timeAdded","direction":"DESC"},"fields":["serialNumber","firstName","lastName","phoneNumber","email","progress"]}}
11745
+
11746
+ Example - order revenue by location for orders closed this year:
11747
+ {"schemaName":"interface","objectTypeName":"order","commands":[{"type":"filter","filter":{"$closedAt":{"datetime":"2026-01-01T00:00:00.000Z","match":"GTE"}}},{"type":"aggregate","aggregate":{"aggregate":{"$amount":"SUM"},"groupBy":{"$locationId":"EXACT"}}}]}
11748
+
11749
+ Example - a member's recent texts, newest first:
11750
+ {"schemaName":"texting","objectTypeName":"textMessage","commands":[{"type":"filter","filter":{"$serialNumber":{"string":"<serial>","match":"EQ"}}}],"args":{"limit":50,"order":{"field":"dateCreated","direction":"DESC"}}}
11751
+
11752
+ Filter leaves are one column each, written as the column name prefixed with $, combined with {"type":"and"|"or","filters":[...]}. Use {"strings":[...]} for any-of rather than a large or. Page by passing the returned nextCursor back as args.cursor.`,
11753
+ "needsApproval": false,
11754
+ "type": "query",
11755
+ "path": [
11756
+ "api",
11757
+ "data",
11758
+ "query"
11759
+ ],
11760
+ "inputJsonSchema": {
11761
+ "type": "object",
11762
+ "properties": {
11763
+ "schemaName": {
11764
+ "type": "string",
11765
+ "description": 'Schema as reported by describeData, e.g. "interface", "core", "events", "texting".'
11766
+ },
11767
+ "objectTypeName": {
11768
+ "type": "string",
11769
+ "description": 'Object type as reported by describeData, e.g. "order", "guest", "userEvent", "textMessage".'
11770
+ },
11771
+ "commands": {
11772
+ "type": "array",
11773
+ "items": {
11774
+ "anyOf": [
11775
+ {
11776
+ "type": "object",
11777
+ "properties": {
11778
+ "type": {
11779
+ "type": "string",
11780
+ "const": "filter"
11781
+ },
11782
+ "filter": {
11783
+ "description": 'Required. A filter tree. One column per leaf, as the column name prefixed with $. Leaf: {"$<column>": <predicate>}. Combine with {"type":"and","filters":[...]} or {"type":"or","filters":[...]}. Predicates by column type - string: {"string":"Jane","match":"EQ"|"NEQ"|"IN"|"ENDS_WITH"} where IN means contains, or {"strings":["a","b"]} for any-of. number: {"number":5,"match":"GT"|"GTE"|"LT"|"LTE"|"EQ"|"NEQ"}. boolean: {"boolean":true} with no match key. datetime/date: {"datetime":"2026-01-01T00:00:00.000Z","match":"GTE"}. Nullable columns also accept {"match":"NULL"} or {"match":"NOT_NULL"}. Example: {"type":"and","filters":[{"$optIn":{"boolean":true}},{"$progress":{"number":5,"match":"GT"}}]}'
11784
+ }
11785
+ },
11786
+ "required": [
11787
+ "type",
11788
+ "filter"
11789
+ ],
11790
+ "additionalProperties": false,
11791
+ "description": 'Restrict the set to matching rows. Example: {"type":"filter","filter":{"$eventType":{"strings":["order","rewardAwarded"]}}}'
11792
+ },
11793
+ {
11794
+ "type": "object",
11795
+ "properties": {
11796
+ "type": {
11797
+ "type": "string",
11798
+ "const": "pivot"
11799
+ },
11800
+ "name": {
11801
+ "type": "string",
11802
+ "description": "A link name from describeData's links for the current object type. Replaces the set with that linked object type, so later commands apply to the new type."
11803
+ }
11804
+ },
11805
+ "required": [
11806
+ "type",
11807
+ "name"
11808
+ ],
11809
+ "additionalProperties": false,
11810
+ "description": 'Traverse a link. Example: {"type":"pivot","name":"orderItem"} on interface.order gives its line items.'
11811
+ },
11812
+ {
11813
+ "type": "object",
11814
+ "properties": {
11815
+ "type": {
11816
+ "type": "string",
11817
+ "const": "aggregate"
11818
+ },
11819
+ "aggregate": {
11820
+ "description": 'Required. Group and aggregate, replacing the row shape with the grouped and aggregated columns only. Shape: {"aggregate":{"$<column>":"SUM"|"MAX"|"MIN"|"AVG"|"COUNT"},"groupBy":{"$<column>":"EXACT"}}. Example - revenue by location: {"aggregate":{"$amount":"SUM"},"groupBy":{"$locationId":"EXACT"}}'
11821
+ }
11822
+ },
11823
+ "required": [
11824
+ "type",
11825
+ "aggregate"
11826
+ ],
11827
+ "additionalProperties": false,
11828
+ "description": 'Group and aggregate. Example: {"type":"aggregate","aggregate":{"aggregate":{"$amount":"SUM"},"groupBy":{"$locationId":"EXACT"}}}'
11829
+ },
11830
+ {
11831
+ "type": "object",
11832
+ "properties": {
11833
+ "type": {
11834
+ "type": "string",
11835
+ "const": "join"
11836
+ },
11837
+ "name": {
11838
+ "type": "string",
11839
+ "description": "A link name from describeData for the current object type."
11840
+ },
11841
+ "fields": {
11842
+ "type": "array",
11843
+ "items": {
11844
+ "type": "string"
11845
+ },
11846
+ "description": "Columns to pull from the linked object type."
11847
+ }
11848
+ },
11849
+ "required": [
11850
+ "type",
11851
+ "name",
11852
+ "fields"
11853
+ ],
11854
+ "additionalProperties": false,
11855
+ "description": 'Widen each row with columns from a linked object, keeping the current object type. Example: {"type":"join","name":"order","fields":["closedAt","amount"]}'
11856
+ }
11857
+ ]
11858
+ },
11859
+ "description": "Ordered list of commands, each narrowing or reshaping the set before the fetch. Omit to fetch the object type unfiltered. pivot and join must come before any aggregate."
11860
+ },
11861
+ "args": {
11862
+ "type": "object",
11863
+ "properties": {
11864
+ "limit": {
11865
+ "type": "integer",
11866
+ "exclusiveMinimum": 0,
11867
+ "maximum": 1e3,
11868
+ "description": "Rows to return. Defaults to 100, maximum 1000."
11869
+ },
11870
+ "cursor": {
11871
+ "type": "integer",
11872
+ "minimum": 0,
11873
+ "description": "Row offset. Pass the nextCursor from the previous response to page; a missing nextCursor means there are no more rows."
11874
+ },
11875
+ "order": {
11876
+ "type": "object",
11877
+ "properties": {
11878
+ "field": {
11879
+ "type": "string"
11880
+ },
11881
+ "direction": {
11882
+ "type": "string",
11883
+ "enum": [
11884
+ "ASC",
11885
+ "DESC"
11886
+ ]
11887
+ }
11888
+ },
11889
+ "required": [
11890
+ "field",
11891
+ "direction"
11892
+ ],
11893
+ "additionalProperties": false,
11894
+ "description": 'Sort order. Example: {"field":"eventTime","direction":"DESC"}'
11895
+ },
11896
+ "fields": {
11897
+ "type": "array",
11898
+ "items": {
11899
+ "type": "string"
11900
+ },
11901
+ "description": "Columns to return. Omit for all of them. The main lever for keeping responses under the payload cap on wide object types."
11902
+ }
11903
+ },
11904
+ "additionalProperties": false
11905
+ }
11906
+ },
11907
+ "required": [
11908
+ "schemaName",
11909
+ "objectTypeName"
11910
+ ],
11911
+ "additionalProperties": false,
11912
+ "$schema": "http://json-schema.org/draft-07/schema#"
11913
+ }
11914
+ },
11547
11915
  {
11548
11916
  "id": "saveFunnelEdits",
11549
11917
  "domain": "funnel",
@@ -17563,6 +17931,9 @@ var CLI_MANIFEST = {
17563
17931
  "type": "string",
17564
17932
  "enum": [
17565
17933
  "first",
17934
+ "second",
17935
+ "third",
17936
+ "fourth",
17566
17937
  "last"
17567
17938
  ]
17568
17939
  }
@@ -17984,7 +18355,8 @@ Call options:
17984
18355
 
17985
18356
  Environment:
17986
18357
  FEAST_API_URL Override the API base URL (default: production)
17987
- FEAST_WEB_URL Override the web app base URL for browser login (default: production)`;
18358
+ FEAST_WEB_URL Override the web app base URL for browser login (default: production)
18359
+ FEAST_NO_UPDATE_CHECK Disable the daily check for a newer published version`;
17988
18360
  function parseArgs(argv) {
17989
18361
  const positional = [];
17990
18362
  const flags = {};
@@ -18237,11 +18609,121 @@ async function runCli(argv) {
18237
18609
  }
18238
18610
  }
18239
18611
 
18612
+ // src/updateNotifier.ts
18613
+ import fs3 from "fs";
18614
+ import { createRequire as createRequire2 } from "module";
18615
+ import os2 from "os";
18616
+ import path2 from "path";
18617
+ var PACKAGE_NAME = "@feastalytics/cli";
18618
+ var REGISTRY_URL = "https://registry.npmjs.org";
18619
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
18620
+ var FETCH_TIMEOUT_MS = 1500;
18621
+ var CACHE_DIR = path2.join(os2.homedir(), ".config", "feast-cli");
18622
+ var CACHE_PATH = path2.join(CACHE_DIR, "update-check.json");
18623
+ function readPackageVersion2() {
18624
+ try {
18625
+ const require2 = createRequire2(import.meta.url);
18626
+ const version = require2("../package.json").version;
18627
+ return typeof version === "string" ? version : void 0;
18628
+ } catch {
18629
+ return void 0;
18630
+ }
18631
+ }
18632
+ function parseVersion(version) {
18633
+ const parts = version.split(".");
18634
+ if (parts.length !== 3) {
18635
+ return void 0;
18636
+ }
18637
+ const numbers = parts.map((part) => Number(part));
18638
+ return numbers.some((number) => !Number.isInteger(number) || number < 0) ? void 0 : numbers;
18639
+ }
18640
+ function isNewer(latest, current) {
18641
+ const a = parseVersion(latest);
18642
+ const b = parseVersion(current);
18643
+ if (a == null || b == null) {
18644
+ return false;
18645
+ }
18646
+ for (let i = 0; i < 3; i++) {
18647
+ if (a[i] !== b[i]) {
18648
+ return a[i] > b[i];
18649
+ }
18650
+ }
18651
+ return false;
18652
+ }
18653
+ function isDisabled() {
18654
+ return process.env.FEAST_NO_UPDATE_CHECK != null || process.env.NO_UPDATE_NOTIFIER != null || process.env.CI != null || !process.stderr.isTTY;
18655
+ }
18656
+ function readCache() {
18657
+ try {
18658
+ const parsed = JSON.parse(
18659
+ fs3.readFileSync(CACHE_PATH, "utf-8")
18660
+ );
18661
+ return typeof parsed.latest === "string" && typeof parsed.checkedAt === "number" ? parsed : void 0;
18662
+ } catch {
18663
+ return void 0;
18664
+ }
18665
+ }
18666
+ function writeCache(cache) {
18667
+ try {
18668
+ fs3.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
18669
+ fs3.writeFileSync(CACHE_PATH, JSON.stringify(cache));
18670
+ } catch {
18671
+ return;
18672
+ }
18673
+ }
18674
+ async function fetchLatestVersion() {
18675
+ try {
18676
+ const response = await fetch(
18677
+ `${REGISTRY_URL}/${encodeURIComponent(PACKAGE_NAME)}/latest`,
18678
+ {
18679
+ headers: { accept: "application/json" },
18680
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
18681
+ }
18682
+ );
18683
+ if (!response.ok) {
18684
+ return void 0;
18685
+ }
18686
+ const body = await response.json();
18687
+ return typeof body.version === "string" ? body.version : void 0;
18688
+ } catch {
18689
+ return void 0;
18690
+ }
18691
+ }
18692
+ async function notifyIfOutdated() {
18693
+ if (isDisabled()) {
18694
+ return;
18695
+ }
18696
+ const current = readPackageVersion2();
18697
+ if (current == null) {
18698
+ return;
18699
+ }
18700
+ const cached = readCache();
18701
+ let latest = cached?.latest;
18702
+ if (cached == null || Date.now() - cached.checkedAt > CHECK_INTERVAL_MS) {
18703
+ const fetched = await fetchLatestVersion();
18704
+ if (fetched != null) {
18705
+ latest = fetched;
18706
+ writeCache({ checkedAt: Date.now(), latest: fetched });
18707
+ } else {
18708
+ writeCache({ checkedAt: Date.now(), latest: latest ?? current });
18709
+ }
18710
+ }
18711
+ if (latest == null || !isNewer(latest, current)) {
18712
+ return;
18713
+ }
18714
+ console.error(
18715
+ `
18716
+ Update available: feast ${current} \u2192 ${latest}
18717
+ npx ${PACKAGE_NAME}@latest <command> (or: npm install -g ${PACKAGE_NAME}@latest)
18718
+ `
18719
+ );
18720
+ }
18721
+
18240
18722
  // src/cli.ts
18241
18723
  runCli(process.argv.slice(2)).catch((error) => {
18242
18724
  console.error(errorMessage(error));
18243
18725
  process.exitCode = 1;
18244
- });
18726
+ }).then(() => notifyIfOutdated()).catch(() => void 0);
18245
18727
  /*! Bundled license information:
18246
18728
 
18247
18729
  @trpc/client/dist/httpUtils-b9d0cb48.mjs:
package/feast/SKILL.md CHANGED
@@ -14,17 +14,37 @@ The CLI is the source of truth for *which* tools exist and *what* they accept
14
14
  The `feast` CLI must be installed and on PATH:
15
15
 
16
16
  ```bash
17
- npm install -g @feast/cli # or run ad-hoc with: npx @feast/cli <command>
17
+ npm install -g @feastalytics/cli # or run ad-hoc with: npx @feastalytics/cli <command>
18
18
  ```
19
19
 
20
20
  Authenticate once — tokens are cached in `~/.config/feast-cli/credentials.json` and refreshed automatically:
21
21
 
22
22
  ```bash
23
- feast login <username>
23
+ feast login # opens a browser to authorize (default)
24
+ feast login --password [username] # headless / CI: username + password prompt, no browser
24
25
  ```
25
26
 
26
27
  If a command reports you're not logged in or the session expired, re-run `feast login`.
27
28
 
29
+ ## Staying current
30
+
31
+ Neither the CLI nor this skill updates itself. When a command prints an update notice on stderr:
32
+
33
+ ```
34
+ Update available: feast 0.1.1 → 0.2.0
35
+ ```
36
+
37
+ update both, then tell the user in one line that you did:
38
+
39
+ ```bash
40
+ npm install -g @feastalytics/cli@latest # only if `feast` is already on PATH from a global install
41
+ npx skills add feastalytics/cli -g -a '*' -y # refresh this skill from the repo
42
+ ```
43
+
44
+ If you've been invoking the CLI through `npx` rather than a global install, skip the `npm install -g` and use `npx @feastalytics/cli@latest <command>` for the rest of the session instead — `npx` reuses a cached copy otherwise.
45
+
46
+ 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. If the global install fails on permissions, don't retry with `sudo` — tell the user and fall back to `npx @feastalytics/cli@latest`.
47
+
28
48
  ## The core loop: discover → describe → call
29
49
 
30
50
  Don't guess tool names or input shapes. Introspect the live CLI:
@@ -88,7 +108,7 @@ The pattern generalizes: identify the org, learn the tool, resolve any reference
88
108
 
89
109
  ## When something fails
90
110
 
91
- - "Not logged in / session expired" → `feast login <username>`.
111
+ - "Not logged in / session expired" → `feast login` (or `feast login --password [username]` in a headless/CI context).
92
112
  - "You belong to multiple organizations" → pick one with `--org`, using `feast whoami` to get the id.
93
113
  - "Input does not match the tool schema" → re-read `feast describe <tool>` and fix the named fields.
94
114
  - A tool you expected isn't listed by `feast tools` → it may not be exposed yet; don't fabricate a call, tell the user.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feastalytics/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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": {