@datarails-ai/dr-cli 1.6.150 → 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.150",
11
+ version: "1.6.151",
12
12
  description: "Datarails CLI",
13
13
  type: "module",
14
14
  engines: {
@@ -69,7 +69,7 @@ var package_default = {
69
69
  };
70
70
 
71
71
  // src/buildInfo.ts
72
- var BUILD_TIME = "2026-10-20T20:43:46.967Z";
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
  });
@@ -46214,503 +46221,1194 @@ function toControlPlaneError(method, path13, err) {
46214
46221
  );
46215
46222
  }
46216
46223
 
46217
- // src/commands/connectorsDevFetch.ts
46218
- import { promises as fs19 } from "fs";
46219
- import * as path9 from "path";
46220
- import { randomUUID as randomUUID2 } from "crypto";
46221
- function buildDevEnvelope(i) {
46222
- const config = {
46223
- ...i.publicConfig,
46224
- ...i.queryConfig,
46225
- // Coerce absent values to null (not undefined) so JSON.stringify keeps the
46226
- // key — matches build_hook_job's dq.get(...) -> None; a dropped objectType
46227
- // becomes a cryptic KeyError in the runner's dispatch.
46228
- objectType: i.dataQuery.objectType ?? null,
46229
- type: i.dataQuery.type ?? null,
46230
- query: i.dataQuery.query
46231
- };
46232
- if (i.period) config.period = i.period;
46233
- return {
46234
- correlation_id: i.correlationId,
46235
- config,
46236
- secret_ref: i.secretRef,
46237
- cred_token: "dev-local",
46238
- output_sas: "file:///out/output.csv",
46239
- input_sas: null,
46240
- cursor: null,
46241
- contract_version: i.contractVersion,
46242
- metadata: {}
46243
- };
46244
- }
46245
- function asObject(v) {
46246
- if (typeof v === "object" && v !== null && !Array.isArray(v)) {
46247
- 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);
46248
46231
  }
46249
- return null;
46232
+ if (out === null) throw new Error(`${method} ${path13}: empty response from control-plane ${what}`);
46233
+ return out;
46250
46234
  }
46251
- function extractDataQueryFields(raw2) {
46252
- const root = asObject(decodeShape(raw2));
46253
- if (root === null || Object.keys(root).length === 0) {
46254
- throw new Error(
46255
- "no data-query attached to this filebox (empty data_query response) \u2014 attach one in the filebox UI first"
46256
- );
46257
- }
46258
- const cfg = asObject(root.config);
46259
- const src = cfg ?? root;
46260
- const query = src.query;
46261
- let queryObj;
46262
- if (query === void 0 || query === null) {
46263
- queryObj = {};
46264
- } else {
46265
- const asObj = asObject(query);
46266
- if (asObj === null) {
46267
- throw new Error(
46268
- `data-query \`query\` is not an object (got ${typeof query}) \u2014 expected the Sync-query field`
46269
- );
46270
- }
46271
- queryObj = asObj;
46272
- }
46273
- 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
+ );
46274
46243
  }
46275
- function extractCredentials(integration) {
46276
- const cfg = asObject(integration.config);
46277
- if (cfg === null || Object.keys(cfg).length === 0) {
46278
- throw new Error(
46279
- `datasource ${integration.id} has an empty/missing \`config\` \u2014 nothing to use as connector credentials`
46280
- );
46281
- }
46282
- 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
+ );
46283
46252
  }
46284
- var ENV_FILE = "env.json";
46285
- var SECRETS_FILE = "secrets.json";
46286
- var CONNECTION_FILE = "connection.py";
46287
- var QUERY_FILE = "query.py";
46288
- async function writeRunDir(input) {
46289
- let connectionSource;
46290
- try {
46291
- connectionSource = await fs19.readFile(input.connectionPath, "utf-8");
46292
- } catch {
46293
- throw new Error(`${input.connectionPath}: connection source not found or unreadable`);
46294
- }
46295
- 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;
46296
46279
  try {
46297
- querySource = await fs19.readFile(input.queryPath, "utf-8");
46298
- } catch {
46299
- throw new Error(`${input.queryPath}: dataquery source not found or unreadable`);
46280
+ out = await send();
46281
+ } catch (err) {
46282
+ throw toControlPlaneError(method, path13, err);
46300
46283
  }
46301
- await fs19.mkdir(input.outDir, { recursive: true });
46302
- await fs19.writeFile(
46303
- path9.join(input.outDir, ENV_FILE),
46304
- JSON.stringify(input.env, null, 2) + "\n",
46305
- "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"
46306
46293
  );
46307
- const secretsPath = path9.join(input.outDir, SECRETS_FILE);
46308
- await fs19.writeFile(
46309
- secretsPath,
46310
- JSON.stringify({ [input.secretRef]: input.credentials }, null, 2) + "\n",
46311
- "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"
46312
46310
  );
46313
- if (process.platform !== "win32") {
46314
- await fs19.chmod(secretsPath, 384);
46315
- }
46316
- await fs19.writeFile(path9.join(input.outDir, CONNECTION_FILE), connectionSource, "utf-8");
46317
- await fs19.writeFile(path9.join(input.outDir, QUERY_FILE), querySource, "utf-8");
46318
46311
  }
46319
- async function ensureGitignored(entry) {
46320
- const gitignorePath = path9.join(process.cwd(), ".gitignore");
46321
- let current = "";
46312
+ async function deleteTask(client, taskId, reqOptions) {
46313
+ const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
46322
46314
  try {
46323
- current = await fs19.readFile(gitignorePath, "utf-8");
46324
- } catch {
46315
+ await client.deleteJSON(path13, reqOptions);
46316
+ } catch (err) {
46317
+ throw toControlPlaneError("DELETE", path13, err);
46325
46318
  }
46326
- const normalized = entry.split("\\").join("/").replace(/\/+$/, "") + "/";
46327
- const lines = current.split(/\r?\n/).map((l) => l.trim());
46328
- if (lines.includes(normalized) || lines.includes(entry)) return;
46329
- const sep = current === "" || current.endsWith("\n") ? "" : "\n";
46330
- await fs19.writeFile(gitignorePath, `${current}${sep}${normalized}
46331
- `, "utf-8");
46332
46319
  }
46333
- function parsePositiveInt3(raw2, label) {
46334
- const n = Number(raw2);
46335
- if (!Number.isInteger(n) || String(n) !== raw2.trim() || n <= 0) {
46336
- throw new Error(`invalid ${label}: "${raw2}"`);
46337
- }
46338
- 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
+ );
46339
46328
  }
46340
- function registerDevFetch(connectors) {
46341
- const cmd = connectors.command("dev-fetch").description(
46342
- "Assemble a local run dir (env.json + secrets.json + connection.py + query.py) for dev-run.sh"
46343
- ).argument("<fileboxUuid>", "filebox UUID of the connector binding").requiredOption(
46344
- "--datasource <id>",
46345
- "datasource (data-integration) id whose credentials to fetch"
46346
- ).requiredOption(
46347
- "--document-id <n>",
46348
- "numeric filebox document id (no uuid->id lookup exists; required)"
46349
- ).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) => {
46350
- const datasourceId = parsePositiveInt3(opts.datasource, "datasource id");
46351
- const documentId = parsePositiveInt3(opts.documentId, "document-id");
46352
- if (opts.from === void 0 !== (opts.to === void 0)) {
46353
- throw new Error("--from and --to must be provided together");
46354
- }
46355
- const defaultRoot = path9.join("dev-run", fileboxUuid);
46356
- const outDir = opts.output ?? defaultRoot;
46357
- const gitignoreEntry = opts.output ?? "dev-run";
46358
- const serverKey = resolveServer(opts.server);
46359
- const host = resolveConnectorsUrl(serverKey, opts.connectorsUrl);
46360
- const monolithHost = resolve(serverKey).host;
46361
- const cpSetup = await setupCall(opts.server, opts.account, { host, authHost: monolithHost });
46362
- try {
46363
- const binding = await getBinding(cpSetup.client, fileboxUuid, {
46364
- signal: cpSetup.signal
46365
- });
46366
- const monoSetup = await setupCall(opts.server, opts.account, {
46367
- host: monolithHost,
46368
- authHost: monolithHost
46369
- });
46370
- try {
46371
- const rawDataQuery = await getDataQueryRaw(monoSetup.client, documentId, {
46372
- signal: monoSetup.signal
46373
- });
46374
- const dataQuery = extractDataQueryFields(rawDataQuery);
46375
- if (dataQuery.objectType === void 0 || dataQuery.objectType === null || dataQuery.objectType === "") {
46376
- process.stderr.write(
46377
- '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'
46378
- );
46379
- }
46380
- const integration = await itGet(monoSetup.client, datasourceId, {
46381
- signal: monoSetup.signal
46382
- });
46383
- const credentials = extractCredentials(integration);
46384
- let period;
46385
- if (opts.from !== void 0 && opts.to !== void 0) {
46386
- period = { from: opts.from, to: opts.to };
46387
- }
46388
- const env = buildDevEnvelope({
46389
- correlationId: randomUUID2(),
46390
- secretRef: binding.secret_ref,
46391
- publicConfig: binding.public_config,
46392
- queryConfig: binding.query_config,
46393
- dataQuery,
46394
- ...period !== void 0 ? { period } : {},
46395
- // Deliberate sandbox stub: the fetch/dispatch path doesn't branch on
46396
- // contract_version, so a fixed "1" is sufficient for a local run.
46397
- contractVersion: "1"
46398
- });
46399
- await writeRunDir({
46400
- outDir,
46401
- env,
46402
- secretRef: binding.secret_ref,
46403
- credentials,
46404
- connectionPath: opts.connection,
46405
- queryPath: opts.query
46406
- });
46407
- await ensureGitignored(gitignoreEntry);
46408
- process.stdout.write(`Wrote run dir: ${outDir}
46409
- `);
46410
- process.stdout.write(
46411
- ` env.json (secret_ref=${binding.secret_ref}, flavor=${binding.runtime_flavor})
46412
- `
46413
- );
46414
- process.stdout.write(" secrets.json (credentials NOT shown)\n");
46415
- process.stdout.write(" connection.py, query.py\n");
46416
- } finally {
46417
- monoSetup.cleanup();
46418
- }
46419
- } catch (err) {
46420
- throw augmentTlsCertError(err);
46421
- } finally {
46422
- cpSetup.cleanup();
46423
- }
46424
- });
46425
- addServerFlag(cmd);
46426
- addAccountFlag(cmd);
46427
- cmd.option(
46428
- "--connectors-url <url>",
46429
- "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"
46430
46354
  );
46431
46355
  }
46432
46356
 
46433
- // src/commands/connectorsPoll.ts
46434
- import * as readline2 from "readline";
46435
- import { createHash as createHash11 } from "crypto";
46436
-
46437
- // src/types/connectors.ts
46438
- var POLL_ITEM_STATES = [
46439
- "seen",
46440
- "new",
46441
- "dispatched",
46442
- "running",
46443
- "retry_wait",
46444
- "landed",
46445
- "failed",
46446
- "skipped",
46447
- "seeded",
46448
- "superseded"
46449
- ];
46450
-
46451
- // src/commands/connectorsPoll.ts
46357
+ // src/commands/connectorsTasks.ts
46452
46358
  var DURATION_UNIT_SECONDS = { s: 1, m: 60, h: 3600, d: 86400 };
46453
- var DURATION_BOUNDS = {
46454
- "--interval": [300, 7 * 86400],
46455
- "--settle": [0, 86400]
46456
- };
46457
- var LOOKBACK_MAX_DAYS = 3650;
46458
- function parseDurationSeconds(raw2, flag) {
46359
+ var MIN_EVERY_SECONDS = 60;
46360
+ var MAX_EVERY_SECONDS = 2 ** 31 - 1;
46361
+ function parseEvery(raw2) {
46459
46362
  const m = /^(\d+)([smhd])?$/.exec(raw2.trim());
46460
46363
  const seconds = m === null ? NaN : Number(m[1]) * (DURATION_UNIT_SECONDS[m[2] ?? "s"] ?? 1);
46461
- const [min, max] = DURATION_BOUNDS[flag] ?? [0, Number.MAX_SAFE_INTEGER];
46462
- if (m === null) {
46463
- throw new Error(`invalid ${flag} "${raw2}": expected a duration like 90s, 5m or 1h`);
46464
- }
46465
- if (!Number.isSafeInteger(seconds) || seconds < min || seconds > max) {
46466
- throw new Error(
46467
- `invalid ${flag} "${raw2}": must be between ${formatSeconds(min)} and ${formatSeconds(max)}`
46468
- );
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`);
46469
46368
  }
46470
46369
  return seconds;
46471
46370
  }
46472
- function parseLookbackDays(raw2) {
46473
- const m = /^(\d+)d?$/.exec(raw2.trim());
46474
- const days = m === null ? 0 : Number(m[1]);
46475
- if (days < 1 || days > LOOKBACK_MAX_DAYS) {
46476
- throw new Error(
46477
- `invalid --lookback "${raw2}": must be between 1 and ${LOOKBACK_MAX_DAYS} days, e.g. 180d`
46478
- );
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 * * * *"`);
46479
46375
  }
46480
- return days;
46481
- }
46482
- var DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
46483
- var DATE_TIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(?:Z|[+-](\d{2}):(\d{2}))$/;
46484
- function inRange(value, max, min = 0) {
46485
- const n = Number(value ?? "0");
46486
- return n >= min && n <= max;
46376
+ return fields.join(" ");
46487
46377
  }
46488
- function isCalendarDay(y, m, d) {
46489
- if (!inRange(m, 12, 1)) return false;
46490
- const daysInMonth3 = new Date(Date.UTC(Number(y), Number(m), 0)).getUTCDate();
46491
- 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;
46492
46397
  }
46493
- function parseTimestampFlag(raw2, flag) {
46494
- const invalid = new Error(
46495
- `invalid ${flag} "${raw2}": expected YYYY-MM-DD or a timestamp with a zone (e.g. 2026-10-08T17:30:00+03:00)`
46496
- );
46497
- const value = raw2.trim();
46498
- const dateOnly = DATE_ONLY_RE.exec(value);
46499
- if (dateOnly !== null) {
46500
- if (!isCalendarDay(dateOnly[1], dateOnly[2], dateOnly[3])) throw invalid;
46501
- 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}`);
46502
46406
  }
46503
- const dt = DATE_TIME_RE.exec(value);
46504
- 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);
46505
- const ms = valid ? Date.parse(value) : NaN;
46506
- if (Number.isNaN(ms)) throw invalid;
46507
- 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;
46508
46411
  }
46509
- function parseSameName(raw2) {
46510
- if (raw2 === "new-version") return "new_version";
46511
- if (raw2 === "ignore") return "ignore";
46512
- throw new Error(`--same-name must be new-version or ignore (got "${raw2}")`);
46412
+ function readUtf8(p) {
46413
+ return readFileSync3(p, "utf-8");
46513
46414
  }
46514
- function parseChoice(raw2, flag, choices) {
46515
- const match = choices.find((c) => c === raw2);
46516
- if (match === void 0) {
46517
- throw new Error(`${flag} must be ${choices.join(" or ")} (got "${raw2}")`);
46518
- }
46519
- return match;
46415
+ function collect(value, previous = []) {
46416
+ return [...previous, value];
46520
46417
  }
46521
- var WIDEN_CHOICES = ["seed", "land"];
46522
- var POLICY_CHOICES = ["rekey", "seed", "land"];
46523
- function parseSettings(opts) {
46524
- const fields = {};
46525
- if (opts.interval !== void 0) {
46526
- 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");
46527
46421
  }
46528
- if (opts.since !== void 0) fields.since = parseTimestampFlag(opts.since, "--since");
46529
- if (opts.lookback !== void 0) fields.lookback_days = parseLookbackDays(opts.lookback);
46530
- if (opts.settle !== void 0) {
46531
- 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
+ }
46532
46437
  }
46533
- if (opts.sameName !== void 0) fields.change_policy = parseSameName(opts.sameName);
46534
46438
  return fields;
46535
46439
  }
46536
- function settingsArgs(opts) {
46537
- const pairs = [
46538
- ["--interval", opts.interval],
46539
- ["--since", opts.since],
46540
- ["--lookback", opts.lookback],
46541
- ["--settle", opts.settle],
46542
- ["--same-name", opts.sameName]
46543
- ];
46544
- 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
+ };
46545
46463
  }
46546
- async function resolvePollBinding(client, filebox, signal) {
46547
- const wanted = filebox.trim();
46548
- const byId = /^\d+$/.test(wanted);
46549
- const rows = await listQueries(client, { signal });
46550
- const matches = rows.filter(
46551
- (q) => byId ? q.filebox_id === Number(wanted) : q.filebox_uuid.toLowerCase() === wanted.toLowerCase()
46552
- );
46553
- const [first] = matches;
46554
- 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) {
46555
46470
  throw new Error(
46556
- `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)`
46557
46472
  );
46558
46473
  }
46559
- if (matches.length > 1) {
46560
- const ids = matches.map((q) => `#${q.query_id}`).join(", ");
46561
- throw new Error(`filebox ${wanted} has several connector bindings (${ids})`);
46562
- }
46563
- return first;
46474
+ return match.id;
46564
46475
  }
46565
- function isNotPolled(err) {
46566
- 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;
46567
46501
  }
46568
- function isPollThrottled(err) {
46569
- 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;
46570
46505
  }
46571
- async function explainNotPolled(filebox, call2) {
46506
+ function detailOf(body) {
46572
46507
  try {
46573
- return await call2;
46574
- } catch (err) {
46575
- if (isNotPolled(err)) {
46576
- throw new Error(
46577
- `filebox ${filebox} is not set to polled ingestion; run dr connectors poll enable ${filebox}`,
46578
- { cause: err }
46579
- );
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("; ");
46580
46515
  }
46581
- throw err;
46516
+ } catch {
46582
46517
  }
46518
+ return body;
46583
46519
  }
46584
- async function readFileboxDateTag(monolith, fileboxId, signal) {
46585
- const details = await getDetails(monolith, fileboxId, { signal });
46586
- const dateTags = (details.tags_config ?? []).filter((t) => t.type === "DATE");
46587
- const [only] = dateTags;
46588
- if (only === void 0) return {};
46589
- if (dateTags.length > 1) {
46590
- const names = dateTags.map((t) => t.name).join(", ");
46591
- throw new Error(
46592
- `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
+ `
46593
46563
  );
46594
46564
  }
46595
- return { id: only.id, name: only.name };
46596
46565
  }
46597
- function formatSeconds(sec) {
46598
- if (sec > 0 && sec % 86400 === 0) return `${sec / 86400}d`;
46599
- if (sec > 0 && sec % 3600 === 0) return `${sec / 3600}h`;
46600
- if (sec > 0 && sec % 60 === 0) return `${sec / 60}m`;
46601
- 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
+ }
46602
46578
  }
46603
- function oneLine3(s) {
46604
- 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;
46605
46585
  }
46606
- function sameNameLabel(policy) {
46607
- 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)");
46608
46591
  }
46609
- function formatDateTag(tag) {
46610
- if (tag?.id === void 0) return "none (undated filebox)";
46611
- 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
+ });
46612
46603
  }
46613
- function bindingTarget(binding) {
46614
- 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
+ });
46615
46731
  }
46616
- 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) {
46617
46776
  const row = (label, value) => {
46618
- out.write(` ${`${label}:`.padEnd(15)}${value}
46777
+ out.write(` ${`${label}:`.padEnd(14)}${value}
46619
46778
  `);
46620
46779
  };
46621
- const status2 = `${p.enabled ? "enabled" : "disabled"}${p.paused_reason === null ? "" : `, paused: ${oneLine3(p.paused_reason)}`}`;
46622
- const counts = Object.entries(p.counts).sort(([a], [b]) => a.localeCompare(b)).map(([state, n]) => `${state}=${n}`).join(" ");
46623
- 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)}
46624
46796
  `);
46625
- row("Status", status2);
46626
- row("Interval", formatSeconds(p.interval_seconds));
46627
- row("Date tag", formatDateTag(p.date_tag));
46628
- row("Since", p.since ?? "-");
46629
- row("Lookback", `${p.lookback_days}d`);
46630
- row("Settle", formatSeconds(p.settle_seconds));
46631
- row("Same name", sameNameLabel(p.change_policy));
46632
- row("Max attempts", String(p.max_attempts));
46633
- row("Last poll", `${p.last_poll_at ?? "-"} ${oneLine3(p.last_poll_status ?? "")}`.trimEnd());
46634
- row("Next poll", p.next_poll_at);
46635
- row("Failures", String(p.consecutive_failures));
46636
- row("Discover", p.discover_in_flight ? `in flight since ${p.discover_sent_at ?? "?"}` : "-");
46637
- row("Last digest", p.last_digest ?? "-");
46638
- row("Pending seed", p.poll_run_seed_until ?? "-");
46639
- row(
46640
- "Pending rekey",
46641
- 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"
46642
46800
  );
46643
- row("Items", counts === "" ? "(none)" : counts);
46644
46801
  }
46645
- function writeTable26(out, rows) {
46646
- const widths = [];
46647
- for (const r of rows) {
46648
- r.forEach((cell, i) => {
46649
- widths[i] = Math.max(widths[i] ?? 0, cell.length);
46650
- });
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
+ ]);
46651
46817
  }
46818
+ const widths = rows[0].map((_, i) => Math.max(...rows.map((r) => r[i].length)));
46652
46819
  for (const r of rows) {
46653
- const cells = r.map(
46654
- (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
+ `
46655
46823
  );
46656
- out.write(`${cells.join("").trimEnd()}
46657
- `);
46658
46824
  }
46659
46825
  }
46660
- function renderItemTable(out, items) {
46661
- const rows = [["ID", "LABEL", "DATE", "STATE", "ATTEMPTS", "VERSION", "ERROR"]];
46662
- for (const it of items) {
46663
- const error = it.last_error ?? (it.skip_reason === null ? null : `skipped: ${it.skip_reason}`);
46664
- rows.push([
46665
- String(it.id),
46666
- oneLine3(it.label),
46667
- it.item_date ?? "-",
46668
- it.state,
46669
- String(it.attempts),
46670
- it.version_id === null ? "-" : String(it.version_id),
46671
- error === null ? "-" : truncate3(oneLine3(error), 80)
46672
- ]);
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;
46832
+ }
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
46869
+ );
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
+ }
46911
+ );
46673
46912
  }
46674
- writeTable26(out, rows);
46675
46913
  }
46676
- async function runPollEnable(cfg) {
46677
- const binding = await resolvePollBinding(cfg.client, cfg.filebox, cfg.signal);
46678
- let existing;
46679
- try {
46680
- existing = await getPoll(cfg.client, binding.query_id, { signal: cfg.signal });
46681
- } catch (err) {
46682
- if (!isNotPolled(err)) throw err;
46683
- existing = null;
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
+ };
46942
+ }
46943
+ function asObject(v) {
46944
+ if (typeof v === "object" && v !== null && !Array.isArray(v)) {
46945
+ return v;
46684
46946
  }
46685
- const s = cfg.settings;
46686
- if (existing !== null && (s.since !== void 0 || s.lookback_days !== void 0 || s.change_policy !== void 0)) {
46947
+ return null;
46948
+ }
46949
+ function extractDataQueryFields(raw2) {
46950
+ const root = asObject(decodeShape(raw2));
46951
+ if (root === null || Object.keys(root).length === 0) {
46687
46952
  throw new Error(
46688
- `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`
46953
+ "no data-query attached to this filebox (empty data_query response) \u2014 attach one in the filebox UI first"
46689
46954
  );
46690
46955
  }
46691
- if (binding.filebox_id <= 0) {
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
+ );
46968
+ }
46969
+ queryObj = asObj;
46970
+ }
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) {
46692
46976
  throw new Error(
46693
- `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>`
46977
+ `datasource ${integration.id} has an empty/missing \`config\` \u2014 nothing to use as connector credentials`
46694
46978
  );
46695
46979
  }
46696
- const dateTag = await readFileboxDateTag(cfg.monolith, binding.filebox_id, cfg.signal);
46697
- const stored = await putPoll(
46698
- cfg.client,
46699
- binding.query_id,
46700
- { enabled: true, date_tag: dateTag, ...s },
46701
- { signal: cfg.signal }
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"
46702
47004
  );
46703
- if (cfg.asJson) {
46704
- writeJSONIndented(cfg.out, stored);
46705
- return;
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);
46706
47013
  }
46707
- renderPoll(cfg.out, "Polling enabled", binding, stored);
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");
46708
47016
  }
46709
- function windowStart(since, lookbackDays, now) {
46710
- const start = now - lookbackDays * 864e5;
46711
- return since === null ? start : Math.max(start, Date.parse(since));
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");
46712
47030
  }
46713
- function widening(current, changes, now) {
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) {
46714
47412
  if (changes.since === void 0 && changes.lookback_days === void 0) return null;
46715
47413
  const from = windowStart(current.since, current.lookback_days, now);
46716
47414
  const to = windowStart(
@@ -47083,793 +47781,332 @@ async function withMonolith(opts, action) {
47083
47781
  function scopeOf(opts) {
47084
47782
  const serverKey = resolveServer(opts.server);
47085
47783
  return {
47086
- serverKey,
47087
- account: normaliseAccount(opts.account),
47088
- controlPlane: resolveConnectorsUrl(serverKey, opts.connectorsUrl)
47089
- };
47090
- }
47091
- function targetArgsOf(opts) {
47092
- const url = (opts.connectorsUrl ?? "").trim();
47093
- return ` --server ${resolveServer(opts.server)}${url === "" ? "" : ` --connectors-url ${url}`}`;
47094
- }
47095
- function addCommonFlags(cmd, jsonHelp) {
47096
- cmd.option("--json", jsonHelp, false);
47097
- addServerFlag(cmd);
47098
- addAccountFlag(cmd);
47099
- addConnectorsUrlFlag(cmd);
47100
- }
47101
- function addConfirmTokenFlag(cmd) {
47102
- return cmd.option("--confirm-token <token>", "Token from a prior plan run \u2014 required to proceed");
47103
- }
47104
- function addSettingsFlags(cmd) {
47105
- 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(
47106
- "--same-name <policy>",
47107
- "a changed file under the same name: new-version (lands again) or ignore"
47108
- );
47109
- }
47110
- function ttyChoicePrompt(message, choices, defaultChoice, io = { input: process.stdin, output: process.stderr }) {
47111
- return new Promise((resolvePrompt) => {
47112
- const rl = readline2.createInterface({
47113
- input: io.input,
47114
- output: io.output,
47115
- ...io.terminal !== void 0 ? { terminal: io.terminal } : {}
47116
- });
47117
- let settled = false;
47118
- const settle = (value) => {
47119
- if (settled) return;
47120
- settled = true;
47121
- rl.close();
47122
- resolvePrompt(value);
47123
- };
47124
- rl.on("SIGINT", () => settle(null));
47125
- rl.on("close", () => settle(null));
47126
- io.output.write(`${message}
47127
- `);
47128
- const question = defaultChoice === void 0 ? `[${choices.join("/")}]: ` : `[${choices.join("/")}] (Enter = ${defaultChoice}): `;
47129
- const askOnce = () => {
47130
- rl.question(question, (answer) => {
47131
- const picked = answer.trim().toLowerCase();
47132
- if (picked === "" && defaultChoice !== void 0) {
47133
- settle(defaultChoice);
47134
- } else if (choices.includes(picked)) {
47135
- settle(picked);
47136
- } else {
47137
- io.output.write(`Answer one of: ${choices.join(", ")}
47138
- `);
47139
- askOnce();
47140
- }
47141
- });
47142
- };
47143
- askOnce();
47144
- });
47145
- }
47146
- var POLL_CONFIRM_HELP = [
47147
- "Confirmation:",
47148
- " seed, skip and set --on-policy-change seed print a plan and a --confirm-token;",
47149
- " re-run the printed command, token included, to apply it.",
47150
- " No dialog opens on production, so an agent or script can run a cut-over unattended:",
47151
- " dr connectors poll seed <filebox> --until <cut-over> --server <prod>",
47152
- " dr connectors poll seed <filebox> --until <cut-over> --server <prod> --confirm-token <token>",
47153
- " (seed --dry-run counts the already-discovered items a seed would mark.)",
47154
- " A write with any answer that lands files (set --widen land, set --on-policy-change land,",
47155
- " resume --source land) also asks a person to type a word in a dialog on production,",
47156
- " even when the same set also seeds (--on-policy-change seed --widen land)."
47157
- ].join("\n");
47158
- function registerConnectorsPoll(connectors) {
47159
- const poll2 = connectors.command("poll").description("Polled file ingestion: the control-plane lands each new file as one version").addHelpText("after", `
47160
- ${POLL_CONFIRM_HELP}`);
47161
- const enableCmd = addSettingsFlags(
47162
- poll2.command("enable").description("Poll a filebox's binding (dated with the filebox's DATE tag)").argument("<filebox>", "filebox id or uuid")
47163
- ).action(async (filebox, opts) => {
47164
- const settings = parseSettings(opts);
47165
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47166
- await withMonolith(opts, async (monolith) => {
47167
- await runPollEnable({
47168
- client,
47169
- monolith,
47170
- filebox,
47171
- settings,
47172
- asJson: opts.json === true,
47173
- signal,
47174
- out: process.stdout
47175
- });
47176
- });
47177
- });
47178
- });
47179
- addCommonFlags(enableCmd, "Output the stored binding as JSON");
47180
- const setCmd = addConfirmTokenFlag(
47181
- addSettingsFlags(
47182
- poll2.command("set").description("Change polling settings (only the given flags change)").argument("<filebox>", "filebox id or uuid")
47183
- ).option(
47184
- "--widen <seed|land>",
47185
- "a wider window: seed (mark exposed items landed) or land them"
47186
- ).option("--on-policy-change <rekey|seed|land>", "a --same-name switch: rekey, seed or land").addHelpText("after", `
47187
- ${POLL_CONFIRM_HELP}`)
47188
- ).action(
47189
- async (filebox, opts) => {
47190
- const changes = parseSettings(opts);
47191
- const widen = opts.widen === void 0 ? void 0 : parseChoice(opts.widen, "--widen", WIDEN_CHOICES);
47192
- const onPolicyChange = opts.onPolicyChange === void 0 ? void 0 : parseChoice(opts.onPolicyChange, "--on-policy-change", POLICY_CHOICES);
47193
- await preflightConfirmToken(
47194
- pollConfirmAction(filebox, pollSetConfirmVerb(widen, onPolicyChange), scopeOf(opts), {
47195
- ...changes,
47196
- ...widen !== void 0 ? { widen } : {},
47197
- ...onPolicyChange !== void 0 ? { on_policy_change: onPolicyChange } : {}
47198
- }),
47199
- opts.confirmToken
47200
- );
47201
- await withConnectorsSetup(opts, async (setup) => {
47202
- const deadline = {
47203
- pause: () => setup.pauseTimeout(),
47204
- resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
47205
- };
47206
- await runPollSet({
47207
- client: setup.client,
47208
- filebox,
47209
- changes,
47210
- settingsArgs: settingsArgs(opts),
47211
- ...widen !== void 0 ? { widen } : {},
47212
- ...onPolicyChange !== void 0 ? { onPolicyChange } : {},
47213
- interactive: process.stdin.isTTY === true && process.stderr.isTTY === true,
47214
- prompt: async (message, choices, defaultChoice) => {
47215
- deadline.pause();
47216
- try {
47217
- return await ttyChoicePrompt(message, choices, defaultChoice);
47218
- } finally {
47219
- deadline.resume();
47220
- }
47221
- },
47222
- deadline,
47223
- ...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
47224
- scope: scopeOf(opts),
47225
- targetArgs: targetArgsOf(opts),
47226
- asJson: opts.json === true,
47227
- signal: setup.signal,
47228
- out: process.stdout
47229
- });
47230
- });
47231
- }
47232
- );
47233
- addCommonFlags(setCmd, "Output the stored binding as JSON");
47234
- 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) => {
47235
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47236
- await runPollDisable({
47237
- client,
47238
- filebox,
47239
- asJson: opts.json === true,
47240
- signal,
47241
- out: process.stdout
47242
- });
47243
- });
47244
- });
47245
- addCommonFlags(disableCmd, "Output the stored binding as JSON");
47246
- 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) => {
47247
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47248
- await runPollGet({
47249
- client,
47250
- filebox,
47251
- asJson: opts.json === true,
47252
- signal,
47253
- out: process.stdout
47254
- });
47255
- });
47256
- });
47257
- addCommonFlags(getCmd, "Output the raw binding as JSON");
47258
- const runCmd = poll2.command("run").description("Poll now").argument("<filebox>", "filebox id or uuid").action(async (filebox, opts) => {
47259
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47260
- await runPollRun({
47261
- client,
47262
- filebox,
47263
- asJson: opts.json === true,
47264
- signal,
47265
- out: process.stdout
47266
- });
47267
- });
47268
- });
47269
- addCommonFlags(runCmd, "Output the binding as JSON");
47270
- const resumeCmd = addConfirmTokenFlag(
47271
- poll2.command("resume").description("Resume a paused binding (after fixing credentials, or a source change)").argument("<filebox>", "filebox id or uuid").option(
47272
- "--source <seed|land>",
47273
- "after a source change: seed (mark present items landed) or land them"
47274
- )
47275
- ).action(async (filebox, opts) => {
47276
- const source = opts.source === void 0 ? void 0 : parseChoice(opts.source, "--source", WIDEN_CHOICES);
47277
- if (source === "land") {
47278
- await preflightConfirmToken(
47279
- pollConfirmAction(filebox, "resume-land", scopeOf(opts)),
47280
- opts.confirmToken
47281
- );
47282
- }
47283
- await withConnectorsSetup(opts, async (setup) => {
47284
- await runPollResume({
47285
- client: setup.client,
47286
- filebox,
47287
- ...source !== void 0 ? { source } : {},
47288
- deadline: {
47289
- pause: () => setup.pauseTimeout(),
47290
- resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
47291
- },
47292
- ...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
47293
- scope: scopeOf(opts),
47294
- targetArgs: targetArgsOf(opts),
47295
- asJson: opts.json === true,
47296
- signal: setup.signal,
47297
- out: process.stdout
47298
- });
47299
- });
47300
- });
47301
- addCommonFlags(resumeCmd, "Output the binding as JSON");
47302
- 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(
47303
- async (filebox, opts) => {
47304
- const state = opts.state === void 0 ? void 0 : POLL_ITEM_STATES.find((s) => s === opts.state);
47305
- if (opts.state !== void 0 && state === void 0) {
47306
- throw new Error(
47307
- `invalid --state "${opts.state}": expected one of ${POLL_ITEM_STATES.join(", ")}`
47308
- );
47309
- }
47310
- const limit = opts.limit === void 0 ? void 0 : parsePositiveIntArg2(opts.limit, "--limit");
47311
- const after = opts.after === void 0 ? void 0 : parsePositiveIntArg2(opts.after, "--after");
47312
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47313
- await runPollItems({
47314
- client,
47315
- filebox,
47316
- ...state !== void 0 ? { state } : {},
47317
- ...limit !== void 0 ? { limit } : {},
47318
- ...after !== void 0 ? { after } : {},
47319
- asJson: opts.json === true,
47320
- signal,
47321
- out: process.stdout
47322
- });
47323
- });
47324
- }
47325
- );
47326
- addCommonFlags(itemsCmd, "Output the raw page as JSON");
47327
- 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) => {
47328
- const itemId = parsePositiveIntArg2(item, "item id");
47329
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47330
- await runPollRetry({
47331
- client,
47332
- filebox,
47333
- itemId,
47334
- asJson: opts.json === true,
47335
- signal,
47336
- out: process.stdout
47337
- });
47338
- });
47339
- });
47340
- addCommonFlags(retryCmd, "Output the item as JSON");
47341
- const skipCmd = addConfirmTokenFlag(
47342
- 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)")
47343
- ).action(async (filebox, item, opts) => {
47344
- const itemId = parsePositiveIntArg2(item, "item id");
47345
- await preflightConfirmToken(
47346
- pollItemConfirmAction(filebox, itemId, scopeOf(opts)),
47347
- opts.confirmToken
47348
- );
47349
- await withConnectorsSetup(opts, async (setup) => {
47350
- await runPollSkip({
47351
- client: setup.client,
47352
- filebox,
47353
- itemId,
47354
- deadline: {
47355
- pause: () => setup.pauseTimeout(),
47356
- resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
47357
- },
47358
- ...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
47359
- scope: scopeOf(opts),
47360
- targetArgs: targetArgsOf(opts),
47361
- asJson: opts.json === true,
47362
- signal: setup.signal,
47363
- out: process.stdout
47364
- });
47365
- });
47366
- });
47367
- addCommonFlags(skipCmd, "Output the item as JSON");
47368
- const seedCmd = addConfirmTokenFlag(
47369
- poll2.command("seed").description(
47370
- "Mark items modified at or before --until as already landed (cut-over; token, no dialog)"
47371
- ).argument("<filebox>", "filebox id or uuid").requiredOption(
47372
- "--until <timestamp>",
47373
- "date or timestamp with a zone, e.g. 2026-10-08T17:30:00Z"
47374
- ).option(
47375
- "--dry-run",
47376
- "count the already-discovered items it would mark, without changing anything",
47377
- false
47378
- ).addHelpText("after", `
47379
- ${POLL_CONFIRM_HELP}`)
47380
- ).action(
47381
- async (filebox, opts) => {
47382
- const until = parseTimestampFlag(opts.until, "--until");
47383
- const dryRun = opts.dryRun === true;
47384
- if (!dryRun) {
47385
- await preflightConfirmToken(
47386
- pollConfirmAction(filebox, "seed", scopeOf(opts), { until }),
47387
- opts.confirmToken
47388
- );
47389
- }
47390
- await withConnectorsSetup(opts, async (setup) => {
47391
- await runPollSeed({
47392
- client: setup.client,
47393
- filebox,
47394
- until,
47395
- untilArg: opts.until.trim(),
47396
- dryRun,
47397
- deadline: {
47398
- pause: () => setup.pauseTimeout(),
47399
- resume: () => setup.extendTimeout(DEFAULT_CALL_TIMEOUT_MS)
47400
- },
47401
- ...opts.confirmToken !== void 0 ? { token: opts.confirmToken } : {},
47402
- scope: scopeOf(opts),
47403
- targetArgs: targetArgsOf(opts),
47404
- asJson: opts.json === true,
47405
- signal: setup.signal,
47406
- out: process.stdout
47407
- });
47408
- });
47409
- }
47410
- );
47411
- addCommonFlags(seedCmd, "Output the binding (or the dry-run count) as JSON");
47412
- }
47413
-
47414
- // src/commands/connectorsTasks.ts
47415
- import { readFileSync as readFileSync3 } from "fs";
47416
-
47417
- // src/api/scheduledTasks.ts
47418
- var BASE2 = "/scheduled-tasks";
47419
- async function call(method, path13, send, what) {
47420
- let out;
47421
- try {
47422
- out = await send();
47423
- } catch (err) {
47424
- throw toControlPlaneError(method, path13, err);
47425
- }
47426
- if (out === null) throw new Error(`${method} ${path13}: empty response from control-plane ${what}`);
47427
- return out;
47428
- }
47429
- async function createTask(client, fields, reqOptions) {
47430
- return call(
47431
- "POST",
47432
- BASE2,
47433
- () => client.postJSON(BASE2, fields, reqOptions),
47434
- "task create"
47435
- );
47436
- }
47437
- async function listTasks(client, orgId, reqOptions) {
47438
- const path13 = orgId === void 0 ? BASE2 : `${BASE2}?org_id=${orgId}`;
47439
- return call("GET", path13, () => client.getJSON(path13, reqOptions), "task list");
47440
- }
47441
- async function getTask(client, taskId, reqOptions) {
47442
- const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
47443
- return call("GET", path13, () => client.getJSON(path13, reqOptions), "task get");
47444
- }
47445
- async function updateTask(client, taskId, fields, reqOptions) {
47446
- const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
47447
- return call(
47448
- "PATCH",
47449
- path13,
47450
- () => client.patchJSON(path13, fields, reqOptions),
47451
- "task update"
47452
- );
47453
- }
47454
- async function deleteTask(client, taskId, reqOptions) {
47455
- const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}`;
47456
- try {
47457
- await client.deleteJSON(path13, reqOptions);
47458
- } catch (err) {
47459
- throw toControlPlaneError("DELETE", path13, err);
47460
- }
47461
- }
47462
- async function runTask(client, taskId, reqOptions) {
47463
- const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/run`;
47464
- return call(
47465
- "POST",
47466
- path13,
47467
- () => client.postJSON(path13, {}, reqOptions),
47468
- "task run"
47469
- );
47470
- }
47471
- async function pauseTask(client, taskId, reqOptions) {
47472
- const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/pause`;
47473
- return call(
47474
- "POST",
47475
- path13,
47476
- () => client.postJSON(path13, {}, reqOptions),
47477
- "task pause"
47478
- );
47479
- }
47480
- async function resumeTask(client, taskId, reqOptions) {
47481
- const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/resume`;
47482
- return call(
47483
- "POST",
47484
- path13,
47485
- () => client.postJSON(path13, {}, reqOptions),
47486
- "task resume"
47487
- );
47488
- }
47489
- async function listRuns(client, taskId, limit, reqOptions) {
47490
- const path13 = `/scheduled-tasks/${encodeURIComponent(taskId)}/runs?limit=${limit}`;
47491
- return call(
47492
- "GET",
47493
- path13,
47494
- () => client.getJSON(path13, reqOptions),
47495
- "task runs"
47496
- );
47497
- }
47498
-
47499
- // src/commands/connectorsTasks.ts
47500
- var DURATION_UNIT_SECONDS2 = { s: 1, m: 60, h: 3600, d: 86400 };
47501
- var MIN_EVERY_SECONDS = 60;
47502
- var MAX_EVERY_SECONDS = 2 ** 31 - 1;
47503
- function parseEvery(raw2) {
47504
- const m = /^(\d+)([smhd])?$/.exec(raw2.trim());
47505
- const seconds = m === null ? NaN : Number(m[1]) * (DURATION_UNIT_SECONDS2[m[2] ?? "s"] ?? 1);
47506
- if (m === null)
47507
- throw new Error(`invalid --every "${raw2}": expected a duration like 5m, 1h or 1d`);
47508
- if (!Number.isSafeInteger(seconds) || seconds < MIN_EVERY_SECONDS || seconds > MAX_EVERY_SECONDS) {
47509
- throw new Error(`invalid --every "${raw2}": must be at least 1m`);
47510
- }
47511
- return seconds;
47512
- }
47513
- function parseCron(raw2) {
47514
- const fields = raw2.trim().split(/\s+/);
47515
- if (fields.length !== 5 || fields.some((f) => f === "")) {
47516
- throw new Error(`invalid --cron "${raw2}": expected five fields, e.g. "15 * * * *"`);
47517
- }
47518
- return fields.join(" ");
47519
- }
47520
- var ROLE_RE = /^[a-z][a-z_]*$/;
47521
- var DATASOURCE_ID_RE = /^[0-9]{1,18}$/;
47522
- function parseDatasources(values) {
47523
- const out = {};
47524
- for (const raw2 of values) {
47525
- const eq = raw2.indexOf("=");
47526
- const role = eq < 0 ? "" : raw2.slice(0, eq).trim();
47527
- const id = eq < 0 ? "" : raw2.slice(eq + 1).trim();
47528
- if (!ROLE_RE.test(role) || !DATASOURCE_ID_RE.test(id)) {
47529
- throw new Error(
47530
- `invalid --datasource "${raw2}": expected <role>=<datasource id>, e.g. target=4242 (role: lowercase letters and _; id: digits)`
47531
- );
47532
- }
47533
- if (Object.hasOwn(out, role)) throw new Error(`--datasource role "${role}" given twice`);
47534
- out[role] = id;
47535
- }
47536
- const count = Object.keys(out).length;
47537
- if (count < 1 || count > 4) throw new Error("give between 1 and 4 --datasource roles");
47538
- return out;
47539
- }
47540
- function parseConfig(raw2, readFile9 = readUtf8) {
47541
- const text2 = raw2.startsWith("@") ? readFile9(raw2.slice(1)) : raw2;
47542
- let parsed2;
47543
- try {
47544
- parsed2 = JSON.parse(text2);
47545
- } catch (err) {
47546
- const msg = err instanceof Error ? err.message : String(err);
47547
- throw new Error(`invalid --config JSON: ${msg}`);
47548
- }
47549
- if (typeof parsed2 !== "object" || parsed2 === null || Array.isArray(parsed2)) {
47550
- throw new Error("invalid --config: expected a JSON object");
47551
- }
47552
- return parsed2;
47553
- }
47554
- function readUtf8(p) {
47555
- return readFileSync3(p, "utf-8");
47556
- }
47557
- function collect(value, previous = []) {
47558
- return [...previous, value];
47559
- }
47560
- function parseScheduleFlags(opts) {
47561
- if (opts.every !== void 0 && opts.cron !== void 0) {
47562
- throw new Error("give --every or --cron, not both");
47563
- }
47564
- const fields = {};
47565
- if (opts.artifact !== void 0) fields.artifact_name = opts.artifact;
47566
- if (opts.flavor !== void 0) fields.runtime_flavor = opts.flavor;
47567
- if (opts.datasource !== void 0) fields.datasources = parseDatasources(opts.datasource);
47568
- if (opts.primaryRole !== void 0) fields.primary_role = opts.primaryRole;
47569
- if (opts.config !== void 0) fields.config = parseConfig(opts.config);
47570
- if (opts.every !== void 0) fields.every_seconds = parseEvery(opts.every);
47571
- if (opts.cron !== void 0) fields.cron = parseCron(opts.cron);
47572
- if (opts.tz !== void 0) fields.timezone = opts.tz;
47573
- if (fields.primary_role !== void 0 && fields.datasources !== void 0) {
47574
- if (!Object.hasOwn(fields.datasources, fields.primary_role)) {
47575
- throw new Error(
47576
- `--primary-role "${fields.primary_role}" is not one of the --datasource roles`
47577
- );
47578
- }
47579
- }
47580
- return fields;
47581
- }
47582
- function buildCreateFields(name, orgId, opts) {
47583
- const missing = [
47584
- ["--artifact", opts.artifact],
47585
- ["--flavor", opts.flavor],
47586
- ["--datasource", opts.datasource],
47587
- ["--config", opts.config]
47588
- ].filter(([, v]) => v === void 0).map(([flag]) => flag);
47589
- if (opts.every === void 0 && opts.cron === void 0) missing.push("--every or --cron");
47590
- if (missing.length > 0) throw new Error(`missing ${missing.join(", ")}`);
47591
- const fields = parseScheduleFlags(opts);
47592
- return {
47593
- org_id: orgId,
47594
- name,
47595
- artifact_name: fields.artifact_name,
47596
- runtime_flavor: fields.runtime_flavor,
47597
- datasources: fields.datasources,
47598
- config: fields.config,
47599
- ...fields.primary_role !== void 0 ? { primary_role: fields.primary_role } : {},
47600
- ...fields.every_seconds !== void 0 ? { every_seconds: fields.every_seconds } : {},
47601
- ...fields.cron !== void 0 ? { cron: fields.cron } : {},
47602
- ...fields.timezone !== void 0 ? { timezone: fields.timezone } : {},
47603
- ...opts.disabled === true ? { enabled: false } : {}
47604
- };
47605
- }
47606
- var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
47607
- async function resolveTaskId(client, task, signal) {
47608
- if (UUID_RE.test(task)) return task;
47609
- const tasks = await listTasks(client, void 0, { signal });
47610
- const match = tasks.find((t) => t.name === task);
47611
- if (match === void 0) {
47612
- throw new Error(
47613
- `no scheduled task named "${task}" in this org (see: dr connectors tasks list)`
47614
- );
47615
- }
47616
- return match.id;
47617
- }
47618
- function explainTaskError(err) {
47619
- const forbidden = err instanceof ControlPlaneError && err.status === 403 || err instanceof Error && /\bHTTP 403\b/.test(err.message);
47620
- if (forbidden) {
47621
- const message = err instanceof Error ? err.message : String(err);
47622
- const body = err instanceof ControlPlaneError ? err.body : (/HTTP 403[^:]*(?:\([^)]*\))?:\s*([\s\S]*)$/.exec(message)?.[1] ?? "").trim();
47623
- return withCause(
47624
- new ControlPlaneError(
47625
- 403,
47626
- body,
47627
- `${message}
47628
- 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.`
47629
- ),
47630
- err
47631
- );
47632
- }
47633
- if (!(err instanceof ControlPlaneError)) return err;
47634
- const detail = detailOf(err.body);
47635
- if (err.status === 404) {
47636
- const message = `scheduled task not found (${detail || err.message})`;
47637
- return withCause(new ControlPlaneError(404, err.body, message), err);
47638
- }
47639
- if (err.status === 409 || err.status === 422) {
47640
- return withCause(new ControlPlaneError(err.status, err.body, detail || err.message), err);
47641
- }
47642
- return err;
47643
- }
47644
- function withCause(error, cause) {
47645
- Object.defineProperty(error, "cause", { value: cause, enumerable: false, configurable: true });
47646
- return error;
47647
- }
47648
- function detailOf(body) {
47649
- try {
47650
- const parsed2 = JSON.parse(body);
47651
- const d = parsed2.detail;
47652
- if (typeof d === "string") return d;
47653
- if (Array.isArray(d)) {
47654
- return d.map(
47655
- (e) => `${Array.isArray(e.loc) ? e.loc.slice(1).join(".") : ""}: ${e.msg ?? ""}`.trim()
47656
- ).join("; ");
47657
- }
47658
- } catch {
47659
- }
47660
- return body;
47661
- }
47662
- function scheduleText(t) {
47663
- if (t.schedule_kind === "cron") return `cron "${t.cron ?? ""}" ${t.timezone}`;
47664
- const s = t.every_seconds ?? 0;
47665
- const unit = s % 86400 === 0 ? `${s / 86400}d` : s % 3600 === 0 ? `${s / 3600}h` : s % 60 === 0 ? `${s / 60}m` : `${s}s`;
47666
- return `every ${unit}`;
47667
- }
47668
- function stateText(t) {
47669
- if (!t.enabled) return "disabled";
47670
- if (t.paused_reason !== null) return `paused (${t.paused_reason})`;
47671
- return t.running ? "running" : "enabled";
47672
- }
47673
- function writeTask(out, t) {
47674
- const roles = Object.entries(t.datasources).map(([role, id]) => `${role}=${id}${role === t.primary_role ? " (primary)" : ""}`).join(", ");
47675
- out.write(`Task: ${t.name} (${t.id})
47676
- `);
47677
- out.write(` Org: ${t.org_id}
47678
- `);
47679
- out.write(` State: ${stateText(t)}
47680
- `);
47681
- out.write(` Artifact: ${t.artifact_name} [${t.runtime_flavor}]
47682
- `);
47683
- out.write(` Schedule: ${scheduleText(t)}
47684
- `);
47685
- out.write(` Next run: ${t.next_run_at}
47686
- `);
47687
- out.write(` Last run: ${t.last_run_at ?? "-"} ${t.last_run_status ?? ""}
47688
- `);
47689
- if (t.consecutive_failures > 0) out.write(` Failures: ${t.consecutive_failures} in a row
47690
- `);
47691
- out.write(` Datasources: ${roles}
47692
- `);
47693
- out.write(` Config: ${truncate3(JSON.stringify(t.config), 200)}
47694
- `);
47695
- }
47696
- function writeTaskList(out, tasks) {
47697
- if (tasks.length === 0) {
47698
- out.write("No scheduled tasks.\n");
47699
- return;
47700
- }
47701
- for (const t of tasks) {
47702
- out.write(
47703
- `${t.name.padEnd(28)} ${stateText(t).padEnd(20)} ${scheduleText(t).padEnd(26)} next ${t.next_run_at} last ${t.last_run_status ?? "-"} ${t.id}
47704
- `
47705
- );
47706
- }
47707
- }
47708
- function writeRuns(out, runs) {
47709
- if (runs.length === 0) {
47710
- out.write("No runs yet.\n");
47711
- return;
47712
- }
47713
- for (const r of runs) {
47714
- const tail = r.error !== null ? ` error: ${truncate3(r.error, 160)}` : r.summary !== null ? ` ${truncate3(JSON.stringify(r.summary), 160)}` : "";
47715
- out.write(
47716
- `${r.started_at} ${r.status.padEnd(15)} ${r.trigger.padEnd(8)} attempt ${r.attempt} ${r.artifact_version ?? "-"}${tail}
47717
- `
47718
- );
47719
- }
47784
+ serverKey,
47785
+ account: normaliseAccount(opts.account),
47786
+ controlPlane: resolveConnectorsUrl(serverKey, opts.connectorsUrl)
47787
+ };
47720
47788
  }
47721
- function withFlags(cmd) {
47722
- 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);
47723
47795
  addServerFlag(cmd);
47724
47796
  addAccountFlag(cmd);
47725
47797
  addConnectorsUrlFlag(cmd);
47726
- return cmd;
47727
47798
  }
47728
- function addScheduleFlags(cmd) {
47729
- 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(
47730
- "--primary-role <role>",
47731
- "the role passed as the primary datasource (default: the first)"
47732
- ).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");
47733
47801
  }
47734
- async function onTask(task, opts, act, print) {
47735
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47736
- try {
47737
- const taskId = await resolveTaskId(client, task, signal);
47738
- const result = await act(client, taskId, signal);
47739
- if (opts.json === true) writeJSONIndented(process.stdout, result);
47740
- else print(process.stdout, result);
47741
- } catch (err) {
47742
- throw explainTaskError(err);
47743
- }
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();
47744
47842
  });
47745
47843
  }
47746
- function registerConnectorsTasks(connectors) {
47747
- const tasks = connectors.command("tasks").description("DRIP scheduled tasks: run an artifact on a schedule (support user or org admin)");
47748
- withFlags(
47749
- addScheduleFlags(
47750
- 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)
47751
- )
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}`)
47752
47886
  ).action(
47753
- async (name, opts) => {
47754
- const explicitOrg = opts.orgId !== void 0 ? parsePositiveIntArg2(opts.orgId, "--org-id") : void 0;
47755
- const parsed2 = buildCreateFields(name, explicitOrg ?? 0, opts);
47756
- await withConnectorsSetup(opts, async ({ client, signal }) => {
47757
- const fields = { ...parsed2, org_id: explicitOrg ?? client.getCurrentOrgId() };
47758
- try {
47759
- const t = await createTask(client, fields, { signal });
47760
- if (opts.json === true) writeJSONIndented(process.stdout, t);
47761
- else writeTask(process.stdout, t);
47762
- } catch (err) {
47763
- throw explainTaskError(err);
47764
- }
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
+ });
47765
47928
  });
47766
47929
  }
47767
47930
  );
47768
- withFlags(
47769
- tasks.command("list").description("List the org's scheduled tasks").option("--org-id <id>", "org id (defaults to the JWT org)")
47770
- ).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) => {
47771
47933
  await withConnectorsSetup(opts, async ({ client, signal }) => {
47772
- const orgId = opts.orgId !== void 0 ? parsePositiveIntArg2(opts.orgId, "--org-id") : void 0;
47773
- try {
47774
- const list28 = await listTasks(client, orgId, { signal });
47775
- if (opts.json === true) writeJSONIndented(process.stdout, list28);
47776
- else writeTaskList(process.stdout, list28);
47777
- } catch (err) {
47778
- throw explainTaskError(err);
47779
- }
47934
+ await runPollDisable({
47935
+ client,
47936
+ filebox,
47937
+ asJson: opts.json === true,
47938
+ signal,
47939
+ out: process.stdout
47940
+ });
47780
47941
  });
47781
47942
  });
47782
- withFlags(
47783
- tasks.command("get").description("Show a scheduled task").argument("<task>", "task id or name")
47784
- ).action(
47785
- async (task, opts) => onTask(
47786
- task,
47787
- opts,
47788
- (c, id, signal) => getTask(c, id, { signal }),
47789
- (o, r) => writeTask(o, r)
47790
- )
47791
- );
47792
- withFlags(
47793
- addScheduleFlags(
47794
- 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"
47795
47972
  )
47796
- ).action(
47797
- async (task, opts) => {
47798
- if (opts.enable === true && opts.disable === true)
47799
- throw new Error("give --enable or --disable, not both");
47800
- const fields = parseScheduleFlags(opts);
47801
- if (opts.enable === true) fields.enabled = true;
47802
- if (opts.disable === true) fields.enabled = false;
47803
- if (Object.keys(fields).length === 0)
47804
- throw new Error("nothing to change: give at least one flag");
47805
- await onTask(
47806
- task,
47807
- opts,
47808
- (c, id, signal) => updateTask(c, id, fields, { signal }),
47809
- (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
47810
47979
  );
47811
47980
  }
47812
- );
47813
- withFlags(
47814
- tasks.command("delete").description("Delete a scheduled task and its run history").argument("<task>", "task id or name")
47815
- ).action(
47816
- async (task, opts) => onTask(
47817
- task,
47818
- opts,
47819
- async (c, id, signal) => {
47820
- await deleteTask(c, id, { signal });
47821
- return { deleted: id };
47822
- },
47823
- (o, r) => o.write(`Deleted scheduled task ${r.deleted}
47824
- `)
47825
- )
47826
- );
47827
- withFlags(
47828
- tasks.command("run").description("Run a task now (does not move its schedule)").argument("<task>", "task id or name")
47829
- ).action(
47830
- async (task, opts) => onTask(
47831
- task,
47832
- opts,
47833
- (c, id, signal) => runTask(c, id, { signal }),
47834
- (o, r) => {
47835
- const run = r;
47836
- o.write(`Queued run ${run.id} (${run.status}); see: dr connectors tasks runs ${task}
47837
- `);
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
+ );
47838
48007
  }
47839
- )
47840
- );
47841
- withFlags(
47842
- tasks.command("pause").description("Pause a scheduled task").argument("<task>", "task id or name")
47843
- ).action(
47844
- async (task, opts) => onTask(
47845
- task,
47846
- opts,
47847
- (c, id, signal) => pauseTask(c, id, { signal }),
47848
- (o, r) => writeTask(o, r)
47849
- )
47850
- );
47851
- withFlags(
47852
- tasks.command("resume").description("Resume a paused task (clears its failure count, schedules the next run)").argument("<task>", "task id or name")
47853
- ).action(
47854
- async (task, opts) => onTask(
47855
- task,
47856
- opts,
47857
- (c, id, signal) => resumeTask(c, id, { signal }),
47858
- (o, r) => writeTask(o, r)
47859
- )
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
+ }
47860
48023
  );
47861
- withFlags(
47862
- 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")
47863
- ).action(async (task, opts) => {
47864
- const limit = parsePositiveIntArg2(opts.limit, "--limit");
47865
- if (limit > 200) throw new Error("invalid --limit: at most 200");
47866
- await onTask(
47867
- task,
47868
- opts,
47869
- (c, id, signal) => listRuns(c, id, limit, { signal }),
47870
- (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
47871
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
+ });
47872
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");
47873
48110
  }
47874
48111
 
47875
48112
  // src/commands/connectors.ts
@@ -48530,6 +48767,7 @@ ${TLS_VPN_HELP}`);
48530
48767
  registerDevFetch(connectors);
48531
48768
  registerConnectorsPoll(connectors);
48532
48769
  registerConnectorsTasks(connectors);
48770
+ registerConnectorsAws(connectors);
48533
48771
  }
48534
48772
 
48535
48773
  // src/types/dashboards.ts
@@ -54001,13 +54239,13 @@ async function runReport(cfg) {
54001
54239
  function collectFile(value, previous) {
54002
54240
  return previous.concat([value]);
54003
54241
  }
54004
- async function withOnboardingClient(opts, run, setupOptions = {}) {
54242
+ async function withOnboardingClient(opts, run2, setupOptions = {}) {
54005
54243
  const { serverKey, host } = resolveOnboardingTarget(opts.server);
54006
54244
  const callOptions = {};
54007
54245
  if (setupOptions.timeoutMs !== void 0) callOptions.timeoutMs = setupOptions.timeoutMs;
54008
54246
  const setup = await setupCall(serverKey, opts.account, callOptions);
54009
54247
  try {
54010
- await run(setup.client, host, setup.signal);
54248
+ await run2(setup.client, host, setup.signal);
54011
54249
  } finally {
54012
54250
  setup.cleanup();
54013
54251
  }
@@ -69438,8 +69676,8 @@ async function runExtractFromFiles(cfg) {
69438
69676
  const p = await preflightFile(f);
69439
69677
  parts.push({ blob: p.blob, filename: p.filename, sourcePath: f });
69440
69678
  }
69441
- const run = cfg.useAsync === false ? await extractSync(cfg, parts) : await extractAsync(cfg, parts);
69442
- 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;
69443
69681
  await emitExtractionDraft(cfg, allResults, sourceFiles, outcomes, abortedMidRun);
69444
69682
  }
69445
69683
  async function extractAsync(cfg, parts) {
@@ -71557,8 +71795,8 @@ function querySuffix(domain) {
71557
71795
  if (domain === void 0 || domain === "") return "";
71558
71796
  return `?domain=${encodeURIComponent(domain)}`;
71559
71797
  }
71560
- function required(out, call2) {
71561
- 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`);
71562
71800
  return out;
71563
71801
  }
71564
71802
 
@@ -73435,6 +73673,175 @@ function registerPalettesCommand(root) {
73435
73673
  addAccountFlag(createCmd);
73436
73674
  }
73437
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
+
73438
73845
  // src/commands/appSpace.ts
73439
73846
  import { buildRootCommand as buildAppSpaceRoot } from "@datarails-ai/dr-appspace-cli/dist/cli.js";
73440
73847
  function registerAppSpaceCommand(root) {
@@ -74048,6 +74455,7 @@ function buildRootCommand() {
74048
74455
  registerMultiProductCommand(root);
74049
74456
  registerDeskCommand(root);
74050
74457
  registerPalettesCommand(root);
74458
+ registerCalendarsCommand(root);
74051
74459
  registerAppSpaceCommand(root);
74052
74460
  return root;
74053
74461
  }