aodw-skill 0.7.17 → 0.7.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -0
- package/bin/aodw.js +10 -108
- package/bin/commands/new.js +4 -120
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,6 +124,51 @@ npx aodw-skill new
|
|
|
124
124
|
|
|
125
125
|
交互式创建新的开发任务 Ticket。
|
|
126
126
|
|
|
127
|
+
## 维护者发布流程
|
|
128
|
+
|
|
129
|
+
后续版本发布统一采用以下方式:
|
|
130
|
+
|
|
131
|
+
1. 进入目录并确认状态
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
cd cli
|
|
135
|
+
git status
|
|
136
|
+
npm whoami
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
2. 版本升级(会同步模板并更新版本号)
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
./publish.sh patch # 或 minor / major
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
3. 发布到 npm
|
|
146
|
+
|
|
147
|
+
- OTP 模式(账号策略要求 2FA):
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npm publish --otp=<6位验证码>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- Token 模式(Granular Access Token + bypass 2FA):
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npm config set //registry.npmjs.org/:_authToken=<TOKEN>
|
|
157
|
+
npm publish
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
4. 发布校验
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npm view aodw-skill version
|
|
164
|
+
npx aodw-skill@latest --help
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
5. 收尾
|
|
168
|
+
|
|
169
|
+
- 如 `package.json` 版本号变更未提交,请补充 commit 并 push。
|
|
170
|
+
- 若 token 曾在不安全位置暴露,请立即撤销并更换。
|
|
171
|
+
|
|
127
172
|
## 了解更多
|
|
128
173
|
|
|
129
174
|
- [AODW-Next 完整文档](#)
|
package/bin/aodw.js
CHANGED
|
@@ -7,7 +7,6 @@ import fs from 'fs-extra';
|
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { createRequire } from 'module';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
|
-
import fetch from 'node-fetch';
|
|
11
10
|
|
|
12
11
|
import {
|
|
13
12
|
AntigravityProcessor,
|
|
@@ -205,30 +204,6 @@ async function returnToMenu() {
|
|
|
205
204
|
}]);
|
|
206
205
|
}
|
|
207
206
|
|
|
208
|
-
// Helper: Check server health
|
|
209
|
-
async function checkServerHealth(url) {
|
|
210
|
-
try {
|
|
211
|
-
// Ensure URL has protocol
|
|
212
|
-
if (!url.startsWith('http')) {
|
|
213
|
-
url = `http://${url}`;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Create timeout signal
|
|
217
|
-
const controller = new AbortController();
|
|
218
|
-
const timeout = setTimeout(() => controller.abort(), 3000); // 3s timeout
|
|
219
|
-
|
|
220
|
-
const res = await fetch(`${url}/api/health`, {
|
|
221
|
-
method: 'GET',
|
|
222
|
-
signal: controller.signal
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
clearTimeout(timeout);
|
|
226
|
-
return res.ok;
|
|
227
|
-
} catch (error) {
|
|
228
|
-
return false;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
207
|
async function runInit() {
|
|
233
208
|
console.log(chalk.blue('🚀 正在初始化 AODW-Next...'));
|
|
234
209
|
|
|
@@ -289,10 +264,11 @@ async function runInit() {
|
|
|
289
264
|
await saveProjectConfig({ project_name: projectName });
|
|
290
265
|
}
|
|
291
266
|
|
|
292
|
-
// --- Step 2:
|
|
267
|
+
// --- Step 2: Fixed Local Mode (independent only) ---
|
|
293
268
|
const userConfig = getUserConfig();
|
|
294
|
-
if (
|
|
295
|
-
await
|
|
269
|
+
if (userConfig.mode !== 'independent' || userConfig.server_url) {
|
|
270
|
+
await saveUserConfig({ mode: 'independent', server_url: '' });
|
|
271
|
+
console.log(chalk.gray('已固定为独立模式(本地生成 ID)'));
|
|
296
272
|
}
|
|
297
273
|
|
|
298
274
|
// --- Step 3: Platform Selection (Multi-select) ---
|
|
@@ -479,11 +455,7 @@ async function runInit() {
|
|
|
479
455
|
console.log(chalk.green('\n✅ AODW-Next 初始化成功!'));
|
|
480
456
|
console.log(chalk.white(`项目: ${projectName}`));
|
|
481
457
|
|
|
482
|
-
|
|
483
|
-
console.log(chalk.white(`模式: ${updatedConfig.mode === 'independent' ? '独立模式 (本地)' : '协作模式 (联网)'}`));
|
|
484
|
-
if (updatedConfig.mode === 'collaborative') {
|
|
485
|
-
console.log(chalk.white(`服务器: ${updatedConfig.server_url}`));
|
|
486
|
-
}
|
|
458
|
+
console.log(chalk.white('模式: 独立模式 (本地)'));
|
|
487
459
|
console.log(chalk.white(`平台: ${platforms.join(', ')}`));
|
|
488
460
|
}
|
|
489
461
|
|
|
@@ -670,68 +642,6 @@ async function generateToolsPrompt() {
|
|
|
670
642
|
}
|
|
671
643
|
}
|
|
672
644
|
|
|
673
|
-
async function configureMode(pause = true, forceConnect = false) {
|
|
674
|
-
const { mode } = await inquirer.prompt([{
|
|
675
|
-
type: 'list',
|
|
676
|
-
name: 'mode',
|
|
677
|
-
message: '选择开发模式:',
|
|
678
|
-
choices: [
|
|
679
|
-
{ name: '独立模式 (本地生成 ID, 适合个人开发)', value: 'independent' },
|
|
680
|
-
{ name: '协作模式 (联网获取 ID, 适合团队开发)', value: 'collaborative' }
|
|
681
|
-
]
|
|
682
|
-
}]);
|
|
683
|
-
|
|
684
|
-
let serverUrl = '';
|
|
685
|
-
if (mode === 'collaborative') {
|
|
686
|
-
while (true) {
|
|
687
|
-
const answers = await inquirer.prompt([{
|
|
688
|
-
type: 'input',
|
|
689
|
-
name: 'serverUrl',
|
|
690
|
-
message: '请输入 AODW-Next ID 服务器地址:',
|
|
691
|
-
default: 'http://114.67.218.31:2005',
|
|
692
|
-
validate: (input) => {
|
|
693
|
-
if (!input || input.trim() === '') {
|
|
694
|
-
return '协作模式必须提供服务器地址';
|
|
695
|
-
}
|
|
696
|
-
return true;
|
|
697
|
-
}
|
|
698
|
-
}]);
|
|
699
|
-
serverUrl = answers.serverUrl.trim();
|
|
700
|
-
|
|
701
|
-
process.stdout.write(chalk.gray(`正在测试连接 ${serverUrl}... `));
|
|
702
|
-
const healthy = await checkServerHealth(serverUrl);
|
|
703
|
-
|
|
704
|
-
if (healthy) {
|
|
705
|
-
console.log(chalk.green('✅ 连接成功'));
|
|
706
|
-
break;
|
|
707
|
-
} else {
|
|
708
|
-
console.log(chalk.red('❌ 连接失败'));
|
|
709
|
-
const { action } = await inquirer.prompt([{
|
|
710
|
-
type: 'list',
|
|
711
|
-
name: 'action',
|
|
712
|
-
message: '无法连接到 ID 服务器,请选择:',
|
|
713
|
-
choices: [
|
|
714
|
-
{ name: '重试输入', value: 'retry' },
|
|
715
|
-
{ name: '强制保存 (离线使用)', value: 'force' },
|
|
716
|
-
{ name: '切换回独立模式', value: 'switch_independent' }
|
|
717
|
-
]
|
|
718
|
-
}]);
|
|
719
|
-
|
|
720
|
-
if (action === 'force') break;
|
|
721
|
-
if (action === 'switch_independent') {
|
|
722
|
-
await saveUserConfig({ mode: 'independent', server_url: '' });
|
|
723
|
-
console.log(chalk.green('✅ 全局配置已保存 (切换为独立模式)'));
|
|
724
|
-
return;
|
|
725
|
-
}
|
|
726
|
-
// retry continues loop
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
await saveUserConfig({ mode, server_url: serverUrl });
|
|
732
|
-
console.log(chalk.green('✅ 全局配置已保存!'));
|
|
733
|
-
}
|
|
734
|
-
|
|
735
645
|
async function showMainMenu() {
|
|
736
646
|
while (true) {
|
|
737
647
|
console.clear();
|
|
@@ -739,9 +649,7 @@ async function showMainMenu() {
|
|
|
739
649
|
console.log(chalk.gray('版本: ' + packageJson.version));
|
|
740
650
|
|
|
741
651
|
// Show current config summary
|
|
742
|
-
|
|
743
|
-
const modeStr = config.mode === 'independent' ? '🏠 独立模式' : '🌐 协作模式';
|
|
744
|
-
console.log(chalk.gray(`当前配置: ${modeStr} ${config.mode === 'collaborative' ? `(${config.server_url})` : ''}`));
|
|
652
|
+
console.log(chalk.gray('当前配置: 🏠 独立模式 (本地生成 ID)'));
|
|
745
653
|
console.log('');
|
|
746
654
|
|
|
747
655
|
const { action } = await inquirer.prompt([{
|
|
@@ -752,15 +660,14 @@ async function showMainMenu() {
|
|
|
752
660
|
choices: [
|
|
753
661
|
new inquirer.Separator('--- 核心功能 ---'),
|
|
754
662
|
{ name: '1. 初始化 / 更新 AODW-Next (在本项目)', value: 'init' },
|
|
755
|
-
{ name: '2. 配置全局开发模式 (单机/联网)', value: 'config' },
|
|
756
663
|
|
|
757
664
|
new inquirer.Separator('--- 工具箱 ---'),
|
|
758
|
-
{ name: '
|
|
759
|
-
{ name: '
|
|
665
|
+
{ name: '2. 项目概览初始化 (Architecture) - 生成提示词', value: 'init-overview-prompt' },
|
|
666
|
+
{ name: '3. 工具初始化 (ESLint/Ruff/Stack) - 生成提示词', value: 'init-tools-prompt' },
|
|
760
667
|
|
|
761
668
|
new inquirer.Separator('--- 帮助与维护 ---'),
|
|
762
|
-
{ name: '
|
|
763
|
-
{ name: '
|
|
669
|
+
{ name: '4. 查看帮助 & 部署指南', value: 'help' },
|
|
670
|
+
{ name: '5. 卸载 AODW-Next', value: 'uninstall' },
|
|
764
671
|
new inquirer.Separator(),
|
|
765
672
|
{ name: '0. 退出 (Exit)', value: 'exit' }
|
|
766
673
|
]
|
|
@@ -777,10 +684,6 @@ async function showMainMenu() {
|
|
|
777
684
|
await runInit();
|
|
778
685
|
await returnToMenu();
|
|
779
686
|
break;
|
|
780
|
-
case 'config':
|
|
781
|
-
await configureMode();
|
|
782
|
-
await returnToMenu();
|
|
783
|
-
break;
|
|
784
687
|
case 'init-overview-prompt':
|
|
785
688
|
await generateOverviewPrompt();
|
|
786
689
|
await returnToMenu();
|
|
@@ -831,7 +734,6 @@ program
|
|
|
831
734
|
program
|
|
832
735
|
.command('new')
|
|
833
736
|
.description('Create a new Request Ticket (RT)')
|
|
834
|
-
.option('--server <url>', 'URL of the ID server')
|
|
835
737
|
.option('--project <name>', 'Project identifier')
|
|
836
738
|
.option('--title <string>', 'Title of the RT')
|
|
837
739
|
.action(createNewRT);
|
package/bin/commands/new.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import fs from 'fs-extra';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import http from 'http';
|
|
4
|
-
import https from 'https';
|
|
5
|
-
import { URL } from 'url';
|
|
6
3
|
import inquirer from 'inquirer';
|
|
7
4
|
import chalk from 'chalk';
|
|
8
|
-
import { getProjectConfig
|
|
5
|
+
import { getProjectConfig } from '../utils/config.js';
|
|
9
6
|
|
|
10
7
|
function getProjectName() {
|
|
11
8
|
// 1. Try .aodw/project.yaml
|
|
@@ -25,35 +22,6 @@ function getProjectName() {
|
|
|
25
22
|
return path.basename(process.cwd());
|
|
26
23
|
}
|
|
27
24
|
|
|
28
|
-
function fetchIdFromServer(serverUrl, project) {
|
|
29
|
-
return new Promise((resolve, reject) => {
|
|
30
|
-
const url = new URL('/api/next-id', serverUrl);
|
|
31
|
-
url.searchParams.set('project', project);
|
|
32
|
-
|
|
33
|
-
const client = url.protocol === 'https:' ? https : http;
|
|
34
|
-
|
|
35
|
-
const req = client.get(url.toString(), (res) => {
|
|
36
|
-
if (res.statusCode !== 200) {
|
|
37
|
-
reject(new Error(`Server returned status ${res.statusCode}`));
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let data = '';
|
|
42
|
-
res.on('data', (chunk) => data += chunk);
|
|
43
|
-
res.on('end', () => {
|
|
44
|
-
try {
|
|
45
|
-
const json = JSON.parse(data);
|
|
46
|
-
resolve(json.id);
|
|
47
|
-
} catch (e) {
|
|
48
|
-
reject(e);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
req.on('error', (e) => reject(e));
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
25
|
function getLocalMaxSeq() {
|
|
58
26
|
const rtDir = path.join(process.cwd(), 'RT');
|
|
59
27
|
if (!fs.existsSync(rtDir)) return 0;
|
|
@@ -77,34 +45,7 @@ function getLocalNextId() {
|
|
|
77
45
|
return `RT-${String(maxSeq + 1).padStart(3, '0')}`;
|
|
78
46
|
}
|
|
79
47
|
|
|
80
|
-
function syncIdToServer(serverUrl, project, seq) {
|
|
81
|
-
return new Promise((resolve, reject) => {
|
|
82
|
-
const url = new URL('/api/sync-id', serverUrl);
|
|
83
|
-
url.searchParams.set('project', project);
|
|
84
|
-
url.searchParams.set('seq', seq.toString());
|
|
85
|
-
|
|
86
|
-
const client = url.protocol === 'https:' ? https : http;
|
|
87
|
-
|
|
88
|
-
const req = client.request(url.toString(), { method: 'POST' }, (res) => {
|
|
89
|
-
if (res.statusCode === 200) {
|
|
90
|
-
resolve(true);
|
|
91
|
-
} else {
|
|
92
|
-
reject(new Error(`Server returned status ${res.statusCode}`));
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
req.on('error', (e) => reject(e));
|
|
97
|
-
req.end();
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
48
|
export async function createNewRT(options) {
|
|
103
|
-
const userConfig = getUserConfig();
|
|
104
|
-
|
|
105
|
-
// Determine Server URL: Flag > Config > Env Var
|
|
106
|
-
const serverUrl = options.server || userConfig.server_url || process.env.AODW_ID_SERVER;
|
|
107
|
-
|
|
108
49
|
// Determine Project Name: Flag > Config/Package/Dir
|
|
109
50
|
const project = options.project || getProjectName();
|
|
110
51
|
|
|
@@ -120,66 +61,9 @@ export async function createNewRT(options) {
|
|
|
120
61
|
title = answers.title;
|
|
121
62
|
}
|
|
122
63
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (isCollaborativeMode) {
|
|
128
|
-
// In collaborative mode, server URL is required
|
|
129
|
-
if (!serverUrl || serverUrl.trim() === '') {
|
|
130
|
-
console.error(chalk.red('Error: Collaborative mode requires a server URL.'));
|
|
131
|
-
console.error(chalk.yellow('Please configure the server URL by running: aodw config'));
|
|
132
|
-
console.error(chalk.yellow('Or set the AODW_ID_SERVER environment variable.'));
|
|
133
|
-
process.exit(1);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
console.log(chalk.blue(`Fetching ID from server (${serverUrl})...`));
|
|
138
|
-
const serverId = await fetchIdFromServer(serverUrl, project);
|
|
139
|
-
const serverSeq = parseInt(serverId.replace('RT-', ''), 10);
|
|
140
|
-
|
|
141
|
-
// Get local max ID and compare
|
|
142
|
-
const localMaxSeq = getLocalMaxSeq();
|
|
143
|
-
|
|
144
|
-
if (serverSeq <= localMaxSeq) {
|
|
145
|
-
// Server ID is outdated, use local max + 1
|
|
146
|
-
const finalSeq = localMaxSeq + 1;
|
|
147
|
-
id = `RT-${String(finalSeq).padStart(3, '0')}`;
|
|
148
|
-
console.log(chalk.yellow(`⚠ Server ID (${serverId}) ≤ local max (RT-${String(localMaxSeq).padStart(3, '0')})`));
|
|
149
|
-
console.log(chalk.green(`Using local ID: ${id}`));
|
|
150
|
-
|
|
151
|
-
// Sync to server
|
|
152
|
-
try {
|
|
153
|
-
await syncIdToServer(serverUrl, project, finalSeq);
|
|
154
|
-
console.log(chalk.blue(`✔ Synced ID to server: ${id}`));
|
|
155
|
-
} catch (syncErr) {
|
|
156
|
-
console.warn(chalk.yellow(`⚠ Failed to sync ID to server: ${syncErr.message}`));
|
|
157
|
-
console.warn(chalk.yellow(' The server may not support sync-id API, but RT creation will continue.'));
|
|
158
|
-
}
|
|
159
|
-
} else {
|
|
160
|
-
id = serverId;
|
|
161
|
-
console.log(chalk.green(`Obtained ID: ${id}`));
|
|
162
|
-
}
|
|
163
|
-
} catch (e) {
|
|
164
|
-
console.error(chalk.red(`Failed to fetch ID from server: ${e.message}`));
|
|
165
|
-
const { useLocal } = await inquirer.prompt([{
|
|
166
|
-
type: 'confirm',
|
|
167
|
-
name: 'useLocal',
|
|
168
|
-
message: 'Do you want to fall back to local ID generation? (Warning: Risk of collision)',
|
|
169
|
-
default: false
|
|
170
|
-
}]);
|
|
171
|
-
if (!useLocal) process.exit(1);
|
|
172
|
-
id = getLocalNextId();
|
|
173
|
-
}
|
|
174
|
-
} else {
|
|
175
|
-
// Independent mode: always use local generation, ignore server_url
|
|
176
|
-
// This ensures that when user chooses independent mode, no network request is made
|
|
177
|
-
id = getLocalNextId();
|
|
178
|
-
console.log(chalk.yellow(`Using local ID generation: ${id}`));
|
|
179
|
-
if (serverUrl) {
|
|
180
|
-
console.log(chalk.gray(`Note: server_url is configured but ignored in independent mode`));
|
|
181
|
-
}
|
|
182
|
-
}
|
|
64
|
+
// 固定独立模式:始终本地生成 RT-ID
|
|
65
|
+
const id = getLocalNextId();
|
|
66
|
+
console.log(chalk.yellow(`Using local ID generation: ${id}`));
|
|
183
67
|
|
|
184
68
|
// Create Directory
|
|
185
69
|
const rtPath = path.join(process.cwd(), 'RT', id);
|