agentic-workflow-manager 4.0.0 → 4.1.0
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.
|
@@ -11,6 +11,8 @@ const picocolors_1 = __importDefault(require("picocolors"));
|
|
|
11
11
|
const context_1 = require("../core/diagnostics/context");
|
|
12
12
|
const checks_1 = require("../core/diagnostics/checks");
|
|
13
13
|
const paths_1 = require("../core/paths");
|
|
14
|
+
// `readPreferences`, no `getPreferences`: este comando esta documentado como
|
|
15
|
+
// read-only y el segundo auto-vivifica el archivo. Ver la nota en config.ts.
|
|
14
16
|
const config_1 = require("../utils/config");
|
|
15
17
|
const agent_targets_1 = require("../core/agent-targets");
|
|
16
18
|
function glyph(status) {
|
|
@@ -128,7 +130,7 @@ function runDoctor(opts = {}) {
|
|
|
128
130
|
// internal-error catch further down.
|
|
129
131
|
let targets;
|
|
130
132
|
try {
|
|
131
|
-
const prefs = (0, config_1.
|
|
133
|
+
const prefs = (0, config_1.readPreferences)();
|
|
132
134
|
targets = resolveTargets({ prefs, explicit: opts.agent });
|
|
133
135
|
}
|
|
134
136
|
catch (err) {
|
package/dist/src/index.js
CHANGED
|
@@ -477,18 +477,43 @@ program.command('list [package]')
|
|
|
477
477
|
console.log(picocolors_1.default.dim(` awm list <pkg> · awm list --all`));
|
|
478
478
|
(0, prompts_1.outro)(`Run ${picocolors_1.default.green('awm add')} to install artifacts.`);
|
|
479
479
|
});
|
|
480
|
-
|
|
481
|
-
|
|
480
|
+
// Simetrico con `add`: si se puede instalar scripteado, se tiene que poder desinstalar
|
|
481
|
+
// scripteado. `remove` era interactivo puro — sin nombre posicional, sin `--scope`, sin
|
|
482
|
+
// `--yes` — asi que cualquier limpieza automatizada quedaba bloqueada, y el playbook de
|
|
483
|
+
// aceptacion (CORE-17) scripteaba `awm remove dev --yes`, una invocacion que nunca
|
|
484
|
+
// existio. Tercera vez en esta sesion que un playbook se escribio contra lo que la doc
|
|
485
|
+
// prometia y no contra el comportamiento. Ver docs/decisions.md, D-006.
|
|
486
|
+
//
|
|
487
|
+
// El nombre es de BUNDLE, igual que en `add` (D-001).
|
|
488
|
+
program.command('remove [name]')
|
|
489
|
+
.description('Remove an installed bundle interactively, or non-interactively with flags')
|
|
482
490
|
.option('-a, --agent <agent>', `Target agent(s), comma-separated: ${providers_1.AGENT_TARGETS.join(', ')} (defaults to every enabled agent)`)
|
|
483
|
-
.
|
|
491
|
+
.option('-s, --scope <scope>', 'Scope: local or global (skips the prompt)')
|
|
492
|
+
.option('-y, --yes', 'Skip the confirmation prompt (requires a name)')
|
|
493
|
+
.action(async (name, options) => {
|
|
484
494
|
(0, prompts_1.intro)(picocolors_1.default.bgCyan(picocolors_1.default.black(' AWM - Remove Artifact ')));
|
|
485
495
|
const prefs = (0, config_1.getPreferences)();
|
|
496
|
+
// `--yes` sin nombre borraria lo que el usuario nunca eligio: un `rm -rf` silencioso
|
|
497
|
+
// sobre todo lo instalado. Se rechaza antes de tocar nada.
|
|
498
|
+
if (options.yes && !name) {
|
|
499
|
+
console.error(picocolors_1.default.red('--yes requires a bundle name: `awm remove <bundle> --yes`.'));
|
|
500
|
+
console.error(picocolors_1.default.dim('Without a name, removal stays interactive so you see what you are deleting.'));
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
486
503
|
// Multi-agent selection (matching the add command flow). --agent skips
|
|
487
504
|
// the interactive prompt and resolves the same way as add/sync/update/doctor (R12/R13).
|
|
488
505
|
let targetAgents;
|
|
489
506
|
if (options.agent) {
|
|
490
507
|
targetAgents = resolveTargetsOrExit(prefs, options.agent);
|
|
491
508
|
}
|
|
509
|
+
else if (options.yes) {
|
|
510
|
+
// `--yes` significa CERO prompts, no "cero confirmaciones". Sin esto, un
|
|
511
|
+
// `awm remove dev --yes` sin `--agent` seguia abriendo el multiselect de
|
|
512
|
+
// agentes y colgaba cualquier script — el flag prometia no-interactivo y no lo
|
|
513
|
+
// era. Sin `--agent`, el default son los agentes habilitados, igual que en
|
|
514
|
+
// `add`, `sync`, `update` y `doctor`.
|
|
515
|
+
targetAgents = [...prefs.enabledAgents];
|
|
516
|
+
}
|
|
492
517
|
else {
|
|
493
518
|
const agentChoice = await (0, prompts_1.multiselect)({
|
|
494
519
|
message: 'From which agent(s)?',
|
|
@@ -502,16 +527,26 @@ program.command('remove')
|
|
|
502
527
|
handleCancel(agentChoice);
|
|
503
528
|
targetAgents = agentChoice;
|
|
504
529
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
options
|
|
508
|
-
{
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
|
|
530
|
+
let scopeVal;
|
|
531
|
+
if (options.scope) {
|
|
532
|
+
if (options.scope !== 'local' && options.scope !== 'global') {
|
|
533
|
+
console.error(picocolors_1.default.red(`Invalid scope "${options.scope}". Use: local or global.`));
|
|
534
|
+
process.exit(1);
|
|
535
|
+
}
|
|
536
|
+
scopeVal = options.scope;
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
const scopeChoice = await (0, prompts_1.select)({
|
|
540
|
+
message: 'Scope?',
|
|
541
|
+
options: [
|
|
542
|
+
{ value: 'local', label: 'Project (Local)' },
|
|
543
|
+
{ value: 'global', label: 'Global' }
|
|
544
|
+
],
|
|
545
|
+
initialValue: prefs.defaultScope
|
|
546
|
+
});
|
|
547
|
+
handleCancel(scopeChoice);
|
|
548
|
+
scopeVal = scopeChoice;
|
|
549
|
+
}
|
|
515
550
|
// projectRoot explicito: `config.local` es relativo, y resolverlo contra
|
|
516
551
|
// `process.cwd()` hacia que `awm remove` no encontrara nada desde un
|
|
517
552
|
// subdirectorio — y que le pasara rutas RELATIVAS a fs.rmSync cuando si.
|
|
@@ -532,16 +567,43 @@ program.command('remove')
|
|
|
532
567
|
}
|
|
533
568
|
return `${icons} ${c.baseName} ${picocolors_1.default.dim(`(in: ${Array.from(locations).join(', ')})`)}`;
|
|
534
569
|
});
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
570
|
+
let resolved;
|
|
571
|
+
if (name) {
|
|
572
|
+
// Los artefactos del bundle pedido que estan REALMENTE instalados en este
|
|
573
|
+
// scope. Se cruza contra `installed`: lo que no esta instalado no se puede
|
|
574
|
+
// remover, y decirlo es mejor que borrar un subconjunto en silencio.
|
|
575
|
+
const bundle = (0, bundles_1.discoverAllBundles)().find((b) => b.name === name);
|
|
576
|
+
if (!bundle) {
|
|
577
|
+
console.error(picocolors_1.default.red(`Bundle "${name}" not found in registry.`));
|
|
578
|
+
console.error(picocolors_1.default.dim('Run `awm list` to see available packages.'));
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
const owned = new Set([
|
|
582
|
+
...bundle.skills.map((sk) => sk.name),
|
|
583
|
+
...bundle.workflows,
|
|
584
|
+
...bundle.agents,
|
|
585
|
+
]);
|
|
586
|
+
resolved = installed.filter((a) => owned.has(a.name));
|
|
587
|
+
if (resolved.length === 0) {
|
|
588
|
+
(0, prompts_1.outro)(picocolors_1.default.yellow(`Nothing from "${name}" is installed at ${scopeVal} scope for the selected agent(s).`));
|
|
589
|
+
process.exit(0);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
else {
|
|
593
|
+
const toRemove = await (0, prompts_1.multiselect)({
|
|
594
|
+
message: 'Select artifact(s) to remove',
|
|
595
|
+
options: groupedOpts,
|
|
596
|
+
required: true
|
|
597
|
+
});
|
|
598
|
+
handleCancel(toRemove);
|
|
599
|
+
resolved = resolveSelectedArtifacts(toRemove);
|
|
600
|
+
}
|
|
542
601
|
const names = resolved.map(a => picocolors_1.default.red(a.name)).join(', ');
|
|
543
|
-
|
|
544
|
-
|
|
602
|
+
// `--yes` salta la confirmacion, nunca la seleccion: lo que se borra siempre sale
|
|
603
|
+
// de un nombre que el usuario escribio.
|
|
604
|
+
const confirmRemove = options.yes ? true : await (0, prompts_1.confirm)({ message: `Remove ${names}?` });
|
|
605
|
+
if (!options.yes)
|
|
606
|
+
handleCancel(confirmRemove);
|
|
545
607
|
if (confirmRemove) {
|
|
546
608
|
try {
|
|
547
609
|
for (const artifact of resolved) {
|
package/dist/src/utils/config.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.preferencesExist = preferencesExist;
|
|
7
|
+
exports.readPreferences = readPreferences;
|
|
7
8
|
exports.getPreferences = getPreferences;
|
|
8
9
|
exports.loadPreferences = loadPreferences;
|
|
9
10
|
exports.savePreferences = savePreferences;
|
|
@@ -84,6 +85,28 @@ function normalizePreferences(value, allowEnabledAgentsMigration = true) {
|
|
|
84
85
|
JSON.stringify(source) !== JSON.stringify(enabledAgents),
|
|
85
86
|
};
|
|
86
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Las preferencias TAL COMO ESTAN, sin escribir nada. Devuelve los defaults en memoria
|
|
90
|
+
* cuando el archivo no existe todavia.
|
|
91
|
+
*
|
|
92
|
+
* `getPreferences` auto-vivifica: si no hay archivo, lo crea con los defaults. Eso esta
|
|
93
|
+
* bien para un comando que va a configurar la maquina, y esta mal para uno que solo
|
|
94
|
+
* mira. `awm doctor` —documentado como «Read-only dashboard. Changes nothing.»— lo
|
|
95
|
+
* llamaba, asi que en una maquina limpia el comando de inspeccion dejaba un
|
|
96
|
+
* `preferences.json` con `claude-code` como agente por defecto, que despues se lee como
|
|
97
|
+
* «el usuario configuro esto» cuando no lo hizo. Lo encontro el playbook de aceptacion,
|
|
98
|
+
* no la suite: ningun test preguntaba si un comando de lectura escribia.
|
|
99
|
+
*
|
|
100
|
+
* Se midieron los otros siete comandos de inspeccion (`list`, `preflight`, `sensors
|
|
101
|
+
* status`, `context-budget`, `backup list`, `ledger list`, `registry status`): todos
|
|
102
|
+
* limpios. `doctor` era el unico.
|
|
103
|
+
*/
|
|
104
|
+
function readPreferences() {
|
|
105
|
+
const loaded = loadPreferences();
|
|
106
|
+
return loaded.exists
|
|
107
|
+
? loaded.prefs
|
|
108
|
+
: { ...DEFAULT_PREFS, enabledAgents: [...DEFAULT_PREFS.enabledAgents] };
|
|
109
|
+
}
|
|
87
110
|
function getPreferences() {
|
|
88
111
|
const loaded = loadPreferences();
|
|
89
112
|
if (!loaded.exists) {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
// `awm doctor` está documentado como «Read-only dashboard of machine + project harness
|
|
7
|
+
// state. **Changes nothing.**» — y escribía.
|
|
8
|
+
//
|
|
9
|
+
// `getPreferences()` auto-vivifica `preferences.json` con los defaults cuando el archivo
|
|
10
|
+
// no existe (`config.ts`: `if (!loaded.exists) savePreferences(DEFAULT_PREFS)`). Un
|
|
11
|
+
// getter con efecto de escritura escondido adentro. `doctor` lo llamaba para resolver a
|
|
12
|
+
// qué agentes mirar, así que en una máquina limpia el comando de inspección dejaba un
|
|
13
|
+
// `preferences.json` con `claude-code` como agente por defecto — que después se lee como
|
|
14
|
+
// «el usuario configuró esto» cuando no lo hizo.
|
|
15
|
+
//
|
|
16
|
+
// Lo encontró el playbook de aceptación en su segundo check, no la suite: ningún test
|
|
17
|
+
// preguntaba si un comando de lectura escribía. Se midieron los otros siete comandos de
|
|
18
|
+
// inspección (`list`, `preflight`, `sensors status`, `context-budget`, `backup list`,
|
|
19
|
+
// `ledger list`, `registry status`) y todos estaban limpios — así que acá el sitio ES la
|
|
20
|
+
// clase, con medición en vez de suposición.
|
|
21
|
+
const fs_1 = __importDefault(require("fs"));
|
|
22
|
+
const os_1 = __importDefault(require("os"));
|
|
23
|
+
const path_1 = __importDefault(require("path"));
|
|
24
|
+
describe('awm doctor no escribe nada', () => {
|
|
25
|
+
let home;
|
|
26
|
+
let projectRoot;
|
|
27
|
+
let saved;
|
|
28
|
+
let writeSpy;
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
home = fs_1.default.realpathSync(fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-doctor-ro-')));
|
|
31
|
+
saved = { HOME: process.env.HOME, AWM_HOME: process.env.AWM_HOME };
|
|
32
|
+
process.env.HOME = home;
|
|
33
|
+
process.env.AWM_HOME = path_1.default.join(home, '.awm');
|
|
34
|
+
projectRoot = path_1.default.join(home, 'proj');
|
|
35
|
+
fs_1.default.mkdirSync(path_1.default.join(projectRoot, '.git'), { recursive: true });
|
|
36
|
+
jest.resetModules();
|
|
37
|
+
writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
38
|
+
});
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
writeSpy.mockRestore();
|
|
41
|
+
if (saved.HOME === undefined)
|
|
42
|
+
delete process.env.HOME;
|
|
43
|
+
else
|
|
44
|
+
process.env.HOME = saved.HOME;
|
|
45
|
+
if (saved.AWM_HOME === undefined)
|
|
46
|
+
delete process.env.AWM_HOME;
|
|
47
|
+
else
|
|
48
|
+
process.env.AWM_HOME = saved.AWM_HOME;
|
|
49
|
+
fs_1.default.rmSync(home, { recursive: true, force: true });
|
|
50
|
+
});
|
|
51
|
+
/** Todo lo que exista bajo `home`, para comparar el antes y el después. */
|
|
52
|
+
const treeOf = (dir) => {
|
|
53
|
+
const out = [];
|
|
54
|
+
const walk = (sub) => {
|
|
55
|
+
for (const e of fs_1.default.readdirSync(path_1.default.join(dir, sub), { withFileTypes: true })) {
|
|
56
|
+
const rel = path_1.default.join(sub, e.name);
|
|
57
|
+
out.push(rel);
|
|
58
|
+
if (e.isDirectory())
|
|
59
|
+
walk(rel);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
walk('.');
|
|
63
|
+
return out.sort();
|
|
64
|
+
};
|
|
65
|
+
it('no crea AWM_HOME ni preferences.json en una máquina limpia', () => {
|
|
66
|
+
const { runDoctor } = require('../../src/commands/doctor');
|
|
67
|
+
const before = treeOf(home);
|
|
68
|
+
runDoctor({ cwd: projectRoot, json: true });
|
|
69
|
+
expect(fs_1.default.existsSync(path_1.default.join(home, '.awm', 'preferences.json'))).toBe(false);
|
|
70
|
+
expect(treeOf(home)).toEqual(before);
|
|
71
|
+
});
|
|
72
|
+
it('tampoco escribe cuando ya hay preferencias', () => {
|
|
73
|
+
// El otro camino de `getPreferences`: el archivo existe. `migrationRequired`
|
|
74
|
+
// podía reescribirlo — sigue siendo una escritura desde un comando de lectura.
|
|
75
|
+
fs_1.default.mkdirSync(path_1.default.join(home, '.awm'), { recursive: true });
|
|
76
|
+
const prefsPath = path_1.default.join(home, '.awm', 'preferences.json');
|
|
77
|
+
fs_1.default.writeFileSync(prefsPath, JSON.stringify({
|
|
78
|
+
enabledAgents: ['claude-code'], defaultAgent: 'claude-code',
|
|
79
|
+
installMethod: 'symlink', defaultScope: 'global',
|
|
80
|
+
}));
|
|
81
|
+
const before = fs_1.default.readFileSync(prefsPath, 'utf-8');
|
|
82
|
+
const beforeTree = treeOf(home);
|
|
83
|
+
const { runDoctor } = require('../../src/commands/doctor');
|
|
84
|
+
runDoctor({ cwd: projectRoot, json: true });
|
|
85
|
+
expect(fs_1.default.readFileSync(prefsPath, 'utf-8')).toBe(before);
|
|
86
|
+
expect(treeOf(home)).toEqual(beforeTree);
|
|
87
|
+
});
|
|
88
|
+
it('sigue reportando sobre el agente por defecto sin archivo de preferencias', () => {
|
|
89
|
+
// No alcanza con no escribir: tiene que seguir funcionando. Sin preferencias,
|
|
90
|
+
// los defaults en memoria son la respuesta correcta.
|
|
91
|
+
const { runDoctor } = require('../../src/commands/doctor');
|
|
92
|
+
const code = runDoctor({ cwd: projectRoot, json: true });
|
|
93
|
+
expect([0, 1]).toContain(code); // 0 sano / 1 degradado, nunca 2 (error)
|
|
94
|
+
const emitted = writeSpy.mock.calls.map((c) => String(c[0])).join('');
|
|
95
|
+
expect(JSON.parse(emitted).providers.map((p) => p.id)).toEqual(['claude-code']);
|
|
96
|
+
});
|
|
97
|
+
});
|