agentic-workflow-manager 3.1.0 → 3.2.1

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 (92) hide show
  1. package/dist/src/commands/add.js +84 -0
  2. package/dist/src/commands/agent.js +95 -0
  3. package/dist/src/commands/backup.js +46 -0
  4. package/dist/src/commands/doctor.js +80 -4
  5. package/dist/src/commands/hooks/claude.js +192 -0
  6. package/dist/src/commands/hooks/codex.js +191 -0
  7. package/dist/src/commands/hooks/index.js +23 -8
  8. package/dist/src/commands/hooks/install.js +11 -107
  9. package/dist/src/commands/hooks/resync.js +28 -18
  10. package/dist/src/commands/hooks/shared.js +94 -0
  11. package/dist/src/commands/hooks/status.js +13 -64
  12. package/dist/src/commands/hooks/uninstall.js +11 -47
  13. package/dist/src/commands/init.js +90 -28
  14. package/dist/src/commands/registry/index.js +1 -1
  15. package/dist/src/commands/sync.js +113 -0
  16. package/dist/src/commands/update.js +107 -0
  17. package/dist/src/core/agent-targets.js +50 -0
  18. package/dist/src/core/artifact-state.js +65 -0
  19. package/dist/src/core/atomic-file.js +72 -0
  20. package/dist/src/core/bundle-install.js +60 -38
  21. package/dist/src/core/bundles.js +21 -0
  22. package/dist/src/core/context/managed-block.js +190 -0
  23. package/dist/src/core/context/orchestrator.js +3 -1
  24. package/dist/src/core/context/project-constitution-inject.js +1 -1
  25. package/dist/src/core/context/regenerate.js +3 -3
  26. package/dist/src/core/context/strategies/codex-agents.js +100 -0
  27. package/dist/src/core/diagnostics/checks.js +16 -0
  28. package/dist/src/core/diagnostics/context.js +37 -8
  29. package/dist/src/core/diagnostics/provider-checks.js +219 -0
  30. package/dist/src/core/executor.js +30 -9
  31. package/dist/src/core/init/mutation-targets.js +139 -0
  32. package/dist/src/core/init/provider-facts.js +179 -0
  33. package/dist/src/core/init/steps.js +65 -9
  34. package/dist/src/core/install-planner.js +206 -0
  35. package/dist/src/core/install-transaction.js +405 -0
  36. package/dist/src/core/profile.js +1 -1
  37. package/dist/src/core/provider-artifacts.js +54 -0
  38. package/dist/src/core/provider-version.js +36 -0
  39. package/dist/src/core/reconciliation.js +61 -0
  40. package/dist/src/core/registries.js +10 -0
  41. package/dist/src/core/renderers/canonical-agent.js +27 -0
  42. package/dist/src/core/renderers/codex-agent.js +60 -0
  43. package/dist/src/core/skill-integrity.js +4 -3
  44. package/dist/src/index.js +97 -289
  45. package/dist/src/providers/index.js +143 -39
  46. package/dist/src/ui/provider-preflight.js +26 -0
  47. package/dist/src/utils/config.js +101 -9
  48. package/dist/tests/commands/agent.test.js +103 -0
  49. package/dist/tests/commands/backup.test.js +109 -0
  50. package/dist/tests/commands/doctor.test.js +65 -4
  51. package/dist/tests/commands/hooks/codex.test.js +230 -0
  52. package/dist/tests/commands/hooks/install.test.js +20 -1
  53. package/dist/tests/commands/hooks/resync.test.js +53 -6
  54. package/dist/tests/commands/hooks/status.test.js +10 -0
  55. package/dist/tests/commands/hooks/uninstall.test.js +12 -0
  56. package/dist/tests/commands/init.test.js +195 -8
  57. package/dist/tests/commands/multi-agent-targeting.test.js +231 -0
  58. package/dist/tests/core/agent-targets.test.js +45 -0
  59. package/dist/tests/core/artifact-state.test.js +144 -0
  60. package/dist/tests/core/atomic-file.test.js +96 -0
  61. package/dist/tests/core/bundle-install.test.js +208 -17
  62. package/dist/tests/core/bundles.test.js +12 -0
  63. package/dist/tests/core/context/managed-block.test.js +90 -0
  64. package/dist/tests/core/context/orchestrator.test.js +48 -3
  65. package/dist/tests/core/context/regenerate.test.js +1 -1
  66. package/dist/tests/core/context/strategies/codex-agents.test.js +185 -0
  67. package/dist/tests/core/context/strategies/config-instructions.test.js +1 -1
  68. package/dist/tests/core/diagnostics/checks.test.js +19 -0
  69. package/dist/tests/core/diagnostics/context.test.js +74 -0
  70. package/dist/tests/core/diagnostics/provider-checks.test.js +252 -0
  71. package/dist/tests/core/executor.test.js +20 -0
  72. package/dist/tests/core/export/engine.test.js +3 -2
  73. package/dist/tests/core/init/mutation-targets.test.js +235 -0
  74. package/dist/tests/core/init/orchestrator.test.js +1 -1
  75. package/dist/tests/core/init/provider-facts.test.js +177 -0
  76. package/dist/tests/core/init/steps.test.js +106 -2
  77. package/dist/tests/core/install-planner.test.js +224 -0
  78. package/dist/tests/core/install-transaction.test.js +257 -0
  79. package/dist/tests/core/provider-artifacts.test.js +64 -0
  80. package/dist/tests/core/provider-version.test.js +53 -0
  81. package/dist/tests/core/reconciliation.test.js +165 -0
  82. package/dist/tests/core/registries-sync.test.js +4 -4
  83. package/dist/tests/core/renderers/canonical-agent.test.js +58 -0
  84. package/dist/tests/core/renderers/codex-agent.test.js +128 -0
  85. package/dist/tests/core/versioning.test.js +1 -1
  86. package/dist/tests/integration/codex-provider-isolated.test.js +235 -0
  87. package/dist/tests/providers/hooks-config.test.js +43 -25
  88. package/dist/tests/providers/index.test.js +144 -39
  89. package/dist/tests/structural/codex-agent-escaping-completeness.test.js +77 -0
  90. package/dist/tests/ui/provider-preflight.test.js +25 -0
  91. package/dist/tests/utils/config.test.js +178 -18
  92. package/package.json +1 -1
@@ -0,0 +1,84 @@
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.runAddBundleCore = runAddBundleCore;
7
+ // src/commands/add.ts
8
+ //
9
+ // `awm add <bundle>` — the non-interactive, headless bundle-activation path
10
+ // of `awm add` (works without a TTY, unlike the interactive artifact
11
+ // picker). Resolves agent targets the same way as `remove`/`sync`/`update`/
12
+ // `doctor` (R12/R13): every enabled agent when `--agent` is absent, or an
13
+ // explicit comma-separated subset validated against `enabledAgents`.
14
+ const picocolors_1 = __importDefault(require("picocolors"));
15
+ const agent_targets_1 = require("../core/agent-targets");
16
+ const bundles_1 = require("../core/bundles");
17
+ const bundle_install_1 = require("../core/bundle-install");
18
+ const profile_1 = require("../core/profile");
19
+ /**
20
+ * Installs `options.name`'s bundle closure for the resolved agent targets.
21
+ * Pure-ish: the only side effect is the injected `addBundle` (defaults to
22
+ * the real one). Returns a result object rather than calling `process.exit`
23
+ * so it's directly testable and composable with `index.ts`'s Commander
24
+ * action (which does the console output / exit-code translation).
25
+ */
26
+ function runAddBundleCore(options, prefs, bundles, deps = {}) {
27
+ const d = { addBundle: bundle_install_1.addBundle, ...deps };
28
+ const matchedBundle = bundles.find((b) => b.name === options.name);
29
+ if (!matchedBundle) {
30
+ console.error(picocolors_1.default.red(`Bundle "${options.name}" not found in registry.`));
31
+ console.error(picocolors_1.default.dim('Run `awm list` to see available packages.'));
32
+ return { code: 1, selectedAgents: [] };
33
+ }
34
+ const resolved = (0, agent_targets_1.resolveAgentTargetsOrError)({ prefs, explicit: options.agent });
35
+ if (!resolved.ok) {
36
+ console.error(picocolors_1.default.red(resolved.error));
37
+ return { code: 1, selectedAgents: [] };
38
+ }
39
+ const selectedAgents = resolved.targets;
40
+ let scopeOverride;
41
+ if (options.scope) {
42
+ if (!['local', 'global'].includes(options.scope)) {
43
+ console.error(picocolors_1.default.red(`Invalid scope "${options.scope}". Use: local or global.`));
44
+ return { code: 1, selectedAgents };
45
+ }
46
+ scopeOverride = options.scope;
47
+ }
48
+ const effective = scopeOverride ?? (0, bundles_1.defaultScopeForBundle)(matchedBundle.scope);
49
+ const cwd = options.cwd ?? process.cwd();
50
+ const projectRoot = (0, profile_1.findProjectRoot)(cwd);
51
+ if (effective === 'local' && !projectRoot) {
52
+ console.error(picocolors_1.default.red('No project root found (need a .git/, package.json, or .awm/profile.json here). Run inside a project, or pass --global.'));
53
+ return { code: 1, selectedAgents };
54
+ }
55
+ let result;
56
+ try {
57
+ result = d.addBundle({
58
+ bundleName: matchedBundle.name,
59
+ bundles,
60
+ agents: selectedAgents,
61
+ method: 'symlink',
62
+ projectRoot: projectRoot ?? cwd,
63
+ scopeOverride,
64
+ });
65
+ }
66
+ catch (e) {
67
+ console.error(picocolors_1.default.red(e.message));
68
+ return { code: 1, selectedAgents };
69
+ }
70
+ if (result.skipped.length > 0) {
71
+ for (const s of result.skipped)
72
+ console.log(picocolors_1.default.yellow(` ⚠ Skipped: ${s}`));
73
+ }
74
+ if (result.installed.length === 0) {
75
+ console.log(picocolors_1.default.yellow(`Nothing installed for bundle "${matchedBundle.name}".`));
76
+ return { code: 0, selectedAgents, result };
77
+ }
78
+ const lines = result.installed.map((n) => picocolors_1.default.green(n)).join('\n ');
79
+ const recordNote = result.recordedExtension
80
+ ? `\n\n${picocolors_1.default.dim('Recorded as a project extension in .awm/profile.json (commit it; symlinks are gitignored).')}`
81
+ : '';
82
+ console.log(`✅ Installed bundle ${picocolors_1.default.cyan(matchedBundle.name)}:\n ${lines}${recordNote}`);
83
+ return { code: 0, selectedAgents, result };
84
+ }
@@ -0,0 +1,95 @@
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.listAgents = listAgents;
7
+ exports.disableAgent = disableAgent;
8
+ exports.registerAgentCommand = registerAgentCommand;
9
+ const picocolors_1 = __importDefault(require("picocolors"));
10
+ const providers_1 = require("../providers");
11
+ const config_1 = require("../utils/config");
12
+ /** Every known agent target, tagged with this machine's enabled/default state. */
13
+ function listAgents() {
14
+ const prefs = (0, config_1.getPreferences)();
15
+ return providers_1.AGENT_TARGETS.map((id) => ({
16
+ id,
17
+ label: (0, providers_1.providerFor)(id).label,
18
+ enabled: prefs.enabledAgents.includes(id),
19
+ default: prefs.defaultAgent === id,
20
+ }));
21
+ }
22
+ /**
23
+ * Disables `agent` in AWM's own bookkeeping only (preferences.json's
24
+ * enabledAgents). Never touches the agent's provider files.
25
+ *
26
+ * Refuses to disable the current default without a `replacement` (must
27
+ * itself remain enabled after the change) — AWM always needs a default agent
28
+ * to fall back to for commands that don't specify `--agent`.
29
+ */
30
+ function disableAgent(agent, replacement) {
31
+ if (!(0, providers_1.isAgentTarget)(agent)) {
32
+ throw new Error(`Invalid agent target: ${String(agent)}`);
33
+ }
34
+ const prefs = (0, config_1.getPreferences)();
35
+ if (!prefs.enabledAgents.includes(agent)) {
36
+ throw new Error(`${agent} is not enabled`);
37
+ }
38
+ if (prefs.defaultAgent === agent && replacement === undefined) {
39
+ throw new Error('Cannot disable the default agent; pass --default <agent>');
40
+ }
41
+ if (replacement !== undefined && !(0, providers_1.isAgentTarget)(replacement)) {
42
+ throw new Error(`Invalid replacement agent target: ${String(replacement)}`);
43
+ }
44
+ const enabledAgents = prefs.enabledAgents.filter((candidate) => candidate !== agent);
45
+ const defaultAgent = replacement ?? prefs.defaultAgent;
46
+ if (!enabledAgents.includes(defaultAgent)) {
47
+ throw new Error(`Replacement default ${defaultAgent} must remain enabled`);
48
+ }
49
+ (0, config_1.savePreferences)({ ...prefs, enabledAgents, defaultAgent });
50
+ }
51
+ function registerAgentCommand(program) {
52
+ const agent = program.command('agent').description('Manage which agent targets AWM tracks');
53
+ agent.command('list')
54
+ .description('List every agent target and this machine\'s enabled/default state')
55
+ .option('--json', 'Emit the agent list as JSON')
56
+ .action((options) => {
57
+ const rows = listAgents();
58
+ if (options.json) {
59
+ process.stdout.write(JSON.stringify(rows, null, 2) + '\n');
60
+ return;
61
+ }
62
+ for (const row of rows) {
63
+ const marker = row.default ? picocolors_1.default.cyan(' (default)') : '';
64
+ const state = row.enabled ? picocolors_1.default.green('enabled') : picocolors_1.default.dim('disabled');
65
+ process.stdout.write(` ${row.id.padEnd(12)} ${state}${marker}\n`);
66
+ }
67
+ });
68
+ agent.command('disable <agent>')
69
+ .description('Stop AWM from managing <agent> (preferences only — provider files are untouched)')
70
+ .option('--default <agent>', 'Replacement default agent, required when disabling the current default')
71
+ .action((agentArg, options) => {
72
+ if (!(0, providers_1.isAgentTarget)(agentArg)) {
73
+ console.error(picocolors_1.default.red(`Invalid agent "${agentArg}". Use: ${providers_1.AGENT_TARGETS.join(', ')}.`));
74
+ process.exitCode = 1;
75
+ return;
76
+ }
77
+ let replacement;
78
+ if (options.default !== undefined) {
79
+ if (!(0, providers_1.isAgentTarget)(options.default)) {
80
+ console.error(picocolors_1.default.red(`Invalid --default "${options.default}". Use: ${providers_1.AGENT_TARGETS.join(', ')}.`));
81
+ process.exitCode = 1;
82
+ return;
83
+ }
84
+ replacement = options.default;
85
+ }
86
+ try {
87
+ disableAgent(agentArg, replacement);
88
+ console.log(picocolors_1.default.green(`✓ ${agentArg} disabled.`));
89
+ }
90
+ catch (e) {
91
+ console.error(picocolors_1.default.red(`✗ ${e.message}`));
92
+ process.exitCode = 1;
93
+ }
94
+ });
95
+ }
@@ -0,0 +1,46 @@
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.registerBackupCommand = registerBackupCommand;
7
+ const picocolors_1 = __importDefault(require("picocolors"));
8
+ const install_transaction_1 = require("../core/install-transaction");
9
+ function registerBackupCommand(program) {
10
+ const backup = program.command('backup')
11
+ .description('inspect and restore AWM filesystem backups (~/.awm/backups)');
12
+ backup
13
+ .command('list')
14
+ .description('list recorded backup transactions')
15
+ .option('--json', 'emit the list as JSON')
16
+ .action((options) => {
17
+ const backups = (0, install_transaction_1.listBackups)();
18
+ if (options.json) {
19
+ process.stdout.write(JSON.stringify(backups, null, 2) + '\n');
20
+ return;
21
+ }
22
+ if (backups.length === 0) {
23
+ console.log(picocolors_1.default.dim('(no backups recorded)'));
24
+ return;
25
+ }
26
+ for (const b of backups) {
27
+ const status = b.committed ? picocolors_1.default.dim('committed') : picocolors_1.default.yellow('uncommitted');
28
+ console.log(`${b.id} ${status} ${b.targets.length} target(s)`);
29
+ }
30
+ });
31
+ backup
32
+ .command('restore <transactionId>')
33
+ .description('restore every filesystem target recorded in a backup transaction')
34
+ .action((transactionId) => {
35
+ try {
36
+ const result = (0, install_transaction_1.restoreBackup)(transactionId);
37
+ console.log(picocolors_1.default.green(`Restored ${result.restored.length} target(s) from ${transactionId}`));
38
+ for (const target of result.restored)
39
+ console.log(` ${target}`);
40
+ }
41
+ catch (e) {
42
+ console.error(picocolors_1.default.red(e instanceof Error ? e.message : String(e)));
43
+ process.exit(1);
44
+ }
45
+ });
46
+ }
@@ -4,12 +4,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.renderReport = renderReport;
7
+ exports.renderProviderReport = renderProviderReport;
7
8
  exports.runDoctor = runDoctor;
8
9
  exports.registerDoctorCommand = registerDoctorCommand;
9
10
  const picocolors_1 = __importDefault(require("picocolors"));
10
11
  const context_1 = require("../core/diagnostics/context");
11
12
  const checks_1 = require("../core/diagnostics/checks");
12
13
  const paths_1 = require("../core/paths");
14
+ const config_1 = require("../utils/config");
15
+ const agent_targets_1 = require("../core/agent-targets");
13
16
  function glyph(status) {
14
17
  if (status === 'ok')
15
18
  return picocolors_1.default.green('✔');
@@ -52,10 +55,82 @@ function renderReport(report) {
52
55
  lines.push(`status: ${status} · ${actions} suggested actions`);
53
56
  return lines.join('\n');
54
57
  }
58
+ // --- Task 9: per-provider report rendering ---------------------------------
59
+ //
60
+ // Separate from renderReport/CheckReport above (which `init.ts` still uses,
61
+ // unchanged, for its before/after machine+project display). `doctor`'s own
62
+ // text/JSON output is provider-centric.
63
+ const CHECK_LABELS = {
64
+ 'binary.version': 'binary/version',
65
+ 'skills.global': 'global skills',
66
+ 'agents.native': 'native agents',
67
+ 'context.global': 'global context',
68
+ 'hook.trust': 'hook SessionStart',
69
+ 'guidance.project': 'project guidance',
70
+ 'constitution.delivery': 'constitution delivery',
71
+ };
72
+ const OK_STATES = ['supported', 'healthy', 'shared', 'delivered'];
73
+ const PENDING_STATES = ['pending', 'pending-trust'];
74
+ const WARN_STATES = ['stale'];
75
+ function providerGlyph(state) {
76
+ if (OK_STATES.includes(state))
77
+ return picocolors_1.default.green('✔');
78
+ if (PENDING_STATES.includes(state))
79
+ return picocolors_1.default.yellow('◷');
80
+ if (WARN_STATES.includes(state))
81
+ return picocolors_1.default.yellow('⚠');
82
+ return picocolors_1.default.red('✖');
83
+ }
84
+ function providerCheckDetail(check) {
85
+ if (check.id === 'binary.version' && check.target)
86
+ return picocolors_1.default.dim(` (${check.target})`);
87
+ if (check.state === 'pending-trust')
88
+ return picocolors_1.default.dim(' (pending trust)');
89
+ if (check.detail)
90
+ return picocolors_1.default.dim(` (${check.detail})`);
91
+ return '';
92
+ }
93
+ function providerCheckRemedy(check) {
94
+ return check.remediationCode ? picocolors_1.default.dim(` → ${check.remediationCode}`) : '';
95
+ }
96
+ function providerCheckLine(check) {
97
+ return ` ${providerGlyph(check.state)} ${CHECK_LABELS[check.id]}${providerCheckDetail(check)}${providerCheckRemedy(check)}`;
98
+ }
99
+ function renderProviderReport(report) {
100
+ const lines = [];
101
+ lines.push(picocolors_1.default.bold('AWM · harness status'));
102
+ lines.push('');
103
+ for (const provider of report.providers) {
104
+ lines.push(`Provider: ${provider.label}`);
105
+ for (const check of provider.checks)
106
+ lines.push(providerCheckLine(check));
107
+ lines.push('');
108
+ }
109
+ const status = report.overall === 'healthy' ? picocolors_1.default.green('healthy') : picocolors_1.default.red('degraded');
110
+ lines.push(`status: ${status}`);
111
+ return lines.join('\n');
112
+ }
55
113
  function runDoctor(opts = {}) {
114
+ const resolveTargets = opts.resolveTargets ?? agent_targets_1.resolveAgentTargets;
115
+ // Validates --agent separately from the general diagnostic gathering below:
116
+ // an unknown or disabled agent is a normal input-validation failure (the
117
+ // user typo'd or forgot `awm init --agent <x>`), not an "internal error" —
118
+ // give it its own clear message rather than lumping it under the generic
119
+ // internal-error catch further down.
120
+ let targets;
121
+ try {
122
+ const prefs = (0, config_1.getPreferences)();
123
+ targets = resolveTargets({ prefs, explicit: opts.agent });
124
+ }
125
+ catch (err) {
126
+ process.stderr.write(`awm doctor: ${err.message}\n`);
127
+ return 2;
128
+ }
56
129
  let report;
57
130
  try {
58
- report = (0, checks_1.runChecks)((0, context_1.gatherContext)({ cwd: opts.cwd }));
131
+ const ctx = (0, context_1.gatherContext)({ cwd: opts.cwd, agents: targets });
132
+ const providers = ctx.providers ?? [];
133
+ report = { providers, overall: (0, checks_1.computeProviderOverall)(providers) };
59
134
  }
60
135
  catch (err) {
61
136
  process.stderr.write(`awm doctor: internal error: ${err.message}\n`);
@@ -65,15 +140,16 @@ function runDoctor(opts = {}) {
65
140
  process.stdout.write(JSON.stringify(report, null, 2) + '\n');
66
141
  }
67
142
  else {
68
- process.stdout.write(renderReport(report) + '\n');
143
+ process.stdout.write(renderProviderReport(report) + '\n');
69
144
  }
70
145
  return report.overall === 'healthy' ? 0 : 1;
71
146
  }
72
147
  function registerDoctorCommand(program) {
73
148
  program.command('doctor')
74
- .description('Read-only dashboard of the AWM harness state (machine + project)')
149
+ .description('Read-only dashboard of the AWM harness state, per provider')
75
150
  .option('--json', 'Emit the diagnostic report as JSON')
151
+ .option('-a, --agent <agent>', 'Target agent subset (comma-separated); defaults to every enabled agent')
76
152
  .action((options) => {
77
- process.exitCode = runDoctor({ json: options.json });
153
+ process.exitCode = runDoctor({ json: options.json, agent: options.agent });
78
154
  });
79
155
  }
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ // cli/src/commands/hooks/claude.ts
3
+ //
4
+ // Claude Code hook adapter: merges a single AWM SessionStart entry into
5
+ // ~/.claude/settings.json, keyed by matcher + scriptsDir substring match.
6
+ // Extracted verbatim (behavior-preserving) from the former monolithic
7
+ // install.ts/status.ts/uninstall.ts — see tests/commands/hooks/install.test.ts's
8
+ // characterization test for the frozen contract this must keep honoring.
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.installClaudeHook = installClaudeHook;
14
+ exports.computeClaudeHookStatus = computeClaudeHookStatus;
15
+ exports.uninstallClaudeHook = uninstallClaudeHook;
16
+ exports.resyncClaudeHookFiles = resyncClaudeHookFiles;
17
+ exports.claudeResyncSourcesExist = claudeResyncSourcesExist;
18
+ const fs_1 = __importDefault(require("fs"));
19
+ const path_1 = __importDefault(require("path"));
20
+ const providers_1 = require("../../providers");
21
+ const shared_1 = require("./shared");
22
+ function isAwmEntry(entry, scriptsDir, matcher) {
23
+ return (entry?.matcher === matcher &&
24
+ Array.isArray(entry?.hooks) &&
25
+ entry.hooks.some((h) => typeof h?.command === 'string' && h.command.includes(scriptsDir)));
26
+ }
27
+ function installClaudeHook(options) {
28
+ const config = (0, providers_1.getSettingsMergeHookConfig)(options.agent);
29
+ // 1. Verify registry sources exist FIRST (before touching settings)
30
+ const sourceHooks = path_1.default.join(options.registryRoot, 'hooks');
31
+ const sourceSkill = path_1.default.join(options.registryRoot, 'skills/using-awm/SKILL.md');
32
+ if (!fs_1.default.existsSync(path_1.default.join(sourceHooks, 'session-start'))) {
33
+ throw new Error(`AWM registry not found at ${sourceHooks}. Run 'awm update' to refresh the registry.`);
34
+ }
35
+ if (!fs_1.default.existsSync(sourceSkill)) {
36
+ throw new Error(`using-awm skill not found at ${sourceSkill}. Run 'awm update' first.`);
37
+ }
38
+ // 2. Sync scripts
39
+ fs_1.default.mkdirSync(config.scriptsDir, { recursive: true });
40
+ (0, shared_1.syncExecutable)(path_1.default.join(sourceHooks, 'session-start'), path_1.default.join(config.scriptsDir, 'session-start'), options.installMethod);
41
+ (0, shared_1.syncExecutable)(path_1.default.join(sourceHooks, 'run-hook.cmd'), path_1.default.join(config.scriptsDir, 'run-hook.cmd'), options.installMethod);
42
+ // 3. Link the skill (default: symlink so 'awm update' propagates; fall back to copy if symlink is unavailable, e.g. Windows without Developer Mode)
43
+ const skillDest = path_1.default.join(config.scriptsDir, 'using-awm.md');
44
+ try {
45
+ fs_1.default.unlinkSync(skillDest);
46
+ }
47
+ catch { /* not exists */ }
48
+ try {
49
+ fs_1.default.symlinkSync(sourceSkill, skillDest);
50
+ }
51
+ catch {
52
+ // best-effort: copy the single skill file; 'awm update' will not auto-propagate
53
+ fs_1.default.copyFileSync(sourceSkill, skillDest);
54
+ }
55
+ // 4. Backup settings if it exists
56
+ const backupPath = (0, shared_1.backupManagedFile)(config.settingsPath);
57
+ // 5. Read or initialize settings
58
+ let settings = {};
59
+ if (fs_1.default.existsSync(config.settingsPath)) {
60
+ const raw = fs_1.default.readFileSync(config.settingsPath, 'utf-8');
61
+ try {
62
+ settings = JSON.parse(raw);
63
+ }
64
+ catch {
65
+ throw new Error(`${config.settingsPath} is not valid JSON. Backup created at ${backupPath}. Fix the file manually, then re-run.`);
66
+ }
67
+ }
68
+ else {
69
+ fs_1.default.mkdirSync(path_1.default.dirname(config.settingsPath), { recursive: true });
70
+ }
71
+ // 6. Merge AWM entry
72
+ if (!settings.hooks)
73
+ settings.hooks = {};
74
+ if (!settings.hooks[config.eventName])
75
+ settings.hooks[config.eventName] = [];
76
+ const entries = settings.hooks[config.eventName];
77
+ const awmEntryIdx = entries.findIndex((e) => isAwmEntry(e, config.scriptsDir, config.matcher));
78
+ const newEntry = {
79
+ matcher: config.matcher,
80
+ hooks: [{
81
+ type: 'command',
82
+ command: `${path_1.default.join(config.scriptsDir, 'run-hook.cmd')} session-start`,
83
+ async: false
84
+ }]
85
+ };
86
+ let status;
87
+ if (awmEntryIdx >= 0) {
88
+ if (JSON.stringify(entries[awmEntryIdx]) === JSON.stringify(newEntry)) {
89
+ return { status: 'already-up-to-date', scriptsDir: config.scriptsDir, settingsPath: config.settingsPath, backupPath: null };
90
+ }
91
+ entries[awmEntryIdx] = newEntry;
92
+ status = 'installed';
93
+ }
94
+ else {
95
+ entries.push(newEntry);
96
+ status = 'installed';
97
+ }
98
+ // 7. Write settings
99
+ fs_1.default.writeFileSync(config.settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
100
+ return { status, scriptsDir: config.scriptsDir, settingsPath: config.settingsPath, backupPath };
101
+ }
102
+ function checkSettingsEntry(settingsPath, scriptsDir, matcher, eventName) {
103
+ if (!fs_1.default.existsSync(settingsPath)) {
104
+ return { ok: false, detail: `settings.json not found: ${settingsPath}` };
105
+ }
106
+ let parsed;
107
+ try {
108
+ parsed = JSON.parse(fs_1.default.readFileSync(settingsPath, 'utf-8'));
109
+ }
110
+ catch {
111
+ return { ok: false, detail: 'settings.json is not valid JSON' };
112
+ }
113
+ const entries = parsed?.hooks?.[eventName] ?? [];
114
+ const awmEntry = entries.find((e) => e?.matcher === matcher &&
115
+ (e?.hooks ?? []).some((h) => typeof h?.command === 'string' && h.command.includes(scriptsDir)));
116
+ if (!awmEntry) {
117
+ return { ok: false, detail: `no AWM SessionStart entry in ${settingsPath}` };
118
+ }
119
+ return { ok: true, detail: settingsPath };
120
+ }
121
+ function computeClaudeHookStatus(agent) {
122
+ const config = (0, providers_1.getSettingsMergeHookConfig)(agent);
123
+ const checks = {
124
+ bootstrapSkill: (0, shared_1.checkFile)(path_1.default.join(config.scriptsDir, 'using-awm.md')),
125
+ sessionStartScript: (0, shared_1.checkExecutable)(path_1.default.join(config.scriptsDir, 'session-start')),
126
+ runHookWrapper: (0, shared_1.checkExecutable)(path_1.default.join(config.scriptsDir, 'run-hook.cmd')),
127
+ settingsEntry: checkSettingsEntry(config.settingsPath, config.scriptsDir, config.matcher, config.eventName)
128
+ };
129
+ const allOk = Object.values(checks).every((c) => c.ok);
130
+ const settingsOnlyMissing = !checks.settingsEntry.ok && checks.bootstrapSkill.ok && checks.sessionStartScript.ok && checks.runHookWrapper.ok;
131
+ let overall;
132
+ if (allOk)
133
+ overall = 'HEALTHY';
134
+ else if (settingsOnlyMissing)
135
+ overall = 'NOT_INSTALLED';
136
+ else
137
+ overall = 'DEGRADED';
138
+ return { overall, checks };
139
+ }
140
+ function uninstallClaudeHook(agent) {
141
+ const config = (0, providers_1.getSettingsMergeHookConfig)(agent);
142
+ if (!fs_1.default.existsSync(config.settingsPath)) {
143
+ return { status: 'not-installed', backupPath: null };
144
+ }
145
+ let settings;
146
+ try {
147
+ settings = JSON.parse(fs_1.default.readFileSync(config.settingsPath, 'utf-8'));
148
+ }
149
+ catch {
150
+ throw new Error(`${config.settingsPath} is not valid JSON. Manual cleanup required.`);
151
+ }
152
+ const entries = settings?.hooks?.[config.eventName] ?? [];
153
+ const beforeLength = entries.length;
154
+ const filtered = entries.filter((e) => !isAwmEntry(e, config.scriptsDir, config.matcher));
155
+ if (filtered.length === beforeLength) {
156
+ return { status: 'not-installed', backupPath: null };
157
+ }
158
+ const backupPath = (0, shared_1.backupManagedFile)(config.settingsPath);
159
+ if (filtered.length === 0) {
160
+ delete settings.hooks[config.eventName];
161
+ if (Object.keys(settings.hooks).length === 0) {
162
+ delete settings.hooks;
163
+ }
164
+ }
165
+ else {
166
+ settings.hooks[config.eventName] = filtered;
167
+ }
168
+ fs_1.default.writeFileSync(config.settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
169
+ return { status: 'uninstalled', backupPath };
170
+ }
171
+ /** Refresh the Claude hook's script/skill files in place (used by resync). Assumes the caller has verified the settings entry is already present. */
172
+ function resyncClaudeHookFiles(config, registryRoot, method) {
173
+ const sourceHooks = path_1.default.join(registryRoot, 'hooks');
174
+ const sourceSkill = path_1.default.join(registryRoot, 'skills/using-awm/SKILL.md');
175
+ fs_1.default.mkdirSync(config.scriptsDir, { recursive: true });
176
+ (0, shared_1.syncExecutable)(path_1.default.join(sourceHooks, 'session-start'), path_1.default.join(config.scriptsDir, 'session-start'), method);
177
+ (0, shared_1.syncExecutable)(path_1.default.join(sourceHooks, 'run-hook.cmd'), path_1.default.join(config.scriptsDir, 'run-hook.cmd'), method);
178
+ const skillDest = path_1.default.join(config.scriptsDir, 'using-awm.md');
179
+ try {
180
+ fs_1.default.unlinkSync(skillDest);
181
+ }
182
+ catch { /* not exists */ }
183
+ fs_1.default.symlinkSync(sourceSkill, skillDest);
184
+ }
185
+ /** True when the registry has everything needed to resync the Claude hook files. */
186
+ function claudeResyncSourcesExist(registryRoot) {
187
+ const sourceHooks = path_1.default.join(registryRoot, 'hooks');
188
+ const sourceSkill = path_1.default.join(registryRoot, 'skills/using-awm/SKILL.md');
189
+ return fs_1.default.existsSync(path_1.default.join(sourceHooks, 'session-start')) &&
190
+ fs_1.default.existsSync(path_1.default.join(sourceHooks, 'run-hook.cmd')) &&
191
+ fs_1.default.existsSync(sourceSkill);
192
+ }