@lifeaitools/clauth 1.30.26 → 1.31.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 (39) hide show
  1. package/.clauth-skill/SKILL.md +0 -31
  2. package/.clauth-skill/references/keys-guide.md +270 -270
  3. package/.clauth-skill/references/operator-guide.md +0 -27
  4. package/README.md +2 -48
  5. package/cli/api.js +238 -238
  6. package/cli/commands/agent-pool.js +51 -15
  7. package/cli/commands/install.js +396 -396
  8. package/cli/commands/login.js +135 -0
  9. package/cli/commands/login.test.js +73 -0
  10. package/cli/commands/serve.js +16 -846
  11. package/cli/commands/uninstall.js +164 -164
  12. package/cli/commands/watchdog.js +1 -1
  13. package/cli/index.js +29 -20
  14. package/cli/supervisor-registry.js +1 -6
  15. package/cli/watchdog-registry.js +2 -30
  16. package/cli/watchdog-registry.test.js +5 -28
  17. package/cli/webdav-service.js +339 -339
  18. package/install.ps1 +102 -102
  19. package/install.sh +49 -49
  20. package/package.json +4 -6
  21. package/scripts/bin/bootstrap-linux +0 -0
  22. package/scripts/bin/bootstrap-macos +0 -0
  23. package/scripts/bin/bootstrap-win.exe +0 -0
  24. package/scripts/bootstrap.cjs +121 -121
  25. package/scripts/build.mjs +66 -0
  26. package/scripts/build.sh +5 -45
  27. package/scripts/postinstall.js +189 -189
  28. package/supabase/functions/auth-vault/index.ts +350 -350
  29. package/supabase/migrations/001_clauth_schema.sql +94 -94
  30. package/supabase/migrations/002_vault_helpers.sql +90 -90
  31. package/supabase/migrations/20260317_lockout.sql +26 -26
  32. package/cli/commands/ops-install.js +0 -211
  33. package/cli/commands/ops.js +0 -69
  34. package/cli/ops/coolify-adapter.js +0 -80
  35. package/cli/ops/deployment-adapter.js +0 -63
  36. package/cli/ops/job-store.js +0 -116
  37. package/cli/ops/operation-policy.js +0 -51
  38. package/cli/ops/pm2-adapter.js +0 -128
  39. package/cli/ops/serialized-executor.js +0 -9
@@ -1,164 +1,164 @@
1
- // cli/commands/uninstall.js
2
- // clauth uninstall — full teardown: DB objects, Edge Function, secrets, skill, local config
3
- //
4
- // Reverses everything `clauth install` does:
5
- // 1. Drops clauth tables, policies, triggers, functions from Supabase
6
- // 2. Deletes auth-vault Edge Function
7
- // 3. Removes CLAUTH_* secrets
8
- // 4. Removes Claude skill directory
9
- // 5. Clears local config (Conf store)
10
-
11
- import { existsSync, rmSync } from 'fs';
12
- import { join } from 'path';
13
- import Conf from 'conf';
14
- import chalk from 'chalk';
15
- import ora from 'ora';
16
-
17
- const MGMT = 'https://api.supabase.com/v1';
18
- const SKILLS_DIR = process.env.CLAUTH_SKILLS_DIR ||
19
- (process.platform === 'win32'
20
- ? join(process.env.USERPROFILE || '', '.claude', 'skills')
21
- : join(process.env.HOME || '', '.claude', 'skills'));
22
-
23
- // ─────────────────────────────────────────────
24
- // Supabase Management API helper
25
- // ─────────────────────────────────────────────
26
- async function mgmt(pat, method, path, body) {
27
- const res = await fetch(`${MGMT}${path}`, {
28
- method,
29
- headers: { 'Authorization': `Bearer ${pat}`, 'Content-Type': 'application/json' },
30
- body: body ? JSON.stringify(body) : undefined,
31
- });
32
- if (!res.ok) {
33
- const text = await res.text().catch(() => res.statusText);
34
- throw new Error(`${method} ${path} → HTTP ${res.status}: ${text}`);
35
- }
36
- if (res.status === 204) return {};
37
- const text = await res.text();
38
- if (!text) return {};
39
- return JSON.parse(text);
40
- }
41
-
42
- // ─────────────────────────────────────────────
43
- // Main uninstall command
44
- // ─────────────────────────────────────────────
45
- export async function runUninstall(opts = {}) {
46
- console.log(chalk.red('\n🗑️ clauth uninstall\n'));
47
-
48
- const config = new Conf({ projectName: 'clauth' });
49
-
50
- // ── Collect credentials ────────────────────
51
- const ref = opts.ref || config.get('supabase_url')?.match(/https:\/\/(.+)\.supabase\.co/)?.[1];
52
- const pat = opts.pat;
53
-
54
- if (!ref) {
55
- console.log(chalk.red(' Cannot determine Supabase project ref.'));
56
- console.log(chalk.gray(' Use: clauth uninstall --ref <project-ref> --pat <personal-access-token>'));
57
- process.exit(1);
58
- }
59
- if (!pat) {
60
- console.log(chalk.red(' Supabase PAT required for teardown.'));
61
- console.log(chalk.gray(' Use: clauth uninstall --ref <project-ref> --pat <personal-access-token>'));
62
- process.exit(1);
63
- }
64
-
65
- console.log(chalk.gray(` Project: ${ref}\n`));
66
-
67
- // ── Step 1: Drop database objects ──────────
68
- const s1 = ora('Dropping clauth database objects...').start();
69
- const teardownSQL = `
70
- -- Drop triggers
71
- DROP TRIGGER IF EXISTS clauth_services_updated ON public.clauth_services;
72
-
73
- -- Drop tables (CASCADE drops policies automatically)
74
- DROP TABLE IF EXISTS public.clauth_audit CASCADE;
75
- DROP TABLE IF EXISTS public.clauth_machines CASCADE;
76
- DROP TABLE IF EXISTS public.clauth_services CASCADE;
77
-
78
- -- Drop functions
79
- DROP FUNCTION IF EXISTS public.clauth_touch_updated() CASCADE;
80
- DROP FUNCTION IF EXISTS public.clauth_upsert_vault_secret(text, text) CASCADE;
81
- DROP FUNCTION IF EXISTS public.clauth_get_vault_secret(text) CASCADE;
82
- DROP FUNCTION IF EXISTS public.clauth_delete_vault_secret(text) CASCADE;
83
- `;
84
-
85
- try {
86
- await mgmt(pat, 'POST', `/projects/${ref}/database/query`, { query: teardownSQL });
87
- s1.succeed('Database objects dropped (tables, triggers, functions, policies)');
88
- } catch (e) {
89
- s1.fail(`Database teardown failed: ${e.message}`);
90
- console.log(chalk.yellow(' You may need to drop objects manually via SQL editor.'));
91
- }
92
-
93
- // ── Step 2: Delete Edge Function ───────────
94
- const s2 = ora('Deleting auth-vault Edge Function...').start();
95
- try {
96
- const res = await fetch(`${MGMT}/projects/${ref}/functions/auth-vault`, {
97
- method: 'DELETE',
98
- headers: { 'Authorization': `Bearer ${pat}` },
99
- });
100
- if (res.ok || res.status === 404) {
101
- s2.succeed(res.status === 404
102
- ? 'Edge Function not found (already deleted)'
103
- : 'Edge Function deleted');
104
- } else {
105
- const text = await res.text().catch(() => res.statusText);
106
- throw new Error(`HTTP ${res.status}: ${text}`);
107
- }
108
- } catch (e) {
109
- s2.fail(`Edge Function delete failed: ${e.message}`);
110
- console.log(chalk.yellow(' Delete manually: Supabase Dashboard → Edge Functions → auth-vault → Delete'));
111
- }
112
-
113
- // ── Step 3: Remove secrets ─────────────────
114
- const s3 = ora('Removing clauth secrets...').start();
115
- try {
116
- // Supabase Management API: DELETE /projects/{ref}/secrets with body listing secret names
117
- const res = await fetch(`${MGMT}/projects/${ref}/secrets`, {
118
- method: 'DELETE',
119
- headers: { 'Authorization': `Bearer ${pat}`, 'Content-Type': 'application/json' },
120
- body: JSON.stringify(['CLAUTH_HMAC_SALT', 'CLAUTH_ADMIN_BOOTSTRAP_TOKEN']),
121
- });
122
- if (res.ok) {
123
- s3.succeed('Secrets removed (CLAUTH_HMAC_SALT, CLAUTH_ADMIN_BOOTSTRAP_TOKEN)');
124
- } else {
125
- const text = await res.text().catch(() => res.statusText);
126
- throw new Error(`HTTP ${res.status}: ${text}`);
127
- }
128
- } catch (e) {
129
- s3.warn(`Secret removal failed: ${e.message}`);
130
- console.log(chalk.yellow(' Remove manually: Supabase → Settings → Edge Functions → Secrets'));
131
- }
132
-
133
- // ── Step 4: Remove Claude skill ────────────
134
- const s4 = ora('Removing Claude skill...').start();
135
- const skillDir = join(SKILLS_DIR, 'clauth');
136
- if (existsSync(skillDir)) {
137
- try {
138
- rmSync(skillDir, { recursive: true, force: true });
139
- s4.succeed(`Skill removed: ${skillDir}`);
140
- } catch (e) {
141
- s4.warn(`Could not remove skill: ${e.message}`);
142
- }
143
- } else {
144
- s4.succeed('Skill directory not found (already removed)');
145
- }
146
-
147
- // ── Step 5: Clear local config ─────────────
148
- const s5 = ora('Clearing local config...').start();
149
- try {
150
- config.clear();
151
- s5.succeed('Local config cleared');
152
- } catch (e) {
153
- s5.warn(`Could not clear config: ${e.message}`);
154
- }
155
-
156
- // ── Done ───────────────────────────────────
157
- console.log('');
158
- console.log(chalk.red('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
159
- console.log(chalk.yellow(' ✓ clauth fully uninstalled'));
160
- console.log(chalk.red('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
161
- console.log('');
162
- console.log(chalk.gray(' To reinstall: npx @lifeaitools/clauth install'));
163
- console.log('');
164
- }
1
+ // cli/commands/uninstall.js
2
+ // clauth uninstall — full teardown: DB objects, Edge Function, secrets, skill, local config
3
+ //
4
+ // Reverses everything `clauth install` does:
5
+ // 1. Drops clauth tables, policies, triggers, functions from Supabase
6
+ // 2. Deletes auth-vault Edge Function
7
+ // 3. Removes CLAUTH_* secrets
8
+ // 4. Removes Claude skill directory
9
+ // 5. Clears local config (Conf store)
10
+
11
+ import { existsSync, rmSync } from 'fs';
12
+ import { join } from 'path';
13
+ import Conf from 'conf';
14
+ import chalk from 'chalk';
15
+ import ora from 'ora';
16
+
17
+ const MGMT = 'https://api.supabase.com/v1';
18
+ const SKILLS_DIR = process.env.CLAUTH_SKILLS_DIR ||
19
+ (process.platform === 'win32'
20
+ ? join(process.env.USERPROFILE || '', '.claude', 'skills')
21
+ : join(process.env.HOME || '', '.claude', 'skills'));
22
+
23
+ // ─────────────────────────────────────────────
24
+ // Supabase Management API helper
25
+ // ─────────────────────────────────────────────
26
+ async function mgmt(pat, method, path, body) {
27
+ const res = await fetch(`${MGMT}${path}`, {
28
+ method,
29
+ headers: { 'Authorization': `Bearer ${pat}`, 'Content-Type': 'application/json' },
30
+ body: body ? JSON.stringify(body) : undefined,
31
+ });
32
+ if (!res.ok) {
33
+ const text = await res.text().catch(() => res.statusText);
34
+ throw new Error(`${method} ${path} → HTTP ${res.status}: ${text}`);
35
+ }
36
+ if (res.status === 204) return {};
37
+ const text = await res.text();
38
+ if (!text) return {};
39
+ return JSON.parse(text);
40
+ }
41
+
42
+ // ─────────────────────────────────────────────
43
+ // Main uninstall command
44
+ // ─────────────────────────────────────────────
45
+ export async function runUninstall(opts = {}) {
46
+ console.log(chalk.red('\n🗑️ clauth uninstall\n'));
47
+
48
+ const config = new Conf({ projectName: 'clauth' });
49
+
50
+ // ── Collect credentials ────────────────────
51
+ const ref = opts.ref || config.get('supabase_url')?.match(/https:\/\/(.+)\.supabase\.co/)?.[1];
52
+ const pat = opts.pat;
53
+
54
+ if (!ref) {
55
+ console.log(chalk.red(' Cannot determine Supabase project ref.'));
56
+ console.log(chalk.gray(' Use: clauth uninstall --ref <project-ref> --pat <personal-access-token>'));
57
+ process.exit(1);
58
+ }
59
+ if (!pat) {
60
+ console.log(chalk.red(' Supabase PAT required for teardown.'));
61
+ console.log(chalk.gray(' Use: clauth uninstall --ref <project-ref> --pat <personal-access-token>'));
62
+ process.exit(1);
63
+ }
64
+
65
+ console.log(chalk.gray(` Project: ${ref}\n`));
66
+
67
+ // ── Step 1: Drop database objects ──────────
68
+ const s1 = ora('Dropping clauth database objects...').start();
69
+ const teardownSQL = `
70
+ -- Drop triggers
71
+ DROP TRIGGER IF EXISTS clauth_services_updated ON public.clauth_services;
72
+
73
+ -- Drop tables (CASCADE drops policies automatically)
74
+ DROP TABLE IF EXISTS public.clauth_audit CASCADE;
75
+ DROP TABLE IF EXISTS public.clauth_machines CASCADE;
76
+ DROP TABLE IF EXISTS public.clauth_services CASCADE;
77
+
78
+ -- Drop functions
79
+ DROP FUNCTION IF EXISTS public.clauth_touch_updated() CASCADE;
80
+ DROP FUNCTION IF EXISTS public.clauth_upsert_vault_secret(text, text) CASCADE;
81
+ DROP FUNCTION IF EXISTS public.clauth_get_vault_secret(text) CASCADE;
82
+ DROP FUNCTION IF EXISTS public.clauth_delete_vault_secret(text) CASCADE;
83
+ `;
84
+
85
+ try {
86
+ await mgmt(pat, 'POST', `/projects/${ref}/database/query`, { query: teardownSQL });
87
+ s1.succeed('Database objects dropped (tables, triggers, functions, policies)');
88
+ } catch (e) {
89
+ s1.fail(`Database teardown failed: ${e.message}`);
90
+ console.log(chalk.yellow(' You may need to drop objects manually via SQL editor.'));
91
+ }
92
+
93
+ // ── Step 2: Delete Edge Function ───────────
94
+ const s2 = ora('Deleting auth-vault Edge Function...').start();
95
+ try {
96
+ const res = await fetch(`${MGMT}/projects/${ref}/functions/auth-vault`, {
97
+ method: 'DELETE',
98
+ headers: { 'Authorization': `Bearer ${pat}` },
99
+ });
100
+ if (res.ok || res.status === 404) {
101
+ s2.succeed(res.status === 404
102
+ ? 'Edge Function not found (already deleted)'
103
+ : 'Edge Function deleted');
104
+ } else {
105
+ const text = await res.text().catch(() => res.statusText);
106
+ throw new Error(`HTTP ${res.status}: ${text}`);
107
+ }
108
+ } catch (e) {
109
+ s2.fail(`Edge Function delete failed: ${e.message}`);
110
+ console.log(chalk.yellow(' Delete manually: Supabase Dashboard → Edge Functions → auth-vault → Delete'));
111
+ }
112
+
113
+ // ── Step 3: Remove secrets ─────────────────
114
+ const s3 = ora('Removing clauth secrets...').start();
115
+ try {
116
+ // Supabase Management API: DELETE /projects/{ref}/secrets with body listing secret names
117
+ const res = await fetch(`${MGMT}/projects/${ref}/secrets`, {
118
+ method: 'DELETE',
119
+ headers: { 'Authorization': `Bearer ${pat}`, 'Content-Type': 'application/json' },
120
+ body: JSON.stringify(['CLAUTH_HMAC_SALT', 'CLAUTH_ADMIN_BOOTSTRAP_TOKEN']),
121
+ });
122
+ if (res.ok) {
123
+ s3.succeed('Secrets removed (CLAUTH_HMAC_SALT, CLAUTH_ADMIN_BOOTSTRAP_TOKEN)');
124
+ } else {
125
+ const text = await res.text().catch(() => res.statusText);
126
+ throw new Error(`HTTP ${res.status}: ${text}`);
127
+ }
128
+ } catch (e) {
129
+ s3.warn(`Secret removal failed: ${e.message}`);
130
+ console.log(chalk.yellow(' Remove manually: Supabase → Settings → Edge Functions → Secrets'));
131
+ }
132
+
133
+ // ── Step 4: Remove Claude skill ────────────
134
+ const s4 = ora('Removing Claude skill...').start();
135
+ const skillDir = join(SKILLS_DIR, 'clauth');
136
+ if (existsSync(skillDir)) {
137
+ try {
138
+ rmSync(skillDir, { recursive: true, force: true });
139
+ s4.succeed(`Skill removed: ${skillDir}`);
140
+ } catch (e) {
141
+ s4.warn(`Could not remove skill: ${e.message}`);
142
+ }
143
+ } else {
144
+ s4.succeed('Skill directory not found (already removed)');
145
+ }
146
+
147
+ // ── Step 5: Clear local config ─────────────
148
+ const s5 = ora('Clearing local config...').start();
149
+ try {
150
+ config.clear();
151
+ s5.succeed('Local config cleared');
152
+ } catch (e) {
153
+ s5.warn(`Could not clear config: ${e.message}`);
154
+ }
155
+
156
+ // ── Done ───────────────────────────────────
157
+ console.log('');
158
+ console.log(chalk.red('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
159
+ console.log(chalk.yellow(' ✓ clauth fully uninstalled'));
160
+ console.log(chalk.red('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
161
+ console.log('');
162
+ console.log(chalk.gray(' To reinstall: npx @lifeaitools/clauth install'));
163
+ console.log('');
164
+ }
@@ -131,7 +131,7 @@ export async function runWatchdog(action, opts = {}) {
131
131
  console.log("Usage: clauth watchdog restart <service-id>");
132
132
  return;
133
133
  }
134
- const result = await restartWatchdogService(serviceId);
134
+ const result = restartWatchdogService(serviceId);
135
135
  console.log(JSON.stringify(result, null, 2));
136
136
  return;
137
137
  }
package/cli/index.js CHANGED
@@ -150,10 +150,37 @@ import { runInstall } from './commands/install.js';
150
150
  import { runUninstall } from './commands/uninstall.js';
151
151
  import { runScrub } from './commands/scrub.js';
152
152
  import { runServe } from './commands/serve.js';
153
- import { runOps } from './commands/ops.js';
154
- import { runOpsInstall } from './commands/ops-install.js';
155
153
  import { runCodevelop } from './commands/codevelop.js';
156
154
  import { runNpm, runPublish } from './commands/npm.js';
155
+ import { runLogin } from './commands/login.js';
156
+
157
+ // ──────────────────────────────────────────────
158
+ // clauth login <target>
159
+ // THE global login directive — the one sanctioned way to SSH into any
160
+ // clauth-managed box. Never hand-fetch a private key (clauth get <ssh-service>)
161
+ // and hand-roll ssh-agent/ssh yourself; add the box to LOGIN_TARGETS in
162
+ // cli/commands/login.js instead and use this command.
163
+ // ──────────────────────────────────────────────
164
+ program
165
+ .command('login [target]')
166
+ .description('SSH into a clauth-managed box by name (e.g. vultr) — run with no target to list them')
167
+ .option('-c, --command <cmd>', 'Run one command instead of opening an interactive shell')
168
+ .option('--host <user@host>', 'Override the target\'s default host')
169
+ .option('--native-windows', 'Human-only: use native Windows OpenSSH instead of Git Bash (blocked for agent sessions)')
170
+ .addHelpText('after', `
171
+ Examples:
172
+ clauth login List known login targets
173
+ clauth login vultr Interactive shell on the Vultr dev box
174
+ clauth login vultr -c "pm2 list" Run one command, non-interactive
175
+ clauth login vultr --host root@1.2.3.4 Override the default host for this target
176
+ `)
177
+ .action(async (target, opts) => {
178
+ await runLogin(target, {
179
+ command: opts.command,
180
+ host: opts.host,
181
+ nativeWindows: opts.nativeWindows,
182
+ });
183
+ });
157
184
 
158
185
  program
159
186
  .command('install')
@@ -1052,22 +1079,4 @@ Examples:
1052
1079
  await runServe({ ...opts, action: resolvedAction });
1053
1080
  });
1054
1081
 
1055
- program
1056
- .command("ops <action>")
1057
- .description("Call the bearer-authenticated PM2 and Coolify operations control plane")
1058
- .option("--endpoint <url>", "HTTPS control-plane endpoint (or CLAUTH_OPS_ENDPOINT)")
1059
- .option("--target <name>", "PM2 process name or id")
1060
- .option("--script <path>", "PM2 script path for start")
1061
- .option("--instances <n>", "PM2 scale target")
1062
- .option("--operation <name>", "PM2 operation for run")
1063
- .option("--args-json <json>", "JSON positional arguments for a raw pm2_* operation")
1064
- .option("--options-json <json>", "JSON PM2 options for a typed operation")
1065
- .option("--application <uuid>", "registered Coolify application UUID for promote")
1066
- .option("--ref <name>", "registered Git ref for deploy")
1067
- .option("--job <id>", "job id for status lookup")
1068
- .option("--config <path>", "server-side JSON policy for ops install")
1069
- .option("--dry-run", "validate and print ops install configuration without changing PM2")
1070
- .addHelpText("after", `\nActions: catalog | list | describe | run | deploy | promote | job | install\n\nInstall: clauth ops install --config /etc/clauth/ops-control-plane.json\nThe installer creates or updates a PM2-managed local control plane, then proves /health and the bearer gate without reading a token.\n\nThe bearer is retrieved only from local clauth service vultr-ops-api-token and is never printed.\n`)
1071
- .action(async (action, opts) => { if (action === "install") await runOpsInstall(opts); else await runOps(action, opts); });
1072
-
1073
1082
  program.parse(process.argv);
@@ -7,12 +7,7 @@ import { spawnSync } from "node:child_process";
7
7
  const SCHEMA = "lifeai.plugin.v1";
8
8
  const DEFAULT_TIMEOUT_MS = 3000;
9
9
  const DEFAULT_SUPERVISOR_PORT = 52439;
10
- // local/clauth/daemon: for a surface that is not a separately-startable
11
- // process at all, but literally embedded in the clauth daemon itself (e.g.
12
- // fs-mcp) — distinct from local/clauth/pm2 (a separate local process clauth
13
- // manages via pm2) because there is nothing for a surface action to
14
- // start/stop/restart; the daemon's own lifecycle IS the surface's lifecycle.
15
- const DESTINATIONS = new Set(["local/clauth/pm2", "local/clauth/daemon", "vultr/clauth/pm2", "coolify/clauth/docker"]);
10
+ const DESTINATIONS = new Set(["local/clauth/pm2", "vultr/clauth/pm2", "coolify/clauth/docker"]);
16
11
  const OWNERS = new Set(["clauth", "plugin", "external"]);
17
12
  const ACTIONS = new Set(["start", "stop", "restart", "reconcile", "test", "promote", "rollback"]);
18
13
  const DEFAULT_HEALTH_RECONCILE_INTERVAL_MS = 10000;
@@ -178,30 +178,7 @@ export function readWatchdogEvents(limit = 100) {
178
178
  }
179
179
  }
180
180
 
181
- async function verifyRestartHealth(service) {
182
- if (!service.health?.url) return { ok: true, health_status: "not_configured" };
183
-
184
- const attempts = Number(service.health.readyAttempts || 20);
185
- const delayMs = Number(service.health.readyDelayMs || 250);
186
- let observed;
187
- for (let attempt = 1; attempt <= attempts; attempt += 1) {
188
- observed = await evaluateWatchdogService(service);
189
- if (observed.status === "healthy") {
190
- return { ok: true, health_status: observed.status, health_http_status: observed.httpStatus, attempts: attempt };
191
- }
192
- if (attempt < attempts) await new Promise((resolve) => setTimeout(resolve, delayMs));
193
- }
194
- return {
195
- ok: false,
196
- error: "restart_health_unreachable",
197
- health_status: observed?.status || "unknown",
198
- health_http_status: observed?.httpStatus,
199
- health_error: observed?.error,
200
- attempts,
201
- };
202
- }
203
-
204
- export async function restartWatchdogService(id) {
181
+ export function restartWatchdogService(id) {
205
182
  const service = loadRegistry().services.find((candidate) => candidate.id === id);
206
183
  if (!service) return { ok: false, error: "service_not_registered" };
207
184
  if (!service.restart) return { ok: false, error: "restart_not_configured" };
@@ -215,23 +192,18 @@ export async function restartWatchdogService(id) {
215
192
  encoding: "utf8",
216
193
  timeout: Number(service.restart.timeoutMs || 30000),
217
194
  });
218
- const health = result.status === 0 ? await verifyRestartHealth(service) : { ok: false, health_status: "not_checked" };
219
195
  const event = {
220
196
  kind: "restart",
221
197
  service_id: id,
222
198
  status: result.status,
223
- health_status: health.health_status,
224
- health_http_status: health.health_http_status,
225
- health_error: health.health_error,
226
199
  error: result.error ? result.error.message : undefined,
227
200
  };
228
201
  appendEvent(event);
229
202
  return {
230
- ok: result.status === 0 && health.ok,
203
+ ok: result.status === 0,
231
204
  status: result.status,
232
205
  stdout: result.stdout,
233
206
  stderr: result.stderr,
234
207
  error: result.error ? result.error.message : undefined,
235
- ...health,
236
208
  };
237
209
  }
@@ -13,12 +13,12 @@ import {
13
13
  validateWatchdogService,
14
14
  } from "./watchdog-registry.js";
15
15
 
16
- async function withTempRegistry(fn) {
16
+ function withTempRegistry(fn) {
17
17
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-watchdog-"));
18
18
  const old = process.env.CLAUTH_WATCHDOG_DIR;
19
19
  process.env.CLAUTH_WATCHDOG_DIR = dir;
20
20
  try {
21
- return await fn(dir);
21
+ return fn(dir);
22
22
  } finally {
23
23
  if (old === undefined) delete process.env.CLAUTH_WATCHDOG_DIR;
24
24
  else process.env.CLAUTH_WATCHDOG_DIR = old;
@@ -78,35 +78,12 @@ test("registerWatchdogManifest upserts services by id", () => withTempRegistry((
78
78
  assert.equal(registry.services.find((service) => service.id === "codeflow").label, "CodeFlow Updated");
79
79
  }));
80
80
 
81
- test("restartWatchdogService rejects missing and unapproved services", async () => withTempRegistry(async () => {
82
- assert.deepEqual(await restartWatchdogService("missing"), { ok: false, error: "service_not_registered" });
81
+ test("restartWatchdogService rejects missing and unapproved services", () => withTempRegistry(() => {
82
+ assert.deepEqual(restartWatchdogService("missing"), { ok: false, error: "service_not_registered" });
83
83
  registerWatchdogManifest({
84
84
  services: [
85
85
  { id: "dev-center", label: "Dev Center", kind: "process", restart: { cmd: "node", args: ["--version"] } },
86
86
  ],
87
87
  });
88
- assert.deepEqual(await restartWatchdogService("dev-center"), { ok: false, error: "approval_required" });
89
- }));
90
-
91
- test("restartWatchdogService requires registered health after launching", async () => withTempRegistry(async () => {
92
- registerWatchdogManifest({
93
- services: [{
94
- id: "health-gated",
95
- label: "Health gated",
96
- kind: "http",
97
- health: { url: "http://127.0.0.1:3109/health", readyAttempts: 2, readyDelayMs: 0 },
98
- restart: { cmd: process.execPath, args: ["--version"] },
99
- approvalRequired: false,
100
- }],
101
- });
102
- const originalFetch = globalThis.fetch;
103
- globalThis.fetch = async () => ({ ok: false, status: 503 });
104
- try {
105
- const result = await restartWatchdogService("health-gated");
106
- assert.equal(result.ok, false);
107
- assert.equal(result.error, "restart_health_unreachable");
108
- assert.equal(result.health_status, "degraded");
109
- } finally {
110
- globalThis.fetch = originalFetch;
111
- }
88
+ assert.deepEqual(restartWatchdogService("dev-center"), { ok: false, error: "approval_required" });
112
89
  }));