@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 +18 -9
- package/dist/cli.js +78 -25
- package/dist/examples/docker-compose.runner.yml +2 -0
- package/dist/index.d.ts +669 -489
- package/dist/index.js +82 -28
- package/dist/index.js.map +1 -1
- package/examples/docker-compose.runner.yml +2 -0
- package/openapi/spec.json +1051 -731
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# @walkeros/cli
|
|
2
2
|
|
|
3
|
-
## 4.6.0
|
|
3
|
+
## 4.6.0
|
|
4
4
|
|
|
5
5
|
### Minor Changes
|
|
6
6
|
|
|
7
|
-
- 23e9034: `walkeros login` now uses the standard device authorization
|
|
8
|
-
refreshes its session automatically. Existing tokens keep working
|
|
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
|
|
34
|
-
- @walkeros/server-core@4.6.0
|
|
35
|
-
- @walkeros/core@4.6.0
|
|
36
|
-
- @walkeros/server-destination-api@4.6.0
|
|
37
|
-
- @walkeros/transformer-validate@4.6.0
|
|
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
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
14874
|
-
|
|
14875
|
-
|
|
14876
|
-
|
|
14877
|
-
|
|
14878
|
-
|
|
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
|