@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 +15 -0
- package/dist/cli.js +551 -69
- package/feast/SKILL.md +23 -3
- package/package.json +1 -1
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(
|
|
3225
|
-
let input =
|
|
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 [
|
|
3478
|
-
wsComponent.path =
|
|
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,
|
|
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
|
-
...
|
|
6846
|
+
...path3,
|
|
6844
6847
|
key
|
|
6845
6848
|
]);
|
|
6846
6849
|
},
|
|
6847
6850
|
apply(_1, _2, args) {
|
|
6848
|
-
const isApply =
|
|
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 ?
|
|
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
|
|
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:
|
|
7272
|
+
path: path3,
|
|
7270
7273
|
inputs
|
|
7271
7274
|
});
|
|
7272
7275
|
return url.length <= maxURLLength;
|
|
7273
7276
|
};
|
|
7274
|
-
const
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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(
|
|
7445
|
+
query(path3, input, opts) {
|
|
7443
7446
|
return this.requestAsPromise({
|
|
7444
7447
|
type: "query",
|
|
7445
|
-
path:
|
|
7448
|
+
path: path3,
|
|
7446
7449
|
input,
|
|
7447
7450
|
context: opts?.context,
|
|
7448
7451
|
signal: opts?.signal
|
|
7449
7452
|
});
|
|
7450
7453
|
}
|
|
7451
|
-
mutation(
|
|
7454
|
+
mutation(path3, input, opts) {
|
|
7452
7455
|
return this.requestAsPromise({
|
|
7453
7456
|
type: "mutation",
|
|
7454
|
-
path:
|
|
7457
|
+
path: path3,
|
|
7455
7458
|
input,
|
|
7456
7459
|
context: opts?.context,
|
|
7457
7460
|
signal: opts?.signal
|
|
7458
7461
|
});
|
|
7459
7462
|
}
|
|
7460
|
-
subscription(
|
|
7463
|
+
subscription(path3, input, opts) {
|
|
7461
7464
|
const observable$ = this.$request({
|
|
7462
7465
|
type: "subscription",
|
|
7463
|
-
path:
|
|
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:
|
|
7538
|
+
return createRecursiveProxy(({ path: path3, args }) => {
|
|
7536
7539
|
const pathCopy = [
|
|
7537
7540
|
key,
|
|
7538
|
-
...
|
|
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
|
|
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:
|
|
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 = (
|
|
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(
|
|
8114
|
-
if (includes(
|
|
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(
|
|
8120
|
+
if (includes(path3, "prototype")) {
|
|
8118
8121
|
throw new Error("prototype is not allowed as a property");
|
|
8119
8122
|
}
|
|
8120
|
-
if (includes(
|
|
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,
|
|
8125
|
-
validatePath(
|
|
8126
|
-
for (let i = 0; i <
|
|
8127
|
-
const key =
|
|
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 = +
|
|
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,
|
|
8149
|
-
validatePath(
|
|
8150
|
-
if (
|
|
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 <
|
|
8155
|
-
const key =
|
|
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 ===
|
|
8168
|
+
const isEnd = i === path3.length - 2;
|
|
8166
8169
|
if (isEnd) {
|
|
8167
8170
|
break;
|
|
8168
8171
|
}
|
|
8169
8172
|
const row = +key;
|
|
8170
|
-
const type = +
|
|
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 =
|
|
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 = +
|
|
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,
|
|
8245
|
-
plain = setDeep(plain,
|
|
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,
|
|
8252
|
-
const object = getDeep(plain, parsePath(
|
|
8253
|
-
identicalPaths.map((
|
|
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,
|
|
8274
|
+
function addIdentity(object, path3, identities) {
|
|
8272
8275
|
const existingSet = identities.get(object);
|
|
8273
8276
|
if (existingSet) {
|
|
8274
|
-
existingSet.push(
|
|
8277
|
+
existingSet.push(path3);
|
|
8275
8278
|
} else {
|
|
8276
|
-
identities.set(object, [
|
|
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((
|
|
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,
|
|
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,
|
|
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, [...
|
|
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 @
|
|
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
|
|
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
|
|
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.
|
|
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": {
|