@feastalytics/cli 0.1.1 → 0.1.3

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/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 });
@@ -8626,146 +8656,6 @@ function errorMessage(error) {
8626
8656
  // src/main.ts
8627
8657
  import fs2 from "fs";
8628
8658
 
8629
- // src/browserLogin.ts
8630
- import { spawn } from "child_process";
8631
- import { createHash, randomBytes } from "crypto";
8632
- import http from "http";
8633
- var CALLBACK_TIMEOUT_MS = 5 * 60 * 1e3;
8634
- function base64url(buffer) {
8635
- return buffer.toString("base64url");
8636
- }
8637
- function tabHtml(title, body) {
8638
- return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title>
8639
- <style>body{font-family:-apple-system,system-ui,sans-serif;background:#0f0f0f;color:#f5f5f5;
8640
- display:flex;min-height:100vh;align-items:center;justify-content:center;margin:0}
8641
- .card{text-align:center;padding:2rem 3rem}.t{font-size:1.4rem;font-weight:600;margin-bottom:.5rem}
8642
- .b{color:#a1a1a1}</style></head><body><div class="card">
8643
- <div class="t">${title}</div><div class="b">${body}</div></div></body></html>`;
8644
- }
8645
- function openBrowser(url) {
8646
- let command;
8647
- let args;
8648
- if (process.platform === "darwin") {
8649
- command = "open";
8650
- args = [url];
8651
- } else if (process.platform === "win32") {
8652
- command = "cmd";
8653
- args = ["/c", "start", "", url];
8654
- } else {
8655
- command = "xdg-open";
8656
- args = [url];
8657
- }
8658
- try {
8659
- const child = spawn(command, args, { stdio: "ignore", detached: true });
8660
- child.on("error", () => {
8661
- });
8662
- child.unref();
8663
- } catch {
8664
- }
8665
- }
8666
- function startLoopback(expectedState) {
8667
- return new Promise((resolveOuter, rejectOuter) => {
8668
- let resolveCode = () => {
8669
- };
8670
- let rejectCode = () => {
8671
- };
8672
- const waitForCode = new Promise((resolve, reject) => {
8673
- resolveCode = resolve;
8674
- rejectCode = reject;
8675
- });
8676
- waitForCode.catch(() => {
8677
- });
8678
- const server = http.createServer((req, res) => {
8679
- const url = new URL(req.url ?? "/", "http://127.0.0.1");
8680
- if (url.pathname !== "/callback") {
8681
- res.writeHead(404);
8682
- res.end();
8683
- return;
8684
- }
8685
- const error = url.searchParams.get("error");
8686
- if (error != null) {
8687
- res.writeHead(200, { "Content-Type": "text/html" });
8688
- res.end(tabHtml("Authorization declined", "You can close this tab."));
8689
- rejectCode(new Error(`Authorization declined: ${error}`));
8690
- return;
8691
- }
8692
- if (url.searchParams.get("state") !== expectedState) {
8693
- res.writeHead(400, { "Content-Type": "text/html" });
8694
- res.end(
8695
- tabHtml("Invalid request", "State mismatch \u2014 retry from the CLI.")
8696
- );
8697
- rejectCode(new Error("State mismatch \u2014 aborting (possible CSRF)"));
8698
- return;
8699
- }
8700
- const code = url.searchParams.get("code");
8701
- if (code == null || code.length === 0) {
8702
- res.writeHead(400);
8703
- res.end();
8704
- rejectCode(new Error("Authorization callback did not include a code"));
8705
- return;
8706
- }
8707
- res.writeHead(200, {
8708
- "Content-Type": "text/html",
8709
- Connection: "close"
8710
- });
8711
- res.end(
8712
- tabHtml(
8713
- "You're all set \u2713",
8714
- "The feast CLI is authorized. Close this tab and return to your terminal."
8715
- )
8716
- );
8717
- resolveCode(code);
8718
- });
8719
- const timeout = setTimeout(() => {
8720
- rejectCode(
8721
- new Error("Timed out waiting for browser authorization (5 minutes)")
8722
- );
8723
- }, CALLBACK_TIMEOUT_MS);
8724
- const close = () => {
8725
- clearTimeout(timeout);
8726
- server.closeAllConnections?.();
8727
- server.close();
8728
- };
8729
- server.on("error", rejectOuter);
8730
- server.listen(0, "127.0.0.1", () => {
8731
- server.unref();
8732
- const address = server.address();
8733
- const port = typeof address === "object" && address != null ? address.port : 0;
8734
- resolveOuter({ port, waitForCode, close });
8735
- });
8736
- });
8737
- }
8738
- async function browserLogin() {
8739
- const codeVerifier = base64url(randomBytes(32));
8740
- const codeChallenge = base64url(
8741
- createHash("sha256").update(codeVerifier).digest()
8742
- );
8743
- const state = base64url(randomBytes(16));
8744
- const { port, waitForCode, close } = await startLoopback(state);
8745
- try {
8746
- const authUrl = `${getWebBaseUrl()}/oauth?port=${port}&state=${encodeURIComponent(state)}&challenge=${encodeURIComponent(codeChallenge)}`;
8747
- console.error("Opening your browser to authorize the feast CLI...");
8748
- console.error(`If it doesn't open, visit:
8749
- ${authUrl}
8750
- `);
8751
- openBrowser(authUrl);
8752
- const code = await waitForCode;
8753
- const client = createHttpCaller({});
8754
- const response = await client.api.auth.exchangeAuthCode.mutate({
8755
- code,
8756
- codeVerifier
8757
- });
8758
- const tokens = response?.tokens;
8759
- if (tokens?.accessToken?.jwtToken == null) {
8760
- throw new Error("Authorization failed: empty token response");
8761
- }
8762
- saveTokens(tokens);
8763
- return tokens;
8764
- } finally {
8765
- close();
8766
- }
8767
- }
8768
-
8769
8659
  // src/generated/manifest.ts
8770
8660
  var CLI_MANIFEST = {
8771
8661
  "awsAccountId": "891817334910",
@@ -10828,6 +10718,82 @@ var CLI_MANIFEST = {
10828
10718
  "$schema": "http://json-schema.org/draft-07/schema#"
10829
10719
  }
10830
10720
  },
10721
+ {
10722
+ "id": "deleteMedia",
10723
+ "domain": "core",
10724
+ "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).",
10725
+ "needsApproval": true,
10726
+ "type": "mutation",
10727
+ "path": [
10728
+ "api",
10729
+ "media",
10730
+ "delete"
10731
+ ],
10732
+ "inputJsonSchema": {
10733
+ "type": "object",
10734
+ "properties": {
10735
+ "scope": {
10736
+ "type": "string",
10737
+ "enum": [
10738
+ "passBuilder",
10739
+ "funnel",
10740
+ "organizationFilePublic",
10741
+ "organizationFilePrivate",
10742
+ "creativeLibrary",
10743
+ "creativeLibraryBroll"
10744
+ ]
10745
+ },
10746
+ "key": {
10747
+ "type": "string"
10748
+ }
10749
+ },
10750
+ "required": [
10751
+ "scope",
10752
+ "key"
10753
+ ],
10754
+ "additionalProperties": false,
10755
+ "$schema": "http://json-schema.org/draft-07/schema#"
10756
+ }
10757
+ },
10758
+ {
10759
+ "id": "describeData",
10760
+ "domain": "data",
10761
+ "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.",
10762
+ "needsApproval": false,
10763
+ "type": "query",
10764
+ "path": [
10765
+ "api",
10766
+ "data",
10767
+ "describe"
10768
+ ],
10769
+ "inputJsonSchema": {
10770
+ "type": "object",
10771
+ "properties": {
10772
+ "schemaName": {
10773
+ "type": "string",
10774
+ "description": 'Narrow to one schema, e.g. "interface", "core", "events", "texting". Omit for the index of everything.'
10775
+ },
10776
+ "objectTypeName": {
10777
+ "type": "string",
10778
+ "description": 'Narrow to one object type within schemaName, e.g. "order", "guest". Requires schemaName.'
10779
+ },
10780
+ "detail": {
10781
+ "type": "string",
10782
+ "enum": [
10783
+ "index",
10784
+ "full"
10785
+ ],
10786
+ "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.'
10787
+ },
10788
+ "includeGrammar": {
10789
+ "type": "boolean",
10790
+ "description": "Whether to include the query grammar for queryData. Defaults to true; pass false once you already have it."
10791
+ }
10792
+ },
10793
+ "additionalProperties": false,
10794
+ "$schema": "http://json-schema.org/draft-07/schema#"
10795
+ }
10796
+ },
10831
10797
  {
10832
10798
  "id": "dfyCreateOffer",
10833
10799
  "domain": "offer",
@@ -11252,6 +11218,50 @@ var CLI_MANIFEST = {
11252
11218
  "$schema": "http://json-schema.org/draft-07/schema#"
11253
11219
  }
11254
11220
  },
11221
+ {
11222
+ "id": "getMediaUploadUrl",
11223
+ "domain": "core",
11224
+ "description": "Get a presigned S3 upload URL for a media file in a given scope (passBuilder, funnel, organizationFilePublic, organizationFilePrivate, creativeLibrary, creativeLibraryBroll). 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.",
11225
+ "needsApproval": false,
11226
+ "type": "mutation",
11227
+ "path": [
11228
+ "api",
11229
+ "media",
11230
+ "getUploadUrl"
11231
+ ],
11232
+ "inputJsonSchema": {
11233
+ "type": "object",
11234
+ "properties": {
11235
+ "scope": {
11236
+ "type": "string",
11237
+ "enum": [
11238
+ "passBuilder",
11239
+ "funnel",
11240
+ "organizationFilePublic",
11241
+ "organizationFilePrivate",
11242
+ "creativeLibrary",
11243
+ "creativeLibraryBroll"
11244
+ ]
11245
+ },
11246
+ "fileName": {
11247
+ "type": "string"
11248
+ },
11249
+ "fileType": {
11250
+ "type": "string"
11251
+ },
11252
+ "cropped": {
11253
+ "type": "boolean"
11254
+ }
11255
+ },
11256
+ "required": [
11257
+ "scope",
11258
+ "fileName",
11259
+ "fileType"
11260
+ ],
11261
+ "additionalProperties": false,
11262
+ "$schema": "http://json-schema.org/draft-07/schema#"
11263
+ }
11264
+ },
11255
11265
  {
11256
11266
  "id": "listAutomationFlows",
11257
11267
  "domain": "automations",
@@ -11430,6 +11440,49 @@ var CLI_MANIFEST = {
11430
11440
  "$schema": "http://json-schema.org/draft-07/schema#"
11431
11441
  }
11432
11442
  },
11443
+ {
11444
+ "id": "listMedia",
11445
+ "domain": "core",
11446
+ "description": "List uploaded media files for the organization across one or more scopes (passBuilder, funnel, organizationFilePublic, organizationFilePrivate, creativeLibrary, creativeLibraryBroll). 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.",
11447
+ "needsApproval": false,
11448
+ "type": "query",
11449
+ "path": [
11450
+ "api",
11451
+ "media",
11452
+ "list"
11453
+ ],
11454
+ "inputJsonSchema": {
11455
+ "type": "object",
11456
+ "properties": {
11457
+ "scopes": {
11458
+ "type": "array",
11459
+ "items": {
11460
+ "type": "string",
11461
+ "enum": [
11462
+ "passBuilder",
11463
+ "funnel",
11464
+ "organizationFilePublic",
11465
+ "organizationFilePrivate",
11466
+ "creativeLibrary",
11467
+ "creativeLibraryBroll"
11468
+ ]
11469
+ },
11470
+ "minItems": 1
11471
+ },
11472
+ "mediaOnly": {
11473
+ "type": "boolean"
11474
+ },
11475
+ "cropped": {
11476
+ "type": "boolean"
11477
+ }
11478
+ },
11479
+ "required": [
11480
+ "scopes"
11481
+ ],
11482
+ "additionalProperties": false,
11483
+ "$schema": "http://json-schema.org/draft-07/schema#"
11484
+ }
11485
+ },
11433
11486
  {
11434
11487
  "id": "loadCurrentOrganization",
11435
11488
  "domain": "core",
@@ -11527,18 +11580,196 @@ var CLI_MANIFEST = {
11527
11580
  "imageKey": {
11528
11581
  "type": "string"
11529
11582
  },
11530
- "imageUrl": {
11531
- "type": "string"
11583
+ "imageUrl": {
11584
+ "type": "string"
11585
+ }
11586
+ },
11587
+ "required": [
11588
+ "promotionName"
11589
+ ],
11590
+ "additionalProperties": false
11591
+ }
11592
+ },
11593
+ "required": [
11594
+ "campaignId"
11595
+ ],
11596
+ "additionalProperties": false,
11597
+ "$schema": "http://json-schema.org/draft-07/schema#"
11598
+ }
11599
+ },
11600
+ {
11601
+ "id": "queryData",
11602
+ "domain": "data",
11603
+ "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.
11604
+ Results are always scoped to the calling organization, so never filter on organizationId yourself.
11605
+
11606
+ Example - export opted-in members with more than 5 visits, newest first:
11607
+ {"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"]}}
11608
+
11609
+ Example - order revenue by location for orders closed this year:
11610
+ {"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"}}}]}
11611
+
11612
+ Example - a member's recent texts, newest first:
11613
+ {"schemaName":"texting","objectTypeName":"textMessage","commands":[{"type":"filter","filter":{"$serialNumber":{"string":"<serial>","match":"EQ"}}}],"args":{"limit":50,"order":{"field":"dateCreated","direction":"DESC"}}}
11614
+
11615
+ 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.`,
11616
+ "needsApproval": false,
11617
+ "type": "query",
11618
+ "path": [
11619
+ "api",
11620
+ "data",
11621
+ "query"
11622
+ ],
11623
+ "inputJsonSchema": {
11624
+ "type": "object",
11625
+ "properties": {
11626
+ "schemaName": {
11627
+ "type": "string",
11628
+ "description": 'Schema as reported by describeData, e.g. "interface", "core", "events", "texting".'
11629
+ },
11630
+ "objectTypeName": {
11631
+ "type": "string",
11632
+ "description": 'Object type as reported by describeData, e.g. "order", "guest", "userEvent", "textMessage".'
11633
+ },
11634
+ "commands": {
11635
+ "type": "array",
11636
+ "items": {
11637
+ "anyOf": [
11638
+ {
11639
+ "type": "object",
11640
+ "properties": {
11641
+ "type": {
11642
+ "type": "string",
11643
+ "const": "filter"
11644
+ },
11645
+ "filter": {
11646
+ "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"}}]}'
11647
+ }
11648
+ },
11649
+ "required": [
11650
+ "type",
11651
+ "filter"
11652
+ ],
11653
+ "additionalProperties": false,
11654
+ "description": 'Restrict the set to matching rows. Example: {"type":"filter","filter":{"$eventType":{"strings":["order","rewardAwarded"]}}}'
11655
+ },
11656
+ {
11657
+ "type": "object",
11658
+ "properties": {
11659
+ "type": {
11660
+ "type": "string",
11661
+ "const": "pivot"
11662
+ },
11663
+ "name": {
11664
+ "type": "string",
11665
+ "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."
11666
+ }
11667
+ },
11668
+ "required": [
11669
+ "type",
11670
+ "name"
11671
+ ],
11672
+ "additionalProperties": false,
11673
+ "description": 'Traverse a link. Example: {"type":"pivot","name":"orderItem"} on interface.order gives its line items.'
11674
+ },
11675
+ {
11676
+ "type": "object",
11677
+ "properties": {
11678
+ "type": {
11679
+ "type": "string",
11680
+ "const": "aggregate"
11681
+ },
11682
+ "aggregate": {
11683
+ "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"}}'
11684
+ }
11685
+ },
11686
+ "required": [
11687
+ "type",
11688
+ "aggregate"
11689
+ ],
11690
+ "additionalProperties": false,
11691
+ "description": 'Group and aggregate. Example: {"type":"aggregate","aggregate":{"aggregate":{"$amount":"SUM"},"groupBy":{"$locationId":"EXACT"}}}'
11692
+ },
11693
+ {
11694
+ "type": "object",
11695
+ "properties": {
11696
+ "type": {
11697
+ "type": "string",
11698
+ "const": "join"
11699
+ },
11700
+ "name": {
11701
+ "type": "string",
11702
+ "description": "A link name from describeData for the current object type."
11703
+ },
11704
+ "fields": {
11705
+ "type": "array",
11706
+ "items": {
11707
+ "type": "string"
11708
+ },
11709
+ "description": "Columns to pull from the linked object type."
11710
+ }
11711
+ },
11712
+ "required": [
11713
+ "type",
11714
+ "name",
11715
+ "fields"
11716
+ ],
11717
+ "additionalProperties": false,
11718
+ "description": 'Widen each row with columns from a linked object, keeping the current object type. Example: {"type":"join","name":"order","fields":["closedAt","amount"]}'
11719
+ }
11720
+ ]
11721
+ },
11722
+ "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."
11723
+ },
11724
+ "args": {
11725
+ "type": "object",
11726
+ "properties": {
11727
+ "limit": {
11728
+ "type": "integer",
11729
+ "exclusiveMinimum": 0,
11730
+ "maximum": 1e3,
11731
+ "description": "Rows to return. Defaults to 100, maximum 1000."
11732
+ },
11733
+ "cursor": {
11734
+ "type": "integer",
11735
+ "minimum": 0,
11736
+ "description": "Row offset. Pass the nextCursor from the previous response to page; a missing nextCursor means there are no more rows."
11737
+ },
11738
+ "order": {
11739
+ "type": "object",
11740
+ "properties": {
11741
+ "field": {
11742
+ "type": "string"
11743
+ },
11744
+ "direction": {
11745
+ "type": "string",
11746
+ "enum": [
11747
+ "ASC",
11748
+ "DESC"
11749
+ ]
11750
+ }
11751
+ },
11752
+ "required": [
11753
+ "field",
11754
+ "direction"
11755
+ ],
11756
+ "additionalProperties": false,
11757
+ "description": 'Sort order. Example: {"field":"eventTime","direction":"DESC"}'
11758
+ },
11759
+ "fields": {
11760
+ "type": "array",
11761
+ "items": {
11762
+ "type": "string"
11763
+ },
11764
+ "description": "Columns to return. Omit for all of them. The main lever for keeping responses under the payload cap on wide object types."
11532
11765
  }
11533
11766
  },
11534
- "required": [
11535
- "promotionName"
11536
- ],
11537
11767
  "additionalProperties": false
11538
11768
  }
11539
11769
  },
11540
11770
  "required": [
11541
- "campaignId"
11771
+ "schemaName",
11772
+ "objectTypeName"
11542
11773
  ],
11543
11774
  "additionalProperties": false,
11544
11775
  "$schema": "http://json-schema.org/draft-07/schema#"
@@ -17563,6 +17794,9 @@ var CLI_MANIFEST = {
17563
17794
  "type": "string",
17564
17795
  "enum": [
17565
17796
  "first",
17797
+ "second",
17798
+ "third",
17799
+ "fourth",
17566
17800
  "last"
17567
17801
  ]
17568
17802
  }
@@ -17894,6 +18128,234 @@ ${available}`
17894
18128
  };
17895
18129
  }
17896
18130
 
18131
+ // src/actingContext.ts
18132
+ function environmentAccessToken() {
18133
+ const token = process.env.FEAST_ACCESS_TOKEN;
18134
+ return token != null && token.length > 0 ? token : void 0;
18135
+ }
18136
+ function firstNonEmpty(...values) {
18137
+ for (const value of values) {
18138
+ if (value != null && value.length > 0) {
18139
+ return value;
18140
+ }
18141
+ }
18142
+ return void 0;
18143
+ }
18144
+ function roleLabelFromArn(arn) {
18145
+ return arn.split("/").pop()?.split(".").pop() ?? arn;
18146
+ }
18147
+ function buildPreferredRole(organizationId, role) {
18148
+ if (role.startsWith("arn:")) {
18149
+ return role;
18150
+ }
18151
+ if (!CLI_MANIFEST.organizationRoles.includes(role)) {
18152
+ throw new Error(
18153
+ `Unknown role "${role}". Expected one of ${CLI_MANIFEST.organizationRoles.join(", ")}, or a full role ARN.`
18154
+ );
18155
+ }
18156
+ return formRoleArn(organizationId, role);
18157
+ }
18158
+ function resolvePinned(pinned, flag, variableName, flagName) {
18159
+ if (pinned == null) {
18160
+ return flag;
18161
+ }
18162
+ if (flag != null && flag !== pinned) {
18163
+ throw new Error(
18164
+ `${variableName} pins this session to ${pinned}, so ${flagName} ${flag} is not allowed.`
18165
+ );
18166
+ }
18167
+ return pinned;
18168
+ }
18169
+ function resolveFromEnvironment(accessToken, orgFlag, roleFlag) {
18170
+ const organizationId = resolvePinned(
18171
+ firstNonEmpty(process.env.FEAST_ORGANIZATION_ID),
18172
+ orgFlag,
18173
+ "FEAST_ORGANIZATION_ID",
18174
+ "--org"
18175
+ );
18176
+ if (organizationId == null) {
18177
+ throw new Error(
18178
+ "FEAST_ACCESS_TOKEN is set, so the organization cannot be read from the token. Pass --org <organizationId> or set FEAST_ORGANIZATION_ID."
18179
+ );
18180
+ }
18181
+ const role = resolvePinned(
18182
+ firstNonEmpty(process.env.FEAST_PREFERRED_ROLE),
18183
+ roleFlag,
18184
+ "FEAST_PREFERRED_ROLE",
18185
+ "--role"
18186
+ );
18187
+ if (role == null) {
18188
+ throw new Error(
18189
+ "FEAST_ACCESS_TOKEN is set, so the role cannot be read from the token. Pass --role <role> or set FEAST_PREFERRED_ROLE."
18190
+ );
18191
+ }
18192
+ const preferredRole = buildPreferredRole(organizationId, role);
18193
+ return {
18194
+ accessToken,
18195
+ organizationId,
18196
+ preferredRole,
18197
+ roleLabel: roleLabelFromArn(preferredRole),
18198
+ source: "environment"
18199
+ };
18200
+ }
18201
+ async function resolveActingContext(orgFlag, roleFlag) {
18202
+ const environmentToken = environmentAccessToken();
18203
+ if (environmentToken != null) {
18204
+ return resolveFromEnvironment(environmentToken, orgFlag, roleFlag);
18205
+ }
18206
+ const tokens = await ensureFreshTokens();
18207
+ const accessToken = tokens.accessToken.jwtToken;
18208
+ const organizationId = resolveOrganizationId(accessToken, orgFlag);
18209
+ const resolved = resolvePreferredRole(accessToken, organizationId);
18210
+ return {
18211
+ accessToken,
18212
+ organizationId,
18213
+ preferredRole: resolved.arn,
18214
+ roleLabel: resolved.orgRole.role,
18215
+ source: "credentials"
18216
+ };
18217
+ }
18218
+
18219
+ // src/browserLogin.ts
18220
+ import { spawn } from "child_process";
18221
+ import { createHash, randomBytes } from "crypto";
18222
+ import http from "http";
18223
+ var CALLBACK_TIMEOUT_MS = 5 * 60 * 1e3;
18224
+ function base64url(buffer) {
18225
+ return buffer.toString("base64url");
18226
+ }
18227
+ function tabHtml(title, body) {
18228
+ return `<!doctype html><html><head><meta charset="utf-8"><title>${title}</title>
18229
+ <style>body{font-family:-apple-system,system-ui,sans-serif;background:#0f0f0f;color:#f5f5f5;
18230
+ display:flex;min-height:100vh;align-items:center;justify-content:center;margin:0}
18231
+ .card{text-align:center;padding:2rem 3rem}.t{font-size:1.4rem;font-weight:600;margin-bottom:.5rem}
18232
+ .b{color:#a1a1a1}</style></head><body><div class="card">
18233
+ <div class="t">${title}</div><div class="b">${body}</div></div></body></html>`;
18234
+ }
18235
+ function openBrowser(url) {
18236
+ let command;
18237
+ let args;
18238
+ if (process.platform === "darwin") {
18239
+ command = "open";
18240
+ args = [url];
18241
+ } else if (process.platform === "win32") {
18242
+ command = "cmd";
18243
+ args = ["/c", "start", "", url];
18244
+ } else {
18245
+ command = "xdg-open";
18246
+ args = [url];
18247
+ }
18248
+ try {
18249
+ const child = spawn(command, args, { stdio: "ignore", detached: true });
18250
+ child.on("error", () => {
18251
+ });
18252
+ child.unref();
18253
+ } catch {
18254
+ }
18255
+ }
18256
+ function startLoopback(expectedState) {
18257
+ return new Promise((resolveOuter, rejectOuter) => {
18258
+ let resolveCode = () => {
18259
+ };
18260
+ let rejectCode = () => {
18261
+ };
18262
+ const waitForCode = new Promise((resolve, reject) => {
18263
+ resolveCode = resolve;
18264
+ rejectCode = reject;
18265
+ });
18266
+ waitForCode.catch(() => {
18267
+ });
18268
+ const server = http.createServer((req, res) => {
18269
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
18270
+ if (url.pathname !== "/callback") {
18271
+ res.writeHead(404);
18272
+ res.end();
18273
+ return;
18274
+ }
18275
+ const error = url.searchParams.get("error");
18276
+ if (error != null) {
18277
+ res.writeHead(200, { "Content-Type": "text/html" });
18278
+ res.end(tabHtml("Authorization declined", "You can close this tab."));
18279
+ rejectCode(new Error(`Authorization declined: ${error}`));
18280
+ return;
18281
+ }
18282
+ if (url.searchParams.get("state") !== expectedState) {
18283
+ res.writeHead(400, { "Content-Type": "text/html" });
18284
+ res.end(
18285
+ tabHtml("Invalid request", "State mismatch \u2014 retry from the CLI.")
18286
+ );
18287
+ rejectCode(new Error("State mismatch \u2014 aborting (possible CSRF)"));
18288
+ return;
18289
+ }
18290
+ const code = url.searchParams.get("code");
18291
+ if (code == null || code.length === 0) {
18292
+ res.writeHead(400);
18293
+ res.end();
18294
+ rejectCode(new Error("Authorization callback did not include a code"));
18295
+ return;
18296
+ }
18297
+ res.writeHead(200, {
18298
+ "Content-Type": "text/html",
18299
+ Connection: "close"
18300
+ });
18301
+ res.end(
18302
+ tabHtml(
18303
+ "You're all set \u2713",
18304
+ "The feast CLI is authorized. Close this tab and return to your terminal."
18305
+ )
18306
+ );
18307
+ resolveCode(code);
18308
+ });
18309
+ const timeout = setTimeout(() => {
18310
+ rejectCode(
18311
+ new Error("Timed out waiting for browser authorization (5 minutes)")
18312
+ );
18313
+ }, CALLBACK_TIMEOUT_MS);
18314
+ const close = () => {
18315
+ clearTimeout(timeout);
18316
+ server.closeAllConnections?.();
18317
+ server.close();
18318
+ };
18319
+ server.on("error", rejectOuter);
18320
+ server.listen(0, "127.0.0.1", () => {
18321
+ server.unref();
18322
+ const address = server.address();
18323
+ const port = typeof address === "object" && address != null ? address.port : 0;
18324
+ resolveOuter({ port, waitForCode, close });
18325
+ });
18326
+ });
18327
+ }
18328
+ async function browserLogin() {
18329
+ const codeVerifier = base64url(randomBytes(32));
18330
+ const codeChallenge = base64url(
18331
+ createHash("sha256").update(codeVerifier).digest()
18332
+ );
18333
+ const state = base64url(randomBytes(16));
18334
+ const { port, waitForCode, close } = await startLoopback(state);
18335
+ try {
18336
+ const authUrl = `${getWebBaseUrl()}/oauth?port=${port}&state=${encodeURIComponent(state)}&challenge=${encodeURIComponent(codeChallenge)}`;
18337
+ console.error("Opening your browser to authorize the feast CLI...");
18338
+ console.error(`If it doesn't open, visit:
18339
+ ${authUrl}
18340
+ `);
18341
+ openBrowser(authUrl);
18342
+ const code = await waitForCode;
18343
+ const client = createHttpCaller({});
18344
+ const response = await client.api.auth.exchangeAuthCode.mutate({
18345
+ code,
18346
+ codeVerifier
18347
+ });
18348
+ const tokens = response?.tokens;
18349
+ if (tokens?.accessToken?.jwtToken == null) {
18350
+ throw new Error("Authorization failed: empty token response");
18351
+ }
18352
+ saveTokens(tokens);
18353
+ return tokens;
18354
+ } finally {
18355
+ close();
18356
+ }
18357
+ }
18358
+
17897
18359
  // src/prompt.ts
17898
18360
  import readline from "readline";
17899
18361
  function promptText(question) {
@@ -17978,17 +18440,26 @@ Commands:
17978
18440
 
17979
18441
  Call options:
17980
18442
  --org <organizationId> Organization to act on (required unless you belong to exactly one)
18443
+ --role <role|roleArn> Role to act as, e.g. OWNER (required when FEAST_ACCESS_TOKEN is set)
17981
18444
  --input <json> Tool input as a JSON string (default: {})
17982
18445
  --input-file <path> Tool input from a JSON file
17983
18446
  --yes Skip the confirmation prompt for mutations
17984
18447
 
17985
18448
  Environment:
17986
18449
  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)`;
18450
+ FEAST_WEB_URL Override the web app base URL for browser login (default: production)
18451
+ FEAST_NO_UPDATE_CHECK Disable the daily check for a newer published version
18452
+
18453
+ Non-interactive credentials (for sandboxed agents):
18454
+ FEAST_ACCESS_TOKEN Access token to send verbatim. When set, no token is read
18455
+ from disk and none is refreshed, and the token is never
18456
+ decoded \u2014 so the organization and role must be supplied too.
18457
+ FEAST_ORGANIZATION_ID Pins the organization. --org must match it or be omitted.
18458
+ FEAST_PREFERRED_ROLE Pins the role. --role must match it or be omitted.`;
17988
18459
  function parseArgs(argv) {
17989
18460
  const positional = [];
17990
18461
  const flags = {};
17991
- const valueFlags = /* @__PURE__ */ new Set(["org", "input", "input-file", "domain"]);
18462
+ const valueFlags = /* @__PURE__ */ new Set(["org", "role", "input", "input-file", "domain"]);
17992
18463
  for (let i = 0; i < argv.length; i++) {
17993
18464
  const arg = argv[i];
17994
18465
  if (!arg.startsWith("--")) {
@@ -18010,6 +18481,11 @@ function parseArgs(argv) {
18010
18481
  return { positional, flags };
18011
18482
  }
18012
18483
  async function commandLogin(args) {
18484
+ if (environmentAccessToken() != null) {
18485
+ throw new Error(
18486
+ "FEAST_ACCESS_TOKEN is set, so this CLI is using credentials from the environment. Unset it to log in and store tokens on disk."
18487
+ );
18488
+ }
18013
18489
  if (args.flags["password"] === true) {
18014
18490
  const username = args.positional[0] ?? await promptText("Username: ");
18015
18491
  if (username.length === 0) {
@@ -18030,7 +18506,20 @@ async function commandLogin(args) {
18030
18506
  console.info(`Logged in as ${payload.username ?? payload.sub}`);
18031
18507
  console.info(`Tokens stored in ${getCredentialsPath()}`);
18032
18508
  }
18033
- async function commandWhoami() {
18509
+ async function commandWhoami(args) {
18510
+ if (environmentAccessToken() != null) {
18511
+ const acting = await resolveActingContext(
18512
+ args.flags["org"],
18513
+ args.flags["role"]
18514
+ );
18515
+ console.info("Credentials: FEAST_ACCESS_TOKEN (environment)");
18516
+ console.info(`Organization: ${acting.organizationId}`);
18517
+ console.info(`Role: ${acting.roleLabel}`);
18518
+ console.info(
18519
+ "The token is opaque to this CLI, so the user and their other organizations cannot be listed."
18520
+ );
18521
+ return;
18522
+ }
18034
18523
  const tokens = await ensureFreshTokens();
18035
18524
  const payload = decodeJwtPayload(tokens.accessToken.jwtToken);
18036
18525
  const orgRoles = listOrgRoles(tokens.accessToken.jwtToken);
@@ -18172,25 +18661,20 @@ ${issues.join("\n")}
18172
18661
  Run: feast describe ${tool.id}`
18173
18662
  );
18174
18663
  }
18175
- const tokens = await ensureFreshTokens();
18176
- const isWrite = tool.type === "mutation" || tool.needsApproval;
18177
- const organizationId = resolveOrganizationId(
18178
- tokens.accessToken.jwtToken,
18179
- args.flags["org"]
18180
- );
18181
- const resolved = resolvePreferredRole(
18182
- tokens.accessToken.jwtToken,
18183
- organizationId
18664
+ const acting = await resolveActingContext(
18665
+ args.flags["org"],
18666
+ args.flags["role"]
18184
18667
  );
18668
+ const isWrite = tool.type === "mutation" || tool.needsApproval;
18185
18669
  console.error(
18186
- `Acting on organization ${organizationId} as ${resolved.orgRole.role}`
18670
+ `Acting on organization ${acting.organizationId} as ${acting.roleLabel}`
18187
18671
  );
18188
18672
  const client = createHttpCaller({
18189
- accessToken: tokens.accessToken.jwtToken,
18190
- preferredRole: resolved.arn
18673
+ accessToken: acting.accessToken,
18674
+ preferredRole: acting.preferredRole
18191
18675
  });
18192
18676
  if (isWrite) {
18193
- const organization = await verifyOrganization(client, organizationId);
18677
+ const organization = await verifyOrganization(client, acting.organizationId);
18194
18678
  if (args.flags["yes"] !== true) {
18195
18679
  const orgLabel = organization.name != null ? `"${organization.name}" (${organization.id})` : organization.id;
18196
18680
  const approved = await promptConfirm(
@@ -18218,7 +18702,7 @@ async function runCli(argv) {
18218
18702
  console.info("Logged out");
18219
18703
  break;
18220
18704
  case "whoami":
18221
- await commandWhoami();
18705
+ await commandWhoami(args);
18222
18706
  break;
18223
18707
  case "tools":
18224
18708
  commandTools(args);
@@ -18237,11 +18721,121 @@ async function runCli(argv) {
18237
18721
  }
18238
18722
  }
18239
18723
 
18724
+ // src/updateNotifier.ts
18725
+ import fs3 from "fs";
18726
+ import { createRequire as createRequire2 } from "module";
18727
+ import os2 from "os";
18728
+ import path2 from "path";
18729
+ var PACKAGE_NAME = "@feastalytics/cli";
18730
+ var REGISTRY_URL = "https://registry.npmjs.org";
18731
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
18732
+ var FETCH_TIMEOUT_MS = 1500;
18733
+ var CACHE_DIR = path2.join(os2.homedir(), ".config", "feast-cli");
18734
+ var CACHE_PATH = path2.join(CACHE_DIR, "update-check.json");
18735
+ function readPackageVersion2() {
18736
+ try {
18737
+ const require2 = createRequire2(import.meta.url);
18738
+ const version = require2("../package.json").version;
18739
+ return typeof version === "string" ? version : void 0;
18740
+ } catch {
18741
+ return void 0;
18742
+ }
18743
+ }
18744
+ function parseVersion(version) {
18745
+ const parts = version.split(".");
18746
+ if (parts.length !== 3) {
18747
+ return void 0;
18748
+ }
18749
+ const numbers = parts.map((part) => Number(part));
18750
+ return numbers.some((number) => !Number.isInteger(number) || number < 0) ? void 0 : numbers;
18751
+ }
18752
+ function isNewer(latest, current) {
18753
+ const a = parseVersion(latest);
18754
+ const b = parseVersion(current);
18755
+ if (a == null || b == null) {
18756
+ return false;
18757
+ }
18758
+ for (let i = 0; i < 3; i++) {
18759
+ if (a[i] !== b[i]) {
18760
+ return a[i] > b[i];
18761
+ }
18762
+ }
18763
+ return false;
18764
+ }
18765
+ function isDisabled() {
18766
+ return process.env.FEAST_NO_UPDATE_CHECK != null || process.env.NO_UPDATE_NOTIFIER != null || process.env.CI != null || !process.stderr.isTTY;
18767
+ }
18768
+ function readCache() {
18769
+ try {
18770
+ const parsed = JSON.parse(
18771
+ fs3.readFileSync(CACHE_PATH, "utf-8")
18772
+ );
18773
+ return typeof parsed.latest === "string" && typeof parsed.checkedAt === "number" ? parsed : void 0;
18774
+ } catch {
18775
+ return void 0;
18776
+ }
18777
+ }
18778
+ function writeCache(cache) {
18779
+ try {
18780
+ fs3.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
18781
+ fs3.writeFileSync(CACHE_PATH, JSON.stringify(cache));
18782
+ } catch {
18783
+ return;
18784
+ }
18785
+ }
18786
+ async function fetchLatestVersion() {
18787
+ try {
18788
+ const response = await fetch(
18789
+ `${REGISTRY_URL}/${encodeURIComponent(PACKAGE_NAME)}/latest`,
18790
+ {
18791
+ headers: { accept: "application/json" },
18792
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
18793
+ }
18794
+ );
18795
+ if (!response.ok) {
18796
+ return void 0;
18797
+ }
18798
+ const body = await response.json();
18799
+ return typeof body.version === "string" ? body.version : void 0;
18800
+ } catch {
18801
+ return void 0;
18802
+ }
18803
+ }
18804
+ async function notifyIfOutdated() {
18805
+ if (isDisabled()) {
18806
+ return;
18807
+ }
18808
+ const current = readPackageVersion2();
18809
+ if (current == null) {
18810
+ return;
18811
+ }
18812
+ const cached = readCache();
18813
+ let latest = cached?.latest;
18814
+ if (cached == null || Date.now() - cached.checkedAt > CHECK_INTERVAL_MS) {
18815
+ const fetched = await fetchLatestVersion();
18816
+ if (fetched != null) {
18817
+ latest = fetched;
18818
+ writeCache({ checkedAt: Date.now(), latest: fetched });
18819
+ } else {
18820
+ writeCache({ checkedAt: Date.now(), latest: latest ?? current });
18821
+ }
18822
+ }
18823
+ if (latest == null || !isNewer(latest, current)) {
18824
+ return;
18825
+ }
18826
+ console.error(
18827
+ `
18828
+ Update available: feast ${current} \u2192 ${latest}
18829
+ npx ${PACKAGE_NAME}@latest <command> (or: npm install -g ${PACKAGE_NAME}@latest)
18830
+ `
18831
+ );
18832
+ }
18833
+
18240
18834
  // src/cli.ts
18241
18835
  runCli(process.argv.slice(2)).catch((error) => {
18242
18836
  console.error(errorMessage(error));
18243
18837
  process.exitCode = 1;
18244
- });
18838
+ }).then(() => notifyIfOutdated()).catch(() => void 0);
18245
18839
  /*! Bundled license information:
18246
18840
 
18247
18841
  @trpc/client/dist/httpUtils-b9d0cb48.mjs: