@datarails-ai/dr-cli 1.6.149 → 1.6.151

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { fileURLToPath as fileURLToPath3 } from "url";
8
8
  // package.json
9
9
  var package_default = {
10
10
  name: "@datarails-ai/dr-cli",
11
- version: "1.6.149",
11
+ version: "1.6.151",
12
12
  description: "Datarails CLI",
13
13
  type: "module",
14
14
  engines: {
@@ -40,7 +40,7 @@ var package_default = {
40
40
  postinstall: "node scripts/postinstall.mjs"
41
41
  },
42
42
  dependencies: {
43
- "@datarails-ai/dr-appspace-cli": "0.12.20",
43
+ "@datarails-ai/dr-appspace-cli": "0.12.21",
44
44
  "@modelcontextprotocol/sdk": "^1.29.0",
45
45
  "@napi-rs/keyring": "^1.3.0",
46
46
  commander: "^12.1.0",
@@ -69,7 +69,7 @@ var package_default = {
69
69
  };
70
70
 
71
71
  // src/buildInfo.ts
72
- var BUILD_TIME = "2026-10-20T13:20:59.293Z";
72
+ var BUILD_TIME = "2026-10-22T09:31:33.112Z";
73
73
  function formatBuildTime(iso) {
74
74
  if (iso === "") return "";
75
75
  const d = new Date(iso);
@@ -512,6 +512,12 @@ function resolve(input) {
512
512
  return lookup(trimmed);
513
513
  }
514
514
 
515
+ // src/auth/userAgent.ts
516
+ var USER_AGENT = `dr-cli/${package_default.version} (${process.platform}; ${process.arch}; node/${process.versions.node})`;
517
+ function withUserAgent(headers) {
518
+ return { "User-Agent": USER_AGENT, ...headers };
519
+ }
520
+
515
521
  // src/auth/errors.ts
516
522
  var NotLoggedInError = class extends Error {
517
523
  code = "ERR_NOT_LOGGED_IN";
@@ -760,12 +766,12 @@ async function mintJwt(host, sessionId, csrfToken, options) {
760
766
  const base = trimTrailingSlash(host);
761
767
  const res = await fetch(`${base}/jwt/api/token/`, {
762
768
  method: "POST",
763
- headers: {
769
+ headers: withUserAgent({
764
770
  "Content-Type": "application/json",
765
771
  Cookie: `sessionid=${sessionId}; csrftoken=${csrfToken}`,
766
772
  "X-CSRFToken": csrfToken,
767
773
  Referer: `${base}/`
768
- },
774
+ }),
769
775
  body: "{}",
770
776
  signal: options?.signal
771
777
  });
@@ -789,10 +795,10 @@ async function refreshJwt(host, refreshToken, options) {
789
795
  const base = trimTrailingSlash(host);
790
796
  const res = await fetch(`${base}/jwt/api/token/refresh/`, {
791
797
  method: "POST",
792
- headers: {
798
+ headers: withUserAgent({
793
799
  "Content-Type": "application/json",
794
800
  Referer: `${base}/`
795
- },
801
+ }),
796
802
  body: JSON.stringify({ refresh: refreshToken }),
797
803
  signal: options?.signal
798
804
  });
@@ -816,7 +822,7 @@ async function refreshExternalJwt(host, refreshToken, options) {
816
822
  const base = trimTrailingSlash(host);
817
823
  const res = await fetch(`${base}/jwt/api/external/token/refresh/`, {
818
824
  method: "POST",
819
- headers: { "Content-Type": "application/json" },
825
+ headers: withUserAgent({ "Content-Type": "application/json" }),
820
826
  body: JSON.stringify({ refresh: refreshToken }),
821
827
  signal: options?.signal
822
828
  });
@@ -855,7 +861,7 @@ async function exchangeApiKey(host, apiKey, options) {
855
861
  const base = trimTrailingSlash(host);
856
862
  const res = await fetch(`${base}/jwt/api/apikey/token/`, {
857
863
  method: "POST",
858
- headers: { Authorization: `Bearer ${key}`, Accept: "application/json" },
864
+ headers: withUserAgent({ Authorization: `Bearer ${key}`, Accept: "application/json" }),
859
865
  signal: options?.signal
860
866
  });
861
867
  const text2 = await res.text();
@@ -912,10 +918,10 @@ async function loginAs(host, operatorAccessToken, userId, options) {
912
918
  }
913
919
  const res = await fetch(`${base}/jwt/api/loginas/`, {
914
920
  method: "POST",
915
- headers: {
921
+ headers: withUserAgent({
916
922
  "Content-Type": "application/json",
917
923
  Authorization: `Bearer ${operatorAccessToken}`
918
- },
924
+ }),
919
925
  body: JSON.stringify(body),
920
926
  signal: options?.signal
921
927
  });
@@ -1075,7 +1081,7 @@ function exchangeForSession(store, serverKey, email, session, host, opts) {
1075
1081
  const deadline = AbortSignal.timeout(API_KEY_EXCHANGE_DEADLINE_MS);
1076
1082
  deadline.addEventListener("abort", () => controller.abort(deadline.reason), { once: true });
1077
1083
  const signal = controller.signal;
1078
- const run = (async () => {
1084
+ const run2 = (async () => {
1079
1085
  if (host === "") {
1080
1086
  throw new SessionExpiredError(`${identity2} (no host to exchange the API key against)`);
1081
1087
  }
@@ -1148,20 +1154,20 @@ function exchangeForSession(store, serverKey, email, session, host, opts) {
1148
1154
  }
1149
1155
  })();
1150
1156
  const evict = () => {
1151
- if (inFlightExchanges.get(key)?.run === run) inFlightExchanges.delete(key);
1157
+ if (inFlightExchanges.get(key)?.run === run2) inFlightExchanges.delete(key);
1152
1158
  };
1153
1159
  const entry = {
1154
- run,
1160
+ run: run2,
1155
1161
  waiters: 0,
1156
1162
  abandon: () => {
1157
- if (inFlightExchanges.get(key)?.run !== run) return;
1163
+ if (inFlightExchanges.get(key)?.run !== run2) return;
1158
1164
  evict();
1159
1165
  const reason = new Error(`${identity2} (api-key exchange abandoned by its last caller)`);
1160
1166
  reason.name = "AbortError";
1161
1167
  controller.abort(reason);
1162
1168
  }
1163
1169
  };
1164
- void run.then(evict, evict);
1170
+ void run2.then(evict, evict);
1165
1171
  inFlightExchanges.set(key, entry);
1166
1172
  return joinExchange(entry, opts.signal);
1167
1173
  }
@@ -2150,6 +2156,7 @@ var AuthorizedClient = class _AuthorizedClient {
2150
2156
  * @internal
2151
2157
  */
2152
2158
  applyAuthHeaders(headers, hasBody, includeBearer) {
2159
+ headers.set("User-Agent", USER_AGENT);
2153
2160
  if (!this.isCookieless()) {
2154
2161
  headers.set(
2155
2162
  "Cookie",
@@ -5607,11 +5614,11 @@ async function endCookieSession(base, session) {
5607
5614
  return res.status >= 200 && res.status < 400 ? void 0 : `HTTP ${res.status}`;
5608
5615
  });
5609
5616
  }
5610
- async function withTimeout(run) {
5617
+ async function withTimeout(run2) {
5611
5618
  const controller = new AbortController();
5612
5619
  const timeout = setTimeout(() => controller.abort(), REVOKE_TIMEOUT_MS);
5613
5620
  try {
5614
- return await run(controller.signal);
5621
+ return await run2(controller.signal);
5615
5622
  } catch {
5616
5623
  return controller.signal.aborted ? "timed out" : "network error";
5617
5624
  } finally {
@@ -5652,7 +5659,7 @@ async function requestDeviceCode(host, options) {
5652
5659
  const body = new URLSearchParams({ client_id: CLIENT_ID, scope: paths.scope });
5653
5660
  const res = await fetch(`${base}${paths.code}`, {
5654
5661
  method: "POST",
5655
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
5662
+ headers: withUserAgent({ "Content-Type": "application/x-www-form-urlencoded" }),
5656
5663
  body: body.toString(),
5657
5664
  signal: options?.signal
5658
5665
  });
@@ -5780,7 +5787,7 @@ async function poll(host, path13, deviceCode, options, terminalHints, onSuccess)
5780
5787
  });
5781
5788
  const res = await fetch(`${base}${path13}`, {
5782
5789
  method: "POST",
5783
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
5790
+ headers: withUserAgent({ "Content-Type": "application/x-www-form-urlencoded" }),
5784
5791
  body: body.toString(),
5785
5792
  signal
5786
5793
  });
@@ -25922,14 +25929,14 @@ function wireDimensionSuggestions(dimensions) {
25922
25929
  refresh: runDimensionSuggestionsRefresh
25923
25930
  });
25924
25931
  }
25925
- function wireSuggestionGroup(parent, r, run) {
25932
+ function wireSuggestionGroup(parent, r, run2) {
25926
25933
  const group = parent.command("suggestions").description(
25927
25934
  `AI-suggested ${r.noun}s \u2014 agent-generated; requires the ${r.flagName} Unleash flag and an org-admin user. \`profile\` is the exception: read-only table/field profiling, no flag needed`
25928
25935
  );
25929
25936
  const listCmd = group.command("list").description(`List AI ${r.noun} suggestions and the latest generation-run status (last_run)`).option("--json", "Output raw JSON instead of a table", false).action(async (opts) => {
25930
25937
  const setup = await setupCall(opts.server, opts.account);
25931
25938
  try {
25932
- await run.list({
25939
+ await run2.list({
25933
25940
  client: setup.client,
25934
25941
  asJson: opts.json === true,
25935
25942
  signal: setup.signal,
@@ -25959,7 +25966,7 @@ function wireSuggestionGroup(parent, r, run) {
25959
25966
  timeoutMs: suggestionsCallTimeoutMs(wait, timeoutSec)
25960
25967
  });
25961
25968
  try {
25962
- await run.refresh({
25969
+ await run2.refresh({
25963
25970
  client: setup.client,
25964
25971
  asJson: opts.json === true,
25965
25972
  wait,
@@ -31281,16 +31288,16 @@ async function runEnrichmentTypesList(cfg) {
31281
31288
  function oneLine2(s, max) {
31282
31289
  return truncate3(s.replace(/\s+/g, " "), max);
31283
31290
  }
31284
- async function runBatch(items, target, run, signal) {
31291
+ async function runBatch(items, target, run2, signal) {
31285
31292
  if (items.length === 1) {
31286
31293
  const only = items[0];
31287
- return [{ target: target(only, 0), ok: true, value: await run(only) }];
31294
+ return [{ target: target(only, 0), ok: true, value: await run2(only) }];
31288
31295
  }
31289
31296
  const outcomes = [];
31290
31297
  for (let i = 0; i < items.length; i++) {
31291
31298
  const item = items[i];
31292
31299
  try {
31293
- outcomes.push({ target: target(item, i), ok: true, value: await run(item) });
31300
+ outcomes.push({ target: target(item, i), ok: true, value: await run2(item) });
31294
31301
  } catch (err) {
31295
31302
  outcomes.push({
31296
31303
  target: target(item, i),
@@ -33227,16 +33234,16 @@ function findArityViolations(entries) {
33227
33234
  for (const entry of entries) {
33228
33235
  if (typeof entry.formula !== "string") continue;
33229
33236
  const calls = findFunctionCalls(entry.formula);
33230
- for (const call2 of calls) {
33231
- const upper = call2.name.toUpperCase();
33237
+ for (const call3 of calls) {
33238
+ const upper = call3.name.toUpperCase();
33232
33239
  const arity = KNOWN_FUNCTION_ARITIES[upper];
33233
33240
  if (arity === void 0) continue;
33234
- if (call2.argCount >= arity.min && call2.argCount <= arity.max) continue;
33241
+ if (call3.argCount >= arity.min && call3.argCount <= arity.max) continue;
33235
33242
  const expected = arity.min === arity.max ? `exactly ${arity.min}` : `${arity.min}-${arity.max}`;
33236
33243
  violations.push({
33237
33244
  fieldName: entry.name === "" ? "(unnamed)" : entry.name,
33238
- functionName: call2.name,
33239
- got: call2.argCount,
33245
+ functionName: call3.name,
33246
+ got: call3.argCount,
33240
33247
  expected,
33241
33248
  note: arity.note
33242
33249
  });
@@ -33373,13 +33380,20 @@ import { promises as fs16 } from "fs";
33373
33380
  import * as os3 from "os";
33374
33381
  import * as path7 from "path";
33375
33382
  var RESPONSE_BODY_MAX_BYTES = 8 * 1024;
33383
+ var MAX_NAME_SUFFIX = 99;
33376
33384
  async function writeErrorLog(opts) {
33377
- const filename = `dr-cli-error-${timestampStamp()}.md`;
33385
+ const stamp2 = timestampStamp();
33378
33386
  const dir = await chooseDir(opts.dirOverride);
33379
- const filePath = path7.join(dir, filename);
33380
33387
  const md = formatLog(opts);
33381
- await fs16.writeFile(filePath, md, { encoding: "utf8" });
33382
- return filePath;
33388
+ for (let n = 0; ; n++) {
33389
+ const filePath = path7.join(dir, `dr-cli-error-${stamp2}${n === 0 ? "" : `-${n}`}.md`);
33390
+ try {
33391
+ await fs16.writeFile(filePath, md, { encoding: "utf8", mode: 384, flag: "wx" });
33392
+ return filePath;
33393
+ } catch (err) {
33394
+ if (err.code !== "EEXIST" || n >= MAX_NAME_SUFFIX) throw err;
33395
+ }
33396
+ }
33383
33397
  }
33384
33398
  function timestampStamp() {
33385
33399
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}/, "");
@@ -46207,557 +46221,1248 @@ function toControlPlaneError(method, path13, err) {
46207
46221
  );
46208
46222
  }
46209
46223
 
46210
- // src/commands/connectorsDevFetch.ts
46211
- import { promises as fs19 } from "fs";
46212
- import * as path9 from "path";
46213
- import { randomUUID as randomUUID2 } from "crypto";
46214
- function buildDevEnvelope(i) {
46215
- const config = {
46216
- ...i.publicConfig,
46217
- ...i.queryConfig,
46218
- // Coerce absent values to null (not undefined) so JSON.stringify keeps the
46219
- // key — matches build_hook_job's dq.get(...) -> None; a dropped objectType
46220
- // becomes a cryptic KeyError in the runner's dispatch.
46221
- objectType: i.dataQuery.objectType ?? null,
46222
- type: i.dataQuery.type ?? null,
46223
- query: i.dataQuery.query
46224
- };
46225
- if (i.period) config.period = i.period;
46226
- return {
46227
- correlation_id: i.correlationId,
46228
- config,
46229
- secret_ref: i.secretRef,
46230
- cred_token: "dev-local",
46231
- output_sas: "file:///out/output.csv",
46232
- input_sas: null,
46233
- cursor: null,
46234
- contract_version: i.contractVersion,
46235
- metadata: {}
46236
- };
46237
- }
46238
- function asObject(v) {
46239
- if (typeof v === "object" && v !== null && !Array.isArray(v)) {
46240
- return v;
46224
+ // src/api/awsConnections.ts
46225
+ async function call(method, path13, send, what) {
46226
+ let out;
46227
+ try {
46228
+ out = await send();
46229
+ } catch (err) {
46230
+ throw toControlPlaneError(method, path13, err);
46241
46231
  }
46242
- return null;
46232
+ if (out === null) throw new Error(`${method} ${path13}: empty response from control-plane ${what}`);
46233
+ return out;
46243
46234
  }
46244
- function extractDataQueryFields(raw2) {
46245
- const root = asObject(decodeShape(raw2));
46246
- if (root === null || Object.keys(root).length === 0) {
46247
- throw new Error(
46248
- "no data-query attached to this filebox (empty data_query response) \u2014 attach one in the filebox UI first"
46249
- );
46250
- }
46251
- const cfg = asObject(root.config);
46252
- const src = cfg ?? root;
46253
- const query = src.query;
46254
- let queryObj;
46255
- if (query === void 0 || query === null) {
46256
- queryObj = {};
46257
- } else {
46258
- const asObj = asObject(query);
46259
- if (asObj === null) {
46260
- throw new Error(
46261
- `data-query \`query\` is not an object (got ${typeof query}) \u2014 expected the Sync-query field`
46262
- );
46263
- }
46264
- queryObj = asObj;
46265
- }
46266
- return { objectType: queryObj.objectType, type: src.type, query: queryObj };
46235
+ async function createAwsConnection(client, fields, reqOptions) {
46236
+ const path13 = "/aws-connections";
46237
+ return call(
46238
+ "POST",
46239
+ path13,
46240
+ () => client.postJSON(path13, fields, reqOptions),
46241
+ "aws connection create"
46242
+ );
46267
46243
  }
46268
- function extractCredentials(integration) {
46269
- const cfg = asObject(integration.config);
46270
- if (cfg === null || Object.keys(cfg).length === 0) {
46271
- throw new Error(
46272
- `datasource ${integration.id} has an empty/missing \`config\` \u2014 nothing to use as connector credentials`
46273
- );
46274
- }
46275
- return { ...cfg };
46244
+ async function listAwsConnections(client, secretRef, reqOptions) {
46245
+ const path13 = secretRef === void 0 ? "/aws-connections" : `/aws-connections?secret_ref=${encodeURIComponent(secretRef)}`;
46246
+ return call(
46247
+ "GET",
46248
+ path13,
46249
+ () => client.getJSON(path13, reqOptions),
46250
+ "aws connection list"
46251
+ );
46276
46252
  }
46277
- var ENV_FILE = "env.json";
46278
- var SECRETS_FILE = "secrets.json";
46279
- var CONNECTION_FILE = "connection.py";
46280
- var QUERY_FILE = "query.py";
46281
- async function writeRunDir(input) {
46282
- let connectionSource;
46283
- try {
46284
- connectionSource = await fs19.readFile(input.connectionPath, "utf-8");
46285
- } catch {
46286
- throw new Error(`${input.connectionPath}: connection source not found or unreadable`);
46287
- }
46288
- let querySource;
46253
+ async function getAwsConnection(client, id, reqOptions) {
46254
+ const path13 = `/aws-connections/${id}`;
46255
+ return call(
46256
+ "GET",
46257
+ path13,
46258
+ () => client.getJSON(path13, reqOptions),
46259
+ "aws connection get"
46260
+ );
46261
+ }
46262
+ async function updateAwsConnection(client, id, fields, reqOptions) {
46263
+ const path13 = `/aws-connections/${id}`;
46264
+ return call(
46265
+ "PATCH",
46266
+ path13,
46267
+ () => client.patchJSON(path13, fields, reqOptions),
46268
+ "aws connection update"
46269
+ );
46270
+ }
46271
+
46272
+ // src/commands/connectorsTasks.ts
46273
+ import { readFileSync as readFileSync3 } from "fs";
46274
+
46275
+ // src/api/scheduledTasks.ts
46276
+ var BASE2 = "/scheduled-tasks";
46277
+ async function call2(method, path13, send, what) {
46278
+ let out;
46289
46279
  try {
46290
- querySource = await fs19.readFile(input.queryPath, "utf-8");
46291
- } catch {
46292
- throw new Error(`${input.queryPath}: dataquery source not found or unreadable`);
46280
+ out = await send();
46281
+ } catch (err) {
46282
+ throw toControlPlaneError(method, path13, err);
46293
46283
  }
46294
- await fs19.mkdir(input.outDir, { recursive: true });
46295
- await fs19.writeFile(
46296
- path9.join(input.outDir, ENV_FILE),
46297
- JSON.stringify(input.env, null, 2) + "\n",
46298
- "utf-8"
46284
+ if (out === null) throw new Error(`${method} ${path13}: empty response from control-plane ${what}`);
46285
+ return out;
46286
+ }
46287
+ async function createTask(client, fields, reqOptions) {
46288
+ return call2(
46289
+ "POST",
46290
+ BASE2,
46291
+ () => client.postJSON(BASE2, fields, reqOptions),
46292
+ "task create"
46299
46293
  );
46300
- const secretsPath = path9.join(input.outDir, SECRETS_FILE);
46301
- await fs19.writeFile(
46302
- secretsPath,
46303
- JSON.stringify({ [input.secretRef]: input.credentials }, null, 2) + "\n",
46304
- "utf-8"
46294
+ }
46295
+ async function listTasks(client, orgId, reqOptions) {
46296
+ const path13 = orgId === void 0 ? BASE2 : `${BASE2}?org_id=${orgId}`;
46297
+ return call2("GET", path13, () => client.getJSON(path13, reqOptions), "task list");
46298
+ }
46299
+ async function getTask(client, taskId, reqOptions) {
46300
+ const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
46301
+ return call2("GET", path13, () => client.getJSON(path13, reqOptions), "task get");
46302
+ }
46303
+ async function updateTask(client, taskId, fields, reqOptions) {
46304
+ const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
46305
+ return call2(
46306
+ "PATCH",
46307
+ path13,
46308
+ () => client.patchJSON(path13, fields, reqOptions),
46309
+ "task update"
46305
46310
  );
46306
- if (process.platform !== "win32") {
46307
- await fs19.chmod(secretsPath, 384);
46308
- }
46309
- await fs19.writeFile(path9.join(input.outDir, CONNECTION_FILE), connectionSource, "utf-8");
46310
- await fs19.writeFile(path9.join(input.outDir, QUERY_FILE), querySource, "utf-8");
46311
46311
  }
46312
- async function ensureGitignored(entry) {
46313
- const gitignorePath = path9.join(process.cwd(), ".gitignore");
46314
- let current = "";
46312
+ async function deleteTask(client, taskId, reqOptions) {
46313
+ const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
46315
46314
  try {
46316
- current = await fs19.readFile(gitignorePath, "utf-8");
46317
- } catch {
46315
+ await client.deleteJSON(path13, reqOptions);
46316
+ } catch (err) {
46317
+ throw toControlPlaneError("DELETE", path13, err);
46318
46318
  }
46319
- const normalized = entry.split("\\").join("/").replace(/\/+$/, "") + "/";
46320
- const lines = current.split(/\r?\n/).map((l) => l.trim());
46321
- if (lines.includes(normalized) || lines.includes(entry)) return;
46322
- const sep = current === "" || current.endsWith("\n") ? "" : "\n";
46323
- await fs19.writeFile(gitignorePath, `${current}${sep}${normalized}
46324
- `, "utf-8");
46325
46319
  }
46326
- function parsePositiveInt3(raw2, label) {
46327
- const n = Number(raw2);
46328
- if (!Number.isInteger(n) || String(n) !== raw2.trim() || n <= 0) {
46329
- throw new Error(`invalid ${label}: "${raw2}"`);
46330
- }
46331
- return n;
46320
+ async function runTask(client, taskId, reqOptions) {
46321
+ const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/run`;
46322
+ return call2(
46323
+ "POST",
46324
+ path13,
46325
+ () => client.postJSON(path13, {}, reqOptions),
46326
+ "task run"
46327
+ );
46332
46328
  }
46333
- function registerDevFetch(connectors) {
46334
- const cmd = connectors.command("dev-fetch").description(
46335
- "Assemble a local run dir (env.json + secrets.json + connection.py + query.py) for dev-run.sh"
46336
- ).argument("<fileboxUuid>", "filebox UUID of the connector binding").requiredOption(
46337
- "--datasource <id>",
46338
- "datasource (data-integration) id whose credentials to fetch"
46339
- ).requiredOption(
46340
- "--document-id <n>",
46341
- "numeric filebox document id (no uuid->id lookup exists; required)"
46342
- ).requiredOption("--connection <path>", "path to the connector's connection .py source").requiredOption("--query <path>", "path to the connector's dataquery .py source").option("--from <date>", "period start (paired with --to)").option("--to <date>", "period end (paired with --from)").option("-o, --output <dir>", "output run dir (default ./dev-run/<fileboxUuid>/)").action(async (fileboxUuid, opts) => {
46343
- const datasourceId = parsePositiveInt3(opts.datasource, "datasource id");
46344
- const documentId = parsePositiveInt3(opts.documentId, "document-id");
46345
- if (opts.from === void 0 !== (opts.to === void 0)) {
46346
- throw new Error("--from and --to must be provided together");
46347
- }
46348
- const defaultRoot = path9.join("dev-run", fileboxUuid);
46349
- const outDir = opts.output ?? defaultRoot;
46350
- const gitignoreEntry = opts.output ?? "dev-run";
46351
- const serverKey = resolveServer(opts.server);
46352
- const host = resolveConnectorsUrl(serverKey, opts.connectorsUrl);
46353
- const monolithHost = resolve(serverKey).host;
46354
- const cpSetup = await setupCall(opts.server, opts.account, { host, authHost: monolithHost });
46355
- try {
46356
- const binding = await getBinding(cpSetup.client, fileboxUuid, {
46357
- signal: cpSetup.signal
46358
- });
46359
- const monoSetup = await setupCall(opts.server, opts.account, {
46360
- host: monolithHost,
46361
- authHost: monolithHost
46362
- });
46363
- try {
46364
- const rawDataQuery = await getDataQueryRaw(monoSetup.client, documentId, {
46365
- signal: monoSetup.signal
46366
- });
46367
- const dataQuery = extractDataQueryFields(rawDataQuery);
46368
- if (dataQuery.objectType === void 0 || dataQuery.objectType === null || dataQuery.objectType === "") {
46369
- process.stderr.write(
46370
- 'WARNING: objectType is not set inside the Sync-query (config.query.objectType). get_query_class will receive None \u2014 fine only if the connector has a single default dataset. Add it to the data-query, e.g. config.query.objectType = "<dataset>".\n'
46371
- );
46372
- }
46373
- const integration = await itGet(monoSetup.client, datasourceId, {
46374
- signal: monoSetup.signal
46375
- });
46376
- const credentials = extractCredentials(integration);
46377
- let period;
46378
- if (opts.from !== void 0 && opts.to !== void 0) {
46379
- period = { from: opts.from, to: opts.to };
46380
- }
46381
- const env = buildDevEnvelope({
46382
- correlationId: randomUUID2(),
46383
- secretRef: binding.secret_ref,
46384
- publicConfig: binding.public_config,
46385
- queryConfig: binding.query_config,
46386
- dataQuery,
46387
- ...period !== void 0 ? { period } : {},
46388
- // Deliberate sandbox stub: the fetch/dispatch path doesn't branch on
46389
- // contract_version, so a fixed "1" is sufficient for a local run.
46390
- contractVersion: "1"
46391
- });
46392
- await writeRunDir({
46393
- outDir,
46394
- env,
46395
- secretRef: binding.secret_ref,
46396
- credentials,
46397
- connectionPath: opts.connection,
46398
- queryPath: opts.query
46399
- });
46400
- await ensureGitignored(gitignoreEntry);
46401
- process.stdout.write(`Wrote run dir: ${outDir}
46402
- `);
46403
- process.stdout.write(
46404
- ` env.json (secret_ref=${binding.secret_ref}, flavor=${binding.runtime_flavor})
46405
- `
46406
- );
46407
- process.stdout.write(" secrets.json (credentials NOT shown)\n");
46408
- process.stdout.write(" connection.py, query.py\n");
46409
- } finally {
46410
- monoSetup.cleanup();
46411
- }
46412
- } catch (err) {
46413
- throw augmentTlsCertError(err);
46414
- } finally {
46415
- cpSetup.cleanup();
46416
- }
46417
- });
46418
- addServerFlag(cmd);
46419
- addAccountFlag(cmd);
46420
- cmd.option(
46421
- "--connectors-url <url>",
46422
- "Override the control-plane base URL (e.g. a kubectl port-forward http://localhost:8000)"
46329
+ async function pauseTask(client, taskId, reqOptions) {
46330
+ const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/pause`;
46331
+ return call2(
46332
+ "POST",
46333
+ path13,
46334
+ () => client.postJSON(path13, {}, reqOptions),
46335
+ "task pause"
46336
+ );
46337
+ }
46338
+ async function resumeTask(client, taskId, reqOptions) {
46339
+ const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/resume`;
46340
+ return call2(
46341
+ "POST",
46342
+ path13,
46343
+ () => client.postJSON(path13, {}, reqOptions),
46344
+ "task resume"
46345
+ );
46346
+ }
46347
+ async function listRuns(client, taskId, limit, reqOptions) {
46348
+ const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/runs?limit=${limit}`;
46349
+ return call2(
46350
+ "GET",
46351
+ path13,
46352
+ () => client.getJSON(path13, reqOptions),
46353
+ "task runs"
46423
46354
  );
46424
46355
  }
46425
46356
 
46426
- // src/commands/connectorsPoll.ts
46427
- import * as readline2 from "readline";
46428
- import { createHash as createHash11 } from "crypto";
46429
-
46430
- // src/types/connectors.ts
46431
- var POLL_ITEM_STATES = [
46432
- "seen",
46433
- "new",
46434
- "dispatched",
46435
- "running",
46436
- "retry_wait",
46437
- "landed",
46438
- "failed",
46439
- "skipped",
46440
- "seeded",
46441
- "superseded"
46442
- ];
46443
-
46444
- // src/commands/connectorsPoll.ts
46357
+ // src/commands/connectorsTasks.ts
46445
46358
  var DURATION_UNIT_SECONDS = { s: 1, m: 60, h: 3600, d: 86400 };
46446
- var DURATION_BOUNDS = {
46447
- "--interval": [300, 7 * 86400],
46448
- "--settle": [0, 86400]
46449
- };
46450
- var LOOKBACK_MAX_DAYS = 3650;
46451
- function parseDurationSeconds(raw2, flag) {
46359
+ var MIN_EVERY_SECONDS = 60;
46360
+ var MAX_EVERY_SECONDS = 2 ** 31 - 1;
46361
+ function parseEvery(raw2) {
46452
46362
  const m = /^(\d+)([smhd])?$/.exec(raw2.trim());
46453
46363
  const seconds = m === null ? NaN : Number(m[1]) * (DURATION_UNIT_SECONDS[m[2] ?? "s"] ?? 1);
46454
- const [min, max] = DURATION_BOUNDS[flag] ?? [0, Number.MAX_SAFE_INTEGER];
46455
- if (m === null) {
46456
- throw new Error(`invalid ${flag} "${raw2}": expected a duration like 90s, 5m or 1h`);
46457
- }
46458
- if (!Number.isSafeInteger(seconds) || seconds < min || seconds > max) {
46459
- throw new Error(
46460
- `invalid ${flag} "${raw2}": must be between ${formatSeconds(min)} and ${formatSeconds(max)}`
46461
- );
46364
+ if (m === null)
46365
+ throw new Error(`invalid --every "${raw2}": expected a duration like 5m, 1h or 1d`);
46366
+ if (!Number.isSafeInteger(seconds) || seconds < MIN_EVERY_SECONDS || seconds > MAX_EVERY_SECONDS) {
46367
+ throw new Error(`invalid --every "${raw2}": must be at least 1m`);
46462
46368
  }
46463
46369
  return seconds;
46464
46370
  }
46465
- function parseLookbackDays(raw2) {
46466
- const m = /^(\d+)d?$/.exec(raw2.trim());
46467
- const days = m === null ? 0 : Number(m[1]);
46468
- if (days < 1 || days > LOOKBACK_MAX_DAYS) {
46469
- throw new Error(
46470
- `invalid --lookback "${raw2}": must be between 1 and ${LOOKBACK_MAX_DAYS} days, e.g. 180d`
46471
- );
46371
+ function parseCron(raw2) {
46372
+ const fields = raw2.trim().split(/\s+/);
46373
+ if (fields.length !== 5 || fields.some((f) => f === "")) {
46374
+ throw new Error(`invalid --cron "${raw2}": expected five fields, e.g. "15 * * * *"`);
46472
46375
  }
46473
- return days;
46474
- }
46475
- var DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
46476
- var DATE_TIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(?:Z|[+-](\d{2}):(\d{2}))$/;
46477
- function inRange(value, max, min = 0) {
46478
- const n = Number(value ?? "0");
46479
- return n >= min && n <= max;
46376
+ return fields.join(" ");
46480
46377
  }
46481
- function isCalendarDay(y, m, d) {
46482
- if (!inRange(m, 12, 1)) return false;
46483
- const daysInMonth3 = new Date(Date.UTC(Number(y), Number(m), 0)).getUTCDate();
46484
- return inRange(d, daysInMonth3, 1);
46378
+ var ROLE_RE = /^[a-z][a-z_]*$/;
46379
+ var DATASOURCE_ID_RE = /^[0-9]{1,18}$/;
46380
+ function parseDatasources(values) {
46381
+ const out = {};
46382
+ for (const raw2 of values) {
46383
+ const eq = raw2.indexOf("=");
46384
+ const role = eq < 0 ? "" : raw2.slice(0, eq).trim();
46385
+ const id = eq < 0 ? "" : raw2.slice(eq + 1).trim();
46386
+ if (!ROLE_RE.test(role) || !DATASOURCE_ID_RE.test(id)) {
46387
+ throw new Error(
46388
+ `invalid --datasource "${raw2}": expected <role>=<datasource id>, e.g. target=4242 (role: lowercase letters and _; id: digits)`
46389
+ );
46390
+ }
46391
+ if (Object.hasOwn(out, role)) throw new Error(`--datasource role "${role}" given twice`);
46392
+ out[role] = id;
46393
+ }
46394
+ const count = Object.keys(out).length;
46395
+ if (count < 1 || count > 4) throw new Error("give between 1 and 4 --datasource roles");
46396
+ return out;
46485
46397
  }
46486
- function parseTimestampFlag(raw2, flag) {
46487
- const invalid = new Error(
46488
- `invalid ${flag} "${raw2}": expected YYYY-MM-DD or a timestamp with a zone (e.g. 2026-10-08T17:30:00+03:00)`
46489
- );
46490
- const value = raw2.trim();
46491
- const dateOnly = DATE_ONLY_RE.exec(value);
46492
- if (dateOnly !== null) {
46493
- if (!isCalendarDay(dateOnly[1], dateOnly[2], dateOnly[3])) throw invalid;
46494
- return (/* @__PURE__ */ new Date(`${value}T00:00:00Z`)).toISOString();
46398
+ function parseConfig(raw2, readFile9 = readUtf8) {
46399
+ const text2 = raw2.startsWith("@") ? readFile9(raw2.slice(1)) : raw2;
46400
+ let parsed2;
46401
+ try {
46402
+ parsed2 = JSON.parse(text2);
46403
+ } catch (err) {
46404
+ const msg = err instanceof Error ? err.message : String(err);
46405
+ throw new Error(`invalid --config JSON: ${msg}`);
46495
46406
  }
46496
- const dt = DATE_TIME_RE.exec(value);
46497
- const valid = dt !== null && isCalendarDay(dt[1], dt[2], dt[3]) && inRange(dt[4], 23) && inRange(dt[5], 59) && inRange(dt[6], 59) && inRange(dt[7], 23) && inRange(dt[8], 59);
46498
- const ms = valid ? Date.parse(value) : NaN;
46499
- if (Number.isNaN(ms)) throw invalid;
46500
- return new Date(ms).toISOString();
46407
+ if (typeof parsed2 !== "object" || parsed2 === null || Array.isArray(parsed2)) {
46408
+ throw new Error("invalid --config: expected a JSON object");
46409
+ }
46410
+ return parsed2;
46501
46411
  }
46502
- function parseSameName(raw2) {
46503
- if (raw2 === "new-version") return "new_version";
46504
- if (raw2 === "ignore") return "ignore";
46505
- throw new Error(`--same-name must be new-version or ignore (got "${raw2}")`);
46412
+ function readUtf8(p) {
46413
+ return readFileSync3(p, "utf-8");
46506
46414
  }
46507
- function parseChoice(raw2, flag, choices) {
46508
- const match = choices.find((c) => c === raw2);
46509
- if (match === void 0) {
46510
- throw new Error(`${flag} must be ${choices.join(" or ")} (got "${raw2}")`);
46511
- }
46512
- return match;
46415
+ function collect(value, previous = []) {
46416
+ return [...previous, value];
46513
46417
  }
46514
- var WIDEN_CHOICES = ["seed", "land"];
46515
- var POLICY_CHOICES = ["rekey", "seed", "land"];
46516
- function parseSettings(opts) {
46517
- const fields = {};
46518
- if (opts.interval !== void 0) {
46519
- fields.interval_seconds = parseDurationSeconds(opts.interval, "--interval");
46418
+ function parseScheduleFlags(opts) {
46419
+ if (opts.every !== void 0 && opts.cron !== void 0) {
46420
+ throw new Error("give --every or --cron, not both");
46520
46421
  }
46521
- if (opts.since !== void 0) fields.since = parseTimestampFlag(opts.since, "--since");
46522
- if (opts.lookback !== void 0) fields.lookback_days = parseLookbackDays(opts.lookback);
46523
- if (opts.settle !== void 0) {
46524
- fields.settle_seconds = parseDurationSeconds(opts.settle, "--settle");
46422
+ const fields = {};
46423
+ if (opts.artifact !== void 0) fields.artifact_name = opts.artifact;
46424
+ if (opts.flavor !== void 0) fields.runtime_flavor = opts.flavor;
46425
+ if (opts.datasource !== void 0) fields.datasources = parseDatasources(opts.datasource);
46426
+ if (opts.primaryRole !== void 0) fields.primary_role = opts.primaryRole;
46427
+ if (opts.config !== void 0) fields.config = parseConfig(opts.config);
46428
+ if (opts.every !== void 0) fields.every_seconds = parseEvery(opts.every);
46429
+ if (opts.cron !== void 0) fields.cron = parseCron(opts.cron);
46430
+ if (opts.tz !== void 0) fields.timezone = opts.tz;
46431
+ if (fields.primary_role !== void 0 && fields.datasources !== void 0) {
46432
+ if (!Object.hasOwn(fields.datasources, fields.primary_role)) {
46433
+ throw new Error(
46434
+ `--primary-role "${fields.primary_role}" is not one of the --datasource roles`
46435
+ );
46436
+ }
46525
46437
  }
46526
- if (opts.sameName !== void 0) fields.change_policy = parseSameName(opts.sameName);
46527
46438
  return fields;
46528
46439
  }
46529
- function settingsArgs(opts) {
46530
- const pairs = [
46531
- ["--interval", opts.interval],
46532
- ["--since", opts.since],
46533
- ["--lookback", opts.lookback],
46534
- ["--settle", opts.settle],
46535
- ["--same-name", opts.sameName]
46536
- ];
46537
- return pairs.filter((p) => p[1] !== void 0).map(([flag, value]) => ` ${flag} ${value.trim()}`).join("");
46440
+ function buildCreateFields(name, orgId, opts) {
46441
+ const missing = [
46442
+ ["--artifact", opts.artifact],
46443
+ ["--flavor", opts.flavor],
46444
+ ["--datasource", opts.datasource],
46445
+ ["--config", opts.config]
46446
+ ].filter(([, v]) => v === void 0).map(([flag]) => flag);
46447
+ if (opts.every === void 0 && opts.cron === void 0) missing.push("--every or --cron");
46448
+ if (missing.length > 0) throw new Error(`missing ${missing.join(", ")}`);
46449
+ const fields = parseScheduleFlags(opts);
46450
+ return {
46451
+ org_id: orgId,
46452
+ name,
46453
+ artifact_name: fields.artifact_name,
46454
+ runtime_flavor: fields.runtime_flavor,
46455
+ datasources: fields.datasources,
46456
+ config: fields.config,
46457
+ ...fields.primary_role !== void 0 ? { primary_role: fields.primary_role } : {},
46458
+ ...fields.every_seconds !== void 0 ? { every_seconds: fields.every_seconds } : {},
46459
+ ...fields.cron !== void 0 ? { cron: fields.cron } : {},
46460
+ ...fields.timezone !== void 0 ? { timezone: fields.timezone } : {},
46461
+ ...opts.disabled === true ? { enabled: false } : {}
46462
+ };
46538
46463
  }
46539
- async function resolvePollBinding(client, filebox, signal) {
46540
- const wanted = filebox.trim();
46541
- const byId = /^\d+$/.test(wanted);
46542
- const rows = await listQueries(client, { signal });
46543
- const matches = rows.filter(
46544
- (q) => byId ? q.filebox_id === Number(wanted) : q.filebox_uuid.toLowerCase() === wanted.toLowerCase()
46545
- );
46546
- const [first] = matches;
46547
- if (first === void 0) {
46464
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
46465
+ async function resolveTaskId(client, task, signal) {
46466
+ if (UUID_RE.test(task)) return task;
46467
+ const tasks = await listTasks(client, void 0, { signal });
46468
+ const match = tasks.find((t) => t.name === task);
46469
+ if (match === void 0) {
46548
46470
  throw new Error(
46549
- `no connector binding for filebox ${wanted} in this org (see dr connectors query list)`
46471
+ `no scheduled task named "${task}" in this org (see: dr connectors tasks list)`
46550
46472
  );
46551
46473
  }
46552
- if (matches.length > 1) {
46553
- const ids = matches.map((q) => `#${q.query_id}`).join(", ");
46554
- throw new Error(`filebox ${wanted} has several connector bindings (${ids})`);
46555
- }
46556
- return first;
46474
+ return match.id;
46557
46475
  }
46558
- function isNotPolled(err) {
46559
- return err instanceof ControlPlaneError && err.status === 404 && err.body.includes("not polled");
46476
+ function explainTaskError(err) {
46477
+ const forbidden = err instanceof ControlPlaneError && err.status === 403 || err instanceof Error && /\bHTTP 403\b/.test(err.message);
46478
+ if (forbidden) {
46479
+ const message = err instanceof Error ? err.message : String(err);
46480
+ const body = err instanceof ControlPlaneError ? err.body : (/HTTP 403[^:]*(?:\([^)]*\))?:\s*([\s\S]*)$/.exec(message)?.[1] ?? "").trim();
46481
+ return withCause(
46482
+ new ControlPlaneError(
46483
+ 403,
46484
+ body,
46485
+ `${message}
46486
+ Scheduled tasks need a support user or an org admin: log in as one (--account) \u2014 e.g. dr login-as the customer's support user.`
46487
+ ),
46488
+ err
46489
+ );
46490
+ }
46491
+ if (!(err instanceof ControlPlaneError)) return err;
46492
+ const detail = detailOf(err.body);
46493
+ if (err.status === 404) {
46494
+ const message = `scheduled task not found (${detail || err.message})`;
46495
+ return withCause(new ControlPlaneError(404, err.body, message), err);
46496
+ }
46497
+ if (err.status === 409 || err.status === 422) {
46498
+ return withCause(new ControlPlaneError(err.status, err.body, detail || err.message), err);
46499
+ }
46500
+ return err;
46560
46501
  }
46561
- function isPollThrottled(err) {
46562
- return err instanceof ControlPlaneError && err.status === 409 && (err.body.includes("discover is in flight") || err.body.includes("polled less than"));
46502
+ function withCause(error, cause) {
46503
+ Object.defineProperty(error, "cause", { value: cause, enumerable: false, configurable: true });
46504
+ return error;
46563
46505
  }
46564
- async function explainNotPolled(filebox, call2) {
46506
+ function detailOf(body) {
46565
46507
  try {
46566
- return await call2;
46567
- } catch (err) {
46568
- if (isNotPolled(err)) {
46569
- throw new Error(
46570
- `filebox ${filebox} is not set to polled ingestion; run dr connectors poll enable ${filebox}`,
46571
- { cause: err }
46572
- );
46508
+ const parsed2 = JSON.parse(body);
46509
+ const d = parsed2.detail;
46510
+ if (typeof d === "string") return d;
46511
+ if (Array.isArray(d)) {
46512
+ return d.map(
46513
+ (e) => `${Array.isArray(e.loc) ? e.loc.slice(1).join(".") : ""}: ${e.msg ?? ""}`.trim()
46514
+ ).join("; ");
46573
46515
  }
46574
- throw err;
46516
+ } catch {
46575
46517
  }
46518
+ return body;
46576
46519
  }
46577
- async function readFileboxDateTag(monolith, fileboxId, signal) {
46578
- const details = await getDetails(monolith, fileboxId, { signal });
46579
- const dateTags = (details.tags_config ?? []).filter((t) => t.type === "DATE");
46580
- const [only] = dateTags;
46581
- if (only === void 0) return {};
46582
- if (dateTags.length > 1) {
46583
- const names = dateTags.map((t) => t.name).join(", ");
46584
- throw new Error(
46585
- `filebox ${fileboxId} has several DATE tags (${names}); a polled filebox needs exactly one`
46520
+ function scheduleText(t) {
46521
+ if (t.schedule_kind === "cron") return `cron "${t.cron ?? ""}" ${t.timezone}`;
46522
+ const s = t.every_seconds ?? 0;
46523
+ const unit = s % 86400 === 0 ? `${s / 86400}d` : s % 3600 === 0 ? `${s / 3600}h` : s % 60 === 0 ? `${s / 60}m` : `${s}s`;
46524
+ return `every ${unit}`;
46525
+ }
46526
+ function stateText(t) {
46527
+ if (!t.enabled) return "disabled";
46528
+ if (t.paused_reason !== null) return `paused (${t.paused_reason})`;
46529
+ return t.running ? "running" : "enabled";
46530
+ }
46531
+ function writeTask(out, t) {
46532
+ const roles = Object.entries(t.datasources).map(([role, id]) => `${role}=${id}${role === t.primary_role ? " (primary)" : ""}`).join(", ");
46533
+ out.write(`Task: ${t.name} (${t.id})
46534
+ `);
46535
+ out.write(` Org: ${t.org_id}
46536
+ `);
46537
+ out.write(` State: ${stateText(t)}
46538
+ `);
46539
+ out.write(` Artifact: ${t.artifact_name} [${t.runtime_flavor}]
46540
+ `);
46541
+ out.write(` Schedule: ${scheduleText(t)}
46542
+ `);
46543
+ out.write(` Next run: ${t.next_run_at}
46544
+ `);
46545
+ out.write(` Last run: ${t.last_run_at ?? "-"} ${t.last_run_status ?? ""}
46546
+ `);
46547
+ if (t.consecutive_failures > 0) out.write(` Failures: ${t.consecutive_failures} in a row
46548
+ `);
46549
+ out.write(` Datasources: ${roles}
46550
+ `);
46551
+ out.write(` Config: ${truncate3(JSON.stringify(t.config), 200)}
46552
+ `);
46553
+ }
46554
+ function writeTaskList(out, tasks) {
46555
+ if (tasks.length === 0) {
46556
+ out.write("No scheduled tasks.\n");
46557
+ return;
46558
+ }
46559
+ for (const t of tasks) {
46560
+ out.write(
46561
+ `${t.name.padEnd(28)} ${stateText(t).padEnd(20)} ${scheduleText(t).padEnd(26)} next ${t.next_run_at} last ${t.last_run_status ?? "-"} ${t.id}
46562
+ `
46586
46563
  );
46587
46564
  }
46588
- return { id: only.id, name: only.name };
46589
46565
  }
46590
- function formatSeconds(sec) {
46591
- if (sec > 0 && sec % 86400 === 0) return `${sec / 86400}d`;
46592
- if (sec > 0 && sec % 3600 === 0) return `${sec / 3600}h`;
46593
- if (sec > 0 && sec % 60 === 0) return `${sec / 60}m`;
46594
- return `${sec}s`;
46566
+ function writeRuns(out, runs) {
46567
+ if (runs.length === 0) {
46568
+ out.write("No runs yet.\n");
46569
+ return;
46570
+ }
46571
+ for (const r of runs) {
46572
+ const tail = r.error !== null ? ` error: ${truncate3(r.error, 160)}` : r.summary !== null ? ` ${truncate3(JSON.stringify(r.summary), 160)}` : "";
46573
+ out.write(
46574
+ `${r.started_at} ${r.status.padEnd(15)} ${r.trigger.padEnd(8)} attempt ${r.attempt} ${r.artifact_version ?? "-"}${tail}
46575
+ `
46576
+ );
46577
+ }
46595
46578
  }
46596
- function oneLine3(s) {
46597
- return s.replace(/\s+/g, " ").trim();
46579
+ function withFlags(cmd) {
46580
+ cmd.option("--json", "Output raw JSON instead of a summary", false);
46581
+ addServerFlag(cmd);
46582
+ addAccountFlag(cmd);
46583
+ addConnectorsUrlFlag(cmd);
46584
+ return cmd;
46598
46585
  }
46599
- function sameNameLabel(policy) {
46600
- return policy === "new_version" ? "new-version" : String(policy);
46586
+ function addScheduleFlags(cmd) {
46587
+ return cmd.option("--artifact <name>", "published artifact name, e.g. sftp_export__daily_sales").option("--flavor <flavor>", "runner flavor the artifact was published for, e.g. sftp").option("--datasource <role=id>", "monolith datasource for a role (repeat per role)", collect).option(
46588
+ "--primary-role <role>",
46589
+ "the role passed as the primary datasource (default: the first)"
46590
+ ).option("--config <json|@file>", "task config: a JSON object, or @path to a JSON file").option("--every <duration>", "run on an interval, e.g. 5m, 1h (at least 1m)").option("--cron <expr>", 'run on a five-field cron line, e.g. "15 * * * *"').option("--tz <iana>", "timezone the cron line is read in (default UTC)");
46601
46591
  }
46602
- function formatDateTag(tag) {
46603
- if (tag?.id === void 0) return "none (undated filebox)";
46604
- return `${tag.name ?? "?"} (id ${tag.id})`;
46592
+ async function onTask(task, opts, act, print) {
46593
+ await withConnectorsSetup(opts, async ({ client, signal }) => {
46594
+ try {
46595
+ const taskId = await resolveTaskId(client, task, signal);
46596
+ const result = await act(client, taskId, signal);
46597
+ if (opts.json === true) writeJSONIndented(process.stdout, result);
46598
+ else print(process.stdout, result);
46599
+ } catch (err) {
46600
+ throw explainTaskError(err);
46601
+ }
46602
+ });
46605
46603
  }
46606
- function bindingTarget(binding) {
46607
- return `filebox ${binding.filebox_uuid} (query #${binding.query_id})`;
46604
+ function registerConnectorsTasks(connectors) {
46605
+ const tasks = connectors.command("tasks").description("DRIP scheduled tasks: run an artifact on a schedule (support user or org admin)");
46606
+ withFlags(
46607
+ addScheduleFlags(
46608
+ tasks.command("create").description("Create a scheduled task").argument("<name>", "task name, unique in the org (a-z, 0-9, _ and -)").option("--org-id <id>", "org id (defaults to the JWT org)").option("--disabled", "create it disabled", false)
46609
+ )
46610
+ ).action(
46611
+ async (name, opts) => {
46612
+ const explicitOrg = opts.orgId !== void 0 ? parsePositiveIntArg2(opts.orgId, "--org-id") : void 0;
46613
+ const parsed2 = buildCreateFields(name, explicitOrg ?? 0, opts);
46614
+ await withConnectorsSetup(opts, async ({ client, signal }) => {
46615
+ const fields = { ...parsed2, org_id: explicitOrg ?? client.getCurrentOrgId() };
46616
+ try {
46617
+ const t = await createTask(client, fields, { signal });
46618
+ if (opts.json === true) writeJSONIndented(process.stdout, t);
46619
+ else writeTask(process.stdout, t);
46620
+ } catch (err) {
46621
+ throw explainTaskError(err);
46622
+ }
46623
+ });
46624
+ }
46625
+ );
46626
+ withFlags(
46627
+ tasks.command("list").description("List the org's scheduled tasks").option("--org-id <id>", "org id (defaults to the JWT org)")
46628
+ ).action(async (opts) => {
46629
+ await withConnectorsSetup(opts, async ({ client, signal }) => {
46630
+ const orgId = opts.orgId !== void 0 ? parsePositiveIntArg2(opts.orgId, "--org-id") : void 0;
46631
+ try {
46632
+ const list28 = await listTasks(client, orgId, { signal });
46633
+ if (opts.json === true) writeJSONIndented(process.stdout, list28);
46634
+ else writeTaskList(process.stdout, list28);
46635
+ } catch (err) {
46636
+ throw explainTaskError(err);
46637
+ }
46638
+ });
46639
+ });
46640
+ withFlags(
46641
+ tasks.command("get").description("Show a scheduled task").argument("<task>", "task id or name")
46642
+ ).action(
46643
+ async (task, opts) => onTask(
46644
+ task,
46645
+ opts,
46646
+ (c, id, signal) => getTask(c, id, { signal }),
46647
+ (o, r) => writeTask(o, r)
46648
+ )
46649
+ );
46650
+ withFlags(
46651
+ addScheduleFlags(
46652
+ tasks.command("update").description("Change a scheduled task (only the flags given change)").argument("<task>", "task id or name").option("--enable", "enable the task").option("--disable", "disable the task")
46653
+ )
46654
+ ).action(
46655
+ async (task, opts) => {
46656
+ if (opts.enable === true && opts.disable === true)
46657
+ throw new Error("give --enable or --disable, not both");
46658
+ const fields = parseScheduleFlags(opts);
46659
+ if (opts.enable === true) fields.enabled = true;
46660
+ if (opts.disable === true) fields.enabled = false;
46661
+ if (Object.keys(fields).length === 0)
46662
+ throw new Error("nothing to change: give at least one flag");
46663
+ await onTask(
46664
+ task,
46665
+ opts,
46666
+ (c, id, signal) => updateTask(c, id, fields, { signal }),
46667
+ (o, r) => writeTask(o, r)
46668
+ );
46669
+ }
46670
+ );
46671
+ withFlags(
46672
+ tasks.command("delete").description("Delete a scheduled task and its run history").argument("<task>", "task id or name")
46673
+ ).action(
46674
+ async (task, opts) => onTask(
46675
+ task,
46676
+ opts,
46677
+ async (c, id, signal) => {
46678
+ await deleteTask(c, id, { signal });
46679
+ return { deleted: id };
46680
+ },
46681
+ (o, r) => o.write(`Deleted scheduled task ${r.deleted}
46682
+ `)
46683
+ )
46684
+ );
46685
+ withFlags(
46686
+ tasks.command("run").description("Run a task now (does not move its schedule)").argument("<task>", "task id or name")
46687
+ ).action(
46688
+ async (task, opts) => onTask(
46689
+ task,
46690
+ opts,
46691
+ (c, id, signal) => runTask(c, id, { signal }),
46692
+ (o, r) => {
46693
+ const run2 = r;
46694
+ o.write(`Queued run ${run2.id} (${run2.status}); see: dr connectors tasks runs ${task}
46695
+ `);
46696
+ }
46697
+ )
46698
+ );
46699
+ withFlags(
46700
+ tasks.command("pause").description("Pause a scheduled task").argument("<task>", "task id or name")
46701
+ ).action(
46702
+ async (task, opts) => onTask(
46703
+ task,
46704
+ opts,
46705
+ (c, id, signal) => pauseTask(c, id, { signal }),
46706
+ (o, r) => writeTask(o, r)
46707
+ )
46708
+ );
46709
+ withFlags(
46710
+ tasks.command("resume").description("Resume a paused task (clears its failure count, schedules the next run)").argument("<task>", "task id or name")
46711
+ ).action(
46712
+ async (task, opts) => onTask(
46713
+ task,
46714
+ opts,
46715
+ (c, id, signal) => resumeTask(c, id, { signal }),
46716
+ (o, r) => writeTask(o, r)
46717
+ )
46718
+ );
46719
+ withFlags(
46720
+ tasks.command("runs").description("A task's run history, newest first").argument("<task>", "task id or name").option("--limit <n>", "how many runs (1-200)", "20")
46721
+ ).action(async (task, opts) => {
46722
+ const limit = parsePositiveIntArg2(opts.limit, "--limit");
46723
+ if (limit > 200) throw new Error("invalid --limit: at most 200");
46724
+ await onTask(
46725
+ task,
46726
+ opts,
46727
+ (c, id, signal) => listRuns(c, id, limit, { signal }),
46728
+ (o, r) => writeRuns(o, r)
46729
+ );
46730
+ });
46608
46731
  }
46609
- function renderPoll(out, headline, binding, p) {
46732
+
46733
+ // src/commands/connectorsAws.ts
46734
+ var ROLE_ARN_RE = /^arn:aws[a-z-]*:iam::\d{12}:role\/\S+$/;
46735
+ var OTHER_ORG_HINT = "if your session is valid, the connection or --org-id may belong to another org than the --account you are logged in as";
46736
+ function explainAwsError(err, id) {
46737
+ if (!(err instanceof ControlPlaneError)) {
46738
+ if (err instanceof Error && /\bHTTP 403\b/.test(err.message)) {
46739
+ return withCause(
46740
+ new ControlPlaneError(
46741
+ 403,
46742
+ "",
46743
+ `${err.message}
46744
+ ${OTHER_ORG_HINT}`
46745
+ ),
46746
+ err
46747
+ );
46748
+ }
46749
+ return err;
46750
+ }
46751
+ if (!hasDetail(err.body)) return err;
46752
+ const detail = detailOf(err.body) || err.message;
46753
+ const messages = {
46754
+ 403: `forbidden: ${detail} (${OTHER_ORG_HINT})`,
46755
+ 404: `AWS connection${id === void 0 ? "" : ` ${id}`} not found: ${detail}`,
46756
+ 409: `conflict: ${detail}`,
46757
+ 422: `rejected: ${detail}`,
46758
+ 503: `the control plane cannot broker AWS roles on this site: ${detail}`
46759
+ };
46760
+ const message = messages[err.status];
46761
+ if (message === void 0) return err;
46762
+ return withCause(new ControlPlaneError(err.status, err.body, message), err);
46763
+ }
46764
+ function hasDetail(body) {
46765
+ try {
46766
+ const parsed2 = JSON.parse(body);
46767
+ return typeof parsed2 === "object" && parsed2 !== null && "detail" in parsed2;
46768
+ } catch {
46769
+ return false;
46770
+ }
46771
+ }
46772
+ function oneLine3(s) {
46773
+ return s.replace(/\s+/g, " ").trim();
46774
+ }
46775
+ function writeConnection(out, c) {
46610
46776
  const row = (label, value) => {
46611
- out.write(` ${`${label}:`.padEnd(15)}${value}
46777
+ out.write(` ${`${label}:`.padEnd(14)}${value}
46612
46778
  `);
46613
46779
  };
46614
- const status2 = `${p.enabled ? "enabled" : "disabled"}${p.paused_reason === null ? "" : `, paused: ${oneLine3(p.paused_reason)}`}`;
46615
- const counts = Object.entries(p.counts).sort(([a], [b]) => a.localeCompare(b)).map(([state, n]) => `${state}=${n}`).join(" ");
46616
- out.write(`${headline} for ${bindingTarget(binding)}
46780
+ out.write(`AWS connection #${c.id} (org ${c.org_id})
46781
+ `);
46782
+ row("Datasource", c.secret_ref);
46783
+ row("Role ARN", c.role_arn);
46784
+ row("Status", `${c.status}${c.enabled ? "" : " (disabled)"}`);
46785
+ row("Broker ARN", c.broker_arn ?? "-");
46786
+ row("External ID", c.external_id);
46787
+ row("Expires", c.expires_at ?? "-");
46788
+ row("Last assumed", c.last_assumed_at ?? "-");
46789
+ row("Last error", c.last_error === null ? "-" : oneLine3(c.last_error));
46790
+ row("Failures", String(c.consecutive_failures));
46791
+ if (c.trust_statement === null) {
46792
+ out.write("\nTrust statement: none returned by the control plane.\n");
46793
+ } else {
46794
+ out.write("\nTrust statement (add it to the customer role's trust policy):\n");
46795
+ out.write(`${JSON.stringify(c.trust_statement, null, 2)}
46617
46796
  `);
46618
- row("Status", status2);
46619
- row("Interval", formatSeconds(p.interval_seconds));
46620
- row("Date tag", formatDateTag(p.date_tag));
46621
- row("Since", p.since ?? "-");
46622
- row("Lookback", `${p.lookback_days}d`);
46623
- row("Settle", formatSeconds(p.settle_seconds));
46624
- row("Same name", sameNameLabel(p.change_policy));
46625
- row("Max attempts", String(p.max_attempts));
46626
- row("Last poll", `${p.last_poll_at ?? "-"} ${oneLine3(p.last_poll_status ?? "")}`.trimEnd());
46627
- row("Next poll", p.next_poll_at);
46628
- row("Failures", String(p.consecutive_failures));
46629
- row("Discover", p.discover_in_flight ? `in flight since ${p.discover_sent_at ?? "?"}` : "-");
46630
- row("Last digest", p.last_digest ?? "-");
46631
- row("Pending seed", p.poll_run_seed_until ?? "-");
46632
- row(
46633
- "Pending rekey",
46634
- p.poll_run_rekey_to === null ? "-" : `to ${sameNameLabel(p.poll_run_rekey_to)}`
46797
+ }
46798
+ out.write(
46799
+ "\nThe customer role's permissions should be read-only on their bucket/prefix (plus kms:Decrypt for a customer-managed KMS key).\n"
46635
46800
  );
46636
- row("Items", counts === "" ? "(none)" : counts);
46637
46801
  }
46638
- function writeTable26(out, rows) {
46639
- const widths = [];
46640
- for (const r of rows) {
46641
- r.forEach((cell, i) => {
46642
- widths[i] = Math.max(widths[i] ?? 0, cell.length);
46643
- });
46802
+ function writeConnectionTable(out, list28) {
46803
+ if (list28.length === 0) {
46804
+ out.write("No AWS connections for this org.\n");
46805
+ return;
46806
+ }
46807
+ const rows = [["ID", "SECRET_REF", "ROLE_ARN", "STATUS", "EXPIRES_AT", "LAST_ERROR"]];
46808
+ for (const c of list28) {
46809
+ rows.push([
46810
+ String(c.id),
46811
+ oneLine3(c.secret_ref),
46812
+ oneLine3(c.role_arn),
46813
+ c.status,
46814
+ c.expires_at ?? "-",
46815
+ c.last_error === null ? "-" : truncate3(oneLine3(c.last_error), 60)
46816
+ ]);
46644
46817
  }
46818
+ const widths = rows[0].map((_, i) => Math.max(...rows.map((r) => r[i].length)));
46645
46819
  for (const r of rows) {
46646
- const cells = r.map(
46647
- (cell, i) => i === r.length - 1 ? cell : cell.padEnd((widths[i] ?? 0) + 2)
46820
+ out.write(
46821
+ `${r.map((cell, i) => cell.padEnd(widths[i] + 2)).join("").trimEnd()}
46822
+ `
46648
46823
  );
46649
- out.write(`${cells.join("").trimEnd()}
46650
- `);
46651
46824
  }
46652
46825
  }
46653
- function renderItemTable(out, items) {
46654
- const rows = [["ID", "LABEL", "DATE", "STATE", "ATTEMPTS", "VERSION", "ERROR"]];
46655
- for (const it of items) {
46656
- const error = it.last_error ?? (it.skip_reason === null ? null : `skipped: ${it.skip_reason}`);
46657
- rows.push([
46658
- String(it.id),
46659
- oneLine3(it.label),
46660
- it.item_date ?? "-",
46661
- it.state,
46662
- String(it.attempts),
46663
- it.version_id === null ? "-" : String(it.version_id),
46664
- error === null ? "-" : truncate3(oneLine3(error), 80)
46665
- ]);
46666
- }
46667
- writeTable26(out, rows);
46826
+ function withFlags2(cmd) {
46827
+ cmd.option("--json", "Output raw JSON instead of a summary", false);
46828
+ addServerFlag(cmd);
46829
+ addAccountFlag(cmd);
46830
+ addConnectorsUrlFlag(cmd);
46831
+ return cmd;
46668
46832
  }
46669
- async function runPollEnable(cfg) {
46670
- const binding = await resolvePollBinding(cfg.client, cfg.filebox, cfg.signal);
46671
- let existing;
46672
- try {
46673
- existing = await getPoll(cfg.client, binding.query_id, { signal: cfg.signal });
46674
- } catch (err) {
46675
- if (!isNotPolled(err)) throw err;
46676
- existing = null;
46677
- }
46678
- const s = cfg.settings;
46679
- if (existing !== null && (s.since !== void 0 || s.lookback_days !== void 0 || s.change_policy !== void 0)) {
46680
- throw new Error(
46681
- `filebox ${cfg.filebox} is already polled; change --since, --lookback or --same-name with dr connectors poll set ${cfg.filebox}, which asks what to do with a wider window or a policy switch`
46833
+ async function run(opts, act, print, id) {
46834
+ await withConnectorsSetup(opts, async (setup) => {
46835
+ let result;
46836
+ try {
46837
+ result = await act(setup);
46838
+ } catch (err) {
46839
+ throw explainAwsError(err, id);
46840
+ }
46841
+ if (opts.json === true) writeJSONIndented(process.stdout, result);
46842
+ else print(process.stdout, result);
46843
+ });
46844
+ }
46845
+ function registerConnectorsAws(connectors) {
46846
+ const aws = connectors.command("aws").description("Customer IAM roles DRIP assumes to read the customer's S3 (external ID, trust)");
46847
+ withFlags2(
46848
+ aws.command("create").description("Register a customer IAM role for a datasource; prints the trust statement").requiredOption("--secret-ref <datasourceId>", "monolith datasource id the role serves").requiredOption("--role-arn <arn>", "customer role, e.g. arn:aws:iam::123456789012:role/x").option("--org-id <id>", "org id (defaults to the JWT org)")
46849
+ ).action(async (opts) => {
46850
+ const secretRef = opts.secretRef.trim();
46851
+ if (secretRef === "" || secretRef.length > 200) {
46852
+ throw new Error(`invalid --secret-ref "${opts.secretRef}": expected 1-200 characters`);
46853
+ }
46854
+ if (!ROLE_ARN_RE.test(opts.roleArn)) {
46855
+ throw new Error(
46856
+ `invalid --role-arn "${opts.roleArn}": expected arn:aws:iam::<12-digit account>:role/<name>`
46857
+ );
46858
+ }
46859
+ const orgId = opts.orgId === void 0 ? void 0 : parsePositiveIntArg2(opts.orgId, "--org-id");
46860
+ const fields = {
46861
+ secret_ref: secretRef,
46862
+ role_arn: opts.roleArn,
46863
+ ...orgId !== void 0 ? { org_id: orgId } : {}
46864
+ };
46865
+ await run(
46866
+ opts,
46867
+ ({ client, signal }) => createAwsConnection(client, fields, { signal }),
46868
+ writeConnection
46682
46869
  );
46683
- }
46684
- if (binding.filebox_id <= 0) {
46685
- throw new Error(
46686
- `connector-query #${binding.query_id} has no filebox id recorded, so its date tag cannot be read; record it with dr connectors query repoint ${binding.query_id} --filebox-id <id>`
46870
+ });
46871
+ withFlags2(
46872
+ aws.command("list").description("List the org's AWS connections").option("--secret-ref <datasourceId>", "only the connection of this datasource")
46873
+ ).action(async (opts) => {
46874
+ await run(
46875
+ opts,
46876
+ ({ client, signal }) => listAwsConnections(client, opts.secretRef, { signal }),
46877
+ writeConnectionTable
46878
+ );
46879
+ });
46880
+ withFlags2(
46881
+ aws.command("get").description("Show an AWS connection and its trust statement").argument("<id>", "connection id")
46882
+ ).action(async (idArg, opts) => {
46883
+ const id = parsePositiveIntArg2(idArg, "connection id");
46884
+ await run(
46885
+ opts,
46886
+ ({ client, signal }) => getAwsConnection(client, id, { signal }),
46887
+ writeConnection,
46888
+ id
46889
+ );
46890
+ });
46891
+ for (const [verb, enabled, help] of [
46892
+ ["enable", true, "Enable an AWS connection; also retries now (stored credentials are dropped)"],
46893
+ ["disable", false, "Disable an AWS connection (stored credentials are dropped)"]
46894
+ ]) {
46895
+ withFlags2(aws.command(verb).description(help).argument("<id>", "connection id")).action(
46896
+ async (idArg, opts) => {
46897
+ const id = parsePositiveIntArg2(idArg, "connection id");
46898
+ await run(
46899
+ opts,
46900
+ ({ client, signal }) => updateAwsConnection(client, id, { enabled }, { signal }),
46901
+ (out, c) => {
46902
+ writeConnection(out, c);
46903
+ if (enabled)
46904
+ out.write(
46905
+ "\nStored credentials were dropped; the poller assumes the role again now.\n"
46906
+ );
46907
+ },
46908
+ id
46909
+ );
46910
+ }
46687
46911
  );
46688
46912
  }
46689
- const dateTag = await readFileboxDateTag(cfg.monolith, binding.filebox_id, cfg.signal);
46690
- const stored = await putPoll(
46691
- cfg.client,
46692
- binding.query_id,
46693
- { enabled: true, date_tag: dateTag, ...s },
46694
- { signal: cfg.signal }
46695
- );
46696
- if (cfg.asJson) {
46697
- writeJSONIndented(cfg.out, stored);
46698
- return;
46699
- }
46700
- renderPoll(cfg.out, "Polling enabled", binding, stored);
46701
- }
46702
- function windowStart(since, lookbackDays, now) {
46703
- const start = now - lookbackDays * 864e5;
46704
- return since === null ? start : Math.max(start, Date.parse(since));
46705
- }
46706
- function widening(current, changes, now) {
46707
- if (changes.since === void 0 && changes.lookback_days === void 0) return null;
46708
- const from = windowStart(current.since, current.lookback_days, now);
46709
- const to = windowStart(
46710
- changes.since ?? current.since,
46711
- changes.lookback_days ?? current.lookback_days,
46712
- now
46713
- );
46714
- if (to >= from) return null;
46715
- return { from: new Date(from).toISOString(), to: new Date(to).toISOString() };
46716
46913
  }
46717
- async function ask(cfg, message, choices, defaultChoice) {
46718
- const answer = await cfg.prompt(message, choices, defaultChoice);
46719
- if (answer === null) throw new Error("poll set cancelled; nothing was changed");
46720
- return parseChoice(answer, "the answer", choices);
46914
+
46915
+ // src/commands/connectorsDevFetch.ts
46916
+ import { promises as fs19 } from "fs";
46917
+ import * as path9 from "path";
46918
+ import { randomUUID as randomUUID2 } from "crypto";
46919
+ function buildDevEnvelope(i) {
46920
+ const config = {
46921
+ ...i.publicConfig,
46922
+ ...i.queryConfig,
46923
+ // Coerce absent values to null (not undefined) so JSON.stringify keeps the
46924
+ // key — matches build_hook_job's dq.get(...) -> None; a dropped objectType
46925
+ // becomes a cryptic KeyError in the runner's dispatch.
46926
+ objectType: i.dataQuery.objectType ?? null,
46927
+ type: i.dataQuery.type ?? null,
46928
+ query: i.dataQuery.query
46929
+ };
46930
+ if (i.period) config.period = i.period;
46931
+ return {
46932
+ correlation_id: i.correlationId,
46933
+ config,
46934
+ secret_ref: i.secretRef,
46935
+ cred_token: "dev-local",
46936
+ output_sas: "file:///out/output.csv",
46937
+ input_sas: null,
46938
+ cursor: null,
46939
+ contract_version: i.contractVersion,
46940
+ metadata: {}
46941
+ };
46721
46942
  }
46722
- var WIDEN_HELP = "seed = mark the newly exposed items as already landed, land = land them all";
46723
- var POLICY_HELP = "rekey = re-key the ledger from a fresh listing (nothing lands again), seed = mark everything in the window as landed, land = land everything in the window";
46724
- function pollSetConfirmVerb(widen, onPolicyChange) {
46725
- return widen === "land" || onPolicyChange === "land" ? "set-land" : "set-seed";
46943
+ function asObject(v) {
46944
+ if (typeof v === "object" && v !== null && !Array.isArray(v)) {
46945
+ return v;
46946
+ }
46947
+ return null;
46726
46948
  }
46727
- async function runPollSet(cfg) {
46728
- if (Object.keys(cfg.changes).length === 0) {
46949
+ function extractDataQueryFields(raw2) {
46950
+ const root = asObject(decodeShape(raw2));
46951
+ if (root === null || Object.keys(root).length === 0) {
46729
46952
  throw new Error(
46730
- "poll set: pass at least one of --interval, --since, --lookback, --settle, --same-name"
46953
+ "no data-query attached to this filebox (empty data_query response) \u2014 attach one in the filebox UI first"
46731
46954
  );
46732
46955
  }
46733
- const binding = await resolvePollBinding(cfg.client, cfg.filebox, cfg.signal);
46734
- const current = await explainNotPolled(
46735
- cfg.filebox,
46736
- getPoll(cfg.client, binding.query_id, { signal: cfg.signal })
46737
- );
46738
- const body = { ...cfg.changes };
46739
- const detail = [`target: ${bindingTarget(binding)}`];
46740
- const wider = widening(current, cfg.changes, cfg.now ?? Date.now());
46741
- if (wider !== null) {
46742
- const what = `the polling window widens (start ${wider.from} -> ${wider.to})`;
46743
- if (cfg.widen === void 0 && !cfg.interactive) {
46744
- throw new Error(`${what}; pass --widen seed or --widen land (${WIDEN_HELP})`);
46956
+ const cfg = asObject(root.config);
46957
+ const src = cfg ?? root;
46958
+ const query = src.query;
46959
+ let queryObj;
46960
+ if (query === void 0 || query === null) {
46961
+ queryObj = {};
46962
+ } else {
46963
+ const asObj = asObject(query);
46964
+ if (asObj === null) {
46965
+ throw new Error(
46966
+ `data-query \`query\` is not an object (got ${typeof query}) \u2014 expected the Sync-query field`
46967
+ );
46745
46968
  }
46746
- body.widen = cfg.widen ?? await ask(cfg, `${what}. ${WIDEN_HELP}.`, WIDEN_CHOICES);
46747
- detail.push(`window: start ${wider.from} -> ${wider.to}, widen ${body.widen}`);
46748
- } else if (cfg.widen !== void 0) {
46749
- body.widen = cfg.widen;
46969
+ queryObj = asObj;
46750
46970
  }
46751
- const newPolicy = cfg.changes.change_policy;
46752
- const policySwitch = newPolicy !== void 0 && newPolicy !== current.change_policy;
46753
- if (policySwitch) {
46754
- const change = `${sameNameLabel(current.change_policy)} -> ${sameNameLabel(newPolicy)}`;
46755
- const what = `--same-name changes ${change}`;
46756
- if (cfg.onPolicyChange === void 0 && !cfg.interactive) {
46757
- throw new Error(`${what}; pass --on-policy-change rekey, seed or land (${POLICY_HELP})`);
46758
- }
46759
- body.on_policy_change = cfg.onPolicyChange ?? await ask(cfg, `${what}. ${POLICY_HELP}.`, POLICY_CHOICES, "rekey");
46760
- detail.push(`same name: ${change}, on policy change ${body.on_policy_change}`);
46971
+ return { objectType: queryObj.objectType, type: src.type, query: queryObj };
46972
+ }
46973
+ function extractCredentials(integration) {
46974
+ const cfg = asObject(integration.config);
46975
+ if (cfg === null || Object.keys(cfg).length === 0) {
46976
+ throw new Error(
46977
+ `datasource ${integration.id} has an empty/missing \`config\` \u2014 nothing to use as connector credentials`
46978
+ );
46979
+ }
46980
+ return { ...cfg };
46981
+ }
46982
+ var ENV_FILE = "env.json";
46983
+ var SECRETS_FILE = "secrets.json";
46984
+ var CONNECTION_FILE = "connection.py";
46985
+ var QUERY_FILE = "query.py";
46986
+ async function writeRunDir(input) {
46987
+ let connectionSource;
46988
+ try {
46989
+ connectionSource = await fs19.readFile(input.connectionPath, "utf-8");
46990
+ } catch {
46991
+ throw new Error(`${input.connectionPath}: connection source not found or unreadable`);
46992
+ }
46993
+ let querySource;
46994
+ try {
46995
+ querySource = await fs19.readFile(input.queryPath, "utf-8");
46996
+ } catch {
46997
+ throw new Error(`${input.queryPath}: dataquery source not found or unreadable`);
46998
+ }
46999
+ await fs19.mkdir(input.outDir, { recursive: true });
47000
+ await fs19.writeFile(
47001
+ path9.join(input.outDir, ENV_FILE),
47002
+ JSON.stringify(input.env, null, 2) + "\n",
47003
+ "utf-8"
47004
+ );
47005
+ const secretsPath = path9.join(input.outDir, SECRETS_FILE);
47006
+ await fs19.writeFile(
47007
+ secretsPath,
47008
+ JSON.stringify({ [input.secretRef]: input.credentials }, null, 2) + "\n",
47009
+ "utf-8"
47010
+ );
47011
+ if (process.platform !== "win32") {
47012
+ await fs19.chmod(secretsPath, 384);
47013
+ }
47014
+ await fs19.writeFile(path9.join(input.outDir, CONNECTION_FILE), connectionSource, "utf-8");
47015
+ await fs19.writeFile(path9.join(input.outDir, QUERY_FILE), querySource, "utf-8");
47016
+ }
47017
+ async function ensureGitignored(entry) {
47018
+ const gitignorePath = path9.join(process.cwd(), ".gitignore");
47019
+ let current = "";
47020
+ try {
47021
+ current = await fs19.readFile(gitignorePath, "utf-8");
47022
+ } catch {
47023
+ }
47024
+ const normalized = entry.split("\\").join("/").replace(/\/+$/, "") + "/";
47025
+ const lines = current.split(/\r?\n/).map((l) => l.trim());
47026
+ if (lines.includes(normalized) || lines.includes(entry)) return;
47027
+ const sep = current === "" || current.endsWith("\n") ? "" : "\n";
47028
+ await fs19.writeFile(gitignorePath, `${current}${sep}${normalized}
47029
+ `, "utf-8");
47030
+ }
47031
+ function parsePositiveInt3(raw2, label) {
47032
+ const n = Number(raw2);
47033
+ if (!Number.isInteger(n) || String(n) !== raw2.trim() || n <= 0) {
47034
+ throw new Error(`invalid ${label}: "${raw2}"`);
47035
+ }
47036
+ return n;
47037
+ }
47038
+ function registerDevFetch(connectors) {
47039
+ const cmd = connectors.command("dev-fetch").description(
47040
+ "Assemble a local run dir (env.json + secrets.json + connection.py + query.py) for dev-run.sh"
47041
+ ).argument("<fileboxUuid>", "filebox UUID of the connector binding").requiredOption(
47042
+ "--datasource <id>",
47043
+ "datasource (data-integration) id whose credentials to fetch"
47044
+ ).requiredOption(
47045
+ "--document-id <n>",
47046
+ "numeric filebox document id (no uuid->id lookup exists; required)"
47047
+ ).requiredOption("--connection <path>", "path to the connector's connection .py source").requiredOption("--query <path>", "path to the connector's dataquery .py source").option("--from <date>", "period start (paired with --to)").option("--to <date>", "period end (paired with --from)").option("-o, --output <dir>", "output run dir (default ./dev-run/<fileboxUuid>/)").action(async (fileboxUuid, opts) => {
47048
+ const datasourceId = parsePositiveInt3(opts.datasource, "datasource id");
47049
+ const documentId = parsePositiveInt3(opts.documentId, "document-id");
47050
+ if (opts.from === void 0 !== (opts.to === void 0)) {
47051
+ throw new Error("--from and --to must be provided together");
47052
+ }
47053
+ const defaultRoot = path9.join("dev-run", fileboxUuid);
47054
+ const outDir = opts.output ?? defaultRoot;
47055
+ const gitignoreEntry = opts.output ?? "dev-run";
47056
+ const serverKey = resolveServer(opts.server);
47057
+ const host = resolveConnectorsUrl(serverKey, opts.connectorsUrl);
47058
+ const monolithHost = resolve(serverKey).host;
47059
+ const cpSetup = await setupCall(opts.server, opts.account, { host, authHost: monolithHost });
47060
+ try {
47061
+ const binding = await getBinding(cpSetup.client, fileboxUuid, {
47062
+ signal: cpSetup.signal
47063
+ });
47064
+ const monoSetup = await setupCall(opts.server, opts.account, {
47065
+ host: monolithHost,
47066
+ authHost: monolithHost
47067
+ });
47068
+ try {
47069
+ const rawDataQuery = await getDataQueryRaw(monoSetup.client, documentId, {
47070
+ signal: monoSetup.signal
47071
+ });
47072
+ const dataQuery = extractDataQueryFields(rawDataQuery);
47073
+ if (dataQuery.objectType === void 0 || dataQuery.objectType === null || dataQuery.objectType === "") {
47074
+ process.stderr.write(
47075
+ 'WARNING: objectType is not set inside the Sync-query (config.query.objectType). get_query_class will receive None \u2014 fine only if the connector has a single default dataset. Add it to the data-query, e.g. config.query.objectType = "<dataset>".\n'
47076
+ );
47077
+ }
47078
+ const integration = await itGet(monoSetup.client, datasourceId, {
47079
+ signal: monoSetup.signal
47080
+ });
47081
+ const credentials = extractCredentials(integration);
47082
+ let period;
47083
+ if (opts.from !== void 0 && opts.to !== void 0) {
47084
+ period = { from: opts.from, to: opts.to };
47085
+ }
47086
+ const env = buildDevEnvelope({
47087
+ correlationId: randomUUID2(),
47088
+ secretRef: binding.secret_ref,
47089
+ publicConfig: binding.public_config,
47090
+ queryConfig: binding.query_config,
47091
+ dataQuery,
47092
+ ...period !== void 0 ? { period } : {},
47093
+ // Deliberate sandbox stub: the fetch/dispatch path doesn't branch on
47094
+ // contract_version, so a fixed "1" is sufficient for a local run.
47095
+ contractVersion: "1"
47096
+ });
47097
+ await writeRunDir({
47098
+ outDir,
47099
+ env,
47100
+ secretRef: binding.secret_ref,
47101
+ credentials,
47102
+ connectionPath: opts.connection,
47103
+ queryPath: opts.query
47104
+ });
47105
+ await ensureGitignored(gitignoreEntry);
47106
+ process.stdout.write(`Wrote run dir: ${outDir}
47107
+ `);
47108
+ process.stdout.write(
47109
+ ` env.json (secret_ref=${binding.secret_ref}, flavor=${binding.runtime_flavor})
47110
+ `
47111
+ );
47112
+ process.stdout.write(" secrets.json (credentials NOT shown)\n");
47113
+ process.stdout.write(" connection.py, query.py\n");
47114
+ } finally {
47115
+ monoSetup.cleanup();
47116
+ }
47117
+ } catch (err) {
47118
+ throw augmentTlsCertError(err);
47119
+ } finally {
47120
+ cpSetup.cleanup();
47121
+ }
47122
+ });
47123
+ addServerFlag(cmd);
47124
+ addAccountFlag(cmd);
47125
+ cmd.option(
47126
+ "--connectors-url <url>",
47127
+ "Override the control-plane base URL (e.g. a kubectl port-forward http://localhost:8000)"
47128
+ );
47129
+ }
47130
+
47131
+ // src/commands/connectorsPoll.ts
47132
+ import * as readline2 from "readline";
47133
+ import { createHash as createHash11 } from "crypto";
47134
+
47135
+ // src/types/connectors.ts
47136
+ var POLL_ITEM_STATES = [
47137
+ "seen",
47138
+ "new",
47139
+ "dispatched",
47140
+ "running",
47141
+ "retry_wait",
47142
+ "landed",
47143
+ "failed",
47144
+ "skipped",
47145
+ "seeded",
47146
+ "superseded"
47147
+ ];
47148
+
47149
+ // src/commands/connectorsPoll.ts
47150
+ var DURATION_UNIT_SECONDS2 = { s: 1, m: 60, h: 3600, d: 86400 };
47151
+ var DURATION_BOUNDS = {
47152
+ "--interval": [300, 7 * 86400],
47153
+ "--settle": [0, 86400]
47154
+ };
47155
+ var LOOKBACK_MAX_DAYS = 3650;
47156
+ function parseDurationSeconds(raw2, flag) {
47157
+ const m = /^(\d+)([smhd])?$/.exec(raw2.trim());
47158
+ const seconds = m === null ? NaN : Number(m[1]) * (DURATION_UNIT_SECONDS2[m[2] ?? "s"] ?? 1);
47159
+ const [min, max] = DURATION_BOUNDS[flag] ?? [0, Number.MAX_SAFE_INTEGER];
47160
+ if (m === null) {
47161
+ throw new Error(`invalid ${flag} "${raw2}": expected a duration like 90s, 5m or 1h`);
47162
+ }
47163
+ if (!Number.isSafeInteger(seconds) || seconds < min || seconds > max) {
47164
+ throw new Error(
47165
+ `invalid ${flag} "${raw2}": must be between ${formatSeconds(min)} and ${formatSeconds(max)}`
47166
+ );
47167
+ }
47168
+ return seconds;
47169
+ }
47170
+ function parseLookbackDays(raw2) {
47171
+ const m = /^(\d+)d?$/.exec(raw2.trim());
47172
+ const days = m === null ? 0 : Number(m[1]);
47173
+ if (days < 1 || days > LOOKBACK_MAX_DAYS) {
47174
+ throw new Error(
47175
+ `invalid --lookback "${raw2}": must be between 1 and ${LOOKBACK_MAX_DAYS} days, e.g. 180d`
47176
+ );
47177
+ }
47178
+ return days;
47179
+ }
47180
+ var DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
47181
+ var DATE_TIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(?:Z|[+-](\d{2}):(\d{2}))$/;
47182
+ function inRange(value, max, min = 0) {
47183
+ const n = Number(value ?? "0");
47184
+ return n >= min && n <= max;
47185
+ }
47186
+ function isCalendarDay(y, m, d) {
47187
+ if (!inRange(m, 12, 1)) return false;
47188
+ const daysInMonth3 = new Date(Date.UTC(Number(y), Number(m), 0)).getUTCDate();
47189
+ return inRange(d, daysInMonth3, 1);
47190
+ }
47191
+ function parseTimestampFlag(raw2, flag) {
47192
+ const invalid = new Error(
47193
+ `invalid ${flag} "${raw2}": expected YYYY-MM-DD or a timestamp with a zone (e.g. 2026-10-08T17:30:00+03:00)`
47194
+ );
47195
+ const value = raw2.trim();
47196
+ const dateOnly = DATE_ONLY_RE.exec(value);
47197
+ if (dateOnly !== null) {
47198
+ if (!isCalendarDay(dateOnly[1], dateOnly[2], dateOnly[3])) throw invalid;
47199
+ return (/* @__PURE__ */ new Date(`${value}T00:00:00Z`)).toISOString();
47200
+ }
47201
+ const dt = DATE_TIME_RE.exec(value);
47202
+ const valid = dt !== null && isCalendarDay(dt[1], dt[2], dt[3]) && inRange(dt[4], 23) && inRange(dt[5], 59) && inRange(dt[6], 59) && inRange(dt[7], 23) && inRange(dt[8], 59);
47203
+ const ms = valid ? Date.parse(value) : NaN;
47204
+ if (Number.isNaN(ms)) throw invalid;
47205
+ return new Date(ms).toISOString();
47206
+ }
47207
+ function parseSameName(raw2) {
47208
+ if (raw2 === "new-version") return "new_version";
47209
+ if (raw2 === "ignore") return "ignore";
47210
+ throw new Error(`--same-name must be new-version or ignore (got "${raw2}")`);
47211
+ }
47212
+ function parseChoice(raw2, flag, choices) {
47213
+ const match = choices.find((c) => c === raw2);
47214
+ if (match === void 0) {
47215
+ throw new Error(`${flag} must be ${choices.join(" or ")} (got "${raw2}")`);
47216
+ }
47217
+ return match;
47218
+ }
47219
+ var WIDEN_CHOICES = ["seed", "land"];
47220
+ var POLICY_CHOICES = ["rekey", "seed", "land"];
47221
+ function parseSettings(opts) {
47222
+ const fields = {};
47223
+ if (opts.interval !== void 0) {
47224
+ fields.interval_seconds = parseDurationSeconds(opts.interval, "--interval");
47225
+ }
47226
+ if (opts.since !== void 0) fields.since = parseTimestampFlag(opts.since, "--since");
47227
+ if (opts.lookback !== void 0) fields.lookback_days = parseLookbackDays(opts.lookback);
47228
+ if (opts.settle !== void 0) {
47229
+ fields.settle_seconds = parseDurationSeconds(opts.settle, "--settle");
47230
+ }
47231
+ if (opts.sameName !== void 0) fields.change_policy = parseSameName(opts.sameName);
47232
+ return fields;
47233
+ }
47234
+ function settingsArgs(opts) {
47235
+ const pairs = [
47236
+ ["--interval", opts.interval],
47237
+ ["--since", opts.since],
47238
+ ["--lookback", opts.lookback],
47239
+ ["--settle", opts.settle],
47240
+ ["--same-name", opts.sameName]
47241
+ ];
47242
+ return pairs.filter((p) => p[1] !== void 0).map(([flag, value]) => ` ${flag} ${value.trim()}`).join("");
47243
+ }
47244
+ async function resolvePollBinding(client, filebox, signal) {
47245
+ const wanted = filebox.trim();
47246
+ const byId = /^\d+$/.test(wanted);
47247
+ const rows = await listQueries(client, { signal });
47248
+ const matches = rows.filter(
47249
+ (q) => byId ? q.filebox_id === Number(wanted) : q.filebox_uuid.toLowerCase() === wanted.toLowerCase()
47250
+ );
47251
+ const [first] = matches;
47252
+ if (first === void 0) {
47253
+ throw new Error(
47254
+ `no connector binding for filebox ${wanted} in this org (see dr connectors query list)`
47255
+ );
47256
+ }
47257
+ if (matches.length > 1) {
47258
+ const ids = matches.map((q) => `#${q.query_id}`).join(", ");
47259
+ throw new Error(`filebox ${wanted} has several connector bindings (${ids})`);
47260
+ }
47261
+ return first;
47262
+ }
47263
+ function isNotPolled(err) {
47264
+ return err instanceof ControlPlaneError && err.status === 404 && err.body.includes("not polled");
47265
+ }
47266
+ function isPollThrottled(err) {
47267
+ return err instanceof ControlPlaneError && err.status === 409 && (err.body.includes("discover is in flight") || err.body.includes("polled less than"));
47268
+ }
47269
+ async function explainNotPolled(filebox, call3) {
47270
+ try {
47271
+ return await call3;
47272
+ } catch (err) {
47273
+ if (isNotPolled(err)) {
47274
+ throw new Error(
47275
+ `filebox ${filebox} is not set to polled ingestion; run dr connectors poll enable ${filebox}`,
47276
+ { cause: err }
47277
+ );
47278
+ }
47279
+ throw err;
47280
+ }
47281
+ }
47282
+ async function readFileboxDateTag(monolith, fileboxId, signal) {
47283
+ const details = await getDetails(monolith, fileboxId, { signal });
47284
+ const dateTags = (details.tags_config ?? []).filter((t) => t.type === "DATE");
47285
+ const [only] = dateTags;
47286
+ if (only === void 0) return {};
47287
+ if (dateTags.length > 1) {
47288
+ const names = dateTags.map((t) => t.name).join(", ");
47289
+ throw new Error(
47290
+ `filebox ${fileboxId} has several DATE tags (${names}); a polled filebox needs exactly one`
47291
+ );
47292
+ }
47293
+ return { id: only.id, name: only.name };
47294
+ }
47295
+ function formatSeconds(sec) {
47296
+ if (sec > 0 && sec % 86400 === 0) return `${sec / 86400}d`;
47297
+ if (sec > 0 && sec % 3600 === 0) return `${sec / 3600}h`;
47298
+ if (sec > 0 && sec % 60 === 0) return `${sec / 60}m`;
47299
+ return `${sec}s`;
47300
+ }
47301
+ function oneLine4(s) {
47302
+ return s.replace(/\s+/g, " ").trim();
47303
+ }
47304
+ function sameNameLabel(policy) {
47305
+ return policy === "new_version" ? "new-version" : String(policy);
47306
+ }
47307
+ function formatDateTag(tag) {
47308
+ if (tag?.id === void 0) return "none (undated filebox)";
47309
+ return `${tag.name ?? "?"} (id ${tag.id})`;
47310
+ }
47311
+ function bindingTarget(binding) {
47312
+ return `filebox ${binding.filebox_uuid} (query #${binding.query_id})`;
47313
+ }
47314
+ function renderPoll(out, headline, binding, p) {
47315
+ const row = (label, value) => {
47316
+ out.write(` ${`${label}:`.padEnd(15)}${value}
47317
+ `);
47318
+ };
47319
+ const status2 = `${p.enabled ? "enabled" : "disabled"}${p.paused_reason === null ? "" : `, paused: ${oneLine4(p.paused_reason)}`}`;
47320
+ const counts = Object.entries(p.counts).sort(([a], [b]) => a.localeCompare(b)).map(([state, n]) => `${state}=${n}`).join(" ");
47321
+ out.write(`${headline} for ${bindingTarget(binding)}
47322
+ `);
47323
+ row("Status", status2);
47324
+ row("Interval", formatSeconds(p.interval_seconds));
47325
+ row("Date tag", formatDateTag(p.date_tag));
47326
+ row("Since", p.since ?? "-");
47327
+ row("Lookback", `${p.lookback_days}d`);
47328
+ row("Settle", formatSeconds(p.settle_seconds));
47329
+ row("Same name", sameNameLabel(p.change_policy));
47330
+ row("Max attempts", String(p.max_attempts));
47331
+ row("Last poll", `${p.last_poll_at ?? "-"} ${oneLine4(p.last_poll_status ?? "")}`.trimEnd());
47332
+ row("Next poll", p.next_poll_at);
47333
+ row("Failures", String(p.consecutive_failures));
47334
+ row("Discover", p.discover_in_flight ? `in flight since ${p.discover_sent_at ?? "?"}` : "-");
47335
+ row("Last digest", p.last_digest ?? "-");
47336
+ row("Pending seed", p.poll_run_seed_until ?? "-");
47337
+ row(
47338
+ "Pending rekey",
47339
+ p.poll_run_rekey_to === null ? "-" : `to ${sameNameLabel(p.poll_run_rekey_to)}`
47340
+ );
47341
+ row("Items", counts === "" ? "(none)" : counts);
47342
+ }
47343
+ function writeTable26(out, rows) {
47344
+ const widths = [];
47345
+ for (const r of rows) {
47346
+ r.forEach((cell, i) => {
47347
+ widths[i] = Math.max(widths[i] ?? 0, cell.length);
47348
+ });
47349
+ }
47350
+ for (const r of rows) {
47351
+ const cells = r.map(
47352
+ (cell, i) => i === r.length - 1 ? cell : cell.padEnd((widths[i] ?? 0) + 2)
47353
+ );
47354
+ out.write(`${cells.join("").trimEnd()}
47355
+ `);
47356
+ }
47357
+ }
47358
+ function renderItemTable(out, items) {
47359
+ const rows = [["ID", "LABEL", "DATE", "STATE", "ATTEMPTS", "VERSION", "ERROR"]];
47360
+ for (const it of items) {
47361
+ const error = it.last_error ?? (it.skip_reason === null ? null : `skipped: ${it.skip_reason}`);
47362
+ rows.push([
47363
+ String(it.id),
47364
+ oneLine4(it.label),
47365
+ it.item_date ?? "-",
47366
+ it.state,
47367
+ String(it.attempts),
47368
+ it.version_id === null ? "-" : String(it.version_id),
47369
+ error === null ? "-" : truncate3(oneLine4(error), 80)
47370
+ ]);
47371
+ }
47372
+ writeTable26(out, rows);
47373
+ }
47374
+ async function runPollEnable(cfg) {
47375
+ const binding = await resolvePollBinding(cfg.client, cfg.filebox, cfg.signal);
47376
+ let existing;
47377
+ try {
47378
+ existing = await getPoll(cfg.client, binding.query_id, { signal: cfg.signal });
47379
+ } catch (err) {
47380
+ if (!isNotPolled(err)) throw err;
47381
+ existing = null;
47382
+ }
47383
+ const s = cfg.settings;
47384
+ if (existing !== null && (s.since !== void 0 || s.lookback_days !== void 0 || s.change_policy !== void 0)) {
47385
+ throw new Error(
47386
+ `filebox ${cfg.filebox} is already polled; change --since, --lookback or --same-name with dr connectors poll set ${cfg.filebox}, which asks what to do with a wider window or a policy switch`
47387
+ );
47388
+ }
47389
+ if (binding.filebox_id <= 0) {
47390
+ throw new Error(
47391
+ `connector-query #${binding.query_id} has no filebox id recorded, so its date tag cannot be read; record it with dr connectors query repoint ${binding.query_id} --filebox-id <id>`
47392
+ );
47393
+ }
47394
+ const dateTag = await readFileboxDateTag(cfg.monolith, binding.filebox_id, cfg.signal);
47395
+ const stored = await putPoll(
47396
+ cfg.client,
47397
+ binding.query_id,
47398
+ { enabled: true, date_tag: dateTag, ...s },
47399
+ { signal: cfg.signal }
47400
+ );
47401
+ if (cfg.asJson) {
47402
+ writeJSONIndented(cfg.out, stored);
47403
+ return;
47404
+ }
47405
+ renderPoll(cfg.out, "Polling enabled", binding, stored);
47406
+ }
47407
+ function windowStart(since, lookbackDays, now) {
47408
+ const start = now - lookbackDays * 864e5;
47409
+ return since === null ? start : Math.max(start, Date.parse(since));
47410
+ }
47411
+ function widening(current, changes, now) {
47412
+ if (changes.since === void 0 && changes.lookback_days === void 0) return null;
47413
+ const from = windowStart(current.since, current.lookback_days, now);
47414
+ const to = windowStart(
47415
+ changes.since ?? current.since,
47416
+ changes.lookback_days ?? current.lookback_days,
47417
+ now
47418
+ );
47419
+ if (to >= from) return null;
47420
+ return { from: new Date(from).toISOString(), to: new Date(to).toISOString() };
47421
+ }
47422
+ async function ask(cfg, message, choices, defaultChoice) {
47423
+ const answer = await cfg.prompt(message, choices, defaultChoice);
47424
+ if (answer === null) throw new Error("poll set cancelled; nothing was changed");
47425
+ return parseChoice(answer, "the answer", choices);
47426
+ }
47427
+ var WIDEN_HELP = "seed = mark the newly exposed items as already landed, land = land them all";
47428
+ var POLICY_HELP = "rekey = re-key the ledger from a fresh listing (nothing lands again), seed = mark everything in the window as landed, land = land everything in the window";
47429
+ function pollSetConfirmVerb(widen, onPolicyChange) {
47430
+ return widen === "land" || onPolicyChange === "land" ? "set-land" : "set-seed";
47431
+ }
47432
+ async function runPollSet(cfg) {
47433
+ if (Object.keys(cfg.changes).length === 0) {
47434
+ throw new Error(
47435
+ "poll set: pass at least one of --interval, --since, --lookback, --settle, --same-name"
47436
+ );
47437
+ }
47438
+ const binding = await resolvePollBinding(cfg.client, cfg.filebox, cfg.signal);
47439
+ const current = await explainNotPolled(
47440
+ cfg.filebox,
47441
+ getPoll(cfg.client, binding.query_id, { signal: cfg.signal })
47442
+ );
47443
+ const body = { ...cfg.changes };
47444
+ const detail = [`target: ${bindingTarget(binding)}`];
47445
+ const wider = widening(current, cfg.changes, cfg.now ?? Date.now());
47446
+ if (wider !== null) {
47447
+ const what = `the polling window widens (start ${wider.from} -> ${wider.to})`;
47448
+ if (cfg.widen === void 0 && !cfg.interactive) {
47449
+ throw new Error(`${what}; pass --widen seed or --widen land (${WIDEN_HELP})`);
47450
+ }
47451
+ body.widen = cfg.widen ?? await ask(cfg, `${what}. ${WIDEN_HELP}.`, WIDEN_CHOICES);
47452
+ detail.push(`window: start ${wider.from} -> ${wider.to}, widen ${body.widen}`);
47453
+ } else if (cfg.widen !== void 0) {
47454
+ body.widen = cfg.widen;
47455
+ }
47456
+ const newPolicy = cfg.changes.change_policy;
47457
+ const policySwitch = newPolicy !== void 0 && newPolicy !== current.change_policy;
47458
+ if (policySwitch) {
47459
+ const change = `${sameNameLabel(current.change_policy)} -> ${sameNameLabel(newPolicy)}`;
47460
+ const what = `--same-name changes ${change}`;
47461
+ if (cfg.onPolicyChange === void 0 && !cfg.interactive) {
47462
+ throw new Error(`${what}; pass --on-policy-change rekey, seed or land (${POLICY_HELP})`);
47463
+ }
47464
+ body.on_policy_change = cfg.onPolicyChange ?? await ask(cfg, `${what}. ${POLICY_HELP}.`, POLICY_CHOICES, "rekey");
47465
+ detail.push(`same name: ${change}, on policy change ${body.on_policy_change}`);
46761
47466
  } else if (cfg.onPolicyChange !== void 0) {
46762
47467
  body.on_policy_change = cfg.onPolicyChange;
46763
47468
  }
@@ -47076,793 +47781,332 @@ async function withMonolith(opts, action) {
47076
47781
  function scopeOf(opts) {
47077
47782
  const serverKey = resolveServer(opts.server);
47078
47783
  return {
47079
- serverKey,
47080
- account: normaliseAccount(opts.account),
47081
- controlPlane: resolveConnectorsUrl(serverKey, opts.connectorsUrl)
47082
- };
47083
- }
47084
- function targetArgsOf(opts) {
47085
- const url = (opts.connectorsUrl ?? "").trim();
47086
- return ` --server ${resolveServer(opts.server)}${url === "" ? "" : ` --connectors-url ${url}`}`;
47087
- }
47088
- function addCommonFlags(cmd, jsonHelp) {
47089
- cmd.option("--json", jsonHelp, false);
47090
- addServerFlag(cmd);
47091
- addAccountFlag(cmd);
47092
- addConnectorsUrlFlag(cmd);
47093
- }
47094
- function addConfirmTokenFlag(cmd) {
47095
- return cmd.option("--confirm-token <token>", "Token from a prior plan run \u2014 required to proceed");
47096
- }
47097
- function addSettingsFlags(cmd) {
47098
- return cmd.option("--interval <duration>", "how often to poll, 5m to 7d (default: per source)").option("--since <date>", "never pick up items modified before this date or timestamp").option("--lookback <days>", "window every poll lists, 1d to 3650d (default: per source)").option("--settle <duration>", "how long an item must stay unchanged before landing, 0s to 1d").option(
47099
- "--same-name <policy>",
47100
- "a changed file under the same name: new-version (lands again) or ignore"
47101
- );
47102
- }
47103
- function ttyChoicePrompt(message, choices, defaultChoice, io = { input: process.stdin, output: process.stderr }) {
47104
- return new Promise((resolvePrompt) => {
47105
- const rl = readline2.createInterface({
47106
- input: io.input,
47107
- output: io.output,
47108
- ...io.terminal !== void 0 ? { terminal: io.terminal } : {}
47109
- });
47110
- let settled = false;
47111
- const settle = (value) => {
47112
- if (settled) return;
47113
- settled = true;
47114
- rl.close();
47115
- resolvePrompt(value);
47116
- };
47117
- rl.on("SIGINT", () => settle(null));
47118
- rl.on("close", () => settle(null));
47119
- io.output.write(`${message}
47120
- `);
47121
- const question = defaultChoice === void 0 ? `[${choices.join("/")}]: ` : `[${choices.join("/")}] (Enter = ${defaultChoice}): `;
47122
- const askOnce = () => {
47123
- rl.question(question, (answer) => {
47124
- const picked = answer.trim().toLowerCase();
47125
- if (picked === "" && defaultChoice !== void 0) {
47126
- settle(defaultChoice);
47127
- } else if (choices.includes(picked)) {
47128
- settle(picked);
47129
- } else {
47130
- io.output.write(`Answer one of: ${choices.join(", ")}
47131
- `);
47132
- askOnce();
47133
- }
47134
- });
47135
- };
47136
- askOnce();
47137
- });
47138
- }
47139
- var POLL_CONFIRM_HELP = [
47140
- "Confirmation:",
47141
- " seed, skip and set --on-policy-change seed print a plan and a --confirm-token;",
47142
- " re-run the printed command, token included, to apply it.",
47143
- " No dialog opens on production, so an agent or script can run a cut-over unattended:",
47144
- " dr connectors poll seed <filebox> --until <cut-over> --server <prod>",
47145
- " dr connectors poll seed <filebox> --until <cut-over> --server <prod> --confirm-token <token>",
47146
- " (seed --dry-run counts the already-discovered items a seed would mark.)",
47147
- " A write with any answer that lands files (set --widen land, set --on-policy-change land,",
47148
- " resume --source land) also asks a person to type a word in a dialog on production,",
47149
- " even when the same set also seeds (--on-policy-change seed --widen land)."
47150
- ].join("\n");
47151
- function registerConnectorsPoll(connectors) {
47152
- const poll2 = connectors.command("poll").description("Polled file ingestion: the control-plane lands each new file as one version").addHelpText("after", `
47153
- ${POLL_CONFIRM_HELP}`);
47154
- const enableCmd = addSettingsFlags(
47155
- poll2.command("enable").description("Poll a filebox's binding (dated with the filebox's DATE tag)").argument("<filebox>", "filebox id or uuid")
47156
- ).action(async (filebox, opts) => {
47157
- const settings = parseSettings(opts);
47158
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47159
- await withMonolith(opts, async (monolith) => {
47160
- await runPollEnable({
47161
- client,
47162
- monolith,
47163
- filebox,
47164
- settings,
47165
- asJson: opts.json === true,
47166
- signal,
47167
- out: process.stdout
47168
- });
47169
- });
47170
- });
47171
- });
47172
- addCommonFlags(enableCmd, "Output the stored binding as JSON");
47173
- const setCmd = addConfirmTokenFlag(
47174
- addSettingsFlags(
47175
- poll2.command("set").description("Change polling settings (only the given flags change)").argument("<filebox>", "filebox id or uuid")
47176
- ).option(
47177
- "--widen <seed|land>",
47178
- "a wider window: seed (mark exposed items landed) or land them"
47179
- ).option("--on-policy-change <rekey|seed|land>", "a --same-name switch: rekey, seed or land").addHelpText("after", `
47180
- ${POLL_CONFIRM_HELP}`)
47181
- ).action(
47182
- async (filebox, opts) => {
47183
- const changes = parseSettings(opts);
47184
- const widen = opts.widen === void 0 ? void 0 : parseChoice(opts.widen, "--widen", WIDEN_CHOICES);
47185
- const onPolicyChange = opts.onPolicyChange === void 0 ? void 0 : parseChoice(opts.onPolicyChange, "--on-policy-change", POLICY_CHOICES);
47186
- await preflightConfirmToken(
47187
- pollConfirmAction(filebox, pollSetConfirmVerb(widen, onPolicyChange), scopeOf(opts), {
47188
- ...changes,
47189
- ...widen !== void 0 ? { widen } : {},
47190
- ...onPolicyChange !== void 0 ? { on_policy_change: onPolicyChange } : {}
47191
- }),
47192
- opts.confirmToken
47193
- );
47194
- await withConnectorsSetup(opts, async (setup) => {
47195
- const deadline = {
47196
- pause: () => setup.pauseTimeout(),
47197
- resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
47198
- };
47199
- await runPollSet({
47200
- client: setup.client,
47201
- filebox,
47202
- changes,
47203
- settingsArgs: settingsArgs(opts),
47204
- ...widen !== void 0 ? { widen } : {},
47205
- ...onPolicyChange !== void 0 ? { onPolicyChange } : {},
47206
- interactive: process.stdin.isTTY === true && process.stderr.isTTY === true,
47207
- prompt: async (message, choices, defaultChoice) => {
47208
- deadline.pause();
47209
- try {
47210
- return await ttyChoicePrompt(message, choices, defaultChoice);
47211
- } finally {
47212
- deadline.resume();
47213
- }
47214
- },
47215
- deadline,
47216
- ...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
47217
- scope: scopeOf(opts),
47218
- targetArgs: targetArgsOf(opts),
47219
- asJson: opts.json === true,
47220
- signal: setup.signal,
47221
- out: process.stdout
47222
- });
47223
- });
47224
- }
47225
- );
47226
- addCommonFlags(setCmd, "Output the stored binding as JSON");
47227
- const disableCmd = poll2.command("disable").description("Stop polling (an item already in flight still finishes)").argument("<filebox>", "filebox id or uuid").action(async (filebox, opts) => {
47228
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47229
- await runPollDisable({
47230
- client,
47231
- filebox,
47232
- asJson: opts.json === true,
47233
- signal,
47234
- out: process.stdout
47235
- });
47236
- });
47237
- });
47238
- addCommonFlags(disableCmd, "Output the stored binding as JSON");
47239
- const getCmd = poll2.command("get").description("Show polling settings, last poll, pending seed / rekey and item counts").argument("<filebox>", "filebox id or uuid").action(async (filebox, opts) => {
47240
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47241
- await runPollGet({
47242
- client,
47243
- filebox,
47244
- asJson: opts.json === true,
47245
- signal,
47246
- out: process.stdout
47247
- });
47248
- });
47249
- });
47250
- addCommonFlags(getCmd, "Output the raw binding as JSON");
47251
- const runCmd = poll2.command("run").description("Poll now").argument("<filebox>", "filebox id or uuid").action(async (filebox, opts) => {
47252
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47253
- await runPollRun({
47254
- client,
47255
- filebox,
47256
- asJson: opts.json === true,
47257
- signal,
47258
- out: process.stdout
47259
- });
47260
- });
47261
- });
47262
- addCommonFlags(runCmd, "Output the binding as JSON");
47263
- const resumeCmd = addConfirmTokenFlag(
47264
- poll2.command("resume").description("Resume a paused binding (after fixing credentials, or a source change)").argument("<filebox>", "filebox id or uuid").option(
47265
- "--source <seed|land>",
47266
- "after a source change: seed (mark present items landed) or land them"
47267
- )
47268
- ).action(async (filebox, opts) => {
47269
- const source = opts.source === void 0 ? void 0 : parseChoice(opts.source, "--source", WIDEN_CHOICES);
47270
- if (source === "land") {
47271
- await preflightConfirmToken(
47272
- pollConfirmAction(filebox, "resume-land", scopeOf(opts)),
47273
- opts.confirmToken
47274
- );
47275
- }
47276
- await withConnectorsSetup(opts, async (setup) => {
47277
- await runPollResume({
47278
- client: setup.client,
47279
- filebox,
47280
- ...source !== void 0 ? { source } : {},
47281
- deadline: {
47282
- pause: () => setup.pauseTimeout(),
47283
- resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
47284
- },
47285
- ...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
47286
- scope: scopeOf(opts),
47287
- targetArgs: targetArgsOf(opts),
47288
- asJson: opts.json === true,
47289
- signal: setup.signal,
47290
- out: process.stdout
47291
- });
47292
- });
47293
- });
47294
- addCommonFlags(resumeCmd, "Output the binding as JSON");
47295
- const itemsCmd = poll2.command("items").description("List the item ledger: label, date, state, attempts, version, error").argument("<filebox>", "filebox id or uuid").option("--state <state>", `only items in this state (${POLL_ITEM_STATES.join("|")})`).option("--limit <n>", "page size (default and maximum: the control-plane page size, 100)").option("--after <id>", "start after this item id (the next-page cursor)").action(
47296
- async (filebox, opts) => {
47297
- const state = opts.state === void 0 ? void 0 : POLL_ITEM_STATES.find((s) => s === opts.state);
47298
- if (opts.state !== void 0 && state === void 0) {
47299
- throw new Error(
47300
- `invalid --state "${opts.state}": expected one of ${POLL_ITEM_STATES.join(", ")}`
47301
- );
47302
- }
47303
- const limit = opts.limit === void 0 ? void 0 : parsePositiveIntArg2(opts.limit, "--limit");
47304
- const after = opts.after === void 0 ? void 0 : parsePositiveIntArg2(opts.after, "--after");
47305
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47306
- await runPollItems({
47307
- client,
47308
- filebox,
47309
- ...state !== void 0 ? { state } : {},
47310
- ...limit !== void 0 ? { limit } : {},
47311
- ...after !== void 0 ? { after } : {},
47312
- asJson: opts.json === true,
47313
- signal,
47314
- out: process.stdout
47315
- });
47316
- });
47317
- }
47318
- );
47319
- addCommonFlags(itemsCmd, "Output the raw page as JSON");
47320
- const retryCmd = poll2.command("retry").description("Queue a failed, waiting or skipped item again").argument("<filebox>", "filebox id or uuid").argument("<item>", "item id (from poll items)").action(async (filebox, item, opts) => {
47321
- const itemId = parsePositiveIntArg2(item, "item id");
47322
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47323
- await runPollRetry({
47324
- client,
47325
- filebox,
47326
- itemId,
47327
- asJson: opts.json === true,
47328
- signal,
47329
- out: process.stdout
47330
- });
47331
- });
47332
- });
47333
- addCommonFlags(retryCmd, "Output the item as JSON");
47334
- const skipCmd = addConfirmTokenFlag(
47335
- poll2.command("skip").description("Skip an item so it is never landed (two-step confirmation)").argument("<filebox>", "filebox id or uuid").argument("<item>", "item id (from poll items)")
47336
- ).action(async (filebox, item, opts) => {
47337
- const itemId = parsePositiveIntArg2(item, "item id");
47338
- await preflightConfirmToken(
47339
- pollItemConfirmAction(filebox, itemId, scopeOf(opts)),
47340
- opts.confirmToken
47341
- );
47342
- await withConnectorsSetup(opts, async (setup) => {
47343
- await runPollSkip({
47344
- client: setup.client,
47345
- filebox,
47346
- itemId,
47347
- deadline: {
47348
- pause: () => setup.pauseTimeout(),
47349
- resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
47350
- },
47351
- ...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
47352
- scope: scopeOf(opts),
47353
- targetArgs: targetArgsOf(opts),
47354
- asJson: opts.json === true,
47355
- signal: setup.signal,
47356
- out: process.stdout
47357
- });
47358
- });
47359
- });
47360
- addCommonFlags(skipCmd, "Output the item as JSON");
47361
- const seedCmd = addConfirmTokenFlag(
47362
- poll2.command("seed").description(
47363
- "Mark items modified at or before --until as already landed (cut-over; token, no dialog)"
47364
- ).argument("<filebox>", "filebox id or uuid").requiredOption(
47365
- "--until <timestamp>",
47366
- "date or timestamp with a zone, e.g. 2026-10-08T17:30:00Z"
47367
- ).option(
47368
- "--dry-run",
47369
- "count the already-discovered items it would mark, without changing anything",
47370
- false
47371
- ).addHelpText("after", `
47372
- ${POLL_CONFIRM_HELP}`)
47373
- ).action(
47374
- async (filebox, opts) => {
47375
- const until = parseTimestampFlag(opts.until, "--until");
47376
- const dryRun = opts.dryRun === true;
47377
- if (!dryRun) {
47378
- await preflightConfirmToken(
47379
- pollConfirmAction(filebox, "seed", scopeOf(opts), { until }),
47380
- opts.confirmToken
47381
- );
47382
- }
47383
- await withConnectorsSetup(opts, async (setup) => {
47384
- await runPollSeed({
47385
- client: setup.client,
47386
- filebox,
47387
- until,
47388
- untilArg: opts.until.trim(),
47389
- dryRun,
47390
- deadline: {
47391
- pause: () => setup.pauseTimeout(),
47392
- resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
47393
- },
47394
- ...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
47395
- scope: scopeOf(opts),
47396
- targetArgs: targetArgsOf(opts),
47397
- asJson: opts.json === true,
47398
- signal: setup.signal,
47399
- out: process.stdout
47400
- });
47401
- });
47402
- }
47403
- );
47404
- addCommonFlags(seedCmd, "Output the binding (or the dry-run count) as JSON");
47405
- }
47406
-
47407
- // src/commands/connectorsTasks.ts
47408
- import { readFileSync as readFileSync3 } from "fs";
47409
-
47410
- // src/api/scheduledTasks.ts
47411
- var BASE2 = "/scheduled-tasks";
47412
- async function call(method, path13, send, what) {
47413
- let out;
47414
- try {
47415
- out = await send();
47416
- } catch (err) {
47417
- throw toControlPlaneError(method, path13, err);
47418
- }
47419
- if (out === null) throw new Error(`${method} ${path13}: empty response from control-plane ${what}`);
47420
- return out;
47421
- }
47422
- async function createTask(client, fields, reqOptions) {
47423
- return call(
47424
- "POST",
47425
- BASE2,
47426
- () => client.postJSON(BASE2, fields, reqOptions),
47427
- "task create"
47428
- );
47429
- }
47430
- async function listTasks(client, orgId, reqOptions) {
47431
- const path13 = orgId === void 0 ? BASE2 : `${BASE2}?org_id=${orgId}`;
47432
- return call("GET", path13, () => client.getJSON(path13, reqOptions), "task list");
47433
- }
47434
- async function getTask(client, taskId, reqOptions) {
47435
- const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
47436
- return call("GET", path13, () => client.getJSON(path13, reqOptions), "task get");
47437
- }
47438
- async function updateTask(client, taskId, fields, reqOptions) {
47439
- const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
47440
- return call(
47441
- "PATCH",
47442
- path13,
47443
- () => client.patchJSON(path13, fields, reqOptions),
47444
- "task update"
47445
- );
47446
- }
47447
- async function deleteTask(client, taskId, reqOptions) {
47448
- const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
47449
- try {
47450
- await client.deleteJSON(path13, reqOptions);
47451
- } catch (err) {
47452
- throw toControlPlaneError("DELETE", path13, err);
47453
- }
47454
- }
47455
- async function runTask(client, taskId, reqOptions) {
47456
- const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/run`;
47457
- return call(
47458
- "POST",
47459
- path13,
47460
- () => client.postJSON(path13, {}, reqOptions),
47461
- "task run"
47462
- );
47463
- }
47464
- async function pauseTask(client, taskId, reqOptions) {
47465
- const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/pause`;
47466
- return call(
47467
- "POST",
47468
- path13,
47469
- () => client.postJSON(path13, {}, reqOptions),
47470
- "task pause"
47471
- );
47472
- }
47473
- async function resumeTask(client, taskId, reqOptions) {
47474
- const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/resume`;
47475
- return call(
47476
- "POST",
47477
- path13,
47478
- () => client.postJSON(path13, {}, reqOptions),
47479
- "task resume"
47480
- );
47481
- }
47482
- async function listRuns(client, taskId, limit, reqOptions) {
47483
- const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/runs?limit=${limit}`;
47484
- return call(
47485
- "GET",
47486
- path13,
47487
- () => client.getJSON(path13, reqOptions),
47488
- "task runs"
47489
- );
47490
- }
47491
-
47492
- // src/commands/connectorsTasks.ts
47493
- var DURATION_UNIT_SECONDS2 = { s: 1, m: 60, h: 3600, d: 86400 };
47494
- var MIN_EVERY_SECONDS = 60;
47495
- var MAX_EVERY_SECONDS = 2 ** 31 - 1;
47496
- function parseEvery(raw2) {
47497
- const m = /^(\d+)([smhd])?$/.exec(raw2.trim());
47498
- const seconds = m === null ? NaN : Number(m[1]) * (DURATION_UNIT_SECONDS2[m[2] ?? "s"] ?? 1);
47499
- if (m === null)
47500
- throw new Error(`invalid --every "${raw2}": expected a duration like 5m, 1h or 1d`);
47501
- if (!Number.isSafeInteger(seconds) || seconds < MIN_EVERY_SECONDS || seconds > MAX_EVERY_SECONDS) {
47502
- throw new Error(`invalid --every "${raw2}": must be at least 1m`);
47503
- }
47504
- return seconds;
47505
- }
47506
- function parseCron(raw2) {
47507
- const fields = raw2.trim().split(/\s+/);
47508
- if (fields.length !== 5 || fields.some((f) => f === "")) {
47509
- throw new Error(`invalid --cron "${raw2}": expected five fields, e.g. "15 * * * *"`);
47510
- }
47511
- return fields.join(" ");
47512
- }
47513
- var ROLE_RE = /^[a-z][a-z_]*$/;
47514
- var DATASOURCE_ID_RE = /^[0-9]{1,18}$/;
47515
- function parseDatasources(values) {
47516
- const out = {};
47517
- for (const raw2 of values) {
47518
- const eq = raw2.indexOf("=");
47519
- const role = eq < 0 ? "" : raw2.slice(0, eq).trim();
47520
- const id = eq < 0 ? "" : raw2.slice(eq + 1).trim();
47521
- if (!ROLE_RE.test(role) || !DATASOURCE_ID_RE.test(id)) {
47522
- throw new Error(
47523
- `invalid --datasource "${raw2}": expected <role>=<datasource id>, e.g. target=4242 (role: lowercase letters and _; id: digits)`
47524
- );
47525
- }
47526
- if (Object.hasOwn(out, role)) throw new Error(`--datasource role "${role}" given twice`);
47527
- out[role] = id;
47528
- }
47529
- const count = Object.keys(out).length;
47530
- if (count < 1 || count > 4) throw new Error("give between 1 and 4 --datasource roles");
47531
- return out;
47532
- }
47533
- function parseConfig(raw2, readFile9 = readUtf8) {
47534
- const text2 = raw2.startsWith("@") ? readFile9(raw2.slice(1)) : raw2;
47535
- let parsed2;
47536
- try {
47537
- parsed2 = JSON.parse(text2);
47538
- } catch (err) {
47539
- const msg = err instanceof Error ? err.message : String(err);
47540
- throw new Error(`invalid --config JSON: ${msg}`);
47541
- }
47542
- if (typeof parsed2 !== "object" || parsed2 === null || Array.isArray(parsed2)) {
47543
- throw new Error("invalid --config: expected a JSON object");
47544
- }
47545
- return parsed2;
47546
- }
47547
- function readUtf8(p) {
47548
- return readFileSync3(p, "utf-8");
47549
- }
47550
- function collect(value, previous = []) {
47551
- return [...previous, value];
47552
- }
47553
- function parseScheduleFlags(opts) {
47554
- if (opts.every !== void 0 && opts.cron !== void 0) {
47555
- throw new Error("give --every or --cron, not both");
47556
- }
47557
- const fields = {};
47558
- if (opts.artifact !== void 0) fields.artifact_name = opts.artifact;
47559
- if (opts.flavor !== void 0) fields.runtime_flavor = opts.flavor;
47560
- if (opts.datasource !== void 0) fields.datasources = parseDatasources(opts.datasource);
47561
- if (opts.primaryRole !== void 0) fields.primary_role = opts.primaryRole;
47562
- if (opts.config !== void 0) fields.config = parseConfig(opts.config);
47563
- if (opts.every !== void 0) fields.every_seconds = parseEvery(opts.every);
47564
- if (opts.cron !== void 0) fields.cron = parseCron(opts.cron);
47565
- if (opts.tz !== void 0) fields.timezone = opts.tz;
47566
- if (fields.primary_role !== void 0 && fields.datasources !== void 0) {
47567
- if (!Object.hasOwn(fields.datasources, fields.primary_role)) {
47568
- throw new Error(
47569
- `--primary-role "${fields.primary_role}" is not one of the --datasource roles`
47570
- );
47571
- }
47572
- }
47573
- return fields;
47574
- }
47575
- function buildCreateFields(name, orgId, opts) {
47576
- const missing = [
47577
- ["--artifact", opts.artifact],
47578
- ["--flavor", opts.flavor],
47579
- ["--datasource", opts.datasource],
47580
- ["--config", opts.config]
47581
- ].filter(([, v]) => v === void 0).map(([flag]) => flag);
47582
- if (opts.every === void 0 && opts.cron === void 0) missing.push("--every or --cron");
47583
- if (missing.length > 0) throw new Error(`missing ${missing.join(", ")}`);
47584
- const fields = parseScheduleFlags(opts);
47585
- return {
47586
- org_id: orgId,
47587
- name,
47588
- artifact_name: fields.artifact_name,
47589
- runtime_flavor: fields.runtime_flavor,
47590
- datasources: fields.datasources,
47591
- config: fields.config,
47592
- ...fields.primary_role !== void 0 ? { primary_role: fields.primary_role } : {},
47593
- ...fields.every_seconds !== void 0 ? { every_seconds: fields.every_seconds } : {},
47594
- ...fields.cron !== void 0 ? { cron: fields.cron } : {},
47595
- ...fields.timezone !== void 0 ? { timezone: fields.timezone } : {},
47596
- ...opts.disabled === true ? { enabled: false } : {}
47597
- };
47598
- }
47599
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
47600
- async function resolveTaskId(client, task, signal) {
47601
- if (UUID_RE.test(task)) return task;
47602
- const tasks = await listTasks(client, void 0, { signal });
47603
- const match = tasks.find((t) => t.name === task);
47604
- if (match === void 0) {
47605
- throw new Error(
47606
- `no scheduled task named "${task}" in this org (see: dr connectors tasks list)`
47607
- );
47608
- }
47609
- return match.id;
47610
- }
47611
- function explainTaskError(err) {
47612
- const forbidden = err instanceof ControlPlaneError && err.status === 403 || err instanceof Error && /\bHTTP 403\b/.test(err.message);
47613
- if (forbidden) {
47614
- const message = err instanceof Error ? err.message : String(err);
47615
- const body = err instanceof ControlPlaneError ? err.body : (/HTTP 403[^:]*(?:\([^)]*\))?:\s*([\s\S]*)$/.exec(message)?.[1] ?? "").trim();
47616
- return withCause(
47617
- new ControlPlaneError(
47618
- 403,
47619
- body,
47620
- `${message}
47621
- Scheduled tasks need a support user or an org admin: log in as one (--account) \u2014 e.g. dr login-as the customer's support user.`
47622
- ),
47623
- err
47624
- );
47625
- }
47626
- if (!(err instanceof ControlPlaneError)) return err;
47627
- const detail = detailOf(err.body);
47628
- if (err.status === 404) {
47629
- const message = `scheduled task not found (${detail || err.message})`;
47630
- return withCause(new ControlPlaneError(404, err.body, message), err);
47631
- }
47632
- if (err.status === 409 || err.status === 422) {
47633
- return withCause(new ControlPlaneError(err.status, err.body, detail || err.message), err);
47634
- }
47635
- return err;
47636
- }
47637
- function withCause(error, cause) {
47638
- Object.defineProperty(error, "cause", { value: cause, enumerable: false, configurable: true });
47639
- return error;
47640
- }
47641
- function detailOf(body) {
47642
- try {
47643
- const parsed2 = JSON.parse(body);
47644
- const d = parsed2.detail;
47645
- if (typeof d === "string") return d;
47646
- if (Array.isArray(d)) {
47647
- return d.map(
47648
- (e) => `${Array.isArray(e.loc) ? e.loc.slice(1).join(".") : ""}: ${e.msg ?? ""}`.trim()
47649
- ).join("; ");
47650
- }
47651
- } catch {
47652
- }
47653
- return body;
47654
- }
47655
- function scheduleText(t) {
47656
- if (t.schedule_kind === "cron") return `cron "${t.cron ?? ""}" ${t.timezone}`;
47657
- const s = t.every_seconds ?? 0;
47658
- const unit = s % 86400 === 0 ? `${s / 86400}d` : s % 3600 === 0 ? `${s / 3600}h` : s % 60 === 0 ? `${s / 60}m` : `${s}s`;
47659
- return `every ${unit}`;
47660
- }
47661
- function stateText(t) {
47662
- if (!t.enabled) return "disabled";
47663
- if (t.paused_reason !== null) return `paused (${t.paused_reason})`;
47664
- return t.running ? "running" : "enabled";
47665
- }
47666
- function writeTask(out, t) {
47667
- const roles = Object.entries(t.datasources).map(([role, id]) => `${role}=${id}${role === t.primary_role ? " (primary)" : ""}`).join(", ");
47668
- out.write(`Task: ${t.name} (${t.id})
47669
- `);
47670
- out.write(` Org: ${t.org_id}
47671
- `);
47672
- out.write(` State: ${stateText(t)}
47673
- `);
47674
- out.write(` Artifact: ${t.artifact_name} [${t.runtime_flavor}]
47675
- `);
47676
- out.write(` Schedule: ${scheduleText(t)}
47677
- `);
47678
- out.write(` Next run: ${t.next_run_at}
47679
- `);
47680
- out.write(` Last run: ${t.last_run_at ?? "-"} ${t.last_run_status ?? ""}
47681
- `);
47682
- if (t.consecutive_failures > 0) out.write(` Failures: ${t.consecutive_failures} in a row
47683
- `);
47684
- out.write(` Datasources: ${roles}
47685
- `);
47686
- out.write(` Config: ${truncate3(JSON.stringify(t.config), 200)}
47687
- `);
47688
- }
47689
- function writeTaskList(out, tasks) {
47690
- if (tasks.length === 0) {
47691
- out.write("No scheduled tasks.\n");
47692
- return;
47693
- }
47694
- for (const t of tasks) {
47695
- out.write(
47696
- `${t.name.padEnd(28)} ${stateText(t).padEnd(20)} ${scheduleText(t).padEnd(26)} next ${t.next_run_at} last ${t.last_run_status ?? "-"} ${t.id}
47697
- `
47698
- );
47699
- }
47700
- }
47701
- function writeRuns(out, runs) {
47702
- if (runs.length === 0) {
47703
- out.write("No runs yet.\n");
47704
- return;
47705
- }
47706
- for (const r of runs) {
47707
- const tail = r.error !== null ? ` error: ${truncate3(r.error, 160)}` : r.summary !== null ? ` ${truncate3(JSON.stringify(r.summary), 160)}` : "";
47708
- out.write(
47709
- `${r.started_at} ${r.status.padEnd(15)} ${r.trigger.padEnd(8)} attempt ${r.attempt} ${r.artifact_version ?? "-"}${tail}
47710
- `
47711
- );
47712
- }
47784
+ serverKey,
47785
+ account: normaliseAccount(opts.account),
47786
+ controlPlane: resolveConnectorsUrl(serverKey, opts.connectorsUrl)
47787
+ };
47713
47788
  }
47714
- function withFlags(cmd) {
47715
- cmd.option("--json", "Output raw JSON instead of a summary", false);
47789
+ function targetArgsOf(opts) {
47790
+ const url = (opts.connectorsUrl ?? "").trim();
47791
+ return ` --server ${resolveServer(opts.server)}${url === "" ? "" : ` --connectors-url ${url}`}`;
47792
+ }
47793
+ function addCommonFlags(cmd, jsonHelp) {
47794
+ cmd.option("--json", jsonHelp, false);
47716
47795
  addServerFlag(cmd);
47717
47796
  addAccountFlag(cmd);
47718
47797
  addConnectorsUrlFlag(cmd);
47719
- return cmd;
47720
47798
  }
47721
- function addScheduleFlags(cmd) {
47722
- return cmd.option("--artifact <name>", "published artifact name, e.g. sftp_export__daily_sales").option("--flavor <flavor>", "runner flavor the artifact was published for, e.g. sftp").option("--datasource <role=id>", "monolith datasource for a role (repeat per role)", collect).option(
47723
- "--primary-role <role>",
47724
- "the role passed as the primary datasource (default: the first)"
47725
- ).option("--config <json|@file>", "task config: a JSON object, or @path to a JSON file").option("--every <duration>", "run on an interval, e.g. 5m, 1h (at least 1m)").option("--cron <expr>", 'run on a five-field cron line, e.g. "15 * * * *"').option("--tz <iana>", "timezone the cron line is read in (default UTC)");
47799
+ function addConfirmTokenFlag(cmd) {
47800
+ return cmd.option("--confirm-token <token>", "Token from a prior plan run \u2014 required to proceed");
47726
47801
  }
47727
- async function onTask(task, opts, act, print) {
47728
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47729
- try {
47730
- const taskId = await resolveTaskId(client, task, signal);
47731
- const result = await act(client, taskId, signal);
47732
- if (opts.json === true) writeJSONIndented(process.stdout, result);
47733
- else print(process.stdout, result);
47734
- } catch (err) {
47735
- throw explainTaskError(err);
47736
- }
47802
+ function addSettingsFlags(cmd) {
47803
+ return cmd.option("--interval <duration>", "how often to poll, 5m to 7d (default: per source)").option("--since <date>", "never pick up items modified before this date or timestamp").option("--lookback <days>", "window every poll lists, 1d to 3650d (default: per source)").option("--settle <duration>", "how long an item must stay unchanged before landing, 0s to 1d").option(
47804
+ "--same-name <policy>",
47805
+ "a changed file under the same name: new-version (lands again) or ignore"
47806
+ );
47807
+ }
47808
+ function ttyChoicePrompt(message, choices, defaultChoice, io = { input: process.stdin, output: process.stderr }) {
47809
+ return new Promise((resolvePrompt) => {
47810
+ const rl = readline2.createInterface({
47811
+ input: io.input,
47812
+ output: io.output,
47813
+ ...io.terminal !== void 0 ? { terminal: io.terminal } : {}
47814
+ });
47815
+ let settled = false;
47816
+ const settle = (value) => {
47817
+ if (settled) return;
47818
+ settled = true;
47819
+ rl.close();
47820
+ resolvePrompt(value);
47821
+ };
47822
+ rl.on("SIGINT", () => settle(null));
47823
+ rl.on("close", () => settle(null));
47824
+ io.output.write(`${message}
47825
+ `);
47826
+ const question = defaultChoice === void 0 ? `[${choices.join("/")}]: ` : `[${choices.join("/")}] (Enter = ${defaultChoice}): `;
47827
+ const askOnce = () => {
47828
+ rl.question(question, (answer) => {
47829
+ const picked = answer.trim().toLowerCase();
47830
+ if (picked === "" && defaultChoice !== void 0) {
47831
+ settle(defaultChoice);
47832
+ } else if (choices.includes(picked)) {
47833
+ settle(picked);
47834
+ } else {
47835
+ io.output.write(`Answer one of: ${choices.join(", ")}
47836
+ `);
47837
+ askOnce();
47838
+ }
47839
+ });
47840
+ };
47841
+ askOnce();
47737
47842
  });
47738
47843
  }
47739
- function registerConnectorsTasks(connectors) {
47740
- const tasks = connectors.command("tasks").description("DRIP scheduled tasks: run an artifact on a schedule (support user or org admin)");
47741
- withFlags(
47742
- addScheduleFlags(
47743
- tasks.command("create").description("Create a scheduled task").argument("<name>", "task name, unique in the org (a-z, 0-9, _ and -)").option("--org-id <id>", "org id (defaults to the JWT org)").option("--disabled", "create it disabled", false)
47744
- )
47844
+ var POLL_CONFIRM_HELP = [
47845
+ "Confirmation:",
47846
+ " seed, skip and set --on-policy-change seed print a plan and a --confirm-token;",
47847
+ " re-run the printed command, token included, to apply it.",
47848
+ " No dialog opens on production, so an agent or script can run a cut-over unattended:",
47849
+ " dr connectors poll seed <filebox> --until <cut-over> --server <prod>",
47850
+ " dr connectors poll seed <filebox> --until <cut-over> --server <prod> --confirm-token <token>",
47851
+ " (seed --dry-run counts the already-discovered items a seed would mark.)",
47852
+ " A write with any answer that lands files (set --widen land, set --on-policy-change land,",
47853
+ " resume --source land) also asks a person to type a word in a dialog on production,",
47854
+ " even when the same set also seeds (--on-policy-change seed --widen land)."
47855
+ ].join("\n");
47856
+ function registerConnectorsPoll(connectors) {
47857
+ const poll2 = connectors.command("poll").description("Polled file ingestion: the control-plane lands each new file as one version").addHelpText("after", `
47858
+ ${POLL_CONFIRM_HELP}`);
47859
+ const enableCmd = addSettingsFlags(
47860
+ poll2.command("enable").description("Poll a filebox's binding (dated with the filebox's DATE tag)").argument("<filebox>", "filebox id or uuid")
47861
+ ).action(async (filebox, opts) => {
47862
+ const settings = parseSettings(opts);
47863
+ await withConnectorsSetup(opts, async ({ client, signal }) => {
47864
+ await withMonolith(opts, async (monolith) => {
47865
+ await runPollEnable({
47866
+ client,
47867
+ monolith,
47868
+ filebox,
47869
+ settings,
47870
+ asJson: opts.json === true,
47871
+ signal,
47872
+ out: process.stdout
47873
+ });
47874
+ });
47875
+ });
47876
+ });
47877
+ addCommonFlags(enableCmd, "Output the stored binding as JSON");
47878
+ const setCmd = addConfirmTokenFlag(
47879
+ addSettingsFlags(
47880
+ poll2.command("set").description("Change polling settings (only the given flags change)").argument("<filebox>", "filebox id or uuid")
47881
+ ).option(
47882
+ "--widen <seed|land>",
47883
+ "a wider window: seed (mark exposed items landed) or land them"
47884
+ ).option("--on-policy-change <rekey|seed|land>", "a --same-name switch: rekey, seed or land").addHelpText("after", `
47885
+ ${POLL_CONFIRM_HELP}`)
47745
47886
  ).action(
47746
- async (name, opts) => {
47747
- const explicitOrg = opts.orgId !== void 0 ? parsePositiveIntArg2(opts.orgId, "--org-id") : void 0;
47748
- const parsed2 = buildCreateFields(name, explicitOrg ?? 0, opts);
47749
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47750
- const fields = { ...parsed2, org_id: explicitOrg ?? client.getCurrentOrgId() };
47751
- try {
47752
- const t = await createTask(client, fields, { signal });
47753
- if (opts.json === true) writeJSONIndented(process.stdout, t);
47754
- else writeTask(process.stdout, t);
47755
- } catch (err) {
47756
- throw explainTaskError(err);
47757
- }
47887
+ async (filebox, opts) => {
47888
+ const changes = parseSettings(opts);
47889
+ const widen = opts.widen === void 0 ? void 0 : parseChoice(opts.widen, "--widen", WIDEN_CHOICES);
47890
+ const onPolicyChange = opts.onPolicyChange === void 0 ? void 0 : parseChoice(opts.onPolicyChange, "--on-policy-change", POLICY_CHOICES);
47891
+ await preflightConfirmToken(
47892
+ pollConfirmAction(filebox, pollSetConfirmVerb(widen, onPolicyChange), scopeOf(opts), {
47893
+ ...changes,
47894
+ ...widen !== void 0 ? { widen } : {},
47895
+ ...onPolicyChange !== void 0 ? { on_policy_change: onPolicyChange } : {}
47896
+ }),
47897
+ opts.confirmToken
47898
+ );
47899
+ await withConnectorsSetup(opts, async (setup) => {
47900
+ const deadline = {
47901
+ pause: () => setup.pauseTimeout(),
47902
+ resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
47903
+ };
47904
+ await runPollSet({
47905
+ client: setup.client,
47906
+ filebox,
47907
+ changes,
47908
+ settingsArgs: settingsArgs(opts),
47909
+ ...widen !== void 0 ? { widen } : {},
47910
+ ...onPolicyChange !== void 0 ? { onPolicyChange } : {},
47911
+ interactive: process.stdin.isTTY === true && process.stderr.isTTY === true,
47912
+ prompt: async (message, choices, defaultChoice) => {
47913
+ deadline.pause();
47914
+ try {
47915
+ return await ttyChoicePrompt(message, choices, defaultChoice);
47916
+ } finally {
47917
+ deadline.resume();
47918
+ }
47919
+ },
47920
+ deadline,
47921
+ ...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
47922
+ scope: scopeOf(opts),
47923
+ targetArgs: targetArgsOf(opts),
47924
+ asJson: opts.json === true,
47925
+ signal: setup.signal,
47926
+ out: process.stdout
47927
+ });
47758
47928
  });
47759
47929
  }
47760
47930
  );
47761
- withFlags(
47762
- tasks.command("list").description("List the org's scheduled tasks").option("--org-id <id>", "org id (defaults to the JWT org)")
47763
- ).action(async (opts) => {
47931
+ addCommonFlags(setCmd, "Output the stored binding as JSON");
47932
+ const disableCmd = poll2.command("disable").description("Stop polling (an item already in flight still finishes)").argument("<filebox>", "filebox id or uuid").action(async (filebox, opts) => {
47764
47933
  await withConnectorsSetup(opts, async ({ client, signal }) => {
47765
- const orgId = opts.orgId !== void 0 ? parsePositiveIntArg2(opts.orgId, "--org-id") : void 0;
47766
- try {
47767
- const list28 = await listTasks(client, orgId, { signal });
47768
- if (opts.json === true) writeJSONIndented(process.stdout, list28);
47769
- else writeTaskList(process.stdout, list28);
47770
- } catch (err) {
47771
- throw explainTaskError(err);
47772
- }
47934
+ await runPollDisable({
47935
+ client,
47936
+ filebox,
47937
+ asJson: opts.json === true,
47938
+ signal,
47939
+ out: process.stdout
47940
+ });
47773
47941
  });
47774
47942
  });
47775
- withFlags(
47776
- tasks.command("get").description("Show a scheduled task").argument("<task>", "task id or name")
47777
- ).action(
47778
- async (task, opts) => onTask(
47779
- task,
47780
- opts,
47781
- (c, id, signal) => getTask(c, id, { signal }),
47782
- (o, r) => writeTask(o, r)
47783
- )
47784
- );
47785
- withFlags(
47786
- addScheduleFlags(
47787
- tasks.command("update").description("Change a scheduled task (only the flags given change)").argument("<task>", "task id or name").option("--enable", "enable the task").option("--disable", "disable the task")
47943
+ addCommonFlags(disableCmd, "Output the stored binding as JSON");
47944
+ const getCmd = poll2.command("get").description("Show polling settings, last poll, pending seed / rekey and item counts").argument("<filebox>", "filebox id or uuid").action(async (filebox, opts) => {
47945
+ await withConnectorsSetup(opts, async ({ client, signal }) => {
47946
+ await runPollGet({
47947
+ client,
47948
+ filebox,
47949
+ asJson: opts.json === true,
47950
+ signal,
47951
+ out: process.stdout
47952
+ });
47953
+ });
47954
+ });
47955
+ addCommonFlags(getCmd, "Output the raw binding as JSON");
47956
+ const runCmd = poll2.command("run").description("Poll now").argument("<filebox>", "filebox id or uuid").action(async (filebox, opts) => {
47957
+ await withConnectorsSetup(opts, async ({ client, signal }) => {
47958
+ await runPollRun({
47959
+ client,
47960
+ filebox,
47961
+ asJson: opts.json === true,
47962
+ signal,
47963
+ out: process.stdout
47964
+ });
47965
+ });
47966
+ });
47967
+ addCommonFlags(runCmd, "Output the binding as JSON");
47968
+ const resumeCmd = addConfirmTokenFlag(
47969
+ poll2.command("resume").description("Resume a paused binding (after fixing credentials, or a source change)").argument("<filebox>", "filebox id or uuid").option(
47970
+ "--source <seed|land>",
47971
+ "after a source change: seed (mark present items landed) or land them"
47788
47972
  )
47789
- ).action(
47790
- async (task, opts) => {
47791
- if (opts.enable === true && opts.disable === true)
47792
- throw new Error("give --enable or --disable, not both");
47793
- const fields = parseScheduleFlags(opts);
47794
- if (opts.enable === true) fields.enabled = true;
47795
- if (opts.disable === true) fields.enabled = false;
47796
- if (Object.keys(fields).length === 0)
47797
- throw new Error("nothing to change: give at least one flag");
47798
- await onTask(
47799
- task,
47800
- opts,
47801
- (c, id, signal) => updateTask(c, id, fields, { signal }),
47802
- (o, r) => writeTask(o, r)
47973
+ ).action(async (filebox, opts) => {
47974
+ const source = opts.source === void 0 ? void 0 : parseChoice(opts.source, "--source", WIDEN_CHOICES);
47975
+ if (source === "land") {
47976
+ await preflightConfirmToken(
47977
+ pollConfirmAction(filebox, "resume-land", scopeOf(opts)),
47978
+ opts.confirmToken
47803
47979
  );
47804
47980
  }
47805
- );
47806
- withFlags(
47807
- tasks.command("delete").description("Delete a scheduled task and its run history").argument("<task>", "task id or name")
47808
- ).action(
47809
- async (task, opts) => onTask(
47810
- task,
47811
- opts,
47812
- async (c, id, signal) => {
47813
- await deleteTask(c, id, { signal });
47814
- return { deleted: id };
47815
- },
47816
- (o, r) => o.write(`Deleted scheduled task ${r.deleted}
47817
- `)
47818
- )
47819
- );
47820
- withFlags(
47821
- tasks.command("run").description("Run a task now (does not move its schedule)").argument("<task>", "task id or name")
47822
- ).action(
47823
- async (task, opts) => onTask(
47824
- task,
47825
- opts,
47826
- (c, id, signal) => runTask(c, id, { signal }),
47827
- (o, r) => {
47828
- const run = r;
47829
- o.write(`Queued run ${run.id} (${run.status}); see: dr connectors tasks runs ${task}
47830
- `);
47981
+ await withConnectorsSetup(opts, async (setup) => {
47982
+ await runPollResume({
47983
+ client: setup.client,
47984
+ filebox,
47985
+ ...source !== void 0 ? { source } : {},
47986
+ deadline: {
47987
+ pause: () => setup.pauseTimeout(),
47988
+ resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
47989
+ },
47990
+ ...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
47991
+ scope: scopeOf(opts),
47992
+ targetArgs: targetArgsOf(opts),
47993
+ asJson: opts.json === true,
47994
+ signal: setup.signal,
47995
+ out: process.stdout
47996
+ });
47997
+ });
47998
+ });
47999
+ addCommonFlags(resumeCmd, "Output the binding as JSON");
48000
+ const itemsCmd = poll2.command("items").description("List the item ledger: label, date, state, attempts, version, error").argument("<filebox>", "filebox id or uuid").option("--state <state>", `only items in this state (${POLL_ITEM_STATES.join("|")})`).option("--limit <n>", "page size (default and maximum: the control-plane page size, 100)").option("--after <id>", "start after this item id (the next-page cursor)").action(
48001
+ async (filebox, opts) => {
48002
+ const state = opts.state === void 0 ? void 0 : POLL_ITEM_STATES.find((s) => s === opts.state);
48003
+ if (opts.state !== void 0 && state === void 0) {
48004
+ throw new Error(
48005
+ `invalid --state "${opts.state}": expected one of ${POLL_ITEM_STATES.join(", ")}`
48006
+ );
47831
48007
  }
47832
- )
47833
- );
47834
- withFlags(
47835
- tasks.command("pause").description("Pause a scheduled task").argument("<task>", "task id or name")
47836
- ).action(
47837
- async (task, opts) => onTask(
47838
- task,
47839
- opts,
47840
- (c, id, signal) => pauseTask(c, id, { signal }),
47841
- (o, r) => writeTask(o, r)
47842
- )
47843
- );
47844
- withFlags(
47845
- tasks.command("resume").description("Resume a paused task (clears its failure count, schedules the next run)").argument("<task>", "task id or name")
47846
- ).action(
47847
- async (task, opts) => onTask(
47848
- task,
47849
- opts,
47850
- (c, id, signal) => resumeTask(c, id, { signal }),
47851
- (o, r) => writeTask(o, r)
47852
- )
48008
+ const limit = opts.limit === void 0 ? void 0 : parsePositiveIntArg2(opts.limit, "--limit");
48009
+ const after = opts.after === void 0 ? void 0 : parsePositiveIntArg2(opts.after, "--after");
48010
+ await withConnectorsSetup(opts, async ({ client, signal }) => {
48011
+ await runPollItems({
48012
+ client,
48013
+ filebox,
48014
+ ...state !== void 0 ? { state } : {},
48015
+ ...limit !== void 0 ? { limit } : {},
48016
+ ...after !== void 0 ? { after } : {},
48017
+ asJson: opts.json === true,
48018
+ signal,
48019
+ out: process.stdout
48020
+ });
48021
+ });
48022
+ }
47853
48023
  );
47854
- withFlags(
47855
- tasks.command("runs").description("A task's run history, newest first").argument("<task>", "task id or name").option("--limit <n>", "how many runs (1-200)", "20")
47856
- ).action(async (task, opts) => {
47857
- const limit = parsePositiveIntArg2(opts.limit, "--limit");
47858
- if (limit > 200) throw new Error("invalid --limit: at most 200");
47859
- await onTask(
47860
- task,
47861
- opts,
47862
- (c, id, signal) => listRuns(c, id, limit, { signal }),
47863
- (o, r) => writeRuns(o, r)
48024
+ addCommonFlags(itemsCmd, "Output the raw page as JSON");
48025
+ const retryCmd = poll2.command("retry").description("Queue a failed, waiting or skipped item again").argument("<filebox>", "filebox id or uuid").argument("<item>", "item id (from poll items)").action(async (filebox, item, opts) => {
48026
+ const itemId = parsePositiveIntArg2(item, "item id");
48027
+ await withConnectorsSetup(opts, async ({ client, signal }) => {
48028
+ await runPollRetry({
48029
+ client,
48030
+ filebox,
48031
+ itemId,
48032
+ asJson: opts.json === true,
48033
+ signal,
48034
+ out: process.stdout
48035
+ });
48036
+ });
48037
+ });
48038
+ addCommonFlags(retryCmd, "Output the item as JSON");
48039
+ const skipCmd = addConfirmTokenFlag(
48040
+ poll2.command("skip").description("Skip an item so it is never landed (two-step confirmation)").argument("<filebox>", "filebox id or uuid").argument("<item>", "item id (from poll items)")
48041
+ ).action(async (filebox, item, opts) => {
48042
+ const itemId = parsePositiveIntArg2(item, "item id");
48043
+ await preflightConfirmToken(
48044
+ pollItemConfirmAction(filebox, itemId, scopeOf(opts)),
48045
+ opts.confirmToken
47864
48046
  );
48047
+ await withConnectorsSetup(opts, async (setup) => {
48048
+ await runPollSkip({
48049
+ client: setup.client,
48050
+ filebox,
48051
+ itemId,
48052
+ deadline: {
48053
+ pause: () => setup.pauseTimeout(),
48054
+ resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
48055
+ },
48056
+ ...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
48057
+ scope: scopeOf(opts),
48058
+ targetArgs: targetArgsOf(opts),
48059
+ asJson: opts.json === true,
48060
+ signal: setup.signal,
48061
+ out: process.stdout
48062
+ });
48063
+ });
47865
48064
  });
48065
+ addCommonFlags(skipCmd, "Output the item as JSON");
48066
+ const seedCmd = addConfirmTokenFlag(
48067
+ poll2.command("seed").description(
48068
+ "Mark items modified at or before --until as already landed (cut-over; token, no dialog)"
48069
+ ).argument("<filebox>", "filebox id or uuid").requiredOption(
48070
+ "--until <timestamp>",
48071
+ "date or timestamp with a zone, e.g. 2026-10-08T17:30:00Z"
48072
+ ).option(
48073
+ "--dry-run",
48074
+ "count the already-discovered items it would mark, without changing anything",
48075
+ false
48076
+ ).addHelpText("after", `
48077
+ ${POLL_CONFIRM_HELP}`)
48078
+ ).action(
48079
+ async (filebox, opts) => {
48080
+ const until = parseTimestampFlag(opts.until, "--until");
48081
+ const dryRun = opts.dryRun === true;
48082
+ if (!dryRun) {
48083
+ await preflightConfirmToken(
48084
+ pollConfirmAction(filebox, "seed", scopeOf(opts), { until }),
48085
+ opts.confirmToken
48086
+ );
48087
+ }
48088
+ await withConnectorsSetup(opts, async (setup) => {
48089
+ await runPollSeed({
48090
+ client: setup.client,
48091
+ filebox,
48092
+ until,
48093
+ untilArg: opts.until.trim(),
48094
+ dryRun,
48095
+ deadline: {
48096
+ pause: () => setup.pauseTimeout(),
48097
+ resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
48098
+ },
48099
+ ...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
48100
+ scope: scopeOf(opts),
48101
+ targetArgs: targetArgsOf(opts),
48102
+ asJson: opts.json === true,
48103
+ signal: setup.signal,
48104
+ out: process.stdout
48105
+ });
48106
+ });
48107
+ }
48108
+ );
48109
+ addCommonFlags(seedCmd, "Output the binding (or the dry-run count) as JSON");
47866
48110
  }
47867
48111
 
47868
48112
  // src/commands/connectors.ts
@@ -48523,6 +48767,7 @@ ${TLS_VPN_HELP}`);
48523
48767
  registerDevFetch(connectors);
48524
48768
  registerConnectorsPoll(connectors);
48525
48769
  registerConnectorsTasks(connectors);
48770
+ registerConnectorsAws(connectors);
48526
48771
  }
48527
48772
 
48528
48773
  // src/types/dashboards.ts
@@ -53994,13 +54239,13 @@ async function runReport(cfg) {
53994
54239
  function collectFile(value, previous) {
53995
54240
  return previous.concat([value]);
53996
54241
  }
53997
- async function withOnboardingClient(opts, run, setupOptions = {}) {
54242
+ async function withOnboardingClient(opts, run2, setupOptions = {}) {
53998
54243
  const { serverKey, host } = resolveOnboardingTarget(opts.server);
53999
54244
  const callOptions = {};
54000
54245
  if (setupOptions.timeoutMs !== void 0) callOptions.timeoutMs = setupOptions.timeoutMs;
54001
54246
  const setup = await setupCall(serverKey, opts.account, callOptions);
54002
54247
  try {
54003
- await run(setup.client, host, setup.signal);
54248
+ await run2(setup.client, host, setup.signal);
54004
54249
  } finally {
54005
54250
  setup.cleanup();
54006
54251
  }
@@ -69431,8 +69676,8 @@ async function runExtractFromFiles(cfg) {
69431
69676
  const p = await preflightFile(f);
69432
69677
  parts.push({ blob: p.blob, filename: p.filename, sourcePath: f });
69433
69678
  }
69434
- const run = cfg.useAsync === false ? await extractSync(cfg, parts) : await extractAsync(cfg, parts);
69435
- const { results: allResults, sourceFiles, outcomes, abortedMidRun } = run;
69679
+ const run2 = cfg.useAsync === false ? await extractSync(cfg, parts) : await extractAsync(cfg, parts);
69680
+ const { results: allResults, sourceFiles, outcomes, abortedMidRun } = run2;
69436
69681
  await emitExtractionDraft(cfg, allResults, sourceFiles, outcomes, abortedMidRun);
69437
69682
  }
69438
69683
  async function extractAsync(cfg, parts) {
@@ -71550,8 +71795,8 @@ function querySuffix(domain) {
71550
71795
  if (domain === void 0 || domain === "") return "";
71551
71796
  return `?domain=${encodeURIComponent(domain)}`;
71552
71797
  }
71553
- function required(out, call2) {
71554
- if (out === null) throw new Error(`${call2}: empty response body`);
71798
+ function required(out, call3) {
71799
+ if (out === null) throw new Error(`${call3}: empty response body`);
71555
71800
  return out;
71556
71801
  }
71557
71802
 
@@ -73428,6 +73673,175 @@ function registerPalettesCommand(root) {
73428
73673
  addAccountFlag(createCmd);
73429
73674
  }
73430
73675
 
73676
+ // src/api/businessCalendars.ts
73677
+ function buildAuditQs(q) {
73678
+ if (q === void 0) return "";
73679
+ const sp = new URLSearchParams();
73680
+ if (q.since !== void 0) sp.set("since", q.since);
73681
+ if (q.until !== void 0) sp.set("until", q.until);
73682
+ if (q.action !== void 0) sp.set("action", q.action);
73683
+ if (q.actor_user_id !== void 0) sp.set("actor_user_id", String(q.actor_user_id));
73684
+ if (q.page !== void 0) sp.set("page", String(q.page));
73685
+ if (q.page_size !== void 0) sp.set("page_size", String(q.page_size));
73686
+ const s = sp.toString();
73687
+ return s === "" ? "" : `?${s}`;
73688
+ }
73689
+ async function listAudit(client, calendarId, query, options) {
73690
+ if (calendarId === "") {
73691
+ throw new Error("calendarId must be a non-empty string");
73692
+ }
73693
+ const path13 = `/api/business-calendar/v1/business-calendars/${encodeURIComponent(
73694
+ calendarId
73695
+ )}/audit${buildAuditQs(query)}`;
73696
+ const out = await client.getJSON(path13, options);
73697
+ if (out === null) {
73698
+ return { count: 0, results: [] };
73699
+ }
73700
+ return out;
73701
+ }
73702
+
73703
+ // src/commands/calendars.ts
73704
+ var KNOWN_ACTIONS = /* @__PURE__ */ new Set([
73705
+ "create",
73706
+ "edit",
73707
+ "archive",
73708
+ "restore",
73709
+ "add_exception",
73710
+ "remove_exception"
73711
+ ]);
73712
+ function parseAuditAction(v) {
73713
+ if (!KNOWN_ACTIONS.has(v)) {
73714
+ throw new Error(`invalid --action "${v}" (expected one of: ${[...KNOWN_ACTIONS].join(", ")})`);
73715
+ }
73716
+ return v;
73717
+ }
73718
+ function parsePositiveInt4(flag, v) {
73719
+ const n = Number(v);
73720
+ if (!Number.isInteger(n) || String(n) !== v || n <= 0) {
73721
+ throw new Error(`invalid ${flag} "${v}" (expected: positive integer)`);
73722
+ }
73723
+ return n;
73724
+ }
73725
+ async function runCalendarsAuditList(cfg) {
73726
+ if (cfg.calendarId === "") {
73727
+ throw new Error("<calendar_id> is required");
73728
+ }
73729
+ const resp = await listAudit(
73730
+ cfg.client,
73731
+ cfg.calendarId,
73732
+ {
73733
+ since: cfg.since,
73734
+ until: cfg.until,
73735
+ action: cfg.action,
73736
+ actor_user_id: cfg.actorUserId,
73737
+ page: cfg.page,
73738
+ page_size: cfg.limit
73739
+ },
73740
+ { signal: cfg.signal }
73741
+ );
73742
+ if (cfg.asJson) {
73743
+ writeJSONIndented(cfg.out, resp);
73744
+ return;
73745
+ }
73746
+ renderAuditTable(cfg.out, resp);
73747
+ }
73748
+ function writeTable44(out, rows) {
73749
+ if (rows.length === 0) return;
73750
+ const widths = [];
73751
+ for (const r of rows) {
73752
+ for (let i = 0; i < r.cells.length; i++) {
73753
+ const cell = r.cells[i] ?? "";
73754
+ if ((widths[i] ?? 0) < cell.length) widths[i] = cell.length;
73755
+ }
73756
+ }
73757
+ for (const r of rows) {
73758
+ const parts = [];
73759
+ for (let i = 0; i < r.cells.length; i++) {
73760
+ const cell = r.cells[i] ?? "";
73761
+ if (i === r.cells.length - 1) {
73762
+ parts.push(cell);
73763
+ } else {
73764
+ parts.push(cell.padEnd((widths[i] ?? 0) + 2, " "));
73765
+ }
73766
+ }
73767
+ out.write(parts.join("") + "\n");
73768
+ }
73769
+ }
73770
+ function renderActor(row) {
73771
+ const email = row.actor.email ?? "";
73772
+ return email === "" ? `#${row.actor.id}` : `${email} (#${row.actor.id})`;
73773
+ }
73774
+ function renderFieldSummary(row) {
73775
+ const n = row.field_changes.length;
73776
+ if (n === 0) return "(none)";
73777
+ if (n === 1) return row.field_changes[0]?.field ?? "(1 field)";
73778
+ return `${n} fields`;
73779
+ }
73780
+ function renderAuditTable(out, resp) {
73781
+ if (resp.results.length === 0) {
73782
+ out.write(`(no audit rows; count=${resp.count})
73783
+ `);
73784
+ return;
73785
+ }
73786
+ const rows = [{ cells: ["ID", "TIMESTAMP", "ACTION", "VERSION", "ACTOR", "CHANGES"] }];
73787
+ for (const r of resp.results) {
73788
+ rows.push({
73789
+ cells: [
73790
+ String(r.id),
73791
+ r.timestamp,
73792
+ r.action,
73793
+ String(r.resulting_version),
73794
+ renderActor(r),
73795
+ renderFieldSummary(r)
73796
+ ]
73797
+ });
73798
+ }
73799
+ writeTable44(out, rows);
73800
+ if (resp.count > resp.results.length) {
73801
+ out.write(`
73802
+ (showing ${resp.results.length} of ${resp.count}; use --limit / --page)
73803
+ `);
73804
+ }
73805
+ }
73806
+ function registerCalendarsCommand(root) {
73807
+ const cmd = root.command("calendars").description("Business-calendar (dr-business-calendar) operations");
73808
+ const audit = cmd.command("audit").description("Calendar-change audit log (FR-L13)");
73809
+ const listCmd = audit.command("list").description(
73810
+ "List calendar-change audit rows for one calendar (GET /v1/business-calendars/{id}/audit; isAdminOrSuperAdmin)"
73811
+ ).argument("<calendar_id>", "Calendar id (UUID, any non-empty string)").option("--since <ISO>", "Only rows at or after this ISO timestamp").option("--until <ISO>", "Only rows strictly before this ISO timestamp").option(
73812
+ "--action <enum>",
73813
+ "Only rows for one action: create | edit | archive | restore | add_exception | remove_exception"
73814
+ ).option("--actor-user-id <id>", "Only rows written by this user id (positive integer)").option("--limit <N>", "Max rows per page (positive integer; server page_size)").option("--page <N>", "Page number (positive integer; defaults to server page 1)").option("--json", "Output raw JSON instead of a table", false).action(async (calendarId, opts) => {
73815
+ if (calendarId === "") {
73816
+ throw new Error("<calendar_id> is required");
73817
+ }
73818
+ const action = opts.action === void 0 ? void 0 : parseAuditAction(opts.action);
73819
+ const actorUserId = opts.actorUserId === void 0 ? void 0 : parsePositiveInt4("--actor-user-id", opts.actorUserId);
73820
+ const limit = opts.limit === void 0 ? void 0 : parsePositiveInt4("--limit", opts.limit);
73821
+ const page = opts.page === void 0 ? void 0 : parsePositiveInt4("--page", opts.page);
73822
+ const setup = await setupCall(opts.server, opts.account);
73823
+ try {
73824
+ await runCalendarsAuditList({
73825
+ client: setup.client,
73826
+ calendarId,
73827
+ since: opts.since,
73828
+ until: opts.until,
73829
+ action,
73830
+ actorUserId,
73831
+ limit,
73832
+ page,
73833
+ asJson: opts.json === true,
73834
+ signal: setup.signal,
73835
+ out: process.stdout
73836
+ });
73837
+ } finally {
73838
+ setup.cleanup();
73839
+ }
73840
+ });
73841
+ addServerFlag(listCmd);
73842
+ addAccountFlag(listCmd);
73843
+ }
73844
+
73431
73845
  // src/commands/appSpace.ts
73432
73846
  import { buildRootCommand as buildAppSpaceRoot } from "@datarails-ai/dr-appspace-cli/dist/cli.js";
73433
73847
  function registerAppSpaceCommand(root) {
@@ -74041,6 +74455,7 @@ function buildRootCommand() {
74041
74455
  registerMultiProductCommand(root);
74042
74456
  registerDeskCommand(root);
74043
74457
  registerPalettesCommand(root);
74458
+ registerCalendarsCommand(root);
74044
74459
  registerAppSpaceCommand(root);
74045
74460
  return root;
74046
74461
  }