@haaaiawd/anws 2.0.2 → 2.0.4
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 +2 -1
- package/bin/cli.js +1 -1
- package/lib/init.js +69 -8
- package/lib/install-state.js +9 -5
- package/lib/prompt.js +16 -5
- package/lib/update.js +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<img src="assets/logo-cli.png" width="260" alt="Anws">
|
|
4
4
|
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
|
-
[](https://github.com/Haaaiawd/Anws/releases)
|
|
7
7
|
[](https://github.com/Haaaiawd/Anws)
|
|
8
8
|
|
|
9
9
|
[English](./README.md) | [中文](./README_CN.md)
|
|
@@ -140,6 +140,7 @@ anws update
|
|
|
140
140
|
- **State source**
|
|
141
141
|
- `anws update` reads `.anws/install-lock.json`
|
|
142
142
|
- if the lock is missing or invalid, it falls back to directory scan
|
|
143
|
+
- if lock drift is detected, directory scan becomes the effective source for the current update
|
|
143
144
|
- a real `anws update` can rebuild `.anws/install-lock.json` from detected targets when fallback is active
|
|
144
145
|
|
|
145
146
|
- **`AGENTS.md` behavior**
|
package/bin/cli.js
CHANGED
|
@@ -40,7 +40,7 @@ SUPPORTED TARGETS
|
|
|
40
40
|
EXAMPLES
|
|
41
41
|
anws init # Choose target IDEs and install their managed workflow projections
|
|
42
42
|
anws init --target windsurf,codex,opencode
|
|
43
|
-
anws update # Update all matched targets from install-lock or
|
|
43
|
+
anws update # Update all matched targets from install-lock, fallback scan, or drift repair
|
|
44
44
|
anws update --check # Preview grouped changes per target without writing files
|
|
45
45
|
`.trimStart();
|
|
46
46
|
|
package/lib/init.js
CHANGED
|
@@ -15,10 +15,11 @@ const { success, warn, info, fileLine, skippedLine, blank, logo, section } = req
|
|
|
15
15
|
async function init() {
|
|
16
16
|
const cwd = process.cwd();
|
|
17
17
|
logo();
|
|
18
|
-
const
|
|
18
|
+
const installState = await detectInstallState(cwd);
|
|
19
|
+
const retainedTargetIds = await resolveRetainedTargetIds(cwd, installState);
|
|
20
|
+
const targets = await selectTargets(installState, retainedTargetIds);
|
|
19
21
|
const targetIds = Array.from(new Set(targets.map((item) => item.id)));
|
|
20
22
|
const targetPlans = buildProjectionPlan(targetIds);
|
|
21
|
-
const installState = await detectInstallState(cwd);
|
|
22
23
|
const srcAgents = ROOT_AGENTS_FILE;
|
|
23
24
|
const cliVersion = require(path.join(__dirname, '..', 'package.json')).version;
|
|
24
25
|
|
|
@@ -40,6 +41,7 @@ async function init() {
|
|
|
40
41
|
|
|
41
42
|
for (const targetPlan of targetPlans) {
|
|
42
43
|
const target = getTarget(targetPlan.targetId);
|
|
44
|
+
const targetAlreadyInstalled = retainedTargetIds.includes(target.id);
|
|
43
45
|
const rootAgentsExists = await pathExists(path.join(cwd, 'AGENTS.md'));
|
|
44
46
|
const agentsDecision = target.id === 'antigravity'
|
|
45
47
|
? await resolveAgentsInstall({
|
|
@@ -60,7 +62,11 @@ async function init() {
|
|
|
60
62
|
agentsUpdatePlan = planAgentsUpdate({ templateContent, existingContent });
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
const conflicting = await findConflicts(
|
|
65
|
+
const conflicting = await findConflicts(
|
|
66
|
+
cwd,
|
|
67
|
+
targetAlreadyInstalled ? [] : targetPlan.managedFiles.filter((rel) => rel !== 'AGENTS.md'),
|
|
68
|
+
sessionWrittenFiles
|
|
69
|
+
);
|
|
64
70
|
if (conflicting.length > 0) {
|
|
65
71
|
const confirmed = await askOverwrite(conflicting.length, target.label);
|
|
66
72
|
if (!confirmed) {
|
|
@@ -214,24 +220,79 @@ function printSummary(files, skipped = [], action) {
|
|
|
214
220
|
}
|
|
215
221
|
}
|
|
216
222
|
|
|
217
|
-
async function selectTargets() {
|
|
223
|
+
async function selectTargets(installState, retainedTargetIds = []) {
|
|
224
|
+
const installedTargetIds = new Set(retainedTargetIds);
|
|
225
|
+
|
|
218
226
|
if (global.__ANWS_TARGET_IDS && global.__ANWS_TARGET_IDS.length > 0) {
|
|
219
|
-
return
|
|
227
|
+
return Array.from(new Set([
|
|
228
|
+
...retainedTargetIds,
|
|
229
|
+
...global.__ANWS_TARGET_IDS
|
|
230
|
+
])).map((targetId) => getTarget(targetId));
|
|
220
231
|
}
|
|
221
232
|
|
|
222
233
|
if (!process.stdin.isTTY) {
|
|
223
|
-
return
|
|
234
|
+
return retainedTargetIds.length > 0
|
|
235
|
+
? retainedTargetIds.map((targetId) => getTarget(targetId))
|
|
236
|
+
: [getTarget('antigravity')];
|
|
224
237
|
}
|
|
225
238
|
|
|
226
239
|
const targets = listTargets();
|
|
240
|
+
const lockedIndexes = [];
|
|
241
|
+
const initialSelectedIndexes = [];
|
|
242
|
+
|
|
243
|
+
for (const [index, target] of targets.entries()) {
|
|
244
|
+
if (installedTargetIds.has(target.id)) {
|
|
245
|
+
lockedIndexes.push(index);
|
|
246
|
+
initialSelectedIndexes.push(index);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (initialSelectedIndexes.length === 0) {
|
|
251
|
+
const antigravityIndex = targets.findIndex((target) => target.id === 'antigravity');
|
|
252
|
+
if (antigravityIndex >= 0) {
|
|
253
|
+
initialSelectedIndexes.push(antigravityIndex);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
227
256
|
|
|
228
257
|
return selectMultiple({
|
|
229
258
|
message: 'Choose your target AI IDEs:',
|
|
230
|
-
options: targets.map((target) => ({
|
|
231
|
-
|
|
259
|
+
options: targets.map((target) => ({
|
|
260
|
+
label: target.label,
|
|
261
|
+
value: target.id,
|
|
262
|
+
locked: installedTargetIds.has(target.id)
|
|
263
|
+
})),
|
|
264
|
+
initialSelectedIndexes,
|
|
265
|
+
lockedIndexes
|
|
232
266
|
}).then((selectedOptions) => selectedOptions.map((option) => getTarget(option.value)));
|
|
233
267
|
}
|
|
234
268
|
|
|
269
|
+
async function resolveRetainedTargetIds(cwd, installState) {
|
|
270
|
+
if (!installState.needsFallback && !installState.drift.hasDrift) {
|
|
271
|
+
return installState.selectedTargets;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const retainedTargetIds = [];
|
|
275
|
+
|
|
276
|
+
for (const targetId of installState.selectedTargets) {
|
|
277
|
+
const [targetPlan] = buildProjectionPlan([targetId]);
|
|
278
|
+
const managedFiles = (targetPlan?.managedFiles || []).filter((rel) => rel !== 'AGENTS.md');
|
|
279
|
+
if (managedFiles.length === 0) {
|
|
280
|
+
retainedTargetIds.push(targetId);
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const managedExists = await Promise.all(
|
|
285
|
+
managedFiles.map((rel) => pathExists(path.join(cwd, rel)))
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
if (managedExists.every(Boolean)) {
|
|
289
|
+
retainedTargetIds.push(targetId);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return retainedTargetIds;
|
|
294
|
+
}
|
|
295
|
+
|
|
235
296
|
function printNextSteps(targets) {
|
|
236
297
|
blank();
|
|
237
298
|
section('Next steps', targets.some((target) => target.rootAgentFile)
|
package/lib/install-state.js
CHANGED
|
@@ -170,10 +170,14 @@ async function detectInstallState(cwd) {
|
|
|
170
170
|
const fallbackReason = !needsFallback
|
|
171
171
|
? null
|
|
172
172
|
: (!lockResult.exists ? 'missing_lock' : 'invalid_lock');
|
|
173
|
-
const selectedTargets = lockTargets.length > 0
|
|
174
|
-
? lockTargets.map((item) => item.targetId)
|
|
175
|
-
: scannedTargets.map((item) => item.id);
|
|
176
173
|
const drift = detectLockDrift(lockResult.lock, scannedTargets);
|
|
174
|
+
const shouldPreferScannedTargets = needsFallback || drift.hasDrift || lockTargets.length === 0;
|
|
175
|
+
const selectedTargets = shouldPreferScannedTargets
|
|
176
|
+
? scannedTargets.map((item) => item.id)
|
|
177
|
+
: lockTargets.map((item) => item.targetId);
|
|
178
|
+
const stateSource = needsFallback
|
|
179
|
+
? 'directory_scan_fallback'
|
|
180
|
+
: (drift.hasDrift ? 'directory_scan_drift' : 'install_lock');
|
|
177
181
|
|
|
178
182
|
return {
|
|
179
183
|
lockResult,
|
|
@@ -182,8 +186,8 @@ async function detectInstallState(cwd) {
|
|
|
182
186
|
drift,
|
|
183
187
|
needsFallback,
|
|
184
188
|
fallbackReason,
|
|
185
|
-
stateSource
|
|
186
|
-
canRebuildLock: needsFallback && selectedTargets.length > 0
|
|
189
|
+
stateSource,
|
|
190
|
+
canRebuildLock: (needsFallback || drift.hasDrift) && selectedTargets.length > 0
|
|
187
191
|
};
|
|
188
192
|
}
|
|
189
193
|
|
package/lib/prompt.js
CHANGED
|
@@ -14,12 +14,18 @@ const KEY = {
|
|
|
14
14
|
ARROW_LEFT: '\u001b[D'
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
async function selectMultiple({ message, options, initialSelectedIndexes = [] }) {
|
|
17
|
+
async function selectMultiple({ message, options, initialSelectedIndexes = [], lockedIndexes = [] }) {
|
|
18
|
+
const normalizedLockedIndexes = new Set(lockedIndexes.filter((index) => index >= 0 && index < options.length));
|
|
19
|
+
const normalizedInitialSelectedIndexes = Array.from(new Set([
|
|
20
|
+
...initialSelectedIndexes.filter((index) => index >= 0 && index < options.length),
|
|
21
|
+
...normalizedLockedIndexes
|
|
22
|
+
]));
|
|
23
|
+
|
|
18
24
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
19
|
-
return
|
|
25
|
+
return normalizedInitialSelectedIndexes.map((index) => options[index]).filter(Boolean);
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
const selected = new Set(
|
|
28
|
+
const selected = new Set(normalizedInitialSelectedIndexes);
|
|
23
29
|
const state = {
|
|
24
30
|
cursorIndex: 0,
|
|
25
31
|
errorMessage: ''
|
|
@@ -49,6 +55,10 @@ async function selectMultiple({ message, options, initialSelectedIndexes = [] })
|
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
if (key === KEY.SPACE) {
|
|
58
|
+
if (normalizedLockedIndexes.has(state.cursorIndex)) {
|
|
59
|
+
state.errorMessage = 'Already installed targets are locked. You can only add more targets.';
|
|
60
|
+
return 'render';
|
|
61
|
+
}
|
|
52
62
|
if (selected.has(state.cursorIndex)) {
|
|
53
63
|
selected.delete(state.cursorIndex);
|
|
54
64
|
} else {
|
|
@@ -121,7 +131,8 @@ function renderMultiSelect({ message, options, selected, cursorIndex, errorMessa
|
|
|
121
131
|
const isSelected = selected.has(index);
|
|
122
132
|
const cursor = isActive ? colorize('❯', PALETTE.brand) : ' ';
|
|
123
133
|
const mark = isSelected ? colorize('◉', PALETTE.brand) : colorize('◌', PALETTE.muted);
|
|
124
|
-
const
|
|
134
|
+
const labelText = option.locked ? `${option.label} ${colorize('(installed)', PALETTE.muted)}` : option.label;
|
|
135
|
+
const label = isActive ? colorize(labelText, PALETTE.ink) : labelText;
|
|
125
136
|
return `${cursor} ${mark} ${label}`;
|
|
126
137
|
});
|
|
127
138
|
|
|
@@ -133,7 +144,7 @@ function renderMultiSelect({ message, options, selected, cursorIndex, errorMessa
|
|
|
133
144
|
'',
|
|
134
145
|
...optionLines,
|
|
135
146
|
'',
|
|
136
|
-
errorMessage ? colorize(errorMessage, c.yellow) : colorize('Choose
|
|
147
|
+
errorMessage ? colorize(errorMessage, c.yellow) : colorize('Choose targets to add. Installed targets stay selected.', PALETTE.muted)
|
|
137
148
|
],
|
|
138
149
|
accent: PALETTE.brand,
|
|
139
150
|
borderTone: PALETTE.muted,
|
package/lib/update.js
CHANGED
|
@@ -129,7 +129,7 @@ async function update(options = {}) {
|
|
|
129
129
|
blank();
|
|
130
130
|
}
|
|
131
131
|
printTargetSelection(installState, targetContexts.map((context) => context.target));
|
|
132
|
-
if (!check && installState.
|
|
132
|
+
if (!check && installState.canRebuildLock && selectedTargetIds.length > 0) {
|
|
133
133
|
const generatedAt = new Date().toISOString();
|
|
134
134
|
await writeInstallLock(cwd, createInstallLock({
|
|
135
135
|
cliVersion: version,
|
|
@@ -217,11 +217,14 @@ async function update(options = {}) {
|
|
|
217
217
|
}
|
|
218
218
|
});
|
|
219
219
|
const generatedAt = new Date().toISOString();
|
|
220
|
+
const existingLockTargets = installState.canRebuildLock
|
|
221
|
+
? []
|
|
222
|
+
: (installState.lockResult.lock?.targets || []);
|
|
220
223
|
await writeInstallLock(cwd, createInstallLock({
|
|
221
224
|
cliVersion: version,
|
|
222
225
|
generatedAt,
|
|
223
226
|
targets: dedupeTargets([
|
|
224
|
-
...
|
|
227
|
+
...existingLockTargets,
|
|
225
228
|
...successfulTargets
|
|
226
229
|
]),
|
|
227
230
|
lastUpdateSummary: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haaaiawd/anws",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Anws — A spec-driven workflow framework for AI-assisted development. Empowers prompt engineers to build production-ready software through structured PRD → Architecture → Task decomposition. Works with Claude Code, GitHub Copilot, Cursor, Windsurf, and any tool that reads AGENTS.md.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"anws",
|