@walkeros/cli 4.6.0-next-1788817472881 → 4.6.0

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/CHANGELOG.md CHANGED
@@ -1,12 +1,12 @@
1
1
  # @walkeros/cli
2
2
 
3
- ## 4.6.0-next-1788817472881
3
+ ## 4.6.0
4
4
 
5
5
  ### Minor Changes
6
6
 
7
- - 23e9034: `walkeros login` now uses the standard device authorization grant and
8
- refreshes its session automatically. Existing tokens keep working until they
9
- expire; run `walkeros login` once to switch.
7
+ - 23e9034: `walkeros auth login` now uses the standard device authorization
8
+ grant and refreshes its session automatically. Existing tokens keep working
9
+ until they expire; run `walkeros auth login` once to switch.
10
10
 
11
11
  Breaking: `getAuthHeaders` is async, and it rejects when the session needs a
12
12
  refresh that cannot be carried out rather than quietly returning no header.
@@ -24,17 +24,26 @@
24
24
  spikes, and intermittent 5xx responses. Error logs now show the error's
25
25
  message, name and status code in CLI output, and no longer include event
26
26
  payloads.
27
+ - fd5949e: `fetchHealth` and `compareContract` accept an optional base URL, so a
28
+ caller that is not the local CLI can probe its own backend instead of the one
29
+ resolved from `WALKEROS_APP_URL` and the CLI config file. Omitting it keeps
30
+ today's resolution.
31
+
32
+ `diagnostics` passes the app URL it reports, so the contract verdict and
33
+ `appUrl.resolved` always describe the same backend. A hosted MCP no longer
34
+ probes production while naming its own deployment.
35
+
27
36
  - 403ff6c: The MCP server carries `hub_manage`, which reads a flow's release
28
37
  history, its rationale and the threads on it, and a read-only `frame_manage`,
29
38
  which reads the frames of a measurement plan. The CLI gains the matching
30
39
  programmatic calls. `ToolClient` gains eleven required methods, so a custom
31
40
  implementation of that interface must add them.
32
41
  - Updated dependencies [8802281]
33
- - @walkeros/collector@4.6.0-next-1788817472881
34
- - @walkeros/server-core@4.6.0-next-1788817472881
35
- - @walkeros/core@4.6.0-next-1788817472881
36
- - @walkeros/server-destination-api@4.6.0-next-1788817472881
37
- - @walkeros/transformer-validate@4.6.0-next-1788817472881
42
+ - @walkeros/collector@4.6.0
43
+ - @walkeros/server-core@4.6.0
44
+ - @walkeros/core@4.6.0
45
+ - @walkeros/server-destination-api@4.6.0
46
+ - @walkeros/transformer-validate@4.6.0
38
47
 
39
48
  ## 4.5.0
40
49
 
package/dist/cli.js CHANGED
@@ -1378,6 +1378,7 @@ import {
1378
1378
  chmodSync,
1379
1379
  renameSync
1380
1380
  } from "fs";
1381
+ import { randomBytes } from "crypto";
1381
1382
  import { join as join2 } from "path";
1382
1383
  import { homedir } from "os";
1383
1384
  function getConfigDir() {
@@ -1401,10 +1402,18 @@ function replaceConfigFile(config) {
1401
1402
  const dir = getConfigDir();
1402
1403
  mkdirSync(dir, { recursive: true, mode: 448 });
1403
1404
  const configPath = getConfigPath();
1404
- const tempPath = `${configPath}.tmp`;
1405
- writeFileSync(tempPath, JSON.stringify(config, null, 2), { mode: 384 });
1406
- chmodSync(tempPath, 384);
1407
- renameSync(tempPath, configPath);
1405
+ const tempPath = `${configPath}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
1406
+ try {
1407
+ writeFileSync(tempPath, JSON.stringify(config, null, 2), { mode: 384 });
1408
+ chmodSync(tempPath, 384);
1409
+ renameSync(tempPath, configPath);
1410
+ } catch (error) {
1411
+ try {
1412
+ unlinkSync(tempPath);
1413
+ } catch {
1414
+ }
1415
+ throw error;
1416
+ }
1408
1417
  }
1409
1418
  function writeConfig(config) {
1410
1419
  replaceConfigFile({ ...readConfig() ?? {}, ...config });
@@ -1720,6 +1729,28 @@ var init_tmp = __esm({
1720
1729
  }
1721
1730
  });
1722
1731
 
1732
+ // src/lib/secure-url.ts
1733
+ function isLoopback(hostname) {
1734
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
1735
+ }
1736
+ function requireSecureUrl(url) {
1737
+ let parsed;
1738
+ try {
1739
+ parsed = new URL(url);
1740
+ } catch {
1741
+ return url;
1742
+ }
1743
+ if (parsed.protocol !== "http:" || isLoopback(parsed.hostname)) return url;
1744
+ throw new Error(
1745
+ `Refusing to send walkerOS credentials over plain http to ${url}. Use https, or localhost / 127.0.0.1 / [::1] for local development.`
1746
+ );
1747
+ }
1748
+ var init_secure_url = __esm({
1749
+ "src/lib/secure-url.ts"() {
1750
+ "use strict";
1751
+ }
1752
+ });
1753
+
1723
1754
  // src/lib/config-lock.ts
1724
1755
  import { closeSync, mkdirSync as mkdirSync2, openSync, statSync, unlinkSync as unlinkSync2 } from "fs";
1725
1756
  function getConfigLockPath() {
@@ -1806,10 +1837,11 @@ function describe(response, body) {
1806
1837
  return `HTTP ${response.status}`;
1807
1838
  }
1808
1839
  function post(fetchFn, url, form, signal) {
1809
- return fetchFn(url, {
1840
+ return fetchFn(requireSecureUrl(url), {
1810
1841
  method: "POST",
1811
1842
  headers: { ...FORM_HEADERS },
1812
1843
  body: new URLSearchParams(form).toString(),
1844
+ redirect: "error",
1813
1845
  ...signal ? { signal } : {}
1814
1846
  });
1815
1847
  }
@@ -1834,7 +1866,8 @@ async function startDeviceAuthorization(appUrl, fetchFn = globalThis.fetch) {
1834
1866
  // RFC 8707. The token comes back bound to the API, so a leaked CLI token
1835
1867
  // cannot be replayed against the MCP resource.
1836
1868
  resource: `${appUrl}/api`
1837
- }
1869
+ },
1870
+ AbortSignal.timeout(DEVICE_AUTHORIZATION_TIMEOUT_MS)
1838
1871
  );
1839
1872
  const body = await readJson(response);
1840
1873
  if (!response.ok) throw new Error(describe(response, body));
@@ -1924,15 +1957,17 @@ async function revokeRefreshToken(appUrl, refreshToken, fetchFn = globalThis.fet
1924
1957
  } catch {
1925
1958
  }
1926
1959
  }
1927
- var CLI_CLIENT_ID, CLI_SCOPE, DEVICE_CODE_GRANT, REFRESH_TIMEOUT_MS, REVOKE_TIMEOUT_MS, FORM_HEADERS, DeviceAuthorizationSchema, TokenSchema, ErrorSchema;
1960
+ var CLI_CLIENT_ID, CLI_SCOPE, DEVICE_CODE_GRANT, REFRESH_TIMEOUT_MS, REVOKE_TIMEOUT_MS, DEVICE_AUTHORIZATION_TIMEOUT_MS, FORM_HEADERS, DeviceAuthorizationSchema, TokenSchema, ErrorSchema;
1928
1961
  var init_oauth_client = __esm({
1929
1962
  "src/core/oauth-client.ts"() {
1930
1963
  "use strict";
1964
+ init_secure_url();
1931
1965
  CLI_CLIENT_ID = "walkeros-cli";
1932
1966
  CLI_SCOPE = "read write offline_access";
1933
1967
  DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
1934
1968
  REFRESH_TIMEOUT_MS = 1e4;
1935
1969
  REVOKE_TIMEOUT_MS = 5e3;
1970
+ DEVICE_AUTHORIZATION_TIMEOUT_MS = 1e4;
1936
1971
  FORM_HEADERS = {
1937
1972
  "Content-Type": "application/x-www-form-urlencoded",
1938
1973
  Accept: "application/json"
@@ -1962,7 +1997,7 @@ function noticeLegacyToken() {
1962
1997
  if (legacyNoticeShown) return;
1963
1998
  legacyNoticeShown = true;
1964
1999
  process.stderr.write(
1965
- "walkerOS: using a static token from your config. Run `walkeros login` to switch to a session that refreshes automatically.\n"
2000
+ "walkerOS: using a static token from your config. Run `walkeros auth login` to switch to a session that refreshes automatically.\n"
1966
2001
  );
1967
2002
  }
1968
2003
  function isFresh(config, nowMs) {
@@ -2066,6 +2101,7 @@ function buildHeaders(token, headers) {
2066
2101
  async function apiFetch(path20, init) {
2067
2102
  const baseUrl = resolveAppUrl();
2068
2103
  const token = await resolveAccessToken();
2104
+ if (token) requireSecureUrl(baseUrl);
2069
2105
  return fetch(`${baseUrl}${path20}`, {
2070
2106
  ...init,
2071
2107
  headers: buildHeaders(token, init?.headers)
@@ -2085,6 +2121,7 @@ var init_http = __esm({
2085
2121
  "src/core/http.ts"() {
2086
2122
  "use strict";
2087
2123
  init_config_file();
2124
+ init_secure_url();
2088
2125
  init_auth();
2089
2126
  init_client_context();
2090
2127
  }
@@ -2110,6 +2147,7 @@ function isAppOrigin(url) {
2110
2147
  }
2111
2148
  async function fetchContentString(url) {
2112
2149
  const token = isAppOrigin(url) ? await resolveAccessToken() : null;
2150
+ if (token) requireSecureUrl(url);
2113
2151
  const response = await fetch(url, {
2114
2152
  headers: mergeAuthHeaders(token)
2115
2153
  });
@@ -2184,6 +2222,7 @@ var init_utils = __esm({
2184
2222
  init_http();
2185
2223
  init_auth();
2186
2224
  init_config_file();
2225
+ init_secure_url();
2187
2226
  }
2188
2227
  });
2189
2228
 
@@ -6234,7 +6273,7 @@ var init_contract = __esm({
6234
6273
  "src/core/contract.ts"() {
6235
6274
  "use strict";
6236
6275
  init_config_file();
6237
- bakedContractVersion = true ? "4.5.1" : PLACEHOLDER;
6276
+ bakedContractVersion = true ? "4.6.1" : PLACEHOLDER;
6238
6277
  }
6239
6278
  });
6240
6279
 
@@ -6268,7 +6307,8 @@ function createApiClient() {
6268
6307
  async onRequest({ request }) {
6269
6308
  const token = await resolveAccessToken();
6270
6309
  if (!token)
6271
- throw new Error("Not authenticated. Run `walkeros login` first.");
6310
+ throw new Error("Not authenticated. Run `walkeros auth login` first.");
6311
+ requireSecureUrl(request.url);
6272
6312
  request.headers.set("Authorization", `Bearer ${token}`);
6273
6313
  return request;
6274
6314
  },
@@ -6287,6 +6327,7 @@ var init_api_client = __esm({
6287
6327
  "use strict";
6288
6328
  init_auth();
6289
6329
  init_config_file();
6330
+ init_secure_url();
6290
6331
  init_client_context();
6291
6332
  init_contract();
6292
6333
  driftWarned = false;
@@ -8162,7 +8203,7 @@ function Jt(e5, t4, n5) {
8162
8203
  if (!s5 || !r5) throw new bt("Event name is invalid");
8163
8204
  const { timestamp: a4 = Date.now(), name: i5 = `${s5} ${r5}`, data: c2 = {}, context: u3 = {}, globals: d2 = {}, custom: l3 = {}, user: f2 = e5.user, nested: p3 = [], consent: g3 = e5.consent, id: m3 = Pe(), trigger: h2 = "", entity: v3 = s5, action: y3 = r5, timing: w3 = 0, source: b3 = { type: "collector", schema: "4" } } = t4, k3 = b3 ?? { type: "collector", schema: "4" }, I2 = k3.count ?? (e5.count += 1), C3 = k3.trace ?? n5?._meta.trace ?? e5.trace, E2 = { ...k3, count: I2 };
8164
8205
  void 0 !== C3 && (E2.trace = C3);
8165
- const S2 = e5.name ?? k3.platform ?? "default", q3 = e5.release ?? "4.6.0-next-1788817472881";
8206
+ const S2 = e5.name ?? k3.platform ?? "default", q3 = e5.release ?? "4.6.0";
8166
8207
  E2.release = { ...k3.release, [S2]: q3 };
8167
8208
  return { name: i5, data: c2, context: u3, globals: ce(e5.globals, d2), custom: l3, user: f2, nested: p3, consent: g3, id: m3, trigger: h2, entity: v3, action: y3, timestamp: a4, timing: w3, source: E2 };
8168
8209
  }
@@ -10819,7 +10860,7 @@ async function fetchConfig(options) {
10819
10860
  init_cache();
10820
10861
 
10821
10862
  // src/runtime/heartbeat.ts
10822
- import { randomBytes } from "crypto";
10863
+ import { randomBytes as randomBytes2 } from "crypto";
10823
10864
  init_http();
10824
10865
  init_dist();
10825
10866
 
@@ -10885,7 +10926,7 @@ function snapshotDestinations(status) {
10885
10926
  }
10886
10927
  return result;
10887
10928
  }
10888
- var instanceId = randomBytes(8).toString("hex");
10929
+ var instanceId = randomBytes2(8).toString("hex");
10889
10930
  function getInstanceId() {
10890
10931
  return instanceId;
10891
10932
  }
@@ -14410,7 +14451,7 @@ function validateMapping(input) {
14410
14451
  // src/commands/validate/validators/entry.ts
14411
14452
  init_dist();
14412
14453
  import Ajv from "ajv";
14413
- var CLIENT_HEADER = "walkeros-cli/4.6.0-next-1788817472881";
14454
+ var CLIENT_HEADER = "walkeros-cli/4.6.0";
14414
14455
  var SECTIONS = ["destinations", "sources", "transformers"];
14415
14456
  function resolveEntry(path20, flowConfig) {
14416
14457
  const flows = flowConfig.flows;
@@ -14703,12 +14744,14 @@ async function countEntries(dir) {
14703
14744
  init_cli_logger();
14704
14745
  init_oauth_client();
14705
14746
  init_config_file();
14747
+ init_secure_url();
14706
14748
  import { z as z5 } from "zod";
14707
14749
  var POLL_TIMEOUT_BUFFER_MS = 5e3;
14708
14750
  var DEFAULT_POLL_INTERVAL_MS = 5e3;
14709
14751
  var MIN_SERVER_POLL_INTERVAL_MS = 1e3;
14710
14752
  var DEFAULT_POLL_TIMEOUT_MS = 9e5;
14711
14753
  var SLOW_DOWN_STEP_MS = 5e3;
14754
+ var WHOAMI_TIMEOUT_MS = 1e4;
14712
14755
  var TIMED_OUT = "Authorization timed out. Please try again.";
14713
14756
  var WhoamiSchema = z5.object({ email: z5.string().min(1) });
14714
14757
  async function openInBrowser(url) {
@@ -14745,7 +14788,8 @@ async function loginCommand(options) {
14745
14788
  async function fetchEmail(appUrl, accessToken, fetchFn) {
14746
14789
  try {
14747
14790
  const response = await fetchFn(`${appUrl}/api/auth/whoami`, {
14748
- headers: { Authorization: `Bearer ${accessToken}` }
14791
+ headers: { Authorization: `Bearer ${accessToken}` },
14792
+ signal: AbortSignal.timeout(WHOAMI_TIMEOUT_MS)
14749
14793
  });
14750
14794
  if (!response.ok) return void 0;
14751
14795
  const parsed = WhoamiSchema.safeParse(await response.json());
@@ -14769,7 +14813,7 @@ async function persistSession(appUrl, tokens, fetchFn) {
14769
14813
  }
14770
14814
  async function completeDeviceLogin(deviceCode, options = {}) {
14771
14815
  const fetchFn = options.fetch ?? globalThis.fetch;
14772
- const appUrl = options.url || resolveAppUrl();
14816
+ const appUrl = requireSecureUrl(options.url || resolveAppUrl());
14773
14817
  const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS);
14774
14818
  let intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
14775
14819
  let attempts = 0;
@@ -14802,7 +14846,7 @@ async function completeDeviceLogin(deviceCode, options = {}) {
14802
14846
  }
14803
14847
  async function login(options = {}) {
14804
14848
  const fetchFn = options.fetch ?? globalThis.fetch;
14805
- const appUrl = options.url || resolveAppUrl();
14849
+ const appUrl = requireSecureUrl(options.url || resolveAppUrl());
14806
14850
  let authorization;
14807
14851
  try {
14808
14852
  authorization = await startDeviceAuthorization(appUrl, fetchFn);
@@ -14858,10 +14902,14 @@ init_oauth_client();
14858
14902
  init_config_file();
14859
14903
  async function logoutCommand(options) {
14860
14904
  const logger = createCLILogger(options);
14861
- const { deleted } = await logout();
14905
+ const { deleted, superseded } = await logout();
14862
14906
  const configPath = getConfigPath();
14863
14907
  if (options.json) {
14864
- logger.json({ success: true, deleted });
14908
+ logger.json({ success: true, deleted, superseded });
14909
+ } else if (superseded) {
14910
+ logger.info(
14911
+ "A newer session was stored while logging out, and was kept. Run `walkeros auth logout` again to remove it."
14912
+ );
14865
14913
  } else if (deleted) {
14866
14914
  logger.info(`Logged out. Session removed from ${configPath}`);
14867
14915
  } else {
@@ -14869,13 +14917,18 @@ async function logoutCommand(options) {
14869
14917
  }
14870
14918
  process.exit(0);
14871
14919
  }
14920
+ function sameSession(before, after) {
14921
+ return before?.accessToken === after.accessToken && before?.refreshToken === after.refreshToken && before?.token === after.token;
14922
+ }
14872
14923
  async function logout() {
14873
- const config = readConfig();
14874
- const appUrl = resolveAppUrl();
14875
- if (config?.refreshToken) {
14876
- await revokeRefreshToken(appUrl, config.refreshToken);
14877
- }
14878
- return { deleted: deleteConfig() };
14924
+ const before = readConfig();
14925
+ if (before?.refreshToken) {
14926
+ await revokeRefreshToken(resolveAppUrl(), before.refreshToken);
14927
+ }
14928
+ const after = readConfig();
14929
+ if (after && !sameSession(before, after))
14930
+ return { deleted: false, superseded: true };
14931
+ return { deleted: deleteConfig(), superseded: false };
14879
14932
  }
14880
14933
 
14881
14934
  // src/commands/auth/index.ts
@@ -19,6 +19,8 @@ services:
19
19
  - ./flow.json:/app/flow.json
20
20
  environment:
21
21
  - BUNDLE=/app/flow.json
22
+ # An automation token (wos_pat_...) from Account, Automation tokens, or a
23
+ # bound runner token (wos_run_...) from a flow's self-hosted deploy tab.
22
24
  - WALKEROS_TOKEN=${WALKEROS_TOKEN}
23
25
  - PROJECT_ID=${PROJECT_ID}
24
26
  - PORT=8080