@alphafox/cli 0.3.11 → 0.3.13
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/README.md +31 -4
- package/dist/index.d.ts +1 -0
- package/dist/install/types.d.ts +2 -0
- package/dist/install/wizard.js +3 -26
- package/dist/skills/agent-links.d.ts +29 -0
- package/dist/skills/agent-links.js +217 -0
- package/dist/skills/manager.d.ts +8 -0
- package/dist/skills/manager.js +1 -0
- package/dist/skills/run-command.js +23 -6
- package/dist/skills-manifest.json +41 -41
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/alphafox-cli-installation-guide.md +29 -2
- package/docs/release-supply-chain.md +4 -4
- package/package.json +2 -1
- package/scripts/uninstall.cjs +438 -0
- package/skills/account/SKILL.md +1 -1
- package/skills/admin/SKILL.md +1 -1
- package/skills/alphafox/SKILL.md +59 -5
- package/skills/alphafox-shared/SKILL.md +11 -2
- package/skills/auth/SKILL.md +1 -1
- package/skills/cache/SKILL.md +1 -1
- package/skills/engine-backtest/SKILL.md +1 -1
- package/skills/exchange/SKILL.md +1 -1
- package/skills/market/SKILL.md +1 -1
- package/skills/notification/SKILL.md +1 -1
- package/skills/strategy/SKILL.md +46 -9
- package/skills/trading/SKILL.md +1 -1
package/dist/version.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare const CLI_NAME = "alphafox";
|
|
2
2
|
export declare const CLI_PACKAGE = "@alphafox/cli";
|
|
3
|
-
export declare const CLI_VERSION = "0.3.
|
|
3
|
+
export declare const CLI_VERSION = "0.3.13";
|
|
4
4
|
export { CATALOG_VERSION as CLI_CONTRACT_VERSION } from "./catalog/operations";
|
package/dist/version.js
CHANGED
|
@@ -3,6 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.CLI_CONTRACT_VERSION = exports.CLI_VERSION = exports.CLI_PACKAGE = exports.CLI_NAME = void 0;
|
|
4
4
|
exports.CLI_NAME = "alphafox";
|
|
5
5
|
exports.CLI_PACKAGE = "@alphafox/cli";
|
|
6
|
-
exports.CLI_VERSION = "0.3.
|
|
6
|
+
exports.CLI_VERSION = "0.3.13";
|
|
7
7
|
var operations_1 = require("./catalog/operations");
|
|
8
8
|
Object.defineProperty(exports, "CLI_CONTRACT_VERSION", { enumerable: true, get: function () { return operations_1.CATALOG_VERSION; } });
|
|
@@ -24,6 +24,11 @@ npm install -g @alphafox/cli
|
|
|
24
24
|
alphafox skills sync --format json --no-input
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
`alphafox skills sync` writes the verified bundle to `~/.agents/skills` and
|
|
28
|
+
then links each Skill into `~/.claude/skills` so Claude Code can discover it.
|
|
29
|
+
Cursor (`~/.cursor/skills`) and Codex (`~/.codex/skills`) are linked when those
|
|
30
|
+
tools are already present. `alphafox skills status` reports `agentLinks`.
|
|
31
|
+
|
|
27
32
|
Do not install Skills from GitHub `main` as a fallback. The CLI verifies the
|
|
28
33
|
manifest and hashes inside the npm package before syncing. If sync fails, stop
|
|
29
34
|
and report the error rather than downloading a different Skills version.
|
|
@@ -87,8 +92,14 @@ Parse the JSON envelope: `ok === true` means success. Errors land on
|
|
|
87
92
|
## Step 4: Tell the user to restart
|
|
88
93
|
|
|
89
94
|
Ask the user to **restart the AI tool** so the new Skills are loaded.
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
|
|
96
|
+
## Step 5: New-user welcome
|
|
97
|
+
|
|
98
|
+
After restart and `alphafox auth status --verify` shows `session: active`,
|
|
99
|
+
follow skill `alphafox` **After install**: fetch the Lite square catalog
|
|
100
|
+
(`lite.catalog_config.get`, `lite.signal_sources.list`) and introduce the
|
|
101
|
+
classic strategy definitions from `trading.strategy_definitions.list`. Do not
|
|
102
|
+
invent 带单员 names. Do not create a trader until the user asks.
|
|
92
103
|
|
|
93
104
|
## Human wizard (do not run this from an Agent)
|
|
94
105
|
|
|
@@ -102,3 +113,19 @@ npx @alphafox/cli@latest install
|
|
|
102
113
|
That wizard installs the CLI globally, verifies and syncs the packaged Skills,
|
|
103
114
|
and may prompt for `alphafox auth login --browser`. It is TTY-oriented.
|
|
104
115
|
Agents must follow Steps 1–4 in this document rather than the wizard.
|
|
116
|
+
|
|
117
|
+
## Uninstall
|
|
118
|
+
|
|
119
|
+
Uninstall is a standalone script, not `alphafox uninstall`. After the user
|
|
120
|
+
explicitly asks to uninstall:
|
|
121
|
+
|
|
122
|
+
```shell
|
|
123
|
+
curl -fsSL https://raw.githubusercontent.com/alphafoxai/alphafox-cli/main/scripts/uninstall.cjs | node -- --dry-run
|
|
124
|
+
curl -fsSL https://raw.githubusercontent.com/alphafoxai/alphafox-cli/main/scripts/uninstall.cjs | node -- --yes
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
That removes the global `@alphafox/cli` package, Agent Skills under
|
|
128
|
+
`~/.agents/skills` (and Claude / Cursor / Codex / Grok links),
|
|
129
|
+
`~/.config/alphafox`, login tokens, and local Engine backtest caches.
|
|
130
|
+
It does not delete running traders or other server-side data. Ask the user
|
|
131
|
+
to restart the AI tool afterwards. Do not invent a CLI subcommand.
|
|
@@ -19,7 +19,7 @@ named `@alphafoxai/cli` (that name was never published; npmjs returns 404).
|
|
|
19
19
|
| License | MIT |
|
|
20
20
|
| Node | LTS **≥ 20** (`engines.node`) |
|
|
21
21
|
| OS | macOS, Linux, Windows (npm install; no standalone native binary in v1) |
|
|
22
|
-
| Package contents | `bin/`, `dist/`, `skills/`, `README.md`, `docs/`, `LICENSE` |
|
|
22
|
+
| Package contents | `bin/`, `dist/`, `skills/`, `README.md`, `docs/`, `scripts/uninstall.cjs`, `LICENSE` |
|
|
23
23
|
|
|
24
24
|
v1 does **not** ship independent GitHub Release binaries, so there is no extra
|
|
25
25
|
binary signing/checksum channel. Integrity is npm pack integrity plus
|
|
@@ -92,9 +92,9 @@ OIDC-provenance release.
|
|
|
92
92
|
|
|
93
93
|
| Platform | Install | Update | Uninstall | Secrets |
|
|
94
94
|
|----------|---------|--------|-----------|---------|
|
|
95
|
-
| macOS | `npm install -g @alphafox/cli` or `npx @alphafox/cli` | `npm update -g @alphafox/cli` or pin `@<version>` | `npm uninstall -g @alphafox/cli` | Keychain Access |
|
|
96
|
-
| Linux | same | same | same | Secret Service, or file fallback mode `0600` |
|
|
97
|
-
| Windows | same | same | same | Credential Manager (`CredWrite`/`CredRead`); file fallback mode `0600` if unavailable |
|
|
95
|
+
| macOS | `npm install -g @alphafox/cli` or `npx @alphafox/cli` | `npm update -g @alphafox/cli` or pin `@<version>` | `scripts/uninstall.cjs` (or `npm uninstall -g @alphafox/cli` plus local Skills/config/cache) | Keychain Access |
|
|
96
|
+
| Linux | same | same | same (`scripts/uninstall.cjs`) | Secret Service, or file fallback mode `0600` |
|
|
97
|
+
| Windows | same | same | same (`scripts/uninstall.cjs`) | Credential Manager (`CredWrite`/`CredRead`); file fallback mode `0600` if unavailable |
|
|
98
98
|
|
|
99
99
|
Fresh-machine acceptance (release checklist, not optional):
|
|
100
100
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alphafox/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.13",
|
|
4
4
|
"description": "AlphaFox CLI — Agent/Human entry for the public Application API.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"vendor",
|
|
16
16
|
"README.md",
|
|
17
17
|
"docs",
|
|
18
|
+
"scripts/uninstall.cjs",
|
|
18
19
|
"LICENSE"
|
|
19
20
|
],
|
|
20
21
|
"scripts": {
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require("node:child_process");
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
const os = require("node:os");
|
|
7
|
+
const path = require("node:path");
|
|
8
|
+
const readline = require("node:readline/promises");
|
|
9
|
+
|
|
10
|
+
const CLI_PACKAGE = "@alphafox/cli";
|
|
11
|
+
const PROFILES = Object.freeze(["production", "staging", "local"]);
|
|
12
|
+
const NPM_TIMEOUT_MS = 120_000;
|
|
13
|
+
const KEYCHAIN_TIMEOUT_MS = 10_000;
|
|
14
|
+
const USAGE_EXIT = 64;
|
|
15
|
+
const UNINSTALL_RAW_URL =
|
|
16
|
+
"https://raw.githubusercontent.com/alphafoxai/alphafox-cli/main/scripts/uninstall.cjs";
|
|
17
|
+
const UNINSTALL_CURL = `curl -fsSL ${UNINSTALL_RAW_URL} | node -- --yes`;
|
|
18
|
+
const UNINSTALL_DRY_RUN = `curl -fsSL ${UNINSTALL_RAW_URL} | node -- --dry-run`;
|
|
19
|
+
|
|
20
|
+
function parseUninstallArgs(argv) {
|
|
21
|
+
let dryRun = false;
|
|
22
|
+
let yes = false;
|
|
23
|
+
let help = false;
|
|
24
|
+
const unknown = [];
|
|
25
|
+
for (const arg of argv.slice(1)) {
|
|
26
|
+
if (isLauncherArg(arg)) continue;
|
|
27
|
+
if (arg === "--dry-run") dryRun = true;
|
|
28
|
+
else if (arg === "--yes" || arg === "-y") yes = true;
|
|
29
|
+
else if (arg === "--help" || arg === "-h") help = true;
|
|
30
|
+
else unknown.push(arg);
|
|
31
|
+
}
|
|
32
|
+
return { dryRun, yes, help, unknown };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isLauncherArg(arg) {
|
|
36
|
+
return (
|
|
37
|
+
arg === "-" ||
|
|
38
|
+
arg === "--" ||
|
|
39
|
+
arg.endsWith("uninstall.cjs") ||
|
|
40
|
+
arg.endsWith("uninstall.mjs")
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function uninstallHelpText() {
|
|
45
|
+
return [
|
|
46
|
+
"卸载 AlphaFox CLI、Agent Skills、本机配置、登录凭据和回测缓存。",
|
|
47
|
+
"",
|
|
48
|
+
"用法:",
|
|
49
|
+
` ${UNINSTALL_CURL}`,
|
|
50
|
+
` ${UNINSTALL_DRY_RUN}`,
|
|
51
|
+
" node scripts/uninstall.cjs [--dry-run|--yes]",
|
|
52
|
+
"",
|
|
53
|
+
"不会删除服务器上的策略实例或账户数据。完成后请重启 AI 工具。",
|
|
54
|
+
].join("\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function homeDir(env) {
|
|
58
|
+
return env.ALPHAFOX_AGENT_HOME?.trim() || os.homedir();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isAlphafoxSkillName(name) {
|
|
62
|
+
return name === "alphafox" || name.startsWith("alphafox-");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readSkillName(skillMd) {
|
|
66
|
+
try {
|
|
67
|
+
const match = fs.readFileSync(skillMd, "utf8").match(
|
|
68
|
+
/^name:\s*["']?([^"'\s]+)["']?\s*$/m
|
|
69
|
+
);
|
|
70
|
+
return match?.[1] ?? null;
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function lexists(target) {
|
|
77
|
+
try {
|
|
78
|
+
fs.lstatSync(target);
|
|
79
|
+
return true;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isAlphafoxSkillDir(target) {
|
|
86
|
+
if (isAlphafoxSkillName(path.basename(target))) return true;
|
|
87
|
+
const name = readSkillName(path.join(target, "SKILL.md"));
|
|
88
|
+
return name != null && isAlphafoxSkillName(name);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function skillRoots(env) {
|
|
92
|
+
const home = homeDir(env);
|
|
93
|
+
const claudeHome = env.CLAUDE_CONFIG_DIR?.trim() || path.join(home, ".claude");
|
|
94
|
+
const codexHome = env.CODEX_HOME?.trim() || path.join(home, ".codex");
|
|
95
|
+
return [
|
|
96
|
+
env.ALPHAFOX_SKILLS_DIR?.trim() || path.join(home, ".agents", "skills"),
|
|
97
|
+
path.join(claudeHome, "skills"),
|
|
98
|
+
path.join(home, ".cursor", "skills"),
|
|
99
|
+
path.join(codexHome, "skills"),
|
|
100
|
+
path.join(home, ".grok", "skills"),
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function configDir(env) {
|
|
105
|
+
return env.ALPHAFOX_CONFIG_DIR?.trim() || path.join(homeDir(env), ".config", "alphafox");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function tapeCacheDir(env) {
|
|
109
|
+
return (
|
|
110
|
+
env.ALPHAFOX_TAPE_CACHE_DIR?.trim() ||
|
|
111
|
+
path.join(homeDir(env), ".alphafox", "cache", "engine-backtest")
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function runtimeCacheDir(env) {
|
|
116
|
+
if (env.ALPHAFOX_BACKTEST_RUNTIME_CACHE_DIR?.trim()) {
|
|
117
|
+
return env.ALPHAFOX_BACKTEST_RUNTIME_CACHE_DIR.trim();
|
|
118
|
+
}
|
|
119
|
+
const xdg = env.XDG_CACHE_HOME?.trim();
|
|
120
|
+
return path.join(xdg || path.join(homeDir(env), ".cache"), "alphafox", "engine-backtest");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function managedPath(target, env) {
|
|
124
|
+
const resolved = path.resolve(target);
|
|
125
|
+
const home = path.resolve(homeDir(env));
|
|
126
|
+
const roots = [
|
|
127
|
+
home,
|
|
128
|
+
env.ALPHAFOX_SKILLS_DIR,
|
|
129
|
+
env.ALPHAFOX_CONFIG_DIR,
|
|
130
|
+
env.ALPHAFOX_TAPE_CACHE_DIR,
|
|
131
|
+
env.ALPHAFOX_BACKTEST_RUNTIME_CACHE_DIR,
|
|
132
|
+
env.XDG_CACHE_HOME,
|
|
133
|
+
env.CLAUDE_CONFIG_DIR,
|
|
134
|
+
env.CODEX_HOME,
|
|
135
|
+
]
|
|
136
|
+
.filter(Boolean)
|
|
137
|
+
.map((root) => path.resolve(root));
|
|
138
|
+
if (resolved === home || resolved === path.parse(resolved).root) return false;
|
|
139
|
+
return roots.some(
|
|
140
|
+
(root) => resolved === root || resolved.startsWith(root + path.sep)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function assertRemovable(target, env) {
|
|
145
|
+
const resolved = path.resolve(target);
|
|
146
|
+
if (!managedPath(resolved, env)) {
|
|
147
|
+
throw Object.assign(new Error(`Refusing to remove unmanaged path ${resolved}`), {
|
|
148
|
+
type: "usage",
|
|
149
|
+
subtype: "uninstall_path_unsafe",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return resolved;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function collectSkillPaths(env) {
|
|
156
|
+
const found = [];
|
|
157
|
+
for (const root of skillRoots(env)) {
|
|
158
|
+
if (!lexists(root)) continue;
|
|
159
|
+
let entries;
|
|
160
|
+
try {
|
|
161
|
+
entries = fs.readdirSync(root, { withFileTypes: true });
|
|
162
|
+
} catch {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
for (const entry of entries) {
|
|
166
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
167
|
+
const target = path.join(root, entry.name);
|
|
168
|
+
if (isAlphafoxSkillDir(target)) found.push(target);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return found;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function collectDirPaths(env) {
|
|
175
|
+
const tape = path.resolve(tapeCacheDir(env));
|
|
176
|
+
const runtime = path.resolve(runtimeCacheDir(env));
|
|
177
|
+
const runtimeRoot =
|
|
178
|
+
path.basename(runtime) === "engine-backtest"
|
|
179
|
+
? path.dirname(runtime)
|
|
180
|
+
: runtime;
|
|
181
|
+
return [configDir(env), tape, runtimeRoot];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function buildUninstallPlan(env = process.env) {
|
|
185
|
+
const paths = [...collectSkillPaths(env), ...collectDirPaths(env)]
|
|
186
|
+
.map((target) => path.resolve(target))
|
|
187
|
+
.filter((target, index, all) => all.indexOf(target) === index)
|
|
188
|
+
.filter((target) => lexists(target))
|
|
189
|
+
.map((target) => ({ kind: "path", path: assertRemovable(target, env) }));
|
|
190
|
+
return [
|
|
191
|
+
...paths,
|
|
192
|
+
...PROFILES.map((profile) => ({ kind: "keychain", profile })),
|
|
193
|
+
{ kind: "npm", package: CLI_PACKAGE },
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function rmdirIfEmpty(dir) {
|
|
198
|
+
try {
|
|
199
|
+
fs.rmdirSync(dir);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
if (err && (err.code === "ENOENT" || err.code === "ENOTEMPTY")) return;
|
|
202
|
+
throw err;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function pruneEmptyParents(target, env) {
|
|
207
|
+
const home = path.resolve(homeDir(env));
|
|
208
|
+
let current = path.dirname(path.resolve(target));
|
|
209
|
+
while (current !== home && current !== path.parse(current).root) {
|
|
210
|
+
const base = path.basename(current);
|
|
211
|
+
const parentName = path.basename(path.dirname(current));
|
|
212
|
+
const alphafoxCache = base === "cache" && parentName === ".alphafox";
|
|
213
|
+
if (base !== "alphafox" && base !== ".alphafox" && !alphafoxCache) break;
|
|
214
|
+
rmdirIfEmpty(current);
|
|
215
|
+
current = path.dirname(current);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function defaultRunCommand(command, args, options = {}) {
|
|
220
|
+
const timeout = options.timeoutMs ?? NPM_TIMEOUT_MS;
|
|
221
|
+
const env = options.env ?? process.env;
|
|
222
|
+
const platform = options.platform ?? process.platform;
|
|
223
|
+
const file = platform === "win32" ? "cmd.exe" : command;
|
|
224
|
+
const argv = platform === "win32" ? ["/c", command, ...args] : args;
|
|
225
|
+
const result = spawnSync(file, argv, {
|
|
226
|
+
encoding: "utf8",
|
|
227
|
+
env: { ...process.env, ...env },
|
|
228
|
+
timeout,
|
|
229
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
230
|
+
});
|
|
231
|
+
return {
|
|
232
|
+
status: result.status,
|
|
233
|
+
stdout: result.stdout ?? "",
|
|
234
|
+
stderr: result.stderr ?? "",
|
|
235
|
+
error: result.error,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function keychainPlatform(env) {
|
|
240
|
+
const raw = env.ALPHAFOX_KEYCHAIN_PLATFORM?.trim();
|
|
241
|
+
if (raw === "darwin" || raw === "linux" || raw === "win32") return raw;
|
|
242
|
+
return process.platform;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function keychainDeleteArgs(profile, env) {
|
|
246
|
+
const platform = keychainPlatform(env);
|
|
247
|
+
const service = `alphafox-cli.${profile}`;
|
|
248
|
+
if (platform === "darwin") {
|
|
249
|
+
return {
|
|
250
|
+
command: "security",
|
|
251
|
+
args: ["delete-generic-password", "-s", service, "-a", "oauth-tokens"],
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (platform === "linux") {
|
|
255
|
+
return {
|
|
256
|
+
command: env.ALPHAFOX_SECRET_TOOL?.trim() || "secret-tool",
|
|
257
|
+
args: ["clear", "service", service, "account", "oauth-tokens"],
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
command: "cmdkey",
|
|
262
|
+
args: [`/delete:alphafox-cli/${profile}/oauth-tokens`],
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function isMissingSecret(result) {
|
|
267
|
+
if (result.status === 0) return false;
|
|
268
|
+
const text = `${result.stdout}\n${result.stderr}`.toLowerCase();
|
|
269
|
+
return (
|
|
270
|
+
result.status === 44 ||
|
|
271
|
+
text.includes("could not be found") ||
|
|
272
|
+
text.includes("not found") ||
|
|
273
|
+
text.includes("no such") ||
|
|
274
|
+
text.includes("cannot find")
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function applyPath(item, env) {
|
|
279
|
+
fs.rmSync(item.path, { recursive: true, force: true });
|
|
280
|
+
pruneEmptyParents(item.path, env);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function applyKeychain(item, env, runCommand) {
|
|
284
|
+
const spec = keychainDeleteArgs(item.profile, env);
|
|
285
|
+
const result = runCommand(spec.command, spec.args, {
|
|
286
|
+
env,
|
|
287
|
+
timeoutMs: KEYCHAIN_TIMEOUT_MS,
|
|
288
|
+
});
|
|
289
|
+
if (result.error && result.error.code === "ENOENT") return "skipped";
|
|
290
|
+
if (result.status === 0) return "removed";
|
|
291
|
+
if (isMissingSecret(result)) return "skipped";
|
|
292
|
+
throw new Error(
|
|
293
|
+
`删除 ${item.profile} 凭据失败:${result.stderr.trim() || result.error || result.status}`
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function applyNpm(env, runCommand) {
|
|
298
|
+
const result = runCommand("npm", ["uninstall", "-g", CLI_PACKAGE], {
|
|
299
|
+
env,
|
|
300
|
+
timeoutMs: NPM_TIMEOUT_MS,
|
|
301
|
+
});
|
|
302
|
+
if (result.status === 0 && !result.error) return;
|
|
303
|
+
throw new Error(
|
|
304
|
+
`npm uninstall -g ${CLI_PACKAGE} 失败:${(result.stderr || result.stdout).trim() || result.error || result.status}`
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function confirmUninstall(confirm, isTty) {
|
|
309
|
+
if (typeof confirm === "function") return confirm();
|
|
310
|
+
if (!isTty()) return null;
|
|
311
|
+
const rl = readline.createInterface({
|
|
312
|
+
input: process.stdin,
|
|
313
|
+
output: process.stderr,
|
|
314
|
+
});
|
|
315
|
+
try {
|
|
316
|
+
const answer = await rl.question(
|
|
317
|
+
"将卸载全局 @alphafox/cli、本机 Agent Skills、配置、登录凭据和回测缓存。继续? [Y/n] "
|
|
318
|
+
);
|
|
319
|
+
const token = answer.trim().toLowerCase();
|
|
320
|
+
return token === "" || token === "y" || token === "yes" || token === "是" || token === "好";
|
|
321
|
+
} finally {
|
|
322
|
+
rl.close();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function describeItem(item) {
|
|
327
|
+
if (item.kind === "path") return item.path;
|
|
328
|
+
if (item.kind === "keychain") return `keychain:${item.profile}`;
|
|
329
|
+
return `npm uninstall -g ${item.package}`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function applyItem(item, env, runCommand, log) {
|
|
333
|
+
if (item.kind === "path") {
|
|
334
|
+
applyPath(item, env);
|
|
335
|
+
log(`已删除 ${item.path}`);
|
|
336
|
+
return describeItem(item);
|
|
337
|
+
}
|
|
338
|
+
if (item.kind === "keychain") {
|
|
339
|
+
if (applyKeychain(item, env, runCommand) !== "removed") return null;
|
|
340
|
+
log(`已删除 ${item.profile} 登录凭据`);
|
|
341
|
+
return describeItem(item);
|
|
342
|
+
}
|
|
343
|
+
applyNpm(env, runCommand);
|
|
344
|
+
log(`已卸载 ${CLI_PACKAGE}`);
|
|
345
|
+
return describeItem(item);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function usageError(error, hint) {
|
|
349
|
+
return { ok: false, exitCode: USAGE_EXIT, error, hint, plan: [], removed: [] };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function approveUninstall(flags, input, isTty, log) {
|
|
353
|
+
if (flags.yes) return { ok: true };
|
|
354
|
+
const approved = await confirmUninstall(input.confirm, isTty);
|
|
355
|
+
if (approved === true) return { ok: true };
|
|
356
|
+
if (approved === false) {
|
|
357
|
+
log("已取消。");
|
|
358
|
+
return { ok: true, cancelled: true, plan: [], removed: [] };
|
|
359
|
+
}
|
|
360
|
+
return usageError("非交互卸载需要 --yes。", UNINSTALL_CURL);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function runUninstall(input = {}) {
|
|
364
|
+
const env = input.env ?? process.env;
|
|
365
|
+
const flags = input.flags ?? parseUninstallArgs(process.argv);
|
|
366
|
+
const log = input.log ?? ((message) => process.stderr.write(`${message}\n`));
|
|
367
|
+
const runCommand = input.runCommand ?? defaultRunCommand;
|
|
368
|
+
const isTty =
|
|
369
|
+
input.isTty ?? (() => Boolean(process.stdin.isTTY && process.stderr.isTTY));
|
|
370
|
+
|
|
371
|
+
if (flags.unknown.length > 0) {
|
|
372
|
+
return usageError(
|
|
373
|
+
`未知的 uninstall 参数:${flags.unknown.join(" ")}`,
|
|
374
|
+
"用法:node scripts/uninstall.cjs [--dry-run|--yes]"
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
if (flags.help) {
|
|
378
|
+
log(uninstallHelpText());
|
|
379
|
+
return { ok: true, help: true, plan: [], removed: [] };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const plan = buildUninstallPlan(env);
|
|
383
|
+
if (flags.dryRun) {
|
|
384
|
+
log("这是 --dry-run,不会真正删除:");
|
|
385
|
+
for (const item of plan) log(` ${describeItem(item)}`);
|
|
386
|
+
return { ok: true, dryRun: true, plan, removed: [] };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const approval = await approveUninstall(flags, input, isTty, log);
|
|
390
|
+
if (!approval.ok || approval.cancelled) return approval;
|
|
391
|
+
|
|
392
|
+
const removed = [];
|
|
393
|
+
const errors = [];
|
|
394
|
+
for (const item of plan) {
|
|
395
|
+
try {
|
|
396
|
+
const label = applyItem(item, env, runCommand, log);
|
|
397
|
+
if (label) removed.push(label);
|
|
398
|
+
} catch (err) {
|
|
399
|
+
errors.push(err instanceof Error ? err.message : String(err));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (errors.length > 0) return { ok: false, exitCode: 1, plan, removed, errors };
|
|
403
|
+
log("卸载完成。请重启 AI 工具,以便卸载后的 Skills 生效。");
|
|
404
|
+
return { ok: true, plan, removed };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function main() {
|
|
408
|
+
const result = await runUninstall();
|
|
409
|
+
if (result.error) process.stderr.write(`${result.error}\n`);
|
|
410
|
+
if (result.hint) process.stderr.write(`${result.hint}\n`);
|
|
411
|
+
if (result.errors) {
|
|
412
|
+
for (const line of result.errors) process.stderr.write(`${line}\n`);
|
|
413
|
+
}
|
|
414
|
+
process.exit(result.ok ? 0 : (result.exitCode ?? 1));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (require.main === module) {
|
|
418
|
+
main().catch((err) => {
|
|
419
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
420
|
+
process.exit(1);
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
module.exports = {
|
|
425
|
+
CLI_PACKAGE,
|
|
426
|
+
PROFILES,
|
|
427
|
+
UNINSTALL_CURL,
|
|
428
|
+
UNINSTALL_DRY_RUN,
|
|
429
|
+
UNINSTALL_RAW_URL,
|
|
430
|
+
USAGE_EXIT,
|
|
431
|
+
buildUninstallPlan,
|
|
432
|
+
isAlphafoxSkillDir,
|
|
433
|
+
isAlphafoxSkillName,
|
|
434
|
+
parseUninstallArgs,
|
|
435
|
+
runUninstall,
|
|
436
|
+
skillRoots,
|
|
437
|
+
uninstallHelpText,
|
|
438
|
+
};
|
package/skills/account/SKILL.md
CHANGED
package/skills/admin/SKILL.md
CHANGED
package/skills/alphafox/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: alphafox
|
|
3
|
-
description: AlphaFox CLI entry router. Use for any AlphaFox request — install, update, login, whoami, 回测, engine backtest, 清理回测缓存 / 历史数据, strategy definitions, create/list/start/stop a running strategy (trader), ticker/标的 resolve (美股 or crypto), market data, exchange connectors, wallet, subscriptions, notifications, or admin. If a CLI command prints `[alphafox] update available`, ask the user「检测到新的版本,是否需要我帮你升级?」and only then run `alphafox update --format json --no-input`. After a large backtest, if tape cache is large, ask「回测下载的历史数据比较大,要不要我帮你清理本地缓存?」then open `alphafox-cache`. Start here, then open the routed domain skill. Do not guess alphafox-engine-backtest vs alphafox-strategy vs alphafox-trading from memory.
|
|
4
|
-
version: 0.3.
|
|
3
|
+
description: AlphaFox CLI entry router. Use for any AlphaFox request — install, update, login, whoami, 回测, engine backtest, 清理回测缓存 / 历史数据, strategy definitions, create/list/start/stop a running strategy (trader), ticker/标的 resolve (美股 or crypto), market data, exchange connectors, wallet, subscriptions, notifications, or admin. After a successful install and login, present the 新人引导 in this file (Lite square 带单员 + classic strategies). If a CLI command prints `[alphafox] update available`, ask the user「检测到新的版本,是否需要我帮你升级?」and only then run `alphafox update --format json --no-input`. After a large backtest, if tape cache is large, ask「回测下载的历史数据比较大,要不要我帮你清理本地缓存?」then open `alphafox-cache`. Start here, then open the routed domain skill. Do not guess alphafox-engine-backtest vs alphafox-strategy vs alphafox-trading from memory.
|
|
4
|
+
version: 0.3.13
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# AlphaFox
|
|
@@ -18,7 +18,8 @@ A **trader** is a running strategy instance (paper or live), not a person. Creat
|
|
|
18
18
|
|
|
19
19
|
| User intent | Skill |
|
|
20
20
|
|---|---|
|
|
21
|
-
| Install, update, Skills status/sync, doctor, version, catalog, how to call the CLI | `alphafox-shared` |
|
|
21
|
+
| Install, update, uninstall, Skills status/sync, doctor, version, catalog, how to call the CLI | `alphafox-shared` |
|
|
22
|
+
| 刚安装完 / 新人引导 / 热门带单员 / 经典策略介绍 | this file, **After install** |
|
|
22
23
|
| Login, logout, whoami, profile, staging vs production | `alphafox-auth` |
|
|
23
24
|
| Ticker / 标的 / 美股 / crypto / resolve a misspelled symbol | `alphafox-market` |
|
|
24
25
|
| Engine WASM backtest, experiment, `engine-backtest run`, persist a local run | `alphafox-engine-backtest` |
|
|
@@ -30,14 +31,17 @@ A **trader** is a running strategy instance (paper or live), not a person. Creat
|
|
|
30
31
|
| Notification channels | `alphafox-notification` |
|
|
31
32
|
| Admin-only operations | `alphafox-admin` |
|
|
32
33
|
|
|
33
|
-
If several rows apply, load **all** of them (typical: `alphafox-shared` + `alphafox-market` + one domain skill).
|
|
34
|
+
If several rows apply, load **all** of them (typical: `alphafox-shared` + `alphafox-market` + one domain skill).
|
|
35
|
+
|
|
36
|
+
- “帮我配/建一个网格/DCA/跟单策略” → `alphafox-strategy` (pick definition, ask knobs, validate `{common, strategy}`) **and** `alphafox-market` (resolve tickers) **and** `alphafox-trading` (create the trader). Hidden copy variants still create through `alphafox-trading`.
|
|
37
|
+
- “帮我回测这个配置” → `alphafox-strategy` (definition + config) **and** `alphafox-engine-backtest`.
|
|
34
38
|
|
|
35
39
|
## Upgrade reminder
|
|
36
40
|
|
|
37
41
|
The CLI may print this on **stderr** at most once every 24 hours:
|
|
38
42
|
|
|
39
43
|
```text
|
|
40
|
-
[alphafox] update available: 0.3.
|
|
44
|
+
[alphafox] update available: 0.3.12 -> 0.3.13. After the user confirms, run: alphafox update --format json --no-input,
|
|
41
45
|
```
|
|
42
46
|
|
|
43
47
|
If you see that notice (or `updateAvailable: true` from `alphafox update --check`):
|
|
@@ -54,6 +58,56 @@ alphafox update --format json --no-input
|
|
|
54
58
|
|
|
55
59
|
Do not install Skills from GitHub. Details and dry-run / check commands live in `alphafox-shared`.
|
|
56
60
|
|
|
61
|
+
## After install
|
|
62
|
+
|
|
63
|
+
After CLI + Skills are installed, login is `session: active`, and the AI tool has restarted, present this welcome **once**. Fetch live data first. Do not invent 带单员 names, ROI, or strategy scenarios.
|
|
64
|
+
|
|
65
|
+
Classic product names to introduce (match `trading.strategy_definitions.list` `display.label` zh-CN / `name`; then `byId.get` that row — do not hardcode definition ids):
|
|
66
|
+
|
|
67
|
+
1. 组合跟单策略
|
|
68
|
+
2. 轮动马丁策略
|
|
69
|
+
3. 网格策略
|
|
70
|
+
4. 拼盘策略
|
|
71
|
+
5. 滚仓宝策略
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
alphafox lite catalog_config get --format json --no-input
|
|
75
|
+
alphafox lite signal_sources list --format json --no-input
|
|
76
|
+
alphafox trading strategy_definitions list --format json --no-input
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Keep Lite catalog order from `featuredSourceIds`. Resolve `id` → `name` from `lite.signal_sources` `sources[]`. Take the first **3** named rows. Optional ROI:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
alphafox lite signal_source_metrics list --sourceIds <id,id,id> --window all --mode scalars --format json --no-input
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Print `roi` as returned. If the catalog or metrics call fails, skip that part and say the square catalog could not be loaded — do not substitute other signal sources.
|
|
86
|
+
|
|
87
|
+
For each classic name that matched a definition, `byId.get` and use Chinese `display.description` (fallback English `description`) as the scenario. Then one live leaderboard example:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
alphafox trader_leaderboard list --strategyDefinitionId <id> --sort roi --order desc --positiveRoi true --window 30d --limit 1 --includePaper false --format json --no-input
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Use `items[0].traderName` + `roiPercent` when present. Omit the leaderboard clause when the list is empty.
|
|
94
|
+
|
|
95
|
+
Present in the operator's language, this shape:
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
安装完成!
|
|
99
|
+
以下是最近热门的一些带单员:{name}{、name}{、name}。
|
|
100
|
+
您可以直接通过组合跟单策略来跟随这些带单员,做实时自动化交易。
|
|
101
|
+
|
|
102
|
+
如果您想配置自己的交易策略,推荐先了解这些内置的经典策略。
|
|
103
|
+
{策略名}:{display.description}。排行榜上 {traderName} 近 30 日收益 {roiPercent}%。
|
|
104
|
+
…
|
|
105
|
+
|
|
106
|
+
想跟单或者运行策略,告诉我即可。或者您想先看看排行榜,也可以直接告诉我。
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Do not create a trader from this welcome. When they pick 跟单 / a classic strategy, read `alphafox-strategy` + `alphafox-trading` (+ `alphafox-market` if they name a ticker). When they ask for 排行榜, list `trader_leaderboard` (same flags, no `strategyDefinitionId` unless they named a type) and summarize — do not dump the envelope.
|
|
110
|
+
|
|
57
111
|
## Do not mix these backtest paths
|
|
58
112
|
|
|
59
113
|
- Local Engine tape + wasm + optional persist → `alphafox-engine-backtest` (`alphafox engine-backtest run`, hyphen).
|