@wayai/cli 0.3.136 → 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 +1224 -85
- 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
|
[
|
|
@@ -15052,7 +15058,7 @@ var init_registry = __esm({
|
|
|
15052
15058
|
disposition: "namespaced",
|
|
15053
15059
|
surviving: "bases tokens",
|
|
15054
15060
|
clause: 3,
|
|
15055
|
-
shipped:
|
|
15061
|
+
shipped: true,
|
|
15056
15062
|
reason: "Mints base tokens; WayAI's own token surface mints platform tokens."
|
|
15057
15063
|
},
|
|
15058
15064
|
{
|
|
@@ -15060,7 +15066,7 @@ var init_registry = __esm({
|
|
|
15060
15066
|
disposition: "namespaced",
|
|
15061
15067
|
surviving: "bases secrets",
|
|
15062
15068
|
clause: 3,
|
|
15063
|
-
shipped:
|
|
15069
|
+
shipped: true,
|
|
15064
15070
|
reason: "Base org vault; WayAI has connection credentials."
|
|
15065
15071
|
},
|
|
15066
15072
|
{
|
|
@@ -15068,7 +15074,7 @@ var init_registry = __esm({
|
|
|
15068
15074
|
disposition: "namespaced",
|
|
15069
15075
|
surviving: "bases sql",
|
|
15070
15076
|
clause: 3,
|
|
15071
|
-
shipped:
|
|
15077
|
+
shipped: true,
|
|
15072
15078
|
reason: "WayAI already has tenant SQL over conversation analytics."
|
|
15073
15079
|
},
|
|
15074
15080
|
{
|
|
@@ -15076,7 +15082,7 @@ var init_registry = __esm({
|
|
|
15076
15082
|
disposition: "namespaced",
|
|
15077
15083
|
surviving: "bases import",
|
|
15078
15084
|
clause: 3,
|
|
15079
|
-
shipped:
|
|
15085
|
+
shipped: true,
|
|
15080
15086
|
reason: "WayAI has outbound-contact import."
|
|
15081
15087
|
},
|
|
15082
15088
|
{
|
|
@@ -15084,7 +15090,7 @@ var init_registry = __esm({
|
|
|
15084
15090
|
disposition: "namespaced",
|
|
15085
15091
|
surviving: "bases batch",
|
|
15086
15092
|
clause: 3,
|
|
15087
|
-
shipped:
|
|
15093
|
+
shipped: true,
|
|
15088
15094
|
reason: "Too generic to stand alone."
|
|
15089
15095
|
},
|
|
15090
15096
|
{
|
|
@@ -15092,7 +15098,7 @@ var init_registry = __esm({
|
|
|
15092
15098
|
disposition: "namespaced",
|
|
15093
15099
|
surviving: "bases providers",
|
|
15094
15100
|
clause: 3,
|
|
15095
|
-
shipped:
|
|
15101
|
+
shipped: true,
|
|
15096
15102
|
reason: "WayAI's provider is a connector."
|
|
15097
15103
|
},
|
|
15098
15104
|
{
|
|
@@ -15101,14 +15107,14 @@ var init_registry = __esm({
|
|
|
15101
15107
|
surviving: "admin bases",
|
|
15102
15108
|
clause: 3,
|
|
15103
15109
|
shipped: false,
|
|
15104
|
-
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."
|
|
15105
15111
|
},
|
|
15106
15112
|
{
|
|
15107
15113
|
original: "report",
|
|
15108
15114
|
disposition: "namespaced",
|
|
15109
15115
|
surviving: "bases report",
|
|
15110
15116
|
clause: 3,
|
|
15111
|
-
shipped:
|
|
15117
|
+
shipped: true,
|
|
15112
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."
|
|
15113
15119
|
},
|
|
15114
15120
|
// --- Neither: one verb each, routing by workspace subtree. ---
|
|
@@ -16868,15 +16874,15 @@ function scanResourceFiles(dir, prefix = "") {
|
|
|
16868
16874
|
files.push(...scanResourceFiles(path12.join(dir, entry.name), relPath));
|
|
16869
16875
|
} else if (entry.isFile()) {
|
|
16870
16876
|
const fullPath = path12.join(dir, entry.name);
|
|
16871
|
-
const
|
|
16872
|
-
if (
|
|
16873
|
-
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)`);
|
|
16874
16880
|
continue;
|
|
16875
16881
|
}
|
|
16876
16882
|
const fileEntry = {
|
|
16877
16883
|
path: relPath,
|
|
16878
16884
|
mime_type: guessMimeType(entry.name),
|
|
16879
|
-
file_size:
|
|
16885
|
+
file_size: stat2.size
|
|
16880
16886
|
};
|
|
16881
16887
|
const data = fs10.readFileSync(fullPath);
|
|
16882
16888
|
fileEntry.hash = computeHash(data);
|
|
@@ -17068,34 +17074,34 @@ function resolveTurnAttachments(hubFolder, turn, label, bytesByHash) {
|
|
|
17068
17074
|
if (abs !== root && !abs.startsWith(root + path13.sep)) {
|
|
17069
17075
|
throw expected(`${label}: attachment "${entry}" escapes the hub folder.`);
|
|
17070
17076
|
}
|
|
17071
|
-
let
|
|
17077
|
+
let stat2;
|
|
17072
17078
|
try {
|
|
17073
|
-
|
|
17079
|
+
stat2 = fs11.statSync(abs);
|
|
17074
17080
|
} catch {
|
|
17075
17081
|
}
|
|
17076
|
-
if (!
|
|
17082
|
+
if (!stat2?.isFile()) {
|
|
17077
17083
|
throw expected(`${label}: attachment "${entry}" not found.`);
|
|
17078
17084
|
}
|
|
17079
17085
|
if (!realpathContained(root, abs)) {
|
|
17080
17086
|
throw expected(`${label}: attachment "${entry}" resolves outside the hub folder (symlinks are not allowed).`);
|
|
17081
17087
|
}
|
|
17082
|
-
if (
|
|
17088
|
+
if (stat2.size > MAX_RESOURCE_FILE_SIZE3) {
|
|
17083
17089
|
throw expected(
|
|
17084
|
-
`${label}: attachment "${entry}" is ${(
|
|
17090
|
+
`${label}: attachment "${entry}" is ${(stat2.size / 1024 / 1024).toFixed(1)}MB, over the 10MB limit.`
|
|
17085
17091
|
);
|
|
17086
17092
|
}
|
|
17087
17093
|
const data = fs11.readFileSync(abs);
|
|
17088
17094
|
const hash = computeHash(data);
|
|
17089
17095
|
const fileName = path13.basename(abs);
|
|
17090
17096
|
const mimeType = guessMimeType(fileName) ?? "application/octet-stream";
|
|
17091
|
-
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 });
|
|
17092
17098
|
if (!bytesByHash.has(hash)) {
|
|
17093
17099
|
bytesByHash.set(hash, {
|
|
17094
17100
|
hash,
|
|
17095
17101
|
content_base64: data.toString("base64"),
|
|
17096
17102
|
file_name: fileName,
|
|
17097
17103
|
mime_type: mimeType,
|
|
17098
|
-
file_size:
|
|
17104
|
+
file_size: stat2.size
|
|
17099
17105
|
});
|
|
17100
17106
|
}
|
|
17101
17107
|
}
|
|
@@ -19396,18 +19402,18 @@ __export(send_message_exports, {
|
|
|
19396
19402
|
import * as fs19 from "fs";
|
|
19397
19403
|
import * as path25 from "path";
|
|
19398
19404
|
function statAttachment(filePath) {
|
|
19399
|
-
let
|
|
19405
|
+
let stat2;
|
|
19400
19406
|
try {
|
|
19401
|
-
|
|
19407
|
+
stat2 = fs19.statSync(filePath);
|
|
19402
19408
|
} catch {
|
|
19403
19409
|
console.error(`Error: file not found: ${filePath}`);
|
|
19404
19410
|
process.exit(1);
|
|
19405
19411
|
}
|
|
19406
|
-
if (
|
|
19412
|
+
if (stat2.isDirectory()) {
|
|
19407
19413
|
console.error(`Error: not a file: ${filePath}`);
|
|
19408
19414
|
process.exit(1);
|
|
19409
19415
|
}
|
|
19410
|
-
return { filePath, size:
|
|
19416
|
+
return { filePath, size: stat2.size };
|
|
19411
19417
|
}
|
|
19412
19418
|
function readAttachment(filePath, size) {
|
|
19413
19419
|
const fileName = path25.basename(filePath);
|
|
@@ -20674,7 +20680,7 @@ async function stopEvalSessionWithRetries(stop, opts = {}) {
|
|
|
20674
20680
|
const attempts = opts.attempts ?? STOP_ATTEMPTS;
|
|
20675
20681
|
const attemptTimeoutMs = opts.attemptTimeoutMs ?? STOP_ATTEMPT_TIMEOUT_MS;
|
|
20676
20682
|
const backoffMs = opts.backoffMs ?? STOP_BACKOFF_MS;
|
|
20677
|
-
const
|
|
20683
|
+
const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
20678
20684
|
let lastError = new Error("Stop was never attempted");
|
|
20679
20685
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
20680
20686
|
let timeout;
|
|
@@ -20695,7 +20701,7 @@ async function stopEvalSessionWithRetries(stop, opts = {}) {
|
|
|
20695
20701
|
if (timeout !== void 0) clearTimeout(timeout);
|
|
20696
20702
|
}
|
|
20697
20703
|
const delay2 = backoffMs[attempt - 1];
|
|
20698
|
-
if (attempt < attempts && delay2) await
|
|
20704
|
+
if (attempt < attempts && delay2) await sleep3(delay2);
|
|
20699
20705
|
}
|
|
20700
20706
|
throw lastError;
|
|
20701
20707
|
}
|
|
@@ -24981,8 +24987,8 @@ async function createDataClient(orgId) {
|
|
|
24981
24987
|
}
|
|
24982
24988
|
const org = selected ?? readRepoConfig()?.organization_id;
|
|
24983
24989
|
return {
|
|
24984
|
-
async request(method,
|
|
24985
|
-
const envelope = await api.dataRequest(method,
|
|
24990
|
+
async request(method, path31, body) {
|
|
24991
|
+
const envelope = await api.dataRequest(method, path31, body, org);
|
|
24986
24992
|
return envelope.data;
|
|
24987
24993
|
},
|
|
24988
24994
|
async collectPages(makePath) {
|
|
@@ -25011,6 +25017,23 @@ async function createDataClient(orgId) {
|
|
|
25011
25017
|
}
|
|
25012
25018
|
};
|
|
25013
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
|
+
}
|
|
25014
25037
|
var MAX_PAGES;
|
|
25015
25038
|
var init_client = __esm({
|
|
25016
25039
|
"src/data/client.ts"() {
|
|
@@ -25080,6 +25103,36 @@ function printFields(obj) {
|
|
|
25080
25103
|
console.log(`${key.padEnd(width)} ${formatCell2(value)}`);
|
|
25081
25104
|
}
|
|
25082
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
|
+
}
|
|
25083
25136
|
function cellAt(row, column) {
|
|
25084
25137
|
return formatCell2(row?.[column]);
|
|
25085
25138
|
}
|
|
@@ -25126,11 +25179,23 @@ function foreignSegment(value, label = "id") {
|
|
|
25126
25179
|
function pathSegments(value, label = "path") {
|
|
25127
25180
|
return value.split("/").map((segment) => foreignSegment(segment, label)).join("/");
|
|
25128
25181
|
}
|
|
25129
|
-
function parseData(data) {
|
|
25130
|
-
|
|
25131
|
-
|
|
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)}`);
|
|
25132
25198
|
}
|
|
25133
|
-
return JSON.parse(data);
|
|
25134
25199
|
}
|
|
25135
25200
|
function toolsetMcpUrl(slug) {
|
|
25136
25201
|
return `https://data-mcp.wayai.pro/t/${slug}/mcp`;
|
|
@@ -25140,8 +25205,17 @@ function globals(cmd) {
|
|
|
25140
25205
|
while (namespace.parent?.parent) namespace = namespace.parent;
|
|
25141
25206
|
return namespace.opts();
|
|
25142
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
|
+
}
|
|
25143
25217
|
function findBase(cmd) {
|
|
25144
|
-
return
|
|
25218
|
+
return baseOptionHolder(cmd)?.opts()?.base;
|
|
25145
25219
|
}
|
|
25146
25220
|
function requireBase(cmd) {
|
|
25147
25221
|
const base = findBase(cmd) ?? process.env.WAYAI_BASE ?? "";
|
|
@@ -25176,12 +25250,35 @@ async function readSecret(source, label) {
|
|
|
25176
25250
|
if (!value) throw expected(`${label} is required.`);
|
|
25177
25251
|
return value;
|
|
25178
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;
|
|
25179
25269
|
var init_helpers = __esm({
|
|
25180
25270
|
"src/data/helpers.ts"() {
|
|
25181
25271
|
"use strict";
|
|
25182
25272
|
init_base_id();
|
|
25183
25273
|
init_expected();
|
|
25184
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
|
+
};
|
|
25185
25282
|
}
|
|
25186
25283
|
});
|
|
25187
25284
|
|
|
@@ -25363,6 +25460,1031 @@ var init_attachments = __esm({
|
|
|
25363
25460
|
}
|
|
25364
25461
|
});
|
|
25365
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)
|
|
25580
|
+
).option(
|
|
25581
|
+
"--request-key <key>",
|
|
25582
|
+
"Idempotency key for this logical run (default: generated). Re-use it to resume."
|
|
25583
|
+
).option(
|
|
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")) {
|
|
25599
|
+
throw expected(
|
|
25600
|
+
"--start-chunk requires --chunk-size to be given explicitly, and it must match the original run."
|
|
25601
|
+
);
|
|
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
|
+
}
|
|
25609
|
+
const client = await createDataClient();
|
|
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;
|
|
25626
|
+
try {
|
|
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
|
+
);
|
|
25651
|
+
}
|
|
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`);
|
|
25658
|
+
}
|
|
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.");
|
|
25719
|
+
} else {
|
|
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
|
+
}
|
|
25727
|
+
}
|
|
25728
|
+
}
|
|
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
|
+
|
|
25366
26488
|
// src/lib/base-binding.ts
|
|
25367
26489
|
var binding2, getBaseBindingPath, readBaseBinding, writeBaseBinding, clearBaseBinding, autoBindBaseIfUnbound, assertBaseMatchesBinding, assertNoBindingBlocksBaseCreation;
|
|
25368
26490
|
var init_base_binding = __esm({
|
|
@@ -25389,7 +26511,7 @@ var init_base_binding = __esm({
|
|
|
25389
26511
|
});
|
|
25390
26512
|
|
|
25391
26513
|
// src/data/commands/bases.ts
|
|
25392
|
-
import { Command as
|
|
26514
|
+
import { Command as Command10 } from "commander";
|
|
25393
26515
|
import * as fs24 from "fs";
|
|
25394
26516
|
import * as path30 from "path";
|
|
25395
26517
|
import * as yaml9 from "js-yaml";
|
|
@@ -25405,7 +26527,7 @@ function parseEnum(flag, value, allowed) {
|
|
|
25405
26527
|
return value;
|
|
25406
26528
|
}
|
|
25407
26529
|
function buildBasesCommand() {
|
|
25408
|
-
const bases = new
|
|
26530
|
+
const bases = new Command10("bases").description("Manage bases");
|
|
25409
26531
|
bases.command("list").description("List all bases").option("--tag <tag>", "Filter by tag").action(async function(opts) {
|
|
25410
26532
|
const client = await createDataClient();
|
|
25411
26533
|
const qs = opts.tag ? `?tag=${encodeURIComponent(opts.tag)}` : "";
|
|
@@ -25612,6 +26734,13 @@ function buildBasesCommand() {
|
|
|
25612
26734
|
previous ? `Worktree unbound from base (was: ${previous}).` : "No base binding to clear."
|
|
25613
26735
|
);
|
|
25614
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());
|
|
25615
26744
|
return bases;
|
|
25616
26745
|
}
|
|
25617
26746
|
function resolveSelectorToBaseId(selector) {
|
|
@@ -25632,6 +26761,13 @@ var BASE_META_FILE, BASE_ID_FIELD;
|
|
|
25632
26761
|
var init_bases = __esm({
|
|
25633
26762
|
"src/data/commands/bases.ts"() {
|
|
25634
26763
|
"use strict";
|
|
26764
|
+
init_batch();
|
|
26765
|
+
init_import();
|
|
26766
|
+
init_providers();
|
|
26767
|
+
init_report2();
|
|
26768
|
+
init_secrets();
|
|
26769
|
+
init_sql();
|
|
26770
|
+
init_tokens();
|
|
25635
26771
|
init_client();
|
|
25636
26772
|
init_output();
|
|
25637
26773
|
init_helpers();
|
|
@@ -25648,9 +26784,9 @@ var init_bases = __esm({
|
|
|
25648
26784
|
});
|
|
25649
26785
|
|
|
25650
26786
|
// src/data/commands/file-types.ts
|
|
25651
|
-
import { Command as
|
|
26787
|
+
import { Command as Command11 } from "commander";
|
|
25652
26788
|
function buildFileTypesCommand() {
|
|
25653
|
-
const fileTypes = new
|
|
26789
|
+
const fileTypes = new Command11("file-types").description(
|
|
25654
26790
|
"Manage file types (Buckets) \u2014 storage policy + optional metadata schema for files"
|
|
25655
26791
|
);
|
|
25656
26792
|
fileTypes.command("list").description("List all file types in a base").action(async function() {
|
|
@@ -25706,8 +26842,8 @@ var init_file_types = __esm({
|
|
|
25706
26842
|
});
|
|
25707
26843
|
|
|
25708
26844
|
// src/data/commands/files.ts
|
|
25709
|
-
import { Command as
|
|
25710
|
-
import { readFileSync as
|
|
26845
|
+
import { Command as Command12 } from "commander";
|
|
26846
|
+
import { readFileSync as readFileSync25, writeFileSync as writeFileSync16 } from "fs";
|
|
25711
26847
|
import { basename as basename14 } from "path";
|
|
25712
26848
|
function renderFileDiff(fileType, filePath, from, to, d) {
|
|
25713
26849
|
console.log(sanitizeTerminalText(`${fileType}/${filePath}: v${from} \u2192 v${to}`));
|
|
@@ -25749,14 +26885,14 @@ function downloadTarget(remotePath, to) {
|
|
|
25749
26885
|
return derived;
|
|
25750
26886
|
}
|
|
25751
26887
|
function buildFilesCommand() {
|
|
25752
|
-
const files = new
|
|
26888
|
+
const files = new Command12("files").description(
|
|
25753
26889
|
"Manage files (path-addressed, versioned content) within a file type"
|
|
25754
26890
|
);
|
|
25755
26891
|
files.command("put <file_type> <path>").description(
|
|
25756
26892
|
"Upload a local file to a path (e.g. wayai files put reports q3/summary.pdf --file ./summary.pdf)"
|
|
25757
26893
|
).requiredOption("--file <local>", "Local file to upload").option("--content-type <type>", "MIME type", "application/octet-stream").action(async function(fileType, filePath, opts) {
|
|
25758
26894
|
const base = pathSegment(requireBase(this), "--base");
|
|
25759
|
-
const body =
|
|
26895
|
+
const body = readFileSync25(opts.file);
|
|
25760
26896
|
const client = await createDataClient();
|
|
25761
26897
|
printOutput(
|
|
25762
26898
|
await client.upload(
|
|
@@ -25787,7 +26923,7 @@ function buildFilesCommand() {
|
|
|
25787
26923
|
const { bytes } = await client.download(
|
|
25788
26924
|
`/v1/${base}/files/${pathSegment(fileType, "file_type")}/${encoded}${versionQs ? `?${versionQs}` : ""}`
|
|
25789
26925
|
);
|
|
25790
|
-
|
|
26926
|
+
writeFileSync16(out, bytes);
|
|
25791
26927
|
console.log(`Downloaded to ${out}`);
|
|
25792
26928
|
});
|
|
25793
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) {
|
|
@@ -25908,10 +27044,10 @@ var init_files = __esm({
|
|
|
25908
27044
|
});
|
|
25909
27045
|
|
|
25910
27046
|
// src/data/commands/inbound-webhooks.ts
|
|
25911
|
-
import { randomUUID } from "crypto";
|
|
25912
|
-
import { Command as
|
|
27047
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
27048
|
+
import { Command as Command13 } from "commander";
|
|
25913
27049
|
function buildInboundWebhooksCommand() {
|
|
25914
|
-
const inboundWebhooks = new
|
|
27050
|
+
const inboundWebhooks = new Command13("inbound-webhooks").description(
|
|
25915
27051
|
"Manage inbound webhook endpoints"
|
|
25916
27052
|
);
|
|
25917
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(
|
|
@@ -25949,7 +27085,7 @@ function buildInboundWebhooksCommand() {
|
|
|
25949
27085
|
if (opts.hydration) body.hydration = parseData(opts.hydration);
|
|
25950
27086
|
const base = pathSegment(requireBase(this), "--base");
|
|
25951
27087
|
const client = await createDataClient();
|
|
25952
|
-
const inboundWebhookId = opts.id ??
|
|
27088
|
+
const inboundWebhookId = opts.id ?? randomUUID2();
|
|
25953
27089
|
printOutput(
|
|
25954
27090
|
await client.request(
|
|
25955
27091
|
"PUT",
|
|
@@ -26009,9 +27145,9 @@ var init_inbound_webhooks = __esm({
|
|
|
26009
27145
|
});
|
|
26010
27146
|
|
|
26011
27147
|
// src/data/commands/query-relationships.ts
|
|
26012
|
-
import { Command as
|
|
27148
|
+
import { Command as Command14 } from "commander";
|
|
26013
27149
|
function buildQueryRelationshipsCommand() {
|
|
26014
|
-
return new
|
|
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) {
|
|
26015
27151
|
const base = pathSegment(requireBase(this), "--base");
|
|
26016
27152
|
const client = await createDataClient();
|
|
26017
27153
|
const params = new URLSearchParams();
|
|
@@ -26044,9 +27180,9 @@ var init_query_relationships = __esm({
|
|
|
26044
27180
|
});
|
|
26045
27181
|
|
|
26046
27182
|
// src/data/commands/record-types.ts
|
|
26047
|
-
import { Command as
|
|
27183
|
+
import { Command as Command15 } from "commander";
|
|
26048
27184
|
function buildRecordTypesCommand() {
|
|
26049
|
-
const recordTypes = new
|
|
27185
|
+
const recordTypes = new Command15("record-types").description("Manage record_types");
|
|
26050
27186
|
recordTypes.command("list").description("List all record_types in a base").action(async function() {
|
|
26051
27187
|
const base = pathSegment(requireBase(this), "--base");
|
|
26052
27188
|
const client = await createDataClient();
|
|
@@ -26104,12 +27240,12 @@ var init_record_types = __esm({
|
|
|
26104
27240
|
});
|
|
26105
27241
|
|
|
26106
27242
|
// src/data/commands/records.ts
|
|
26107
|
-
import { Command as
|
|
27243
|
+
import { Command as Command16 } from "commander";
|
|
26108
27244
|
function recordKey(id, externalSource) {
|
|
26109
27245
|
return externalSource ? foreignSegment(id, "external id") : pathSegment(id, "record id");
|
|
26110
27246
|
}
|
|
26111
27247
|
function buildRecordsCommand() {
|
|
26112
|
-
const records = new
|
|
27248
|
+
const records = new Command16("records").description("Manage records");
|
|
26113
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) {
|
|
26114
27250
|
const base = pathSegment(requireBase(this), "--base");
|
|
26115
27251
|
const client = await createDataClient();
|
|
@@ -26198,9 +27334,9 @@ var init_records = __esm({
|
|
|
26198
27334
|
});
|
|
26199
27335
|
|
|
26200
27336
|
// src/data/commands/relationship-types.ts
|
|
26201
|
-
import { Command as
|
|
27337
|
+
import { Command as Command17 } from "commander";
|
|
26202
27338
|
function buildRelationshipTypesCommand() {
|
|
26203
|
-
const relationshipTypes = new
|
|
27339
|
+
const relationshipTypes = new Command17("relationship-types").description(
|
|
26204
27340
|
"Manage relationship types (the schema for a relationship's metadata)"
|
|
26205
27341
|
);
|
|
26206
27342
|
relationshipTypes.command("list").description("List all relationship types in a base").action(async function() {
|
|
@@ -26277,7 +27413,7 @@ var init_relationship_types = __esm({
|
|
|
26277
27413
|
});
|
|
26278
27414
|
|
|
26279
27415
|
// src/data/commands/relationships.ts
|
|
26280
|
-
import { Command as
|
|
27416
|
+
import { Command as Command18 } from "commander";
|
|
26281
27417
|
function relationshipKey(id, relType) {
|
|
26282
27418
|
return relType ? foreignSegment(id, "external id") : pathSegment(id, "relationship id");
|
|
26283
27419
|
}
|
|
@@ -26313,7 +27449,7 @@ function byKeyQuery(relType, externalSource) {
|
|
|
26313
27449
|
return q ? `?${q}` : "";
|
|
26314
27450
|
}
|
|
26315
27451
|
function buildRelationshipsCommand() {
|
|
26316
|
-
const relationships = new
|
|
27452
|
+
const relationships = new Command18("relationships").description(
|
|
26317
27453
|
"Manage relationships between records"
|
|
26318
27454
|
);
|
|
26319
27455
|
relationships.command("upsert").description("Create or update a relationship").option("--source <record_type/id>", "Source record by internal id").option(
|
|
@@ -26398,9 +27534,9 @@ var init_relationships = __esm({
|
|
|
26398
27534
|
});
|
|
26399
27535
|
|
|
26400
27536
|
// src/data/commands/seed.ts
|
|
26401
|
-
import { Command as
|
|
27537
|
+
import { Command as Command19 } from "commander";
|
|
26402
27538
|
function buildSeedCommand() {
|
|
26403
|
-
const seed = new
|
|
27539
|
+
const seed = new Command19("seed").description(
|
|
26404
27540
|
"Apply, reset, or clear a seed fixture on a preview base (hermetic eval data)"
|
|
26405
27541
|
);
|
|
26406
27542
|
seed.command("apply <name>").description(
|
|
@@ -26492,7 +27628,7 @@ var init_seed = __esm({
|
|
|
26492
27628
|
});
|
|
26493
27629
|
|
|
26494
27630
|
// src/data/commands/toolsets.ts
|
|
26495
|
-
import { Command as
|
|
27631
|
+
import { Command as Command20 } from "commander";
|
|
26496
27632
|
function collect(value, previous) {
|
|
26497
27633
|
return [...previous, value];
|
|
26498
27634
|
}
|
|
@@ -26515,7 +27651,7 @@ function parseRelSpec(spec) {
|
|
|
26515
27651
|
return { rel_type: key, operations: ops };
|
|
26516
27652
|
}
|
|
26517
27653
|
function buildToolsetsCommand() {
|
|
26518
|
-
const toolsets = new
|
|
27654
|
+
const toolsets = new Command20("toolsets").description("Manage MCP toolsets");
|
|
26519
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(
|
|
26520
27656
|
"--action <ref>",
|
|
26521
27657
|
"Reference an Action by id, optionally renamed: <action_id> or <action_id>=<surface_name> (repeatable). Create Actions first with `wayai actions upsert`.",
|
|
@@ -26606,10 +27742,10 @@ var init_toolsets = __esm({
|
|
|
26606
27742
|
});
|
|
26607
27743
|
|
|
26608
27744
|
// src/data/commands/triggers.ts
|
|
26609
|
-
import { randomUUID as
|
|
26610
|
-
import { Command as
|
|
27745
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
27746
|
+
import { Command as Command21 } from "commander";
|
|
26611
27747
|
function buildTriggersCommand() {
|
|
26612
|
-
const triggers = new
|
|
27748
|
+
const triggers = new Command21("triggers").description(
|
|
26613
27749
|
"Manage triggers (outbound webhooks, internal writes, external writes)"
|
|
26614
27750
|
);
|
|
26615
27751
|
triggers.command("create").description(
|
|
@@ -26652,7 +27788,7 @@ function buildTriggersCommand() {
|
|
|
26652
27788
|
}
|
|
26653
27789
|
const base = pathSegment(requireBase(this), "--base");
|
|
26654
27790
|
const client = await createDataClient();
|
|
26655
|
-
const triggerId = opts.id ??
|
|
27791
|
+
const triggerId = opts.id ?? randomUUID3();
|
|
26656
27792
|
printOutput(
|
|
26657
27793
|
await client.request("PUT", `/v1/${base}/triggers/${pathSegment(triggerId, "--id")}`, body),
|
|
26658
27794
|
outputFormat(this)
|
|
@@ -26708,26 +27844,37 @@ var program_exports = {};
|
|
|
26708
27844
|
__export(program_exports, {
|
|
26709
27845
|
DATA_NAMESPACES: () => DATA_NAMESPACES,
|
|
26710
27846
|
buildDataProgram: () => buildDataProgram,
|
|
27847
|
+
handleParserExit: () => handleParserExit,
|
|
26711
27848
|
isDataNamespace: () => isDataNamespace,
|
|
27849
|
+
routeErrorsToCli: () => routeErrorsToCli,
|
|
26712
27850
|
runDataCommand: () => runDataCommand,
|
|
26713
27851
|
withBaseOption: () => withBaseOption,
|
|
26714
27852
|
withDataGlobals: () => withDataGlobals
|
|
26715
27853
|
});
|
|
26716
|
-
import { Command as
|
|
27854
|
+
import { Command as Command22 } from "commander";
|
|
26717
27855
|
function withDataGlobals(command2) {
|
|
26718
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");
|
|
26719
27857
|
}
|
|
26720
|
-
function withBaseOption(command2) {
|
|
26721
|
-
return command2.option("--base <id>", "Base id (or set WAYAI_BASE)");
|
|
26722
|
-
}
|
|
26723
27858
|
function routeErrorsToCli(command2) {
|
|
26724
27859
|
command2.exitOverride().configureOutput({ outputError: () => {
|
|
26725
27860
|
} });
|
|
26726
27861
|
for (const child of command2.commands) routeErrorsToCli(child);
|
|
26727
27862
|
return command2;
|
|
26728
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
|
+
}
|
|
26729
27876
|
function buildDataProgram() {
|
|
26730
|
-
const program = new
|
|
27877
|
+
const program = new Command22("wayai");
|
|
26731
27878
|
program.addCommand(withDataGlobals(buildBasesCommand()));
|
|
26732
27879
|
for (const build of [
|
|
26733
27880
|
buildRecordsCommand,
|
|
@@ -26759,16 +27906,7 @@ async function runDataCommand(namespace, args2) {
|
|
|
26759
27906
|
try {
|
|
26760
27907
|
await program.parseAsync(["node", "wayai", namespace, ...args2]);
|
|
26761
27908
|
} catch (err) {
|
|
26762
|
-
|
|
26763
|
-
if (code === "commander.helpDisplayed" || code === "commander.version") return;
|
|
26764
|
-
if (code === "commander.help") {
|
|
26765
|
-
process.exitCode = exitCode || 1;
|
|
26766
|
-
return;
|
|
26767
|
-
}
|
|
26768
|
-
if (typeof code === "string" && code.startsWith("commander.")) {
|
|
26769
|
-
throw expected(err instanceof Error ? err.message : String(err));
|
|
26770
|
-
}
|
|
26771
|
-
throw err;
|
|
27909
|
+
handleParserExit(err);
|
|
26772
27910
|
}
|
|
26773
27911
|
}
|
|
26774
27912
|
var init_program = __esm({
|
|
@@ -26793,6 +27931,7 @@ var init_program = __esm({
|
|
|
26793
27931
|
init_org_context();
|
|
26794
27932
|
init_registry();
|
|
26795
27933
|
init_registry();
|
|
27934
|
+
init_helpers();
|
|
26796
27935
|
}
|
|
26797
27936
|
});
|
|
26798
27937
|
|
|
@@ -26802,9 +27941,9 @@ init_errors2();
|
|
|
26802
27941
|
init_mask_secrets();
|
|
26803
27942
|
init_utils();
|
|
26804
27943
|
init_registry();
|
|
26805
|
-
import { readFileSync as
|
|
26806
|
-
import { fileURLToPath as
|
|
26807
|
-
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";
|
|
26808
27947
|
|
|
26809
27948
|
// src/lib/version-refresh.ts
|
|
26810
27949
|
init_version_cache();
|
|
@@ -26959,8 +28098,8 @@ Run \`wayai admin skill install\` to update.`);
|
|
|
26959
28098
|
}
|
|
26960
28099
|
|
|
26961
28100
|
// src/index.ts
|
|
26962
|
-
var __dirname =
|
|
26963
|
-
var pkg = JSON.parse(
|
|
28101
|
+
var __dirname = dirname10(fileURLToPath3(import.meta.url));
|
|
28102
|
+
var pkg = JSON.parse(readFileSync26(join29(__dirname, "..", "package.json"), "utf-8"));
|
|
26964
28103
|
var [, , command, ...args] = process.argv;
|
|
26965
28104
|
var isBackgroundRefresh = command === REFRESH_COMMAND;
|
|
26966
28105
|
if (!isBackgroundRefresh) initSentry(command, pkg.version);
|
|
@@ -27305,7 +28444,7 @@ async function runForeground() {
|
|
|
27305
28444
|
try {
|
|
27306
28445
|
await main();
|
|
27307
28446
|
if (command && !SKIP_UPDATE_CHECK.includes(command) && !wantsHelp(args)) {
|
|
27308
|
-
showUpdateNudges(pkg.version,
|
|
28447
|
+
showUpdateNudges(pkg.version, fileURLToPath3(import.meta.url));
|
|
27309
28448
|
}
|
|
27310
28449
|
await closeSentry();
|
|
27311
28450
|
} catch (err) {
|