@mison/ag-kit-cn 2.0.1
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/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
- package/.agent/ARCHITECTURE.md +285 -0
- package/.agent/agents/backend-specialist.md +268 -0
- package/.agent/agents/code-archaeologist.md +106 -0
- package/.agent/agents/database-architect.md +225 -0
- package/.agent/agents/debugger.md +225 -0
- package/.agent/agents/devops-engineer.md +242 -0
- package/.agent/agents/documentation-writer.md +104 -0
- package/.agent/agents/explorer-agent.md +73 -0
- package/.agent/agents/frontend-specialist.md +618 -0
- package/.agent/agents/game-developer.md +162 -0
- package/.agent/agents/mobile-developer.md +382 -0
- package/.agent/agents/orchestrator.md +438 -0
- package/.agent/agents/penetration-tester.md +188 -0
- package/.agent/agents/performance-optimizer.md +187 -0
- package/.agent/agents/product-manager.md +112 -0
- package/.agent/agents/product-owner.md +95 -0
- package/.agent/agents/project-planner.md +405 -0
- package/.agent/agents/qa-automation-engineer.md +103 -0
- package/.agent/agents/security-auditor.md +170 -0
- package/.agent/agents/seo-specialist.md +111 -0
- package/.agent/agents/test-engineer.md +158 -0
- package/.agent/mcp_config.json +12 -0
- package/.agent/rules/GEMINI.md +273 -0
- package/.agent/scripts/auto_preview.py +148 -0
- package/.agent/scripts/checklist.py +217 -0
- package/.agent/scripts/session_manager.py +120 -0
- package/.agent/scripts/verify_all.py +327 -0
- package/.agent/skills/api-patterns/SKILL.md +84 -0
- package/.agent/skills/api-patterns/api-style.md +42 -0
- package/.agent/skills/api-patterns/auth.md +24 -0
- package/.agent/skills/api-patterns/documentation.md +26 -0
- package/.agent/skills/api-patterns/graphql.md +41 -0
- package/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/.agent/skills/api-patterns/response.md +37 -0
- package/.agent/skills/api-patterns/rest.md +40 -0
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agent/skills/api-patterns/security-testing.md +122 -0
- package/.agent/skills/api-patterns/trpc.md +41 -0
- package/.agent/skills/api-patterns/versioning.md +22 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +74 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +40 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
- package/.agent/skills/architecture/SKILL.md +57 -0
- package/.agent/skills/architecture/context-discovery.md +43 -0
- package/.agent/skills/architecture/examples.md +94 -0
- package/.agent/skills/architecture/pattern-selection.md +68 -0
- package/.agent/skills/architecture/patterns-reference.md +50 -0
- package/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/.agent/skills/bash-linux/SKILL.md +201 -0
- package/.agent/skills/behavioral-modes/SKILL.md +264 -0
- package/.agent/skills/brainstorming/SKILL.md +164 -0
- package/.agent/skills/brainstorming/dynamic-questioning.md +359 -0
- package/.agent/skills/clean-code/SKILL.md +200 -0
- package/.agent/skills/code-review-checklist/SKILL.md +125 -0
- package/.agent/skills/database-design/SKILL.md +54 -0
- package/.agent/skills/database-design/database-selection.md +43 -0
- package/.agent/skills/database-design/indexing.md +39 -0
- package/.agent/skills/database-design/migrations.md +50 -0
- package/.agent/skills/database-design/optimization.md +36 -0
- package/.agent/skills/database-design/orm-selection.md +30 -0
- package/.agent/skills/database-design/schema-design.md +56 -0
- package/.agent/skills/database-design/scripts/schema_validator.py +172 -0
- package/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/.agent/skills/doc.md +177 -0
- package/.agent/skills/documentation-templates/SKILL.md +194 -0
- package/.agent/skills/frontend-design/SKILL.md +418 -0
- package/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/.agent/skills/frontend-design/color-system.md +307 -0
- package/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agent/skills/frontend-design/scripts/ux_audit.py +727 -0
- package/.agent/skills/frontend-design/typography-system.md +345 -0
- package/.agent/skills/frontend-design/ux-psychology.md +1118 -0
- package/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/.agent/skills/game-development/2d-games/SKILL.md +119 -0
- package/.agent/skills/game-development/3d-games/SKILL.md +135 -0
- package/.agent/skills/game-development/SKILL.md +167 -0
- package/.agent/skills/game-development/game-art/SKILL.md +185 -0
- package/.agent/skills/game-development/game-audio/SKILL.md +190 -0
- package/.agent/skills/game-development/game-design/SKILL.md +129 -0
- package/.agent/skills/game-development/mobile-games/SKILL.md +108 -0
- package/.agent/skills/game-development/multiplayer/SKILL.md +132 -0
- package/.agent/skills/game-development/pc-games/SKILL.md +144 -0
- package/.agent/skills/game-development/vr-ar/SKILL.md +123 -0
- package/.agent/skills/game-development/web-games/SKILL.md +150 -0
- package/.agent/skills/geo-fundamentals/SKILL.md +155 -0
- package/.agent/skills/geo-fundamentals/scripts/geo_checker.py +289 -0
- package/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agent/skills/intelligent-routing/SKILL.md +335 -0
- package/.agent/skills/lint-and-validate/SKILL.md +44 -0
- package/.agent/skills/lint-and-validate/scripts/lint_runner.py +184 -0
- package/.agent/skills/lint-and-validate/scripts/type_coverage.py +173 -0
- package/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/.agent/skills/mobile-design/SKILL.md +394 -0
- package/.agent/skills/mobile-design/decision-trees.md +516 -0
- package/.agent/skills/mobile-design/mobile-backend.md +491 -0
- package/.agent/skills/mobile-design/mobile-color-system.md +420 -0
- package/.agent/skills/mobile-design/mobile-debugging.md +122 -0
- package/.agent/skills/mobile-design/mobile-design-thinking.md +355 -0
- package/.agent/skills/mobile-design/mobile-navigation.md +458 -0
- package/.agent/skills/mobile-design/mobile-performance.md +767 -0
- package/.agent/skills/mobile-design/mobile-testing.md +356 -0
- package/.agent/skills/mobile-design/mobile-typography.md +432 -0
- package/.agent/skills/mobile-design/platform-android.md +666 -0
- package/.agent/skills/mobile-design/platform-ios.md +561 -0
- package/.agent/skills/mobile-design/scripts/mobile_audit.py +670 -0
- package/.agent/skills/mobile-design/touch-psychology.md +537 -0
- package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +311 -0
- package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +241 -0
- package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +489 -0
- package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +263 -0
- package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +581 -0
- package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +431 -0
- package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +683 -0
- package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +149 -0
- package/.agent/skills/nextjs-react-expert/SKILL.md +286 -0
- package/.agent/skills/nextjs-react-expert/scripts/convert_rules.py +222 -0
- package/.agent/skills/nextjs-react-expert/scripts/react_performance_checker.py +252 -0
- package/.agent/skills/nodejs-best-practices/SKILL.md +333 -0
- package/.agent/skills/parallel-agents/SKILL.md +194 -0
- package/.agent/skills/performance-profiling/SKILL.md +149 -0
- package/.agent/skills/performance-profiling/scripts/lighthouse_audit.py +76 -0
- package/.agent/skills/plan-writing/SKILL.md +152 -0
- package/.agent/skills/powershell-windows/SKILL.md +166 -0
- package/.agent/skills/python-patterns/SKILL.md +441 -0
- package/.agent/skills/red-team-tactics/SKILL.md +203 -0
- package/.agent/skills/rust-pro/SKILL.md +190 -0
- package/.agent/skills/seo-fundamentals/SKILL.md +135 -0
- package/.agent/skills/seo-fundamentals/scripts/seo_checker.py +215 -0
- package/.agent/skills/server-management/SKILL.md +161 -0
- package/.agent/skills/systematic-debugging/SKILL.md +114 -0
- package/.agent/skills/tailwind-patterns/SKILL.md +269 -0
- package/.agent/skills/tdd-workflow/SKILL.md +149 -0
- package/.agent/skills/testing-patterns/SKILL.md +178 -0
- package/.agent/skills/testing-patterns/scripts/test_runner.py +219 -0
- package/.agent/skills/vulnerability-scanner/SKILL.md +276 -0
- package/.agent/skills/vulnerability-scanner/checklists.md +131 -0
- package/.agent/skills/vulnerability-scanner/scripts/security_scan.py +459 -0
- package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
- package/.agent/skills/webapp-testing/SKILL.md +187 -0
- package/.agent/skills/webapp-testing/scripts/playwright_runner.py +173 -0
- package/.agent/workflows/brainstorm.md +113 -0
- package/.agent/workflows/create.md +59 -0
- package/.agent/workflows/debug.md +103 -0
- package/.agent/workflows/deploy.md +176 -0
- package/.agent/workflows/enhance.md +63 -0
- package/.agent/workflows/orchestrate.md +242 -0
- package/.agent/workflows/plan.md +89 -0
- package/.agent/workflows/preview.md +80 -0
- package/.agent/workflows/restore-localize-compat.md +525 -0
- package/.agent/workflows/status.md +86 -0
- package/.agent/workflows/test.md +144 -0
- package/.agent/workflows/ui-ux-pro-max.md +295 -0
- package/AGENT_FLOW.md +609 -0
- package/CHANGELOG.md +68 -0
- package/LICENSE +21 -0
- package/README.md +260 -0
- package/bin/adapters/base.js +63 -0
- package/bin/adapters/codex.js +391 -0
- package/bin/adapters/gemini.js +137 -0
- package/bin/ag-kit.js +1336 -0
- package/bin/core/builder.js +80 -0
- package/bin/core/generator.js +59 -0
- package/bin/core/resource-loader.js +64 -0
- package/bin/core/transformer.js +208 -0
- package/bin/interactive.js +65 -0
- package/bin/utils/atomic-writer.js +97 -0
- package/bin/utils/git-helper.js +68 -0
- package/bin/utils/managed-block.js +65 -0
- package/bin/utils/manifest.js +241 -0
- package/bin/utils.js +82 -0
- package/docs/codex-rules-template.md +36 -0
- package/docs/mapping-spec.md +68 -0
- package/docs/multi-target-adapter.md +80 -0
- package/docs/official/README.md +53 -0
- package/docs/official/antigravity/agent-modes-settings.md +64 -0
- package/docs/official/antigravity/rules-workflows.md +96 -0
- package/docs/official/antigravity/skills.md +147 -0
- package/docs/official/codex/agents-md.md +119 -0
- package/docs/official/codex/config-advanced.md +358 -0
- package/docs/official/codex/config-basic.md +141 -0
- package/docs/official/codex/config-reference.md +223 -0
- package/docs/official/codex/config-sample.md +216 -0
- package/docs/official/codex/mcp.md +107 -0
- package/docs/official/codex/rules.md +79 -0
- package/docs/official/codex/skills.md +114 -0
- package/docs/official/sources-index.md +32 -0
- package/docs/operations.md +145 -0
- package/docs/terminology-style-guide.md +69 -0
- package/package.json +51 -0
- package/scripts/postinstall-check.js +112 -0
package/bin/ag-kit.js
ADDED
|
@@ -0,0 +1,1336 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
const pkg = require("../package.json");
|
|
8
|
+
const { readGlobalNpmDependencies } = require("./utils");
|
|
9
|
+
const GeminiAdapter = require("./adapters/gemini");
|
|
10
|
+
const CodexAdapter = require("./adapters/codex");
|
|
11
|
+
const { selectTargets } = require("./interactive");
|
|
12
|
+
|
|
13
|
+
const BUNDLED_AGENT_DIR = path.resolve(__dirname, "../.agent");
|
|
14
|
+
const WORKSPACE_INDEX_VERSION = 2;
|
|
15
|
+
const UPSTREAM_GLOBAL_PACKAGE = "@vudovn/ag-kit";
|
|
16
|
+
const TOOLKIT_PACKAGE_NAMES = new Set(["@mison/ag-kit-cn", "antigravity-kit-cn", "antigravity-kit"]);
|
|
17
|
+
const SUPPORTED_TARGETS = ["gemini", "codex"];
|
|
18
|
+
const INDEX_LOCK_RETRY_MS = 50;
|
|
19
|
+
const INDEX_LOCK_TIMEOUT_MS = 3000;
|
|
20
|
+
const INDEX_LOCK_STALE_MS = 30000;
|
|
21
|
+
|
|
22
|
+
function nowISO() {
|
|
23
|
+
return new Date().toISOString();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getWorkspaceIndexPath() {
|
|
27
|
+
const customPath = process.env.AG_KIT_INDEX_PATH;
|
|
28
|
+
if (customPath) {
|
|
29
|
+
return path.resolve(process.cwd(), customPath);
|
|
30
|
+
}
|
|
31
|
+
return path.join(os.homedir(), ".ag-kit", "workspaces.json");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function createEmptyWorkspaceIndex() {
|
|
35
|
+
return {
|
|
36
|
+
version: WORKSPACE_INDEX_VERSION,
|
|
37
|
+
updatedAt: "",
|
|
38
|
+
workspaces: [],
|
|
39
|
+
excludedPaths: [],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function printUsage() {
|
|
44
|
+
console.log("用法:");
|
|
45
|
+
console.log(" ag-kit init [--force] [--path <dir>] [--branch <name>] [--target <name>|--targets <a,b>] [--non-interactive] [--no-index] [--quiet] [--dry-run]");
|
|
46
|
+
console.log(" ag-kit update [--path <dir>] [--branch <name>] [--target <name>|--targets <a,b>] [--no-index] [--quiet] [--dry-run]");
|
|
47
|
+
console.log(" ag-kit update-all [--branch <name>] [--targets <a,b>] [--prune-missing] [--quiet] [--dry-run]");
|
|
48
|
+
console.log(" ag-kit doctor [--path <dir>] [--target <name>|--targets <a,b>] [--fix] [--quiet]");
|
|
49
|
+
console.log(" ag-kit exclude list [--quiet]");
|
|
50
|
+
console.log(" ag-kit exclude add --path <dir> [--dry-run] [--quiet]");
|
|
51
|
+
console.log(" ag-kit exclude remove --path <dir> [--dry-run] [--quiet]");
|
|
52
|
+
console.log(" ag-kit status [--path <dir>] [--quiet]");
|
|
53
|
+
console.log(" ag-kit --version");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function printVersion() {
|
|
57
|
+
console.log(`ag-kit version ${pkg.version}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseArgs(argv) {
|
|
61
|
+
if (argv.length === 0) {
|
|
62
|
+
return { command: "", options: {}, providedFlags: [] };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const command = argv[0];
|
|
66
|
+
const options = {
|
|
67
|
+
force: false,
|
|
68
|
+
quiet: false,
|
|
69
|
+
dryRun: false,
|
|
70
|
+
pruneMissing: false,
|
|
71
|
+
nonInteractive: false,
|
|
72
|
+
noIndex: false,
|
|
73
|
+
fix: false,
|
|
74
|
+
subcommand: "",
|
|
75
|
+
path: "",
|
|
76
|
+
branch: "",
|
|
77
|
+
targets: [],
|
|
78
|
+
};
|
|
79
|
+
const providedFlags = [];
|
|
80
|
+
|
|
81
|
+
let startIndex = 1;
|
|
82
|
+
if (command === "exclude") {
|
|
83
|
+
if (argv.length > 1 && !argv[1].startsWith("--")) {
|
|
84
|
+
options.subcommand = argv[1];
|
|
85
|
+
startIndex = 2;
|
|
86
|
+
} else {
|
|
87
|
+
options.subcommand = "list";
|
|
88
|
+
startIndex = 1;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (let i = startIndex; i < argv.length; i++) {
|
|
93
|
+
const arg = argv[i];
|
|
94
|
+
|
|
95
|
+
if (arg === "--force") {
|
|
96
|
+
providedFlags.push(arg);
|
|
97
|
+
options.force = true;
|
|
98
|
+
} else if (arg === "--quiet") {
|
|
99
|
+
providedFlags.push(arg);
|
|
100
|
+
options.quiet = true;
|
|
101
|
+
} else if (arg === "--dry-run") {
|
|
102
|
+
providedFlags.push(arg);
|
|
103
|
+
options.dryRun = true;
|
|
104
|
+
} else if (arg === "--prune-missing") {
|
|
105
|
+
providedFlags.push(arg);
|
|
106
|
+
options.pruneMissing = true;
|
|
107
|
+
} else if (arg === "--non-interactive") {
|
|
108
|
+
providedFlags.push(arg);
|
|
109
|
+
options.nonInteractive = true;
|
|
110
|
+
} else if (arg === "--no-index") {
|
|
111
|
+
providedFlags.push(arg);
|
|
112
|
+
options.noIndex = true;
|
|
113
|
+
} else if (arg === "--fix") {
|
|
114
|
+
providedFlags.push(arg);
|
|
115
|
+
options.fix = true;
|
|
116
|
+
} else if (arg === "--path") {
|
|
117
|
+
providedFlags.push(arg);
|
|
118
|
+
if (i + 1 >= argv.length) {
|
|
119
|
+
throw new Error("--path 需要一个目录参数");
|
|
120
|
+
}
|
|
121
|
+
options.path = argv[++i];
|
|
122
|
+
} else if (arg === "--branch") {
|
|
123
|
+
providedFlags.push(arg);
|
|
124
|
+
if (i + 1 >= argv.length) {
|
|
125
|
+
throw new Error("--branch 需要一个分支名参数");
|
|
126
|
+
}
|
|
127
|
+
options.branch = argv[++i];
|
|
128
|
+
} else if (arg === "--target") {
|
|
129
|
+
providedFlags.push(arg);
|
|
130
|
+
if (i + 1 >= argv.length) {
|
|
131
|
+
throw new Error("--target 需要一个目标参数");
|
|
132
|
+
}
|
|
133
|
+
options.targets.push(argv[++i]);
|
|
134
|
+
} else if (arg === "--targets") {
|
|
135
|
+
providedFlags.push(arg);
|
|
136
|
+
if (i + 1 >= argv.length) {
|
|
137
|
+
throw new Error("--targets 需要一个参数");
|
|
138
|
+
}
|
|
139
|
+
options.targets.push(...String(argv[++i]).split(","));
|
|
140
|
+
} else {
|
|
141
|
+
throw new Error(`未知参数: ${arg}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { command, options, providedFlags };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const COMMAND_ALLOWED_FLAGS = {
|
|
149
|
+
init: ["--force", "--path", "--branch", "--target", "--targets", "--non-interactive", "--no-index", "--quiet", "--dry-run"],
|
|
150
|
+
update: ["--path", "--branch", "--target", "--targets", "--no-index", "--quiet", "--dry-run"],
|
|
151
|
+
"update-all": ["--branch", "--targets", "--prune-missing", "--quiet", "--dry-run"],
|
|
152
|
+
doctor: ["--path", "--target", "--targets", "--fix", "--quiet"],
|
|
153
|
+
status: ["--path", "--quiet"],
|
|
154
|
+
"exclude:list": ["--quiet"],
|
|
155
|
+
"exclude:add": ["--path", "--dry-run", "--quiet"],
|
|
156
|
+
"exclude:remove": ["--path", "--dry-run", "--quiet"],
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
function resolveAllowedFlags(command, options) {
|
|
160
|
+
if (command === "exclude") {
|
|
161
|
+
const subcommand = String(options.subcommand || "list").toLowerCase();
|
|
162
|
+
const key = `exclude:${subcommand}`;
|
|
163
|
+
return COMMAND_ALLOWED_FLAGS[key] || null;
|
|
164
|
+
}
|
|
165
|
+
return COMMAND_ALLOWED_FLAGS[command] || null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function resolveCommandLabel(command, options) {
|
|
169
|
+
if (command === "exclude") {
|
|
170
|
+
const subcommand = String(options.subcommand || "list").toLowerCase();
|
|
171
|
+
return `exclude ${subcommand}`;
|
|
172
|
+
}
|
|
173
|
+
return command;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function validateOptionScope(command, options, providedFlags) {
|
|
177
|
+
const allowedFlags = resolveAllowedFlags(command, options);
|
|
178
|
+
if (!allowedFlags) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const allowedSet = new Set(allowedFlags);
|
|
183
|
+
const unsupported = [];
|
|
184
|
+
const seen = new Set();
|
|
185
|
+
for (const flag of providedFlags || []) {
|
|
186
|
+
if (allowedSet.has(flag)) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (!seen.has(flag)) {
|
|
190
|
+
unsupported.push(flag);
|
|
191
|
+
seen.add(flag);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (unsupported.length === 0) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const commandLabel = resolveCommandLabel(command, options);
|
|
200
|
+
throw new Error(`命令 ${commandLabel} 不支持参数: ${unsupported.join(", ")}。可用参数: ${allowedFlags.join(", ")}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function resolveWorkspaceRoot(customPath) {
|
|
204
|
+
if (!customPath) {
|
|
205
|
+
return process.cwd();
|
|
206
|
+
}
|
|
207
|
+
return path.resolve(process.cwd(), customPath);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function log(options, message) {
|
|
211
|
+
if (!options.quiet) {
|
|
212
|
+
console.log(message);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function normalizeAbsolutePath(inputPath) {
|
|
217
|
+
return path.normalize(path.resolve(inputPath));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function pathCompareKey(inputPath) {
|
|
221
|
+
const normalized = normalizeAbsolutePath(inputPath);
|
|
222
|
+
if (process.platform === "win32") {
|
|
223
|
+
return normalized.toLowerCase();
|
|
224
|
+
}
|
|
225
|
+
return normalized;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function normalizePathList(items) {
|
|
229
|
+
const map = new Map();
|
|
230
|
+
for (const item of items) {
|
|
231
|
+
if (typeof item !== "string" || item.trim() === "") {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const normalizedPath = normalizeAbsolutePath(item);
|
|
235
|
+
const key = pathCompareKey(normalizedPath);
|
|
236
|
+
if (!map.has(key)) {
|
|
237
|
+
map.set(key, normalizedPath);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return Array.from(map.values()).sort((a, b) => a.localeCompare(b));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function isPathInOrUnder(basePath, targetPath) {
|
|
244
|
+
const normalizedBase = normalizeAbsolutePath(basePath);
|
|
245
|
+
const normalizedTarget = normalizeAbsolutePath(targetPath);
|
|
246
|
+
const baseKey = pathCompareKey(normalizedBase);
|
|
247
|
+
const targetKey = pathCompareKey(normalizedTarget);
|
|
248
|
+
|
|
249
|
+
if (targetKey === baseKey) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const prefix = baseKey.endsWith(path.sep) ? baseKey : `${baseKey}${path.sep}`;
|
|
254
|
+
return targetKey.startsWith(prefix);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function isPathExcludedByList(excludedPaths, workspacePath) {
|
|
258
|
+
return excludedPaths.some((excludedPath) => isPathInOrUnder(excludedPath, workspacePath));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function isToolkitSourceDirectory(workspacePath) {
|
|
262
|
+
const packageJsonPath = path.join(workspacePath, "package.json");
|
|
263
|
+
const cliPath = path.join(workspacePath, "bin", "ag-kit.js");
|
|
264
|
+
|
|
265
|
+
if (!fs.existsSync(packageJsonPath) || !fs.existsSync(cliPath)) {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
const content = fs.readFileSync(packageJsonPath, "utf8");
|
|
271
|
+
const parsed = JSON.parse(content);
|
|
272
|
+
const name = typeof parsed.name === "string" ? parsed.name : "";
|
|
273
|
+
return TOOLKIT_PACKAGE_NAMES.has(name);
|
|
274
|
+
} catch (err) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function getSystemTempRoots() {
|
|
280
|
+
const rawRoots = [
|
|
281
|
+
os.tmpdir(),
|
|
282
|
+
process.env.TMPDIR,
|
|
283
|
+
process.env.TMP,
|
|
284
|
+
process.env.TEMP,
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
// Add common POSIX temp roots explicitly.
|
|
288
|
+
// On macOS, os.tmpdir() usually resolves to /var/folders/... and may not cover /tmp.
|
|
289
|
+
if (process.platform !== "win32") {
|
|
290
|
+
rawRoots.push("/tmp", "/var/tmp");
|
|
291
|
+
if (process.platform === "darwin") {
|
|
292
|
+
rawRoots.push("/private/tmp", "/private/var/tmp");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const expandedRoots = [];
|
|
297
|
+
|
|
298
|
+
for (const root of rawRoots) {
|
|
299
|
+
if (typeof root !== "string" || root.trim() === "") {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
expandedRoots.push(root);
|
|
304
|
+
|
|
305
|
+
const normalized = normalizeAbsolutePath(root);
|
|
306
|
+
try {
|
|
307
|
+
const realPath = fs.realpathSync.native
|
|
308
|
+
? fs.realpathSync.native(normalized)
|
|
309
|
+
: fs.realpathSync(normalized);
|
|
310
|
+
expandedRoots.push(realPath);
|
|
311
|
+
} catch (err) {
|
|
312
|
+
// Ignore missing or inaccessible tmp roots from environment variables.
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (process.platform === "darwin") {
|
|
316
|
+
if (normalized === "/var" || normalized.startsWith("/var/")) {
|
|
317
|
+
expandedRoots.push(normalized.replace(/^\/var\b/, "/private/var"));
|
|
318
|
+
} else if (normalized === "/private/var" || normalized.startsWith("/private/var/")) {
|
|
319
|
+
expandedRoots.push(normalized.replace(/^\/private\/var\b/, "/var"));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return normalizePathList(expandedRoots);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const SYSTEM_TEMP_ROOTS = getSystemTempRoots();
|
|
328
|
+
|
|
329
|
+
function isSystemTempDirectory(workspacePath) {
|
|
330
|
+
return SYSTEM_TEMP_ROOTS.some((tempRoot) => isPathInOrUnder(tempRoot, workspacePath));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function normalizeTargetState(value) {
|
|
334
|
+
if (!value || typeof value !== "object") {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
return {
|
|
338
|
+
version: typeof value.version === "string" ? value.version : "",
|
|
339
|
+
installedAt: typeof value.installedAt === "string" ? value.installedAt : "",
|
|
340
|
+
updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : "",
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function normalizeWorkspaceRecordV2(item, normalizedPath) {
|
|
345
|
+
const targets = {};
|
|
346
|
+
if (item && item.targets && typeof item.targets === "object") {
|
|
347
|
+
for (const [targetName, state] of Object.entries(item.targets)) {
|
|
348
|
+
const normalizedState = normalizeTargetState(state);
|
|
349
|
+
if (normalizedState) {
|
|
350
|
+
targets[targetName] = normalizedState;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
path: normalizedPath,
|
|
356
|
+
targets,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function migrateRecordV1ToV2(item, normalizedPath) {
|
|
361
|
+
const targets = {};
|
|
362
|
+
const installedAt = typeof item.installedAt === "string" ? item.installedAt : "";
|
|
363
|
+
if (installedAt) {
|
|
364
|
+
targets.gemini = {
|
|
365
|
+
version: typeof item.cliVersion === "string" ? item.cliVersion : "",
|
|
366
|
+
installedAt,
|
|
367
|
+
updatedAt: typeof item.lastUpdatedAt === "string" ? item.lastUpdatedAt : installedAt,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
path: normalizedPath,
|
|
372
|
+
targets,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function readWorkspaceIndex() {
|
|
377
|
+
const indexPath = getWorkspaceIndexPath();
|
|
378
|
+
if (!fs.existsSync(indexPath)) {
|
|
379
|
+
return { indexPath, index: createEmptyWorkspaceIndex() };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const raw = fs.readFileSync(indexPath, "utf8").trim();
|
|
383
|
+
if (!raw) {
|
|
384
|
+
return { indexPath, index: createEmptyWorkspaceIndex() };
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
let parsed;
|
|
388
|
+
try {
|
|
389
|
+
parsed = JSON.parse(raw);
|
|
390
|
+
} catch (err) {
|
|
391
|
+
throw new Error(`工作区索引文件解析失败: ${indexPath}`);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const normalized = createEmptyWorkspaceIndex();
|
|
395
|
+
normalized.updatedAt = typeof parsed.updatedAt === "string" ? parsed.updatedAt : "";
|
|
396
|
+
|
|
397
|
+
const records = Array.isArray(parsed.workspaces) ? parsed.workspaces : [];
|
|
398
|
+
const dedupMap = new Map();
|
|
399
|
+
const isV1 = !parsed.version || parsed.version === 1;
|
|
400
|
+
|
|
401
|
+
for (const item of records) {
|
|
402
|
+
if (!item || typeof item.path !== "string" || item.path.trim() === "") {
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const workspacePath = normalizeAbsolutePath(item.path);
|
|
407
|
+
const key = pathCompareKey(workspacePath);
|
|
408
|
+
const record = isV1
|
|
409
|
+
? migrateRecordV1ToV2(item, workspacePath)
|
|
410
|
+
: normalizeWorkspaceRecordV2(item, workspacePath);
|
|
411
|
+
|
|
412
|
+
dedupMap.set(key, record);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
normalized.workspaces = Array.from(dedupMap.values()).sort((a, b) => a.path.localeCompare(b.path));
|
|
416
|
+
normalized.excludedPaths = normalizePathList(Array.isArray(parsed.excludedPaths) ? parsed.excludedPaths : []);
|
|
417
|
+
return { indexPath, index: normalized };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function writeWorkspaceIndex(indexPath, index) {
|
|
421
|
+
const workspaceMap = new Map();
|
|
422
|
+
|
|
423
|
+
for (const item of Array.isArray(index.workspaces) ? index.workspaces : []) {
|
|
424
|
+
if (!item || typeof item.path !== "string" || item.path.trim() === "") {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const normalizedPath = normalizeAbsolutePath(item.path);
|
|
429
|
+
const normalizedRecord = normalizeWorkspaceRecordV2(item, normalizedPath);
|
|
430
|
+
workspaceMap.set(pathCompareKey(normalizedPath), normalizedRecord);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const payload = {
|
|
434
|
+
version: WORKSPACE_INDEX_VERSION,
|
|
435
|
+
updatedAt: index.updatedAt || nowISO(),
|
|
436
|
+
workspaces: Array.from(workspaceMap.values()).sort((a, b) => a.path.localeCompare(b.path)),
|
|
437
|
+
excludedPaths: normalizePathList(Array.isArray(index.excludedPaths) ? index.excludedPaths : []),
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
fs.mkdirSync(path.dirname(indexPath), { recursive: true });
|
|
441
|
+
fs.writeFileSync(indexPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function sleepSync(ms) {
|
|
445
|
+
const buffer = new SharedArrayBuffer(4);
|
|
446
|
+
const view = new Int32Array(buffer);
|
|
447
|
+
Atomics.wait(view, 0, 0, ms);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function acquireWorkspaceIndexLock(indexPath) {
|
|
451
|
+
const lockPath = `${indexPath}.lock`;
|
|
452
|
+
const startedAt = Date.now();
|
|
453
|
+
|
|
454
|
+
while (true) {
|
|
455
|
+
try {
|
|
456
|
+
fs.mkdirSync(path.dirname(indexPath), { recursive: true });
|
|
457
|
+
const fd = fs.openSync(lockPath, "wx");
|
|
458
|
+
fs.writeFileSync(fd, `${process.pid}\n${nowISO()}\n`, "utf8");
|
|
459
|
+
return { lockPath, fd };
|
|
460
|
+
} catch (err) {
|
|
461
|
+
if (err && err.code !== "EEXIST") {
|
|
462
|
+
throw new Error(`索引锁创建失败: ${lockPath}`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
let removedStale = false;
|
|
466
|
+
try {
|
|
467
|
+
const stat = fs.statSync(lockPath);
|
|
468
|
+
if (Date.now() - stat.mtimeMs > INDEX_LOCK_STALE_MS) {
|
|
469
|
+
fs.rmSync(lockPath, { force: true });
|
|
470
|
+
removedStale = true;
|
|
471
|
+
}
|
|
472
|
+
} catch (_statErr) {
|
|
473
|
+
removedStale = false;
|
|
474
|
+
}
|
|
475
|
+
if (removedStale) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (Date.now() - startedAt >= INDEX_LOCK_TIMEOUT_MS) {
|
|
480
|
+
throw new Error(`工作区索引正被其他进程占用: ${indexPath}`);
|
|
481
|
+
}
|
|
482
|
+
sleepSync(INDEX_LOCK_RETRY_MS);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function releaseWorkspaceIndexLock(lockHandle) {
|
|
488
|
+
if (!lockHandle) return;
|
|
489
|
+
try {
|
|
490
|
+
if (typeof lockHandle.fd === "number") {
|
|
491
|
+
fs.closeSync(lockHandle.fd);
|
|
492
|
+
}
|
|
493
|
+
} catch (_closeErr) {
|
|
494
|
+
// ignore
|
|
495
|
+
}
|
|
496
|
+
try {
|
|
497
|
+
fs.rmSync(lockHandle.lockPath, { force: true });
|
|
498
|
+
} catch (_rmErr) {
|
|
499
|
+
// ignore
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function withWorkspaceIndexLock(indexPath, fn) {
|
|
504
|
+
const lockHandle = acquireWorkspaceIndexLock(indexPath);
|
|
505
|
+
try {
|
|
506
|
+
return fn();
|
|
507
|
+
} finally {
|
|
508
|
+
releaseWorkspaceIndexLock(lockHandle);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function evaluateWorkspaceExclusion(index, workspaceRoot) {
|
|
513
|
+
const normalizedPath = normalizeAbsolutePath(workspaceRoot);
|
|
514
|
+
const excludedPaths = Array.isArray(index.excludedPaths) ? index.excludedPaths : [];
|
|
515
|
+
|
|
516
|
+
if (isPathExcludedByList(excludedPaths, normalizedPath)) {
|
|
517
|
+
return {
|
|
518
|
+
excluded: true,
|
|
519
|
+
reason: "命中用户排除清单",
|
|
520
|
+
path: normalizedPath,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (isToolkitSourceDirectory(normalizedPath)) {
|
|
525
|
+
return {
|
|
526
|
+
excluded: true,
|
|
527
|
+
reason: "检测为 Ag-Kit 工具包源码目录(默认排除)",
|
|
528
|
+
path: normalizedPath,
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (isSystemTempDirectory(normalizedPath)) {
|
|
533
|
+
return {
|
|
534
|
+
excluded: true,
|
|
535
|
+
reason: "检测为系统临时目录(默认排除)",
|
|
536
|
+
path: normalizedPath,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return {
|
|
541
|
+
excluded: false,
|
|
542
|
+
reason: "",
|
|
543
|
+
path: normalizedPath,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function removeWorkspaceRecord(index, workspaceRoot) {
|
|
548
|
+
const normalizedPath = normalizeAbsolutePath(workspaceRoot);
|
|
549
|
+
const targetKey = pathCompareKey(normalizedPath);
|
|
550
|
+
const before = index.workspaces.length;
|
|
551
|
+
index.workspaces = index.workspaces.filter((item) => pathCompareKey(item.path) !== targetKey);
|
|
552
|
+
return before - index.workspaces.length;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function upsertWorkspaceTarget(index, workspaceRoot, targetName, timestamp) {
|
|
556
|
+
const normalizedPath = normalizeAbsolutePath(workspaceRoot);
|
|
557
|
+
const targetKey = pathCompareKey(normalizedPath);
|
|
558
|
+
|
|
559
|
+
let record = index.workspaces.find((item) => pathCompareKey(item.path) === targetKey);
|
|
560
|
+
if (!record) {
|
|
561
|
+
record = { path: normalizedPath, targets: {} };
|
|
562
|
+
index.workspaces.push(record);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (!record.targets || typeof record.targets !== "object") {
|
|
566
|
+
record.targets = {};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const prev = normalizeTargetState(record.targets[targetName]) || {
|
|
570
|
+
version: "",
|
|
571
|
+
installedAt: "",
|
|
572
|
+
updatedAt: "",
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
record.targets[targetName] = {
|
|
576
|
+
version: pkg.version,
|
|
577
|
+
installedAt: prev.installedAt || timestamp,
|
|
578
|
+
updatedAt: timestamp,
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function previewWorkspaceIndexRegistration(workspaceRoot, targetName, options) {
|
|
583
|
+
const { indexPath, index } = readWorkspaceIndex();
|
|
584
|
+
const exclusion = evaluateWorkspaceExclusion(index, workspaceRoot);
|
|
585
|
+
const normalizedPath = normalizeAbsolutePath(workspaceRoot);
|
|
586
|
+
|
|
587
|
+
if (exclusion.excluded) {
|
|
588
|
+
const exists = index.workspaces.some((item) => pathCompareKey(item.path) === pathCompareKey(normalizedPath));
|
|
589
|
+
log(options, `[dry-run] 索引登记已跳过: ${exclusion.reason}`);
|
|
590
|
+
if (exists) {
|
|
591
|
+
log(options, `[dry-run] 将从索引中移除已存在记录: ${normalizedPath}`);
|
|
592
|
+
}
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const exists = index.workspaces.some((item) => pathCompareKey(item.path) === pathCompareKey(normalizedPath));
|
|
597
|
+
if (exists) {
|
|
598
|
+
log(options, `[dry-run] 将刷新工作区索引记录: ${normalizedPath} [${targetName}]`);
|
|
599
|
+
} else {
|
|
600
|
+
log(options, `[dry-run] 将登记工作区到全局索引: ${normalizedPath} [${targetName}]`);
|
|
601
|
+
}
|
|
602
|
+
log(options, `[dry-run] 索引文件: ${indexPath}`);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function registerWorkspaceTarget(workspaceRoot, targetName, options) {
|
|
606
|
+
if (options.noIndex) {
|
|
607
|
+
if (!options.silentIndexLog) {
|
|
608
|
+
log(options, `⏭️ 已跳过索引登记: ${normalizeAbsolutePath(workspaceRoot)}`);
|
|
609
|
+
log(options, " 原因: 启用了 --no-index");
|
|
610
|
+
}
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (options.dryRun) {
|
|
615
|
+
const normalizedPath = normalizeAbsolutePath(workspaceRoot);
|
|
616
|
+
previewWorkspaceIndexRegistration(normalizedPath, targetName, options);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const normalizedPath = normalizeAbsolutePath(workspaceRoot);
|
|
621
|
+
const indexPath = getWorkspaceIndexPath();
|
|
622
|
+
const timestamp = nowISO();
|
|
623
|
+
let removedCount = 0;
|
|
624
|
+
let exclusionReason = "";
|
|
625
|
+
let excluded = false;
|
|
626
|
+
|
|
627
|
+
withWorkspaceIndexLock(indexPath, () => {
|
|
628
|
+
const { index } = readWorkspaceIndex();
|
|
629
|
+
const exclusion = evaluateWorkspaceExclusion(index, normalizedPath);
|
|
630
|
+
excluded = exclusion.excluded;
|
|
631
|
+
exclusionReason = exclusion.reason;
|
|
632
|
+
|
|
633
|
+
if (exclusion.excluded) {
|
|
634
|
+
removedCount = removeWorkspaceRecord(index, normalizedPath);
|
|
635
|
+
if (removedCount > 0) {
|
|
636
|
+
index.updatedAt = timestamp;
|
|
637
|
+
writeWorkspaceIndex(indexPath, index);
|
|
638
|
+
}
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
upsertWorkspaceTarget(index, normalizedPath, targetName, timestamp);
|
|
643
|
+
index.updatedAt = timestamp;
|
|
644
|
+
writeWorkspaceIndex(indexPath, index);
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
if (excluded) {
|
|
648
|
+
if (!options.silentIndexLog) {
|
|
649
|
+
log(options, `⏭️ 已跳过索引登记: ${normalizedPath}`);
|
|
650
|
+
log(options, ` 原因: ${exclusionReason}`);
|
|
651
|
+
if (removedCount > 0) {
|
|
652
|
+
log(options, `🧹 已清理旧索引记录: ${normalizedPath}`);
|
|
653
|
+
}
|
|
654
|
+
log(options, ` 索引文件: ${indexPath}`);
|
|
655
|
+
}
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (!options.silentIndexLog) {
|
|
660
|
+
log(options, `🗂️ 已登记工作区索引: ${normalizedPath} [${targetName}]`);
|
|
661
|
+
log(options, ` 索引文件: ${indexPath}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function maybeWarnUpstreamGlobalConflict(command, options) {
|
|
666
|
+
if (options.quiet) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (process.env.AG_KIT_SKIP_UPSTREAM_CHECK === "1") {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
if (command !== "init" && command !== "update" && command !== "update-all") {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const deps = readGlobalNpmDependencies();
|
|
677
|
+
if (!deps) {
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (!Object.prototype.hasOwnProperty.call(deps, UPSTREAM_GLOBAL_PACKAGE)) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
log(options, `⚠️ 检测到全局已安装上游英文版 ${UPSTREAM_GLOBAL_PACKAGE}。`);
|
|
686
|
+
log(options, "⚠️ 上游英文版与当前中文版共用 `ag-kit` 命令名,后安装者会覆盖命令入口。");
|
|
687
|
+
log(options, `👉 建议执行: npm uninstall -g ${UPSTREAM_GLOBAL_PACKAGE}`);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function normalizeTargets(rawTargets) {
|
|
691
|
+
const result = [];
|
|
692
|
+
const seen = new Set();
|
|
693
|
+
|
|
694
|
+
for (const raw of rawTargets || []) {
|
|
695
|
+
if (typeof raw !== "string") {
|
|
696
|
+
continue;
|
|
697
|
+
}
|
|
698
|
+
const parts = raw.split(",");
|
|
699
|
+
for (const part of parts) {
|
|
700
|
+
const target = part.trim().toLowerCase();
|
|
701
|
+
if (!target) {
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
if (!SUPPORTED_TARGETS.includes(target)) {
|
|
705
|
+
throw new Error(`不支持的目标: ${target}(可选: ${SUPPORTED_TARGETS.join(", ")})`);
|
|
706
|
+
}
|
|
707
|
+
if (!seen.has(target)) {
|
|
708
|
+
seen.add(target);
|
|
709
|
+
result.push(target);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
return result;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
function detectInstalledTargets(workspaceRoot) {
|
|
718
|
+
const targets = [];
|
|
719
|
+
if (fs.existsSync(path.join(workspaceRoot, ".agent"))) {
|
|
720
|
+
targets.push("gemini");
|
|
721
|
+
}
|
|
722
|
+
if (fs.existsSync(path.join(workspaceRoot, ".agents")) || fs.existsSync(path.join(workspaceRoot, ".codex"))) {
|
|
723
|
+
targets.push("codex");
|
|
724
|
+
}
|
|
725
|
+
return targets;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function isTargetInstalled(workspaceRoot, targetName) {
|
|
729
|
+
if (targetName === "gemini") {
|
|
730
|
+
return fs.existsSync(path.join(workspaceRoot, ".agent"));
|
|
731
|
+
}
|
|
732
|
+
if (targetName === "codex") {
|
|
733
|
+
return fs.existsSync(path.join(workspaceRoot, ".agents")) || fs.existsSync(path.join(workspaceRoot, ".codex"));
|
|
734
|
+
}
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function createAdapter(targetName, workspaceRoot, options) {
|
|
739
|
+
if (targetName === "gemini") {
|
|
740
|
+
return new GeminiAdapter(workspaceRoot, options);
|
|
741
|
+
}
|
|
742
|
+
if (targetName === "codex") {
|
|
743
|
+
return new CodexAdapter(workspaceRoot, options);
|
|
744
|
+
}
|
|
745
|
+
throw new Error(`未知目标: ${targetName}`);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
async function resolveTargetsForInit(options) {
|
|
749
|
+
let targets = normalizeTargets(options.targets);
|
|
750
|
+
|
|
751
|
+
if (targets.length > 0) {
|
|
752
|
+
return targets;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (options.nonInteractive) {
|
|
756
|
+
throw new Error("非交互模式下必须通过 --target 或 --targets 指定目标");
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
760
|
+
throw new Error("当前环境不是交互终端,请通过 --target 或 --targets 指定目标");
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
targets = normalizeTargets(await selectTargets(options));
|
|
764
|
+
if (targets.length === 0) {
|
|
765
|
+
throw new Error("必须选择至少一个目标");
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
return targets;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function resolveTargetsForUpdate(workspaceRoot, options) {
|
|
772
|
+
const requested = normalizeTargets(options.targets);
|
|
773
|
+
if (requested.length > 0) {
|
|
774
|
+
return requested;
|
|
775
|
+
}
|
|
776
|
+
return detectInstalledTargets(workspaceRoot);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async function commandInit(options) {
|
|
780
|
+
const workspaceRoot = resolveWorkspaceRoot(options.path);
|
|
781
|
+
const targets = await resolveTargetsForInit(options);
|
|
782
|
+
|
|
783
|
+
for (const target of targets) {
|
|
784
|
+
const adapter = createAdapter(target, workspaceRoot, options);
|
|
785
|
+
log(options, `📦 正在初始化目标 [${target}] ...`);
|
|
786
|
+
adapter.install(BUNDLED_AGENT_DIR);
|
|
787
|
+
registerWorkspaceTarget(workspaceRoot, target, options);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (targets.length > 0) {
|
|
791
|
+
log(options, `✅ 初始化完成 (Targets: ${targets.join(", ")})`);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
async function commandUpdate(options) {
|
|
796
|
+
const workspaceRoot = resolveWorkspaceRoot(options.path);
|
|
797
|
+
const targets = resolveTargetsForUpdate(workspaceRoot, options);
|
|
798
|
+
|
|
799
|
+
if (targets.length === 0) {
|
|
800
|
+
throw new Error("此目录未检测到 Antigravity Kit 安装,无法更新。请先执行 init。");
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
log(options, `🔄 正在更新 Antigravity Kit (Targets: ${targets.join(", ")})...`);
|
|
804
|
+
|
|
805
|
+
let updatedAny = false;
|
|
806
|
+
for (const target of targets) {
|
|
807
|
+
if (!isTargetInstalled(workspaceRoot, target) && options.targets.length > 0) {
|
|
808
|
+
throw new Error(`目标未安装: ${target}`);
|
|
809
|
+
}
|
|
810
|
+
if (!isTargetInstalled(workspaceRoot, target)) {
|
|
811
|
+
log(options, `⏭️ 目标未安装,跳过: ${target}`);
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const runOptions = { ...options, force: true };
|
|
816
|
+
const adapter = createAdapter(target, workspaceRoot, runOptions);
|
|
817
|
+
log(options, `📦 更新 [${target}] ...`);
|
|
818
|
+
adapter.update(BUNDLED_AGENT_DIR);
|
|
819
|
+
registerWorkspaceTarget(workspaceRoot, target, runOptions);
|
|
820
|
+
updatedAny = true;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (!updatedAny) {
|
|
824
|
+
throw new Error("未找到可更新的目标");
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function mergeUpdatedTargets(record, workspacePath, targetNames, timestamp) {
|
|
829
|
+
const normalizedPath = normalizeAbsolutePath(workspacePath);
|
|
830
|
+
const next = normalizeWorkspaceRecordV2(record || {}, normalizedPath);
|
|
831
|
+
|
|
832
|
+
for (const target of targetNames) {
|
|
833
|
+
const prev = normalizeTargetState(next.targets[target]) || {
|
|
834
|
+
version: "",
|
|
835
|
+
installedAt: "",
|
|
836
|
+
updatedAt: "",
|
|
837
|
+
};
|
|
838
|
+
next.targets[target] = {
|
|
839
|
+
version: pkg.version,
|
|
840
|
+
installedAt: prev.installedAt || timestamp,
|
|
841
|
+
updatedAt: timestamp,
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return next;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
async function commandUpdateAll(options) {
|
|
849
|
+
if (options.path) {
|
|
850
|
+
throw new Error("update-all 不支持 --path,请直接执行 ag-kit update-all");
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const requestedTargets = normalizeTargets(options.targets);
|
|
854
|
+
const { indexPath, index } = readWorkspaceIndex();
|
|
855
|
+
const records = index.workspaces || [];
|
|
856
|
+
|
|
857
|
+
if (records.length === 0) {
|
|
858
|
+
log(options, "ℹ️ 全局索引为空,没有可批量更新的工作区。");
|
|
859
|
+
log(options, " 先在项目中执行 ag-kit init 或 ag-kit update 建立索引。");
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
log(options, `🔄 开始批量更新工作区(共 ${records.length} 个)...`);
|
|
864
|
+
log(options, `📚 索引文件: ${indexPath}`);
|
|
865
|
+
|
|
866
|
+
let updated = 0;
|
|
867
|
+
let skipped = 0;
|
|
868
|
+
let failed = 0;
|
|
869
|
+
let removedMissing = 0;
|
|
870
|
+
let removedExcluded = 0;
|
|
871
|
+
const timestamp = nowISO();
|
|
872
|
+
const nextRecords = [];
|
|
873
|
+
const removedRecordKeys = new Set();
|
|
874
|
+
|
|
875
|
+
for (let i = 0; i < records.length; i++) {
|
|
876
|
+
const item = normalizeWorkspaceRecordV2(records[i], normalizeAbsolutePath(records[i].path));
|
|
877
|
+
const workspacePath = normalizeAbsolutePath(item.path);
|
|
878
|
+
const exclusion = evaluateWorkspaceExclusion(index, workspacePath);
|
|
879
|
+
|
|
880
|
+
if (exclusion.excluded) {
|
|
881
|
+
removedExcluded += 1;
|
|
882
|
+
removedRecordKeys.add(pathCompareKey(workspacePath));
|
|
883
|
+
if (options.dryRun) {
|
|
884
|
+
log(options, `[dry-run] [${i + 1}/${records.length}] 将从批量索引移除排除路径: ${workspacePath}(${exclusion.reason})`);
|
|
885
|
+
} else {
|
|
886
|
+
log(options, `🧽 [${i + 1}/${records.length}] 已从批量索引中移除排除路径: ${workspacePath}(${exclusion.reason})`);
|
|
887
|
+
}
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
if (!fs.existsSync(workspacePath)) {
|
|
892
|
+
if (options.pruneMissing) {
|
|
893
|
+
removedMissing += 1;
|
|
894
|
+
removedRecordKeys.add(pathCompareKey(workspacePath));
|
|
895
|
+
log(options, `🧽 [${i + 1}/${records.length}] 已移除失效工作区索引: ${workspacePath}`);
|
|
896
|
+
} else {
|
|
897
|
+
skipped += 1;
|
|
898
|
+
log(options, `⏭️ [${i + 1}/${records.length}] 工作区不存在,已跳过: ${workspacePath}`);
|
|
899
|
+
nextRecords.push(item);
|
|
900
|
+
}
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
const installedTargets = detectInstalledTargets(workspacePath);
|
|
905
|
+
let targets = [];
|
|
906
|
+
if (requestedTargets.length > 0) {
|
|
907
|
+
targets = installedTargets.filter((target) => requestedTargets.includes(target));
|
|
908
|
+
} else {
|
|
909
|
+
targets = [...Object.keys(item.targets || {}), ...installedTargets];
|
|
910
|
+
}
|
|
911
|
+
targets = normalizeTargets(targets);
|
|
912
|
+
|
|
913
|
+
if (targets.length === 0) {
|
|
914
|
+
skipped += 1;
|
|
915
|
+
log(options, `⏭️ [${i + 1}/${records.length}] 未检测到可更新目标,已跳过: ${workspacePath}`);
|
|
916
|
+
nextRecords.push(item);
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
log(options, `📦 [${i + 1}/${records.length}] 更新: ${workspacePath} [${targets.join(", ")}]`);
|
|
921
|
+
|
|
922
|
+
const updatedTargets = [];
|
|
923
|
+
for (const target of targets) {
|
|
924
|
+
if (!isTargetInstalled(workspacePath, target)) {
|
|
925
|
+
log(options, `⏭️ [${i + 1}/${records.length}] 目标未安装,跳过: ${target}`);
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
try {
|
|
930
|
+
const runOptions = {
|
|
931
|
+
...options,
|
|
932
|
+
force: true,
|
|
933
|
+
path: workspacePath,
|
|
934
|
+
silentIndexLog: true,
|
|
935
|
+
};
|
|
936
|
+
const adapter = createAdapter(target, workspacePath, runOptions);
|
|
937
|
+
adapter.update(BUNDLED_AGENT_DIR);
|
|
938
|
+
updatedTargets.push(target);
|
|
939
|
+
} catch (err) {
|
|
940
|
+
failed += 1;
|
|
941
|
+
if (!options.quiet) {
|
|
942
|
+
console.error(`❌ 更新失败: ${workspacePath} [${target}]`);
|
|
943
|
+
console.error(` ${err.message}`);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
if (updatedTargets.length > 0) {
|
|
949
|
+
updated += 1;
|
|
950
|
+
nextRecords.push(mergeUpdatedTargets(item, workspacePath, updatedTargets, timestamp));
|
|
951
|
+
} else {
|
|
952
|
+
skipped += 1;
|
|
953
|
+
nextRecords.push(item);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
if (!options.dryRun) {
|
|
958
|
+
withWorkspaceIndexLock(indexPath, () => {
|
|
959
|
+
const { index: latestIndex } = readWorkspaceIndex();
|
|
960
|
+
const mergedMap = new Map();
|
|
961
|
+
|
|
962
|
+
for (const item of latestIndex.workspaces || []) {
|
|
963
|
+
if (!item || typeof item.path !== "string") continue;
|
|
964
|
+
mergedMap.set(pathCompareKey(item.path), normalizeWorkspaceRecordV2(item, normalizeAbsolutePath(item.path)));
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
for (const removedKey of removedRecordKeys) {
|
|
968
|
+
mergedMap.delete(removedKey);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
for (const item of nextRecords) {
|
|
972
|
+
if (!item || typeof item.path !== "string") continue;
|
|
973
|
+
mergedMap.set(pathCompareKey(item.path), normalizeWorkspaceRecordV2(item, normalizeAbsolutePath(item.path)));
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
latestIndex.workspaces = Array.from(mergedMap.values()).sort((a, b) => a.path.localeCompare(b.path));
|
|
977
|
+
latestIndex.updatedAt = timestamp;
|
|
978
|
+
writeWorkspaceIndex(indexPath, latestIndex);
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
log(options, "📊 批量更新完成");
|
|
983
|
+
log(options, ` 成功: ${updated}`);
|
|
984
|
+
log(options, ` 跳过: ${skipped}`);
|
|
985
|
+
log(options, ` 失败: ${failed}`);
|
|
986
|
+
log(options, ` 清理排除路径: ${removedExcluded}`);
|
|
987
|
+
if (options.pruneMissing) {
|
|
988
|
+
log(options, ` 清理失效索引: ${removedMissing}`);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
if (failed > 0) {
|
|
992
|
+
process.exitCode = 1;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
async function commandDoctor(options) {
|
|
997
|
+
const workspaceRoot = resolveWorkspaceRoot(options.path);
|
|
998
|
+
let targets = normalizeTargets(options.targets);
|
|
999
|
+
const out = (message) => {
|
|
1000
|
+
if (!options.quiet) {
|
|
1001
|
+
console.log(message);
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
if (targets.length === 0) {
|
|
1006
|
+
targets = detectInstalledTargets(workspaceRoot);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
if (targets.length === 0) {
|
|
1010
|
+
throw new Error("未检测到已安装的目标。请指定 --target 或先执行 init。");
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
log(options, `🩺 开始诊断 (Targets: ${targets.join(", ")})...`);
|
|
1014
|
+
|
|
1015
|
+
let hasIssue = false;
|
|
1016
|
+
for (const target of targets) {
|
|
1017
|
+
const adapter = createAdapter(target, workspaceRoot, options);
|
|
1018
|
+
out(`\n[${target.toUpperCase()}] 检查完整性...`);
|
|
1019
|
+
|
|
1020
|
+
let result = adapter.checkIntegrity();
|
|
1021
|
+
if (result.status === "ok") {
|
|
1022
|
+
out(" ✅ 状态正常");
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
let targetHasIssue = true;
|
|
1027
|
+
out(` ❌ 状态: ${result.status}`);
|
|
1028
|
+
for (const issue of result.issues || []) {
|
|
1029
|
+
out(` - ${issue}`);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
if (options.fix) {
|
|
1033
|
+
const fixRes = adapter.fixIntegrity();
|
|
1034
|
+
if (fixRes && fixRes.fixed) {
|
|
1035
|
+
out(` 🛠️ 已修复: ${fixRes.summary}`);
|
|
1036
|
+
result = adapter.checkIntegrity();
|
|
1037
|
+
if (result.status === "ok") {
|
|
1038
|
+
out(" ✅ 修复后状态正常");
|
|
1039
|
+
targetHasIssue = false;
|
|
1040
|
+
} else {
|
|
1041
|
+
out(` ⚠️ 修复后仍有问题: ${result.status}`);
|
|
1042
|
+
for (const issue of result.issues || []) {
|
|
1043
|
+
out(` - ${issue}`);
|
|
1044
|
+
}
|
|
1045
|
+
targetHasIssue = true;
|
|
1046
|
+
}
|
|
1047
|
+
} else {
|
|
1048
|
+
out(` ℹ️ 自动修复未执行: ${fixRes ? fixRes.summary : "无可用修复"}`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
if (targetHasIssue) {
|
|
1053
|
+
hasIssue = true;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
if (hasIssue) {
|
|
1058
|
+
process.exitCode = 1;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function requirePathOption(options, commandUsage) {
|
|
1063
|
+
if (!options.path) {
|
|
1064
|
+
throw new Error(`${commandUsage} 需要 --path <dir> 参数`);
|
|
1065
|
+
}
|
|
1066
|
+
return resolveWorkspaceRoot(options.path);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function commandExcludeList(options) {
|
|
1070
|
+
const { indexPath, index } = readWorkspaceIndex();
|
|
1071
|
+
const excluded = Array.isArray(index.excludedPaths) ? index.excludedPaths : [];
|
|
1072
|
+
|
|
1073
|
+
if (options.quiet) {
|
|
1074
|
+
for (const item of excluded) {
|
|
1075
|
+
console.log(item);
|
|
1076
|
+
}
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
console.log("🛡️ 工作区排除清单");
|
|
1081
|
+
console.log(` 索引文件: ${indexPath}`);
|
|
1082
|
+
console.log(" 默认规则: 自动排除 Ag-Kit 工具包源码目录与系统临时目录(无需手动添加)");
|
|
1083
|
+
|
|
1084
|
+
if (excluded.length === 0) {
|
|
1085
|
+
console.log(" 当前无自定义排除路径。");
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
console.log(` 自定义排除路径 (${excluded.length}):`);
|
|
1090
|
+
for (let i = 0; i < excluded.length; i++) {
|
|
1091
|
+
console.log(` ${i + 1}. ${excluded[i]}`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function commandExcludeAdd(options) {
|
|
1096
|
+
const targetPath = requirePathOption(options, "exclude add");
|
|
1097
|
+
const indexPath = getWorkspaceIndexPath();
|
|
1098
|
+
const normalizedTarget = normalizeAbsolutePath(targetPath);
|
|
1099
|
+
const targetKey = pathCompareKey(normalizedTarget);
|
|
1100
|
+
let existed = false;
|
|
1101
|
+
let matchedCount = 0;
|
|
1102
|
+
|
|
1103
|
+
withWorkspaceIndexLock(indexPath, () => {
|
|
1104
|
+
const { index } = readWorkspaceIndex();
|
|
1105
|
+
existed = index.excludedPaths.some((item) => pathCompareKey(item) === targetKey);
|
|
1106
|
+
matchedCount = index.workspaces.filter((item) => isPathInOrUnder(normalizedTarget, item.path)).length;
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
if (options.dryRun) {
|
|
1110
|
+
if (existed) {
|
|
1111
|
+
log(options, `[dry-run] 排除路径已存在: ${normalizedTarget}`);
|
|
1112
|
+
} else {
|
|
1113
|
+
log(options, `[dry-run] 将新增排除路径: ${normalizedTarget}`);
|
|
1114
|
+
}
|
|
1115
|
+
if (matchedCount > 0) {
|
|
1116
|
+
log(options, `[dry-run] 将移除 ${matchedCount} 条已登记工作区记录(位于该排除路径下)。`);
|
|
1117
|
+
}
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
if (!existed) {
|
|
1122
|
+
withWorkspaceIndexLock(indexPath, () => {
|
|
1123
|
+
const { index } = readWorkspaceIndex();
|
|
1124
|
+
const hasTarget = index.excludedPaths.some((item) => pathCompareKey(item) === targetKey);
|
|
1125
|
+
if (!hasTarget) {
|
|
1126
|
+
index.excludedPaths.push(normalizedTarget);
|
|
1127
|
+
index.excludedPaths = normalizePathList(index.excludedPaths);
|
|
1128
|
+
}
|
|
1129
|
+
index.workspaces = index.workspaces.filter((item) => !isPathInOrUnder(normalizedTarget, item.path));
|
|
1130
|
+
index.updatedAt = nowISO();
|
|
1131
|
+
writeWorkspaceIndex(indexPath, index);
|
|
1132
|
+
});
|
|
1133
|
+
} else {
|
|
1134
|
+
withWorkspaceIndexLock(indexPath, () => {
|
|
1135
|
+
const { index } = readWorkspaceIndex();
|
|
1136
|
+
index.workspaces = index.workspaces.filter((item) => !isPathInOrUnder(normalizedTarget, item.path));
|
|
1137
|
+
index.updatedAt = nowISO();
|
|
1138
|
+
writeWorkspaceIndex(indexPath, index);
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
if (existed) {
|
|
1143
|
+
log(options, `ℹ️ 排除路径已存在: ${normalizedTarget}`);
|
|
1144
|
+
} else {
|
|
1145
|
+
log(options, `✅ 已新增排除路径: ${normalizedTarget}`);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
if (matchedCount > 0) {
|
|
1149
|
+
log(options, `🧹 已移除 ${matchedCount} 条已登记工作区记录(位于排除路径下)。`);
|
|
1150
|
+
}
|
|
1151
|
+
log(options, `📚 索引文件: ${indexPath}`);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
function commandExcludeRemove(options) {
|
|
1155
|
+
const targetPath = requirePathOption(options, "exclude remove");
|
|
1156
|
+
const indexPath = getWorkspaceIndexPath();
|
|
1157
|
+
const normalizedTarget = normalizeAbsolutePath(targetPath);
|
|
1158
|
+
const targetKey = pathCompareKey(normalizedTarget);
|
|
1159
|
+
let existed = false;
|
|
1160
|
+
|
|
1161
|
+
withWorkspaceIndexLock(indexPath, () => {
|
|
1162
|
+
const { index } = readWorkspaceIndex();
|
|
1163
|
+
existed = index.excludedPaths.some((item) => pathCompareKey(item) === targetKey);
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
if (!existed) {
|
|
1167
|
+
log(options, `ℹ️ 排除路径不存在: ${normalizedTarget}`);
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
if (options.dryRun) {
|
|
1172
|
+
log(options, `[dry-run] 将移除排除路径: ${normalizedTarget}`);
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
withWorkspaceIndexLock(indexPath, () => {
|
|
1177
|
+
const { index } = readWorkspaceIndex();
|
|
1178
|
+
index.excludedPaths = index.excludedPaths.filter((item) => pathCompareKey(item) !== targetKey);
|
|
1179
|
+
index.updatedAt = nowISO();
|
|
1180
|
+
writeWorkspaceIndex(indexPath, index);
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1183
|
+
log(options, `✅ 已移除排除路径: ${normalizedTarget}`);
|
|
1184
|
+
log(options, `📚 索引文件: ${indexPath}`);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
function commandExclude(options) {
|
|
1188
|
+
const subcommand = (options.subcommand || "list").toLowerCase();
|
|
1189
|
+
|
|
1190
|
+
if (subcommand === "list") {
|
|
1191
|
+
commandExcludeList(options);
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
if (subcommand === "add") {
|
|
1195
|
+
commandExcludeAdd(options);
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
if (subcommand === "remove") {
|
|
1199
|
+
commandExcludeRemove(options);
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
throw new Error(`未知 exclude 子命令: ${subcommand}`);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function countFilesIfExists(dir, filterFn) {
|
|
1207
|
+
if (!fs.existsSync(dir)) return 0;
|
|
1208
|
+
return fs.readdirSync(dir).filter(filterFn).length;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
function countSkillsRecursive(dir) {
|
|
1212
|
+
if (!fs.existsSync(dir)) return 0;
|
|
1213
|
+
let count = 0;
|
|
1214
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1215
|
+
for (const entry of entries) {
|
|
1216
|
+
const fullPath = path.join(dir, entry.name);
|
|
1217
|
+
if (entry.isDirectory()) {
|
|
1218
|
+
count += countSkillsRecursive(fullPath);
|
|
1219
|
+
} else if (entry.name === "SKILL.md") {
|
|
1220
|
+
count += 1;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return count;
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
function commandStatus(options) {
|
|
1227
|
+
const workspaceRoot = resolveWorkspaceRoot(options.path);
|
|
1228
|
+
const installedTargets = detectInstalledTargets(workspaceRoot);
|
|
1229
|
+
|
|
1230
|
+
if (installedTargets.length === 0) {
|
|
1231
|
+
if (!options.quiet) {
|
|
1232
|
+
console.log("❌ 未检测到 Antigravity Kit 安装");
|
|
1233
|
+
console.log(` 目标目录: ${workspaceRoot}`);
|
|
1234
|
+
}
|
|
1235
|
+
process.exitCode = 1;
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
if (options.quiet) {
|
|
1240
|
+
console.log("installed");
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
console.log("✅ Antigravity Kit 已安装");
|
|
1245
|
+
console.log(` CLI 版本: ${pkg.version}`);
|
|
1246
|
+
console.log(` 工作区: ${workspaceRoot}`);
|
|
1247
|
+
console.log(` Targets: ${installedTargets.join(", ")}`);
|
|
1248
|
+
|
|
1249
|
+
if (installedTargets.includes("gemini")) {
|
|
1250
|
+
const agentDir = path.join(workspaceRoot, ".agent");
|
|
1251
|
+
const agentsCount = countFilesIfExists(path.join(agentDir, "agents"), (name) => name.endsWith(".md"));
|
|
1252
|
+
const workflowsCount = countFilesIfExists(path.join(agentDir, "workflows"), (name) => name.endsWith(".md"));
|
|
1253
|
+
const skillsCount = countSkillsRecursive(path.join(agentDir, "skills"));
|
|
1254
|
+
console.log("\n[gemini]");
|
|
1255
|
+
console.log(` 路径: ${agentDir}`);
|
|
1256
|
+
console.log(` Agents: ${agentsCount}`);
|
|
1257
|
+
console.log(` Skills: ${skillsCount}`);
|
|
1258
|
+
console.log(` Workflows: ${workflowsCount}`);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
if (installedTargets.includes("codex")) {
|
|
1262
|
+
const managedDir = path.join(workspaceRoot, ".agents");
|
|
1263
|
+
const legacyDir = path.join(workspaceRoot, ".codex");
|
|
1264
|
+
const activeDir = fs.existsSync(managedDir) ? managedDir : legacyDir;
|
|
1265
|
+
const skillsCount = countSkillsRecursive(path.join(activeDir, "skills"));
|
|
1266
|
+
const hasManifest = fs.existsSync(path.join(activeDir, "manifest.json"));
|
|
1267
|
+
const legacyDetected = fs.existsSync(legacyDir);
|
|
1268
|
+
console.log("\n[codex]");
|
|
1269
|
+
console.log(` 路径: ${activeDir}`);
|
|
1270
|
+
console.log(` Skills: ${skillsCount}`);
|
|
1271
|
+
console.log(` Manifest: ${hasManifest ? "yes" : "no"}`);
|
|
1272
|
+
if (legacyDetected) {
|
|
1273
|
+
console.log(" Legacy: 检测到 .codex(建议执行 ag-kit update 迁移清理)");
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
async function main() {
|
|
1279
|
+
try {
|
|
1280
|
+
const { command, options, providedFlags } = parseArgs(process.argv.slice(2));
|
|
1281
|
+
|
|
1282
|
+
if (!command || command === "--help" || command === "-h") {
|
|
1283
|
+
printUsage();
|
|
1284
|
+
if (!command || command === "--help" || command === "-h") {
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
if (command === "--version" || command === "-v") {
|
|
1290
|
+
printVersion();
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
validateOptionScope(command, options, providedFlags);
|
|
1295
|
+
maybeWarnUpstreamGlobalConflict(command, options);
|
|
1296
|
+
|
|
1297
|
+
if (command === "init") {
|
|
1298
|
+
await commandInit(options);
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
if (command === "update") {
|
|
1303
|
+
await commandUpdate(options);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
if (command === "update-all") {
|
|
1308
|
+
await commandUpdateAll(options);
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
if (command === "doctor") {
|
|
1313
|
+
await commandDoctor(options);
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
if (command === "exclude") {
|
|
1318
|
+
commandExclude(options);
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
if (command === "status") {
|
|
1323
|
+
commandStatus(options);
|
|
1324
|
+
return;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
console.error(`未知命令: ${command}`);
|
|
1328
|
+
printUsage();
|
|
1329
|
+
process.exitCode = 1;
|
|
1330
|
+
} catch (err) {
|
|
1331
|
+
console.error(`❌ ${err.message}`);
|
|
1332
|
+
process.exitCode = 1;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
main();
|