@wayai/cli 0.3.135 → 0.3.137
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/index.js +2890 -300
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -236,7 +236,7 @@ var init_mask_secrets = __esm({
|
|
|
236
236
|
"use strict";
|
|
237
237
|
CREDENTIAL_KEY_RE = /token|secret|credential|password|passwd|api[_-]?key|authorization|private[_-]?key|session[_-]?id|cookie|jwt/i;
|
|
238
238
|
NUMERIC_CREDENTIAL_KEY_RE = /(?:password|passwd|pin|token|secret|credential|api[_-]?key|authorization|private[_-]?key|session[_-]?id|cookie|jwt)$/i;
|
|
239
|
-
FULLY_MASKED_VALUE_RE = /^(?:(?:Bearer|Basic)\s+)?(?:way_|wst_)?\[REDACTED(?:_JWT|_BASE64)?\]$/;
|
|
239
|
+
FULLY_MASKED_VALUE_RE = /^(?:(?:Bearer|Basic)\s+)?(?:way_|wst_|rec_)?\[REDACTED(?:_JWT|_BASE64)?\]$/;
|
|
240
240
|
isCredentialKey = (key) => CREDENTIAL_KEY_RE.test(key);
|
|
241
241
|
isBase64UrlChar = (char) => {
|
|
242
242
|
const code = char.charCodeAt(0);
|
|
@@ -249,6 +249,12 @@ var init_mask_secrets = __esm({
|
|
|
249
249
|
[/\bway_[A-Za-z0-9_-]{8,}/g, "way_[REDACTED]"],
|
|
250
250
|
// wst_ single-use WebSocket tickets (same opaque base64url format).
|
|
251
251
|
[/\bwst_[A-Za-z0-9_-]{8,}/g, "wst_[REDACTED]"],
|
|
252
|
+
// rec_ Data-surface API tokens. Same opaque base64url shape, and needed for
|
|
253
|
+
// the same reason as way_: the credential-KEY rules below only fire when the
|
|
254
|
+
// token sits behind a recognisable key, so a `rec_` token that appears bare in
|
|
255
|
+
// an error body's prose would otherwise pass through unmasked. Unpadded
|
|
256
|
+
// base64url, so the trailing base64 rule does not cover it either.
|
|
257
|
+
[/\brec_[A-Za-z0-9_-]{8,}/g, "rec_[REDACTED]"],
|
|
252
258
|
// Stripe API keys and webhook signing secrets. Keep this aligned with
|
|
253
259
|
// packages/core/src/redact.ts; both modules protect observability sinks.
|
|
254
260
|
[
|
|
@@ -7546,6 +7552,20 @@ var init_contracts = __esm({
|
|
|
7546
7552
|
production_bases: external_exports.boolean(),
|
|
7547
7553
|
/** Opaque to Rekor; compared for equality only, to reset `usage:` counters (§2.3). */
|
|
7548
7554
|
window_key: external_exports.string().min(1),
|
|
7555
|
+
/**
|
|
7556
|
+
* The window's TRUE lower bound, for the reconcile's `operations_log` sum (§2.2).
|
|
7557
|
+
*
|
|
7558
|
+
* OPTIONAL, and that is not a rollout convenience — it is what "never gates" means
|
|
7559
|
+
* on the writer's side. The tier sources can be absent (a paid row carrying
|
|
7560
|
+
* `current_period_end` with no `current_period_start`) and inverted pairs are a
|
|
7561
|
+
* §2.2 diagnostic, so requiring it here would let an inert field throw the strict
|
|
7562
|
+
* parse and leave the org with NO projection — permanently stale under §2.3, with
|
|
7563
|
+
* §2.5's fail-closed gates refusing base creation and 503-ing every import. The
|
|
7564
|
+
* writer emits it whenever it has a sound value and omits it otherwise; Rekor's
|
|
7565
|
+
* observed-flip fallback covers the gap, and rollout step 3 keeps that fallback
|
|
7566
|
+
* for exactly this reason.
|
|
7567
|
+
*/
|
|
7568
|
+
window_start: external_exports.string().datetime().optional(),
|
|
7549
7569
|
/** The sole staleness authority (§2.3). Never triggers a counter reset. */
|
|
7550
7570
|
window_resets_at: external_exports.string().datetime()
|
|
7551
7571
|
}).strict();
|
|
@@ -8700,7 +8720,8 @@ __export(api_client_exports, {
|
|
|
8700
8720
|
ApiClient: () => ApiClient,
|
|
8701
8721
|
ApiError: () => ApiError,
|
|
8702
8722
|
dataErrorMessage: () => dataErrorMessage,
|
|
8703
|
-
toDataProxyPath: () => toDataProxyPath
|
|
8723
|
+
toDataProxyPath: () => toDataProxyPath,
|
|
8724
|
+
withOrgSelector: () => withOrgSelector
|
|
8704
8725
|
});
|
|
8705
8726
|
function isRetryableErrorBody(body) {
|
|
8706
8727
|
try {
|
|
@@ -8716,6 +8737,11 @@ function toDataProxyPath(v1Path) {
|
|
|
8716
8737
|
}
|
|
8717
8738
|
return `${DATA_PROXY_PREFIX}${v1Path.slice(REKOR_V1_PREFIX.length)}`;
|
|
8718
8739
|
}
|
|
8740
|
+
function withOrgSelector(path31, orgId) {
|
|
8741
|
+
if (!orgId) return path31;
|
|
8742
|
+
const separator = path31.includes("?") ? "&" : "?";
|
|
8743
|
+
return `${path31}${separator}${DATA_PROXY_ORG_QUERY_PARAM}=${encodeURIComponent(orgId)}`;
|
|
8744
|
+
}
|
|
8719
8745
|
function dataErrorMessage(err) {
|
|
8720
8746
|
if (!(err instanceof ApiError)) return null;
|
|
8721
8747
|
let parsed;
|
|
@@ -9328,12 +9354,12 @@ var init_api_client = __esm({
|
|
|
9328
9354
|
`/api/admin/data-explorer/debug/observability/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}${qs}`
|
|
9329
9355
|
);
|
|
9330
9356
|
}
|
|
9331
|
-
async request(method, path31, body) {
|
|
9357
|
+
async request(method, path31, body, extraHeaders, contentType) {
|
|
9332
9358
|
addApiBreadcrumb(method, path31);
|
|
9333
9359
|
const url = `${this.apiUrl}${path31}`;
|
|
9334
9360
|
let refreshedOn401 = false;
|
|
9335
9361
|
for (let retry = 0; ; retry++) {
|
|
9336
|
-
let response = await this.send(url, method, body);
|
|
9362
|
+
let response = await this.send(url, method, body, extraHeaders, contentType);
|
|
9337
9363
|
if (response.status === 401 && this.onUnauthorized && !refreshedOn401) {
|
|
9338
9364
|
refreshedOn401 = true;
|
|
9339
9365
|
let refreshed;
|
|
@@ -9343,7 +9369,7 @@ var init_api_client = __esm({
|
|
|
9343
9369
|
}
|
|
9344
9370
|
if (refreshed) {
|
|
9345
9371
|
this.accessToken = refreshed;
|
|
9346
|
-
response = await this.send(url, method, body);
|
|
9372
|
+
response = await this.send(url, method, body, extraHeaders, contentType);
|
|
9347
9373
|
}
|
|
9348
9374
|
}
|
|
9349
9375
|
if (response.ok) {
|
|
@@ -9384,30 +9410,98 @@ var init_api_client = __esm({
|
|
|
9384
9410
|
* since it is WayAI's selector and not an input Rekor sees.
|
|
9385
9411
|
*/
|
|
9386
9412
|
async dataRequest(method, v1Path, body, orgId) {
|
|
9387
|
-
const separator = v1Path.includes("?") ? "&" : "?";
|
|
9388
|
-
const orgQuery = orgId ? `${separator}${DATA_PROXY_ORG_QUERY_PARAM}=${encodeURIComponent(orgId)}` : "";
|
|
9389
9413
|
const envelope = await this.request(
|
|
9390
9414
|
method,
|
|
9391
|
-
|
|
9415
|
+
withOrgSelector(toDataProxyPath(v1Path), orgId),
|
|
9392
9416
|
body
|
|
9393
9417
|
);
|
|
9394
9418
|
return envelope ?? {};
|
|
9395
9419
|
}
|
|
9420
|
+
/**
|
|
9421
|
+
* Upload raw bytes to a Data `/v1` path — file content and record
|
|
9422
|
+
* attachments, the two places that surface takes a body which is not JSON.
|
|
9423
|
+
* `extraHeaders` carries the surface's own metadata headers, which are
|
|
9424
|
+
* caller-supplied and so cannot displace the org selector or anything `send`
|
|
9425
|
+
* pins.
|
|
9426
|
+
*/
|
|
9427
|
+
async dataUpload(v1Path, bytes, contentType, orgId, extraHeaders) {
|
|
9428
|
+
const envelope = await this.request(
|
|
9429
|
+
"PUT",
|
|
9430
|
+
withOrgSelector(toDataProxyPath(v1Path), orgId),
|
|
9431
|
+
bytes,
|
|
9432
|
+
extraHeaders,
|
|
9433
|
+
// NOT a header on the map above: `send` pins the content type out of
|
|
9434
|
+
// caller reach, and takes it here so it always describes the real body.
|
|
9435
|
+
contentType
|
|
9436
|
+
);
|
|
9437
|
+
return envelope ?? {};
|
|
9438
|
+
}
|
|
9439
|
+
/**
|
|
9440
|
+
* Download a Data `/v1` path's raw bytes. Distinct from `dataRequest`, which
|
|
9441
|
+
* parses the JSON envelope: the response here is the stored blob itself, so
|
|
9442
|
+
* this reads `arrayBuffer()`. Single-refresh-on-401 like `request`, but no
|
|
9443
|
+
* transient-retry loop — re-streaming a blob is not worth a cold-start probe.
|
|
9444
|
+
*/
|
|
9445
|
+
async dataDownload(v1Path, orgId) {
|
|
9446
|
+
const path31 = withOrgSelector(toDataProxyPath(v1Path), orgId);
|
|
9447
|
+
addApiBreadcrumb("GET", path31);
|
|
9448
|
+
const url = `${this.apiUrl}${path31}`;
|
|
9449
|
+
let response = await this.send(url, "GET");
|
|
9450
|
+
if (response.status === 401 && this.onUnauthorized) {
|
|
9451
|
+
let refreshed;
|
|
9452
|
+
try {
|
|
9453
|
+
refreshed = await this.onUnauthorized();
|
|
9454
|
+
} catch {
|
|
9455
|
+
}
|
|
9456
|
+
if (refreshed) {
|
|
9457
|
+
this.accessToken = refreshed;
|
|
9458
|
+
response = await this.send(url, "GET");
|
|
9459
|
+
}
|
|
9460
|
+
}
|
|
9461
|
+
if (!response.ok) {
|
|
9462
|
+
throw new ApiError("GET", path31, response.status, await response.text());
|
|
9463
|
+
}
|
|
9464
|
+
return {
|
|
9465
|
+
bytes: new Uint8Array(await response.arrayBuffer()),
|
|
9466
|
+
contentType: response.headers.get("Content-Type") ?? "application/octet-stream"
|
|
9467
|
+
};
|
|
9468
|
+
}
|
|
9469
|
+
/**
|
|
9470
|
+
* The absolute URL of a Data `/v1` path, for handing the user a link they can
|
|
9471
|
+
* fetch themselves. Only the address — every request the CLI issues goes
|
|
9472
|
+
* through the methods above, which carry the session.
|
|
9473
|
+
*/
|
|
9474
|
+
dataUrl(v1Path, orgId) {
|
|
9475
|
+
return `${this.apiUrl}${withOrgSelector(toDataProxyPath(v1Path), orgId)}`;
|
|
9476
|
+
}
|
|
9396
9477
|
/** Issue a single authenticated request with the client's current token. */
|
|
9397
|
-
send(url, method, body) {
|
|
9478
|
+
send(url, method, body, extraHeaders, contentType) {
|
|
9398
9479
|
return fetch(url, {
|
|
9399
9480
|
method,
|
|
9400
9481
|
headers: {
|
|
9482
|
+
// Caller-supplied FIRST, so everything pinned below is out of their
|
|
9483
|
+
// reach: a metadata header cannot restate the credential or mislabel
|
|
9484
|
+
// the body.
|
|
9485
|
+
...extraHeaders,
|
|
9401
9486
|
Authorization: `Bearer ${this.accessToken}`,
|
|
9402
|
-
|
|
9403
|
-
//
|
|
9487
|
+
// The content type describes THIS body, so it is derived from the body
|
|
9488
|
+
// rather than accepted from `extraHeaders` — which is pinned out above,
|
|
9489
|
+
// and would otherwise be the one way to mislabel a JSON request.
|
|
9490
|
+
// `||`, not `??`: an empty content type is not a content type, and an
|
|
9491
|
+
// unlabelled body should fall back rather than announce nothing.
|
|
9492
|
+
"Content-Type": body instanceof Uint8Array ? contentType || "application/octet-stream" : "application/json",
|
|
9493
|
+
// Names the calling surface for the Data Worker's audit rows, which
|
|
9494
|
+
// record `cli` vs `api` vs `mcp` (contracts §1.4). A HINT, not an
|
|
9404
9495
|
// authorization input: any client can send it, so the backend must
|
|
9405
9496
|
// normalize it against what it knows about its own ingress rather than
|
|
9406
9497
|
// trust it. Nothing is granted or denied on its value; the Data Worker's
|
|
9407
9498
|
// audit rows are its consumer (contracts §1.4).
|
|
9408
9499
|
"X-WayAI-Client": "cli"
|
|
9409
9500
|
},
|
|
9410
|
-
|
|
9501
|
+
// Bytes go on the wire verbatim (file content, attachments); everything
|
|
9502
|
+
// else is JSON. Encoding a Uint8Array through JSON.stringify would send
|
|
9503
|
+
// `{"0":137,…}` — a corrupt upload rather than a failed one.
|
|
9504
|
+
body: !body ? void 0 : body instanceof Uint8Array ? body : JSON.stringify(body)
|
|
9411
9505
|
});
|
|
9412
9506
|
}
|
|
9413
9507
|
};
|
|
@@ -12849,6 +12943,20 @@ var init_dist = __esm({
|
|
|
12849
12943
|
production_bases: external_exports.boolean(),
|
|
12850
12944
|
/** Opaque to Rekor; compared for equality only, to reset `usage:` counters (§2.3). */
|
|
12851
12945
|
window_key: external_exports.string().min(1),
|
|
12946
|
+
/**
|
|
12947
|
+
* The window's TRUE lower bound, for the reconcile's `operations_log` sum (§2.2).
|
|
12948
|
+
*
|
|
12949
|
+
* OPTIONAL, and that is not a rollout convenience — it is what "never gates" means
|
|
12950
|
+
* on the writer's side. The tier sources can be absent (a paid row carrying
|
|
12951
|
+
* `current_period_end` with no `current_period_start`) and inverted pairs are a
|
|
12952
|
+
* §2.2 diagnostic, so requiring it here would let an inert field throw the strict
|
|
12953
|
+
* parse and leave the org with NO projection — permanently stale under §2.3, with
|
|
12954
|
+
* §2.5's fail-closed gates refusing base creation and 503-ing every import. The
|
|
12955
|
+
* writer emits it whenever it has a sound value and omits it otherwise; Rekor's
|
|
12956
|
+
* observed-flip fallback covers the gap, and rollout step 3 keeps that fallback
|
|
12957
|
+
* for exactly this reason.
|
|
12958
|
+
*/
|
|
12959
|
+
window_start: external_exports.string().datetime().optional(),
|
|
12852
12960
|
/** The sole staleness authority (§2.3). Never triggers a counter reset. */
|
|
12853
12961
|
window_resets_at: external_exports.string().datetime()
|
|
12854
12962
|
}).strict();
|
|
@@ -13979,6 +14087,18 @@ var init_dist = __esm({
|
|
|
13979
14087
|
}
|
|
13980
14088
|
});
|
|
13981
14089
|
|
|
14090
|
+
// src/lib/expected.ts
|
|
14091
|
+
function expected(message) {
|
|
14092
|
+
const err = new Error(message);
|
|
14093
|
+
err.isExpected = true;
|
|
14094
|
+
return err;
|
|
14095
|
+
}
|
|
14096
|
+
var init_expected = __esm({
|
|
14097
|
+
"src/lib/expected.ts"() {
|
|
14098
|
+
"use strict";
|
|
14099
|
+
}
|
|
14100
|
+
});
|
|
14101
|
+
|
|
13982
14102
|
// src/lib/layout.ts
|
|
13983
14103
|
import * as fs from "fs";
|
|
13984
14104
|
import * as path from "path";
|
|
@@ -14700,11 +14820,31 @@ function isNewerVersion(latest, current) {
|
|
|
14700
14820
|
}
|
|
14701
14821
|
return false;
|
|
14702
14822
|
}
|
|
14823
|
+
function resolveSecretSource(source) {
|
|
14824
|
+
if (source.stdin && source.prompt) {
|
|
14825
|
+
throw expected(`${source.stdinFlag} and ${source.promptHint} are mutually exclusive \u2014 pass one.`);
|
|
14826
|
+
}
|
|
14827
|
+
if (source.stdin) {
|
|
14828
|
+
if (process.stdin.isTTY) {
|
|
14829
|
+
throw expected(
|
|
14830
|
+
`${source.stdinFlag} needs piped input. Pipe it \u2014 printf %s "$SECRET" | wayai \u2026 ${source.stdinFlag} \u2014 or ${source.promptHint}.`
|
|
14831
|
+
);
|
|
14832
|
+
}
|
|
14833
|
+
return "stdin";
|
|
14834
|
+
}
|
|
14835
|
+
if (!process.stdin.isTTY) {
|
|
14836
|
+
throw expected(
|
|
14837
|
+
`A secret cannot be prompted for when stdin is not a terminal. Pipe it instead: printf %s "$SECRET" | wayai \u2026 ${source.stdinFlag}`
|
|
14838
|
+
);
|
|
14839
|
+
}
|
|
14840
|
+
return "prompt";
|
|
14841
|
+
}
|
|
14703
14842
|
var UUID_RE2, slugify, OWN_HELP_COMMANDS;
|
|
14704
14843
|
var init_utils = __esm({
|
|
14705
14844
|
"src/lib/utils.ts"() {
|
|
14706
14845
|
"use strict";
|
|
14707
14846
|
init_dist();
|
|
14847
|
+
init_expected();
|
|
14708
14848
|
UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
14709
14849
|
slugify = slugifySkillName;
|
|
14710
14850
|
OWN_HELP_COMMANDS = /* @__PURE__ */ new Set([
|
|
@@ -14789,7 +14929,7 @@ var init_registry = __esm({
|
|
|
14789
14929
|
disposition: "top-level",
|
|
14790
14930
|
surviving: "records",
|
|
14791
14931
|
clause: 2,
|
|
14792
|
-
shipped:
|
|
14932
|
+
shipped: true,
|
|
14793
14933
|
reason: "Ontology entity. No WayAI command of this name."
|
|
14794
14934
|
},
|
|
14795
14935
|
{
|
|
@@ -14797,7 +14937,7 @@ var init_registry = __esm({
|
|
|
14797
14937
|
disposition: "top-level",
|
|
14798
14938
|
surviving: "record-types",
|
|
14799
14939
|
clause: 2,
|
|
14800
|
-
shipped:
|
|
14940
|
+
shipped: true,
|
|
14801
14941
|
reason: "Ontology entity."
|
|
14802
14942
|
},
|
|
14803
14943
|
{
|
|
@@ -14805,7 +14945,7 @@ var init_registry = __esm({
|
|
|
14805
14945
|
disposition: "top-level",
|
|
14806
14946
|
surviving: "relationships",
|
|
14807
14947
|
clause: 2,
|
|
14808
|
-
shipped:
|
|
14948
|
+
shipped: true,
|
|
14809
14949
|
reason: "Ontology entity."
|
|
14810
14950
|
},
|
|
14811
14951
|
{
|
|
@@ -14813,7 +14953,7 @@ var init_registry = __esm({
|
|
|
14813
14953
|
disposition: "top-level",
|
|
14814
14954
|
surviving: "relationship-types",
|
|
14815
14955
|
clause: 2,
|
|
14816
|
-
shipped:
|
|
14956
|
+
shipped: true,
|
|
14817
14957
|
reason: "Ontology entity."
|
|
14818
14958
|
},
|
|
14819
14959
|
{
|
|
@@ -14821,7 +14961,7 @@ var init_registry = __esm({
|
|
|
14821
14961
|
disposition: "top-level",
|
|
14822
14962
|
surviving: "query-relationships",
|
|
14823
14963
|
clause: 2,
|
|
14824
|
-
shipped:
|
|
14964
|
+
shipped: true,
|
|
14825
14965
|
reason: "Ontology traversal over relationships."
|
|
14826
14966
|
},
|
|
14827
14967
|
{
|
|
@@ -14829,7 +14969,7 @@ var init_registry = __esm({
|
|
|
14829
14969
|
disposition: "top-level",
|
|
14830
14970
|
surviving: "files",
|
|
14831
14971
|
clause: 2,
|
|
14832
|
-
shipped:
|
|
14972
|
+
shipped: true,
|
|
14833
14973
|
reason: "Unambiguous: WayAI has no `files` command \u2014 hub-local resource files are reached through hub config-as-code, and the two file surfaces stay deliberately separate."
|
|
14834
14974
|
},
|
|
14835
14975
|
{
|
|
@@ -14837,7 +14977,7 @@ var init_registry = __esm({
|
|
|
14837
14977
|
disposition: "top-level",
|
|
14838
14978
|
surviving: "file-types",
|
|
14839
14979
|
clause: 2,
|
|
14840
|
-
shipped:
|
|
14980
|
+
shipped: true,
|
|
14841
14981
|
reason: "Ontology entity."
|
|
14842
14982
|
},
|
|
14843
14983
|
{
|
|
@@ -14845,7 +14985,7 @@ var init_registry = __esm({
|
|
|
14845
14985
|
disposition: "top-level",
|
|
14846
14986
|
surviving: "attachments",
|
|
14847
14987
|
clause: 2,
|
|
14848
|
-
shipped:
|
|
14988
|
+
shipped: true,
|
|
14849
14989
|
reason: "Ontology entity."
|
|
14850
14990
|
},
|
|
14851
14991
|
{
|
|
@@ -14853,7 +14993,7 @@ var init_registry = __esm({
|
|
|
14853
14993
|
disposition: "top-level",
|
|
14854
14994
|
surviving: "toolsets",
|
|
14855
14995
|
clause: 2,
|
|
14856
|
-
shipped:
|
|
14996
|
+
shipped: true,
|
|
14857
14997
|
reason: "Ontology entity."
|
|
14858
14998
|
},
|
|
14859
14999
|
{
|
|
@@ -14861,7 +15001,7 @@ var init_registry = __esm({
|
|
|
14861
15001
|
disposition: "top-level",
|
|
14862
15002
|
surviving: "actions",
|
|
14863
15003
|
clause: 2,
|
|
14864
|
-
shipped:
|
|
15004
|
+
shipped: true,
|
|
14865
15005
|
reason: "Ontology entity."
|
|
14866
15006
|
},
|
|
14867
15007
|
{
|
|
@@ -14869,7 +15009,7 @@ var init_registry = __esm({
|
|
|
14869
15009
|
disposition: "top-level",
|
|
14870
15010
|
surviving: "triggers",
|
|
14871
15011
|
clause: 2,
|
|
14872
|
-
shipped:
|
|
15012
|
+
shipped: true,
|
|
14873
15013
|
reason: "Ontology entity."
|
|
14874
15014
|
},
|
|
14875
15015
|
{
|
|
@@ -14877,7 +15017,7 @@ var init_registry = __esm({
|
|
|
14877
15017
|
disposition: "top-level",
|
|
14878
15018
|
surviving: "inbound-webhooks",
|
|
14879
15019
|
clause: 2,
|
|
14880
|
-
shipped:
|
|
15020
|
+
shipped: true,
|
|
14881
15021
|
reason: "Ontology entity."
|
|
14882
15022
|
},
|
|
14883
15023
|
{
|
|
@@ -14885,7 +15025,7 @@ var init_registry = __esm({
|
|
|
14885
15025
|
disposition: "top-level",
|
|
14886
15026
|
surviving: "seed",
|
|
14887
15027
|
clause: 2,
|
|
14888
|
-
shipped:
|
|
15028
|
+
shipped: true,
|
|
14889
15029
|
reason: "Ontology entity. `wayai status` vs `wayai seed lease status` differ by depth, not by word."
|
|
14890
15030
|
},
|
|
14891
15031
|
// --- Clause 3: namespaced. ---
|
|
@@ -14918,7 +15058,7 @@ var init_registry = __esm({
|
|
|
14918
15058
|
disposition: "namespaced",
|
|
14919
15059
|
surviving: "bases tokens",
|
|
14920
15060
|
clause: 3,
|
|
14921
|
-
shipped:
|
|
15061
|
+
shipped: true,
|
|
14922
15062
|
reason: "Mints base tokens; WayAI's own token surface mints platform tokens."
|
|
14923
15063
|
},
|
|
14924
15064
|
{
|
|
@@ -14926,7 +15066,7 @@ var init_registry = __esm({
|
|
|
14926
15066
|
disposition: "namespaced",
|
|
14927
15067
|
surviving: "bases secrets",
|
|
14928
15068
|
clause: 3,
|
|
14929
|
-
shipped:
|
|
15069
|
+
shipped: true,
|
|
14930
15070
|
reason: "Base org vault; WayAI has connection credentials."
|
|
14931
15071
|
},
|
|
14932
15072
|
{
|
|
@@ -14934,7 +15074,7 @@ var init_registry = __esm({
|
|
|
14934
15074
|
disposition: "namespaced",
|
|
14935
15075
|
surviving: "bases sql",
|
|
14936
15076
|
clause: 3,
|
|
14937
|
-
shipped:
|
|
15077
|
+
shipped: true,
|
|
14938
15078
|
reason: "WayAI already has tenant SQL over conversation analytics."
|
|
14939
15079
|
},
|
|
14940
15080
|
{
|
|
@@ -14942,7 +15082,7 @@ var init_registry = __esm({
|
|
|
14942
15082
|
disposition: "namespaced",
|
|
14943
15083
|
surviving: "bases import",
|
|
14944
15084
|
clause: 3,
|
|
14945
|
-
shipped:
|
|
15085
|
+
shipped: true,
|
|
14946
15086
|
reason: "WayAI has outbound-contact import."
|
|
14947
15087
|
},
|
|
14948
15088
|
{
|
|
@@ -14950,7 +15090,7 @@ var init_registry = __esm({
|
|
|
14950
15090
|
disposition: "namespaced",
|
|
14951
15091
|
surviving: "bases batch",
|
|
14952
15092
|
clause: 3,
|
|
14953
|
-
shipped:
|
|
15093
|
+
shipped: true,
|
|
14954
15094
|
reason: "Too generic to stand alone."
|
|
14955
15095
|
},
|
|
14956
15096
|
{
|
|
@@ -14958,7 +15098,7 @@ var init_registry = __esm({
|
|
|
14958
15098
|
disposition: "namespaced",
|
|
14959
15099
|
surviving: "bases providers",
|
|
14960
15100
|
clause: 3,
|
|
14961
|
-
shipped:
|
|
15101
|
+
shipped: true,
|
|
14962
15102
|
reason: "WayAI's provider is a connector."
|
|
14963
15103
|
},
|
|
14964
15104
|
{
|
|
@@ -14967,14 +15107,14 @@ var init_registry = __esm({
|
|
|
14967
15107
|
surviving: "admin bases",
|
|
14968
15108
|
clause: 3,
|
|
14969
15109
|
shipped: false,
|
|
14970
|
-
reason: "Clause 3's group exception: `wayai admin` is an existing group with its own gating and subgroups, so the operator surface joins it as a peer rather than sitting under `bases`."
|
|
15110
|
+
reason: "Clause 3's group exception: `wayai admin` is an existing group with its own gating and subgroups, so the operator surface joins it as a peer rather than sitting under `bases`. UNSHIPPED pending a transport: the Data backend's operator routes live at `/admin/*`, a SIBLING of `/v1/*`, and the merged CLI reaches Data only through WayAI's `/api/data` proxy, which contains every forwarded path under `/v1` by construction (`rekor/upstream-url.ts`). The standalone CLI could call them because it held a second `api_url` pointed straight at the Data backend \u2014 the thing the merge deliberately removed. Shipping needs a proxy channel for operator routes plus platform-admin gating on it, which is a backend change, not a CLI one."
|
|
14971
15111
|
},
|
|
14972
15112
|
{
|
|
14973
15113
|
original: "report",
|
|
14974
15114
|
disposition: "namespaced",
|
|
14975
15115
|
surviving: "bases report",
|
|
14976
15116
|
clause: 3,
|
|
14977
|
-
shipped:
|
|
15117
|
+
shipped: true,
|
|
14978
15118
|
reason: "Two report queues still exist, so the merged CLI must be able to file to either. The two collapse when the queues do."
|
|
14979
15119
|
},
|
|
14980
15120
|
// --- Neither: one verb each, routing by workspace subtree. ---
|
|
@@ -15457,18 +15597,6 @@ var init_token_store = __esm({
|
|
|
15457
15597
|
}
|
|
15458
15598
|
});
|
|
15459
15599
|
|
|
15460
|
-
// src/lib/expected.ts
|
|
15461
|
-
function expected(message) {
|
|
15462
|
-
const err = new Error(message);
|
|
15463
|
-
err.isExpected = true;
|
|
15464
|
-
return err;
|
|
15465
|
-
}
|
|
15466
|
-
var init_expected = __esm({
|
|
15467
|
-
"src/lib/expected.ts"() {
|
|
15468
|
-
"use strict";
|
|
15469
|
-
}
|
|
15470
|
-
});
|
|
15471
|
-
|
|
15472
15600
|
// src/lib/auth.ts
|
|
15473
15601
|
var auth_exports = {};
|
|
15474
15602
|
__export(auth_exports, {
|
|
@@ -16746,15 +16874,15 @@ function scanResourceFiles(dir, prefix = "") {
|
|
|
16746
16874
|
files.push(...scanResourceFiles(path12.join(dir, entry.name), relPath));
|
|
16747
16875
|
} else if (entry.isFile()) {
|
|
16748
16876
|
const fullPath = path12.join(dir, entry.name);
|
|
16749
|
-
const
|
|
16750
|
-
if (
|
|
16751
|
-
console.warn(` Warning: skipping ${relPath} (${(
|
|
16877
|
+
const stat2 = fs10.statSync(fullPath);
|
|
16878
|
+
if (stat2.size > MAX_RESOURCE_FILE_SIZE3) {
|
|
16879
|
+
console.warn(` Warning: skipping ${relPath} (${(stat2.size / 1024 / 1024).toFixed(1)}MB exceeds 10MB limit)`);
|
|
16752
16880
|
continue;
|
|
16753
16881
|
}
|
|
16754
16882
|
const fileEntry = {
|
|
16755
16883
|
path: relPath,
|
|
16756
16884
|
mime_type: guessMimeType(entry.name),
|
|
16757
|
-
file_size:
|
|
16885
|
+
file_size: stat2.size
|
|
16758
16886
|
};
|
|
16759
16887
|
const data = fs10.readFileSync(fullPath);
|
|
16760
16888
|
fileEntry.hash = computeHash(data);
|
|
@@ -16946,34 +17074,34 @@ function resolveTurnAttachments(hubFolder, turn, label, bytesByHash) {
|
|
|
16946
17074
|
if (abs !== root && !abs.startsWith(root + path13.sep)) {
|
|
16947
17075
|
throw expected(`${label}: attachment "${entry}" escapes the hub folder.`);
|
|
16948
17076
|
}
|
|
16949
|
-
let
|
|
17077
|
+
let stat2;
|
|
16950
17078
|
try {
|
|
16951
|
-
|
|
17079
|
+
stat2 = fs11.statSync(abs);
|
|
16952
17080
|
} catch {
|
|
16953
17081
|
}
|
|
16954
|
-
if (!
|
|
17082
|
+
if (!stat2?.isFile()) {
|
|
16955
17083
|
throw expected(`${label}: attachment "${entry}" not found.`);
|
|
16956
17084
|
}
|
|
16957
17085
|
if (!realpathContained(root, abs)) {
|
|
16958
17086
|
throw expected(`${label}: attachment "${entry}" resolves outside the hub folder (symlinks are not allowed).`);
|
|
16959
17087
|
}
|
|
16960
|
-
if (
|
|
17088
|
+
if (stat2.size > MAX_RESOURCE_FILE_SIZE3) {
|
|
16961
17089
|
throw expected(
|
|
16962
|
-
`${label}: attachment "${entry}" is ${(
|
|
17090
|
+
`${label}: attachment "${entry}" is ${(stat2.size / 1024 / 1024).toFixed(1)}MB, over the 10MB limit.`
|
|
16963
17091
|
);
|
|
16964
17092
|
}
|
|
16965
17093
|
const data = fs11.readFileSync(abs);
|
|
16966
17094
|
const hash = computeHash(data);
|
|
16967
17095
|
const fileName = path13.basename(abs);
|
|
16968
17096
|
const mimeType = guessMimeType(fileName) ?? "application/octet-stream";
|
|
16969
|
-
refs.push({ hash, file_name: fileName, mime_type: mimeType, file_size:
|
|
17097
|
+
refs.push({ hash, file_name: fileName, mime_type: mimeType, file_size: stat2.size });
|
|
16970
17098
|
if (!bytesByHash.has(hash)) {
|
|
16971
17099
|
bytesByHash.set(hash, {
|
|
16972
17100
|
hash,
|
|
16973
17101
|
content_base64: data.toString("base64"),
|
|
16974
17102
|
file_name: fileName,
|
|
16975
17103
|
mime_type: mimeType,
|
|
16976
|
-
file_size:
|
|
17104
|
+
file_size: stat2.size
|
|
16977
17105
|
});
|
|
16978
17106
|
}
|
|
16979
17107
|
}
|
|
@@ -17011,7 +17139,7 @@ function rewriteEvalAttachmentsToLocalPaths(payload) {
|
|
|
17011
17139
|
return base === "" || base === "." || base === ".." ? ref.hash.slice(0, 16) : base;
|
|
17012
17140
|
};
|
|
17013
17141
|
const hashesByName = /* @__PURE__ */ new Map();
|
|
17014
|
-
const
|
|
17142
|
+
const collect2 = (turn) => {
|
|
17015
17143
|
if (!Array.isArray(turn.attachments)) return;
|
|
17016
17144
|
for (const ref of turn.attachments) {
|
|
17017
17145
|
if (!ref?.hash || !ref.file_name) continue;
|
|
@@ -17024,7 +17152,7 @@ function rewriteEvalAttachmentsToLocalPaths(payload) {
|
|
|
17024
17152
|
set.add(ref.hash);
|
|
17025
17153
|
}
|
|
17026
17154
|
};
|
|
17027
|
-
forEachAttachableTurn(payload,
|
|
17155
|
+
forEachAttachableTurn(payload, collect2);
|
|
17028
17156
|
const pathByKey = /* @__PURE__ */ new Map();
|
|
17029
17157
|
const usedLower = /* @__PURE__ */ new Set();
|
|
17030
17158
|
const localPathFor = (ref) => {
|
|
@@ -19274,18 +19402,18 @@ __export(send_message_exports, {
|
|
|
19274
19402
|
import * as fs19 from "fs";
|
|
19275
19403
|
import * as path25 from "path";
|
|
19276
19404
|
function statAttachment(filePath) {
|
|
19277
|
-
let
|
|
19405
|
+
let stat2;
|
|
19278
19406
|
try {
|
|
19279
|
-
|
|
19407
|
+
stat2 = fs19.statSync(filePath);
|
|
19280
19408
|
} catch {
|
|
19281
19409
|
console.error(`Error: file not found: ${filePath}`);
|
|
19282
19410
|
process.exit(1);
|
|
19283
19411
|
}
|
|
19284
|
-
if (
|
|
19412
|
+
if (stat2.isDirectory()) {
|
|
19285
19413
|
console.error(`Error: not a file: ${filePath}`);
|
|
19286
19414
|
process.exit(1);
|
|
19287
19415
|
}
|
|
19288
|
-
return { filePath, size:
|
|
19416
|
+
return { filePath, size: stat2.size };
|
|
19289
19417
|
}
|
|
19290
19418
|
function readAttachment(filePath, size) {
|
|
19291
19419
|
const fileName = path25.basename(filePath);
|
|
@@ -20552,7 +20680,7 @@ async function stopEvalSessionWithRetries(stop, opts = {}) {
|
|
|
20552
20680
|
const attempts = opts.attempts ?? STOP_ATTEMPTS;
|
|
20553
20681
|
const attemptTimeoutMs = opts.attemptTimeoutMs ?? STOP_ATTEMPT_TIMEOUT_MS;
|
|
20554
20682
|
const backoffMs = opts.backoffMs ?? STOP_BACKOFF_MS;
|
|
20555
|
-
const
|
|
20683
|
+
const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
20556
20684
|
let lastError = new Error("Stop was never attempted");
|
|
20557
20685
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
20558
20686
|
let timeout;
|
|
@@ -20573,7 +20701,7 @@ async function stopEvalSessionWithRetries(stop, opts = {}) {
|
|
|
20573
20701
|
if (timeout !== void 0) clearTimeout(timeout);
|
|
20574
20702
|
}
|
|
20575
20703
|
const delay2 = backoffMs[attempt - 1];
|
|
20576
|
-
if (attempt < attempts && delay2) await
|
|
20704
|
+
if (attempt < attempts && delay2) await sleep3(delay2);
|
|
20577
20705
|
}
|
|
20578
20706
|
throw lastError;
|
|
20579
20707
|
}
|
|
@@ -22512,11 +22640,19 @@ async function updateCredentialCommand(args2) {
|
|
|
22512
22640
|
const body = {};
|
|
22513
22641
|
if (wantsSecret) {
|
|
22514
22642
|
let input;
|
|
22515
|
-
|
|
22516
|
-
|
|
22517
|
-
|
|
22518
|
-
|
|
22519
|
-
|
|
22643
|
+
let secretSource;
|
|
22644
|
+
try {
|
|
22645
|
+
secretSource = resolveSecretSource({
|
|
22646
|
+
stdin: parsed.stdin,
|
|
22647
|
+
prompt: parsed.secretPrompt,
|
|
22648
|
+
stdinFlag: "--stdin",
|
|
22649
|
+
promptHint: "use --secret for an interactive masked prompt"
|
|
22650
|
+
});
|
|
22651
|
+
} catch (err) {
|
|
22652
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
22653
|
+
process.exit(1);
|
|
22654
|
+
}
|
|
22655
|
+
if (secretSource === "stdin") {
|
|
22520
22656
|
input = await readStdin();
|
|
22521
22657
|
} else if (authType === "basic_auth") {
|
|
22522
22658
|
const username = await prompt("Enter username: ");
|
|
@@ -24851,8 +24987,8 @@ async function createDataClient(orgId) {
|
|
|
24851
24987
|
}
|
|
24852
24988
|
const org = selected ?? readRepoConfig()?.organization_id;
|
|
24853
24989
|
return {
|
|
24854
|
-
async request(method,
|
|
24855
|
-
const envelope = await api.dataRequest(method,
|
|
24990
|
+
async request(method, path31, body) {
|
|
24991
|
+
const envelope = await api.dataRequest(method, path31, body, org);
|
|
24856
24992
|
return envelope.data;
|
|
24857
24993
|
},
|
|
24858
24994
|
async collectPages(makePath) {
|
|
@@ -24868,9 +25004,36 @@ async function createDataClient(orgId) {
|
|
|
24868
25004
|
seen.add(cursor);
|
|
24869
25005
|
}
|
|
24870
25006
|
return all;
|
|
25007
|
+
},
|
|
25008
|
+
async upload(v1Path, bytes, contentType, headers) {
|
|
25009
|
+
const envelope = await api.dataUpload(v1Path, bytes, contentType, org, headers);
|
|
25010
|
+
return envelope.data;
|
|
25011
|
+
},
|
|
25012
|
+
download(v1Path) {
|
|
25013
|
+
return api.dataDownload(v1Path, org);
|
|
25014
|
+
},
|
|
25015
|
+
url(v1Path) {
|
|
25016
|
+
return api.dataUrl(v1Path, org);
|
|
24871
25017
|
}
|
|
24872
25018
|
};
|
|
24873
25019
|
}
|
|
25020
|
+
function dataErrorEnvelope(err) {
|
|
25021
|
+
if (!(err instanceof ApiError)) return null;
|
|
25022
|
+
try {
|
|
25023
|
+
const parsed = JSON.parse(err.body);
|
|
25024
|
+
return parsed?.error ?? null;
|
|
25025
|
+
} catch {
|
|
25026
|
+
return null;
|
|
25027
|
+
}
|
|
25028
|
+
}
|
|
25029
|
+
function dataErrorCode(err) {
|
|
25030
|
+
const code = dataErrorEnvelope(err)?.code;
|
|
25031
|
+
return typeof code === "string" && code ? code : null;
|
|
25032
|
+
}
|
|
25033
|
+
function dataErrorDetails(err) {
|
|
25034
|
+
const details = dataErrorEnvelope(err)?.details;
|
|
25035
|
+
return details && typeof details === "object" ? details : void 0;
|
|
25036
|
+
}
|
|
24874
25037
|
var MAX_PAGES;
|
|
24875
25038
|
var init_client = __esm({
|
|
24876
25039
|
"src/data/client.ts"() {
|
|
@@ -24940,6 +25103,36 @@ function printFields(obj) {
|
|
|
24940
25103
|
console.log(`${key.padEnd(width)} ${formatCell2(value)}`);
|
|
24941
25104
|
}
|
|
24942
25105
|
}
|
|
25106
|
+
function printBatchResult(response) {
|
|
25107
|
+
const obj = response && typeof response === "object" ? response : {};
|
|
25108
|
+
const results = Array.isArray(obj.results) ? obj.results : [];
|
|
25109
|
+
const batchId = typeof obj.batch_id === "string" ? obj.batch_id : "(none)";
|
|
25110
|
+
console.log(
|
|
25111
|
+
`batch_id: ${sanitizeTerminalText(batchId)} \xB7 ${results.length} operation${results.length === 1 ? "" : "s"}`
|
|
25112
|
+
);
|
|
25113
|
+
if (results.length === 0) return;
|
|
25114
|
+
printRows(
|
|
25115
|
+
results.map((r, i) => ({
|
|
25116
|
+
"#": String(i),
|
|
25117
|
+
operation: r?.type ?? "",
|
|
25118
|
+
result: summarizeBatchResult(r)
|
|
25119
|
+
}))
|
|
25120
|
+
);
|
|
25121
|
+
}
|
|
25122
|
+
function summarizeBatchResult(r) {
|
|
25123
|
+
if (!r || typeof r !== "object") return String(r ?? "");
|
|
25124
|
+
if (r.deleted === true) return "deleted";
|
|
25125
|
+
const entity = r.record ?? r.relationship ?? r.record_type;
|
|
25126
|
+
if (entity && typeof entity === "object") {
|
|
25127
|
+
const e = entity;
|
|
25128
|
+
const id = typeof e.id === "string" ? e.id : void 0;
|
|
25129
|
+
const label = typeof e.record_type === "string" ? e.record_type : typeof e.rel_type === "string" ? e.rel_type : typeof e.name === "string" ? e.name : void 0;
|
|
25130
|
+
const version = e.version != null ? ` v${String(e.version)}` : "";
|
|
25131
|
+
if (id && label) return `${label}/${id}${version}`;
|
|
25132
|
+
if (id) return `${id}${version}`;
|
|
25133
|
+
}
|
|
25134
|
+
return JSON.stringify(r);
|
|
25135
|
+
}
|
|
24943
25136
|
function cellAt(row, column) {
|
|
24944
25137
|
return formatCell2(row?.[column]);
|
|
24945
25138
|
}
|
|
@@ -24977,313 +25170,2672 @@ function pathSegment(id, label = "id") {
|
|
|
24977
25170
|
}
|
|
24978
25171
|
return encodeURIComponent(id);
|
|
24979
25172
|
}
|
|
24980
|
-
function
|
|
24981
|
-
if (
|
|
24982
|
-
|
|
25173
|
+
function foreignSegment(value, label = "id") {
|
|
25174
|
+
if (value === "." || value === "..") {
|
|
25175
|
+
throw expected(`Invalid ${label}: ${JSON.stringify(value)} is a path traversal segment.`);
|
|
25176
|
+
}
|
|
25177
|
+
return encodeURIComponent(value);
|
|
25178
|
+
}
|
|
25179
|
+
function pathSegments(value, label = "path") {
|
|
25180
|
+
return value.split("/").map((segment) => foreignSegment(segment, label)).join("/");
|
|
25181
|
+
}
|
|
25182
|
+
function parseData(data, flag) {
|
|
25183
|
+
const prefix = flag ? `${flag}: ` : "";
|
|
25184
|
+
const source = data.startsWith("@") ? data.slice(1) : void 0;
|
|
25185
|
+
let text = data;
|
|
25186
|
+
if (source !== void 0) {
|
|
25187
|
+
try {
|
|
25188
|
+
text = readFileSync19(source, "utf-8");
|
|
25189
|
+
} catch (e) {
|
|
25190
|
+
throw expected(`${prefix}${source}: ${e instanceof Error ? e.message : "could not be read"}`);
|
|
25191
|
+
}
|
|
25192
|
+
}
|
|
25193
|
+
try {
|
|
25194
|
+
return JSON.parse(text);
|
|
25195
|
+
} catch (e) {
|
|
25196
|
+
const where = source !== void 0 ? ` in ${source}` : "";
|
|
25197
|
+
throw expected(`${prefix}invalid JSON${where}: ${e instanceof Error ? e.message : String(e)}`);
|
|
24983
25198
|
}
|
|
24984
|
-
|
|
25199
|
+
}
|
|
25200
|
+
function toolsetMcpUrl(slug) {
|
|
25201
|
+
return `https://data-mcp.wayai.pro/t/${slug}/mcp`;
|
|
24985
25202
|
}
|
|
24986
25203
|
function globals(cmd) {
|
|
24987
25204
|
let namespace = cmd;
|
|
24988
25205
|
while (namespace.parent?.parent) namespace = namespace.parent;
|
|
24989
25206
|
return namespace.opts();
|
|
24990
25207
|
}
|
|
25208
|
+
function withBaseOption(command2) {
|
|
25209
|
+
return command2.option("--base <id>", "Base id (or set WAYAI_BASE)");
|
|
25210
|
+
}
|
|
25211
|
+
function baseOptionHolder(cmd) {
|
|
25212
|
+
for (let c = cmd; c; c = c.parent ?? void 0) {
|
|
25213
|
+
if (c.options.some((o) => o.long === "--base")) return c;
|
|
25214
|
+
}
|
|
25215
|
+
return void 0;
|
|
25216
|
+
}
|
|
25217
|
+
function findBase(cmd) {
|
|
25218
|
+
return baseOptionHolder(cmd)?.opts()?.base;
|
|
25219
|
+
}
|
|
25220
|
+
function requireBase(cmd) {
|
|
25221
|
+
const base = findBase(cmd) ?? process.env.WAYAI_BASE ?? "";
|
|
25222
|
+
if (base) return base;
|
|
25223
|
+
console.error("Error: --base is required (or set WAYAI_BASE)");
|
|
25224
|
+
return process.exit(1);
|
|
25225
|
+
}
|
|
24991
25226
|
function outputFormat(cmd) {
|
|
24992
25227
|
const opts = globals(cmd);
|
|
24993
25228
|
return opts.json || opts.output === "json" ? "json" : "table";
|
|
24994
25229
|
}
|
|
25230
|
+
function historyQuery(opts) {
|
|
25231
|
+
const qs = new URLSearchParams();
|
|
25232
|
+
if (opts.limit) qs.set("limit", opts.limit);
|
|
25233
|
+
if (opts.offset) qs.set("offset", opts.offset);
|
|
25234
|
+
if (opts.diff) qs.set("diff", "true");
|
|
25235
|
+
const q = qs.toString();
|
|
25236
|
+
return q ? `?${q}` : "";
|
|
25237
|
+
}
|
|
24995
25238
|
function splitList(value) {
|
|
24996
25239
|
return value.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
|
|
24997
25240
|
}
|
|
25241
|
+
function parseByteCount(flag, value) {
|
|
25242
|
+
const n = Number(value);
|
|
25243
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
25244
|
+
throw expected(`${flag} must be a whole number of bytes (0 = unlimited), not ${JSON.stringify(value)}.`);
|
|
25245
|
+
}
|
|
25246
|
+
return n;
|
|
25247
|
+
}
|
|
25248
|
+
async function readSecret(source, label) {
|
|
25249
|
+
const value = resolveSecretSource(source) === "stdin" ? (await readStdin()).trim() : (await promptSecret(`${label}: `)).trim();
|
|
25250
|
+
if (!value) throw expected(`${label} is required.`);
|
|
25251
|
+
return value;
|
|
25252
|
+
}
|
|
25253
|
+
function isInteractive() {
|
|
25254
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
25255
|
+
}
|
|
25256
|
+
function parseDuration(input, flag = "duration") {
|
|
25257
|
+
const match = DURATION_RE.exec(input.trim());
|
|
25258
|
+
const value = Number(match?.[1] ?? 0);
|
|
25259
|
+
const unit = match?.[2];
|
|
25260
|
+
const unitMs = unit ? DURATION_UNIT_MS[unit] : void 0;
|
|
25261
|
+
if (!unitMs || value <= 0) {
|
|
25262
|
+
throw expected(
|
|
25263
|
+
`Invalid ${flag} ${JSON.stringify(input)} \u2014 use <number><unit> with unit s/m/h/d (e.g. 10m, 2h, 30d).`
|
|
25264
|
+
);
|
|
25265
|
+
}
|
|
25266
|
+
return value * unitMs;
|
|
25267
|
+
}
|
|
25268
|
+
var DURATION_RE, DURATION_UNIT_MS;
|
|
24998
25269
|
var init_helpers = __esm({
|
|
24999
25270
|
"src/data/helpers.ts"() {
|
|
25000
25271
|
"use strict";
|
|
25001
25272
|
init_base_id();
|
|
25002
25273
|
init_expected();
|
|
25274
|
+
init_utils();
|
|
25275
|
+
DURATION_RE = /^(\d+)([smhd])$/;
|
|
25276
|
+
DURATION_UNIT_MS = {
|
|
25277
|
+
s: 1e3,
|
|
25278
|
+
m: 6e4,
|
|
25279
|
+
h: 36e5,
|
|
25280
|
+
d: 864e5
|
|
25281
|
+
};
|
|
25003
25282
|
}
|
|
25004
25283
|
});
|
|
25005
25284
|
|
|
25006
|
-
// src/
|
|
25007
|
-
|
|
25008
|
-
|
|
25009
|
-
"
|
|
25285
|
+
// src/data/commands/actions.ts
|
|
25286
|
+
import { Command } from "commander";
|
|
25287
|
+
function buildActionsCommand() {
|
|
25288
|
+
const actions = new Command("actions").description(
|
|
25289
|
+
"Manage Actions \u2014 reusable, named definitions: one record_type + one operation, or a composite of atomic write steps (--config with `steps`)"
|
|
25290
|
+
);
|
|
25291
|
+
actions.command("upsert <id>").description(
|
|
25292
|
+
"Create or update an Action \u2014 one record_type + one operation, or a composite (--config with `steps`)"
|
|
25293
|
+
).option(
|
|
25294
|
+
"--record-type <record_type>",
|
|
25295
|
+
"The record_type this action operates over (single-op; omit for a composite)"
|
|
25296
|
+
).option(
|
|
25297
|
+
"--operation <op>",
|
|
25298
|
+
"The operation: create | get | list | update | delete (single-op; omit for a composite)"
|
|
25299
|
+
).option("--description <desc>", "Agent-facing description").option(
|
|
25300
|
+
"--config <json>",
|
|
25301
|
+
'Full Action config as JSON. Single-op: writable_fields, filterable_fields (per field: field (required), param, match, enum, pattern, optional, description \u2014 a declared field generates a required param unless optional: true; a range match is always optional), base_filter (a Filter DSL predicate merged server-side into every list call, invisible to the agent; list only, over a native record_type or a source-backed one whose list endpoint declares local_filters; the value "$now" resolves to the instant of each call, on a date-time field with a range op), precondition, binding, external_source, expose_*/default_* (machinery params are hidden unless expose_<param>: true). Composite: `steps` \u2014 an ordered list of atomic write steps ({key, record_type|rel_type, operation, writable_fields?, precondition?}) run in one transaction. Use @file.json to read from file.'
|
|
25302
|
+
).action(async function(id, opts) {
|
|
25303
|
+
let body;
|
|
25304
|
+
if (opts.config) {
|
|
25305
|
+
body = parseData(opts.config);
|
|
25306
|
+
if (opts.recordType) body.record_type = opts.recordType;
|
|
25307
|
+
if (opts.operation) body.operation = opts.operation;
|
|
25308
|
+
if (opts.description) body.description = opts.description;
|
|
25309
|
+
} else {
|
|
25310
|
+
if (!opts.recordType || !opts.operation) {
|
|
25311
|
+
throw expected("Provide --record-type and --operation (or --config <json>).");
|
|
25312
|
+
}
|
|
25313
|
+
body = { record_type: opts.recordType, operation: opts.operation };
|
|
25314
|
+
if (opts.description) body.description = opts.description;
|
|
25315
|
+
}
|
|
25316
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25317
|
+
const client = await createDataClient();
|
|
25318
|
+
printOutput(
|
|
25319
|
+
await client.request("PUT", `/v1/${base}/actions/${pathSegment(id, "action id")}`, body),
|
|
25320
|
+
outputFormat(this)
|
|
25321
|
+
);
|
|
25322
|
+
});
|
|
25323
|
+
actions.command("get <id>").description("Get an Action by id").action(async function(id) {
|
|
25324
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25325
|
+
const client = await createDataClient();
|
|
25326
|
+
printOutput(await client.request("GET", `/v1/${base}/actions/${pathSegment(id, "action id")}`), outputFormat(this));
|
|
25327
|
+
});
|
|
25328
|
+
actions.command("list").description("List Actions in this base").action(async function() {
|
|
25329
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25330
|
+
const client = await createDataClient();
|
|
25331
|
+
printOutput(await client.request("GET", `/v1/${base}/actions`), outputFormat(this));
|
|
25332
|
+
});
|
|
25333
|
+
actions.command("delete <id>").description("Delete an Action").option("-y, --yes", "Skip confirmation").action(async function(id, opts) {
|
|
25334
|
+
if (!opts.yes && !await confirm(`Delete action ${id}? This cannot be undone.`)) {
|
|
25335
|
+
console.log("Aborted.");
|
|
25336
|
+
return;
|
|
25337
|
+
}
|
|
25338
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25339
|
+
const client = await createDataClient();
|
|
25340
|
+
await client.request("DELETE", `/v1/${base}/actions/${pathSegment(id, "action id")}`);
|
|
25341
|
+
console.log(`Deleted action ${id}`);
|
|
25342
|
+
});
|
|
25343
|
+
return actions;
|
|
25344
|
+
}
|
|
25345
|
+
var init_actions = __esm({
|
|
25346
|
+
"src/data/commands/actions.ts"() {
|
|
25010
25347
|
"use strict";
|
|
25011
|
-
|
|
25012
|
-
|
|
25013
|
-
|
|
25014
|
-
|
|
25015
|
-
|
|
25016
|
-
noun: "base",
|
|
25017
|
-
idLabel: "base id (must be a slug)",
|
|
25018
|
-
unbindCommand: "wayai bases unbind",
|
|
25019
|
-
useCommand: "wayai bases use"
|
|
25020
|
-
});
|
|
25021
|
-
getBaseBindingPath = binding2.getBindingPath;
|
|
25022
|
-
readBaseBinding = binding2.readBinding;
|
|
25023
|
-
writeBaseBinding = binding2.writeBinding;
|
|
25024
|
-
clearBaseBinding = binding2.clearBinding;
|
|
25025
|
-
autoBindBaseIfUnbound = binding2.autoBindIfUnbound;
|
|
25026
|
-
assertBaseMatchesBinding = binding2.assertMatchesBinding;
|
|
25027
|
-
assertNoBindingBlocksBaseCreation = binding2.assertNoBindingBlocksCreation;
|
|
25348
|
+
init_expected();
|
|
25349
|
+
init_client();
|
|
25350
|
+
init_output();
|
|
25351
|
+
init_helpers();
|
|
25352
|
+
init_utils();
|
|
25028
25353
|
}
|
|
25029
25354
|
});
|
|
25030
25355
|
|
|
25031
|
-
// src/data/commands/
|
|
25032
|
-
import { Command } from "commander";
|
|
25033
|
-
import
|
|
25034
|
-
|
|
25035
|
-
|
|
25036
|
-
function pageOf(path31, cursor) {
|
|
25037
|
-
if (!cursor) return path31;
|
|
25038
|
-
return `${path31}${path31.includes("?") ? "&" : "?"}cursor=${encodeURIComponent(cursor)}`;
|
|
25356
|
+
// src/data/commands/attachments.ts
|
|
25357
|
+
import { Command as Command2 } from "commander";
|
|
25358
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
25359
|
+
function findAttachmentByFilename(attachments, filename) {
|
|
25360
|
+
return attachments.find((a) => a.key.endsWith(`/${filename}`)) ?? null;
|
|
25039
25361
|
}
|
|
25040
|
-
function
|
|
25041
|
-
if (
|
|
25042
|
-
|
|
25043
|
-
|
|
25362
|
+
function uploadPathFrom(uploadUrl) {
|
|
25363
|
+
if (typeof uploadUrl !== "string") {
|
|
25364
|
+
throw expected(
|
|
25365
|
+
`The server returned an upload target this CLI will not send credentials to: ${String(uploadUrl)}`
|
|
25366
|
+
);
|
|
25044
25367
|
}
|
|
25045
|
-
|
|
25368
|
+
let resolved;
|
|
25369
|
+
try {
|
|
25370
|
+
resolved = new URL(toDataProxyPath(uploadUrl), CONTAINMENT_ORIGIN);
|
|
25371
|
+
} catch {
|
|
25372
|
+
throw expected(
|
|
25373
|
+
`The server returned an upload target this CLI will not send credentials to: ${uploadUrl}`
|
|
25374
|
+
);
|
|
25375
|
+
}
|
|
25376
|
+
if (resolved.origin !== CONTAINMENT_ORIGIN || !resolved.pathname.startsWith(`${DATA_PROXY_PREFIX}/`)) {
|
|
25377
|
+
throw expected(
|
|
25378
|
+
`The server returned an upload target this CLI will not send credentials to: ${uploadUrl}`
|
|
25379
|
+
);
|
|
25380
|
+
}
|
|
25381
|
+
return uploadUrl;
|
|
25046
25382
|
}
|
|
25047
|
-
function
|
|
25048
|
-
const
|
|
25049
|
-
|
|
25383
|
+
function buildAttachmentsCommand() {
|
|
25384
|
+
const attachments = new Command2("attachments").description("Manage record attachments");
|
|
25385
|
+
attachments.command("upload <record_type> <id>").description(
|
|
25386
|
+
"Get a presigned upload URL for a record attachment. With --file, uploads the file content in one step."
|
|
25387
|
+
).requiredOption("--filename <name>", "File name or path (e.g. docs/guide.md)").option("--content-type <type>", "MIME type", "application/octet-stream").option("--file <path>", "Local file to upload (skips presigned URL output, uploads directly)").action(async function(recordType2, id, opts) {
|
|
25388
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25050
25389
|
const client = await createDataClient();
|
|
25051
|
-
const
|
|
25390
|
+
const data = await client.request(
|
|
25391
|
+
"POST",
|
|
25392
|
+
`/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(id, "record id")}/attachments`,
|
|
25393
|
+
{ filename: opts.filename, content_type: opts.contentType }
|
|
25394
|
+
);
|
|
25395
|
+
if (!opts.file) {
|
|
25396
|
+
printOutput(data, outputFormat(this));
|
|
25397
|
+
return;
|
|
25398
|
+
}
|
|
25399
|
+
const body = readFileSync20(opts.file);
|
|
25400
|
+
await client.upload(uploadPathFrom(data?.upload_url), body, opts.contentType);
|
|
25401
|
+
printOutput({ ...data, uploaded: true }, outputFormat(this));
|
|
25402
|
+
});
|
|
25403
|
+
attachments.command("url <record_type> <id>").description("Get a download URL for an attachment by filename").requiredOption("--filename <name>", "File name or path (e.g. docs/guide.md)").action(async function(recordType2, id, opts) {
|
|
25404
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25405
|
+
const client = await createDataClient();
|
|
25406
|
+
const rows = await client.request(
|
|
25407
|
+
"GET",
|
|
25408
|
+
`/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(id, "record id")}/attachments`
|
|
25409
|
+
);
|
|
25410
|
+
const match = findAttachmentByFilename(rows ?? [], opts.filename);
|
|
25411
|
+
if (!match) {
|
|
25412
|
+
throw expected(`Attachment "${opts.filename}" not found`);
|
|
25413
|
+
}
|
|
25052
25414
|
printOutput(
|
|
25053
|
-
|
|
25415
|
+
{ download_url: client.url(`/v1/${base}/attachments/${pathSegments(match.key, "attachment key")}`), ...match },
|
|
25054
25416
|
outputFormat(this)
|
|
25055
25417
|
);
|
|
25056
25418
|
});
|
|
25057
|
-
|
|
25419
|
+
attachments.command("list <record_type> <id>").description("List attachments for a record").option("--prefix <path>", "Filter by path prefix (e.g. docs/)").action(async function(recordType2, id, opts) {
|
|
25420
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25058
25421
|
const client = await createDataClient();
|
|
25059
|
-
|
|
25422
|
+
const query = opts.prefix ? `?prefix=${encodeURIComponent(opts.prefix)}` : "";
|
|
25423
|
+
printOutput(
|
|
25424
|
+
await client.request(
|
|
25425
|
+
"GET",
|
|
25426
|
+
`/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(id, "record id")}/attachments${query}`
|
|
25427
|
+
),
|
|
25428
|
+
outputFormat(this)
|
|
25429
|
+
);
|
|
25060
25430
|
});
|
|
25061
|
-
|
|
25062
|
-
|
|
25063
|
-
|
|
25064
|
-
|
|
25065
|
-
|
|
25066
|
-
|
|
25067
|
-
|
|
25068
|
-
const
|
|
25069
|
-
"production",
|
|
25070
|
-
"preview"
|
|
25071
|
-
]);
|
|
25431
|
+
attachments.command("delete <record_type> <id> <attachment-id>").description("Delete an attachment").option("-y, --yes", "Skip confirmation prompt").action(async function(recordType2, id, attachmentId, opts) {
|
|
25432
|
+
if (!opts.yes && !await confirm(
|
|
25433
|
+
`Delete attachment ${attachmentId} on ${recordType2}/${id}? This cannot be undone.`
|
|
25434
|
+
)) {
|
|
25435
|
+
console.log("Aborted");
|
|
25436
|
+
return;
|
|
25437
|
+
}
|
|
25438
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25072
25439
|
const client = await createDataClient();
|
|
25073
|
-
|
|
25074
|
-
|
|
25075
|
-
|
|
25076
|
-
|
|
25077
|
-
|
|
25440
|
+
await client.request(
|
|
25441
|
+
"DELETE",
|
|
25442
|
+
`/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(id, "record id")}/attachments/${pathSegment(attachmentId, "attachment id")}`
|
|
25443
|
+
);
|
|
25444
|
+
console.log("Deleted");
|
|
25078
25445
|
});
|
|
25079
|
-
|
|
25080
|
-
|
|
25081
|
-
|
|
25082
|
-
|
|
25083
|
-
|
|
25446
|
+
return attachments;
|
|
25447
|
+
}
|
|
25448
|
+
var CONTAINMENT_ORIGIN;
|
|
25449
|
+
var init_attachments = __esm({
|
|
25450
|
+
"src/data/commands/attachments.ts"() {
|
|
25451
|
+
"use strict";
|
|
25452
|
+
init_expected();
|
|
25453
|
+
init_contracts();
|
|
25454
|
+
init_api_client();
|
|
25455
|
+
init_client();
|
|
25456
|
+
init_output();
|
|
25457
|
+
init_helpers();
|
|
25458
|
+
init_utils();
|
|
25459
|
+
CONTAINMENT_ORIGIN = "https://containment.invalid";
|
|
25460
|
+
}
|
|
25461
|
+
});
|
|
25462
|
+
|
|
25463
|
+
// src/data/commands/batch.ts
|
|
25464
|
+
import { Command as Command3 } from "commander";
|
|
25465
|
+
function buildBasesBatchCommand() {
|
|
25466
|
+
return withBaseOption(new Command3("batch")).description("Execute atomic batch operations (up to 1,000 operations)").requiredOption("--operations <json>", "Operations array (inline JSON or @filename)").action(async function(opts) {
|
|
25467
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25468
|
+
const operations = parseData(opts.operations, "--operations");
|
|
25469
|
+
const client = await createDataClient();
|
|
25470
|
+
const data = await client.request("POST", `/v1/${base}/batch`, {
|
|
25471
|
+
operations: Array.isArray(operations) ? operations : [operations]
|
|
25472
|
+
});
|
|
25473
|
+
const format = outputFormat(this);
|
|
25474
|
+
if (format === "json") {
|
|
25475
|
+
printOutput(data, format);
|
|
25476
|
+
return;
|
|
25477
|
+
}
|
|
25478
|
+
printBatchResult(data);
|
|
25479
|
+
});
|
|
25480
|
+
}
|
|
25481
|
+
var init_batch = __esm({
|
|
25482
|
+
"src/data/commands/batch.ts"() {
|
|
25483
|
+
"use strict";
|
|
25484
|
+
init_client();
|
|
25485
|
+
init_output();
|
|
25486
|
+
init_helpers();
|
|
25487
|
+
}
|
|
25488
|
+
});
|
|
25489
|
+
|
|
25490
|
+
// src/data/commands/import.ts
|
|
25491
|
+
import { createReadStream } from "fs";
|
|
25492
|
+
import { access, constants, stat } from "fs/promises";
|
|
25493
|
+
import { createInterface as createInterface3 } from "readline";
|
|
25494
|
+
import { randomUUID } from "crypto";
|
|
25495
|
+
import { Command as Command4 } from "commander";
|
|
25496
|
+
function isRetryable(err) {
|
|
25497
|
+
if (isNetworkError(err)) return true;
|
|
25498
|
+
if (!(err instanceof ApiError)) return false;
|
|
25499
|
+
const code = dataErrorCode(err);
|
|
25500
|
+
if (code && RETRYABLE_CODES.has(code)) return true;
|
|
25501
|
+
return err.status >= 500 && err.status !== 501;
|
|
25502
|
+
}
|
|
25503
|
+
function isExpiredSession(err) {
|
|
25504
|
+
if (!(err instanceof ApiError) || err.status !== 409) return false;
|
|
25505
|
+
if (dataErrorCode(err) !== "IMPORT_SESSION_CLOSED") return false;
|
|
25506
|
+
return dataErrorDetails(err)?.status === "expired";
|
|
25507
|
+
}
|
|
25508
|
+
async function withRetry(state, fn, what) {
|
|
25509
|
+
let lastErr;
|
|
25510
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
25511
|
+
try {
|
|
25512
|
+
return await fn();
|
|
25513
|
+
} catch (err) {
|
|
25514
|
+
lastErr = err;
|
|
25515
|
+
if (!isRetryable(err) || attempt === MAX_ATTEMPTS) throw err;
|
|
25516
|
+
const code = err instanceof ApiError ? dataErrorCode(err) : null;
|
|
25517
|
+
if (!code || !RETRYABLE_CODES.has(code)) state.sawAmbiguous = true;
|
|
25518
|
+
const delay2 = BASE_BACKOFF_MS * 2 ** (attempt - 1);
|
|
25519
|
+
console.error(
|
|
25520
|
+
` ${what}: ${err.message} \u2014 retrying in ${delay2}ms (${attempt}/${MAX_ATTEMPTS - 1})`
|
|
25521
|
+
);
|
|
25522
|
+
await sleep2(delay2);
|
|
25523
|
+
}
|
|
25524
|
+
}
|
|
25525
|
+
throw lastErr;
|
|
25526
|
+
}
|
|
25527
|
+
async function* readChunks(file, size, startChunk) {
|
|
25528
|
+
const rl = createInterface3({ input: createReadStream(file, "utf8"), crlfDelay: Infinity });
|
|
25529
|
+
let rows = [];
|
|
25530
|
+
let index = 0;
|
|
25531
|
+
let lineNo = 0;
|
|
25532
|
+
let inChunk = 0;
|
|
25533
|
+
for await (const line of rl) {
|
|
25534
|
+
lineNo++;
|
|
25535
|
+
const trimmed = line.trim();
|
|
25536
|
+
if (!trimmed) continue;
|
|
25537
|
+
if (index < startChunk) {
|
|
25538
|
+
if (++inChunk === size) {
|
|
25539
|
+
inChunk = 0;
|
|
25540
|
+
index++;
|
|
25541
|
+
}
|
|
25542
|
+
continue;
|
|
25543
|
+
}
|
|
25544
|
+
let parsed;
|
|
25545
|
+
try {
|
|
25546
|
+
parsed = JSON.parse(trimmed);
|
|
25547
|
+
} catch {
|
|
25548
|
+
throw expected(`${file}:${lineNo}: not valid JSON. Each line must be one JSON object.`);
|
|
25549
|
+
}
|
|
25550
|
+
rows.push(parsed);
|
|
25551
|
+
if (rows.length === size) {
|
|
25552
|
+
yield { index, rows };
|
|
25553
|
+
rows = [];
|
|
25554
|
+
index++;
|
|
25555
|
+
}
|
|
25556
|
+
}
|
|
25557
|
+
if (rows.length > 0) yield { index, rows };
|
|
25558
|
+
}
|
|
25559
|
+
async function beginSession(state, client, base, recordType2, requestKey) {
|
|
25560
|
+
const res = await withRetry(
|
|
25561
|
+
state,
|
|
25562
|
+
() => client.request("POST", `/v1/${base}/import/begin`, {
|
|
25563
|
+
request_key: requestKey,
|
|
25564
|
+
record_type: recordType2
|
|
25565
|
+
}),
|
|
25566
|
+
"begin"
|
|
25567
|
+
);
|
|
25568
|
+
return res.session;
|
|
25569
|
+
}
|
|
25570
|
+
function buildBasesImportCommand() {
|
|
25571
|
+
const importCmd = withBaseOption(new Command4("import")).description(
|
|
25572
|
+
"Bulk historical import: load an NDJSON backfill, list sessions, or undo one"
|
|
25573
|
+
);
|
|
25574
|
+
importCmd.command("run <record_type>").description(
|
|
25575
|
+
"Load an NDJSON file into a record type (one JSON object per line, each {external_id, data})"
|
|
25576
|
+
).requiredOption("--file <path>", "NDJSON file to import").option(
|
|
25577
|
+
"--chunk-size <n>",
|
|
25578
|
+
"Rows per request, max 1000. Must match the original run when resuming.",
|
|
25579
|
+
String(MAX_CHUNK_ROWS)
|
|
25084
25580
|
).option(
|
|
25085
|
-
"--
|
|
25086
|
-
"
|
|
25581
|
+
"--request-key <key>",
|
|
25582
|
+
"Idempotency key for this logical run (default: generated). Re-use it to resume."
|
|
25087
25583
|
).option(
|
|
25088
|
-
"--
|
|
25089
|
-
"
|
|
25090
|
-
|
|
25091
|
-
|
|
25092
|
-
|
|
25584
|
+
"--start-chunk <n>",
|
|
25585
|
+
"Skip chunks before this index \u2014 resume a run that stopped partway",
|
|
25586
|
+
"0"
|
|
25587
|
+
).option("--no-finalize", "Leave the session open (resume later with the same --request-key)").action(async function(recordType2, opts) {
|
|
25588
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25589
|
+
const state = { sawAmbiguous: false };
|
|
25590
|
+
const chunkSize = Number(opts.chunkSize);
|
|
25591
|
+
if (!Number.isInteger(chunkSize) || chunkSize < 1 || chunkSize > MAX_CHUNK_ROWS) {
|
|
25592
|
+
throw expected(`--chunk-size must be an integer between 1 and ${MAX_CHUNK_ROWS}.`);
|
|
25593
|
+
}
|
|
25594
|
+
const startChunk = Number(opts.startChunk);
|
|
25595
|
+
if (!Number.isInteger(startChunk) || startChunk < 0) {
|
|
25596
|
+
throw expected("--start-chunk must be a non-negative integer.");
|
|
25597
|
+
}
|
|
25598
|
+
if (startChunk > 0 && !this.getOptionValueSource("chunkSize")?.startsWith("cli")) {
|
|
25093
25599
|
throw expected(
|
|
25094
|
-
"
|
|
25600
|
+
"--start-chunk requires --chunk-size to be given explicitly, and it must match the original run."
|
|
25095
25601
|
);
|
|
25096
25602
|
}
|
|
25603
|
+
try {
|
|
25604
|
+
if ((await stat(opts.file)).isDirectory()) throw new Error("is a directory");
|
|
25605
|
+
await access(opts.file, constants.R_OK);
|
|
25606
|
+
} catch {
|
|
25607
|
+
throw expected(`--file ${opts.file}: not a readable file.`);
|
|
25608
|
+
}
|
|
25097
25609
|
const client = await createDataClient();
|
|
25098
|
-
const
|
|
25099
|
-
if (opts.
|
|
25100
|
-
|
|
25101
|
-
|
|
25102
|
-
|
|
25103
|
-
|
|
25104
|
-
|
|
25105
|
-
|
|
25106
|
-
|
|
25610
|
+
const requestKey = opts.requestKey ?? `cli-${randomUUID()}`;
|
|
25611
|
+
if (!opts.requestKey) {
|
|
25612
|
+
console.error(`request_key: ${requestKey} (pass --request-key to resume this run)`);
|
|
25613
|
+
}
|
|
25614
|
+
let session = await beginSession(state, client, base, recordType2, requestKey);
|
|
25615
|
+
console.error(`import_id: ${session.import_id}`);
|
|
25616
|
+
const importIds = [session.import_id];
|
|
25617
|
+
let written = 0;
|
|
25618
|
+
let skipped = 0;
|
|
25619
|
+
let sent = 0;
|
|
25620
|
+
let nextIndex = startChunk;
|
|
25621
|
+
try {
|
|
25622
|
+
for await (const { index, rows } of readChunks(opts.file, chunkSize, startChunk)) {
|
|
25623
|
+
nextIndex = index;
|
|
25624
|
+
const body = { import_id: session.import_id, chunk_index: index, rows };
|
|
25625
|
+
let result;
|
|
25107
25626
|
try {
|
|
25108
|
-
|
|
25109
|
-
|
|
25110
|
-
|
|
25627
|
+
result = await withRetry(
|
|
25628
|
+
state,
|
|
25629
|
+
() => client.request("POST", `/v1/${base}/import/chunk`, body),
|
|
25630
|
+
`chunk ${index}`
|
|
25631
|
+
);
|
|
25632
|
+
} catch (err) {
|
|
25633
|
+
if (!isExpiredSession(err)) throw err;
|
|
25634
|
+
console.error(
|
|
25635
|
+
` chunk ${index}: session expired (month boundary) \u2014 starting a fresh session and re-sending.`
|
|
25636
|
+
);
|
|
25637
|
+
console.error(
|
|
25638
|
+
` NOTE: this run now spans two import_ids; undo needs both. Previous: ${session.import_id}`
|
|
25639
|
+
);
|
|
25640
|
+
session = await beginSession(state, client, base, recordType2, requestKey);
|
|
25641
|
+
importIds.push(session.import_id);
|
|
25642
|
+
console.error(` import_id: ${session.import_id}`);
|
|
25643
|
+
result = await withRetry(
|
|
25644
|
+
state,
|
|
25645
|
+
() => client.request("POST", `/v1/${base}/import/chunk`, {
|
|
25646
|
+
...body,
|
|
25647
|
+
import_id: session.import_id
|
|
25648
|
+
}),
|
|
25649
|
+
`chunk ${index}`
|
|
25650
|
+
);
|
|
25111
25651
|
}
|
|
25112
|
-
|
|
25113
|
-
|
|
25652
|
+
written += result.rows_written ?? 0;
|
|
25653
|
+
skipped += result.rows_skipped ?? 0;
|
|
25654
|
+
sent++;
|
|
25655
|
+
nextIndex = index + 1;
|
|
25656
|
+
if (sent % 10 === 0) {
|
|
25657
|
+
console.error(` ${sent} chunks \u2014 ${written} written, ${skipped} unchanged`);
|
|
25114
25658
|
}
|
|
25115
|
-
|
|
25659
|
+
}
|
|
25660
|
+
if (opts.finalize) {
|
|
25661
|
+
await withRetry(
|
|
25662
|
+
state,
|
|
25663
|
+
() => client.request(
|
|
25664
|
+
"POST",
|
|
25665
|
+
`/v1/${base}/import/${pathSegment(session.import_id, "import id")}/finalize`
|
|
25666
|
+
),
|
|
25667
|
+
"finalize"
|
|
25668
|
+
);
|
|
25669
|
+
}
|
|
25670
|
+
} catch (err) {
|
|
25671
|
+
console.error(
|
|
25672
|
+
`
|
|
25673
|
+
Stopped at chunk ${nextIndex}. To resume, re-run the SAME command with:
|
|
25674
|
+
--start-chunk ${nextIndex} --chunk-size ${chunkSize} --request-key ${requestKey}
|
|
25675
|
+
(keep every other flag you passed \u2014 the base and org decide where the rows land, and the chunk size decides which rows each index covers.)`
|
|
25676
|
+
);
|
|
25677
|
+
throw err;
|
|
25678
|
+
}
|
|
25679
|
+
printOutput(
|
|
25680
|
+
{
|
|
25681
|
+
import_ids: importIds,
|
|
25682
|
+
rows_written: written,
|
|
25683
|
+
rows_skipped: skipped,
|
|
25684
|
+
chunks_sent: sent,
|
|
25685
|
+
finalized: opts.finalize
|
|
25686
|
+
},
|
|
25687
|
+
outputFormat(this)
|
|
25688
|
+
);
|
|
25689
|
+
});
|
|
25690
|
+
importCmd.command("list").description("List import sessions, newest first \u2014 find a stuck run or the id to undo").option("--limit <n>", "Maximum sessions to return").option("--offset <n>", "Sessions to skip").action(async function(opts) {
|
|
25691
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25692
|
+
const qs = new URLSearchParams();
|
|
25693
|
+
if (opts.limit) qs.set("limit", opts.limit);
|
|
25694
|
+
if (opts.offset) qs.set("offset", opts.offset);
|
|
25695
|
+
const suffix = qs.toString() ? `?${qs}` : "";
|
|
25696
|
+
const client = await createDataClient();
|
|
25697
|
+
printOutput(
|
|
25698
|
+
await client.request("GET", `/v1/${base}/import/sessions${suffix}`),
|
|
25699
|
+
outputFormat(this)
|
|
25700
|
+
);
|
|
25701
|
+
});
|
|
25702
|
+
importCmd.command("rollback <import_id>").description(
|
|
25703
|
+
"Undo an import \u2014 deletes only the rows it CREATED. Re-importing corrected data is usually the better fix."
|
|
25704
|
+
).option("--yes", "Skip the confirmation prompt").action(async function(importId, opts) {
|
|
25705
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25706
|
+
const format = outputFormat(this);
|
|
25707
|
+
const state = { sawAmbiguous: false };
|
|
25708
|
+
const client = await createDataClient();
|
|
25709
|
+
if (!opts.yes && !isInteractive()) {
|
|
25710
|
+
throw expected(
|
|
25711
|
+
"Refusing to roll back an import without confirmation \u2014 pass --yes to run non-interactively."
|
|
25712
|
+
);
|
|
25713
|
+
}
|
|
25714
|
+
if (!opts.yes) {
|
|
25715
|
+
const plan = await client.request("GET", `/v1/${base}/import/${pathSegment(importId, "import id")}/rollback-plan?counts=true`);
|
|
25716
|
+
const n = plan.matched_records ?? 0;
|
|
25717
|
+
if (n === 0) {
|
|
25718
|
+
console.error("No rows left to undo \u2014 closing the import session.");
|
|
25116
25719
|
} else {
|
|
25117
|
-
const
|
|
25118
|
-
|
|
25720
|
+
const ok = await confirm(
|
|
25721
|
+
`Delete ${n} record${n === 1 ? "" : "s"} created by this import into "${sanitizeTerminalText(String(plan.record_type))}", plus every relationship pointing at them?`
|
|
25722
|
+
);
|
|
25723
|
+
if (!ok) {
|
|
25724
|
+
console.error("Aborted.");
|
|
25725
|
+
return;
|
|
25726
|
+
}
|
|
25119
25727
|
}
|
|
25120
|
-
if (opts.timezone !== void 0) settings.timezone = opts.timezone;
|
|
25121
|
-
body.settings = settings;
|
|
25122
25728
|
}
|
|
25123
|
-
|
|
25729
|
+
let records = 0;
|
|
25730
|
+
let relationships = 0;
|
|
25731
|
+
let calls = 0;
|
|
25732
|
+
let incomplete = false;
|
|
25733
|
+
for (let sweep = 0; ; sweep++) {
|
|
25734
|
+
const res = await withRetry(
|
|
25735
|
+
state,
|
|
25736
|
+
() => client.request("POST", `/v1/${base}/import/${pathSegment(importId, "import id")}/rollback`),
|
|
25737
|
+
"rollback"
|
|
25738
|
+
);
|
|
25739
|
+
records += res.swept.records;
|
|
25740
|
+
relationships += res.swept.relationships;
|
|
25741
|
+
calls++;
|
|
25742
|
+
if (res.complete) break;
|
|
25743
|
+
const sweptNothing = res.swept.records === 0 && res.swept.relationships === 0;
|
|
25744
|
+
if (sweptNothing || sweep + 1 >= MAX_ROLLBACK_SWEEPS) {
|
|
25745
|
+
incomplete = true;
|
|
25746
|
+
console.error(
|
|
25747
|
+
sweptNothing ? " the sweep stopped making progress before the server reported it complete \u2014 re-run to continue." : ` stopped after ${MAX_ROLLBACK_SWEEPS} sweeps without completing \u2014 re-run to continue.`
|
|
25748
|
+
);
|
|
25749
|
+
break;
|
|
25750
|
+
}
|
|
25751
|
+
if (format !== "json") {
|
|
25752
|
+
console.error(` swept ${records} records, ${relationships} relationships so far\u2026`);
|
|
25753
|
+
}
|
|
25754
|
+
}
|
|
25755
|
+
if (state.sawAmbiguous && format !== "json") {
|
|
25756
|
+
console.error(
|
|
25757
|
+
"Note: a request was re-sent after its outcome was unknown, so the counts below are a lower bound \u2014 a page that committed before its response was lost is not included."
|
|
25758
|
+
);
|
|
25759
|
+
}
|
|
25760
|
+
printOutput(
|
|
25761
|
+
{
|
|
25762
|
+
import_id: importId,
|
|
25763
|
+
swept: { records, relationships },
|
|
25764
|
+
requests: calls,
|
|
25765
|
+
// The server's own completion claim, carried so a script can tell an
|
|
25766
|
+
// undo that FINISHED from one that stopped early and needs re-running.
|
|
25767
|
+
complete: !incomplete,
|
|
25768
|
+
...state.sawAmbiguous ? { counts_are_lower_bound: true } : {}
|
|
25769
|
+
},
|
|
25770
|
+
format
|
|
25771
|
+
);
|
|
25772
|
+
if (incomplete) return process.exit(1);
|
|
25773
|
+
});
|
|
25774
|
+
return importCmd;
|
|
25775
|
+
}
|
|
25776
|
+
var MAX_CHUNK_ROWS, MAX_ATTEMPTS, MAX_ROLLBACK_SWEEPS, BASE_BACKOFF_MS, RETRYABLE_CODES, sleep2;
|
|
25777
|
+
var init_import = __esm({
|
|
25778
|
+
"src/data/commands/import.ts"() {
|
|
25779
|
+
"use strict";
|
|
25780
|
+
init_client();
|
|
25781
|
+
init_output();
|
|
25782
|
+
init_helpers();
|
|
25783
|
+
init_api_client();
|
|
25784
|
+
init_network_error();
|
|
25785
|
+
init_terminal_output();
|
|
25786
|
+
init_utils();
|
|
25787
|
+
init_expected();
|
|
25788
|
+
MAX_CHUNK_ROWS = 1e3;
|
|
25789
|
+
MAX_ATTEMPTS = 5;
|
|
25790
|
+
MAX_ROLLBACK_SWEEPS = 1e3;
|
|
25791
|
+
BASE_BACKOFF_MS = 500;
|
|
25792
|
+
RETRYABLE_CODES = /* @__PURE__ */ new Set(["BASE_OVERLOADED", "SERVICE_UNAVAILABLE", "RATE_LIMITED"]);
|
|
25793
|
+
sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
25794
|
+
}
|
|
25795
|
+
});
|
|
25796
|
+
|
|
25797
|
+
// src/data/commands/providers.ts
|
|
25798
|
+
import { Command as Command5 } from "commander";
|
|
25799
|
+
import { writeFileSync as writeFileSync15 } from "fs";
|
|
25800
|
+
function providerSegment(provider) {
|
|
25801
|
+
if (!VALID_PROVIDERS.includes(provider)) {
|
|
25802
|
+
throw expected(`Unknown provider ${JSON.stringify(provider)}. Expected one of: ${VALID_PROVIDERS_HELP}.`);
|
|
25803
|
+
}
|
|
25804
|
+
return pathSegment(provider, "provider");
|
|
25805
|
+
}
|
|
25806
|
+
function buildBasesProvidersCommand() {
|
|
25807
|
+
const providers = withBaseOption(new Command5("providers")).description(
|
|
25808
|
+
"Import/export tool definitions between LLM providers and record types"
|
|
25809
|
+
);
|
|
25810
|
+
providers.command("import <provider>").description(`Import tool definitions as record types. Providers: ${VALID_PROVIDERS_HELP}`).requiredOption("--tools <json>", "Tool definitions (inline JSON or @filename)").action(async function(provider, opts) {
|
|
25811
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25812
|
+
const tools = parseData(opts.tools, "--tools");
|
|
25813
|
+
const client = await createDataClient();
|
|
25814
|
+
printOutput(
|
|
25815
|
+
await client.request("POST", `/v1/${base}/providers/${providerSegment(provider)}/import`, {
|
|
25816
|
+
tools: Array.isArray(tools) ? tools : [tools]
|
|
25817
|
+
}),
|
|
25818
|
+
outputFormat(this)
|
|
25819
|
+
);
|
|
25820
|
+
});
|
|
25821
|
+
providers.command("export <provider>").description(`Export record types as tool definitions. Providers: ${VALID_PROVIDERS_HELP}`).option("--record-types <ids>", "Comma-separated record type ids (omit for all)").option("--to <file>", "Write the exported definitions to a file instead of stdout").action(async function(provider, opts) {
|
|
25822
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25823
|
+
const recordTypes = opts.recordTypes ? splitList(opts.recordTypes) : [];
|
|
25824
|
+
const query = recordTypes.length ? `?record_types=${encodeURIComponent(recordTypes.join(","))}` : "";
|
|
25825
|
+
const client = await createDataClient();
|
|
25826
|
+
const data = await client.request(
|
|
25827
|
+
"GET",
|
|
25828
|
+
`/v1/${base}/providers/${providerSegment(provider)}/export${query}`
|
|
25829
|
+
);
|
|
25830
|
+
if (opts.to) {
|
|
25831
|
+
try {
|
|
25832
|
+
writeFileSync15(opts.to, JSON.stringify(data, null, 2));
|
|
25833
|
+
} catch (e) {
|
|
25834
|
+
throw expected(`--to ${opts.to}: ${e instanceof Error ? e.message : "could not be written"}`);
|
|
25835
|
+
}
|
|
25836
|
+
printOutput({ written_to: opts.to }, outputFormat(this));
|
|
25837
|
+
return;
|
|
25838
|
+
}
|
|
25839
|
+
printOutput(data, outputFormat(this));
|
|
25840
|
+
});
|
|
25841
|
+
providers.command("import-call <provider> <record_type>").description(
|
|
25842
|
+
`Create a record from a tool call in provider format. Providers: ${VALID_PROVIDERS_HELP}`
|
|
25843
|
+
).requiredOption("--data <json>", "Tool call data in provider format (inline JSON or @filename)").option("--external-id <id>", "External id for idempotent upsert").option("--external-source <source>", "External source identifier").action(async function(provider, recordType2, opts) {
|
|
25844
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25845
|
+
const callData = parseData(opts.data, "--data");
|
|
25846
|
+
const queryParts = [];
|
|
25847
|
+
if (opts.externalId) queryParts.push(`external_id=${encodeURIComponent(opts.externalId)}`);
|
|
25848
|
+
if (opts.externalSource) {
|
|
25849
|
+
queryParts.push(`external_source=${encodeURIComponent(opts.externalSource)}`);
|
|
25850
|
+
}
|
|
25851
|
+
const qs = queryParts.length ? `?${queryParts.join("&")}` : "";
|
|
25852
|
+
const client = await createDataClient();
|
|
25853
|
+
printOutput(
|
|
25854
|
+
await client.request(
|
|
25855
|
+
"POST",
|
|
25856
|
+
`/v1/${base}/providers/${providerSegment(provider)}/records/${pathSegment(recordType2, "record_type")}${qs}`,
|
|
25857
|
+
callData
|
|
25858
|
+
),
|
|
25859
|
+
outputFormat(this)
|
|
25860
|
+
);
|
|
25861
|
+
});
|
|
25862
|
+
return providers;
|
|
25863
|
+
}
|
|
25864
|
+
var VALID_PROVIDERS, VALID_PROVIDERS_HELP;
|
|
25865
|
+
var init_providers = __esm({
|
|
25866
|
+
"src/data/commands/providers.ts"() {
|
|
25867
|
+
"use strict";
|
|
25868
|
+
init_client();
|
|
25869
|
+
init_output();
|
|
25870
|
+
init_helpers();
|
|
25871
|
+
init_expected();
|
|
25872
|
+
VALID_PROVIDERS = ["openai", "anthropic", "google", "mcp"];
|
|
25873
|
+
VALID_PROVIDERS_HELP = VALID_PROVIDERS.join(", ");
|
|
25874
|
+
}
|
|
25875
|
+
});
|
|
25876
|
+
|
|
25877
|
+
// src/data/commands/report.ts
|
|
25878
|
+
import { Command as Command6 } from "commander";
|
|
25879
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
25880
|
+
import { dirname as dirname9, join as join27 } from "path";
|
|
25881
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
25882
|
+
function resolveCliVersion() {
|
|
25883
|
+
for (const candidate of [
|
|
25884
|
+
join27(here, "..", "package.json"),
|
|
25885
|
+
join27(here, "..", "..", "..", "package.json")
|
|
25886
|
+
]) {
|
|
25887
|
+
try {
|
|
25888
|
+
const version = JSON.parse(readFileSync21(candidate, "utf-8")).version;
|
|
25889
|
+
if (typeof version === "string" && version) return version;
|
|
25890
|
+
} catch {
|
|
25891
|
+
}
|
|
25892
|
+
}
|
|
25893
|
+
return "unknown";
|
|
25894
|
+
}
|
|
25895
|
+
function resolveReportClientVersions(projectRoot, cliVersion) {
|
|
25896
|
+
const versions = { cli_version: cliVersion };
|
|
25897
|
+
try {
|
|
25898
|
+
const skillVersion = lowestInstalledVersion(findInstalledSkills(projectRoot));
|
|
25899
|
+
if (skillVersion) versions.skill_version = skillVersion;
|
|
25900
|
+
} catch {
|
|
25901
|
+
}
|
|
25902
|
+
return versions;
|
|
25903
|
+
}
|
|
25904
|
+
function reportActionError2(err) {
|
|
25905
|
+
if (err instanceof ApiError) {
|
|
25906
|
+
if (err.status === 404) {
|
|
25907
|
+
console.error("Report not found, or it was not filed by you.");
|
|
25908
|
+
} else if (err.status === 403) {
|
|
25909
|
+
console.error("This report is agent-authored and can only be handled by a platform admin.");
|
|
25910
|
+
} else if (err.status === 409) {
|
|
25911
|
+
console.error(err.message);
|
|
25912
|
+
} else if (err.status === 429) {
|
|
25913
|
+
console.error("Rate limit exceeded. Please try again later.");
|
|
25914
|
+
} else {
|
|
25915
|
+
console.error(`Request failed: ${err.message}`);
|
|
25916
|
+
}
|
|
25917
|
+
return process.exit(1);
|
|
25918
|
+
}
|
|
25919
|
+
throw err;
|
|
25920
|
+
}
|
|
25921
|
+
function buildBasesReportCommand() {
|
|
25922
|
+
const report = new Command6("report").description(
|
|
25923
|
+
"File a report to the Data triage queue and verify the outcome"
|
|
25924
|
+
);
|
|
25925
|
+
report.command("create").description("Submit a report to the Data triage queue (deduplicated)").requiredOption("--title <title>", "Short summary").requiredOption("--description <text>", "What happened").option("--source <source>", "Origin: cli_report (default) | review | security_audit").option("--classification <c>", "Nature: bug (default) | flaky_test | security | enhancement").option(
|
|
25926
|
+
"--dedup-key <key>",
|
|
25927
|
+
'Stable dedup key (e.g. "<file>::<test>"); collapses repeat occurrences'
|
|
25928
|
+
).option("--severity <level>", "low | medium | high | critical").option("--steps <text>", "Steps to reproduce").option("--error-message <text>", "Exact error text if any").option("--context <text>", "Additional context (logs, request ids)").option("--locale <code>", "Notification locale (en | pt | es)", "en").option("--reporter-email <addr>", "Override reporter email (defaults to session email)").option("--base-id <id>", "Base the error relates to (defaults to the configured base)").option("--record-type <name>", "Record type the error relates to").option("--record-id <id>", "Record id (UUID) the error relates to").option("--external-id <id>", "External id of the related record").option("--external-source <src>", "External source of the related record").option("--relationship-id <id>", "Relationship id the error relates to").action(async function(opts) {
|
|
25929
|
+
const body = {
|
|
25930
|
+
title: opts.title,
|
|
25931
|
+
description: opts.description,
|
|
25932
|
+
locale: opts.locale
|
|
25933
|
+
};
|
|
25934
|
+
if (opts.source) body.source = opts.source;
|
|
25935
|
+
if (opts.classification) body.classification = opts.classification;
|
|
25936
|
+
if (opts.dedupKey) body.dedup_key = opts.dedupKey;
|
|
25937
|
+
if (opts.severity) body.severity = opts.severity;
|
|
25938
|
+
if (opts.steps) body.steps = opts.steps;
|
|
25939
|
+
if (opts.errorMessage) body.error_message = opts.errorMessage;
|
|
25940
|
+
if (opts.context) body.context = opts.context;
|
|
25941
|
+
if (opts.reporterEmail) body.reporter_email = opts.reporterEmail;
|
|
25942
|
+
const baseId = opts.baseId ?? findBase(this) ?? process.env.WAYAI_BASE;
|
|
25943
|
+
if (baseId) body.base_id = baseId;
|
|
25944
|
+
if (opts.recordType) body.record_type = opts.recordType;
|
|
25945
|
+
if (opts.recordId) body.record_id = opts.recordId;
|
|
25946
|
+
if (opts.externalId) body.external_id = opts.externalId;
|
|
25947
|
+
if (opts.externalSource) body.external_source = opts.externalSource;
|
|
25948
|
+
if (opts.relationshipId) body.relationship_id = opts.relationshipId;
|
|
25949
|
+
const versions = resolveReportClientVersions(
|
|
25950
|
+
findGitRoot() ?? process.cwd(),
|
|
25951
|
+
resolveCliVersion()
|
|
25952
|
+
);
|
|
25953
|
+
body.cli_version = versions.cli_version;
|
|
25954
|
+
if (versions.skill_version) body.skill_version = versions.skill_version;
|
|
25955
|
+
const client = await createDataClient();
|
|
25956
|
+
const data = await client.request("POST", "/v1/reports", body);
|
|
25957
|
+
if (outputFormat(this) === "json") {
|
|
25958
|
+
printOutput(data, "json");
|
|
25959
|
+
return;
|
|
25960
|
+
}
|
|
25961
|
+
if (data.created) {
|
|
25962
|
+
console.log(`Report submitted (id: ${data.report_id}).`);
|
|
25963
|
+
console.log("Our team will review and follow up by email if you provided one.");
|
|
25964
|
+
} else if (data.recurrence?.reopened) {
|
|
25965
|
+
console.log(`Report reopened for another look (id: ${data.report_id}).`);
|
|
25966
|
+
} else if (data.recurrence) {
|
|
25967
|
+
console.log(
|
|
25968
|
+
`A matching report remains closed (id: ${data.report_id}); this occurrence was recorded.`
|
|
25969
|
+
);
|
|
25970
|
+
} else {
|
|
25971
|
+
console.log(
|
|
25972
|
+
`A matching report already exists (id: ${data.report_id}) \u2014 your submission was merged into it.`
|
|
25973
|
+
);
|
|
25974
|
+
}
|
|
25975
|
+
});
|
|
25976
|
+
report.command("list").description("List your reports, newest first").option("--status <s>", "Filter by status (e.g. shipped \u2014 awaiting your verification)").action(async function(opts) {
|
|
25977
|
+
const client = await createDataClient();
|
|
25978
|
+
const qs = opts.status ? `?status=${encodeURIComponent(opts.status)}` : "";
|
|
25979
|
+
try {
|
|
25980
|
+
const rows = await client.request("GET", `/v1/reports${qs}`);
|
|
25981
|
+
if (outputFormat(this) === "json") {
|
|
25982
|
+
printOutput(rows, "json");
|
|
25983
|
+
return;
|
|
25984
|
+
}
|
|
25985
|
+
if (rows.length === 0) {
|
|
25986
|
+
console.log("No reports found.");
|
|
25987
|
+
return;
|
|
25988
|
+
}
|
|
25989
|
+
for (const r of rows) {
|
|
25990
|
+
const title = sanitizeTerminalText(r.title?.replace(/\s+/g, " ").trim() || "(untitled)");
|
|
25991
|
+
console.log(
|
|
25992
|
+
`${r.report_id} ${r.status.padEnd(10)} ${(r.classification ?? "bug").padEnd(12)} ${title}`
|
|
25993
|
+
);
|
|
25994
|
+
}
|
|
25995
|
+
} catch (err) {
|
|
25996
|
+
reportActionError2(err);
|
|
25997
|
+
}
|
|
25998
|
+
});
|
|
25999
|
+
report.command("get <report_id>").description("Show one of your reports: status + the message thread").action(async function(reportId) {
|
|
26000
|
+
const client = await createDataClient();
|
|
26001
|
+
try {
|
|
26002
|
+
const data = await client.request("GET", `/v1/reports/${pathSegment(reportId, "report id")}`);
|
|
26003
|
+
if (outputFormat(this) === "json") {
|
|
26004
|
+
printOutput(data, "json");
|
|
26005
|
+
return;
|
|
26006
|
+
}
|
|
26007
|
+
const { report: row, messages } = data;
|
|
26008
|
+
console.log(`Report ${row.report_id}`);
|
|
26009
|
+
console.log(` title: ${sanitizeTerminalText(row.title ?? "(unavailable)")}`);
|
|
26010
|
+
console.log(
|
|
26011
|
+
` description: ${sanitizeTerminalText(row.description ?? "(unavailable)")}`
|
|
26012
|
+
);
|
|
26013
|
+
console.log(` status: ${row.status}`);
|
|
26014
|
+
console.log(` classification: ${row.classification ?? "bug"}`);
|
|
26015
|
+
if (row.contest_count > 0) console.log(` contests: ${row.contest_count}`);
|
|
26016
|
+
if (row.dismissal_final) console.log(" dismissal: final (non-contestable)");
|
|
26017
|
+
if (messages.length > 0) {
|
|
26018
|
+
console.log("\n Thread:");
|
|
26019
|
+
for (const m of messages) {
|
|
26020
|
+
console.log(` [${m.author_role}] ${sanitizeTerminalText(m.body)}`);
|
|
26021
|
+
}
|
|
26022
|
+
}
|
|
26023
|
+
if (row.status === "shipped") {
|
|
26024
|
+
console.log(
|
|
26025
|
+
`
|
|
26026
|
+
This fix is shipped. Run \`wayai bases report accept ${row.report_id}\` if it works,`
|
|
26027
|
+
);
|
|
26028
|
+
console.log(
|
|
26029
|
+
` or \`wayai bases report contest ${row.report_id} --reason "..."\` if it does not.`
|
|
26030
|
+
);
|
|
26031
|
+
}
|
|
26032
|
+
} catch (err) {
|
|
26033
|
+
reportActionError2(err);
|
|
26034
|
+
}
|
|
26035
|
+
});
|
|
26036
|
+
report.command("accept <report_id>").description("Accept a shipped fix (\u2192 addressed)").action(async function(reportId) {
|
|
26037
|
+
const client = await createDataClient();
|
|
26038
|
+
try {
|
|
26039
|
+
const data = await client.request(
|
|
26040
|
+
"POST",
|
|
26041
|
+
`/v1/reports/${pathSegment(reportId, "report id")}/accept`
|
|
26042
|
+
);
|
|
26043
|
+
if (outputFormat(this) === "json") {
|
|
26044
|
+
printOutput(data, "json");
|
|
26045
|
+
return;
|
|
26046
|
+
}
|
|
26047
|
+
console.log(`Accepted. Report ${data.report.report_id} \u2192 ${data.report.status}.`);
|
|
26048
|
+
} catch (err) {
|
|
26049
|
+
reportActionError2(err);
|
|
26050
|
+
}
|
|
26051
|
+
});
|
|
26052
|
+
report.command("contest <report_id>").description("Contest a shipped fix or a dismissal (\u2192 back to triage)").requiredOption("--reason <text>", "Why the fix/dismissal is wrong").action(async function(reportId, opts) {
|
|
26053
|
+
const client = await createDataClient();
|
|
26054
|
+
try {
|
|
26055
|
+
const data = await client.request(
|
|
26056
|
+
"POST",
|
|
26057
|
+
`/v1/reports/${pathSegment(reportId, "report id")}/contest`,
|
|
26058
|
+
{ reason: opts.reason }
|
|
26059
|
+
);
|
|
26060
|
+
if (outputFormat(this) === "json") {
|
|
26061
|
+
printOutput(data, "json");
|
|
26062
|
+
return;
|
|
26063
|
+
}
|
|
26064
|
+
console.log(
|
|
26065
|
+
`Contested. Report ${data.report.report_id} \u2192 ${data.report.status} (back to triage).`
|
|
26066
|
+
);
|
|
26067
|
+
} catch (err) {
|
|
26068
|
+
reportActionError2(err);
|
|
26069
|
+
}
|
|
26070
|
+
});
|
|
26071
|
+
return report;
|
|
26072
|
+
}
|
|
26073
|
+
var here;
|
|
26074
|
+
var init_report2 = __esm({
|
|
26075
|
+
"src/data/commands/report.ts"() {
|
|
26076
|
+
"use strict";
|
|
26077
|
+
init_client();
|
|
26078
|
+
init_output();
|
|
26079
|
+
init_helpers();
|
|
26080
|
+
init_api_client();
|
|
26081
|
+
init_terminal_output();
|
|
26082
|
+
init_workspace();
|
|
26083
|
+
init_skill_version();
|
|
26084
|
+
here = dirname9(fileURLToPath2(import.meta.url));
|
|
26085
|
+
}
|
|
26086
|
+
});
|
|
26087
|
+
|
|
26088
|
+
// src/data/commands/secrets.ts
|
|
26089
|
+
import { Command as Command7 } from "commander";
|
|
26090
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
26091
|
+
function withValueSourceOptions(cmd, what) {
|
|
26092
|
+
return cmd.option(
|
|
26093
|
+
"--file <path>",
|
|
26094
|
+
`Read ${what} from a file and base64-encode it (certificates, keystores, service-account JSON)`
|
|
26095
|
+
).option("--value-stdin", `Read ${what} from stdin (recommended for CI)`).option("--value-prompt", `Prompt for ${what}, masked`);
|
|
26096
|
+
}
|
|
26097
|
+
async function resolveValue(opts, label) {
|
|
26098
|
+
if (opts.file !== void 0) {
|
|
26099
|
+
if (opts.valueStdin || opts.valuePrompt) {
|
|
26100
|
+
throw expected("--file cannot be combined with --value-stdin or --value-prompt \u2014 pass one.");
|
|
26101
|
+
}
|
|
26102
|
+
try {
|
|
26103
|
+
return readFileSync22(opts.file).toString("base64");
|
|
26104
|
+
} catch (e) {
|
|
26105
|
+
throw expected(`--file ${opts.file}: ${e instanceof Error ? e.message : "could not be read"}`);
|
|
26106
|
+
}
|
|
26107
|
+
}
|
|
26108
|
+
return readSecret(
|
|
26109
|
+
{
|
|
26110
|
+
stdin: opts.valueStdin,
|
|
26111
|
+
prompt: opts.valuePrompt,
|
|
26112
|
+
stdinFlag: "--value-stdin",
|
|
26113
|
+
promptHint: "--value-prompt"
|
|
26114
|
+
},
|
|
26115
|
+
label
|
|
26116
|
+
);
|
|
26117
|
+
}
|
|
26118
|
+
function listAllSecrets(client, expiringDays) {
|
|
26119
|
+
return client.collectPages((cursor) => {
|
|
26120
|
+
const params = new URLSearchParams({ limit: String(SECRET_LIST_PAGE_LIMIT) });
|
|
26121
|
+
if (expiringDays !== void 0) params.set("expiring_within_days", expiringDays);
|
|
26122
|
+
if (cursor) params.set("cursor", cursor);
|
|
26123
|
+
return `/v1/vault/secrets?${params.toString()}`;
|
|
26124
|
+
});
|
|
26125
|
+
}
|
|
26126
|
+
function buildBasesSecretsCommand() {
|
|
26127
|
+
const secrets = new Command7("secrets").description("Manage organization vault secrets");
|
|
26128
|
+
withValueSourceOptions(
|
|
26129
|
+
secrets.command("create").description("Store a vault secret (value read from stdin, a masked prompt, or --file)").requiredOption("--name <name>", "Secret name").option("--content-type <mime>", "MIME type of the value (e.g. application/x-pkcs12)").option(
|
|
26130
|
+
"--expires-at <date>",
|
|
26131
|
+
"Expiry as ISO-8601 (e.g. 2027-01-01T00:00:00Z) \u2014 surfaced by `list --expiring`"
|
|
26132
|
+
).option("--tags <tags>", "Comma-separated tags"),
|
|
26133
|
+
"the secret value"
|
|
26134
|
+
).action(async function(opts) {
|
|
26135
|
+
const body = {
|
|
26136
|
+
name: opts.name,
|
|
26137
|
+
value: await resolveValue(opts, "Secret value")
|
|
26138
|
+
};
|
|
26139
|
+
if (opts.contentType) body.content_type = opts.contentType;
|
|
26140
|
+
if (opts.expiresAt) body.expires_at = opts.expiresAt;
|
|
26141
|
+
if (opts.tags) body.tags = splitList(opts.tags);
|
|
26142
|
+
const client = await createDataClient();
|
|
26143
|
+
printOutput(await client.request("POST", "/v1/vault/secrets", body), outputFormat(this));
|
|
26144
|
+
});
|
|
26145
|
+
secrets.command("list").description("List vault secrets (values masked)").option("--expiring", "Only secrets expiring within --days (default 30)").option("--days <n>", "Window for --expiring, in days", "30").action(async function(opts) {
|
|
26146
|
+
const client = await createDataClient();
|
|
26147
|
+
const secretRows = await listAllSecrets(client, opts.expiring ? opts.days : void 0);
|
|
26148
|
+
printOutput(secretRows, outputFormat(this));
|
|
26149
|
+
});
|
|
26150
|
+
secrets.command("get <id>").description("Get vault secret metadata (value masked)").action(async function(id) {
|
|
26151
|
+
const client = await createDataClient();
|
|
26152
|
+
printOutput(
|
|
26153
|
+
await client.request("GET", `/v1/vault/secrets/${pathSegment(id, "secret id")}`),
|
|
26154
|
+
outputFormat(this)
|
|
26155
|
+
);
|
|
26156
|
+
});
|
|
26157
|
+
withValueSourceOptions(
|
|
26158
|
+
secrets.command("rotate <id>").description("Rotate a secret by installing a new caller-supplied value").option("--expires-at <date>", "Set the rotated credential's new expiry (ISO-8601)"),
|
|
26159
|
+
"the new secret value"
|
|
26160
|
+
).action(async function(id, opts) {
|
|
26161
|
+
const body = { value: await resolveValue(opts, "New secret value") };
|
|
26162
|
+
if (opts.expiresAt) body.expires_at = opts.expiresAt;
|
|
26163
|
+
const client = await createDataClient();
|
|
26164
|
+
printOutput(
|
|
26165
|
+
await client.request("POST", `/v1/vault/secrets/${pathSegment(id, "secret id")}/rotate`, body),
|
|
26166
|
+
outputFormat(this)
|
|
26167
|
+
);
|
|
26168
|
+
});
|
|
26169
|
+
secrets.command("delete <id>").description("Delete a vault secret").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
|
|
26170
|
+
if (!opts.yes) {
|
|
26171
|
+
if (!isInteractive()) {
|
|
26172
|
+
throw expected("Refusing to delete without confirmation \u2014 pass --yes to delete non-interactively.");
|
|
26173
|
+
}
|
|
26174
|
+
if (!await confirm(`Delete vault secret ${id}? This cannot be undone.`)) {
|
|
26175
|
+
console.log("Aborted");
|
|
26176
|
+
return;
|
|
26177
|
+
}
|
|
26178
|
+
}
|
|
26179
|
+
const client = await createDataClient();
|
|
26180
|
+
await client.request("DELETE", `/v1/vault/secrets/${pathSegment(id, "secret id")}`);
|
|
26181
|
+
printOutput({ id, deleted: true }, outputFormat(this));
|
|
26182
|
+
});
|
|
26183
|
+
return secrets;
|
|
26184
|
+
}
|
|
26185
|
+
var SECRET_LIST_PAGE_LIMIT;
|
|
26186
|
+
var init_secrets = __esm({
|
|
26187
|
+
"src/data/commands/secrets.ts"() {
|
|
26188
|
+
"use strict";
|
|
26189
|
+
init_client();
|
|
26190
|
+
init_output();
|
|
26191
|
+
init_helpers();
|
|
26192
|
+
init_utils();
|
|
26193
|
+
init_expected();
|
|
26194
|
+
SECRET_LIST_PAGE_LIMIT = 250;
|
|
26195
|
+
}
|
|
26196
|
+
});
|
|
26197
|
+
|
|
26198
|
+
// src/data/commands/sql.ts
|
|
26199
|
+
import { Command as Command8 } from "commander";
|
|
26200
|
+
import { readFileSync as readFileSync23 } from "fs";
|
|
26201
|
+
function buildBasesSqlCommand() {
|
|
26202
|
+
return withBaseOption(new Command8("sql")).description("Execute a read-only SQL query against base data").argument("[query]", "SQL query (SELECT only)").option("--file <path>", "Read SQL from a file instead of the argument").option(
|
|
26203
|
+
"--param <kv...>",
|
|
26204
|
+
"Named parameters as key=value pairs (e.g. --param status=issued)"
|
|
26205
|
+
).action(async function(queryArg, opts) {
|
|
26206
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
26207
|
+
let query;
|
|
26208
|
+
if (opts.file) {
|
|
26209
|
+
try {
|
|
26210
|
+
query = readFileSync23(opts.file, "utf-8").trim();
|
|
26211
|
+
} catch (e) {
|
|
26212
|
+
throw expected(`--file ${opts.file}: ${e instanceof Error ? e.message : "could not be read"}`);
|
|
26213
|
+
}
|
|
26214
|
+
if (!query) throw expected(`--file ${opts.file} is empty \u2014 it must contain a SELECT query.`);
|
|
26215
|
+
} else if (queryArg) {
|
|
26216
|
+
query = queryArg;
|
|
26217
|
+
} else {
|
|
26218
|
+
throw expected("Provide a SQL query as an argument or via --file.");
|
|
26219
|
+
}
|
|
26220
|
+
const params = {};
|
|
26221
|
+
for (const kv of opts.param ?? []) {
|
|
26222
|
+
const eqIdx = kv.indexOf("=");
|
|
26223
|
+
if (eqIdx === -1) {
|
|
26224
|
+
throw expected(`Invalid --param ${JSON.stringify(kv)}, expected key=value.`);
|
|
26225
|
+
}
|
|
26226
|
+
params[kv.slice(0, eqIdx)] = kv.slice(eqIdx + 1);
|
|
26227
|
+
}
|
|
26228
|
+
const body = { query };
|
|
26229
|
+
if (Object.keys(params).length > 0) body.params = params;
|
|
26230
|
+
const client = await createDataClient();
|
|
26231
|
+
printOutput(await client.request("POST", `/v1/${base}/sql`, body), outputFormat(this));
|
|
26232
|
+
});
|
|
26233
|
+
}
|
|
26234
|
+
var init_sql = __esm({
|
|
26235
|
+
"src/data/commands/sql.ts"() {
|
|
26236
|
+
"use strict";
|
|
26237
|
+
init_client();
|
|
26238
|
+
init_output();
|
|
26239
|
+
init_helpers();
|
|
26240
|
+
init_expected();
|
|
26241
|
+
}
|
|
26242
|
+
});
|
|
26243
|
+
|
|
26244
|
+
// src/data/commands/tokens.ts
|
|
26245
|
+
import { Command as Command9 } from "commander";
|
|
26246
|
+
function addTokenLifecycleOptions(cmd) {
|
|
26247
|
+
return cmd.option("--expires-at <date>", "Expiration date (ISO 8601, e.g. 2026-06-01T00:00:00Z)").option(
|
|
26248
|
+
"--ttl <duration>",
|
|
26249
|
+
"Relative expiry, e.g. 10m, 2h, 7d (use for disposable probe/test tokens)"
|
|
26250
|
+
).option(
|
|
26251
|
+
"--description <text>",
|
|
26252
|
+
'What holds this token (e.g. "behind credential X"); shown in list and revoke warnings'
|
|
26253
|
+
).addHelpText("after", TOKEN_OUTPUT_HELP);
|
|
26254
|
+
}
|
|
26255
|
+
function tokenCreationOutput(cmd, opts) {
|
|
26256
|
+
return opts.tokenOnly ? "token" : outputFormat(cmd);
|
|
26257
|
+
}
|
|
26258
|
+
function printCreatedToken(data, output) {
|
|
26259
|
+
if (output !== "token") {
|
|
26260
|
+
printOutput(data, output);
|
|
26261
|
+
return;
|
|
26262
|
+
}
|
|
26263
|
+
const token = typeof data === "object" && data !== null && "token" in data ? data.token : void 0;
|
|
26264
|
+
if (typeof token !== "string" || !RAW_TOKEN_PATTERN.test(token)) {
|
|
26265
|
+
throw new Error("Token creation response did not contain a valid rec_ token");
|
|
26266
|
+
}
|
|
26267
|
+
console.log(token);
|
|
26268
|
+
}
|
|
26269
|
+
function resolveExpiresAt(opts) {
|
|
26270
|
+
if (opts.ttl && opts.expiresAt) {
|
|
26271
|
+
throw expected("--ttl and --expires-at are mutually exclusive \u2014 pass one.");
|
|
26272
|
+
}
|
|
26273
|
+
if (opts.ttl) return new Date(Date.now() + parseDuration(opts.ttl, "--ttl")).toISOString();
|
|
26274
|
+
return opts.expiresAt;
|
|
26275
|
+
}
|
|
26276
|
+
function applyTokenLifecycleOpts(body, opts) {
|
|
26277
|
+
const expiresAt = resolveExpiresAt(opts);
|
|
26278
|
+
if (expiresAt) body.expires_at = expiresAt;
|
|
26279
|
+
if (opts.description) body.description = opts.description;
|
|
26280
|
+
}
|
|
26281
|
+
function listAllTokens(client) {
|
|
26282
|
+
return client.collectPages(
|
|
26283
|
+
(cursor) => `/v1/tokens?limit=${TOKEN_LIST_PAGE_LIMIT}${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}`
|
|
26284
|
+
);
|
|
26285
|
+
}
|
|
26286
|
+
function pruneRow(t, reason) {
|
|
26287
|
+
return {
|
|
26288
|
+
token_id: t.token_id,
|
|
26289
|
+
name: t.name,
|
|
26290
|
+
reason,
|
|
26291
|
+
last_used: t.last_used_at ?? "never"
|
|
26292
|
+
};
|
|
26293
|
+
}
|
|
26294
|
+
function buildBasesTokensCommand() {
|
|
26295
|
+
const tokens = new Command9("tokens").description("Manage API tokens");
|
|
26296
|
+
addTokenLifecycleOptions(
|
|
26297
|
+
tokens.command("create").description("Create a scoped API token").requiredOption("--name <name>", "Token name").requiredOption("--grants <json>", "Grant definitions as JSON array").option(
|
|
26298
|
+
"--token-only",
|
|
26299
|
+
"Print only the raw rec_ token for secure command substitution (shown once)"
|
|
26300
|
+
)
|
|
26301
|
+
).action(async function(opts) {
|
|
26302
|
+
const grants = parseData(opts.grants, "--grants");
|
|
26303
|
+
const body = { name: opts.name, grants };
|
|
26304
|
+
applyTokenLifecycleOpts(body, opts);
|
|
26305
|
+
const client = await createDataClient();
|
|
26306
|
+
const data = await client.request("POST", "/v1/tokens", body);
|
|
26307
|
+
printCreatedToken(data, tokenCreationOutput(this, opts));
|
|
26308
|
+
});
|
|
26309
|
+
addTokenLifecycleOptions(
|
|
26310
|
+
// The only token verb that addresses a base, so it alone declares `--base`.
|
|
26311
|
+
// Putting it on the `tokens` group would advertise a flag that does nothing
|
|
26312
|
+
// on `list`, `revoke` and `prune` — the defect `withBaseOption` exists to
|
|
26313
|
+
// avoid.
|
|
26314
|
+
withBaseOption(tokens.command("create-for-toolset <slug>")).description(
|
|
26315
|
+
"Create a toolset-bound (consumer) token whose authorization IS the toolset tool surface \u2014 it can call exactly the tools that toolset exposes (those record types + operations, relationships, batch, and sql only if enabled) and nothing else. Binds to the configured --base."
|
|
26316
|
+
).option("--name <name>", 'Token name (default: "<slug> consumer")').option(
|
|
26317
|
+
"--token-only",
|
|
26318
|
+
"Print only the raw rec_ token for secure command substitution (shown once)"
|
|
26319
|
+
)
|
|
26320
|
+
).action(async function(slug, opts) {
|
|
26321
|
+
const base = requireBase(this);
|
|
26322
|
+
const body = {
|
|
26323
|
+
name: opts.name ?? `${slug} consumer`,
|
|
26324
|
+
toolset_binding: { base_id: base, toolset_id: slug }
|
|
26325
|
+
};
|
|
26326
|
+
applyTokenLifecycleOpts(body, opts);
|
|
26327
|
+
const client = await createDataClient();
|
|
26328
|
+
const data = await client.request("POST", "/v1/tokens", body);
|
|
26329
|
+
const output = tokenCreationOutput(this, opts);
|
|
26330
|
+
printCreatedToken(data, output);
|
|
26331
|
+
if (output === "table") {
|
|
26332
|
+
console.log(`
|
|
26333
|
+
MCP URL: ${toolsetMcpUrl(slug)}`);
|
|
26334
|
+
}
|
|
26335
|
+
});
|
|
26336
|
+
tokens.command("list").description("List API tokens (status, last_used_at, expires_at, description)").action(async function() {
|
|
26337
|
+
const client = await createDataClient();
|
|
26338
|
+
printOutput(await listAllTokens(client), outputFormat(this));
|
|
26339
|
+
});
|
|
26340
|
+
tokens.command("revoke <token_id>").description(
|
|
26341
|
+
"Revoke an API token (permanent). A recently-used or toolset-bound token requires --force."
|
|
26342
|
+
).option("--force", "Revoke even if the token appears to be in live use").action(async function(tokenId, opts) {
|
|
26343
|
+
const client = await createDataClient();
|
|
26344
|
+
const id = pathSegment(tokenId, "token id");
|
|
26345
|
+
const path31 = (force) => `/v1/tokens/${id}${force ? "?force=true" : ""}`;
|
|
26346
|
+
const format = outputFormat(this);
|
|
26347
|
+
const revoked = (forced) => printOutput({ token_id: tokenId, revoked: true, forced }, format);
|
|
26348
|
+
if (opts.force) {
|
|
26349
|
+
await client.request("DELETE", path31(true));
|
|
26350
|
+
revoked(true);
|
|
26351
|
+
return;
|
|
26352
|
+
}
|
|
26353
|
+
try {
|
|
26354
|
+
await client.request("DELETE", path31(false));
|
|
26355
|
+
revoked(false);
|
|
26356
|
+
} catch (err) {
|
|
26357
|
+
if (!(err instanceof ApiError) || err.status !== 409 || dataErrorDetails(err)?.requires_force !== true) {
|
|
26358
|
+
throw err;
|
|
26359
|
+
}
|
|
26360
|
+
console.error(err.message);
|
|
26361
|
+
if (!isInteractive()) {
|
|
26362
|
+
console.error("\nRe-run with --force to revoke anyway.");
|
|
26363
|
+
return process.exit(1);
|
|
26364
|
+
}
|
|
26365
|
+
if (!await confirm("Revoke anyway?")) {
|
|
26366
|
+
console.error("Aborted.");
|
|
26367
|
+
return process.exit(1);
|
|
26368
|
+
}
|
|
26369
|
+
await client.request("DELETE", path31(true));
|
|
26370
|
+
revoked(true);
|
|
26371
|
+
}
|
|
26372
|
+
});
|
|
26373
|
+
tokens.command("prune").description(
|
|
26374
|
+
"Revoke stale tokens, selected by staleness only (expired, or unused past --unused-since) \u2014 never by name. Previews by default; revoking requires --yes or an interactive confirmation."
|
|
26375
|
+
).option(
|
|
26376
|
+
"--unused-since <duration>",
|
|
26377
|
+
"Also prune tokens with no activity for this long (e.g. 30d). Skips toolset-bound and legacy usage-untracked tokens."
|
|
26378
|
+
).option("--include-bound", "Allow pruning stale toolset-bound (consumer) tokens").option("-y, --yes", "Revoke without prompting (required when non-interactive)").action(async function(opts) {
|
|
26379
|
+
const staleMs = opts.unusedSince ? parseDuration(opts.unusedSince, "--unused-since") : void 0;
|
|
26380
|
+
const client = await createDataClient();
|
|
26381
|
+
const allTokens = await listAllTokens(client);
|
|
26382
|
+
const now = Date.now();
|
|
26383
|
+
const targets = [];
|
|
26384
|
+
const skipped = [];
|
|
26385
|
+
for (const t of allTokens) {
|
|
26386
|
+
const expired = t.status ? t.status === "expired" : !!t.expires_at && Date.parse(t.expires_at) <= now;
|
|
26387
|
+
if (expired) {
|
|
26388
|
+
targets.push(pruneRow(t, "expired"));
|
|
26389
|
+
continue;
|
|
26390
|
+
}
|
|
26391
|
+
if (staleMs === void 0) continue;
|
|
26392
|
+
const lastActivity = Date.parse(t.last_used_at ?? t.created_at);
|
|
26393
|
+
if (!(now - lastActivity > staleMs)) continue;
|
|
26394
|
+
if (t.usage_tracked !== true) {
|
|
26395
|
+
skipped.push(
|
|
26396
|
+
pruneRow(
|
|
26397
|
+
t,
|
|
26398
|
+
"usage untracked (legacy token) \u2014 revoke individually if truly unused"
|
|
26399
|
+
)
|
|
26400
|
+
);
|
|
26401
|
+
} else if (t.toolset_binding && !opts.includeBound) {
|
|
26402
|
+
skipped.push(
|
|
26403
|
+
pruneRow(
|
|
26404
|
+
t,
|
|
26405
|
+
`toolset-bound ('${t.toolset_binding.toolset_id}') \u2014 pass --include-bound to prune`
|
|
26406
|
+
)
|
|
26407
|
+
);
|
|
26408
|
+
} else {
|
|
26409
|
+
targets.push(pruneRow(t, `unused for over ${opts.unusedSince}`));
|
|
26410
|
+
}
|
|
26411
|
+
}
|
|
26412
|
+
const format = outputFormat(this);
|
|
26413
|
+
const json = format === "json";
|
|
26414
|
+
const note = (line) => json ? console.error(line) : console.log(line);
|
|
26415
|
+
const printPreview = () => {
|
|
26416
|
+
if (targets.length === 0) {
|
|
26417
|
+
note("Nothing to prune.");
|
|
26418
|
+
} else {
|
|
26419
|
+
note(`Would revoke ${targets.length} token(s):`);
|
|
26420
|
+
if (!json) printOutput(targets, format);
|
|
26421
|
+
}
|
|
26422
|
+
if (skipped.length === 0) return;
|
|
26423
|
+
note(`
|
|
26424
|
+
Skipped ${skipped.length} stale token(s):`);
|
|
26425
|
+
if (!json) printOutput(skipped, format);
|
|
26426
|
+
};
|
|
26427
|
+
printPreview();
|
|
26428
|
+
if (targets.length === 0) {
|
|
26429
|
+
if (json) printOutput({ revoked: 0, failed: 0, targets, skipped }, format);
|
|
26430
|
+
return;
|
|
26431
|
+
}
|
|
26432
|
+
if (!opts.yes) {
|
|
26433
|
+
if (!isInteractive()) {
|
|
26434
|
+
console.error("\nPreview only \u2014 re-run with --yes to revoke these tokens.");
|
|
26435
|
+
if (json) printOutput({ preview: true, revoked: 0, failed: 0, targets, skipped }, format);
|
|
26436
|
+
return process.exit(1);
|
|
26437
|
+
}
|
|
26438
|
+
if (!await confirm(`
|
|
26439
|
+
Revoke ${targets.length} token(s)?`)) {
|
|
26440
|
+
console.error("Aborted.");
|
|
26441
|
+
return process.exit(1);
|
|
26442
|
+
}
|
|
26443
|
+
}
|
|
26444
|
+
let revoked = 0;
|
|
26445
|
+
let failed = 0;
|
|
26446
|
+
for (const t of targets) {
|
|
26447
|
+
try {
|
|
26448
|
+
await client.request(
|
|
26449
|
+
"DELETE",
|
|
26450
|
+
`/v1/tokens/${pathSegment(t.token_id, "token id")}?force=true`
|
|
26451
|
+
);
|
|
26452
|
+
revoked++;
|
|
26453
|
+
} catch (err) {
|
|
26454
|
+
failed++;
|
|
26455
|
+
console.error(
|
|
26456
|
+
`Failed to revoke ${t.token_id} ('${sanitizeTerminalText(t.name)}'): ${err instanceof Error ? err.message : String(err)}`
|
|
26457
|
+
);
|
|
26458
|
+
}
|
|
26459
|
+
}
|
|
26460
|
+
if (json) printOutput({ revoked, failed, targets, skipped }, format);
|
|
26461
|
+
else console.log(`Revoked ${revoked} token(s)${failed > 0 ? `, ${failed} failed` : ""}.`);
|
|
26462
|
+
if (failed > 0) return process.exit(1);
|
|
26463
|
+
});
|
|
26464
|
+
return tokens;
|
|
26465
|
+
}
|
|
26466
|
+
var RAW_TOKEN_PATTERN, TOKEN_OUTPUT_HELP, TOKEN_LIST_PAGE_LIMIT;
|
|
26467
|
+
var init_tokens = __esm({
|
|
26468
|
+
"src/data/commands/tokens.ts"() {
|
|
26469
|
+
"use strict";
|
|
26470
|
+
init_client();
|
|
26471
|
+
init_output();
|
|
26472
|
+
init_helpers();
|
|
26473
|
+
init_api_client();
|
|
26474
|
+
init_terminal_output();
|
|
26475
|
+
init_utils();
|
|
26476
|
+
init_expected();
|
|
26477
|
+
RAW_TOKEN_PATTERN = /^rec_[A-Za-z0-9_-]{43}$/;
|
|
26478
|
+
TOKEN_OUTPUT_HELP = `
|
|
26479
|
+
Output:
|
|
26480
|
+
--json prints the unwrapped response object; the secret is at top-level .token, never .data.token.
|
|
26481
|
+
--token-only prints only a validated rec_ token and overrides table/JSON formatting.
|
|
26482
|
+
The raw token is shown once. Capture it securely and do not log it.
|
|
26483
|
+
`;
|
|
26484
|
+
TOKEN_LIST_PAGE_LIMIT = 250;
|
|
26485
|
+
}
|
|
26486
|
+
});
|
|
26487
|
+
|
|
26488
|
+
// src/lib/base-binding.ts
|
|
26489
|
+
var binding2, getBaseBindingPath, readBaseBinding, writeBaseBinding, clearBaseBinding, autoBindBaseIfUnbound, assertBaseMatchesBinding, assertNoBindingBlocksBaseCreation;
|
|
26490
|
+
var init_base_binding = __esm({
|
|
26491
|
+
"src/lib/base-binding.ts"() {
|
|
26492
|
+
"use strict";
|
|
26493
|
+
init_base_id();
|
|
26494
|
+
init_worktree_binding();
|
|
26495
|
+
binding2 = createWorktreeBinding({
|
|
26496
|
+
filename: "wayai-base-binding",
|
|
26497
|
+
isValidId: isValidBaseId,
|
|
26498
|
+
noun: "base",
|
|
26499
|
+
idLabel: "base id (must be a slug)",
|
|
26500
|
+
unbindCommand: "wayai bases unbind",
|
|
26501
|
+
useCommand: "wayai bases use"
|
|
26502
|
+
});
|
|
26503
|
+
getBaseBindingPath = binding2.getBindingPath;
|
|
26504
|
+
readBaseBinding = binding2.readBinding;
|
|
26505
|
+
writeBaseBinding = binding2.writeBinding;
|
|
26506
|
+
clearBaseBinding = binding2.clearBinding;
|
|
26507
|
+
autoBindBaseIfUnbound = binding2.autoBindIfUnbound;
|
|
26508
|
+
assertBaseMatchesBinding = binding2.assertMatchesBinding;
|
|
26509
|
+
assertNoBindingBlocksBaseCreation = binding2.assertNoBindingBlocksCreation;
|
|
26510
|
+
}
|
|
26511
|
+
});
|
|
26512
|
+
|
|
26513
|
+
// src/data/commands/bases.ts
|
|
26514
|
+
import { Command as Command10 } from "commander";
|
|
26515
|
+
import * as fs24 from "fs";
|
|
26516
|
+
import * as path30 from "path";
|
|
26517
|
+
import * as yaml9 from "js-yaml";
|
|
26518
|
+
function pageOf(path31, cursor) {
|
|
26519
|
+
if (!cursor) return path31;
|
|
26520
|
+
return `${path31}${path31.includes("?") ? "&" : "?"}cursor=${encodeURIComponent(cursor)}`;
|
|
26521
|
+
}
|
|
26522
|
+
function parseEnum(flag, value, allowed) {
|
|
26523
|
+
if (value === void 0) return void 0;
|
|
26524
|
+
if (!allowed.includes(value)) {
|
|
26525
|
+
throw expected(`${flag} must be one of: ${allowed.join(", ")}.`);
|
|
26526
|
+
}
|
|
26527
|
+
return value;
|
|
26528
|
+
}
|
|
26529
|
+
function buildBasesCommand() {
|
|
26530
|
+
const bases = new Command10("bases").description("Manage bases");
|
|
26531
|
+
bases.command("list").description("List all bases").option("--tag <tag>", "Filter by tag").action(async function(opts) {
|
|
26532
|
+
const client = await createDataClient();
|
|
26533
|
+
const qs = opts.tag ? `?tag=${encodeURIComponent(opts.tag)}` : "";
|
|
26534
|
+
printOutput(
|
|
26535
|
+
await client.collectPages((cursor) => pageOf(`/v1/bases${qs}`, cursor)),
|
|
26536
|
+
outputFormat(this)
|
|
26537
|
+
);
|
|
26538
|
+
});
|
|
26539
|
+
bases.command("get <id>").description("Get a base").action(async function(id) {
|
|
26540
|
+
const client = await createDataClient();
|
|
26541
|
+
printOutput(await client.request("GET", `/v1/bases/${pathSegment(id)}`), outputFormat(this));
|
|
26542
|
+
});
|
|
26543
|
+
bases.command("create <id>").description("Create a base").requiredOption("--name <name>", "Base name").option("--description <desc>", "Description").option("--tags <tags>", "Comma-separated tags (e.g. client:acme,billing)").option(
|
|
26544
|
+
"--timezone <tz>",
|
|
26545
|
+
"Default IANA timezone for datetime canonicalization (e.g. America/Sao_Paulo); a record type's own timezone overrides it"
|
|
26546
|
+
).option(
|
|
26547
|
+
"--environment <env>",
|
|
26548
|
+
"production (default) | preview. Production bases require a paid plan. A preview created here has no production origin and cannot be given one later, so it cannot be promoted directly \u2014 move its config to a linked preview with pull/push when you upgrade"
|
|
26549
|
+
).action(async function(id, opts) {
|
|
26550
|
+
const environment = parseEnum("--environment", opts.environment, [
|
|
26551
|
+
"production",
|
|
26552
|
+
"preview"
|
|
26553
|
+
]);
|
|
26554
|
+
const client = await createDataClient();
|
|
26555
|
+
const body = { name: opts.name, description: opts.description };
|
|
26556
|
+
if (environment) body.environment = environment;
|
|
26557
|
+
if (opts.tags) body.tags = splitList(opts.tags);
|
|
26558
|
+
if (opts.timezone) body.settings = { timezone: opts.timezone };
|
|
26559
|
+
printOutput(await client.request("PUT", `/v1/bases/${pathSegment(id)}`, body), outputFormat(this));
|
|
26560
|
+
});
|
|
26561
|
+
bases.command("update <id>").description(
|
|
26562
|
+
"Update a base's mutable config (display name, description, tags, default timezone/settings). The id/slug is immutable."
|
|
26563
|
+
).option("--name <name>", "New display name").option("--description <desc>", "Description").option("--tags <tags>", "Comma-separated tags \u2014 replaces the existing tags").option(
|
|
26564
|
+
"--timezone <tz>",
|
|
26565
|
+
"Default IANA timezone for datetime canonicalization. Merges into existing settings."
|
|
26566
|
+
).option(
|
|
26567
|
+
"--settings <json>",
|
|
26568
|
+
"Raw settings JSON object (inline JSON or @file.json) \u2014 replaces the entire settings object"
|
|
26569
|
+
).option(
|
|
26570
|
+
"--integrations <mode>",
|
|
26571
|
+
"enabled | disabled. Preview-only: `disabled` neutralizes every external integration edge so the seeded preview behaves natively for production-safe agent evals"
|
|
26572
|
+
).action(async function(id, opts) {
|
|
26573
|
+
const integrations = parseEnum("--integrations", opts.integrations, ["enabled", "disabled"]);
|
|
26574
|
+
if (opts.name === void 0 && opts.description === void 0 && opts.tags === void 0 && opts.timezone === void 0 && opts.settings === void 0 && integrations === void 0) {
|
|
26575
|
+
throw expected(
|
|
26576
|
+
"Nothing to update. Provide at least one of --name, --description, --tags, --timezone, --settings, --integrations."
|
|
26577
|
+
);
|
|
26578
|
+
}
|
|
26579
|
+
const client = await createDataClient();
|
|
26580
|
+
const body = {};
|
|
26581
|
+
if (opts.name !== void 0) body.name = opts.name;
|
|
26582
|
+
if (opts.description !== void 0) body.description = opts.description;
|
|
26583
|
+
if (opts.tags !== void 0) body.tags = splitList(opts.tags);
|
|
26584
|
+
if (integrations !== void 0) body.integrations = integrations;
|
|
26585
|
+
if (opts.settings !== void 0 || opts.timezone !== void 0) {
|
|
26586
|
+
let settings;
|
|
26587
|
+
if (opts.settings !== void 0) {
|
|
26588
|
+
let parsed;
|
|
26589
|
+
try {
|
|
26590
|
+
parsed = parseData(opts.settings);
|
|
26591
|
+
} catch (e) {
|
|
26592
|
+
throw expected(`--settings: ${e instanceof Error ? e.message : "could not be read as JSON"}`);
|
|
26593
|
+
}
|
|
26594
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
26595
|
+
throw expected("--settings must be a valid JSON object");
|
|
26596
|
+
}
|
|
26597
|
+
settings = parsed;
|
|
26598
|
+
} else {
|
|
26599
|
+
const existing = await client.request("GET", `/v1/bases/${pathSegment(id)}`);
|
|
26600
|
+
settings = { ...existing?.settings ?? {} };
|
|
26601
|
+
}
|
|
26602
|
+
if (opts.timezone !== void 0) settings.timezone = opts.timezone;
|
|
26603
|
+
body.settings = settings;
|
|
26604
|
+
}
|
|
26605
|
+
printOutput(await client.request("PUT", `/v1/bases/${pathSegment(id)}`, body), outputFormat(this));
|
|
26606
|
+
});
|
|
26607
|
+
bases.command("rename <id>").description(
|
|
26608
|
+
"Rename a base's display name (the id/slug is immutable \u2014 it scopes token grants, MCP endpoints, and references)"
|
|
26609
|
+
).requiredOption("--name <name>", "New display name").action(async function(id, opts) {
|
|
26610
|
+
const client = await createDataClient();
|
|
26611
|
+
printOutput(
|
|
26612
|
+
await client.request("PUT", `/v1/bases/${pathSegment(id)}`, { name: opts.name }),
|
|
26613
|
+
outputFormat(this)
|
|
26614
|
+
);
|
|
26615
|
+
});
|
|
26616
|
+
bases.command("tag <id>").description("Set tags on a base").requiredOption("--tags <tags>", "Comma-separated tags (e.g. client:acme,billing)").action(async function(id, opts) {
|
|
26617
|
+
const client = await createDataClient();
|
|
26618
|
+
printOutput(
|
|
26619
|
+
await client.request("PUT", `/v1/bases/${pathSegment(id)}`, { tags: splitList(opts.tags) }),
|
|
26620
|
+
outputFormat(this)
|
|
26621
|
+
);
|
|
26622
|
+
});
|
|
26623
|
+
bases.command("delete <id>").description(
|
|
26624
|
+
"Delete a base. By default this is a tombstone: the base stops being listed, but its storage is retained and recreating the id restores it. Add --purge (preview bases only) to also reclaim the storage"
|
|
26625
|
+
).option("-y, --yes", "Skip confirmation prompt").option(
|
|
26626
|
+
"--purge",
|
|
26627
|
+
"Preview bases only. Permanently destroy the stored records, relationships, file metadata and config of this base instead of tombstoning it. The id is then retired and cannot be recreated"
|
|
26628
|
+
).action(async function(id, opts) {
|
|
26629
|
+
const prompt3 = opts.purge ? `Purge base "${id}"? Its records, relationships, file metadata and config are destroyed permanently, and the id "${id}" is retired for good.` : `Delete base "${id}"? This cannot be undone.`;
|
|
26630
|
+
if (!opts.yes && !await confirm(prompt3)) {
|
|
26631
|
+
console.log("Aborted");
|
|
26632
|
+
return;
|
|
26633
|
+
}
|
|
26634
|
+
const client = await createDataClient();
|
|
26635
|
+
await client.request("DELETE", `/v1/bases/${pathSegment(id)}${opts.purge ? "?purge=true" : ""}`);
|
|
26636
|
+
printOutput({ id, deleted: true, purged: opts.purge === true }, outputFormat(this));
|
|
26637
|
+
});
|
|
26638
|
+
bases.command("create-preview <origin-id>").description(
|
|
26639
|
+
"Create a preview base by cloning the config of another base. The origin may be a production base or another preview; the new preview id is <origin-id>--<name>, linked to the origin it was cloned from (so a preview of a preview promotes through that origin, never straight to production)"
|
|
26640
|
+
).requiredOption("--name <name>", "Preview base name").option("--description <desc>", "Description").option(
|
|
26641
|
+
"--integrations <mode>",
|
|
26642
|
+
"enabled (default) | disabled. `disabled` makes this preview a seeded, production-safe agent-eval target: every external integration edge is inert and it behaves natively"
|
|
26643
|
+
).option(
|
|
26644
|
+
"--create-only",
|
|
26645
|
+
"Fail with 409 if the derived preview id already exists, instead of re-applying config onto it. Use for per-session ephemeral bases, where landing on a live sibling would corrupt both runs"
|
|
26646
|
+
).action(async function(originId, opts) {
|
|
26647
|
+
const integrations = parseEnum("--integrations", opts.integrations, ["enabled", "disabled"]);
|
|
26648
|
+
const client = await createDataClient();
|
|
26649
|
+
printOutput(
|
|
26650
|
+
await client.request("POST", `/v1/${pathSegment(originId, "origin base id")}/preview`, {
|
|
26651
|
+
name: opts.name,
|
|
26652
|
+
description: opts.description,
|
|
26653
|
+
...integrations !== void 0 ? { integrations } : {},
|
|
26654
|
+
...opts.createOnly ? { create_only: true } : {}
|
|
26655
|
+
}),
|
|
26656
|
+
outputFormat(this)
|
|
26657
|
+
);
|
|
26658
|
+
});
|
|
26659
|
+
bases.command("list-previews <origin-id>").description("List preview bases cloned from a base (production or preview)").action(async function(originId) {
|
|
26660
|
+
const client = await createDataClient();
|
|
26661
|
+
const path31 = `/v1/${pathSegment(originId, "origin base id")}/previews`;
|
|
26662
|
+
printOutput(
|
|
26663
|
+
await client.collectPages((cursor) => pageOf(path31, cursor)),
|
|
26664
|
+
outputFormat(this)
|
|
26665
|
+
);
|
|
26666
|
+
});
|
|
26667
|
+
bases.command("promote <production-id>").description("Promote config from a preview base to production (human-only)").requiredOption("--from <preview-id>", "Source preview base id").option("--dry-run", "Show what would change without applying").option("--record-types <ids>", "Comma-separated record type ids to promote", splitList).option("--triggers <ids>", "Comma-separated trigger ids to promote", splitList).option("--inbound-webhooks <ids>", "Comma-separated inbound webhook ids to promote", splitList).action(async function(productionId, opts) {
|
|
26668
|
+
const client = await createDataClient();
|
|
26669
|
+
const data = await client.request("POST", `/v1/${pathSegment(productionId, "production base id")}/promote`, {
|
|
26670
|
+
source_base_id: opts.from,
|
|
26671
|
+
dry_run: opts.dryRun ?? false,
|
|
26672
|
+
record_types: opts.recordTypes,
|
|
26673
|
+
triggers: opts.triggers,
|
|
26674
|
+
inbound_webhooks: opts.inboundWebhooks
|
|
26675
|
+
});
|
|
26676
|
+
printOutput(data, outputFormat(this));
|
|
26677
|
+
const warnings = data?.source_credential_warnings ?? [];
|
|
26678
|
+
if (warnings.length > 0) {
|
|
26679
|
+
console.warn(
|
|
26680
|
+
"\nExternal source credentials are NOT carried over by promotion \u2014 set these on production before the source will work:"
|
|
26681
|
+
);
|
|
26682
|
+
for (const w of warnings) console.warn(` - ${sanitizeTerminalText(w.message)}`);
|
|
26683
|
+
}
|
|
26684
|
+
});
|
|
26685
|
+
bases.command("rollback <production-id>").description("Roll back a promotion (human-only)").requiredOption("--promotion <promotion-id>", "Promotion id to roll back").action(async function(productionId, opts) {
|
|
26686
|
+
const client = await createDataClient();
|
|
26687
|
+
printOutput(
|
|
26688
|
+
await client.request("POST", `/v1/${pathSegment(productionId, "production base id")}/promote/rollback`, {
|
|
26689
|
+
promotion_id: opts.promotion
|
|
26690
|
+
}),
|
|
26691
|
+
outputFormat(this)
|
|
26692
|
+
);
|
|
26693
|
+
});
|
|
26694
|
+
bases.command("promotions <production-id>").description("List promotion history for a production base").action(async function(productionId) {
|
|
26695
|
+
const client = await createDataClient();
|
|
26696
|
+
const path31 = `/v1/${pathSegment(productionId, "production base id")}/promotions`;
|
|
26697
|
+
printOutput(
|
|
26698
|
+
await client.collectPages((cursor) => pageOf(path31, cursor)),
|
|
26699
|
+
outputFormat(this)
|
|
26700
|
+
);
|
|
26701
|
+
});
|
|
26702
|
+
bases.command("use <base>").description(
|
|
26703
|
+
"Bind this worktree to a base so push/pull refuse to run against a different one"
|
|
26704
|
+
).action(async function(selector) {
|
|
26705
|
+
const baseId = resolveSelectorToBaseId(selector);
|
|
26706
|
+
const previous = readBaseBinding();
|
|
26707
|
+
if (previous !== baseId) {
|
|
26708
|
+
try {
|
|
26709
|
+
writeBaseBinding(baseId);
|
|
26710
|
+
} catch (err) {
|
|
26711
|
+
throw expected(err instanceof Error ? err.message : String(err));
|
|
26712
|
+
}
|
|
26713
|
+
}
|
|
26714
|
+
if (outputFormat(this) === "json") {
|
|
26715
|
+
printOutput({ base_id: baseId, previous_base_id: previous, changed: previous !== baseId }, "json");
|
|
26716
|
+
return;
|
|
26717
|
+
}
|
|
26718
|
+
if (previous === baseId) {
|
|
26719
|
+
console.log(`Worktree already bound to base ${baseId}.`);
|
|
26720
|
+
} else {
|
|
26721
|
+
console.log(
|
|
26722
|
+
previous ? `Worktree rebound: ${previous} -> ${baseId}.` : `Worktree bound to base ${baseId}.`
|
|
26723
|
+
);
|
|
26724
|
+
}
|
|
26725
|
+
});
|
|
26726
|
+
bases.command("unbind").description("Clear the base binding for this worktree").action(async function() {
|
|
26727
|
+
const previous = readBaseBinding();
|
|
26728
|
+
if (previous) clearBaseBinding();
|
|
26729
|
+
if (outputFormat(this) === "json") {
|
|
26730
|
+
printOutput({ previous_base_id: previous, cleared: previous !== null }, "json");
|
|
26731
|
+
return;
|
|
26732
|
+
}
|
|
26733
|
+
console.log(
|
|
26734
|
+
previous ? `Worktree unbound from base (was: ${previous}).` : "No base binding to clear."
|
|
26735
|
+
);
|
|
26736
|
+
});
|
|
26737
|
+
bases.addCommand(buildBasesTokensCommand());
|
|
26738
|
+
bases.addCommand(buildBasesSecretsCommand());
|
|
26739
|
+
bases.addCommand(buildBasesSqlCommand());
|
|
26740
|
+
bases.addCommand(buildBasesImportCommand());
|
|
26741
|
+
bases.addCommand(buildBasesBatchCommand());
|
|
26742
|
+
bases.addCommand(buildBasesProvidersCommand());
|
|
26743
|
+
bases.addCommand(buildBasesReportCommand());
|
|
26744
|
+
return bases;
|
|
26745
|
+
}
|
|
26746
|
+
function resolveSelectorToBaseId(selector) {
|
|
26747
|
+
const gitRoot = findGitRoot();
|
|
26748
|
+
if (!gitRoot || selector.includes("/") || selector.includes(path30.sep)) return selector;
|
|
26749
|
+
if (!isValidBaseId(selector)) return selector;
|
|
26750
|
+
const metaPath = path30.join(resolveBasesDir(gitRoot), selector, BASE_META_FILE);
|
|
26751
|
+
let meta;
|
|
26752
|
+
try {
|
|
26753
|
+
meta = yaml9.load(fs24.readFileSync(metaPath, "utf-8"));
|
|
26754
|
+
} catch {
|
|
26755
|
+
return selector;
|
|
26756
|
+
}
|
|
26757
|
+
const id = meta?.[BASE_ID_FIELD];
|
|
26758
|
+
return typeof id === "string" && isValidBaseId(id) ? id : selector;
|
|
26759
|
+
}
|
|
26760
|
+
var BASE_META_FILE, BASE_ID_FIELD;
|
|
26761
|
+
var init_bases = __esm({
|
|
26762
|
+
"src/data/commands/bases.ts"() {
|
|
26763
|
+
"use strict";
|
|
26764
|
+
init_batch();
|
|
26765
|
+
init_import();
|
|
26766
|
+
init_providers();
|
|
26767
|
+
init_report2();
|
|
26768
|
+
init_secrets();
|
|
26769
|
+
init_sql();
|
|
26770
|
+
init_tokens();
|
|
26771
|
+
init_client();
|
|
26772
|
+
init_output();
|
|
26773
|
+
init_helpers();
|
|
26774
|
+
init_utils();
|
|
26775
|
+
init_terminal_output();
|
|
26776
|
+
init_workspace();
|
|
26777
|
+
init_layout();
|
|
26778
|
+
init_base_binding();
|
|
26779
|
+
init_base_id();
|
|
26780
|
+
init_expected();
|
|
26781
|
+
BASE_META_FILE = "base.yaml";
|
|
26782
|
+
BASE_ID_FIELD = "base_id";
|
|
26783
|
+
}
|
|
26784
|
+
});
|
|
26785
|
+
|
|
26786
|
+
// src/data/commands/file-types.ts
|
|
26787
|
+
import { Command as Command11 } from "commander";
|
|
26788
|
+
function buildFileTypesCommand() {
|
|
26789
|
+
const fileTypes = new Command11("file-types").description(
|
|
26790
|
+
"Manage file types (Buckets) \u2014 storage policy + optional metadata schema for files"
|
|
26791
|
+
);
|
|
26792
|
+
fileTypes.command("list").description("List all file types in a base").action(async function() {
|
|
26793
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
26794
|
+
const client = await createDataClient();
|
|
26795
|
+
printOutput(await client.request("GET", `/v1/${base}/file-types`), outputFormat(this));
|
|
26796
|
+
});
|
|
26797
|
+
fileTypes.command("get <id>").description("Get a file type").action(async function(id) {
|
|
26798
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
26799
|
+
const client = await createDataClient();
|
|
26800
|
+
printOutput(await client.request("GET", `/v1/${base}/file-types/${pathSegment(id, "file_type")}`), outputFormat(this));
|
|
26801
|
+
});
|
|
26802
|
+
fileTypes.command("upsert <id>").description("Create or update a file type (preview bases only)").requiredOption("--name <name>", "File type name").option("--description <desc>", "Description").option("--max-size <bytes>", "Max bytes per file (0 = unlimited)").option(
|
|
26803
|
+
"--allowed-content-types <json>",
|
|
26804
|
+
"Allowed content-types, JSON array (inline or @filename)"
|
|
26805
|
+
).option("--metadata-schema <json>", "JSON Schema for file metadata (inline JSON or @filename)").action(async function(id, opts) {
|
|
26806
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
26807
|
+
const client = await createDataClient();
|
|
26808
|
+
const body = { name: opts.name };
|
|
26809
|
+
if (opts.description) body.description = opts.description;
|
|
26810
|
+
if (opts.maxSize !== void 0) body.max_size = parseByteCount("--max-size", opts.maxSize);
|
|
26811
|
+
if (opts.allowedContentTypes) {
|
|
26812
|
+
body.allowed_content_types = parseData(opts.allowedContentTypes);
|
|
26813
|
+
}
|
|
26814
|
+
if (opts.metadataSchema) body.metadata_schema = parseData(opts.metadataSchema);
|
|
26815
|
+
printOutput(
|
|
26816
|
+
await client.request("PUT", `/v1/${base}/file-types/${pathSegment(id, "file_type")}`, body),
|
|
26817
|
+
outputFormat(this)
|
|
26818
|
+
);
|
|
26819
|
+
});
|
|
26820
|
+
fileTypes.command("delete <id>").description("Delete a file type (cascades its files)").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
|
|
26821
|
+
if (!opts.yes && !await confirm(
|
|
26822
|
+
`Delete file type "${id}"? This cascades its files and cannot be undone.`
|
|
26823
|
+
)) {
|
|
26824
|
+
console.log("Aborted");
|
|
26825
|
+
return;
|
|
26826
|
+
}
|
|
26827
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
26828
|
+
const client = await createDataClient();
|
|
26829
|
+
await client.request("DELETE", `/v1/${base}/file-types/${pathSegment(id, "file_type")}`);
|
|
26830
|
+
console.log("Deleted");
|
|
26831
|
+
});
|
|
26832
|
+
return fileTypes;
|
|
26833
|
+
}
|
|
26834
|
+
var init_file_types = __esm({
|
|
26835
|
+
"src/data/commands/file-types.ts"() {
|
|
26836
|
+
"use strict";
|
|
26837
|
+
init_client();
|
|
26838
|
+
init_output();
|
|
26839
|
+
init_helpers();
|
|
26840
|
+
init_utils();
|
|
26841
|
+
}
|
|
26842
|
+
});
|
|
26843
|
+
|
|
26844
|
+
// src/data/commands/files.ts
|
|
26845
|
+
import { Command as Command12 } from "commander";
|
|
26846
|
+
import { readFileSync as readFileSync25, writeFileSync as writeFileSync16 } from "fs";
|
|
26847
|
+
import { basename as basename14 } from "path";
|
|
26848
|
+
function renderFileDiff(fileType, filePath, from, to, d) {
|
|
26849
|
+
console.log(sanitizeTerminalText(`${fileType}/${filePath}: v${from} \u2192 v${to}`));
|
|
26850
|
+
const md = d.metadata_delta;
|
|
26851
|
+
if (md) {
|
|
26852
|
+
for (const f of ["path", "size", "content_type", "label"]) {
|
|
26853
|
+
const change = md[f];
|
|
26854
|
+
if (change) console.log(sanitizeTerminalText(` ${f}: ${JSON.stringify(change.from)} \u2192 ${JSON.stringify(change.to)}`));
|
|
26855
|
+
}
|
|
26856
|
+
const meta = md.metadata;
|
|
26857
|
+
for (const [k, v] of Object.entries(meta?.added ?? {})) {
|
|
26858
|
+
console.log(sanitizeTerminalText(` metadata + ${k}: ${JSON.stringify(v)}`));
|
|
26859
|
+
}
|
|
26860
|
+
for (const k of Object.keys(meta?.removed ?? {})) console.log(sanitizeTerminalText(` metadata - ${k}`));
|
|
26861
|
+
for (const [k, v] of Object.entries(meta?.changed ?? {})) {
|
|
26862
|
+
console.log(sanitizeTerminalText(` metadata ~ ${k}: ${JSON.stringify(v.from)} \u2192 ${JSON.stringify(v.to)}`));
|
|
26863
|
+
}
|
|
26864
|
+
}
|
|
26865
|
+
const content = d.content;
|
|
26866
|
+
if (!content?.changed) {
|
|
26867
|
+
console.log(" content: unchanged");
|
|
26868
|
+
return;
|
|
26869
|
+
}
|
|
26870
|
+
if (content.kind === "text" && content.lines) {
|
|
26871
|
+
console.log("");
|
|
26872
|
+
for (const l of content.lines) console.log(sanitizeTerminalText(`${l.op} ${l.text}`));
|
|
26873
|
+
} else {
|
|
26874
|
+
console.log(sanitizeTerminalText(` content: changed (${content.kind})`));
|
|
26875
|
+
}
|
|
26876
|
+
}
|
|
26877
|
+
function downloadTarget(remotePath, to) {
|
|
26878
|
+
if (to) return to;
|
|
26879
|
+
const derived = basename14(remotePath);
|
|
26880
|
+
if (derived === "" || derived === "." || derived === "..") {
|
|
26881
|
+
throw expected(
|
|
26882
|
+
`Cannot derive a local filename from "${remotePath}" \u2014 pass --to <local> to name it.`
|
|
26883
|
+
);
|
|
26884
|
+
}
|
|
26885
|
+
return derived;
|
|
26886
|
+
}
|
|
26887
|
+
function buildFilesCommand() {
|
|
26888
|
+
const files = new Command12("files").description(
|
|
26889
|
+
"Manage files (path-addressed, versioned content) within a file type"
|
|
26890
|
+
);
|
|
26891
|
+
files.command("put <file_type> <path>").description(
|
|
26892
|
+
"Upload a local file to a path (e.g. wayai files put reports q3/summary.pdf --file ./summary.pdf)"
|
|
26893
|
+
).requiredOption("--file <local>", "Local file to upload").option("--content-type <type>", "MIME type", "application/octet-stream").action(async function(fileType, filePath, opts) {
|
|
26894
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
26895
|
+
const body = readFileSync25(opts.file);
|
|
26896
|
+
const client = await createDataClient();
|
|
26897
|
+
printOutput(
|
|
26898
|
+
await client.upload(
|
|
26899
|
+
`/v1/${base}/files/${pathSegment(fileType, "file_type")}/${pathSegments(filePath)}`,
|
|
26900
|
+
body,
|
|
26901
|
+
opts.contentType
|
|
26902
|
+
),
|
|
26903
|
+
outputFormat(this)
|
|
26904
|
+
);
|
|
26905
|
+
});
|
|
26906
|
+
files.command("get <file_type> <path>").description(
|
|
26907
|
+
"Download a file (or --meta for metadata only). --version N reads a specific retained content version."
|
|
26908
|
+
).option("--to <local>", "Local file to save to (defaults to the file basename)").option("--meta", "Print metadata JSON instead of downloading the content").option("--version <n>", "Read a specific content version (default: latest)").action(async function(fileType, filePath, opts) {
|
|
26909
|
+
const encoded = pathSegments(filePath);
|
|
26910
|
+
const versionQs = opts.version !== void 0 ? `version=${encodeURIComponent(opts.version)}` : "";
|
|
26911
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
26912
|
+
if (opts.meta) {
|
|
26913
|
+
const qs = ["meta=1", versionQs].filter(Boolean).join("&");
|
|
26914
|
+
const client2 = await createDataClient();
|
|
26915
|
+
printOutput(
|
|
26916
|
+
await client2.request("GET", `/v1/${base}/files/${pathSegment(fileType, "file_type")}/${encoded}?${qs}`),
|
|
26917
|
+
outputFormat(this)
|
|
26918
|
+
);
|
|
26919
|
+
return;
|
|
26920
|
+
}
|
|
26921
|
+
const out = downloadTarget(filePath, opts.to);
|
|
26922
|
+
const client = await createDataClient();
|
|
26923
|
+
const { bytes } = await client.download(
|
|
26924
|
+
`/v1/${base}/files/${pathSegment(fileType, "file_type")}/${encoded}${versionQs ? `?${versionQs}` : ""}`
|
|
26925
|
+
);
|
|
26926
|
+
writeFileSync16(out, bytes);
|
|
26927
|
+
console.log(`Downloaded to ${out}`);
|
|
26928
|
+
});
|
|
26929
|
+
files.command("history <file_type> <path>").description("List the content versions of a file (newest first)").option("--limit <n>", "Max versions to return").option("--offset <n>", "Pagination offset").action(async function(fileType, filePath, opts) {
|
|
26930
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
26931
|
+
const client = await createDataClient();
|
|
26932
|
+
const qs = new URLSearchParams({ history: "1" });
|
|
26933
|
+
if (opts.limit) qs.set("limit", opts.limit);
|
|
26934
|
+
if (opts.offset) qs.set("offset", opts.offset);
|
|
26935
|
+
printOutput(
|
|
26936
|
+
await client.request(
|
|
26937
|
+
"GET",
|
|
26938
|
+
`/v1/${base}/files/${pathSegment(fileType, "file_type")}/${pathSegments(filePath)}?${qs}`
|
|
26939
|
+
),
|
|
26940
|
+
outputFormat(this)
|
|
26941
|
+
);
|
|
26942
|
+
});
|
|
26943
|
+
files.command("diff <file_type> <path>").description("Diff two content versions of a file \u2014 metadata delta plus a text content diff").requiredOption("--from <n>", "Base version number").requiredOption("--to <n>", "Target version number").action(async function(fileType, filePath, opts) {
|
|
26944
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
26945
|
+
const client = await createDataClient();
|
|
26946
|
+
const qs = new URLSearchParams({ diff: "1", from: opts.from, to: opts.to });
|
|
26947
|
+
const data = await client.request(
|
|
26948
|
+
"GET",
|
|
26949
|
+
`/v1/${base}/files/${pathSegment(fileType, "file_type")}/${pathSegments(filePath)}?${qs}`
|
|
26950
|
+
);
|
|
26951
|
+
if (outputFormat(this) === "json") {
|
|
26952
|
+
printOutput(data, "json");
|
|
26953
|
+
return;
|
|
26954
|
+
}
|
|
26955
|
+
renderFileDiff(fileType, filePath, opts.from, opts.to, data);
|
|
26956
|
+
});
|
|
26957
|
+
files.command("list <file_type>").description("List files by path prefix").option("--prefix <path>", "Path prefix filter (e.g. q3/)").option("--depth <n>", "Max folder depth below the prefix (1 = immediate children)").option("--limit <n>", "Max results").option("--offset <n>", "Pagination offset").action(async function(fileType, opts) {
|
|
26958
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
26959
|
+
const client = await createDataClient();
|
|
26960
|
+
const qs = new URLSearchParams();
|
|
26961
|
+
if (opts.prefix) qs.set("prefix", opts.prefix);
|
|
26962
|
+
if (opts.depth) qs.set("depth", opts.depth);
|
|
26963
|
+
if (opts.limit) qs.set("limit", opts.limit);
|
|
26964
|
+
if (opts.offset) qs.set("offset", opts.offset);
|
|
26965
|
+
const q = qs.toString();
|
|
26966
|
+
printOutput(
|
|
26967
|
+
await client.request("GET", `/v1/${base}/files/${pathSegment(fileType, "file_type")}${q ? `?${q}` : ""}`),
|
|
26968
|
+
outputFormat(this)
|
|
26969
|
+
);
|
|
26970
|
+
});
|
|
26971
|
+
files.command("mv <file_type> <from> <to>").description("Rename/move a file (the blob is untouched)").action(async function(fileType, from, to) {
|
|
26972
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
26973
|
+
const client = await createDataClient();
|
|
26974
|
+
printOutput(
|
|
26975
|
+
await client.request("PATCH", `/v1/${base}/files/${pathSegment(fileType, "file_type")}/${pathSegments(from)}`, {
|
|
26976
|
+
new_path: to
|
|
26977
|
+
}),
|
|
26978
|
+
outputFormat(this)
|
|
26979
|
+
);
|
|
26980
|
+
});
|
|
26981
|
+
files.command("rm <file_type> <path>").description("Delete a file").option("-y, --yes", "Skip confirmation prompt").action(async function(fileType, filePath, opts) {
|
|
26982
|
+
if (!opts.yes && !await confirm(`Delete file "${fileType}/${filePath}"? This cannot be undone.`)) {
|
|
26983
|
+
console.log("Aborted");
|
|
26984
|
+
return;
|
|
26985
|
+
}
|
|
26986
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
26987
|
+
const client = await createDataClient();
|
|
26988
|
+
await client.request("DELETE", `/v1/${base}/files/${pathSegment(fileType, "file_type")}/${pathSegments(filePath)}`);
|
|
26989
|
+
console.log("Deleted");
|
|
26990
|
+
});
|
|
26991
|
+
files.command("mount <file_type>").description(
|
|
26992
|
+
"Mint an S3 credential so any harness can mount this file type as a filesystem (s3fs, cloud mount SDKs). Read-only by default; the secret is shown once."
|
|
26993
|
+
).option("--expires-in <seconds>", "Credential lifetime in seconds (default 30 days, max 90)").option(
|
|
26994
|
+
"--mode <mode>",
|
|
26995
|
+
"read_only (default) or read_write \u2014 the whole-bucket mode when no --path is given"
|
|
26996
|
+
).option(
|
|
26997
|
+
"--path <prefix>",
|
|
26998
|
+
"Confine to a path prefix (repeatable); optionally suffix :read_only|:read_write per prefix (e.g. drafts/:read_write)",
|
|
26999
|
+
(v, acc) => {
|
|
27000
|
+
acc.push(v);
|
|
27001
|
+
return acc;
|
|
27002
|
+
},
|
|
27003
|
+
[]
|
|
27004
|
+
).action(async function(fileType, opts) {
|
|
27005
|
+
const body = { file_type: fileType };
|
|
27006
|
+
if (opts.mode) {
|
|
27007
|
+
if (opts.mode !== "read_only" && opts.mode !== "read_write") {
|
|
27008
|
+
throw expected("--mode must be 'read_only' or 'read_write'.");
|
|
27009
|
+
}
|
|
27010
|
+
body.mode = opts.mode;
|
|
27011
|
+
}
|
|
27012
|
+
if (opts.path.length > 0) {
|
|
27013
|
+
body.paths = opts.path.map((p) => {
|
|
27014
|
+
const m = /^(.*):(read_only|read_write)$/.exec(p);
|
|
27015
|
+
return m ? { prefix: m[1], mode: m[2] } : { prefix: p, mode: opts.mode ?? "read_only" };
|
|
27016
|
+
});
|
|
27017
|
+
}
|
|
27018
|
+
if (opts.expiresIn !== void 0) {
|
|
27019
|
+
const n = Number(opts.expiresIn);
|
|
27020
|
+
if (!Number.isFinite(n)) {
|
|
27021
|
+
throw expected("--expires-in must be a number of seconds.");
|
|
27022
|
+
}
|
|
27023
|
+
body.expires_in = n;
|
|
27024
|
+
}
|
|
27025
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27026
|
+
const client = await createDataClient();
|
|
27027
|
+
printOutput(
|
|
27028
|
+
await client.request("POST", `/v1/${base}/s3-credentials`, body),
|
|
27029
|
+
outputFormat(this)
|
|
27030
|
+
);
|
|
27031
|
+
});
|
|
27032
|
+
return files;
|
|
27033
|
+
}
|
|
27034
|
+
var init_files = __esm({
|
|
27035
|
+
"src/data/commands/files.ts"() {
|
|
27036
|
+
"use strict";
|
|
27037
|
+
init_expected();
|
|
27038
|
+
init_client();
|
|
27039
|
+
init_output();
|
|
27040
|
+
init_helpers();
|
|
27041
|
+
init_utils();
|
|
27042
|
+
init_terminal_output();
|
|
27043
|
+
}
|
|
27044
|
+
});
|
|
27045
|
+
|
|
27046
|
+
// src/data/commands/inbound-webhooks.ts
|
|
27047
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
27048
|
+
import { Command as Command13 } from "commander";
|
|
27049
|
+
function buildInboundWebhooksCommand() {
|
|
27050
|
+
const inboundWebhooks = new Command13("inbound-webhooks").description(
|
|
27051
|
+
"Manage inbound webhook endpoints"
|
|
27052
|
+
);
|
|
27053
|
+
inboundWebhooks.command("create").description("Create a new inbound webhook").requiredOption("--name <name>", "Inbound webhook name").option("--secret-stdin", "Read the HMAC shared secret from stdin (recommended for CI)").option("--id <id>", "Inbound webhook ID (auto-generated if omitted)").option("--record-type-scope <record_types>", "Comma-separated record_type scope").option(
|
|
27054
|
+
"--field-mapping <json>",
|
|
27055
|
+
'Inline mapping applied to the payload before write \u2014 a field_mapping JSON, e.g. {"to_external":{"status":"state"}} (to_external renames auto-invert on read); an explicit inbound-rename block and {"computed":{...}} are also accepted. Mutually exclusive with --source-binding.'
|
|
27056
|
+
).option(
|
|
27057
|
+
"--source-binding <json>",
|
|
27058
|
+
`Reuse a record_type source's field_mapping for the inbound translation \u2014 {"record_type":"<id>","source":"<name>"}. Mutually exclusive with --field-mapping.`
|
|
27059
|
+
).option(
|
|
27060
|
+
"--ingest-auth <json>",
|
|
27061
|
+
'Inbound auth scheme. Default (omitted) = HMAC signature. Static-header senders: {"type":"static_header","header":"X-Account-Key"} \u2014 the configured header is compared to the shared secret (constant-time) instead of an HMAC.'
|
|
27062
|
+
).option(
|
|
27063
|
+
"--hydration <json>",
|
|
27064
|
+
`Make this a reference-style ("notification + fetch") webhook: a thin delivery names a record id; the base fetches the full record via the bound source's read endpoint and stores it. Requires --source-binding and a single native --record-type-scope. JSON: {"id_path":"record_id","event_path":"event","event_map":{"ResourceDeleted":"delete"},"default_op":"upsert","merge":true}. Set "merge":true to refresh only the mapped (upstream-owned) fields on re-sync and preserve the locally-owned fields (status/tags/scores) on the same record.`
|
|
27065
|
+
).action(async function(opts) {
|
|
27066
|
+
const secret = await readSecret(
|
|
27067
|
+
{
|
|
27068
|
+
stdin: opts.secretStdin,
|
|
27069
|
+
// No prompt flag here — the prompt is this command's default.
|
|
27070
|
+
prompt: void 0,
|
|
27071
|
+
stdinFlag: "--secret-stdin",
|
|
27072
|
+
promptHint: "omit --secret-stdin to be prompted"
|
|
27073
|
+
},
|
|
27074
|
+
"HMAC shared secret"
|
|
27075
|
+
);
|
|
27076
|
+
const body = {
|
|
27077
|
+
name: opts.name,
|
|
27078
|
+
secret,
|
|
27079
|
+
enabled: true
|
|
27080
|
+
};
|
|
27081
|
+
if (opts.recordTypeScope) body.record_type_scope = splitList(opts.recordTypeScope);
|
|
27082
|
+
if (opts.fieldMapping) body.field_mapping = parseData(opts.fieldMapping);
|
|
27083
|
+
if (opts.sourceBinding) body.source_binding = parseData(opts.sourceBinding);
|
|
27084
|
+
if (opts.ingestAuth) body.ingest_auth = parseData(opts.ingestAuth);
|
|
27085
|
+
if (opts.hydration) body.hydration = parseData(opts.hydration);
|
|
27086
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27087
|
+
const client = await createDataClient();
|
|
27088
|
+
const inboundWebhookId = opts.id ?? randomUUID2();
|
|
27089
|
+
printOutput(
|
|
27090
|
+
await client.request(
|
|
27091
|
+
"PUT",
|
|
27092
|
+
`/v1/${base}/inbound-webhooks/${pathSegment(inboundWebhookId, "--id")}`,
|
|
27093
|
+
body
|
|
27094
|
+
),
|
|
27095
|
+
outputFormat(this)
|
|
27096
|
+
);
|
|
27097
|
+
});
|
|
27098
|
+
inboundWebhooks.command("get <id>").description("Get an inbound webhook").action(async function(id) {
|
|
27099
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27100
|
+
const client = await createDataClient();
|
|
27101
|
+
printOutput(
|
|
27102
|
+
await client.request("GET", `/v1/${base}/inbound-webhooks/${pathSegment(id, "inbound webhook id")}`),
|
|
27103
|
+
outputFormat(this)
|
|
27104
|
+
);
|
|
27105
|
+
});
|
|
27106
|
+
inboundWebhooks.command("list").description("List all inbound webhooks").action(async function() {
|
|
27107
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27108
|
+
const client = await createDataClient();
|
|
27109
|
+
printOutput(await client.request("GET", `/v1/${base}/inbound-webhooks`), outputFormat(this));
|
|
27110
|
+
});
|
|
27111
|
+
inboundWebhooks.command("deliveries").description(
|
|
27112
|
+
'List hydration delivery status for reference-style ("notification + fetch") webhooks'
|
|
27113
|
+
).option("--status <status>", "Filter by status (pending|delivered|failed|dead)").option("--inbound-webhook <id>", "Filter to one inbound webhook").action(async function(opts) {
|
|
27114
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27115
|
+
const client = await createDataClient();
|
|
27116
|
+
const params = new URLSearchParams();
|
|
27117
|
+
if (opts.status) params.set("status", opts.status);
|
|
27118
|
+
if (opts.inboundWebhook) params.set("inbound_webhook_id", opts.inboundWebhook);
|
|
27119
|
+
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
27120
|
+
printOutput(
|
|
27121
|
+
await client.request("GET", `/v1/${base}/inbound-webhooks/deliveries${qs}`),
|
|
27122
|
+
outputFormat(this)
|
|
27123
|
+
);
|
|
27124
|
+
});
|
|
27125
|
+
inboundWebhooks.command("delete <id>").description("Delete an inbound webhook").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
|
|
27126
|
+
if (!opts.yes && !await confirm(`Delete inbound webhook ${id}? This cannot be undone.`)) {
|
|
27127
|
+
console.log("Aborted");
|
|
27128
|
+
return;
|
|
27129
|
+
}
|
|
27130
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27131
|
+
const client = await createDataClient();
|
|
27132
|
+
await client.request("DELETE", `/v1/${base}/inbound-webhooks/${pathSegment(id, "inbound webhook id")}`);
|
|
27133
|
+
console.log("Deleted");
|
|
27134
|
+
});
|
|
27135
|
+
return inboundWebhooks;
|
|
27136
|
+
}
|
|
27137
|
+
var init_inbound_webhooks = __esm({
|
|
27138
|
+
"src/data/commands/inbound-webhooks.ts"() {
|
|
27139
|
+
"use strict";
|
|
27140
|
+
init_client();
|
|
27141
|
+
init_output();
|
|
27142
|
+
init_helpers();
|
|
27143
|
+
init_utils();
|
|
27144
|
+
}
|
|
27145
|
+
});
|
|
27146
|
+
|
|
27147
|
+
// src/data/commands/query-relationships.ts
|
|
27148
|
+
import { Command as Command14 } from "commander";
|
|
27149
|
+
function buildQueryRelationshipsCommand() {
|
|
27150
|
+
return new Command14("query-relationships").description("Query related records").argument("<record_type>", "RecordType of the source record").argument("<id>", "Source record ID").option("--type <type>", "Filter by relationship type").option("--direction <dir>", "Direction: outgoing, incoming, or both", "both").option("--limit <n>", "Max results", "50").option("--offset <n>", "Skip results", "0").action(async function(recordType2, id, opts) {
|
|
27151
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27152
|
+
const client = await createDataClient();
|
|
27153
|
+
const params = new URLSearchParams();
|
|
27154
|
+
if (opts.type) params.set("rel_type", opts.type);
|
|
27155
|
+
params.set("direction", DIRECTIONS[opts.direction] ?? opts.direction);
|
|
27156
|
+
params.set("limit", opts.limit);
|
|
27157
|
+
params.set("offset", opts.offset);
|
|
27158
|
+
printOutput(
|
|
27159
|
+
await client.request(
|
|
27160
|
+
"GET",
|
|
27161
|
+
`/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(id, "record id")}/related?${params.toString()}`
|
|
27162
|
+
),
|
|
27163
|
+
outputFormat(this)
|
|
27164
|
+
);
|
|
27165
|
+
});
|
|
27166
|
+
}
|
|
27167
|
+
var DIRECTIONS;
|
|
27168
|
+
var init_query_relationships = __esm({
|
|
27169
|
+
"src/data/commands/query-relationships.ts"() {
|
|
27170
|
+
"use strict";
|
|
27171
|
+
init_client();
|
|
27172
|
+
init_output();
|
|
27173
|
+
init_helpers();
|
|
27174
|
+
DIRECTIONS = {
|
|
27175
|
+
outgoing: "source",
|
|
27176
|
+
incoming: "target",
|
|
27177
|
+
both: "both"
|
|
27178
|
+
};
|
|
27179
|
+
}
|
|
27180
|
+
});
|
|
27181
|
+
|
|
27182
|
+
// src/data/commands/record-types.ts
|
|
27183
|
+
import { Command as Command15 } from "commander";
|
|
27184
|
+
function buildRecordTypesCommand() {
|
|
27185
|
+
const recordTypes = new Command15("record-types").description("Manage record_types");
|
|
27186
|
+
recordTypes.command("list").description("List all record_types in a base").action(async function() {
|
|
27187
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27188
|
+
const client = await createDataClient();
|
|
27189
|
+
printOutput(await client.request("GET", `/v1/${base}/record-types`), outputFormat(this));
|
|
27190
|
+
});
|
|
27191
|
+
recordTypes.command("get <id>").description("Get a record_type").action(async function(id) {
|
|
27192
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27193
|
+
const client = await createDataClient();
|
|
27194
|
+
printOutput(await client.request("GET", `/v1/${base}/record-types/${pathSegment(id, "record_type")}`), outputFormat(this));
|
|
27195
|
+
});
|
|
27196
|
+
recordTypes.command("upsert <id>").description("Create or update a record_type").requiredOption("--name <name>", "RecordType name").option("--description <desc>", "Description").option("--schema <json>", "JSON Schema (inline JSON or @filename)").option("--icon <icon>", "Icon name").option("--color <color>", "Hex color").option("--sources <json>", "External sources config (inline JSON or @filename)").action(async function(id, opts) {
|
|
27197
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27198
|
+
const client = await createDataClient();
|
|
27199
|
+
const body = { name: opts.name };
|
|
27200
|
+
if (opts.description) body.description = opts.description;
|
|
27201
|
+
if (opts.schema) body.json_schema = parseData(opts.schema);
|
|
27202
|
+
if (opts.icon) body.icon = opts.icon;
|
|
27203
|
+
if (opts.color) body.color = opts.color;
|
|
27204
|
+
if (opts.sources) body.sources = parseData(opts.sources);
|
|
27205
|
+
printOutput(
|
|
27206
|
+
await client.request("PUT", `/v1/${base}/record-types/${pathSegment(id, "record_type")}`, body),
|
|
27207
|
+
outputFormat(this)
|
|
27208
|
+
);
|
|
27209
|
+
});
|
|
27210
|
+
recordTypes.command("delete <id>").description("Delete a record_type").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
|
|
27211
|
+
if (!opts.yes && !await confirm(`Delete record_type "${id}"? This cannot be undone.`)) {
|
|
27212
|
+
console.log("Aborted");
|
|
27213
|
+
return;
|
|
27214
|
+
}
|
|
27215
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27216
|
+
const client = await createDataClient();
|
|
27217
|
+
await client.request("DELETE", `/v1/${base}/record-types/${pathSegment(id, "record_type")}`);
|
|
27218
|
+
console.log("Deleted");
|
|
27219
|
+
});
|
|
27220
|
+
recordTypes.command("history <id>").description(
|
|
27221
|
+
"View the change history (audit log) for a record_type \u2014 every schema/config version, who changed it, and when"
|
|
27222
|
+
).option("--limit <n>", "Max results").option("--offset <n>", "Pagination offset").option("--diff", "Include field-level diffs between versions").action(async function(id, opts) {
|
|
27223
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27224
|
+
const client = await createDataClient();
|
|
27225
|
+
printOutput(
|
|
27226
|
+
await client.request("GET", `/v1/${base}/audit/record-types/${pathSegment(id, "record_type")}${historyQuery(opts)}`),
|
|
27227
|
+
outputFormat(this)
|
|
27228
|
+
);
|
|
27229
|
+
});
|
|
27230
|
+
return recordTypes;
|
|
27231
|
+
}
|
|
27232
|
+
var init_record_types = __esm({
|
|
27233
|
+
"src/data/commands/record-types.ts"() {
|
|
27234
|
+
"use strict";
|
|
27235
|
+
init_client();
|
|
27236
|
+
init_output();
|
|
27237
|
+
init_helpers();
|
|
27238
|
+
init_utils();
|
|
27239
|
+
}
|
|
27240
|
+
});
|
|
27241
|
+
|
|
27242
|
+
// src/data/commands/records.ts
|
|
27243
|
+
import { Command as Command16 } from "commander";
|
|
27244
|
+
function recordKey(id, externalSource) {
|
|
27245
|
+
return externalSource ? foreignSegment(id, "external id") : pathSegment(id, "record id");
|
|
27246
|
+
}
|
|
27247
|
+
function buildRecordsCommand() {
|
|
27248
|
+
const records = new Command16("records").description("Manage records");
|
|
27249
|
+
records.command("upsert <record_type>").description("Create or update a record").requiredOption("--data <json>", "Record data (inline JSON or @filename)").option("--id <id>", "Internal record ID (UUID) to update a known record").option("--external-id <id>", "External/agent-supplied ID for idempotent upsert").option("--external-source <source>", "Source system for external_id (e.g. stripe)").action(async function(recordType2, opts) {
|
|
27250
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27251
|
+
const client = await createDataClient();
|
|
27252
|
+
const body = { data: parseData(opts.data) };
|
|
27253
|
+
if (opts.externalId) body.external_id = opts.externalId;
|
|
27254
|
+
if (opts.externalSource) body.external_source = opts.externalSource;
|
|
27255
|
+
const path31 = opts.id ? `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(opts.id, "--id")}` : `/v1/${base}/records/${pathSegment(recordType2, "record_type")}`;
|
|
27256
|
+
printOutput(await client.request("PUT", path31, body), outputFormat(this));
|
|
27257
|
+
});
|
|
27258
|
+
records.command("query <record_type>").description("List/search records: exact filters + fuzzy `search`, sorting, pagination").option(
|
|
27259
|
+
"--external-source <source>",
|
|
27260
|
+
"List from an external source (proxy-backed record_type). Combine with --filter when the source forwards filters."
|
|
27261
|
+
).option(
|
|
27262
|
+
"--filter <json>",
|
|
27263
|
+
"Filter DSL expression (inline JSON or @filename). Use the `search` operator for fuzzy matching."
|
|
27264
|
+
).option(
|
|
27265
|
+
"--sort <json>",
|
|
27266
|
+
'Sort expressions (inline JSON or @filename), e.g. [{"field":"data.created","direction":"desc"}]'
|
|
27267
|
+
).option("--fields <list>", "Comma-separated field projection").option("--limit <n>", "Max results").option("--offset <n>", "Pagination offset").action(async function(recordType2, opts) {
|
|
27268
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27269
|
+
const client = await createDataClient();
|
|
27270
|
+
const qs = new URLSearchParams();
|
|
27271
|
+
if (opts.externalSource) qs.set("external_source", opts.externalSource);
|
|
27272
|
+
if (opts.filter) qs.set("filter", JSON.stringify(parseData(opts.filter)));
|
|
27273
|
+
if (opts.sort) qs.set("sort", JSON.stringify(parseData(opts.sort)));
|
|
27274
|
+
if (opts.fields) qs.set("fields", opts.fields);
|
|
27275
|
+
if (opts.limit) qs.set("limit", opts.limit);
|
|
27276
|
+
if (opts.offset) qs.set("offset", opts.offset);
|
|
27277
|
+
const q = qs.toString();
|
|
27278
|
+
printOutput(
|
|
27279
|
+
await client.request("GET", `/v1/${base}/records/${pathSegment(recordType2, "record_type")}${q ? `?${q}` : ""}`),
|
|
27280
|
+
outputFormat(this)
|
|
27281
|
+
);
|
|
27282
|
+
});
|
|
27283
|
+
records.command("get <record_type> <id>").description(
|
|
27284
|
+
"Get a record by internal ID, or by external_id (pass --external-source to scope it)"
|
|
27285
|
+
).option(
|
|
27286
|
+
"--external-source <source>",
|
|
27287
|
+
"Treat <id> as an external_id scoped to this source system (e.g. stripe)"
|
|
27288
|
+
).action(async function(recordType2, id, opts) {
|
|
27289
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27290
|
+
const client = await createDataClient();
|
|
27291
|
+
let path31 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
|
|
27292
|
+
if (opts.externalSource) path31 += `?external_source=${encodeURIComponent(opts.externalSource)}`;
|
|
27293
|
+
printOutput(await client.request("GET", path31), outputFormat(this));
|
|
27294
|
+
});
|
|
27295
|
+
records.command("delete <record_type> <id>").description(
|
|
27296
|
+
"Delete a record by internal ID, or by external_id (pass --external-source to scope it)"
|
|
27297
|
+
).option("-y, --yes", "Skip confirmation prompt").option(
|
|
27298
|
+
"--external-source <source>",
|
|
27299
|
+
"Treat <id> as an external_id scoped to this source system (e.g. stripe)"
|
|
27300
|
+
).action(async function(recordType2, id, opts) {
|
|
27301
|
+
if (!opts.yes && !await confirm(`Delete record ${recordType2}/${id}? This cannot be undone.`)) {
|
|
27302
|
+
console.log("Aborted");
|
|
27303
|
+
return;
|
|
27304
|
+
}
|
|
27305
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27306
|
+
const client = await createDataClient();
|
|
27307
|
+
let path31 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
|
|
27308
|
+
if (opts.externalSource) {
|
|
27309
|
+
path31 += `?external_source=${encodeURIComponent(opts.externalSource)}&external_id=${encodeURIComponent(id)}`;
|
|
27310
|
+
}
|
|
27311
|
+
await client.request("DELETE", path31);
|
|
27312
|
+
console.log("Deleted");
|
|
27313
|
+
});
|
|
27314
|
+
records.command("history <id>").description(
|
|
27315
|
+
"View the change history (audit log) for a record \u2014 every version, who changed it, and when"
|
|
27316
|
+
).option("--limit <n>", "Max results").option("--offset <n>", "Pagination offset").option("--diff", "Include field-level diffs between versions").action(async function(id, opts) {
|
|
27317
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27318
|
+
const client = await createDataClient();
|
|
27319
|
+
printOutput(
|
|
27320
|
+
await client.request("GET", `/v1/${base}/audit/records/${pathSegment(id, "record id")}${historyQuery(opts)}`),
|
|
27321
|
+
outputFormat(this)
|
|
27322
|
+
);
|
|
27323
|
+
});
|
|
27324
|
+
return records;
|
|
27325
|
+
}
|
|
27326
|
+
var init_records = __esm({
|
|
27327
|
+
"src/data/commands/records.ts"() {
|
|
27328
|
+
"use strict";
|
|
27329
|
+
init_client();
|
|
27330
|
+
init_output();
|
|
27331
|
+
init_helpers();
|
|
27332
|
+
init_utils();
|
|
27333
|
+
}
|
|
27334
|
+
});
|
|
27335
|
+
|
|
27336
|
+
// src/data/commands/relationship-types.ts
|
|
27337
|
+
import { Command as Command17 } from "commander";
|
|
27338
|
+
function buildRelationshipTypesCommand() {
|
|
27339
|
+
const relationshipTypes = new Command17("relationship-types").description(
|
|
27340
|
+
"Manage relationship types (the schema for a relationship's metadata)"
|
|
27341
|
+
);
|
|
27342
|
+
relationshipTypes.command("list").description("List all relationship types in a base").action(async function() {
|
|
27343
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27344
|
+
const client = await createDataClient();
|
|
27345
|
+
printOutput(
|
|
27346
|
+
await client.request("GET", `/v1/${base}/relationship-types`),
|
|
27347
|
+
outputFormat(this)
|
|
27348
|
+
);
|
|
25124
27349
|
});
|
|
25125
|
-
|
|
25126
|
-
|
|
25127
|
-
).requiredOption("--name <name>", "New display name").action(async function(id, opts) {
|
|
27350
|
+
relationshipTypes.command("get <rel_type>").description("Get a relationship type").action(async function(relType) {
|
|
27351
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25128
27352
|
const client = await createDataClient();
|
|
25129
27353
|
printOutput(
|
|
25130
|
-
await client.request("
|
|
27354
|
+
await client.request("GET", `/v1/${base}/relationship-types/${pathSegment(relType, "rel_type")}`),
|
|
25131
27355
|
outputFormat(this)
|
|
25132
27356
|
);
|
|
25133
27357
|
});
|
|
25134
|
-
|
|
27358
|
+
relationshipTypes.command("upsert <rel_type>").description("Create or update a relationship type").option("--description <desc>", "Description").option(
|
|
27359
|
+
"--schema <json>",
|
|
27360
|
+
"JSON Schema for the relationship data (inline JSON or @filename). Omit to allow any data."
|
|
27361
|
+
).option(
|
|
27362
|
+
"--source-record-types <json>",
|
|
27363
|
+
"JSON array of allowed source record_types (inline JSON or @filename)"
|
|
27364
|
+
).option(
|
|
27365
|
+
"--target-record-types <json>",
|
|
27366
|
+
"JSON array of allowed target record_types (inline JSON or @filename)"
|
|
27367
|
+
).action(async function(relType, opts) {
|
|
27368
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25135
27369
|
const client = await createDataClient();
|
|
27370
|
+
const body = {};
|
|
27371
|
+
if (opts.description) body.description = opts.description;
|
|
27372
|
+
if (opts.schema) body.data_schema = parseData(opts.schema);
|
|
27373
|
+
if (opts.sourceRecordTypes) body.source_record_types = parseData(opts.sourceRecordTypes);
|
|
27374
|
+
if (opts.targetRecordTypes) body.target_record_types = parseData(opts.targetRecordTypes);
|
|
25136
27375
|
printOutput(
|
|
25137
|
-
await client.request("PUT", `/v1/
|
|
27376
|
+
await client.request("PUT", `/v1/${base}/relationship-types/${pathSegment(relType, "rel_type")}`, body),
|
|
25138
27377
|
outputFormat(this)
|
|
25139
27378
|
);
|
|
25140
27379
|
});
|
|
25141
|
-
|
|
25142
|
-
|
|
25143
|
-
).option("-y, --yes", "Skip confirmation prompt").option(
|
|
25144
|
-
"--purge",
|
|
25145
|
-
"Preview bases only. Permanently destroy the stored records, relationships, file metadata and config of this base instead of tombstoning it. The id is then retired and cannot be recreated"
|
|
25146
|
-
).action(async function(id, opts) {
|
|
25147
|
-
const prompt3 = opts.purge ? `Purge base "${id}"? Its records, relationships, file metadata and config are destroyed permanently, and the id "${id}" is retired for good.` : `Delete base "${id}"? This cannot be undone.`;
|
|
25148
|
-
if (!opts.yes && !await confirm(prompt3)) {
|
|
27380
|
+
relationshipTypes.command("delete <rel_type>").description("Delete a relationship type").option("-y, --yes", "Skip confirmation prompt").action(async function(relType, opts) {
|
|
27381
|
+
if (!opts.yes && !await confirm(`Delete relationship type "${relType}"? This cannot be undone.`)) {
|
|
25149
27382
|
console.log("Aborted");
|
|
25150
27383
|
return;
|
|
25151
27384
|
}
|
|
27385
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25152
27386
|
const client = await createDataClient();
|
|
25153
|
-
await client.request("DELETE", `/v1/
|
|
25154
|
-
|
|
27387
|
+
await client.request("DELETE", `/v1/${base}/relationship-types/${pathSegment(relType, "rel_type")}`);
|
|
27388
|
+
console.log("Deleted");
|
|
25155
27389
|
});
|
|
25156
|
-
|
|
25157
|
-
"
|
|
25158
|
-
).
|
|
25159
|
-
"--
|
|
25160
|
-
|
|
27390
|
+
relationshipTypes.command("history <id>").description(
|
|
27391
|
+
"View the change history (audit log) for a relationship type \u2014 every config version, who changed it, and when"
|
|
27392
|
+
).option("--limit <n>", "Max results").option("--offset <n>", "Pagination offset").option("--diff", "Include field-level diffs between versions").action(async function(id, opts) {
|
|
27393
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27394
|
+
const client = await createDataClient();
|
|
27395
|
+
printOutput(
|
|
27396
|
+
await client.request(
|
|
27397
|
+
"GET",
|
|
27398
|
+
`/v1/${base}/audit/relationship-types/${pathSegment(id, "rel_type")}${historyQuery(opts)}`
|
|
27399
|
+
),
|
|
27400
|
+
outputFormat(this)
|
|
27401
|
+
);
|
|
27402
|
+
});
|
|
27403
|
+
return relationshipTypes;
|
|
27404
|
+
}
|
|
27405
|
+
var init_relationship_types = __esm({
|
|
27406
|
+
"src/data/commands/relationship-types.ts"() {
|
|
27407
|
+
"use strict";
|
|
27408
|
+
init_client();
|
|
27409
|
+
init_output();
|
|
27410
|
+
init_helpers();
|
|
27411
|
+
init_utils();
|
|
27412
|
+
}
|
|
27413
|
+
});
|
|
27414
|
+
|
|
27415
|
+
// src/data/commands/relationships.ts
|
|
27416
|
+
import { Command as Command18 } from "commander";
|
|
27417
|
+
function relationshipKey(id, relType) {
|
|
27418
|
+
return relType ? foreignSegment(id, "external id") : pathSegment(id, "relationship id");
|
|
27419
|
+
}
|
|
27420
|
+
function endpointBodyFields(role, byId, byExternal, externalSource) {
|
|
27421
|
+
if ((byId ? 1 : 0) + (byExternal ? 1 : 0) !== 1) {
|
|
27422
|
+
throw expected(`Provide exactly one of --${role} or --${role}-external.`);
|
|
27423
|
+
}
|
|
27424
|
+
const ref = byId ?? byExternal;
|
|
27425
|
+
const segments = ref.split("/");
|
|
27426
|
+
const [recordType2, key] = segments;
|
|
27427
|
+
if (segments.length !== 2 || !recordType2 || !key) {
|
|
27428
|
+
throw expected(
|
|
27429
|
+
`--${role}${byId ? "" : "-external"} must be <record_type/${byId ? "id" : "external_id"}>, got '${ref}'.`
|
|
27430
|
+
);
|
|
27431
|
+
}
|
|
27432
|
+
if (externalSource && !byExternal) {
|
|
27433
|
+
throw expected(`--${role}-external-source requires --${role}-external.`);
|
|
27434
|
+
}
|
|
27435
|
+
const fields = { [`${role}_record_type`]: recordType2 };
|
|
27436
|
+
if (byId) {
|
|
27437
|
+
fields[`${role}_id`] = key;
|
|
27438
|
+
} else {
|
|
27439
|
+
fields[`${role}_external_id`] = key;
|
|
27440
|
+
if (externalSource) fields[`${role}_external_source`] = externalSource;
|
|
27441
|
+
}
|
|
27442
|
+
return fields;
|
|
27443
|
+
}
|
|
27444
|
+
function byKeyQuery(relType, externalSource) {
|
|
27445
|
+
const qs = new URLSearchParams();
|
|
27446
|
+
if (relType) qs.set("rel_type", relType);
|
|
27447
|
+
if (externalSource) qs.set("external_source", externalSource);
|
|
27448
|
+
const q = qs.toString();
|
|
27449
|
+
return q ? `?${q}` : "";
|
|
27450
|
+
}
|
|
27451
|
+
function buildRelationshipsCommand() {
|
|
27452
|
+
const relationships = new Command18("relationships").description(
|
|
27453
|
+
"Manage relationships between records"
|
|
27454
|
+
);
|
|
27455
|
+
relationships.command("upsert").description("Create or update a relationship").option("--source <record_type/id>", "Source record by internal id").option(
|
|
27456
|
+
"--source-external <record_type/external_id>",
|
|
27457
|
+
"Source record by external_id (the record must exist)"
|
|
27458
|
+
).option("--source-external-source <name>", "Source system scoping --source-external").option("--target <record_type/id>", "Target record by internal id").option(
|
|
27459
|
+
"--target-external <record_type/external_id>",
|
|
27460
|
+
"Target record by external_id (the record must exist)"
|
|
27461
|
+
).option("--target-external-source <name>", "Source system scoping --target-external").requiredOption("--type <type>", "Relationship type").option("--id <id>", "Relationship ID").option(
|
|
27462
|
+
"--external-id <key>",
|
|
27463
|
+
"The relationship's own external key: a retried upsert with the same key updates the first row instead of duplicating it"
|
|
27464
|
+
).option("--external-source <name>", "Source system scoping the relationship's --external-id").option("--data <json>", "Relationship metadata (inline JSON or @filename)").action(async function(opts) {
|
|
27465
|
+
const body = {
|
|
27466
|
+
rel_type: opts.type,
|
|
27467
|
+
...endpointBodyFields("source", opts.source, opts.sourceExternal, opts.sourceExternalSource),
|
|
27468
|
+
...endpointBodyFields("target", opts.target, opts.targetExternal, opts.targetExternalSource)
|
|
27469
|
+
};
|
|
27470
|
+
if (opts.id) body.id = opts.id;
|
|
27471
|
+
if (opts.externalId) body.external_id = opts.externalId;
|
|
27472
|
+
if (opts.externalSource) body.external_source = opts.externalSource;
|
|
27473
|
+
if (opts.data) body.data = parseData(opts.data);
|
|
27474
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27475
|
+
const client = await createDataClient();
|
|
27476
|
+
const path31 = opts.id ? `/v1/${base}/relationships/${pathSegment(opts.id, "--id")}` : `/v1/${base}/relationships`;
|
|
27477
|
+
printOutput(await client.request("PUT", path31, body), outputFormat(this));
|
|
27478
|
+
});
|
|
27479
|
+
relationships.command("get <id>").description(
|
|
27480
|
+
"Get a relationship by ID (or by its external key: pass the external_id with --rel-type)"
|
|
25161
27481
|
).option(
|
|
25162
|
-
"--
|
|
25163
|
-
"
|
|
25164
|
-
).action(async function(
|
|
25165
|
-
const
|
|
27482
|
+
"--rel-type <type>",
|
|
27483
|
+
"Treat <id> as the relationship external_id, scoped to this relationship type"
|
|
27484
|
+
).option("--external-source <name>", "Source system scoping the external_id (with --rel-type)").action(async function(id, opts) {
|
|
27485
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25166
27486
|
const client = await createDataClient();
|
|
25167
27487
|
printOutput(
|
|
25168
|
-
await client.request(
|
|
25169
|
-
|
|
25170
|
-
|
|
25171
|
-
|
|
25172
|
-
...opts.createOnly ? { create_only: true } : {}
|
|
25173
|
-
}),
|
|
27488
|
+
await client.request(
|
|
27489
|
+
"GET",
|
|
27490
|
+
`/v1/${base}/relationships/${relationshipKey(id, opts.relType)}${byKeyQuery(opts.relType, opts.externalSource)}`
|
|
27491
|
+
),
|
|
25174
27492
|
outputFormat(this)
|
|
25175
27493
|
);
|
|
25176
27494
|
});
|
|
25177
|
-
|
|
27495
|
+
relationships.command("delete <id>").description(
|
|
27496
|
+
"Delete a relationship by ID (or by its external key: pass the external_id with --rel-type)"
|
|
27497
|
+
).option(
|
|
27498
|
+
"--rel-type <type>",
|
|
27499
|
+
"Treat <id> as the relationship external_id, scoped to this relationship type"
|
|
27500
|
+
).option("--external-source <name>", "Source system scoping the external_id (with --rel-type)").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
|
|
27501
|
+
if (!opts.yes && !await confirm(`Delete relationship ${id}? This cannot be undone.`)) {
|
|
27502
|
+
console.log("Aborted");
|
|
27503
|
+
return;
|
|
27504
|
+
}
|
|
27505
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27506
|
+
const client = await createDataClient();
|
|
27507
|
+
await client.request(
|
|
27508
|
+
"DELETE",
|
|
27509
|
+
`/v1/${base}/relationships/${relationshipKey(id, opts.relType)}${byKeyQuery(opts.relType, opts.externalSource)}`
|
|
27510
|
+
);
|
|
27511
|
+
console.log("Deleted");
|
|
27512
|
+
});
|
|
27513
|
+
relationships.command("history <id>").description(
|
|
27514
|
+
"View the change history (audit log) for a relationship \u2014 every version, who changed it, and when"
|
|
27515
|
+
).option("--limit <n>", "Max results").option("--offset <n>", "Pagination offset").option("--diff", "Include field-level diffs between versions").action(async function(id, opts) {
|
|
27516
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25178
27517
|
const client = await createDataClient();
|
|
25179
|
-
const path31 = `/v1/${pathSegment(originId, "origin base id")}/previews`;
|
|
25180
27518
|
printOutput(
|
|
25181
|
-
await client.
|
|
27519
|
+
await client.request("GET", `/v1/${base}/audit/relationships/${pathSegment(id, "relationship id")}${historyQuery(opts)}`),
|
|
25182
27520
|
outputFormat(this)
|
|
25183
27521
|
);
|
|
25184
27522
|
});
|
|
25185
|
-
|
|
27523
|
+
return relationships;
|
|
27524
|
+
}
|
|
27525
|
+
var init_relationships = __esm({
|
|
27526
|
+
"src/data/commands/relationships.ts"() {
|
|
27527
|
+
"use strict";
|
|
27528
|
+
init_expected();
|
|
27529
|
+
init_client();
|
|
27530
|
+
init_output();
|
|
27531
|
+
init_helpers();
|
|
27532
|
+
init_utils();
|
|
27533
|
+
}
|
|
27534
|
+
});
|
|
27535
|
+
|
|
27536
|
+
// src/data/commands/seed.ts
|
|
27537
|
+
import { Command as Command19 } from "commander";
|
|
27538
|
+
function buildSeedCommand() {
|
|
27539
|
+
const seed = new Command19("seed").description(
|
|
27540
|
+
"Apply, reset, or clear a seed fixture on a preview base (hermetic eval data)"
|
|
27541
|
+
);
|
|
27542
|
+
seed.command("apply <name>").description(
|
|
27543
|
+
"Idempotently upsert the fixture (keyed by external_id, so re-applying never twins). Preview-only."
|
|
27544
|
+
).action(async function(name) {
|
|
27545
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25186
27546
|
const client = await createDataClient();
|
|
25187
|
-
|
|
25188
|
-
|
|
25189
|
-
|
|
25190
|
-
|
|
25191
|
-
triggers: opts.triggers,
|
|
25192
|
-
inbound_webhooks: opts.inboundWebhooks
|
|
25193
|
-
});
|
|
25194
|
-
printOutput(data, outputFormat(this));
|
|
25195
|
-
const warnings = data?.source_credential_warnings ?? [];
|
|
25196
|
-
if (warnings.length > 0) {
|
|
25197
|
-
console.warn(
|
|
25198
|
-
"\nExternal source credentials are NOT carried over by promotion \u2014 set these on production before the source will work:"
|
|
25199
|
-
);
|
|
25200
|
-
for (const w of warnings) console.warn(` - ${sanitizeTerminalText(w.message)}`);
|
|
25201
|
-
}
|
|
27547
|
+
printOutput(
|
|
27548
|
+
await client.request("POST", `/v1/${base}/seeds/${pathSegment(name, "fixture name")}/apply`),
|
|
27549
|
+
outputFormat(this)
|
|
27550
|
+
);
|
|
25202
27551
|
});
|
|
25203
|
-
|
|
27552
|
+
seed.command("reset <name>").description(
|
|
27553
|
+
"Restore the declared baseline: re-apply in place (fixing trigger-mutated records) and prune owned rows the fixture no longer declares. Record types listed in exclusive_record_types are treated as authoritative \u2014 reset also removes their non-declared records (agent-created residue) and any relationships referencing them. Preview-only."
|
|
27554
|
+
).action(async function(name) {
|
|
27555
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25204
27556
|
const client = await createDataClient();
|
|
25205
27557
|
printOutput(
|
|
25206
|
-
await client.request("POST", `/v1/${pathSegment(
|
|
25207
|
-
|
|
27558
|
+
await client.request("POST", `/v1/${base}/seeds/${pathSegment(name, "fixture name")}/reset`),
|
|
27559
|
+
outputFormat(this)
|
|
27560
|
+
);
|
|
27561
|
+
});
|
|
27562
|
+
seed.command("clear <name>").description("Delete every row the fixture owns (teardown). Preview-only.").action(async function(name) {
|
|
27563
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27564
|
+
const client = await createDataClient();
|
|
27565
|
+
printOutput(
|
|
27566
|
+
await client.request("POST", `/v1/${base}/seeds/${pathSegment(name, "fixture name")}/clear`),
|
|
27567
|
+
outputFormat(this)
|
|
27568
|
+
);
|
|
27569
|
+
});
|
|
27570
|
+
seed.command("list").description("List the seed fixtures defined on a base").action(async function() {
|
|
27571
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27572
|
+
const client = await createDataClient();
|
|
27573
|
+
printOutput(await client.request("GET", `/v1/${base}/seeds`), outputFormat(this));
|
|
27574
|
+
});
|
|
27575
|
+
seed.command("get <name>").description("Show a seed fixture definition (its declared records and relationships)").action(async function(name) {
|
|
27576
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27577
|
+
const client = await createDataClient();
|
|
27578
|
+
printOutput(await client.request("GET", `/v1/${base}/seeds/${pathSegment(name, "fixture name")}`), outputFormat(this));
|
|
27579
|
+
});
|
|
27580
|
+
const lease = seed.command("lease").description("Coordinate an isolated fixture lease");
|
|
27581
|
+
lease.command("acquire <fixture>").description("Reset a fixture and atomically acquire the preview base for one eval run").requiredOption("--lease-id <uuidv7>", "Client-generated UUIDv7 (permanently single-use)").requiredOption(
|
|
27582
|
+
"--owner-ref <ref>",
|
|
27583
|
+
"Safe ID: 1-200 letters/digits or ._:@/+~=- (descriptive only)"
|
|
27584
|
+
).action(async function(fixture, opts) {
|
|
27585
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27586
|
+
const client = await createDataClient();
|
|
27587
|
+
printOutput(
|
|
27588
|
+
await client.request("POST", `/v1/${base}/seed-leases/acquire`, {
|
|
27589
|
+
lease_id: opts.leaseId,
|
|
27590
|
+
fixture,
|
|
27591
|
+
owner_ref: opts.ownerRef
|
|
25208
27592
|
}),
|
|
25209
27593
|
outputFormat(this)
|
|
25210
27594
|
);
|
|
25211
27595
|
});
|
|
25212
|
-
|
|
27596
|
+
lease.command("status").description("Inspect your lease, or learn only whether another actor has locked the base").option("--lease-id <uuidv7>", "Inspect a specific lease ID").action(async function(opts) {
|
|
27597
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
25213
27598
|
const client = await createDataClient();
|
|
25214
|
-
const
|
|
27599
|
+
const params = new URLSearchParams();
|
|
27600
|
+
if (opts.leaseId !== void 0) params.set("lease_id", opts.leaseId);
|
|
27601
|
+
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
25215
27602
|
printOutput(
|
|
25216
|
-
await client.
|
|
27603
|
+
await client.request("GET", `/v1/${base}/seed-leases/status${query}`),
|
|
25217
27604
|
outputFormat(this)
|
|
25218
27605
|
);
|
|
25219
27606
|
});
|
|
25220
|
-
|
|
25221
|
-
|
|
25222
|
-
|
|
25223
|
-
|
|
25224
|
-
|
|
25225
|
-
|
|
25226
|
-
|
|
25227
|
-
|
|
25228
|
-
}
|
|
25229
|
-
|
|
27607
|
+
lease.command("release <fixture>").description("Clear fixture-owned data and release the matching lease").requiredOption("--lease-id <uuidv7>", "The UUIDv7 used to acquire the lease").requiredOption("--owner-ref <ref>", "Same safe ID: 1-200 letters/digits or ._:@/+~=-").action(async function(fixture, opts) {
|
|
27608
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27609
|
+
const client = await createDataClient();
|
|
27610
|
+
printOutput(
|
|
27611
|
+
await client.request("POST", `/v1/${base}/seed-leases/release`, {
|
|
27612
|
+
lease_id: opts.leaseId,
|
|
27613
|
+
fixture,
|
|
27614
|
+
owner_ref: opts.ownerRef
|
|
27615
|
+
}),
|
|
27616
|
+
outputFormat(this)
|
|
27617
|
+
);
|
|
27618
|
+
});
|
|
27619
|
+
return seed;
|
|
27620
|
+
}
|
|
27621
|
+
var init_seed = __esm({
|
|
27622
|
+
"src/data/commands/seed.ts"() {
|
|
27623
|
+
"use strict";
|
|
27624
|
+
init_client();
|
|
27625
|
+
init_output();
|
|
27626
|
+
init_helpers();
|
|
27627
|
+
}
|
|
27628
|
+
});
|
|
27629
|
+
|
|
27630
|
+
// src/data/commands/toolsets.ts
|
|
27631
|
+
import { Command as Command20 } from "commander";
|
|
27632
|
+
function collect(value, previous) {
|
|
27633
|
+
return [...previous, value];
|
|
27634
|
+
}
|
|
27635
|
+
function parseSpec(spec) {
|
|
27636
|
+
const [key, opsStr] = spec.split(":");
|
|
27637
|
+
if (!key || !opsStr) {
|
|
27638
|
+
throw expected(`Invalid spec '${spec}'. Expected format: name:op1,op2`);
|
|
27639
|
+
}
|
|
27640
|
+
return { key, ops: splitList(opsStr) };
|
|
27641
|
+
}
|
|
27642
|
+
function parseActionRef(spec) {
|
|
27643
|
+
const [action, actionName] = spec.split("=");
|
|
27644
|
+
if (!action) {
|
|
27645
|
+
throw expected(`Invalid --action '${spec}'. Expected an Action id, or <action_id>=<surface_name>.`);
|
|
27646
|
+
}
|
|
27647
|
+
return actionName ? { action, action_name: actionName } : { action };
|
|
27648
|
+
}
|
|
27649
|
+
function parseRelSpec(spec) {
|
|
27650
|
+
const { key, ops } = parseSpec(spec);
|
|
27651
|
+
return { rel_type: key, operations: ops };
|
|
27652
|
+
}
|
|
27653
|
+
function buildToolsetsCommand() {
|
|
27654
|
+
const toolsets = new Command20("toolsets").description("Manage MCP toolsets");
|
|
27655
|
+
toolsets.command("upsert <slug>").description("Create or update an MCP toolset").option("--name <name>", "Toolset display name (required unless using --config)").option("--description <desc>", "Toolset description").option(
|
|
27656
|
+
"--action <ref>",
|
|
27657
|
+
"Reference an Action by id, optionally renamed: <action_id> or <action_id>=<surface_name> (repeatable). Create Actions first with `wayai actions upsert`.",
|
|
27658
|
+
collect,
|
|
27659
|
+
[]
|
|
27660
|
+
).option("--relationship <spec>", "Relationship tool spec: rel_type:op1,op2 (repeatable)", collect, []).option("--batch <spec>", "Batch operation spec: record_type_or_rel:op1,op2 (repeatable)", collect, []).option("--sql-query", "Enable sql_query tool").option(
|
|
27661
|
+
"--config <json>",
|
|
27662
|
+
"Full toolset config as JSON. A toolset composes first-class Actions by reference \u2014 its `actions` are refs {action: <action_id>, action_name?, description_override?}. Create the Actions with `wayai actions upsert` (each carries its own record_type, operation, and guard/shaping config: filterable_fields, writable_fields, precondition, binding). Use @file.json to read from file."
|
|
27663
|
+
).option(
|
|
27664
|
+
"--mint-token",
|
|
27665
|
+
"After upsert, also mint a toolset-bound (consumer) token for this base and print it (handy for sandbox-testing the preview toolset)"
|
|
27666
|
+
).action(async function(slug, opts) {
|
|
27667
|
+
let body;
|
|
27668
|
+
if (opts.config) {
|
|
27669
|
+
body = parseData(opts.config);
|
|
27670
|
+
} else {
|
|
27671
|
+
if (!opts.name) {
|
|
27672
|
+
throw expected("--name is required (or use --config for full JSON config)");
|
|
27673
|
+
}
|
|
27674
|
+
body = { name: opts.name, actions: opts.action.map(parseActionRef) };
|
|
27675
|
+
if (opts.description) body.description = opts.description;
|
|
27676
|
+
if (opts.relationship.length > 0) {
|
|
27677
|
+
body.relationships = opts.relationship.map(parseRelSpec);
|
|
27678
|
+
}
|
|
27679
|
+
if (opts.batch.length > 0) {
|
|
27680
|
+
const operations = {};
|
|
27681
|
+
for (const spec of opts.batch) {
|
|
27682
|
+
const parsed = parseSpec(spec);
|
|
27683
|
+
operations[parsed.key] = parsed.ops;
|
|
27684
|
+
}
|
|
27685
|
+
body.batch = { enabled: true, operations };
|
|
25230
27686
|
}
|
|
27687
|
+
if (opts.sqlQuery) body.sql_query = true;
|
|
25231
27688
|
}
|
|
25232
|
-
|
|
25233
|
-
|
|
27689
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27690
|
+
const client = await createDataClient();
|
|
27691
|
+
const format = outputFormat(this);
|
|
27692
|
+
printOutput(await client.request("PUT", `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}`, body), format);
|
|
27693
|
+
if (opts.mintToken) {
|
|
27694
|
+
const tokenData = await client.request("POST", "/v1/tokens", {
|
|
27695
|
+
name: `${slug} consumer`,
|
|
27696
|
+
toolset_binding: { base_id: base, toolset_id: slug }
|
|
27697
|
+
});
|
|
27698
|
+
if (format === "json") {
|
|
27699
|
+
printOutput(tokenData, format);
|
|
27700
|
+
} else {
|
|
27701
|
+
console.log("\nToolset-bound token:");
|
|
27702
|
+
printOutput(tokenData, format);
|
|
27703
|
+
console.log(`MCP URL: ${toolsetMcpUrl(slug)}`);
|
|
27704
|
+
}
|
|
27705
|
+
}
|
|
27706
|
+
});
|
|
27707
|
+
toolsets.command("get <slug>").description("Get a toolset").option("--resolved", "Include resolved record_type schemas").action(async function(slug, opts) {
|
|
27708
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27709
|
+
const client = await createDataClient();
|
|
27710
|
+
const path31 = opts.resolved ? `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}/resolved` : `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}`;
|
|
27711
|
+
printOutput(await client.request("GET", path31), outputFormat(this));
|
|
27712
|
+
});
|
|
27713
|
+
toolsets.command("list").description("List all toolsets").action(async function() {
|
|
27714
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27715
|
+
const client = await createDataClient();
|
|
27716
|
+
printOutput(await client.request("GET", `/v1/${base}/toolsets`), outputFormat(this));
|
|
27717
|
+
});
|
|
27718
|
+
toolsets.command("delete <slug>").description("Delete a toolset").option("-y, --yes", "Skip confirmation prompt").action(async function(slug, opts) {
|
|
27719
|
+
if (!opts.yes && !await confirm(`Delete toolset "${slug}"? This cannot be undone.`)) {
|
|
27720
|
+
console.log("Aborted");
|
|
25234
27721
|
return;
|
|
25235
27722
|
}
|
|
25236
|
-
|
|
25237
|
-
|
|
27723
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27724
|
+
const client = await createDataClient();
|
|
27725
|
+
await client.request("DELETE", `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}`);
|
|
27726
|
+
console.log("Deleted");
|
|
27727
|
+
});
|
|
27728
|
+
toolsets.command("url <slug>").description("Get the MCP connection URL for a toolset").action((slug) => {
|
|
27729
|
+
console.log(toolsetMcpUrl(slug));
|
|
27730
|
+
});
|
|
27731
|
+
return toolsets;
|
|
27732
|
+
}
|
|
27733
|
+
var init_toolsets = __esm({
|
|
27734
|
+
"src/data/commands/toolsets.ts"() {
|
|
27735
|
+
"use strict";
|
|
27736
|
+
init_expected();
|
|
27737
|
+
init_client();
|
|
27738
|
+
init_output();
|
|
27739
|
+
init_helpers();
|
|
27740
|
+
init_utils();
|
|
27741
|
+
}
|
|
27742
|
+
});
|
|
27743
|
+
|
|
27744
|
+
// src/data/commands/triggers.ts
|
|
27745
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
27746
|
+
import { Command as Command21 } from "commander";
|
|
27747
|
+
function buildTriggersCommand() {
|
|
27748
|
+
const triggers = new Command21("triggers").description(
|
|
27749
|
+
"Manage triggers (outbound webhooks, internal writes, external writes)"
|
|
27750
|
+
);
|
|
27751
|
+
triggers.command("create").description(
|
|
27752
|
+
"Create a trigger (a webhook, an internal_write that patches a referenced record, or an external_write that mirrors the record to an external source)"
|
|
27753
|
+
).requiredOption("--name <name>", "Trigger name").requiredOption("--events <events>", "Comma-separated event types").option("--url <url>", "Webhook target URL (webhook actions)").option("--secret-stdin", "Read the webhook HMAC signing secret from stdin (webhook actions)").option("--secret-prompt", "Prompt for the webhook HMAC signing secret, masked (webhook actions)").option(
|
|
27754
|
+
"--action <json>",
|
|
27755
|
+
`Action JSON. webhook {"type":"webhook","url":...,"secret":...}; internal_write {"type":"internal_write","target":"slots","match":{"source_field":"data.slot_id"},"patch":{"status":"busy"},"precondition":{"field":"data.status","op":"eq","value":"free"},"mode":"async"} (mode: "async" applies after the write, "transactional" applies atomically so a precondition miss rolls the write back); external_write {"type":"external_write","record_type":"upstream_appointments","source":"legacy","op":"create","watched_fields":"mapped"} (writes the triggering record through that source's write path \u2014 translation, endpoint, auth \u2014 and links the upstream-assigned id back as external_id; "record_type" defaults to the triggering record_type, "op" defaults from the event; "watched_fields" scopes UPDATE dispatch to writes that changed an upstream-mapped field \u2014 "mapped" = the source's to_external keys, or an explicit array of data fields \u2014 so an overlay-only write skips the redundant upstream push; omit to fire on every update)`
|
|
27756
|
+
).option("--id <id>", "Trigger ID (auto-generated if omitted)").option("--record-type-scope <record_types>", "Comma-separated record_type scope").option("--filter <json>", "Filter expression (JSON)").option(
|
|
27757
|
+
"--skip-cascade-writes <bool>",
|
|
27758
|
+
"Skip firing on cascade-originated (internal_write) writes (default true)"
|
|
27759
|
+
).action(async function(opts) {
|
|
27760
|
+
if (!opts.action && !opts.url) {
|
|
27761
|
+
throw expected("Provide --url (webhook) or --action <json> (e.g. an internal_write action).");
|
|
27762
|
+
}
|
|
27763
|
+
const body = {
|
|
27764
|
+
name: opts.name,
|
|
27765
|
+
events: splitList(opts.events),
|
|
27766
|
+
enabled: true
|
|
27767
|
+
};
|
|
27768
|
+
if (opts.action) {
|
|
27769
|
+
body.action = parseData(opts.action);
|
|
25238
27770
|
} else {
|
|
25239
|
-
|
|
25240
|
-
|
|
25241
|
-
|
|
27771
|
+
body.url = opts.url;
|
|
27772
|
+
if (opts.secretStdin || opts.secretPrompt) {
|
|
27773
|
+
body.secret = await readSecret(
|
|
27774
|
+
{
|
|
27775
|
+
stdin: opts.secretStdin,
|
|
27776
|
+
prompt: opts.secretPrompt,
|
|
27777
|
+
stdinFlag: "--secret-stdin",
|
|
27778
|
+
promptHint: "--secret-prompt"
|
|
27779
|
+
},
|
|
27780
|
+
"Webhook signing secret"
|
|
27781
|
+
);
|
|
27782
|
+
}
|
|
27783
|
+
}
|
|
27784
|
+
if (opts.recordTypeScope) body.record_type_scope = splitList(opts.recordTypeScope);
|
|
27785
|
+
if (opts.filter) body.filter = parseData(opts.filter);
|
|
27786
|
+
if (opts.skipCascadeWrites !== void 0) {
|
|
27787
|
+
body.skip_cascade_writes = opts.skipCascadeWrites !== "false";
|
|
25242
27788
|
}
|
|
27789
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27790
|
+
const client = await createDataClient();
|
|
27791
|
+
const triggerId = opts.id ?? randomUUID3();
|
|
27792
|
+
printOutput(
|
|
27793
|
+
await client.request("PUT", `/v1/${base}/triggers/${pathSegment(triggerId, "--id")}`, body),
|
|
27794
|
+
outputFormat(this)
|
|
27795
|
+
);
|
|
25243
27796
|
});
|
|
25244
|
-
|
|
25245
|
-
const
|
|
25246
|
-
|
|
25247
|
-
|
|
25248
|
-
|
|
27797
|
+
triggers.command("get <id>").description("Get a trigger").action(async function(id) {
|
|
27798
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27799
|
+
const client = await createDataClient();
|
|
27800
|
+
printOutput(await client.request("GET", `/v1/${base}/triggers/${pathSegment(id, "trigger id")}`), outputFormat(this));
|
|
27801
|
+
});
|
|
27802
|
+
triggers.command("list").description("List all triggers").action(async function() {
|
|
27803
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27804
|
+
const client = await createDataClient();
|
|
27805
|
+
printOutput(await client.request("GET", `/v1/${base}/triggers`), outputFormat(this));
|
|
27806
|
+
});
|
|
27807
|
+
triggers.command("deliveries").description("List trigger delivery attempts (status, retries, errors)").option("--status <status>", "Filter by status (pending | delivered | failed | dead)").option("--trigger-id <id>", "Filter by trigger id").action(async function(opts) {
|
|
27808
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27809
|
+
const client = await createDataClient();
|
|
27810
|
+
const params = new URLSearchParams();
|
|
27811
|
+
if (opts.status) params.set("status", opts.status);
|
|
27812
|
+
if (opts.triggerId) params.set("trigger_id", opts.triggerId);
|
|
27813
|
+
const qs = params.toString();
|
|
27814
|
+
printOutput(
|
|
27815
|
+
await client.request("GET", `/v1/${base}/triggers/deliveries${qs ? `?${qs}` : ""}`),
|
|
27816
|
+
outputFormat(this)
|
|
27817
|
+
);
|
|
27818
|
+
});
|
|
27819
|
+
triggers.command("delete <id>").description("Delete a trigger").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
|
|
27820
|
+
if (!opts.yes && !await confirm(`Delete trigger ${id}? This cannot be undone.`)) {
|
|
27821
|
+
console.log("Aborted");
|
|
25249
27822
|
return;
|
|
25250
27823
|
}
|
|
25251
|
-
|
|
25252
|
-
|
|
25253
|
-
);
|
|
27824
|
+
const base = pathSegment(requireBase(this), "--base");
|
|
27825
|
+
const client = await createDataClient();
|
|
27826
|
+
await client.request("DELETE", `/v1/${base}/triggers/${pathSegment(id, "trigger id")}`);
|
|
27827
|
+
console.log("Deleted");
|
|
25254
27828
|
});
|
|
25255
|
-
return
|
|
25256
|
-
}
|
|
25257
|
-
function resolveSelectorToBaseId(selector) {
|
|
25258
|
-
const gitRoot = findGitRoot();
|
|
25259
|
-
if (!gitRoot || selector.includes("/") || selector.includes(path30.sep)) return selector;
|
|
25260
|
-
if (!isValidBaseId(selector)) return selector;
|
|
25261
|
-
const metaPath = path30.join(resolveBasesDir(gitRoot), selector, BASE_META_FILE);
|
|
25262
|
-
let meta;
|
|
25263
|
-
try {
|
|
25264
|
-
meta = yaml9.load(fs24.readFileSync(metaPath, "utf-8"));
|
|
25265
|
-
} catch {
|
|
25266
|
-
return selector;
|
|
25267
|
-
}
|
|
25268
|
-
const id = meta?.[BASE_ID_FIELD];
|
|
25269
|
-
return typeof id === "string" && isValidBaseId(id) ? id : selector;
|
|
27829
|
+
return triggers;
|
|
25270
27830
|
}
|
|
25271
|
-
var
|
|
25272
|
-
|
|
25273
|
-
"src/data/commands/bases.ts"() {
|
|
27831
|
+
var init_triggers = __esm({
|
|
27832
|
+
"src/data/commands/triggers.ts"() {
|
|
25274
27833
|
"use strict";
|
|
27834
|
+
init_expected();
|
|
25275
27835
|
init_client();
|
|
25276
27836
|
init_output();
|
|
25277
27837
|
init_helpers();
|
|
25278
27838
|
init_utils();
|
|
25279
|
-
init_terminal_output();
|
|
25280
|
-
init_workspace();
|
|
25281
|
-
init_layout();
|
|
25282
|
-
init_base_binding();
|
|
25283
|
-
init_base_id();
|
|
25284
|
-
init_expected();
|
|
25285
|
-
BASE_META_FILE = "base.yaml";
|
|
25286
|
-
BASE_ID_FIELD = "base_id";
|
|
25287
27839
|
}
|
|
25288
27840
|
});
|
|
25289
27841
|
|
|
@@ -25292,27 +27844,55 @@ var program_exports = {};
|
|
|
25292
27844
|
__export(program_exports, {
|
|
25293
27845
|
DATA_NAMESPACES: () => DATA_NAMESPACES,
|
|
25294
27846
|
buildDataProgram: () => buildDataProgram,
|
|
27847
|
+
handleParserExit: () => handleParserExit,
|
|
25295
27848
|
isDataNamespace: () => isDataNamespace,
|
|
27849
|
+
routeErrorsToCli: () => routeErrorsToCli,
|
|
25296
27850
|
runDataCommand: () => runDataCommand,
|
|
25297
27851
|
withBaseOption: () => withBaseOption,
|
|
25298
27852
|
withDataGlobals: () => withDataGlobals
|
|
25299
27853
|
});
|
|
25300
|
-
import { Command as
|
|
27854
|
+
import { Command as Command22 } from "commander";
|
|
25301
27855
|
function withDataGlobals(command2) {
|
|
25302
27856
|
return command2.option("--org <uuid>", "Organization to operate against (overrides .wayai.yaml)").option("--output <format>", "Output format: json or table", "table").option("--json", "Shorthand for --output json");
|
|
25303
27857
|
}
|
|
25304
|
-
function withBaseOption(command2) {
|
|
25305
|
-
return command2.option("--base <id>", "Base id (or set WAYAI_BASE)");
|
|
25306
|
-
}
|
|
25307
27858
|
function routeErrorsToCli(command2) {
|
|
25308
27859
|
command2.exitOverride().configureOutput({ outputError: () => {
|
|
25309
27860
|
} });
|
|
25310
27861
|
for (const child of command2.commands) routeErrorsToCli(child);
|
|
25311
27862
|
return command2;
|
|
25312
27863
|
}
|
|
27864
|
+
function handleParserExit(err) {
|
|
27865
|
+
const { code, exitCode } = err ?? {};
|
|
27866
|
+
if (code === "commander.helpDisplayed" || code === "commander.version") return;
|
|
27867
|
+
if (code === "commander.help") {
|
|
27868
|
+
process.exitCode = exitCode || 1;
|
|
27869
|
+
return;
|
|
27870
|
+
}
|
|
27871
|
+
if (typeof code === "string" && code.startsWith("commander.")) {
|
|
27872
|
+
throw expected(err instanceof Error ? err.message : String(err));
|
|
27873
|
+
}
|
|
27874
|
+
throw err;
|
|
27875
|
+
}
|
|
25313
27876
|
function buildDataProgram() {
|
|
25314
|
-
const program = new
|
|
27877
|
+
const program = new Command22("wayai");
|
|
25315
27878
|
program.addCommand(withDataGlobals(buildBasesCommand()));
|
|
27879
|
+
for (const build of [
|
|
27880
|
+
buildRecordsCommand,
|
|
27881
|
+
buildRecordTypesCommand,
|
|
27882
|
+
buildRelationshipsCommand,
|
|
27883
|
+
buildRelationshipTypesCommand,
|
|
27884
|
+
buildQueryRelationshipsCommand,
|
|
27885
|
+
buildFilesCommand,
|
|
27886
|
+
buildFileTypesCommand,
|
|
27887
|
+
buildAttachmentsCommand,
|
|
27888
|
+
buildToolsetsCommand,
|
|
27889
|
+
buildActionsCommand,
|
|
27890
|
+
buildTriggersCommand,
|
|
27891
|
+
buildInboundWebhooksCommand,
|
|
27892
|
+
buildSeedCommand
|
|
27893
|
+
]) {
|
|
27894
|
+
program.addCommand(withBaseOption(withDataGlobals(build())));
|
|
27895
|
+
}
|
|
25316
27896
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
25317
27897
|
setDataOrgOverride(globals(actionCommand).org);
|
|
25318
27898
|
});
|
|
@@ -25326,27 +27906,32 @@ async function runDataCommand(namespace, args2) {
|
|
|
25326
27906
|
try {
|
|
25327
27907
|
await program.parseAsync(["node", "wayai", namespace, ...args2]);
|
|
25328
27908
|
} catch (err) {
|
|
25329
|
-
|
|
25330
|
-
if (code === "commander.helpDisplayed" || code === "commander.version") return;
|
|
25331
|
-
if (code === "commander.help") {
|
|
25332
|
-
process.exitCode = exitCode || 1;
|
|
25333
|
-
return;
|
|
25334
|
-
}
|
|
25335
|
-
if (typeof code === "string" && code.startsWith("commander.")) {
|
|
25336
|
-
throw expected(err instanceof Error ? err.message : String(err));
|
|
25337
|
-
}
|
|
25338
|
-
throw err;
|
|
27909
|
+
handleParserExit(err);
|
|
25339
27910
|
}
|
|
25340
27911
|
}
|
|
25341
27912
|
var init_program = __esm({
|
|
25342
27913
|
"src/data/program.ts"() {
|
|
25343
27914
|
"use strict";
|
|
27915
|
+
init_actions();
|
|
27916
|
+
init_attachments();
|
|
25344
27917
|
init_bases();
|
|
27918
|
+
init_file_types();
|
|
27919
|
+
init_files();
|
|
27920
|
+
init_inbound_webhooks();
|
|
27921
|
+
init_query_relationships();
|
|
27922
|
+
init_record_types();
|
|
27923
|
+
init_records();
|
|
27924
|
+
init_relationship_types();
|
|
27925
|
+
init_relationships();
|
|
27926
|
+
init_seed();
|
|
27927
|
+
init_toolsets();
|
|
27928
|
+
init_triggers();
|
|
25345
27929
|
init_helpers();
|
|
25346
27930
|
init_expected();
|
|
25347
27931
|
init_org_context();
|
|
25348
27932
|
init_registry();
|
|
25349
27933
|
init_registry();
|
|
27934
|
+
init_helpers();
|
|
25350
27935
|
}
|
|
25351
27936
|
});
|
|
25352
27937
|
|
|
@@ -25356,9 +27941,9 @@ init_errors2();
|
|
|
25356
27941
|
init_mask_secrets();
|
|
25357
27942
|
init_utils();
|
|
25358
27943
|
init_registry();
|
|
25359
|
-
import { readFileSync as
|
|
25360
|
-
import { fileURLToPath as
|
|
25361
|
-
import { dirname as
|
|
27944
|
+
import { readFileSync as readFileSync26 } from "fs";
|
|
27945
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
27946
|
+
import { dirname as dirname10, join as join29 } from "path";
|
|
25362
27947
|
|
|
25363
27948
|
// src/lib/version-refresh.ts
|
|
25364
27949
|
init_version_cache();
|
|
@@ -25513,14 +28098,14 @@ Run \`wayai admin skill install\` to update.`);
|
|
|
25513
28098
|
}
|
|
25514
28099
|
|
|
25515
28100
|
// src/index.ts
|
|
25516
|
-
var __dirname =
|
|
25517
|
-
var pkg = JSON.parse(
|
|
28101
|
+
var __dirname = dirname10(fileURLToPath3(import.meta.url));
|
|
28102
|
+
var pkg = JSON.parse(readFileSync26(join29(__dirname, "..", "package.json"), "utf-8"));
|
|
25518
28103
|
var [, , command, ...args] = process.argv;
|
|
25519
28104
|
var isBackgroundRefresh = command === REFRESH_COMMAND;
|
|
25520
28105
|
if (!isBackgroundRefresh) initSentry(command, pkg.version);
|
|
25521
28106
|
async function main() {
|
|
25522
28107
|
if (shouldInterceptHelp(command, args, /* @__PURE__ */ new Set([...OWN_HELP_COMMANDS, ...DATA_NAMESPACES]))) {
|
|
25523
|
-
printHelp3();
|
|
28108
|
+
await printHelp3();
|
|
25524
28109
|
return;
|
|
25525
28110
|
}
|
|
25526
28111
|
if (command && !wantsHelp(args)) {
|
|
@@ -25712,7 +28297,7 @@ async function main() {
|
|
|
25712
28297
|
case "--help":
|
|
25713
28298
|
case "-h":
|
|
25714
28299
|
case void 0:
|
|
25715
|
-
printHelp3();
|
|
28300
|
+
await printHelp3();
|
|
25716
28301
|
break;
|
|
25717
28302
|
case "--version":
|
|
25718
28303
|
case "-v":
|
|
@@ -25725,12 +28310,18 @@ async function main() {
|
|
|
25725
28310
|
break;
|
|
25726
28311
|
}
|
|
25727
28312
|
console.error(`Unknown command: ${command}`);
|
|
25728
|
-
printHelp3();
|
|
28313
|
+
await printHelp3();
|
|
25729
28314
|
process.exit(1);
|
|
25730
28315
|
}
|
|
25731
28316
|
}
|
|
25732
28317
|
}
|
|
25733
|
-
function
|
|
28318
|
+
async function dataHelpBlock() {
|
|
28319
|
+
const { buildDataProgram: buildDataProgram2 } = await Promise.resolve().then(() => (init_program(), program_exports));
|
|
28320
|
+
const namespaces = buildDataProgram2().commands;
|
|
28321
|
+
const width = Math.max(...namespaces.map((c) => c.name().length));
|
|
28322
|
+
return namespaces.map((c) => ` ${c.name().padEnd(width)} ${c.description()}`.trimEnd()).join("\n");
|
|
28323
|
+
}
|
|
28324
|
+
async function printHelp3() {
|
|
25734
28325
|
console.log(`
|
|
25735
28326
|
wayai \u2014 WayAI CLI
|
|
25736
28327
|
|
|
@@ -25778,12 +28369,11 @@ Commands:
|
|
|
25778
28369
|
report edit Amend your own pending report (title/description/error/steps/context)
|
|
25779
28370
|
update Update CLI to the latest version
|
|
25780
28371
|
|
|
25781
|
-
Data
|
|
25782
|
-
|
|
28372
|
+
Data \u2014 one namespace per entity. Run \`wayai <namespace> --help\` for its tree.
|
|
28373
|
+
${await dataHelpBlock()}
|
|
25783
28374
|
bases promote Promote a preview base to production (distinct from \`publish\`, which promotes a hub)
|
|
25784
28375
|
bases use <base> Bind this worktree to a base (\`wayai use\` binds a hub)
|
|
25785
28376
|
bases unbind Clear this worktree's base binding
|
|
25786
|
-
Run \`wayai bases --help\` for the full tree.
|
|
25787
28377
|
|
|
25788
28378
|
Flags:
|
|
25789
28379
|
--yes, -y Skip confirmation prompts (useful for CI and scripting)
|
|
@@ -25854,7 +28444,7 @@ async function runForeground() {
|
|
|
25854
28444
|
try {
|
|
25855
28445
|
await main();
|
|
25856
28446
|
if (command && !SKIP_UPDATE_CHECK.includes(command) && !wantsHelp(args)) {
|
|
25857
|
-
showUpdateNudges(pkg.version,
|
|
28447
|
+
showUpdateNudges(pkg.version, fileURLToPath3(import.meta.url));
|
|
25858
28448
|
}
|
|
25859
28449
|
await closeSentry();
|
|
25860
28450
|
} catch (err) {
|