airail 0.1.0 → 0.1.2

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/dist/cli/add.js CHANGED
@@ -45,7 +45,7 @@ async function cmdAdd(arg) {
45
45
  const config = (0, utils_1.requireAirailJson)(claudeDir);
46
46
  if (!arg) {
47
47
  console.error((0, colors_1.err)('用法: airail add <包名[@版本]|路径>'));
48
- process.exit(1);
48
+ throw new Error('');
49
49
  }
50
50
  if (config.pack) {
51
51
  console.log((0, colors_1.warn)(`当前已安装 pack: ${config.pack}`));
@@ -56,7 +56,7 @@ async function cmdAdd(arg) {
56
56
  const localPath = path.resolve(arg);
57
57
  if (!fs.existsSync(localPath)) {
58
58
  console.error((0, colors_1.err)(`路径不存在: ${localPath}`));
59
- process.exit(1);
59
+ throw new Error('');
60
60
  }
61
61
  const packName = readPackName(localPath);
62
62
  (0, utils_1.installPackFromDir)(localPath, packName, claudeDir);
@@ -69,7 +69,7 @@ async function cmdAdd(arg) {
69
69
  const rc = (0, utils_1.readRc)();
70
70
  if (!rc.configServer) {
71
71
  console.error((0, colors_1.err)('未配置配置仓库,请先执行: airail config setup'));
72
- process.exit(1);
72
+ throw new Error('');
73
73
  }
74
74
  await installPackFromConfigCenter(arg, claudeDir, config, rc);
75
75
  (0, utils_1.writeAirailJson)(claudeDir, config);
@@ -113,13 +113,13 @@ async function installPackFromConfigCenter(packNameWithVersion, claudeDir, confi
113
113
  }
114
114
  catch (e) {
115
115
  console.error((0, colors_1.err)(`获取规范包列表失败: ${e.message}`));
116
- process.exit(1);
116
+ throw new Error('');
117
117
  }
118
118
  const pack = packs.find(p => p.name === packName);
119
119
  if (!pack) {
120
120
  console.error((0, colors_1.err)(`规范包 "${packName}" 不存在。`));
121
121
  console.log((0, colors_1.info)(`可用规范包: ${packs.map(p => p.name).join(', ')}`));
122
- process.exit(1);
122
+ throw new Error('');
123
123
  }
124
124
  // 使用用户指定的版本,如果没有指定则使用配置仓库的版本
125
125
  const gitRef = userVersion || pack.gitRef;
@@ -89,7 +89,7 @@ async function setup() {
89
89
  validate: (v) => v.startsWith('http') || '请输入有效的 Git 仓库 URL',
90
90
  });
91
91
  const token = await (0, prompts_1.input)({
92
- message: '访问 Token (私有仓库需要,留空跳过):',
92
+ message: '访问 Token (私有仓库需要,如果 Git 已存储凭据可留空):',
93
93
  default: rc.configToken,
94
94
  });
95
95
  // 清理旧的本地仓库(如果 URL 变了)
@@ -104,19 +104,19 @@ async function listConfigs() {
104
104
  const rc = (0, utils_1.readRc)();
105
105
  if (!rc.configServer) {
106
106
  console.error((0, colors_1.err)('未配置配置仓库,请先执行: airail config setup'));
107
- process.exit(1);
107
+ throw new Error('');
108
108
  }
109
109
  try {
110
110
  ensureConfigRepo(rc.configServer, rc.configToken);
111
111
  }
112
112
  catch (e) {
113
113
  console.error((0, colors_1.err)(e.message));
114
- process.exit(1);
114
+ throw new Error('');
115
115
  }
116
116
  const indexPath = path.join(CONFIG_REPO_DIR, 'settings', 'index.json');
117
117
  if (!fs.existsSync(indexPath)) {
118
118
  console.error((0, colors_1.err)('配置仓库中未找到 settings/index.json'));
119
- process.exit(1);
119
+ throw new Error('');
120
120
  }
121
121
  let entries;
122
122
  try {
@@ -124,7 +124,7 @@ async function listConfigs() {
124
124
  }
125
125
  catch (e) {
126
126
  console.error((0, colors_1.err)(`读取配置列表失败: ${e.message}`));
127
- process.exit(1);
127
+ throw new Error('');
128
128
  }
129
129
  if (entries.length === 0) {
130
130
  console.log((0, colors_1.warn)('暂无可用配置。'));
@@ -139,20 +139,20 @@ async function useConfig(name) {
139
139
  const rc = (0, utils_1.readRc)();
140
140
  if (!rc.configServer) {
141
141
  console.error((0, colors_1.err)('未配置配置仓库,请先执行: airail config setup'));
142
- process.exit(1);
142
+ throw new Error('');
143
143
  }
144
144
  try {
145
145
  ensureConfigRepo(rc.configServer, rc.configToken);
146
146
  }
147
147
  catch (e) {
148
148
  console.error((0, colors_1.err)(e.message));
149
- process.exit(1);
149
+ throw new Error('');
150
150
  }
151
151
  // 获取配置清单
152
152
  const indexPath = path.join(CONFIG_REPO_DIR, 'settings', 'index.json');
153
153
  if (!fs.existsSync(indexPath)) {
154
154
  console.error((0, colors_1.err)('配置仓库中未找到 settings/index.json'));
155
- process.exit(1);
155
+ throw new Error('');
156
156
  }
157
157
  let entries;
158
158
  try {
@@ -160,7 +160,7 @@ async function useConfig(name) {
160
160
  }
161
161
  catch (e) {
162
162
  console.error((0, colors_1.err)(`读取配置列表失败: ${e.message}`));
163
- process.exit(1);
163
+ throw new Error('');
164
164
  }
165
165
  if (entries.length === 0) {
166
166
  console.log((0, colors_1.warn)('暂无可用配置。'));
@@ -171,7 +171,7 @@ async function useConfig(name) {
171
171
  if (name) {
172
172
  if (!entries.find(e => e.name === name)) {
173
173
  console.error((0, colors_1.err)(`配置 "${name}" 不存在,可用: ${entries.map(e => e.name).join(', ')}`));
174
- process.exit(1);
174
+ throw new Error('');
175
175
  }
176
176
  chosen = name;
177
177
  }
@@ -188,7 +188,7 @@ async function useConfig(name) {
188
188
  const settingsPath = path.join(CONFIG_REPO_DIR, 'settings', `${chosen}.json`);
189
189
  if (!fs.existsSync(settingsPath)) {
190
190
  console.error((0, colors_1.err)(`配置文件不存在: settings/${chosen}.json`));
191
- process.exit(1);
191
+ throw new Error('');
192
192
  }
193
193
  let content;
194
194
  try {
@@ -197,7 +197,7 @@ async function useConfig(name) {
197
197
  }
198
198
  catch (e) {
199
199
  console.error((0, colors_1.err)(`读取配置失败: ${e.message}`));
200
- process.exit(1);
200
+ throw new Error('');
201
201
  }
202
202
  const globalClaudeDir = path.join(os.homedir(), '.claude');
203
203
  if (!fs.existsSync(globalClaudeDir)) {
package/dist/cli/index.js CHANGED
@@ -41,6 +41,7 @@ const clear_1 = require("./clear");
41
41
  const update_1 = require("./update");
42
42
  const status_1 = require("./status");
43
43
  const config_1 = require("./config");
44
+ const install_1 = require("./install");
44
45
  function printBanner() {
45
46
  console.log([
46
47
  '',
@@ -59,6 +60,7 @@ function printHelp() {
59
60
  const cmds = [
60
61
  ['init', '在当前项目初始化 airail'],
61
62
  ['add <url|路径>', '安装规范包(git URL 或本地路径)'],
63
+ ['install [工具名]', '安装 CLI 工具(如 claude-code)'],
62
64
  ['clear', '清理 airail 相关内容'],
63
65
  ['update', '更新已安装的规范包'],
64
66
  ['config [子命令]', '管理团队配置仓库 (setup / list / use)'],
@@ -74,27 +76,31 @@ async function executeCommand(cmd, args) {
74
76
  const commands = {
75
77
  init: init_1.cmdInit,
76
78
  add: () => (0, add_1.cmdAdd)(args[0]),
79
+ install: () => (0, install_1.cmdInstall)(args[0]),
77
80
  clear: clear_1.cmdClear,
78
81
  update: update_1.cmdUpdate,
79
82
  status: status_1.cmdStatus,
80
83
  config: () => (0, config_1.cmdConfig)(args[0], ...args.slice(1)),
81
84
  };
82
85
  if (cmd === 'exit') {
83
- return false;
86
+ return { shouldContinue: false, success: true };
84
87
  }
85
88
  const handler = commands[cmd];
86
89
  if (!handler) {
87
90
  console.log(`未知命令: ${cmd}`);
88
91
  printHelp();
89
- return true;
92
+ return { shouldContinue: true, success: false };
90
93
  }
91
94
  try {
92
95
  await handler();
96
+ return { shouldContinue: true, success: true };
93
97
  }
94
98
  catch (e) {
95
- console.error(e.message);
99
+ if (e.message) {
100
+ console.error(e.message);
101
+ }
102
+ return { shouldContinue: true, success: false };
96
103
  }
97
- return true;
98
104
  }
99
105
  function startInteractiveMode() {
100
106
  printBanner();
@@ -113,7 +119,7 @@ function startInteractiveMode() {
113
119
  return;
114
120
  }
115
121
  const [cmd, ...args] = input.split(/\s+/);
116
- const shouldContinue = await executeCommand(cmd, args);
122
+ const { shouldContinue } = await executeCommand(cmd, args);
117
123
  if (!shouldContinue) {
118
124
  rl.close();
119
125
  process.exit(0);
@@ -133,9 +139,9 @@ function runCLI() {
133
139
  return;
134
140
  }
135
141
  // 有参数:直接执行命令(不显示 banner)
136
- executeCommand(cmd, args).then((shouldContinue) => {
137
- if (!shouldContinue) {
138
- process.exit(0);
142
+ executeCommand(cmd, args).then(({ shouldContinue, success }) => {
143
+ if (!shouldContinue || !success) {
144
+ process.exit(success ? 0 : 1);
139
145
  }
140
146
  }).catch((e) => {
141
147
  console.error(e.message);
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cmdInstall = cmdInstall;
4
+ const child_process_1 = require("child_process");
5
+ const prompts_1 = require("@inquirer/prompts");
6
+ const CLI_TOOLS = {
7
+ 'claude-code': {
8
+ name: 'Claude Code',
9
+ package: '@anthropic-ai/claude-code',
10
+ description: 'Anthropic 官方 Claude CLI 工具'
11
+ },
12
+ // 预留扩展
13
+ // 'codex': {
14
+ // name: 'Codex',
15
+ // package: 'codex-cli',
16
+ // description: 'OpenAI Codex CLI 工具'
17
+ // }
18
+ };
19
+ async function cmdInstall(toolName) {
20
+ try {
21
+ let selectedTool = toolName;
22
+ if (!selectedTool) {
23
+ selectedTool = await (0, prompts_1.select)({
24
+ message: '选择要安装的 CLI 工具:',
25
+ choices: Object.entries(CLI_TOOLS).map(([key, tool]) => ({
26
+ value: key,
27
+ name: `${tool.name} - ${tool.description}`
28
+ }))
29
+ });
30
+ }
31
+ const tool = CLI_TOOLS[selectedTool];
32
+ if (!tool) {
33
+ console.log(`\x1b[31m✘ 未知工具: ${selectedTool}\x1b[0m`);
34
+ return;
35
+ }
36
+ console.log(`\n→ 安装 ${tool.name}...`);
37
+ (0, child_process_1.execSync)(`npm install -g ${tool.package}`, { stdio: 'inherit' });
38
+ console.log(`\x1b[32m✔ ${tool.name} 安装成功\x1b[0m\n`);
39
+ }
40
+ catch (e) {
41
+ console.error(`\x1b[31m✘ 安装失败: ${e.message}\x1b[0m`);
42
+ }
43
+ }
@@ -42,10 +42,10 @@ async function cmdUpdate() {
42
42
  const config = (0, utils_1.requireAirailJson)(claudeDir);
43
43
  if (!config.pack) {
44
44
  console.error((0, colors_1.err)('未安装任何 pack,无需更新。'));
45
- process.exit(1);
45
+ throw new Error('');
46
46
  }
47
47
  // 从 airail.json 读取 pack 来源信息(需要扩展 AirailJson 结构)
48
48
  // 暂时简化:提示用户重新 add
49
49
  console.error((0, colors_1.err)('update 功能开发中,请使用 airail add 重新安装。'));
50
- process.exit(1);
50
+ throw new Error('');
51
51
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airail",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "AI coding assistant framework - enforce coding standards via Claude hooks & skills",
5
5
  "bin": {
6
6
  "airail": "./bin/airail.js"
@@ -1,11 +1,9 @@
1
- # AI Coding Standards
1
+ # 项目开发规范
2
2
 
3
- This project uses [airail](https://github.com/your-org/airail) to enforce coding standards.
3
+ > 本项目使用 AIRail 管理编码规范,技能位于 `.claude/skills/`
4
4
 
5
- ## Installed Skills
5
+ ## 项目约定
6
6
 
7
- Skills are located in `.claude/skills/`. The AI will automatically activate relevant skills before implementing code.
7
+ [在此添加项目特定的开发规范、架构约定、命名规范等]
8
8
 
9
- ## Adding Custom Rules
10
9
 
11
- Add project-specific rules to this file. They will be loaded at session start.
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Stop Hook - Claude 回答结束时触发
4
+ * 功能: 清理误创建的 nul 文件(Windows 下 > nul 可能创建该文件)
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ // 清理可能误创建的 nul 文件
11
+ const findAndDeleteNul = (dir, depth = 0) => {
12
+ if (depth > 5) return;
13
+ try {
14
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
15
+ for (const entry of entries) {
16
+ const fullPath = path.join(dir, entry.name);
17
+ if (entry.isFile() && entry.name === 'nul') {
18
+ fs.unlinkSync(fullPath);
19
+ console.error(`🧹 已清理: ${fullPath}`);
20
+ } else if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
21
+ findAndDeleteNul(fullPath, depth + 1);
22
+ }
23
+ }
24
+ } catch {
25
+ // 访问失败时静默忽略
26
+ }
27
+ };
28
+
29
+ findAndDeleteNul(process.cwd());
30
+
31
+ process.exit(0);
@@ -18,6 +18,12 @@
18
18
  "matcher": "Bash|Write",
19
19
  "hooks": [{ "type": "command", "command": "node .claude/hooks/guard.js", "timeout": 5000 }]
20
20
  }
21
+ ],
22
+ "Stop": [
23
+ {
24
+ "matcher": "",
25
+ "hooks": [{ "type": "command", "command": "node .claude/hooks/stop.js" }]
26
+ }
21
27
  ]
22
28
  },
23
29
  "mcpServers": {}