@alphafox/cli 0.1.5 → 0.2.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/dist/auth/browser-login.d.ts +30 -0
- package/dist/auth/browser-login.js +193 -0
- package/dist/auth/loopback-callback.d.ts +32 -0
- package/dist/auth/loopback-callback.js +175 -0
- package/dist/auth/open-browser.d.ts +15 -0
- package/dist/auth/open-browser.js +54 -0
- package/dist/auth/refresh.d.ts +27 -2
- package/dist/auth/refresh.js +52 -15
- package/dist/catalog/allowlist.d.ts +20 -4
- package/dist/catalog/allowlist.js +126 -25
- package/dist/catalog/command-tree.d.ts +34 -0
- package/dist/catalog/command-tree.js +117 -0
- package/dist/catalog/compatibility.d.ts +23 -0
- package/dist/catalog/compatibility.js +58 -0
- package/dist/catalog/generated/registry.json +6137 -0
- package/dist/catalog/generated/schemas.json +31036 -0
- package/dist/catalog/operations.d.ts +76 -3
- package/dist/catalog/operations.js +87 -213
- package/dist/commands/run.js +171 -153
- package/dist/config/profiles.js +3 -3
- package/dist/envelope.d.ts +3 -0
- package/dist/envelope.js +43 -3
- package/dist/http/client.js +18 -15
- package/dist/index.d.ts +12 -5
- package/dist/index.js +32 -1
- package/dist/keychain/linux-secret-service.d.ts +11 -0
- package/dist/keychain/linux-secret-service.js +93 -0
- package/dist/keychain/store.d.ts +20 -1
- package/dist/keychain/store.js +94 -6
- package/dist/keychain/windows-credential.d.ts +13 -0
- package/dist/keychain/windows-credential.js +176 -0
- package/dist/safety/confirmation.d.ts +10 -3
- package/dist/safety/confirmation.js +27 -4
- package/dist/version.d.ts +2 -2
- package/dist/version.js +3 -2
- package/docs/agents/domain.md +51 -0
- package/docs/agents/issue-tracker.md +156 -0
- package/docs/agents/triage-labels.md +18 -0
- package/docs/e2e-staging.md +76 -6
- package/docs/release-supply-chain.md +99 -26
- package/package.json +4 -2
- package/skills/account/SKILL.md +8 -6
- package/skills/admin/SKILL.md +9 -4
- package/skills/alphafox-shared/SKILL.md +23 -14
- package/skills/auth/SKILL.md +21 -6
- package/skills/exchange/SKILL.md +10 -3
- package/skills/market/SKILL.md +9 -4
- package/skills/notification/SKILL.md +8 -3
- package/skills/strategy/SKILL.md +15 -9
- package/skills/trading/SKILL.md +15 -5
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Raw API allowlist
|
|
3
|
-
*
|
|
2
|
+
* Raw API allowlist from the generated Operation Registry.
|
|
3
|
+
* Finite set: infra catalog paths + CLI-included operation facade paths.
|
|
4
|
+
* Unknown `/api/v1/*` paths are denied — raw API cannot bypass the catalog.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Infra paths always allowed (exact, except `/api/v1/operations/*`).
|
|
8
|
+
* Product routes must match a registry facadePath — no prefix smuggling.
|
|
9
|
+
*/
|
|
10
|
+
export declare const FACILITY_ALWAYS_ALLOW: readonly ["/api/v1/meta", "/api/v1/openapi.json", "/api/v1/openapi", "/api/v1/operations"];
|
|
11
|
+
/**
|
|
12
|
+
* Normalize API paths for allowlist decisions:
|
|
13
|
+
* strip query/hash, collapse //, decode %2e-style segments, resolve `.`/`..`.
|
|
14
|
+
* Escaping above `/` via `..` is clamped at root (POSIX-style).
|
|
4
15
|
*/
|
|
5
|
-
/** MVP + core facade paths always allowed; full registry embedded for drift tests. */
|
|
6
|
-
export declare const FACILITY_ALWAYS_ALLOW: readonly ["/api/v1/meta", "/api/v1/me", "/api/v1/operations", "/api/v1/openapi.json", "/api/v1/trading/strategy-definitions", "/api/v1/exchange-connectors", "/api/v1/trading/traders", "/api/v1/chats", "/api/v1/backtests"];
|
|
7
16
|
export declare function normalizeApiPath(path: string): string;
|
|
8
17
|
export declare function isInternalDisallowedPath(path: string): boolean;
|
|
18
|
+
/** Match OpenAPI-style templates: /api/v1/backtests/{backtestId}/cancel */
|
|
19
|
+
export declare function pathTemplateMatches(template: string, actual: string, catchAll?: boolean): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Finite allow set: infra catalog paths + generated operation facade paths.
|
|
22
|
+
* Unknown `/api/v1/*` paths are denied.
|
|
23
|
+
* Matching always uses the segment-resolved path (no `..` prefix smuggling).
|
|
24
|
+
*/
|
|
9
25
|
export declare function isFacadeAllowlistedPath(path: string, extraAllow?: readonly string[]): boolean;
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Raw API allowlist
|
|
4
|
-
*
|
|
3
|
+
* Raw API allowlist from the generated Operation Registry.
|
|
4
|
+
* Finite set: infra catalog paths + CLI-included operation facade paths.
|
|
5
|
+
* Unknown `/api/v1/*` paths are denied — raw API cannot bypass the catalog.
|
|
5
6
|
*/
|
|
6
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
8
|
exports.FACILITY_ALWAYS_ALLOW = void 0;
|
|
8
9
|
exports.normalizeApiPath = normalizeApiPath;
|
|
9
10
|
exports.isInternalDisallowedPath = isInternalDisallowedPath;
|
|
11
|
+
exports.pathTemplateMatches = pathTemplateMatches;
|
|
10
12
|
exports.isFacadeAllowlistedPath = isFacadeAllowlistedPath;
|
|
13
|
+
const operations_1 = require("./operations");
|
|
11
14
|
const INTERNAL_PREFIXES = [
|
|
12
15
|
"/backend",
|
|
13
16
|
"/control-plane",
|
|
@@ -16,53 +19,151 @@ const INTERNAL_PREFIXES = [
|
|
|
16
19
|
"/api/control-plane",
|
|
17
20
|
"/api/signal-center",
|
|
18
21
|
];
|
|
19
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Infra paths always allowed (exact, except `/api/v1/operations/*`).
|
|
24
|
+
* Product routes must match a registry facadePath — no prefix smuggling.
|
|
25
|
+
*/
|
|
20
26
|
exports.FACILITY_ALWAYS_ALLOW = [
|
|
21
27
|
"/api/v1/meta",
|
|
22
|
-
"/api/v1/me",
|
|
23
|
-
"/api/v1/operations",
|
|
24
28
|
"/api/v1/openapi.json",
|
|
25
|
-
"/api/v1/
|
|
26
|
-
"/api/v1/
|
|
27
|
-
"/api/v1/trading/traders",
|
|
28
|
-
"/api/v1/chats",
|
|
29
|
-
"/api/v1/backtests",
|
|
29
|
+
"/api/v1/openapi",
|
|
30
|
+
"/api/v1/operations",
|
|
30
31
|
];
|
|
32
|
+
/**
|
|
33
|
+
* Decode percent-encoded path segments (up to twice for double-encoding)
|
|
34
|
+
* without turning encoded slashes into separators mid-segment incorrectly:
|
|
35
|
+
* each segment is decoded independently so %2F stays inside a segment name.
|
|
36
|
+
*/
|
|
37
|
+
function decodePathSegments(path) {
|
|
38
|
+
let current = path;
|
|
39
|
+
for (let pass = 0; pass < 2; pass += 1) {
|
|
40
|
+
const next = current
|
|
41
|
+
.split("/")
|
|
42
|
+
.map((seg) => {
|
|
43
|
+
if (!seg.includes("%"))
|
|
44
|
+
return seg;
|
|
45
|
+
try {
|
|
46
|
+
return decodeURIComponent(seg);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return seg;
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
.join("/");
|
|
53
|
+
if (next === current)
|
|
54
|
+
break;
|
|
55
|
+
current = next;
|
|
56
|
+
}
|
|
57
|
+
return current;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Normalize API paths for allowlist decisions:
|
|
61
|
+
* strip query/hash, collapse //, decode %2e-style segments, resolve `.`/`..`.
|
|
62
|
+
* Escaping above `/` via `..` is clamped at root (POSIX-style).
|
|
63
|
+
*/
|
|
31
64
|
function normalizeApiPath(path) {
|
|
32
65
|
const trimmed = path.trim();
|
|
33
66
|
if (!trimmed)
|
|
34
67
|
return "/";
|
|
35
|
-
|
|
36
|
-
|
|
68
|
+
let raw = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
69
|
+
raw = (raw.split("?")[0] ?? raw).split("#")[0];
|
|
70
|
+
raw = decodePathSegments(raw);
|
|
71
|
+
raw = raw.replace(/\/{2,}/g, "/");
|
|
72
|
+
const resolved = [];
|
|
73
|
+
for (const seg of raw.split("/")) {
|
|
74
|
+
if (seg === "" || seg === ".") {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (seg === "..") {
|
|
78
|
+
if (resolved.length > 0) {
|
|
79
|
+
resolved.pop();
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (seg.includes("\0") || seg.includes("\\")) {
|
|
84
|
+
return "/";
|
|
85
|
+
}
|
|
86
|
+
resolved.push(seg);
|
|
87
|
+
}
|
|
88
|
+
return resolved.length === 0 ? "/" : `/${resolved.join("/")}`;
|
|
37
89
|
}
|
|
38
90
|
function isInternalDisallowedPath(path) {
|
|
39
91
|
const n = normalizeApiPath(path);
|
|
40
92
|
return INTERNAL_PREFIXES.some((p) => n === p || n.startsWith(`${p}/`) || n.includes(`${p}/`));
|
|
41
93
|
}
|
|
94
|
+
/** Match OpenAPI-style templates: /api/v1/backtests/{backtestId}/cancel */
|
|
95
|
+
function pathTemplateMatches(template, actual, catchAll = false) {
|
|
96
|
+
const t = normalizeApiPath(template).split("/").filter(Boolean);
|
|
97
|
+
const a = normalizeApiPath(actual).split("/").filter(Boolean);
|
|
98
|
+
if (catchAll) {
|
|
99
|
+
if (a.length < t.length)
|
|
100
|
+
return false;
|
|
101
|
+
for (let i = 0; i < t.length; i += 1) {
|
|
102
|
+
const seg = t[i];
|
|
103
|
+
if (seg.startsWith("{") && seg.endsWith("}"))
|
|
104
|
+
continue;
|
|
105
|
+
if (seg !== a[i])
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
if (t.length !== a.length)
|
|
111
|
+
return false;
|
|
112
|
+
for (let i = 0; i < t.length; i += 1) {
|
|
113
|
+
const seg = t[i];
|
|
114
|
+
if (seg.startsWith("{") && seg.endsWith("}"))
|
|
115
|
+
continue;
|
|
116
|
+
if (seg !== a[i])
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
function isInfraAllowlisted(n) {
|
|
122
|
+
if (n === "/api/v1/meta" ||
|
|
123
|
+
n === "/api/v1/openapi.json" ||
|
|
124
|
+
n === "/api/v1/openapi" ||
|
|
125
|
+
n === "/api/v1/operations" ||
|
|
126
|
+
n.startsWith("/api/v1/operations/")) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
function isCatalogPathMatch(n) {
|
|
132
|
+
for (const op of operations_1.CATALOG_OPERATIONS) {
|
|
133
|
+
if (pathTemplateMatches(op.path, n, Boolean(op.catchAll))) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
function isExtraAllowMatch(n, extraAllow) {
|
|
140
|
+
for (const a of extraAllow) {
|
|
141
|
+
if (n === a || n.startsWith(`${a}/`) || pathTemplateMatches(a, n)) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Finite allow set: infra catalog paths + generated operation facade paths.
|
|
149
|
+
* Unknown `/api/v1/*` paths are denied.
|
|
150
|
+
* Matching always uses the segment-resolved path (no `..` prefix smuggling).
|
|
151
|
+
*/
|
|
42
152
|
function isFacadeAllowlistedPath(path, extraAllow = []) {
|
|
43
153
|
const n = normalizeApiPath(path);
|
|
44
154
|
if (isInternalDisallowedPath(n)) {
|
|
45
155
|
return false;
|
|
46
156
|
}
|
|
47
|
-
if (!n.startsWith("/api/v1")) {
|
|
157
|
+
if (n !== "/api/v1" && !n.startsWith("/api/v1/")) {
|
|
48
158
|
return false;
|
|
49
159
|
}
|
|
50
|
-
|
|
51
|
-
for (const a of allow) {
|
|
52
|
-
if (n === a || n.startsWith(`${a}/`)) {
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
// templated backtests etc.
|
|
57
|
-
if (/^\/api\/v1\/backtests\/[^/]+(\/.*)?$/.test(n)) {
|
|
160
|
+
if (isInfraAllowlisted(n)) {
|
|
58
161
|
return true;
|
|
59
162
|
}
|
|
60
|
-
if (
|
|
163
|
+
if (isExtraAllowMatch(n, extraAllow)) {
|
|
61
164
|
return true;
|
|
62
165
|
}
|
|
63
|
-
if (
|
|
64
|
-
// Allow any /api/v1/* that is not internal — facade is the allowlist boundary.
|
|
65
|
-
// Internal services are never mounted under /api/v1.
|
|
166
|
+
if (isCatalogPathMatch(n)) {
|
|
66
167
|
return true;
|
|
67
168
|
}
|
|
68
169
|
return false;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed command tree derived from registry operationIds.
|
|
3
|
+
* Shortcuts may only compose operationIds — never a second schema catalog.
|
|
4
|
+
*/
|
|
5
|
+
import { type CatalogOperation } from "./operations";
|
|
6
|
+
export declare function splitCommandTokens(tokens: readonly string[]): {
|
|
7
|
+
readonly commandTokens: string[];
|
|
8
|
+
readonly flagArgs: string[];
|
|
9
|
+
readonly help: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function operationsWithPrefix(prefix: string): CatalogOperation[];
|
|
12
|
+
export type TypedResolution = {
|
|
13
|
+
readonly kind: "operation";
|
|
14
|
+
readonly operation: CatalogOperation;
|
|
15
|
+
readonly flagArgs: string[];
|
|
16
|
+
readonly help: boolean;
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: "help";
|
|
19
|
+
readonly prefix: string;
|
|
20
|
+
readonly operations: readonly CatalogOperation[];
|
|
21
|
+
readonly flagArgs: string[];
|
|
22
|
+
} | {
|
|
23
|
+
readonly kind: "ambiguous";
|
|
24
|
+
readonly prefix: string;
|
|
25
|
+
readonly candidates: readonly string[];
|
|
26
|
+
} | {
|
|
27
|
+
readonly kind: "missing";
|
|
28
|
+
readonly prefix: string;
|
|
29
|
+
};
|
|
30
|
+
export declare function resolveTypedCommand(tokens: readonly string[]): TypedResolution;
|
|
31
|
+
export declare function typedCommandExample(op: CatalogOperation): {
|
|
32
|
+
readonly typed: string;
|
|
33
|
+
readonly api: string;
|
|
34
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Typed command tree derived from registry operationIds.
|
|
4
|
+
* Shortcuts may only compose operationIds — never a second schema catalog.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.splitCommandTokens = splitCommandTokens;
|
|
8
|
+
exports.operationsWithPrefix = operationsWithPrefix;
|
|
9
|
+
exports.resolveTypedCommand = resolveTypedCommand;
|
|
10
|
+
exports.typedCommandExample = typedCommandExample;
|
|
11
|
+
const operations_1 = require("./operations");
|
|
12
|
+
function hyphenToUnderscore(token) {
|
|
13
|
+
return token.replace(/-/g, "_");
|
|
14
|
+
}
|
|
15
|
+
function splitCommandTokens(tokens) {
|
|
16
|
+
const commandTokens = [];
|
|
17
|
+
const flagArgs = [];
|
|
18
|
+
let help = false;
|
|
19
|
+
let inFlags = false;
|
|
20
|
+
for (const token of tokens) {
|
|
21
|
+
if (!inFlags && (token === "--help" || token === "-h")) {
|
|
22
|
+
help = true;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (!inFlags && token.startsWith("--")) {
|
|
26
|
+
inFlags = true;
|
|
27
|
+
}
|
|
28
|
+
if (inFlags) {
|
|
29
|
+
flagArgs.push(token);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
commandTokens.push(token);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { commandTokens, flagArgs, help };
|
|
36
|
+
}
|
|
37
|
+
function lookupId(id) {
|
|
38
|
+
return ((0, operations_1.findCatalogOperation)(id) ??
|
|
39
|
+
(0, operations_1.findCatalogOperation)(hyphenToUnderscore(id)));
|
|
40
|
+
}
|
|
41
|
+
function operationsWithPrefix(prefix) {
|
|
42
|
+
const norm = hyphenToUnderscore(prefix);
|
|
43
|
+
return operations_1.CATALOG_OPERATIONS.filter((op) => op.operationId === norm || op.operationId.startsWith(`${norm}.`));
|
|
44
|
+
}
|
|
45
|
+
function uniqueSuffixMatch(tokens) {
|
|
46
|
+
if (tokens.length < 2)
|
|
47
|
+
return [];
|
|
48
|
+
const domain = hyphenToUnderscore(tokens[0]);
|
|
49
|
+
const action = hyphenToUnderscore(tokens[tokens.length - 1]);
|
|
50
|
+
const middle = tokens.slice(1, -1).map(hyphenToUnderscore);
|
|
51
|
+
return operations_1.CATALOG_OPERATIONS.filter((op) => {
|
|
52
|
+
if (!op.operationId.startsWith(`${domain}.`))
|
|
53
|
+
return false;
|
|
54
|
+
if (!op.operationId.endsWith(`.${action}`) && op.operationId !== action) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
for (const part of middle) {
|
|
58
|
+
if (!op.operationId.includes(`.${part}.`) && !op.operationId.includes(`${part}.`)) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function resolveTypedCommand(tokens) {
|
|
66
|
+
const { commandTokens, flagArgs, help } = splitCommandTokens(tokens);
|
|
67
|
+
if (commandTokens.length === 0) {
|
|
68
|
+
return { kind: "missing", prefix: "" };
|
|
69
|
+
}
|
|
70
|
+
const joined = commandTokens.join(".");
|
|
71
|
+
const exact = lookupId(joined);
|
|
72
|
+
if (exact) {
|
|
73
|
+
return { kind: "operation", operation: exact, flagArgs, help };
|
|
74
|
+
}
|
|
75
|
+
const suffixHits = uniqueSuffixMatch(commandTokens);
|
|
76
|
+
if (suffixHits.length === 1) {
|
|
77
|
+
return {
|
|
78
|
+
kind: "operation",
|
|
79
|
+
operation: suffixHits[0],
|
|
80
|
+
flagArgs,
|
|
81
|
+
help,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (suffixHits.length > 1) {
|
|
85
|
+
return {
|
|
86
|
+
kind: "ambiguous",
|
|
87
|
+
prefix: joined,
|
|
88
|
+
candidates: suffixHits.map((op) => op.operationId),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const prefixOps = operationsWithPrefix(joined);
|
|
92
|
+
if (prefixOps.length > 0) {
|
|
93
|
+
return {
|
|
94
|
+
kind: "help",
|
|
95
|
+
prefix: hyphenToUnderscore(joined),
|
|
96
|
+
operations: prefixOps,
|
|
97
|
+
flagArgs,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return { kind: "missing", prefix: joined };
|
|
101
|
+
}
|
|
102
|
+
function typedCommandExample(op) {
|
|
103
|
+
const segments = op.operationId.split(".");
|
|
104
|
+
const schemaNames = op.path.match(/\{([a-zA-Z0-9_]+)\}/g)?.map((s) => s.slice(1, -1)) ?? [];
|
|
105
|
+
const pathFlags = schemaNames.map((name) => `--${name} <${name}>`).join(" ");
|
|
106
|
+
const riskFlag = op.risk === "high-risk-write" || op.risk === "unknown" ? " --yes" : "";
|
|
107
|
+
const bodyFlag = op.method !== "GET" && op.method !== "HEAD" && op.method !== "DELETE"
|
|
108
|
+
? " --body '{}'"
|
|
109
|
+
: "";
|
|
110
|
+
const typedFlags = [pathFlags, bodyFlag.trim(), riskFlag.trim()]
|
|
111
|
+
.filter(Boolean)
|
|
112
|
+
.join(" ");
|
|
113
|
+
return {
|
|
114
|
+
typed: `alphafox ${segments.join(" ")}${typedFlags ? ` ${typedFlags}` : ""}`,
|
|
115
|
+
api: `alphafox api ${op.method} ${op.path}${bodyFlag}${riskFlag}`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Equivalent of `@alphafoxai/contracts/public-api` `checkCliCompatibility`.
|
|
3
|
+
* Fail closed on CLI/contract version skew. Do not invent a second range:
|
|
4
|
+
* callers must pass the generated registry compatibility block.
|
|
5
|
+
*/
|
|
6
|
+
export interface CompatibilityRange {
|
|
7
|
+
readonly contractVersion: string;
|
|
8
|
+
readonly registryVersion: string;
|
|
9
|
+
readonly minCliVersion: string;
|
|
10
|
+
readonly maxCliVersion: string;
|
|
11
|
+
readonly openapi: string;
|
|
12
|
+
}
|
|
13
|
+
export type CompatibilityResult = {
|
|
14
|
+
readonly ok: true;
|
|
15
|
+
} | {
|
|
16
|
+
readonly ok: false;
|
|
17
|
+
readonly code: "cli_too_old" | "cli_too_new" | "contract_mismatch" | "invalid_version";
|
|
18
|
+
readonly message: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function checkCliCompatibility(input: {
|
|
21
|
+
readonly cliVersion: string;
|
|
22
|
+
readonly contractVersion: string;
|
|
23
|
+
}, range: CompatibilityRange): CompatibilityResult;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Equivalent of `@alphafoxai/contracts/public-api` `checkCliCompatibility`.
|
|
4
|
+
* Fail closed on CLI/contract version skew. Do not invent a second range:
|
|
5
|
+
* callers must pass the generated registry compatibility block.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.checkCliCompatibility = checkCliCompatibility;
|
|
9
|
+
function parseSemver(raw) {
|
|
10
|
+
const m = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(raw.trim());
|
|
11
|
+
if (!m)
|
|
12
|
+
return null;
|
|
13
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
14
|
+
}
|
|
15
|
+
function cmpSemver(a, b) {
|
|
16
|
+
const pa = parseSemver(a);
|
|
17
|
+
const pb = parseSemver(b);
|
|
18
|
+
if (!pa || !pb)
|
|
19
|
+
return null;
|
|
20
|
+
for (let i = 0; i < 3; i += 1) {
|
|
21
|
+
if (pa[i] !== pb[i])
|
|
22
|
+
return pa[i] - pb[i];
|
|
23
|
+
}
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
function checkCliCompatibility(input, range) {
|
|
27
|
+
const cliVsMin = cmpSemver(input.cliVersion, range.minCliVersion);
|
|
28
|
+
const cliVsMax = cmpSemver(input.cliVersion, range.maxCliVersion);
|
|
29
|
+
if (cliVsMin === null || cliVsMax === null) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
code: "invalid_version",
|
|
33
|
+
message: `CLI version ${input.cliVersion} is not valid semver (expected MAJOR.MINOR.PATCH).`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (cliVsMin < 0) {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
code: "cli_too_old",
|
|
40
|
+
message: `CLI ${input.cliVersion} is older than minCliVersion ${range.minCliVersion} for contract ${range.contractVersion}.`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (cliVsMax > 0) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
code: "cli_too_new",
|
|
47
|
+
message: `CLI ${input.cliVersion} is newer than maxCliVersion ${range.maxCliVersion} for contract ${range.contractVersion}.`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (input.contractVersion !== range.contractVersion) {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
code: "contract_mismatch",
|
|
54
|
+
message: `Server contractVersion ${input.contractVersion} does not match CLI catalog ${range.contractVersion}.`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return { ok: true };
|
|
58
|
+
}
|