@alphafox/cli 0.3.12 → 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 CHANGED
@@ -54,6 +54,32 @@ Locally modified Skills are reported and preserved; use
54
54
  `alphafox skills sync --force --yes` only when you intend to replace them.
55
55
  Restart the AI tool after a sync.
56
56
 
57
+ ## Uninstall
58
+
59
+ This is **not** an `alphafox` subcommand. The script removes the global npm
60
+ package, Agent Skills (including Claude / Cursor / Codex / Grok links), local
61
+ config, login tokens, and Engine backtest caches. It does **not** delete
62
+ server-side traders or account data.
63
+
64
+ ```bash
65
+ curl -fsSL https://raw.githubusercontent.com/alphafoxai/alphafox-cli/main/scripts/uninstall.cjs | node -- --yes
66
+ ```
67
+
68
+ Preview first:
69
+
70
+ ```bash
71
+ curl -fsSL https://raw.githubusercontent.com/alphafoxai/alphafox-cli/main/scripts/uninstall.cjs | node -- --dry-run
72
+ ```
73
+
74
+ From a git checkout:
75
+
76
+ ```bash
77
+ node scripts/uninstall.cjs --dry-run
78
+ node scripts/uninstall.cjs --yes
79
+ ```
80
+
81
+ Restart AI tools afterwards so Skills unload.
82
+
57
83
  ## Quick start
58
84
 
59
85
  After Method 1 or 2:
@@ -1,153 +1,153 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "packageName": "@alphafox/cli",
4
- "packageVersion": "0.3.12",
4
+ "packageVersion": "0.3.13",
5
5
  "contractVersion": "2026-08-13",
6
- "bundleHash": "5129a939b6c209ec5e5d1a08ee94fe3c4999c53bd251731314c27de29f8ff5f0",
6
+ "bundleHash": "6957fd9e293dff1afdc94922f9947590398cff0743f99eba5a2b2abb215df12c",
7
7
  "skills": [
8
8
  {
9
9
  "name": "alphafox",
10
- "version": "0.3.12",
10
+ "version": "0.3.13",
11
11
  "files": [
12
12
  {
13
13
  "path": "SKILL.md",
14
- "sha256": "37dc3b595471e1130662a54d8aed35e12a32222d2f95d3fadf3c9f5636a52ac9",
15
- "size": 7415
14
+ "sha256": "9662240fd4497442711c155723e94ae60a9de84d2f433bbfac81a25d11caf9bd",
15
+ "size": 7426
16
16
  }
17
17
  ],
18
- "hash": "87347f1a8364fa982399b5641d6ff347c85376425e589ad6581001b1d1fefaac"
18
+ "hash": "8d4578a474c6063ff13ea22c45f9e2d75c1ab518567e077d838bd1bef6ec566e"
19
19
  },
20
20
  {
21
21
  "name": "alphafox-account",
22
- "version": "0.3.12",
22
+ "version": "0.3.13",
23
23
  "files": [
24
24
  {
25
25
  "path": "SKILL.md",
26
- "sha256": "c4cdac0a980106ad110c306b5248b9b8f325d9de60e50f39d412b40854238a36",
26
+ "sha256": "74fa3573aa94a54d995f8f78412a06269fc3ef903009f8b7b590f87a23ef2271",
27
27
  "size": 784
28
28
  }
29
29
  ],
30
- "hash": "743b0f52a8f707f568c8ba9fba28d648d67d7ed524c6b41b550b9dd8d56434a0"
30
+ "hash": "3f2644be04b0e3b1b4f4bc9c6f47708cb7576c715d50a8ac6d3ccc7143c75565"
31
31
  },
32
32
  {
33
33
  "name": "alphafox-admin",
34
- "version": "0.3.12",
34
+ "version": "0.3.13",
35
35
  "files": [
36
36
  {
37
37
  "path": "SKILL.md",
38
- "sha256": "6f06d16bd52b3d8651b91b0b6e4dc606d1d98de5827fc5c164f6d23705de3910",
38
+ "sha256": "7fe8f8aa9f0031c46e3b0f5989eb5964fe0d64fc4be3d3ea187ebd11f8fde729",
39
39
  "size": 788
40
40
  }
41
41
  ],
42
- "hash": "ccbca03ae872a794eb950897b8cac4f398014a9c014ac956245c89ffb08bf4f0"
42
+ "hash": "c0bf71914f23455168a3d7e0471a107f8d92de8c2edb958c4749d61ed413d418"
43
43
  },
44
44
  {
45
45
  "name": "alphafox-auth",
46
- "version": "0.3.12",
46
+ "version": "0.3.13",
47
47
  "files": [
48
48
  {
49
49
  "path": "SKILL.md",
50
- "sha256": "5985bdd0663d806e8ae8ab08d2ef2e1c1aa99b541ff5a5e72b14337eff3f22e6",
50
+ "sha256": "f76da1d4e97f3bc7d71afe9487de0e87258afc0e5de85e3cee588b656dfcfef7",
51
51
  "size": 2371
52
52
  }
53
53
  ],
54
- "hash": "a42814a37f6cddf2e21f6badd1f66254091d734d1210c87cff6e11ea17432241"
54
+ "hash": "d2fafe541e2be36e4672ad9ee9473113d8f4f4649247148ff5daacc2dfaefd21"
55
55
  },
56
56
  {
57
57
  "name": "alphafox-cache",
58
- "version": "0.3.12",
58
+ "version": "0.3.13",
59
59
  "files": [
60
60
  {
61
61
  "path": "SKILL.md",
62
- "sha256": "9653fe4bc132b2f587a57cebf5c646527faf4f081d51f80645c3be34e952161e",
62
+ "sha256": "da4cd692abb0e5a62a892b8e8c30cb39c0c77c14ec5f388bdfccf3a692a06d0b",
63
63
  "size": 1530
64
64
  }
65
65
  ],
66
- "hash": "e3c8c25d221c5b0574f17d455b2c29d8a8c075c2a007cac4302c0fa47ea1e226"
66
+ "hash": "bb6ee2004dc699d0392fedefd046520db202fffae856bc665bf6d79b21c2d26f"
67
67
  },
68
68
  {
69
69
  "name": "alphafox-engine-backtest",
70
- "version": "0.3.12",
70
+ "version": "0.3.13",
71
71
  "files": [
72
72
  {
73
73
  "path": "SKILL.md",
74
- "sha256": "1bd19ba3222dd8ab90f69509c8c02db3e2b9ef460c0539816ad83fba396e8e64",
74
+ "sha256": "fbdf4e90837243fa7c4385e2078b4b80f6a8e0b1df97f5477ef69a9dfb340fef",
75
75
  "size": 8278
76
76
  }
77
77
  ],
78
- "hash": "ff899b9199e5ac28ef77f6fc8bf582d341483d0617933f669ca4f9a40e8d5bef"
78
+ "hash": "d0222abf0dbced2551c049483d8381ff85f8ab36e9b89f44dca65adbaaf301d9"
79
79
  },
80
80
  {
81
81
  "name": "alphafox-exchange",
82
- "version": "0.3.12",
82
+ "version": "0.3.13",
83
83
  "files": [
84
84
  {
85
85
  "path": "SKILL.md",
86
- "sha256": "0dd186a4f7c34b01ad19028da5ecdfe2f15a15e2dc00997e048a7caa54e9d5b2",
86
+ "sha256": "a82200ac4fd9b1ebb07f2be560f484e4cc39a5b6365b1cf8f09da237206b3b9f",
87
87
  "size": 744
88
88
  }
89
89
  ],
90
- "hash": "b2a0a6d2ad588da804bbadfd505b395ecace330732b3be5dcc3110f3c4d0a2eb"
90
+ "hash": "49168e1b6cf992d1a357dd2e86a1cc2a41466aa3cb0c17cc058ef75027d309bb"
91
91
  },
92
92
  {
93
93
  "name": "alphafox-market",
94
- "version": "0.3.12",
94
+ "version": "0.3.13",
95
95
  "files": [
96
96
  {
97
97
  "path": "SKILL.md",
98
- "sha256": "fba7e2e1a8a474234d984ab9bb99a91d003e8e365c10796687d6f7d8b20319ab",
98
+ "sha256": "a824b1a49a29cb36f0a9c1d74187420f570137566f3a74f1685a38f7c1023032",
99
99
  "size": 3079
100
100
  }
101
101
  ],
102
- "hash": "9e804c4418f4745b18915a4c0b8f7b7ef1a2eb4e5263d43cb5f665523b547425"
102
+ "hash": "4d5fb1a2e765811c504decf024ab6260969486776b42da1534f977e238ca399b"
103
103
  },
104
104
  {
105
105
  "name": "alphafox-notification",
106
- "version": "0.3.12",
106
+ "version": "0.3.13",
107
107
  "files": [
108
108
  {
109
109
  "path": "SKILL.md",
110
- "sha256": "02fc964294afa9630f220ff9bdaed283df15ddd92d52a9d9e48432072dfe87ed",
110
+ "sha256": "2425c81589f89db06eb361f61d3bcf817ed862a42d00174d31005385b736b681",
111
111
  "size": 699
112
112
  }
113
113
  ],
114
- "hash": "acc77bfbd3f74625e80b4aeabcf2e29ff55eadb9a20a1a421f8be95ab6f2d84b"
114
+ "hash": "1d2d4243dba2410517c0d5add310709f20a3642bc5206e777bccd200d2fcb7ad"
115
115
  },
116
116
  {
117
117
  "name": "alphafox-shared",
118
- "version": "0.3.12",
118
+ "version": "0.3.13",
119
119
  "files": [
120
120
  {
121
121
  "path": "SKILL.md",
122
- "sha256": "3d9d42c3e63c1f3c524923c9b2c544fe948464e3bf9ea1a7ec898023f68efe7c",
123
- "size": 5643
122
+ "sha256": "895d98df84caa7813ed4083d23124bbb9572a93036578fdbaacb02574c29317d",
123
+ "size": 5960
124
124
  }
125
125
  ],
126
- "hash": "68affee820b6b6d41d01c0b83cebcd9431fa70eafe975d98736e983d47059d89"
126
+ "hash": "df50ec696f3b3e4f58cc2f66b2ded0d4dd5b898e2a6aa822eb779f978d04062d"
127
127
  },
128
128
  {
129
129
  "name": "alphafox-strategy",
130
- "version": "0.3.12",
130
+ "version": "0.3.13",
131
131
  "files": [
132
132
  {
133
133
  "path": "SKILL.md",
134
- "sha256": "956198df91f79ae2976d5aed8a5842a9e22c907ecafb28378e89fe5b942ee5c2",
134
+ "sha256": "ebf636f9d4656f98e9c5ba3ba7c00f0816d5e564dece2fbab3242ccd1138f325",
135
135
  "size": 4398
136
136
  }
137
137
  ],
138
- "hash": "58e72dbd185c1fa033aa52c63a79478701e3a75c14583fca13459ad55d9b3622"
138
+ "hash": "4b4fcc5c20337ab2c50499bd58c33c5c0bf3eb167c48f2a53aa48ab06cedba5a"
139
139
  },
140
140
  {
141
141
  "name": "alphafox-trading",
142
- "version": "0.3.12",
142
+ "version": "0.3.13",
143
143
  "files": [
144
144
  {
145
145
  "path": "SKILL.md",
146
- "sha256": "09da55fd6ff864c6fdac0b79e106e62d7c3f083610ae71b4fad38439b529dd91",
146
+ "sha256": "da1c8504eafcd201b8b0d6a06ab2c3ac19185c03a60c723ef8cb7e1d06213ca8",
147
147
  "size": 3123
148
148
  }
149
149
  ],
150
- "hash": "5ae19801df517c9c7f73c7d1b64fe0feeb0790ea660905f0971eea5ae9f209f9"
150
+ "hash": "154e7456e7abceacff29feeb082365fd81ed528004ebcb7899485a2dfef37f51"
151
151
  }
152
152
  ]
153
153
  }
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.12";
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.12";
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; } });
@@ -113,3 +113,19 @@ npx @alphafox/cli@latest install
113
113
  That wizard installs the CLI globally, verifies and syncs the packaged Skills,
114
114
  and may prompt for `alphafox auth login --browser`. It is TTY-oriented.
115
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.12",
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
+ };
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-account
3
3
  description: Account, wallet, and subscription read paths.
4
- version: 0.3.12
4
+ version: 0.3.13
5
5
  ---
6
6
 
7
7
  # Account / wallet
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-admin
3
3
  description: Admin-only operations reusing Web role authorization.
4
- version: 0.3.12
4
+ version: 0.3.13
5
5
  ---
6
6
 
7
7
  # Admin
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox
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.12
4
+ version: 0.3.13
5
5
  ---
6
6
 
7
7
  # AlphaFox
@@ -18,7 +18,7 @@ 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
22
  | 刚安装完 / 新人引导 / 热门带单员 / 经典策略介绍 | this file, **After install** |
23
23
  | Login, logout, whoami, profile, staging vs production | `alphafox-auth` |
24
24
  | Ticker / 标的 / 美股 / crypto / resolve a misspelled symbol | `alphafox-market` |
@@ -41,7 +41,7 @@ If several rows apply, load **all** of them (typical: `alphafox-shared` + `alpha
41
41
  The CLI may print this on **stderr** at most once every 24 hours:
42
42
 
43
43
  ```text
44
- [alphafox] update available: 0.3.11 -> 0.3.12. After the user confirms, run: alphafox update --format json --no-input,
44
+ [alphafox] update available: 0.3.12 -> 0.3.13. After the user confirms, run: alphafox update --format json --no-input,
45
45
  ```
46
46
 
47
47
  If you see that notice (or `updateAvailable: true` from `alphafox update --check`):
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-shared
3
3
  description: Shared AlphaFox CLI rules for Agents — auth, profiles, envelopes, risk gates, and public operationIds only.
4
- version: 0.3.12
4
+ version: 0.3.13
5
5
  ---
6
6
 
7
7
  # AlphaFox shared Agent contract
@@ -24,6 +24,13 @@ npx @alphafox/cli version --format json --no-input
24
24
  npx @alphafox/cli doctor --format json --no-input
25
25
  ```
26
26
 
27
+ Uninstall is **not** `alphafox uninstall`. After an explicit user request:
28
+
29
+ ```bash
30
+ curl -fsSL https://raw.githubusercontent.com/alphafoxai/alphafox-cli/main/scripts/uninstall.cjs | node -- --dry-run
31
+ curl -fsSL https://raw.githubusercontent.com/alphafoxai/alphafox-cli/main/scripts/uninstall.cjs | node -- --yes
32
+ ```
33
+
27
34
  After install, `auth status --verify` shows `session: active`, and the AI tool has restarted, follow skill `alphafox` **After install** (Lite square 带单员 + classic strategies). Do not skip that welcome.
28
35
 
29
36
  The CLI checks npm at most once every 24 hours and only prints a notice on
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-auth
3
3
  description: Login, status, logout, whoami, and environment isolation for AlphaFox CLI.
4
- version: 0.3.12
4
+ version: 0.3.13
5
5
  ---
6
6
 
7
7
  # Auth Skill
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-cache
3
3
  description: Inspect and clean local Engine backtest caches (downloaded OHLCV tape and wasm runtime). Use when the user asks to 清理缓存, free disk, or after a large historical backtest.
4
- version: 0.3.12
4
+ version: 0.3.13
5
5
  ---
6
6
 
7
7
  # Cache
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-engine-backtest
3
3
  description: Local Engine WASM backtest (alphafox engine-backtest run|sweep) vs catalog experiment CRUD.
4
- version: 0.3.12
4
+ version: 0.3.13
5
5
  ---
6
6
 
7
7
  # Engine Backtest
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-exchange
3
3
  description: Exchange connectors list and connection management via Public API.
4
- version: 0.3.12
4
+ version: 0.3.13
5
5
  ---
6
6
 
7
7
  # Exchange connectors
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-market
3
3
  description: Market data and ticker resolution for US equity perps, RWAs, and crypto on the same perp catalog. Use when the user names 美股, NVDA, AAPL, BTC, or any 标的. Keep the operator's asset class via symbolMetadata — do not rewrite NVDA into a crypto coin.
4
- version: 0.3.12
4
+ version: 0.3.13
5
5
  ---
6
6
 
7
7
  # Market
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-notification
3
3
  description: Notification channels and subscriptions.
4
- version: 0.3.12
4
+ version: 0.3.13
5
5
  ---
6
6
 
7
7
  # Notification
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-strategy
3
3
  description: Strategy definitions — list types, read a definition's contract, and validate config. Creating a running strategy is creating a trader; use alphafox-trading for that. Local Engine backtest is alphafox-engine-backtest.
4
- version: 0.3.12
4
+ version: 0.3.13
5
5
  ---
6
6
 
7
7
  # Strategy definitions
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: alphafox-trading
3
3
  description: Running strategies (traders) — create, list, start, and stop. A trader is a live or paper strategy instance (grid, dca, copy, …), not a person.
4
- version: 0.3.12
4
+ version: 0.3.13
5
5
  ---
6
6
 
7
7
  # Running strategies (traders)