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.
Files changed (145) hide show
  1. package/README.md +62 -0
  2. package/dist/src/commands/doctor.js +77 -0
  3. package/dist/src/commands/hooks/index.js +118 -0
  4. package/dist/src/commands/hooks/install.js +113 -0
  5. package/dist/src/commands/hooks/resync.js +50 -0
  6. package/dist/src/commands/hooks/status.js +74 -0
  7. package/dist/src/commands/hooks/uninstall.js +59 -0
  8. package/dist/src/commands/init.js +181 -0
  9. package/dist/src/commands/ledger/index.js +73 -0
  10. package/dist/src/commands/pin.js +75 -0
  11. package/dist/src/commands/registry/add.js +62 -0
  12. package/dist/src/commands/registry/index.js +166 -0
  13. package/dist/src/commands/registry/install-bundles.js +36 -0
  14. package/dist/src/commands/registry/remove.js +18 -0
  15. package/dist/src/commands/registry/status.js +31 -0
  16. package/dist/src/commands/sensors/baseline.js +93 -0
  17. package/dist/src/commands/sensors/formatters/eslint.js +29 -0
  18. package/dist/src/commands/sensors/formatters/generic.js +8 -0
  19. package/dist/src/commands/sensors/formatters/semgrep.js +18 -0
  20. package/dist/src/commands/sensors/formatters/test.js +12 -0
  21. package/dist/src/commands/sensors/formatters/tsc.js +23 -0
  22. package/dist/src/commands/sensors/index.js +94 -0
  23. package/dist/src/commands/sensors/init.js +112 -0
  24. package/dist/src/commands/sensors/install.js +78 -0
  25. package/dist/src/commands/sensors/run.js +217 -0
  26. package/dist/src/commands/sensors/status.js +85 -0
  27. package/dist/src/commands/sensors/types.js +2 -0
  28. package/dist/src/core/bundle-install.js +116 -0
  29. package/dist/src/core/bundles.js +122 -0
  30. package/dist/src/core/cli-version.js +30 -0
  31. package/dist/src/core/context/materializer.js +30 -0
  32. package/dist/src/core/context/orchestrator.js +75 -0
  33. package/dist/src/core/context/project-constitution-inject.js +47 -0
  34. package/dist/src/core/context/provider.js +29 -0
  35. package/dist/src/core/context/regenerate.js +61 -0
  36. package/dist/src/core/context/strategies/config-instructions.js +73 -0
  37. package/dist/src/core/context/strategies/hook-merge.js +23 -0
  38. package/dist/src/core/context/strategies/strategy.js +2 -0
  39. package/dist/src/core/context/types.js +2 -0
  40. package/dist/src/core/diagnostics/checks.js +141 -0
  41. package/dist/src/core/diagnostics/context.js +181 -0
  42. package/dist/src/core/diagnostics/types.js +2 -0
  43. package/dist/src/core/discovery.js +135 -0
  44. package/dist/src/core/executor.js +41 -0
  45. package/dist/src/core/init/detector.js +67 -0
  46. package/dist/src/core/init/orchestrator.js +54 -0
  47. package/dist/src/core/init/steps.js +279 -0
  48. package/dist/src/core/init/types.js +2 -0
  49. package/dist/src/core/ledger/store.js +73 -0
  50. package/dist/src/core/ledger/types.js +2 -0
  51. package/dist/src/core/miro.js +314 -0
  52. package/dist/src/core/profile-pins.js +36 -0
  53. package/dist/src/core/profile.js +146 -0
  54. package/dist/src/core/registries.js +205 -0
  55. package/dist/src/core/registry.js +28 -0
  56. package/dist/src/core/skill-integrity.js +97 -0
  57. package/dist/src/core/story-map-parser.js +83 -0
  58. package/dist/src/core/update-check-worker.js +10 -0
  59. package/dist/src/core/update-check.js +106 -0
  60. package/dist/src/core/versioning.js +112 -0
  61. package/dist/src/index.js +689 -0
  62. package/dist/src/providers/index.js +63 -0
  63. package/dist/src/utils/config.js +35 -0
  64. package/dist/src/utils/grouping.js +51 -0
  65. package/dist/src/utils/registry-view.js +154 -0
  66. package/dist/tests/commands/doctor.test.js +98 -0
  67. package/dist/tests/commands/hooks/install.test.js +149 -0
  68. package/dist/tests/commands/hooks/resync.test.js +135 -0
  69. package/dist/tests/commands/hooks/router.test.js +26 -0
  70. package/dist/tests/commands/hooks/status.test.js +88 -0
  71. package/dist/tests/commands/hooks/uninstall.test.js +104 -0
  72. package/dist/tests/commands/init.test.js +87 -0
  73. package/dist/tests/commands/ledger/index.test.js +64 -0
  74. package/dist/tests/commands/pin.test.js +72 -0
  75. package/dist/tests/commands/registry/add.test.js +223 -0
  76. package/dist/tests/commands/registry/install-bundles.test.js +87 -0
  77. package/dist/tests/commands/registry/remove.test.js +49 -0
  78. package/dist/tests/commands/registry/status.test.js +43 -0
  79. package/dist/tests/commands/sensors/baseline.test.js +106 -0
  80. package/dist/tests/commands/sensors/formatters/eslint.test.js +32 -0
  81. package/dist/tests/commands/sensors/formatters/semgrep.test.js +38 -0
  82. package/dist/tests/commands/sensors/formatters/tsc.test.js +25 -0
  83. package/dist/tests/commands/sensors/index.test.js +18 -0
  84. package/dist/tests/commands/sensors/init.test.js +137 -0
  85. package/dist/tests/commands/sensors/install.test.js +60 -0
  86. package/dist/tests/commands/sensors/router.test.js +23 -0
  87. package/dist/tests/commands/sensors/run.test.js +353 -0
  88. package/dist/tests/commands/sensors/status.test.js +98 -0
  89. package/dist/tests/core/base-remote.test.js +70 -0
  90. package/dist/tests/core/bundle-install.test.js +198 -0
  91. package/dist/tests/core/bundles-multiroot.test.js +68 -0
  92. package/dist/tests/core/bundles-overrides.test.js +49 -0
  93. package/dist/tests/core/bundles.test.js +96 -0
  94. package/dist/tests/core/cli-version.test.js +17 -0
  95. package/dist/tests/core/context/materializer.test.js +46 -0
  96. package/dist/tests/core/context/orchestrator.test.js +127 -0
  97. package/dist/tests/core/context/project-constitution-inject.test.js +82 -0
  98. package/dist/tests/core/context/provider.test.js +45 -0
  99. package/dist/tests/core/context/regenerate.test.js +106 -0
  100. package/dist/tests/core/context/strategies/config-instructions.test.js +88 -0
  101. package/dist/tests/core/context/strategies/hook-merge.test.js +71 -0
  102. package/dist/tests/core/context/types.test.js +15 -0
  103. package/dist/tests/core/diagnostics/checks.test.js +208 -0
  104. package/dist/tests/core/diagnostics/context.test.js +170 -0
  105. package/dist/tests/core/discovery-multiroot.test.js +63 -0
  106. package/dist/tests/core/discovery-overrides.test.js +105 -0
  107. package/dist/tests/core/discovery.test.js +113 -0
  108. package/dist/tests/core/executor.test.js +44 -0
  109. package/dist/tests/core/init/detector.test.js +56 -0
  110. package/dist/tests/core/init/orchestrator.test.js +122 -0
  111. package/dist/tests/core/init/steps.test.js +363 -0
  112. package/dist/tests/core/ledger/gitignore.test.js +13 -0
  113. package/dist/tests/core/ledger/store.test.js +122 -0
  114. package/dist/tests/core/miro-layout.test.js +150 -0
  115. package/dist/tests/core/profile-pins.test.js +131 -0
  116. package/dist/tests/core/profile-registries.test.js +59 -0
  117. package/dist/tests/core/profile.test.js +110 -0
  118. package/dist/tests/core/registries-capability.test.js +52 -0
  119. package/dist/tests/core/registries-seed.test.js +66 -0
  120. package/dist/tests/core/registries-sync.test.js +205 -0
  121. package/dist/tests/core/registries.test.js +91 -0
  122. package/dist/tests/core/registry-manifest.test.js +95 -0
  123. package/dist/tests/core/registry-versioned-sync.test.js +113 -0
  124. package/dist/tests/core/registry.test.js +51 -0
  125. package/dist/tests/core/skill-integrity.test.js +126 -0
  126. package/dist/tests/core/story-map-parser.test.js +136 -0
  127. package/dist/tests/core/sync-gates.test.js +97 -0
  128. package/dist/tests/core/update-check.test.js +106 -0
  129. package/dist/tests/core/versioning.test.js +169 -0
  130. package/dist/tests/integration/pack-e2e.test.js +50 -0
  131. package/dist/tests/providers/hooks-config.test.js +45 -0
  132. package/dist/tests/providers/index.test.js +58 -0
  133. package/dist/tests/providers/injection-config.test.js +25 -0
  134. package/dist/tests/registry/b3-ledger-wiring.test.js +74 -0
  135. package/dist/tests/registry/catalog-consistency.test.js +47 -0
  136. package/dist/tests/registry/design-skills.test.js +146 -0
  137. package/dist/tests/registry/prose-agnostic.test.js +18 -0
  138. package/dist/tests/registry/sensor-packs.test.js +45 -0
  139. package/dist/tests/registry/skill-versions.test.js +26 -0
  140. package/dist/tests/registry/using-awm.test.js +39 -0
  141. package/dist/tests/utils/config.test.js +31 -0
  142. package/dist/tests/utils/grouping.test.js +43 -0
  143. package/dist/tests/utils/registry-view-overrides.test.js +41 -0
  144. package/dist/tests/utils/registry-view.test.js +156 -0
  145. 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
+ }