@alphafox/cli 0.3.10 → 0.3.12

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.
Files changed (41) hide show
  1. package/README.md +5 -4
  2. package/dist/engine-backtest/coverage-notice.d.ts +5 -0
  3. package/dist/engine-backtest/coverage-notice.js +115 -0
  4. package/dist/engine-backtest/parse-args.d.ts +1 -1
  5. package/dist/engine-backtest/parse-args.js +3 -2
  6. package/dist/engine-backtest/persist.d.ts +3 -1
  7. package/dist/engine-backtest/persist.js +5 -0
  8. package/dist/engine-backtest/run-command.js +10 -1
  9. package/dist/engine-backtest/sweep-command.js +10 -1
  10. package/dist/engine-backtest/types.d.ts +24 -0
  11. package/dist/engine-backtest/types.js +3 -0
  12. package/dist/index.d.ts +1 -0
  13. package/dist/install/types.d.ts +2 -0
  14. package/dist/install/wizard.js +3 -26
  15. package/dist/skills/agent-links.d.ts +29 -0
  16. package/dist/skills/agent-links.js +217 -0
  17. package/dist/skills/manager.d.ts +8 -0
  18. package/dist/skills/manager.js +1 -0
  19. package/dist/skills/run-command.js +23 -6
  20. package/dist/skills-manifest.json +42 -42
  21. package/dist/version.d.ts +1 -1
  22. package/dist/version.js +1 -1
  23. package/docs/alphafox-cli-installation-guide.md +13 -2
  24. package/package.json +1 -1
  25. package/skills/account/SKILL.md +1 -1
  26. package/skills/admin/SKILL.md +1 -1
  27. package/skills/alphafox/SKILL.md +58 -4
  28. package/skills/alphafox-shared/SKILL.md +4 -2
  29. package/skills/auth/SKILL.md +1 -1
  30. package/skills/cache/SKILL.md +1 -1
  31. package/skills/engine-backtest/SKILL.md +4 -3
  32. package/skills/exchange/SKILL.md +1 -1
  33. package/skills/market/SKILL.md +1 -1
  34. package/skills/notification/SKILL.md +1 -1
  35. package/skills/strategy/SKILL.md +46 -9
  36. package/skills/trading/SKILL.md +1 -1
  37. package/vendor/backtest-runner/README.md +4 -4
  38. package/vendor/backtest-runner/index.d.ts +13 -0
  39. package/vendor/backtest-runner/index.mjs +3 -0
  40. package/vendor/backtest-runner/lib/coverage.mjs +37 -2
  41. package/vendor/backtest-runner/lib/tape-loader.mjs +6 -1
package/README.md CHANGED
@@ -86,10 +86,11 @@ skills then use public `operationId`s; an explicit local-execution skill may
86
86
  call its co-versioned built-in command.
87
87
 
88
88
  `alphafox install` (and the [Agent install guide](docs/alphafox-cli-installation-guide.md))
89
- verify the packaged Skills manifest and sync managed files into Agent skill
90
- directories (`.cursor/skills`, `.claude/skills`, `~/.agents/skills`, …).
91
- Use `alphafox skills status` to inspect missing, stale, or modified Skills and
92
- `alphafox skills sync` to repair safe drift.
89
+ verify the packaged Skills manifest, copy it into `~/.agents/skills`, and link
90
+ each Skill into `~/.claude/skills` (plus `~/.cursor/skills` / `~/.codex/skills`
91
+ when those agents exist). Use `alphafox skills status` to inspect missing,
92
+ stale, modified, or unlinked Skills and `alphafox skills sync` to repair safe
93
+ drift — including Claude Code links when the canonical bundle is already current.
93
94
 
94
95
  ## Docs
95
96
 
@@ -0,0 +1,5 @@
1
+ import type { TapeCoverageIssue, TapeCoverageNotice } from "./types";
2
+ export declare function summarizeTapeCoverageNotice(issues: readonly TapeCoverageIssue[]): TapeCoverageNotice;
3
+ /** Runtime guard: a missing field must not look like a clean tape. */
4
+ export declare function requireTapeCoverageIssues(value: unknown): readonly TapeCoverageIssue[];
5
+ export declare function snapshotCoverageIssues(issues: readonly TapeCoverageIssue[] | undefined): TapeCoverageIssue[] | undefined;
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.summarizeTapeCoverageNotice = summarizeTapeCoverageNotice;
4
+ exports.requireTapeCoverageIssues = requireTapeCoverageIssues;
5
+ exports.snapshotCoverageIssues = snapshotCoverageIssues;
6
+ const errors_1 = require("./errors");
7
+ function symbolLabel(issue) {
8
+ const timeframe = issue.timeframe && issue.timeframe !== "*" ? ` ${issue.timeframe}` : "";
9
+ return `${issue.symbol}${timeframe}`;
10
+ }
11
+ function uniqueIssues(issues) {
12
+ const seen = new Set();
13
+ const unique = [];
14
+ for (const issue of issues) {
15
+ const key = `${issue.code}\u0000${issue.symbol}\u0000${issue.timeframe}`;
16
+ if (seen.has(key))
17
+ continue;
18
+ seen.add(key);
19
+ unique.push(issue);
20
+ }
21
+ return unique;
22
+ }
23
+ function summarizeTapeCoverageNotice(issues) {
24
+ const prefix = [];
25
+ const internal = [];
26
+ const other = [];
27
+ for (const item of issues) {
28
+ if (item.code === "prefix_gap") {
29
+ prefix.push(item);
30
+ }
31
+ else if (item.code === "internal_gap") {
32
+ internal.push(item);
33
+ }
34
+ else {
35
+ other.push(item);
36
+ }
37
+ }
38
+ const uniquePrefix = uniqueIssues(prefix);
39
+ const uniqueInternal = uniqueIssues(internal);
40
+ const uniqueOther = uniqueIssues(other);
41
+ const severity = uniqueInternal.length > 0
42
+ ? "warning"
43
+ : uniquePrefix.length + uniqueOther.length > 0
44
+ ? "notice"
45
+ : "none";
46
+ const messages = [];
47
+ if (uniqueInternal.length > 0) {
48
+ messages.push(`WARNING: missing mid-range candles (more severe): ${uniqueInternal
49
+ .map(symbolLabel)
50
+ .join(", ")}`);
51
+ }
52
+ if (uniquePrefix.length > 0) {
53
+ messages.push(`NOTICE: missing start candles (less severe): ${uniquePrefix
54
+ .map(symbolLabel)
55
+ .join(", ")}`);
56
+ }
57
+ if (uniqueOther.length > 0) {
58
+ messages.push(`NOTICE: other soft coverage gaps: ${uniqueOther
59
+ .map((issue) => `${symbolLabel(issue)} (${issue.code})`)
60
+ .join(", ")}`);
61
+ }
62
+ return {
63
+ severity,
64
+ prefix: uniquePrefix,
65
+ internal: uniqueInternal,
66
+ other: uniqueOther,
67
+ messages,
68
+ };
69
+ }
70
+ /** Must match alphafox-web `engineBacktestPersistedSnapshotSchema.coverageIssues`. */
71
+ const PERSISTED_COVERAGE_ISSUE_CODES = new Set([
72
+ "market_missing",
73
+ "ohlcv_missing",
74
+ "invalid_ohlcv",
75
+ "non_monotonic",
76
+ "internal_gap",
77
+ "prefix_gap",
78
+ "suffix_gap",
79
+ "warmup_insufficient",
80
+ "load_failed",
81
+ "coverage_insufficient",
82
+ ]);
83
+ /** Runtime guard: a missing field must not look like a clean tape. */
84
+ function requireTapeCoverageIssues(value) {
85
+ if (!Array.isArray(value)) {
86
+ throw new errors_1.EngineBacktestError({
87
+ type: "runtime",
88
+ subtype: "missing_coverage_issues",
89
+ message: "Tape loader omitted coverageIssues; refusing to treat the tape as clean.",
90
+ });
91
+ }
92
+ return value;
93
+ }
94
+ function snapshotCoverageIssues(issues) {
95
+ if (!issues || issues.length === 0)
96
+ return undefined;
97
+ return issues.map((issue) => {
98
+ if (!PERSISTED_COVERAGE_ISSUE_CODES.has(issue.code)) {
99
+ throw new errors_1.EngineBacktestError({
100
+ type: "runtime",
101
+ subtype: "invalid_coverage_issue",
102
+ message: `Cannot persist unknown tape coverage issue code: ${issue.code}`,
103
+ });
104
+ }
105
+ return {
106
+ code: issue.code,
107
+ symbol: issue.symbol,
108
+ timeframe: issue.timeframe,
109
+ ...(issue.expected === undefined ? {} : { expected: issue.expected }),
110
+ ...(issue.actual === undefined ? {} : { actual: issue.actual }),
111
+ ...(issue.timestamp === undefined ? {} : { timestamp: issue.timestamp }),
112
+ ...(issue.message === undefined ? {} : { message: issue.message }),
113
+ };
114
+ });
115
+ }
@@ -1,4 +1,4 @@
1
- import type { EngineBacktestRunArgs, EngineBacktestSweepArgs, ExecutionModel, InclusiveUtcDateRange } from "./types";
1
+ import { type EngineBacktestRunArgs, type EngineBacktestSweepArgs, type ExecutionModel, type InclusiveUtcDateRange } from "./types";
2
2
  export declare const ENGINE_BACKTEST_RUN_USAGE: string[];
3
3
  export declare const ENGINE_BACKTEST_SWEEP_USAGE: string[];
4
4
  export declare function parseInclusiveUtcDateRange(rangeStart: string, rangeEnd: string): InclusiveUtcDateRange;
@@ -9,6 +9,7 @@ exports.parseEngineBacktestSweepArgs = parseEngineBacktestSweepArgs;
9
9
  const errors_1 = require("./errors");
10
10
  const replay_timeframe_1 = require("./replay-timeframe");
11
11
  const sweep_kernel_1 = require("./sweep-kernel");
12
+ const types_1 = require("./types");
12
13
  const DAY_MS = 86_400_000;
13
14
  const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
14
15
  const TIERS = new Set(["free", "pro", "pro_max"]);
@@ -107,7 +108,7 @@ function parseEngineBacktestRunArgs(args) {
107
108
  let to;
108
109
  let initialEquity;
109
110
  let tier;
110
- let dataQualityMode = "strict";
111
+ let dataQualityMode = types_1.DEFAULT_DATA_QUALITY_MODE;
111
112
  let configSchemaVersion;
112
113
  let executionModelOverride;
113
114
  let persist = true;
@@ -343,7 +344,7 @@ function parseEngineBacktestSweepArgs(args) {
343
344
  return {
344
345
  help: true,
345
346
  createExperiment: false,
346
- dataQualityMode: "strict",
347
+ dataQualityMode: types_1.DEFAULT_DATA_QUALITY_MODE,
347
348
  persist: true,
348
349
  replayTimeframe: replay_timeframe_1.ENGINE_BACKTEST_DEFAULT_REPLAY_TIMEFRAME,
349
350
  axesRaw,
@@ -1,5 +1,5 @@
1
1
  import type { ProfileName } from "../config/profiles";
2
- import type { CreateRunRequestBody, CreateSweepRequestBody, DataQualityMode, EngineBacktestMetrics, EngineBacktestScenario, EngineBacktestSweepPointRow, ExecutionModel, PersistedSnapshot, SweepMode, SweepSearchMode } from "./types";
2
+ import type { CreateRunRequestBody, CreateSweepRequestBody, DataQualityMode, EngineBacktestMetrics, EngineBacktestScenario, EngineBacktestSweepPointRow, ExecutionModel, PersistedSnapshot, SweepMode, SweepSearchMode, TapeCoverageIssue } from "./types";
3
3
  /** Aligned with `@alphafoxai/backtest-runner` DEFAULT_EXECUTION_MODEL. */
4
4
  export declare const DEFAULT_EXECUTION_MODEL: ExecutionModel;
5
5
  export declare const SNAPSHOT_SCHEMA_VERSION: 1;
@@ -24,6 +24,7 @@ export declare function buildCreateRunRequest(input: {
24
24
  readonly dataQualityMode: DataQualityMode;
25
25
  readonly engineVersion: string;
26
26
  readonly equityCurve?: unknown;
27
+ readonly coverageIssues?: readonly TapeCoverageIssue[];
27
28
  }): CreateRunRequestBody;
28
29
  /**
29
30
  * Experiment detail URL. Locale prefix (`/zh`, `/en`) is optional — web
@@ -51,6 +52,7 @@ export declare function buildSweepBaseSnapshot(input: {
51
52
  readonly initialEquity: number;
52
53
  readonly subscriptionTier: PersistedSnapshot["subscriptionTier"];
53
54
  readonly dataQualityMode: DataQualityMode;
55
+ readonly coverageIssues?: readonly TapeCoverageIssue[];
54
56
  readonly symbols: readonly string[];
55
57
  readonly timeframes: readonly string[];
56
58
  readonly baseTimeframe: string;
@@ -14,6 +14,7 @@ exports.buildCreateSweepRequest = buildCreateSweepRequest;
14
14
  exports.assertSweepCreateRequestSize = assertSweepCreateRequestSize;
15
15
  const errors_1 = require("./errors");
16
16
  const return_curve_1 = require("./return-curve");
17
+ const coverage_notice_1 = require("./coverage-notice");
17
18
  /** Aligned with `@alphafoxai/backtest-runner` DEFAULT_EXECUTION_MODEL. */
18
19
  exports.DEFAULT_EXECUTION_MODEL = {
19
20
  pricePath: "ohlc_path_4",
@@ -59,6 +60,7 @@ function buildCreateRunRequest(input) {
59
60
  }
60
61
  const timeframes = uniqueSeriesField(scenario.tape.series, "timeframe");
61
62
  const engineVersion = input.engineVersion.trim() || "node-wasm";
63
+ const coverageIssues = (0, coverage_notice_1.snapshotCoverageIssues)(input.coverageIssues);
62
64
  const snapshot = {
63
65
  snapshotSchemaVersion: exports.SNAPSHOT_SCHEMA_VERSION,
64
66
  strategyDefinitionId: scenario.trader.strategyDefinitionId,
@@ -70,6 +72,7 @@ function buildCreateRunRequest(input) {
70
72
  initialEquity: scenario.exchange.initialEquity,
71
73
  subscriptionTier: scenario.trader.subscriptionTier,
72
74
  dataQualityMode: input.dataQualityMode,
75
+ ...(coverageIssues ? { coverageIssues } : {}),
73
76
  symbols,
74
77
  timeframes: timeframes.length > 0 ? timeframes : ["1m"],
75
78
  baseTimeframe: scenario.tape.baseTimeframe || "1m",
@@ -141,6 +144,7 @@ function buildSweepBaseSnapshot(input) {
141
144
  const timeframes = [
142
145
  ...new Set(input.timeframes.map((item) => item.trim()).filter(Boolean)),
143
146
  ];
147
+ const coverageIssues = (0, coverage_notice_1.snapshotCoverageIssues)(input.coverageIssues);
144
148
  return {
145
149
  snapshotSchemaVersion: exports.SNAPSHOT_SCHEMA_VERSION,
146
150
  strategyDefinitionId: input.definitionId,
@@ -152,6 +156,7 @@ function buildSweepBaseSnapshot(input) {
152
156
  initialEquity: input.initialEquity,
153
157
  subscriptionTier: input.subscriptionTier,
154
158
  dataQualityMode: input.dataQualityMode,
159
+ ...(coverageIssues ? { coverageIssues } : {}),
155
160
  symbols,
156
161
  timeframes: timeframes.length > 0 ? timeframes : ["1m"],
157
162
  baseTimeframe: input.baseTimeframe.trim() || "1m",
@@ -9,6 +9,7 @@ const profiles_1 = require("../config/profiles");
9
9
  const envelope_1 = require("../envelope");
10
10
  const client_1 = require("../http/client");
11
11
  const store_1 = require("../keychain/store");
12
+ const coverage_notice_1 = require("./coverage-notice");
12
13
  const errors_1 = require("./errors");
13
14
  const load_config_1 = require("./load-config");
14
15
  const parse_args_1 = require("./parse-args");
@@ -336,6 +337,10 @@ async function executeEngineBacktestRun(args, flags, env = process.env, deps = {
336
337
  details: issues ? { issues } : undefined,
337
338
  });
338
339
  }
340
+ tapeResult = {
341
+ ...tapeResult,
342
+ coverageIssues: (0, coverage_notice_1.requireTapeCoverageIssues)(tapeResult.coverageIssues),
343
+ };
339
344
  const runId = mintId();
340
345
  const executionModel = {
341
346
  ...(runner.DEFAULT_EXECUTION_MODEL ?? persist_1.DEFAULT_EXECUTION_MODEL),
@@ -400,6 +405,7 @@ async function executeEngineBacktestRun(args, flags, env = process.env, deps = {
400
405
  dataQualityMode: args.dataQualityMode,
401
406
  engineVersion,
402
407
  equityCurve: result.equityCurve,
408
+ coverageIssues: tapeResult.coverageIssues,
403
409
  });
404
410
  const res = await postJson(api, profile, env, runsPath(experimentId), body, mintId);
405
411
  persistedRunId = extractEntityId(res.json);
@@ -419,7 +425,9 @@ async function executeEngineBacktestRun(args, flags, env = process.env, deps = {
419
425
  runId: persistedRunId,
420
426
  experimentUrl: (0, persist_1.experimentPageUrl)(profile.name, experimentId),
421
427
  persisted: Boolean(persistedRunId),
422
- coverageWarnings: tapeResult.coverageWarnings ?? [],
428
+ coverageWarnings: tapeResult.coverageWarnings,
429
+ coverageIssues: tapeResult.coverageIssues,
430
+ coverageNotice: (0, coverage_notice_1.summarizeTapeCoverageNotice)(tapeResult.coverageIssues),
423
431
  };
424
432
  }
425
433
  finally {
@@ -441,6 +449,7 @@ function engineBacktestHelpData() {
441
449
  "runs.create is write (not high-risk-write); --yes is not required",
442
450
  "Do not pass --token; use alphafox auth login",
443
451
  "--replay-timeframe defaults to 1m (min 1m). Indicator series still download their native plan timeframes.",
452
+ "--data-quality defaults to basic: hard tape failures still stop, soft gaps finish the run and appear as coverageNotice (prefix_gap less severe, internal_gap more severe). Use --data-quality strict to fail on any gap.",
444
453
  ],
445
454
  };
446
455
  }
@@ -9,6 +9,7 @@ const profiles_1 = require("../config/profiles");
9
9
  const client_1 = require("../http/client");
10
10
  const paths_1 = require("../cache/paths");
11
11
  const store_1 = require("../keychain/store");
12
+ const coverage_notice_1 = require("./coverage-notice");
12
13
  const errors_1 = require("./errors");
13
14
  const load_config_1 = require("./load-config");
14
15
  const parse_axes_1 = require("./parse-axes");
@@ -175,6 +176,7 @@ async function executeEngineBacktestSweep(args, flags, env = process.env, deps =
175
176
  },
176
177
  buffers: {},
177
178
  coverageWarnings: [],
179
+ coverageIssues: [],
178
180
  };
179
181
  if (tapePlans.length > 0) {
180
182
  try {
@@ -209,6 +211,10 @@ async function executeEngineBacktestSweep(args, flags, env = process.env, deps =
209
211
  details: issues ? { issues } : undefined,
210
212
  });
211
213
  }
214
+ tapeResult = {
215
+ ...tapeResult,
216
+ coverageIssues: (0, coverage_notice_1.requireTapeCoverageIssues)(tapeResult.coverageIssues),
217
+ };
212
218
  }
213
219
  emitProgress(flags, writeLine, "tape", 1);
214
220
  while (clients.length < concurrency) {
@@ -353,6 +359,7 @@ async function executeEngineBacktestSweep(args, flags, env = process.env, deps =
353
359
  initialEquity: args.initialEquity,
354
360
  subscriptionTier,
355
361
  dataQualityMode: args.dataQualityMode,
362
+ coverageIssues: tapeResult.coverageIssues,
356
363
  symbols: tapeSymbols.length > 0 ? tapeSymbols : coverage.symbols,
357
364
  timeframes: (0, persist_1.uniqueSeriesField)(tapeResult.tape.series, "timeframe"),
358
365
  baseTimeframe: tapeResult.tape.baseTimeframe || args.replayTimeframe,
@@ -417,7 +424,9 @@ async function executeEngineBacktestSweep(args, flags, env = process.env, deps =
417
424
  experimentUrl: experimentId
418
425
  ? (0, persist_1.experimentSweepPageUrl)(profile.name, experimentId)
419
426
  : undefined,
420
- coverageWarnings: tapeResult.coverageWarnings ?? [],
427
+ coverageWarnings: tapeResult.coverageWarnings,
428
+ coverageIssues: tapeResult.coverageIssues,
429
+ coverageNotice: (0, coverage_notice_1.summarizeTapeCoverageNotice)(tapeResult.coverageIssues),
421
430
  axes: coarsePlan.axes,
422
431
  };
423
432
  }
@@ -1,5 +1,23 @@
1
1
  export type SubscriptionTier = "free" | "pro" | "pro_max";
2
2
  export type DataQualityMode = "strict" | "basic";
3
+ /** New runs default to basic; `--data-quality strict` remains an explicit override. */
4
+ export declare const DEFAULT_DATA_QUALITY_MODE: DataQualityMode;
5
+ export interface TapeCoverageIssue {
6
+ readonly code: string;
7
+ readonly symbol: string;
8
+ readonly timeframe: string;
9
+ readonly expected?: number;
10
+ readonly actual?: number;
11
+ readonly timestamp?: number;
12
+ readonly message?: string;
13
+ }
14
+ export interface TapeCoverageNotice {
15
+ readonly severity: "none" | "notice" | "warning";
16
+ readonly prefix: readonly TapeCoverageIssue[];
17
+ readonly internal: readonly TapeCoverageIssue[];
18
+ readonly other: readonly TapeCoverageIssue[];
19
+ readonly messages: readonly string[];
20
+ }
3
21
  export type PricePath = "ohlc_path_4" | "close_only";
4
22
  export interface ExecutionModel {
5
23
  readonly pricePath: PricePath;
@@ -202,6 +220,7 @@ export interface TapeLoadResult {
202
220
  readonly tape: EngineBacktestTapeInput;
203
221
  readonly buffers: Record<string, ArrayBuffer>;
204
222
  readonly coverageWarnings: readonly string[];
223
+ readonly coverageIssues: readonly TapeCoverageIssue[];
205
224
  }
206
225
  export interface BacktestClientLike {
207
226
  init(): Promise<string>;
@@ -265,6 +284,7 @@ export interface PersistedSnapshot {
265
284
  readonly initialEquity: number;
266
285
  readonly subscriptionTier: SubscriptionTier;
267
286
  readonly dataQualityMode: DataQualityMode;
287
+ readonly coverageIssues?: readonly TapeCoverageIssue[];
268
288
  readonly symbols: string[];
269
289
  readonly timeframes: string[];
270
290
  readonly baseTimeframe: string;
@@ -289,6 +309,8 @@ export interface EngineBacktestRunSuccess {
289
309
  readonly experimentUrl: string;
290
310
  readonly persisted: boolean;
291
311
  readonly coverageWarnings: readonly string[];
312
+ readonly coverageIssues: readonly TapeCoverageIssue[];
313
+ readonly coverageNotice: TapeCoverageNotice;
292
314
  }
293
315
  export interface EngineBacktestSweepPointRow {
294
316
  readonly coordinate: {
@@ -372,6 +394,8 @@ export interface EngineBacktestSweepSuccess {
372
394
  readonly experimentId?: string;
373
395
  readonly experimentUrl?: string;
374
396
  readonly coverageWarnings: readonly string[];
397
+ readonly coverageIssues: readonly TapeCoverageIssue[];
398
+ readonly coverageNotice: TapeCoverageNotice;
375
399
  readonly axes: ReadonlyArray<{
376
400
  readonly path: readonly string[];
377
401
  readonly current: number;
@@ -1,2 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_DATA_QUALITY_MODE = void 0;
4
+ /** New runs default to basic; `--data-quality strict` remains an explicit override. */
5
+ exports.DEFAULT_DATA_QUALITY_MODE = "basic";
package/dist/index.d.ts CHANGED
@@ -18,6 +18,7 @@ export { parseRequestBodyFlags, loadJsonArg } from "./commands/request-body";
18
18
  export { parseInstallArgs, runInstallWizard, semverLessThan, skillsListHasAlphafox, } from "./install/wizard";
19
19
  export { AGENT_INSTALL_GUIDE_BLOB_URL, AGENT_INSTALL_GUIDE_URL, SKILLS_GITHUB_SOURCE, } from "./install/types";
20
20
  export { buildSkillsManifest, inspectSkills, loadAndVerifySkillsManifest, loadSkillsState, syncSkills, writeSkillsManifest, } from "./skills/manager";
21
+ export type { AgentSkillLinkStatus } from "./skills/manager";
21
22
  export { inspectCurrentSkills, installedSkillsRoot, skillsStatePath, syncCurrentSkills, } from "./skills/run-command";
22
23
  export { executeCliUpdate, parseUpdateArgs, } from "./update/run-command";
23
24
  export { formatUpdateNotice, maybeNotifyCliUpdate, shouldSkipUpdateCheck, } from "./update/notify";
@@ -1,3 +1,4 @@
1
+ import type { AgentSkillLinkStatus } from "../skills/manager";
1
2
  /** @deprecated AlphaFox Skills updates must use the verified npm package bundle. */
2
3
  export declare const SKILLS_GITHUB_SOURCE = "alphafoxai/alphafox-cli";
3
4
  export declare const SKILLS_NAME_PREFIX = "alphafox-";
@@ -22,6 +23,7 @@ export interface InstallSkillsStep {
22
23
  readonly removed?: readonly string[];
23
24
  readonly blocked?: readonly string[];
24
25
  readonly backupDir?: string;
26
+ readonly agentLinks?: readonly AgentSkillLinkStatus[];
25
27
  readonly restartRequired?: boolean;
26
28
  }
27
29
  export interface InstallAuthStep {
@@ -17,7 +17,6 @@ const exec_1 = require("./exec");
17
17
  const package_root_1 = require("./package-root");
18
18
  const types_1 = require("./types");
19
19
  const NPM_TIMEOUT_MS = 120_000;
20
- const SKILLS_TIMEOUT_MS = 120_000;
21
20
  function parseInstallArgs(args) {
22
21
  let noAuth = false;
23
22
  let help = false;
@@ -58,7 +57,7 @@ function nextSteps(input) {
58
57
  if (input.auth.action === "skipped" || input.auth.action === "planned") {
59
58
  steps.push("alphafox auth login --browser --format json --no-input", "alphafox auth login --no-wait --format json --no-input");
60
59
  }
61
- steps.push("alphafox doctor --format json --no-input", `Agent 安装指南:${types_1.AGENT_INSTALL_GUIDE_BLOB_URL}`);
60
+ steps.push("alphafox doctor --format json --no-input", `Agent 安装指南:${types_1.AGENT_INSTALL_GUIDE_BLOB_URL}`, "登录并重启后,按 alphafox skill 的 After install 向用户展示新人引导。");
62
61
  if (input.dryRun) {
63
62
  steps.unshift("这是 --dry-run,去掉该参数再运行才会真正安装。");
64
63
  }
@@ -218,30 +217,7 @@ async function stepInstallSkills(flags, runner) {
218
217
  ? `将同步 Skills ${manifest.packageVersion}(${source})`
219
218
  : `正在同步 AI Skills ${manifest.packageVersion}…`);
220
219
  try {
221
- const result = await (0, manager_1.syncSkills)({
222
- manifest,
223
- packageRoot: source,
224
- installedRoot: (0, run_command_1.installedSkillsRoot)(runner.env),
225
- statePath: (0, run_command_1.skillsStatePath)(runner.env),
226
- dryRun: flags.dryRun,
227
- force: false,
228
- }, {
229
- install: async (names) => {
230
- await runner.exec("npx", [
231
- "-y",
232
- "skills",
233
- "add",
234
- source,
235
- "-y",
236
- "-g",
237
- "--skill",
238
- ...names,
239
- ], { timeoutMs: SKILLS_TIMEOUT_MS });
240
- },
241
- remove: async (names) => {
242
- await runner.exec("npx", ["-y", "skills", "remove", ...names, "-y", "-g"], { timeoutMs: SKILLS_TIMEOUT_MS });
243
- },
244
- });
220
+ const result = await (0, run_command_1.syncCurrentSkills)({ force: false, dryRun: flags.dryRun }, runner.env, { runner, packageRoot: source });
245
221
  const action = result.blocked.length > 0
246
222
  ? "blocked"
247
223
  : result.action === "planned"
@@ -268,6 +244,7 @@ async function stepInstallSkills(flags, runner) {
268
244
  removed: result.removed,
269
245
  blocked: result.blocked,
270
246
  backupDir: result.backupDir,
247
+ agentLinks: result.status.agentLinks,
271
248
  restartRequired: result.restartRequired,
272
249
  };
273
250
  }
@@ -0,0 +1,29 @@
1
+ import { type AgentSkillLinkStatus, type SkillsStatus, type SkillsSyncResult } from "./manager";
2
+ export interface AgentSkillTarget {
3
+ readonly id: "claude-code" | "cursor" | "codex";
4
+ readonly required: boolean;
5
+ readonly home: string;
6
+ readonly skillsDir: string;
7
+ }
8
+ export interface AgentLinkChange {
9
+ readonly agent: AgentSkillTarget["id"];
10
+ readonly name: string;
11
+ }
12
+ export interface AgentLinkInput {
13
+ readonly canonicalRoot: string;
14
+ readonly skillNames: readonly string[];
15
+ readonly homeDir: string;
16
+ readonly env?: NodeJS.ProcessEnv;
17
+ }
18
+ export declare function agentHomeDir(env?: NodeJS.ProcessEnv): string;
19
+ export declare function resolveAgentSkillTargets(homeDir: string, env?: NodeJS.ProcessEnv): readonly AgentSkillTarget[];
20
+ export declare function inspectAgentLinks(input: AgentLinkInput): readonly AgentSkillLinkStatus[];
21
+ export declare function ensureAgentLinks(input: AgentLinkInput): readonly AgentLinkChange[];
22
+ export declare function removeAgentLinks(input: AgentLinkInput): void;
23
+ export declare function attachAgentLinks(status: SkillsStatus, env?: NodeJS.ProcessEnv): SkillsStatus;
24
+ export declare function applyAgentLinkPass(result: SkillsSyncResult, input: {
25
+ readonly dryRun: boolean;
26
+ readonly env: NodeJS.ProcessEnv;
27
+ readonly canonicalRoot: string;
28
+ }): SkillsSyncResult;
29
+ export declare function agentLinksNeedWork(agentLinks: readonly AgentSkillLinkStatus[]): boolean;