agentic-workflow-manager 2.0.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.
- package/README.md +62 -0
- package/dist/src/commands/doctor.js +77 -0
- package/dist/src/commands/hooks/index.js +118 -0
- package/dist/src/commands/hooks/install.js +113 -0
- package/dist/src/commands/hooks/resync.js +50 -0
- package/dist/src/commands/hooks/status.js +74 -0
- package/dist/src/commands/hooks/uninstall.js +59 -0
- package/dist/src/commands/init.js +181 -0
- package/dist/src/commands/ledger/index.js +73 -0
- package/dist/src/commands/pin.js +75 -0
- package/dist/src/commands/registry/add.js +62 -0
- package/dist/src/commands/registry/index.js +166 -0
- package/dist/src/commands/registry/install-bundles.js +36 -0
- package/dist/src/commands/registry/remove.js +18 -0
- package/dist/src/commands/registry/status.js +31 -0
- package/dist/src/commands/sensors/baseline.js +93 -0
- package/dist/src/commands/sensors/formatters/eslint.js +29 -0
- package/dist/src/commands/sensors/formatters/generic.js +8 -0
- package/dist/src/commands/sensors/formatters/semgrep.js +18 -0
- package/dist/src/commands/sensors/formatters/test.js +12 -0
- package/dist/src/commands/sensors/formatters/tsc.js +23 -0
- package/dist/src/commands/sensors/index.js +94 -0
- package/dist/src/commands/sensors/init.js +112 -0
- package/dist/src/commands/sensors/install.js +78 -0
- package/dist/src/commands/sensors/run.js +217 -0
- package/dist/src/commands/sensors/status.js +85 -0
- package/dist/src/commands/sensors/types.js +2 -0
- package/dist/src/core/bundle-install.js +116 -0
- package/dist/src/core/bundles.js +122 -0
- package/dist/src/core/cli-version.js +30 -0
- package/dist/src/core/context/materializer.js +30 -0
- package/dist/src/core/context/orchestrator.js +75 -0
- package/dist/src/core/context/project-constitution-inject.js +47 -0
- package/dist/src/core/context/provider.js +29 -0
- package/dist/src/core/context/regenerate.js +61 -0
- package/dist/src/core/context/strategies/config-instructions.js +73 -0
- package/dist/src/core/context/strategies/hook-merge.js +23 -0
- package/dist/src/core/context/strategies/strategy.js +2 -0
- package/dist/src/core/context/types.js +2 -0
- package/dist/src/core/diagnostics/checks.js +141 -0
- package/dist/src/core/diagnostics/context.js +181 -0
- package/dist/src/core/diagnostics/types.js +2 -0
- package/dist/src/core/discovery.js +135 -0
- package/dist/src/core/executor.js +41 -0
- package/dist/src/core/init/detector.js +67 -0
- package/dist/src/core/init/orchestrator.js +54 -0
- package/dist/src/core/init/steps.js +279 -0
- package/dist/src/core/init/types.js +2 -0
- package/dist/src/core/ledger/store.js +73 -0
- package/dist/src/core/ledger/types.js +2 -0
- package/dist/src/core/miro.js +314 -0
- package/dist/src/core/profile-pins.js +36 -0
- package/dist/src/core/profile.js +146 -0
- package/dist/src/core/registries.js +205 -0
- package/dist/src/core/registry.js +28 -0
- package/dist/src/core/skill-integrity.js +97 -0
- package/dist/src/core/story-map-parser.js +83 -0
- package/dist/src/core/update-check-worker.js +10 -0
- package/dist/src/core/update-check.js +106 -0
- package/dist/src/core/versioning.js +112 -0
- package/dist/src/index.js +689 -0
- package/dist/src/providers/index.js +63 -0
- package/dist/src/utils/config.js +35 -0
- package/dist/src/utils/grouping.js +51 -0
- package/dist/src/utils/registry-view.js +154 -0
- package/dist/tests/commands/doctor.test.js +98 -0
- package/dist/tests/commands/hooks/install.test.js +149 -0
- package/dist/tests/commands/hooks/resync.test.js +135 -0
- package/dist/tests/commands/hooks/router.test.js +26 -0
- package/dist/tests/commands/hooks/status.test.js +88 -0
- package/dist/tests/commands/hooks/uninstall.test.js +104 -0
- package/dist/tests/commands/init.test.js +87 -0
- package/dist/tests/commands/ledger/index.test.js +64 -0
- package/dist/tests/commands/pin.test.js +72 -0
- package/dist/tests/commands/registry/add.test.js +223 -0
- package/dist/tests/commands/registry/install-bundles.test.js +87 -0
- package/dist/tests/commands/registry/remove.test.js +49 -0
- package/dist/tests/commands/registry/status.test.js +43 -0
- package/dist/tests/commands/sensors/baseline.test.js +106 -0
- package/dist/tests/commands/sensors/formatters/eslint.test.js +32 -0
- package/dist/tests/commands/sensors/formatters/semgrep.test.js +38 -0
- package/dist/tests/commands/sensors/formatters/tsc.test.js +25 -0
- package/dist/tests/commands/sensors/index.test.js +18 -0
- package/dist/tests/commands/sensors/init.test.js +137 -0
- package/dist/tests/commands/sensors/install.test.js +60 -0
- package/dist/tests/commands/sensors/router.test.js +23 -0
- package/dist/tests/commands/sensors/run.test.js +353 -0
- package/dist/tests/commands/sensors/status.test.js +98 -0
- package/dist/tests/core/base-remote.test.js +70 -0
- package/dist/tests/core/bundle-install.test.js +198 -0
- package/dist/tests/core/bundles-multiroot.test.js +68 -0
- package/dist/tests/core/bundles-overrides.test.js +49 -0
- package/dist/tests/core/bundles.test.js +96 -0
- package/dist/tests/core/cli-version.test.js +17 -0
- package/dist/tests/core/context/materializer.test.js +46 -0
- package/dist/tests/core/context/orchestrator.test.js +127 -0
- package/dist/tests/core/context/project-constitution-inject.test.js +82 -0
- package/dist/tests/core/context/provider.test.js +45 -0
- package/dist/tests/core/context/regenerate.test.js +106 -0
- package/dist/tests/core/context/strategies/config-instructions.test.js +88 -0
- package/dist/tests/core/context/strategies/hook-merge.test.js +71 -0
- package/dist/tests/core/context/types.test.js +15 -0
- package/dist/tests/core/diagnostics/checks.test.js +208 -0
- package/dist/tests/core/diagnostics/context.test.js +170 -0
- package/dist/tests/core/discovery-multiroot.test.js +63 -0
- package/dist/tests/core/discovery-overrides.test.js +105 -0
- package/dist/tests/core/discovery.test.js +113 -0
- package/dist/tests/core/executor.test.js +44 -0
- package/dist/tests/core/init/detector.test.js +56 -0
- package/dist/tests/core/init/orchestrator.test.js +122 -0
- package/dist/tests/core/init/steps.test.js +363 -0
- package/dist/tests/core/ledger/gitignore.test.js +13 -0
- package/dist/tests/core/ledger/store.test.js +122 -0
- package/dist/tests/core/miro-layout.test.js +150 -0
- package/dist/tests/core/profile-pins.test.js +131 -0
- package/dist/tests/core/profile-registries.test.js +59 -0
- package/dist/tests/core/profile.test.js +110 -0
- package/dist/tests/core/registries-capability.test.js +52 -0
- package/dist/tests/core/registries-seed.test.js +66 -0
- package/dist/tests/core/registries-sync.test.js +205 -0
- package/dist/tests/core/registries.test.js +91 -0
- package/dist/tests/core/registry-manifest.test.js +95 -0
- package/dist/tests/core/registry-versioned-sync.test.js +113 -0
- package/dist/tests/core/registry.test.js +51 -0
- package/dist/tests/core/skill-integrity.test.js +126 -0
- package/dist/tests/core/story-map-parser.test.js +136 -0
- package/dist/tests/core/sync-gates.test.js +97 -0
- package/dist/tests/core/update-check.test.js +106 -0
- package/dist/tests/core/versioning.test.js +169 -0
- package/dist/tests/integration/pack-e2e.test.js +50 -0
- package/dist/tests/providers/hooks-config.test.js +45 -0
- package/dist/tests/providers/index.test.js +58 -0
- package/dist/tests/providers/injection-config.test.js +25 -0
- package/dist/tests/registry/b3-ledger-wiring.test.js +74 -0
- package/dist/tests/registry/catalog-consistency.test.js +47 -0
- package/dist/tests/registry/design-skills.test.js +146 -0
- package/dist/tests/registry/prose-agnostic.test.js +18 -0
- package/dist/tests/registry/sensor-packs.test.js +45 -0
- package/dist/tests/registry/skill-versions.test.js +26 -0
- package/dist/tests/registry/using-awm.test.js +39 -0
- package/dist/tests/utils/config.test.js +31 -0
- package/dist/tests/utils/grouping.test.js +43 -0
- package/dist/tests/utils/registry-view-overrides.test.js +41 -0
- package/dist/tests/utils/registry-view.test.js +156 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# AWM CLI Core
|
|
2
|
+
|
|
3
|
+
This directory contains the source code for the `agentic-workflow-manager` Command Line Interface.
|
|
4
|
+
|
|
5
|
+
Built with Node.js and TypeScript, the CLI uses `@clack/prompts` to deliver a zero-friction, interactive Text User Interface (TUI) for installing skills and workflows from configured git registries into the local OS environment.
|
|
6
|
+
|
|
7
|
+
## 📁 Directory Structure
|
|
8
|
+
|
|
9
|
+
The CLI is cleanly layered to decouple configuration mapping from core engine logic:
|
|
10
|
+
- `src/index.ts`: The main entrypoint, orchestrating Commander args and Clack prompts.
|
|
11
|
+
- `src/core/`: The core engine modules:
|
|
12
|
+
- `registry.ts`: Resolves the base registry remote and parses YAML/JSON manifests from `~/.awm/registries/<name>/`.
|
|
13
|
+
- `discovery.ts`: Provides searching, mapping, and categorization inside the registry format.
|
|
14
|
+
- `executor.ts`: The installation logic handling target validations, filesystem hooks, Copy execution, and Symlink deployment.
|
|
15
|
+
- `src/providers/`: Encapsulates logic for routing. e.g. mapping `antigravity` to `~/.gemini/antigravity/` vs mapped `opencode` to standard OS `.agents/` targets.
|
|
16
|
+
- `src/utils/`: Common helpers like git wrappers or string formatting flags.
|
|
17
|
+
- `tests/`: Extensive Jest test suites mirroring each module in `src/`.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 🛠 Developer Guide
|
|
22
|
+
|
|
23
|
+
If you are contributing bug fixes to the CLI or implementing support for a new AI Agent ecosystem (e.g. extending `src/providers/`), follow these steps to work on the CLI locally:
|
|
24
|
+
|
|
25
|
+
### 1. Requirements
|
|
26
|
+
Ensure you have Node.js and `npm` installed.
|
|
27
|
+
|
|
28
|
+
### 2. Install Dependencies
|
|
29
|
+
Navigate to this directory (`/cli/`) and install dev packages:
|
|
30
|
+
```bash
|
|
31
|
+
npm install
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 3. Build the CLI
|
|
35
|
+
Because the CLI uses TypeScript, the source must be transpiled to CommonJS/ESM before Node can natively execute the Javascript output via Commander.
|
|
36
|
+
```bash
|
|
37
|
+
npm run build
|
|
38
|
+
```
|
|
39
|
+
This generates the runnable output under `dist/index.js`.
|
|
40
|
+
|
|
41
|
+
### 4. Running the Tests
|
|
42
|
+
Before submitting a PR, ensure that the core discovery/executor functionality passes the existing test suite:
|
|
43
|
+
```bash
|
|
44
|
+
npm test
|
|
45
|
+
```
|
|
46
|
+
The goal is to maintain 100% passing tests for any new CLI parameter flags.
|
|
47
|
+
|
|
48
|
+
### 5. Local Execution (Testing changes locally)
|
|
49
|
+
|
|
50
|
+
**Method A: Typescript Native execution (Slower)**
|
|
51
|
+
Useful for quick testing without building.
|
|
52
|
+
```bash
|
|
53
|
+
npm run start -- add "specific-name" --agent antigravity --scope global
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Method B: Global NPM Linking (Recommended for interactive QA)**
|
|
57
|
+
Links your compiled `dist/` directory directly to your global `awm` path, ensuring you are testing the exact binary output the end-user will see.
|
|
58
|
+
```bash
|
|
59
|
+
npm run build
|
|
60
|
+
npm link
|
|
61
|
+
```
|
|
62
|
+
Now simply type `awm` from any path on your machine.
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
exports.renderReport = renderReport;
|
|
7
|
+
exports.runDoctor = runDoctor;
|
|
8
|
+
exports.registerDoctorCommand = registerDoctorCommand;
|
|
9
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
10
|
+
const context_1 = require("../core/diagnostics/context");
|
|
11
|
+
const checks_1 = require("../core/diagnostics/checks");
|
|
12
|
+
function glyph(status) {
|
|
13
|
+
if (status === 'ok')
|
|
14
|
+
return picocolors_1.default.green('✔');
|
|
15
|
+
if (status === 'warn')
|
|
16
|
+
return picocolors_1.default.yellow('⚠');
|
|
17
|
+
return picocolors_1.default.red('✖');
|
|
18
|
+
}
|
|
19
|
+
function remedyText(r) {
|
|
20
|
+
if (r.remedy.kind === 'command')
|
|
21
|
+
return picocolors_1.default.dim(`→ ${r.remedy.value}`);
|
|
22
|
+
if (r.remedy.kind === 'skill')
|
|
23
|
+
return picocolors_1.default.dim(`→ skill: ${r.remedy.value}`);
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
function line(r) {
|
|
27
|
+
const rem = remedyText(r);
|
|
28
|
+
const det = r.detail ? picocolors_1.default.dim(` (${r.detail})`) : '';
|
|
29
|
+
return ` ${glyph(r.status)} ${r.label}${det}${rem ? ' ' + rem : ''}`;
|
|
30
|
+
}
|
|
31
|
+
function renderReport(report) {
|
|
32
|
+
const lines = [];
|
|
33
|
+
lines.push(picocolors_1.default.bold('AWM · estado del harness'));
|
|
34
|
+
lines.push('');
|
|
35
|
+
lines.push('Máquina (global)');
|
|
36
|
+
for (const r of report.results.filter((x) => x.level === 'machine'))
|
|
37
|
+
lines.push(line(r));
|
|
38
|
+
lines.push('');
|
|
39
|
+
if (report.hasProject) {
|
|
40
|
+
lines.push(`Proyecto: ${report.projectName ?? ''}`.trimEnd());
|
|
41
|
+
for (const r of report.results.filter((x) => x.level === 'project'))
|
|
42
|
+
lines.push(line(r));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
lines.push(picocolors_1.default.dim('(sin proyecto en el cwd)'));
|
|
46
|
+
}
|
|
47
|
+
lines.push('');
|
|
48
|
+
const actions = report.results.filter((r) => r.remedy.kind !== 'none').length;
|
|
49
|
+
const estado = report.overall === 'healthy' ? picocolors_1.default.green('sano') : picocolors_1.default.red('degradado');
|
|
50
|
+
lines.push(`estado: ${estado} · ${actions} acciones sugeridas`);
|
|
51
|
+
return lines.join('\n');
|
|
52
|
+
}
|
|
53
|
+
function runDoctor(opts = {}) {
|
|
54
|
+
let report;
|
|
55
|
+
try {
|
|
56
|
+
report = (0, checks_1.runChecks)((0, context_1.gatherContext)({ cwd: opts.cwd }));
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
process.stderr.write(`awm doctor: error interno: ${err.message}\n`);
|
|
60
|
+
return 2;
|
|
61
|
+
}
|
|
62
|
+
if (opts.json) {
|
|
63
|
+
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
process.stdout.write(renderReport(report) + '\n');
|
|
67
|
+
}
|
|
68
|
+
return report.overall === 'healthy' ? 0 : 1;
|
|
69
|
+
}
|
|
70
|
+
function registerDoctorCommand(program) {
|
|
71
|
+
program.command('doctor')
|
|
72
|
+
.description('Read-only dashboard of the AWM harness state (machine + project)')
|
|
73
|
+
.option('--json', 'Emit the diagnostic report as JSON')
|
|
74
|
+
.action((options) => {
|
|
75
|
+
process.exitCode = runDoctor({ json: options.json });
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
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
|
+
exports.registerHooksCommand = registerHooksCommand;
|
|
7
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
8
|
+
const prompts_1 = require("@clack/prompts");
|
|
9
|
+
const config_1 = require("../../utils/config");
|
|
10
|
+
const install_1 = require("./install");
|
|
11
|
+
const uninstall_1 = require("./uninstall");
|
|
12
|
+
const status_1 = require("./status");
|
|
13
|
+
const registries_1 = require("../../core/registries");
|
|
14
|
+
function registerHooksCommand(program) {
|
|
15
|
+
const hooks = program.command('hooks').description('Manage SessionStart bootstrap hooks');
|
|
16
|
+
hooks.command('install')
|
|
17
|
+
.description('Install the AWM bootstrap hook into the target harness')
|
|
18
|
+
.option('-t, --target <target>', 'Target harness (claude-code only in this version)', 'claude-code')
|
|
19
|
+
.option('-y, --yes', 'Skip interactive confirmations', false)
|
|
20
|
+
.action(async (options) => {
|
|
21
|
+
const agent = (options.target ?? 'claude-code');
|
|
22
|
+
const prefs = (0, config_1.getPreferences)();
|
|
23
|
+
const hooksRoot = (0, registries_1.capabilityRoot)('hooks');
|
|
24
|
+
if (!hooksRoot) {
|
|
25
|
+
console.error(picocolors_1.default.red('No configured registry provides hooks/ — run `awm update` first.'));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
const result = (0, install_1.installHook)({
|
|
30
|
+
agent,
|
|
31
|
+
registryRoot: hooksRoot,
|
|
32
|
+
installMethod: prefs.installMethod
|
|
33
|
+
});
|
|
34
|
+
if (result.status === 'already-up-to-date') {
|
|
35
|
+
console.log(picocolors_1.default.green('✓ Hook already installed and up-to-date.'));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
console.log('');
|
|
39
|
+
console.log(picocolors_1.default.green('✓ AWM bootstrap hook installed.'));
|
|
40
|
+
console.log('');
|
|
41
|
+
console.log(` Scripts: ${result.scriptsDir}/session-start`);
|
|
42
|
+
console.log(` ${result.scriptsDir}/run-hook.cmd`);
|
|
43
|
+
console.log(` ${result.scriptsDir}/using-awm.md → ~/.awm/registries/baseline/skills/using-awm/SKILL.md`);
|
|
44
|
+
console.log('');
|
|
45
|
+
console.log(` Settings file: ${result.settingsPath}`);
|
|
46
|
+
if (result.backupPath) {
|
|
47
|
+
console.log(` Backup: ${result.backupPath}`);
|
|
48
|
+
}
|
|
49
|
+
console.log('');
|
|
50
|
+
console.log(' Active on: startup | /clear | /compact');
|
|
51
|
+
console.log('');
|
|
52
|
+
console.log(` Verify: ${picocolors_1.default.cyan('awm hooks status')}`);
|
|
53
|
+
console.log(` Remove: ${picocolors_1.default.cyan('awm hooks uninstall')}`);
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(picocolors_1.default.yellow(' ⚠ Restart Claude Code to activate the hook in existing sessions.'));
|
|
56
|
+
}
|
|
57
|
+
catch (e) {
|
|
58
|
+
console.error(picocolors_1.default.red(`✗ ${e.message}`));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
hooks.command('uninstall')
|
|
63
|
+
.description('Remove the AWM bootstrap hook')
|
|
64
|
+
.option('-t, --target <target>', 'Target harness (claude-code only in this version)', 'claude-code')
|
|
65
|
+
.option('-y, --yes', 'Skip interactive confirmations', false)
|
|
66
|
+
.action(async (options) => {
|
|
67
|
+
const agent = (options.target ?? 'claude-code');
|
|
68
|
+
if (!options.yes && process.stdin.isTTY) {
|
|
69
|
+
const ok = await (0, prompts_1.confirm)({ message: 'Remove AWM bootstrap hook from settings.json?' });
|
|
70
|
+
if ((0, prompts_1.isCancel)(ok) || ok !== true) {
|
|
71
|
+
console.log('Cancelled.');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const result = (0, uninstall_1.uninstallHook)({ agent });
|
|
77
|
+
if (result.status === 'not-installed') {
|
|
78
|
+
console.log(picocolors_1.default.yellow('No AWM hook entry found. Nothing to uninstall.'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
console.log(picocolors_1.default.green('✓ AWM bootstrap hook removed.'));
|
|
82
|
+
if (result.backupPath) {
|
|
83
|
+
console.log(` Backup: ${result.backupPath}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
console.error(picocolors_1.default.red(`✗ ${e.message}`));
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
hooks.command('status')
|
|
92
|
+
.description('Check the bootstrap hook installation status')
|
|
93
|
+
.option('-t, --target <target>', 'Target harness (claude-code only in this version)', 'claude-code')
|
|
94
|
+
.action((options) => {
|
|
95
|
+
const agent = (options.target ?? 'claude-code');
|
|
96
|
+
try {
|
|
97
|
+
const result = (0, status_1.computeHookStatus)(agent);
|
|
98
|
+
const symbol = (ok) => ok ? picocolors_1.default.green('✓') : picocolors_1.default.red('✗');
|
|
99
|
+
console.log('');
|
|
100
|
+
console.log(` Bootstrap skill: ${symbol(result.checks.bootstrapSkill.ok)} ${result.checks.bootstrapSkill.detail}`);
|
|
101
|
+
console.log(` Session-start: ${symbol(result.checks.sessionStartScript.ok)} ${result.checks.sessionStartScript.detail}`);
|
|
102
|
+
console.log(` Run-hook wrapper: ${symbol(result.checks.runHookWrapper.ok)} ${result.checks.runHookWrapper.detail}`);
|
|
103
|
+
console.log(` Settings entry: ${symbol(result.checks.settingsEntry.ok)} ${result.checks.settingsEntry.detail}`);
|
|
104
|
+
console.log('');
|
|
105
|
+
const overall = result.overall === 'HEALTHY' ? picocolors_1.default.green(result.overall) :
|
|
106
|
+
result.overall === 'NOT_INSTALLED' ? picocolors_1.default.yellow(result.overall) :
|
|
107
|
+
picocolors_1.default.red(result.overall);
|
|
108
|
+
console.log(` Status: ${overall}`);
|
|
109
|
+
if (result.overall !== 'HEALTHY') {
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
console.error(picocolors_1.default.red(`✗ ${e.message}`));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
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
|
+
exports.syncFile = syncFile;
|
|
7
|
+
exports.installHook = installHook;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const providers_1 = require("../../providers");
|
|
11
|
+
function syncFile(source, dest, method) {
|
|
12
|
+
try {
|
|
13
|
+
fs_1.default.unlinkSync(dest);
|
|
14
|
+
}
|
|
15
|
+
catch { /* not exists, fine */ }
|
|
16
|
+
fs_1.default.mkdirSync(path_1.default.dirname(dest), { recursive: true });
|
|
17
|
+
if (method === 'symlink') {
|
|
18
|
+
fs_1.default.symlinkSync(source, dest);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
fs_1.default.copyFileSync(source, dest);
|
|
22
|
+
const srcMode = fs_1.default.statSync(source).mode;
|
|
23
|
+
fs_1.default.chmodSync(dest, srcMode);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function backupSettings(settingsPath) {
|
|
27
|
+
if (!fs_1.default.existsSync(settingsPath))
|
|
28
|
+
return null;
|
|
29
|
+
const awmHome = process.env.AWM_HOME || path_1.default.join(process.env.HOME, '.awm');
|
|
30
|
+
const backupDir = path_1.default.join(awmHome, 'backups');
|
|
31
|
+
fs_1.default.mkdirSync(backupDir, { recursive: true });
|
|
32
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '-').slice(0, 19);
|
|
33
|
+
const backupPath = path_1.default.join(backupDir, `settings.json.${ts}.bak`);
|
|
34
|
+
fs_1.default.copyFileSync(settingsPath, backupPath);
|
|
35
|
+
return backupPath;
|
|
36
|
+
}
|
|
37
|
+
function isAwmEntry(entry, scriptsDir, matcher) {
|
|
38
|
+
return (entry?.matcher === matcher &&
|
|
39
|
+
Array.isArray(entry?.hooks) &&
|
|
40
|
+
entry.hooks.some((h) => typeof h?.command === 'string' && h.command.includes(scriptsDir)));
|
|
41
|
+
}
|
|
42
|
+
function installHook(options) {
|
|
43
|
+
const config = (0, providers_1.getHookConfig)(options.agent);
|
|
44
|
+
if (!config) {
|
|
45
|
+
throw new Error(`hooks not supported for agent target: ${options.agent}`);
|
|
46
|
+
}
|
|
47
|
+
// 1. Verify registry sources exist FIRST (before touching settings)
|
|
48
|
+
const sourceHooks = path_1.default.join(options.registryRoot, 'hooks');
|
|
49
|
+
const sourceSkill = path_1.default.join(options.registryRoot, 'skills/using-awm/SKILL.md');
|
|
50
|
+
if (!fs_1.default.existsSync(path_1.default.join(sourceHooks, 'session-start'))) {
|
|
51
|
+
throw new Error(`AWM registry not found at ${sourceHooks}. Run 'awm update' to refresh the registry.`);
|
|
52
|
+
}
|
|
53
|
+
if (!fs_1.default.existsSync(sourceSkill)) {
|
|
54
|
+
throw new Error(`using-awm skill not found at ${sourceSkill}. Run 'awm update' first.`);
|
|
55
|
+
}
|
|
56
|
+
// 2. Sync scripts
|
|
57
|
+
fs_1.default.mkdirSync(config.scriptsDir, { recursive: true });
|
|
58
|
+
syncFile(path_1.default.join(sourceHooks, 'session-start'), path_1.default.join(config.scriptsDir, 'session-start'), options.installMethod);
|
|
59
|
+
syncFile(path_1.default.join(sourceHooks, 'run-hook.cmd'), path_1.default.join(config.scriptsDir, 'run-hook.cmd'), options.installMethod);
|
|
60
|
+
// 3. Symlink the skill (ALWAYS symlink so awm update propagates)
|
|
61
|
+
const skillDest = path_1.default.join(config.scriptsDir, 'using-awm.md');
|
|
62
|
+
try {
|
|
63
|
+
fs_1.default.unlinkSync(skillDest);
|
|
64
|
+
}
|
|
65
|
+
catch { /* not exists */ }
|
|
66
|
+
fs_1.default.symlinkSync(sourceSkill, skillDest);
|
|
67
|
+
// 4. Backup settings if it exists
|
|
68
|
+
const backupPath = backupSettings(config.settingsPath);
|
|
69
|
+
// 5. Read or initialize settings
|
|
70
|
+
let settings = {};
|
|
71
|
+
if (fs_1.default.existsSync(config.settingsPath)) {
|
|
72
|
+
const raw = fs_1.default.readFileSync(config.settingsPath, 'utf-8');
|
|
73
|
+
try {
|
|
74
|
+
settings = JSON.parse(raw);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
throw new Error(`${config.settingsPath} is not valid JSON. Backup created at ${backupPath}. Fix the file manually, then re-run.`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
fs_1.default.mkdirSync(path_1.default.dirname(config.settingsPath), { recursive: true });
|
|
82
|
+
}
|
|
83
|
+
// 6. Merge AWM entry
|
|
84
|
+
if (!settings.hooks)
|
|
85
|
+
settings.hooks = {};
|
|
86
|
+
if (!settings.hooks[config.eventName])
|
|
87
|
+
settings.hooks[config.eventName] = [];
|
|
88
|
+
const entries = settings.hooks[config.eventName];
|
|
89
|
+
const awmEntryIdx = entries.findIndex((e) => isAwmEntry(e, config.scriptsDir, config.matcher));
|
|
90
|
+
const newEntry = {
|
|
91
|
+
matcher: config.matcher,
|
|
92
|
+
hooks: [{
|
|
93
|
+
type: 'command',
|
|
94
|
+
command: `${path_1.default.join(config.scriptsDir, 'run-hook.cmd')} session-start`,
|
|
95
|
+
async: false
|
|
96
|
+
}]
|
|
97
|
+
};
|
|
98
|
+
let status;
|
|
99
|
+
if (awmEntryIdx >= 0) {
|
|
100
|
+
if (JSON.stringify(entries[awmEntryIdx]) === JSON.stringify(newEntry)) {
|
|
101
|
+
return { status: 'already-up-to-date', scriptsDir: config.scriptsDir, settingsPath: config.settingsPath, backupPath: null };
|
|
102
|
+
}
|
|
103
|
+
entries[awmEntryIdx] = newEntry;
|
|
104
|
+
status = 'installed';
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
entries.push(newEntry);
|
|
108
|
+
status = 'installed';
|
|
109
|
+
}
|
|
110
|
+
// 7. Write settings
|
|
111
|
+
fs_1.default.writeFileSync(config.settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
112
|
+
return { status, scriptsDir: config.scriptsDir, settingsPath: config.settingsPath, backupPath };
|
|
113
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
exports.resyncInstalledHooks = resyncInstalledHooks;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const providers_1 = require("../../providers");
|
|
10
|
+
const status_1 = require("./status");
|
|
11
|
+
const install_1 = require("./install");
|
|
12
|
+
function detectInstallMethod(scriptsDir) {
|
|
13
|
+
try {
|
|
14
|
+
return fs_1.default.lstatSync(path_1.default.join(scriptsDir, 'session-start')).isSymbolicLink() ? 'symlink' : 'copy';
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return 'copy';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function resyncInstalledHooks(registryRoot) {
|
|
21
|
+
const results = [];
|
|
22
|
+
for (const agent of Object.keys(providers_1.PROVIDERS)) {
|
|
23
|
+
const config = (0, providers_1.getHookConfig)(agent);
|
|
24
|
+
if (!config)
|
|
25
|
+
continue;
|
|
26
|
+
const status = (0, status_1.computeHookStatus)(agent);
|
|
27
|
+
if (!status.checks.settingsEntry.ok) {
|
|
28
|
+
results.push({ agent, action: 'not-installed' });
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const sourceHooks = path_1.default.join(registryRoot, 'hooks');
|
|
32
|
+
const sourceSkill = path_1.default.join(registryRoot, 'skills/using-awm/SKILL.md');
|
|
33
|
+
if (!fs_1.default.existsSync(path_1.default.join(sourceHooks, 'session-start')) || !fs_1.default.existsSync(path_1.default.join(sourceHooks, 'run-hook.cmd')) || !fs_1.default.existsSync(sourceSkill)) {
|
|
34
|
+
results.push({ agent, action: 'registry-missing' });
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const method = detectInstallMethod(config.scriptsDir);
|
|
38
|
+
fs_1.default.mkdirSync(config.scriptsDir, { recursive: true });
|
|
39
|
+
(0, install_1.syncFile)(path_1.default.join(sourceHooks, 'session-start'), path_1.default.join(config.scriptsDir, 'session-start'), method);
|
|
40
|
+
(0, install_1.syncFile)(path_1.default.join(sourceHooks, 'run-hook.cmd'), path_1.default.join(config.scriptsDir, 'run-hook.cmd'), method);
|
|
41
|
+
const skillDest = path_1.default.join(config.scriptsDir, 'using-awm.md');
|
|
42
|
+
try {
|
|
43
|
+
fs_1.default.unlinkSync(skillDest);
|
|
44
|
+
}
|
|
45
|
+
catch { /* not exists */ }
|
|
46
|
+
fs_1.default.symlinkSync(sourceSkill, skillDest);
|
|
47
|
+
results.push({ agent, action: 'resynced' });
|
|
48
|
+
}
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
exports.computeHookStatus = computeHookStatus;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const providers_1 = require("../../providers");
|
|
10
|
+
function checkExecutable(file) {
|
|
11
|
+
if (!fs_1.default.existsSync(file)) {
|
|
12
|
+
return { ok: false, detail: `missing: ${file}` };
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
fs_1.default.accessSync(file, fs_1.default.constants.X_OK);
|
|
16
|
+
return { ok: true, detail: file };
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return { ok: false, detail: `not executable: ${file}` };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function checkFile(file) {
|
|
23
|
+
if (!fs_1.default.existsSync(file)) {
|
|
24
|
+
return { ok: false, detail: `missing: ${file}` };
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
fs_1.default.statSync(file);
|
|
28
|
+
return { ok: true, detail: file };
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return { ok: false, detail: `broken link: ${file}` };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function checkSettingsEntry(settingsPath, scriptsDir, matcher, eventName) {
|
|
35
|
+
if (!fs_1.default.existsSync(settingsPath)) {
|
|
36
|
+
return { ok: false, detail: `settings.json not found: ${settingsPath}` };
|
|
37
|
+
}
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = JSON.parse(fs_1.default.readFileSync(settingsPath, 'utf-8'));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return { ok: false, detail: 'settings.json is not valid JSON' };
|
|
44
|
+
}
|
|
45
|
+
const entries = parsed?.hooks?.[eventName] ?? [];
|
|
46
|
+
const awmEntry = entries.find((e) => e?.matcher === matcher &&
|
|
47
|
+
(e?.hooks ?? []).some((h) => typeof h?.command === 'string' && h.command.includes(scriptsDir)));
|
|
48
|
+
if (!awmEntry) {
|
|
49
|
+
return { ok: false, detail: `no AWM SessionStart entry in ${settingsPath}` };
|
|
50
|
+
}
|
|
51
|
+
return { ok: true, detail: settingsPath };
|
|
52
|
+
}
|
|
53
|
+
function computeHookStatus(agent) {
|
|
54
|
+
const config = (0, providers_1.getHookConfig)(agent);
|
|
55
|
+
if (!config) {
|
|
56
|
+
throw new Error(`hooks not supported for agent target: ${agent}`);
|
|
57
|
+
}
|
|
58
|
+
const checks = {
|
|
59
|
+
bootstrapSkill: checkFile(path_1.default.join(config.scriptsDir, 'using-awm.md')),
|
|
60
|
+
sessionStartScript: checkExecutable(path_1.default.join(config.scriptsDir, 'session-start')),
|
|
61
|
+
runHookWrapper: checkExecutable(path_1.default.join(config.scriptsDir, 'run-hook.cmd')),
|
|
62
|
+
settingsEntry: checkSettingsEntry(config.settingsPath, config.scriptsDir, config.matcher, config.eventName)
|
|
63
|
+
};
|
|
64
|
+
const allOk = Object.values(checks).every((c) => c.ok);
|
|
65
|
+
const settingsOnlyMissing = !checks.settingsEntry.ok && checks.bootstrapSkill.ok && checks.sessionStartScript.ok && checks.runHookWrapper.ok;
|
|
66
|
+
let overall;
|
|
67
|
+
if (allOk)
|
|
68
|
+
overall = 'HEALTHY';
|
|
69
|
+
else if (settingsOnlyMissing)
|
|
70
|
+
overall = 'NOT_INSTALLED';
|
|
71
|
+
else
|
|
72
|
+
overall = 'DEGRADED';
|
|
73
|
+
return { overall, checks };
|
|
74
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
exports.uninstallHook = uninstallHook;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const providers_1 = require("../../providers");
|
|
10
|
+
function backupSettings(settingsPath) {
|
|
11
|
+
if (!fs_1.default.existsSync(settingsPath))
|
|
12
|
+
return null;
|
|
13
|
+
const awmHome = process.env.AWM_HOME || path_1.default.join(process.env.HOME, '.awm');
|
|
14
|
+
const backupDir = path_1.default.join(awmHome, 'backups');
|
|
15
|
+
fs_1.default.mkdirSync(backupDir, { recursive: true });
|
|
16
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '-').slice(0, 19);
|
|
17
|
+
const backupPath = path_1.default.join(backupDir, `settings.json.${ts}.bak`);
|
|
18
|
+
fs_1.default.copyFileSync(settingsPath, backupPath);
|
|
19
|
+
return backupPath;
|
|
20
|
+
}
|
|
21
|
+
function isAwmEntry(entry, scriptsDir, matcher) {
|
|
22
|
+
return (entry?.matcher === matcher &&
|
|
23
|
+
Array.isArray(entry?.hooks) &&
|
|
24
|
+
entry.hooks.some((h) => typeof h?.command === 'string' && h.command.includes(scriptsDir)));
|
|
25
|
+
}
|
|
26
|
+
function uninstallHook(options) {
|
|
27
|
+
const config = (0, providers_1.getHookConfig)(options.agent);
|
|
28
|
+
if (!config) {
|
|
29
|
+
throw new Error(`hooks not supported for agent target: ${options.agent}`);
|
|
30
|
+
}
|
|
31
|
+
if (!fs_1.default.existsSync(config.settingsPath)) {
|
|
32
|
+
return { status: 'not-installed', backupPath: null };
|
|
33
|
+
}
|
|
34
|
+
let settings;
|
|
35
|
+
try {
|
|
36
|
+
settings = JSON.parse(fs_1.default.readFileSync(config.settingsPath, 'utf-8'));
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new Error(`${config.settingsPath} is not valid JSON. Manual cleanup required.`);
|
|
40
|
+
}
|
|
41
|
+
const entries = settings?.hooks?.[config.eventName] ?? [];
|
|
42
|
+
const beforeLength = entries.length;
|
|
43
|
+
const filtered = entries.filter((e) => !isAwmEntry(e, config.scriptsDir, config.matcher));
|
|
44
|
+
if (filtered.length === beforeLength) {
|
|
45
|
+
return { status: 'not-installed', backupPath: null };
|
|
46
|
+
}
|
|
47
|
+
const backupPath = backupSettings(config.settingsPath);
|
|
48
|
+
if (filtered.length === 0) {
|
|
49
|
+
delete settings.hooks[config.eventName];
|
|
50
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
51
|
+
delete settings.hooks;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
settings.hooks[config.eventName] = filtered;
|
|
56
|
+
}
|
|
57
|
+
fs_1.default.writeFileSync(config.settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
58
|
+
return { status: 'uninstalled', backupPath };
|
|
59
|
+
}
|