@alphafox/cli 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alphafox AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # @alphafox/cli
2
+
3
+ Alphafox CLI (`alphafox`) — Agent and human entry for the versioned Public Application API on alphafox-web.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @alphafox/cli
9
+ # or
10
+ npx @alphafox/cli version
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```bash
16
+ alphafox version
17
+ alphafox doctor
18
+ alphafox auth login --no-wait # Device Flow (headless-friendly)
19
+ alphafox auth login --device-code <code>
20
+ alphafox whoami
21
+ alphafox schema me.whoami
22
+ alphafox api GET /api/v1/me
23
+ ```
24
+
25
+ ## Security
26
+
27
+ - Tokens live in the OS keychain (or controlled test injection). **Never** in config files or `--token` argv.
28
+ - Profiles: `production` (default), `staging`, `local` — isolated issuer/audience/client (ADR 0003).
29
+ - High-risk writes require `--yes`. Automation tokens are **deferred** (ADR 0004).
30
+ - Raw `api` only hits allowlisted `/api/v1/*` facade paths.
31
+
32
+ ## Skills
33
+
34
+ Co-versioned Agent Skills live under `skills/`. They route intent to public `operationId`s only.
35
+
36
+ ## Docs
37
+
38
+ - [Release / supply chain](docs/release-supply-chain.md)
39
+ - [Staging E2E checklist](docs/e2e-staging.md)
40
+ - Related ADRs and parity matrix live in the alphafox-web repository
41
+
42
+ ## Development
43
+
44
+ ```bash
45
+ pnpm install
46
+ pnpm build
47
+ pnpm test
48
+ node dist/cli.js version
49
+ ```
50
+
51
+ ## License
52
+
53
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require("../dist/cli.js");
@@ -0,0 +1,13 @@
1
+ export declare function generatePkcePair(): {
2
+ readonly codeVerifier: string;
3
+ readonly codeChallenge: string;
4
+ readonly codeChallengeMethod: "S256";
5
+ };
6
+ export declare function buildAuthorizeUrl(input: {
7
+ readonly issuer: string;
8
+ readonly clientId: string;
9
+ readonly redirectUri: string;
10
+ readonly codeChallenge: string;
11
+ readonly state: string;
12
+ readonly scope?: string;
13
+ }): string;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generatePkcePair = generatePkcePair;
4
+ exports.buildAuthorizeUrl = buildAuthorizeUrl;
5
+ const node_crypto_1 = require("node:crypto");
6
+ function generatePkcePair() {
7
+ const codeVerifier = (0, node_crypto_1.randomBytes)(32).toString("base64url");
8
+ const codeChallenge = (0, node_crypto_1.createHash)("sha256")
9
+ .update(codeVerifier, "utf8")
10
+ .digest("base64url");
11
+ return {
12
+ codeVerifier,
13
+ codeChallenge,
14
+ codeChallengeMethod: "S256",
15
+ };
16
+ }
17
+ function buildAuthorizeUrl(input) {
18
+ const authorize = new URL(`${input.issuer.replace(/\/$/, "")}/oauth/authorize`);
19
+ authorize.searchParams.set("response_type", "code");
20
+ authorize.searchParams.set("client_id", input.clientId);
21
+ authorize.searchParams.set("redirect_uri", input.redirectUri);
22
+ authorize.searchParams.set("code_challenge", input.codeChallenge);
23
+ authorize.searchParams.set("code_challenge_method", "S256");
24
+ authorize.searchParams.set("state", input.state);
25
+ authorize.searchParams.set("scope", input.scope ?? "openid profile offline_access");
26
+ return authorize.toString();
27
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Raw API allowlist + internal path rejection.
3
+ * Mirrors alphafox-contracts public-api allowlist rules.
4
+ */
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
+ export declare function normalizeApiPath(path: string): string;
8
+ export declare function isInternalDisallowedPath(path: string): boolean;
9
+ export declare function isFacadeAllowlistedPath(path: string, extraAllow?: readonly string[]): boolean;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ /**
3
+ * Raw API allowlist + internal path rejection.
4
+ * Mirrors alphafox-contracts public-api allowlist rules.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.FACILITY_ALWAYS_ALLOW = void 0;
8
+ exports.normalizeApiPath = normalizeApiPath;
9
+ exports.isInternalDisallowedPath = isInternalDisallowedPath;
10
+ exports.isFacadeAllowlistedPath = isFacadeAllowlistedPath;
11
+ const INTERNAL_PREFIXES = [
12
+ "/backend",
13
+ "/control-plane",
14
+ "/signal-center",
15
+ "/api/backend",
16
+ "/api/control-plane",
17
+ "/api/signal-center",
18
+ ];
19
+ /** MVP + core facade paths always allowed; full registry embedded for drift tests. */
20
+ exports.FACILITY_ALWAYS_ALLOW = [
21
+ "/api/v1/meta",
22
+ "/api/v1/me",
23
+ "/api/v1/operations",
24
+ "/api/v1/openapi.json",
25
+ "/api/v1/trading/strategy-definitions",
26
+ "/api/v1/exchange-connectors",
27
+ "/api/v1/trading/traders",
28
+ "/api/v1/chats",
29
+ "/api/v1/backtests",
30
+ ];
31
+ function normalizeApiPath(path) {
32
+ const trimmed = path.trim();
33
+ if (!trimmed)
34
+ return "/";
35
+ const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
36
+ return (withSlash.split("?")[0] ?? withSlash).split("#")[0].replace(/\/{2,}/g, "/");
37
+ }
38
+ function isInternalDisallowedPath(path) {
39
+ const n = normalizeApiPath(path);
40
+ return INTERNAL_PREFIXES.some((p) => n === p || n.startsWith(`${p}/`) || n.includes(`${p}/`));
41
+ }
42
+ function isFacadeAllowlistedPath(path, extraAllow = []) {
43
+ const n = normalizeApiPath(path);
44
+ if (isInternalDisallowedPath(n)) {
45
+ return false;
46
+ }
47
+ if (!n.startsWith("/api/v1")) {
48
+ return false;
49
+ }
50
+ const allow = [...exports.FACILITY_ALWAYS_ALLOW, ...extraAllow];
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)) {
58
+ return true;
59
+ }
60
+ if (/^\/api\/v1\/trading\/traders\/[^/]+(\/.*)?$/.test(n)) {
61
+ return true;
62
+ }
63
+ if (/^\/api\/v1\//.test(n)) {
64
+ // Allow any /api/v1/* that is not internal — facade is the allowlist boundary.
65
+ // Internal services are never mounted under /api/v1.
66
+ return true;
67
+ }
68
+ return false;
69
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Embedded capability catalog (co-versioned with CLI).
3
+ * Source of truth for full matrix lives in alphafox-contracts + parity-matrix-m0.md.
4
+ */
5
+ export interface CatalogOperation {
6
+ readonly operationId: string;
7
+ readonly method: string;
8
+ readonly path: string;
9
+ readonly role: string;
10
+ readonly risk: "read" | "write" | "high-risk-write" | string;
11
+ readonly scopes: readonly string[];
12
+ readonly stream?: boolean;
13
+ readonly mvp?: boolean;
14
+ readonly description?: string;
15
+ }
16
+ export declare const CATALOG_VERSION = "2026-08-11";
17
+ export declare const CATALOG_OPERATIONS: readonly CatalogOperation[];
18
+ export declare function findCatalogOperation(operationId: string): CatalogOperation | undefined;
19
+ export declare function buildCapabilityManifest(): {
20
+ contractVersion: string;
21
+ registryVersion: string;
22
+ operations: {
23
+ operationId: string;
24
+ method: string;
25
+ path: string;
26
+ role: string;
27
+ risk: string;
28
+ scopes: readonly string[];
29
+ stream: boolean;
30
+ mvp: boolean;
31
+ }[];
32
+ };
33
+ /** Resolve path templates like /api/v1/backtests/{backtestId}. */
34
+ export declare function resolveOperationPath(template: string, params: Record<string, string>): string;
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ /**
3
+ * Embedded capability catalog (co-versioned with CLI).
4
+ * Source of truth for full matrix lives in alphafox-contracts + parity-matrix-m0.md.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.CATALOG_OPERATIONS = exports.CATALOG_VERSION = void 0;
8
+ exports.findCatalogOperation = findCatalogOperation;
9
+ exports.buildCapabilityManifest = buildCapabilityManifest;
10
+ exports.resolveOperationPath = resolveOperationPath;
11
+ exports.CATALOG_VERSION = "2026-08-11";
12
+ exports.CATALOG_OPERATIONS = [
13
+ {
14
+ operationId: "meta.get",
15
+ method: "GET",
16
+ path: "/api/v1/meta",
17
+ role: "public",
18
+ risk: "read",
19
+ scopes: [],
20
+ mvp: true,
21
+ description: "Environment, commit SHA, contract version",
22
+ },
23
+ {
24
+ operationId: "me.whoami",
25
+ method: "GET",
26
+ path: "/api/v1/me",
27
+ role: "user",
28
+ risk: "read",
29
+ scopes: ["openid", "profile"],
30
+ mvp: true,
31
+ description: "Authenticated user identity",
32
+ },
33
+ {
34
+ operationId: "trading.strategy_definitions.list",
35
+ method: "GET",
36
+ path: "/api/v1/trading/strategy-definitions",
37
+ role: "user",
38
+ risk: "read",
39
+ scopes: ["openid", "profile", "trading:read"],
40
+ mvp: true,
41
+ },
42
+ {
43
+ operationId: "exchange_connectors.list",
44
+ method: "GET",
45
+ path: "/api/v1/exchange-connectors",
46
+ role: "user",
47
+ risk: "read",
48
+ scopes: ["openid", "profile", "exchange-connectors:read"],
49
+ mvp: true,
50
+ },
51
+ {
52
+ operationId: "trading.traders.list",
53
+ method: "GET",
54
+ path: "/api/v1/trading/traders",
55
+ role: "user",
56
+ risk: "read",
57
+ scopes: ["openid", "profile", "trading:read"],
58
+ mvp: true,
59
+ },
60
+ {
61
+ operationId: "chats.create",
62
+ method: "POST",
63
+ path: "/api/v1/chats",
64
+ role: "user",
65
+ risk: "write",
66
+ scopes: ["openid", "profile", "chats:write"],
67
+ mvp: true,
68
+ },
69
+ {
70
+ operationId: "backtests.create",
71
+ method: "POST",
72
+ path: "/api/v1/backtests",
73
+ role: "user",
74
+ risk: "write",
75
+ scopes: ["openid", "profile", "backtests:write"],
76
+ mvp: true,
77
+ },
78
+ {
79
+ operationId: "backtests.byId.get",
80
+ method: "GET",
81
+ path: "/api/v1/backtests/{backtestId}",
82
+ role: "user",
83
+ risk: "read",
84
+ scopes: ["openid", "profile", "backtests:read"],
85
+ mvp: true,
86
+ },
87
+ {
88
+ operationId: "backtests.byId.stream",
89
+ method: "GET",
90
+ path: "/api/v1/backtests/{backtestId}/stream",
91
+ role: "user",
92
+ risk: "read",
93
+ scopes: ["openid", "profile", "backtests:read"],
94
+ stream: true,
95
+ mvp: true,
96
+ },
97
+ {
98
+ operationId: "backtests.byId.cancel",
99
+ method: "POST",
100
+ path: "/api/v1/backtests/{backtestId}/cancel",
101
+ role: "user",
102
+ risk: "write",
103
+ scopes: ["openid", "profile", "backtests:write"],
104
+ mvp: true,
105
+ },
106
+ {
107
+ operationId: "trading.traders.byId.start",
108
+ method: "POST",
109
+ path: "/api/v1/trading/traders/{traderId}/start",
110
+ role: "user",
111
+ risk: "high-risk-write",
112
+ scopes: ["openid", "profile", "trading:write", "trading:high-risk"],
113
+ },
114
+ {
115
+ operationId: "trading.traders.byId.stop",
116
+ method: "POST",
117
+ path: "/api/v1/trading/traders/{traderId}/stop",
118
+ role: "user",
119
+ risk: "high-risk-write",
120
+ scopes: ["openid", "profile", "trading:write", "trading:high-risk"],
121
+ },
122
+ {
123
+ operationId: "wallet.get",
124
+ method: "GET",
125
+ path: "/api/v1/wallet",
126
+ role: "user",
127
+ risk: "read",
128
+ scopes: ["openid", "profile", "wallet:read"],
129
+ },
130
+ {
131
+ operationId: "notification.channels.list",
132
+ method: "GET",
133
+ path: "/api/v1/notification/channels",
134
+ role: "user",
135
+ risk: "read",
136
+ scopes: ["openid", "profile", "notification:read"],
137
+ },
138
+ {
139
+ operationId: "account.exchange_uids.list",
140
+ method: "GET",
141
+ path: "/api/v1/account/exchange-uids",
142
+ role: "user",
143
+ risk: "read",
144
+ scopes: ["openid", "profile", "account:read"],
145
+ },
146
+ {
147
+ operationId: "subscriptions.me.get",
148
+ method: "GET",
149
+ path: "/api/v1/subscriptions/me",
150
+ role: "user",
151
+ risk: "read",
152
+ scopes: ["openid", "profile", "subscriptions:read"],
153
+ },
154
+ {
155
+ operationId: "managed_wallets.list",
156
+ method: "GET",
157
+ path: "/api/v1/managed-wallets",
158
+ role: "user",
159
+ risk: "read",
160
+ scopes: ["openid", "profile", "managed-wallets:read"],
161
+ },
162
+ {
163
+ operationId: "strategy_plaza.publications.list",
164
+ method: "GET",
165
+ path: "/api/v1/strategy-plaza/publications",
166
+ role: "user",
167
+ risk: "read",
168
+ scopes: ["openid", "profile", "strategy-plaza:read"],
169
+ },
170
+ {
171
+ operationId: "spread_radar.pairs.list",
172
+ method: "GET",
173
+ path: "/api/v1/spread-radar/pairs",
174
+ role: "user",
175
+ risk: "read",
176
+ scopes: ["openid", "profile", "spread-radar:read"],
177
+ },
178
+ {
179
+ operationId: "trader_dna.report.get",
180
+ method: "GET",
181
+ path: "/api/v1/trader-dna/report",
182
+ role: "user",
183
+ risk: "read",
184
+ scopes: ["openid", "profile", "trader-dna:read"],
185
+ },
186
+ {
187
+ operationId: "platform_statistics.get",
188
+ method: "GET",
189
+ path: "/api/v1/platform-statistics",
190
+ role: "user",
191
+ risk: "read",
192
+ scopes: ["openid", "profile", "platform-statistics:read"],
193
+ },
194
+ {
195
+ operationId: "asr.transcribe",
196
+ method: "POST",
197
+ path: "/api/v1/asr/transcribe",
198
+ role: "user",
199
+ risk: "write",
200
+ scopes: ["openid", "profile", "asr:write"],
201
+ },
202
+ {
203
+ operationId: "lite.catalog_config.get",
204
+ method: "GET",
205
+ path: "/api/v1/lite/catalog-config",
206
+ role: "user",
207
+ risk: "read",
208
+ scopes: ["openid", "profile", "lite:read"],
209
+ },
210
+ {
211
+ operationId: "admin.users.list",
212
+ method: "GET",
213
+ path: "/api/v1/admin/users",
214
+ role: "admin",
215
+ risk: "read",
216
+ scopes: ["openid", "profile", "admin:read"],
217
+ },
218
+ ];
219
+ function findCatalogOperation(operationId) {
220
+ return exports.CATALOG_OPERATIONS.find((op) => op.operationId === operationId);
221
+ }
222
+ function buildCapabilityManifest() {
223
+ return {
224
+ contractVersion: exports.CATALOG_VERSION,
225
+ registryVersion: "1.0.0",
226
+ operations: exports.CATALOG_OPERATIONS.map((op) => ({
227
+ operationId: op.operationId,
228
+ method: op.method,
229
+ path: op.path,
230
+ role: op.role,
231
+ risk: op.risk,
232
+ scopes: op.scopes,
233
+ stream: Boolean(op.stream),
234
+ mvp: Boolean(op.mvp),
235
+ })),
236
+ };
237
+ }
238
+ /** Resolve path templates like /api/v1/backtests/{backtestId}. */
239
+ function resolveOperationPath(template, params) {
240
+ return template.replace(/\{([a-zA-Z0-9_]+)\}/g, (_, key) => {
241
+ const value = params[key];
242
+ if (!value) {
243
+ throw new Error(`Missing path parameter: ${key}`);
244
+ }
245
+ return encodeURIComponent(value);
246
+ });
247
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const run_1 = require("./commands/run");
5
+ async function main() {
6
+ const code = await (0, run_1.runCli)(process.argv.slice(2), process.env);
7
+ if (typeof code === "number") {
8
+ process.exit(code);
9
+ }
10
+ }
11
+ main().catch((err) => {
12
+ const message = err instanceof Error ? err.message : String(err);
13
+ process.stderr.write(`${JSON.stringify({
14
+ ok: false,
15
+ error: { type: "runtime", message },
16
+ })}\n`);
17
+ process.exit(1);
18
+ });
@@ -0,0 +1,14 @@
1
+ export interface GlobalFlags {
2
+ profile?: string;
3
+ format: "json" | "jsonl" | "text";
4
+ yes: boolean;
5
+ dryRun: boolean;
6
+ noInput: boolean;
7
+ unsafeCustomEndpoint?: string;
8
+ jq?: string;
9
+ }
10
+ export declare function parseGlobalFlags(argv: string[]): {
11
+ flags: GlobalFlags;
12
+ rest: string[];
13
+ };
14
+ export declare function runCli(argv: string[], env?: NodeJS.ProcessEnv): Promise<number>;