agentglow 0.2.7 → 0.2.9

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/cli/agentglow.mjs CHANGED
@@ -7,7 +7,7 @@ import { spawn } from "node:child_process";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { fetchText, probe } from "./lib/net.mjs";
9
9
  import {
10
- baseUrl, claudeSettings, installSettingsFile, isOurHook, readState, startHookCommand, uninstallSettingsFile,
10
+ baseUrl, claudeSettings, installSettingsFile, isOurHook, pluginIdsIn, readState, startHookCommand, uninstallSettingsFile,
11
11
  } from "./lib/settings.mjs";
12
12
  import {
13
13
  clearLock, installCliCopy, localBase, readLock, readPid, removeCliCopies, serveForeground, spawnStarter,
@@ -141,16 +141,23 @@ async function cmdSetup(o) {
141
141
  const remote = remoteUrl();
142
142
  const port = o.port;
143
143
  const base = remote || baseUrl({ port });
144
+ // The AgentGlow Claude Code plugin brings the same hooks + server start; adding ours too would post every event twice.
145
+ const plugin = pluginIdsIn(file);
146
+ if (plugin.length) {
147
+ console.log(`The AgentGlow Claude Code plugin is enabled (${plugin.join(", ")}): it already provides the hooks, so setup`);
148
+ console.log(`skips them and only adds the traces env (token counts). Use one or the other: plugin, or setup's hooks.`);
149
+ }
144
150
  let startCommand = null;
145
- if (!remote) {
151
+ if (!remote && !plugin.length) {
146
152
  const script = installCliCopy({ srcCliDir: here, version: VERSION });
147
153
  startCommand = startHookCommand({
148
154
  port, version: VERSION, node: nodeForHook(), script, cacheDir: process.env.AGENTGLOW_CACHE_DIR,
149
155
  });
150
156
  }
151
- const r = installSettingsFile(file, base, { startCommand, extra: remote ? {} : { port } });
152
- if (!r.changed) console.log(`AgentGlow hooks are already in ${file} (${base}).`);
153
- else console.log(`Added AgentGlow hooks + traces env to ${file}${r.backup ? ` (backup: ${r.backup})` : ""}.`);
157
+ const r = installSettingsFile(file, base, { startCommand, hooks: !plugin.length, extra: remote ? {} : { port } });
158
+ const what = plugin.length ? "traces env" : "hooks + traces env";
159
+ if (!r.changed) console.log(`AgentGlow ${what} already in ${file} (${base}).`);
160
+ else console.log(`Added AgentGlow ${what} to ${file}${r.backup ? ` (backup: ${r.backup})` : ""}.`);
154
161
  if (r.skipped.length) console.log(`Left your own values for: ${r.skipped.join(", ")}`);
155
162
  if (process.env.AGENTGLOW_API_KEY) console.log('For traces with an ingest key also export OTEL_EXPORTER_OTLP_HEADERS="x-api-key=$AGENTGLOW_API_KEY".');
156
163
 
@@ -317,7 +324,12 @@ async function cmdStatus(o) {
317
324
  console.log(`agentglow CLI ${VERSION}`);
318
325
  console.log(`server: ${health?.ok ? `healthy at ${base} (server ${health.version || "?"})` : starting ? `starting on port ${port}` : `not running at ${base}`}`);
319
326
  if (!remote) console.log(`port: ${port}${pid ? ` pid ${pid} (started by this CLI)` : ""}`);
320
- console.log(`hooks: ${installed ? `installed in ${file}${startHook ? " (server auto-starts with claude)" : ""}` : "not installed (run: npx agentglow setup)"}`);
327
+ const plugin = pluginIdsIn(file);
328
+ const hooksLine = installed ? `installed in ${file}${startHook ? " (server auto-starts with claude)" : ""}`
329
+ : plugin.length ? `from the Claude Code plugin ${plugin.join(", ")} (server auto-starts with claude)`
330
+ : "not installed (run: npx agentglow setup, or install the Claude Code plugin)";
331
+ console.log(`hooks: ${hooksLine}`);
332
+ if (installed && plugin.length) console.log(`warning: hooks are in ${file} AND the plugin is enabled, so events post twice. Run \`npx agentglow remove\` or disable the plugin.`);
321
333
  if (hasAutostart()) console.log(`login: server starts at login and restarts on crash`);
322
334
  if (health?.ok) console.log(`view: ${base}/neural`);
323
335
  return 0;
@@ -16,6 +16,7 @@ export const HOOK_EVENTS = [
16
16
  ["SubagentStart", true],
17
17
  ["SubagentStop", true],
18
18
  ["Stop", false],
19
+ ["StopFailure", false],
19
20
  ["SessionEnd", false],
20
21
  ];
21
22
 
@@ -144,14 +145,16 @@ export function unmergeSettings(settings, state = null) {
144
145
  * `startCommand` adds the SessionStart command hook that starts the local server (see startHookCommand).
145
146
  * Returns { settings, state: { addedEnv, base, startHook }, skipped }.
146
147
  */
147
- export function mergeSettings(settings, base, state = null, { startCommand = null } = {}) {
148
+ export function mergeSettings(settings, base, state = null, { startCommand = null, hooks = true } = {}) {
148
149
  const s = unmergeSettings(settings, state);
149
150
  const ours = claudeSettings(base);
150
- if (startCommand) ours.hooks.SessionStart.unshift(startHookGroup(startCommand));
151
+ if (!hooks) { ours.hooks = {}; startCommand = null; } // the AgentGlow plugin already provides them
152
+ else if (startCommand) ours.hooks.SessionStart.unshift(startHookGroup(startCommand));
151
153
  s.hooks = s.hooks && typeof s.hooks === "object" ? s.hooks : {};
152
154
  for (const [event, groups] of Object.entries(ours.hooks)) {
153
155
  s.hooks[event] = [...(Array.isArray(s.hooks[event]) ? s.hooks[event] : []), ...groups];
154
156
  }
157
+ if (!Object.keys(s.hooks).length) delete s.hooks;
155
158
  s.env = s.env && typeof s.env === "object" ? s.env : {};
156
159
  const addedEnv = [];
157
160
  const skipped = [];
@@ -163,6 +166,13 @@ export function mergeSettings(settings, base, state = null, { startCommand = nul
163
166
  return { settings: s, state: { base, addedEnv, startHook: !!startCommand }, skipped };
164
167
  }
165
168
 
169
+ /** Plugin ids like "agentglow@agentglow" that `enabledPlugins` turns on (the Claude Code plugin brings its own hooks). */
170
+ export function enabledAgentglowPlugins(settings) {
171
+ return Object.entries(settings?.enabledPlugins || {})
172
+ .filter(([id, on]) => on === true && id.split("@")[0] === "agentglow")
173
+ .map(([id]) => id);
174
+ }
175
+
166
176
  // ---------- files ----------
167
177
 
168
178
  export function statePath(settingsFile) {
@@ -189,10 +199,10 @@ const same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
189
199
  const sorted = (o) => Object.fromEntries(Object.entries(o || {}).sort(([a], [b]) => a.localeCompare(b)));
190
200
 
191
201
  /** Merge into a settings file. Returns { changed, backup, skipped, file }. */
192
- export function installSettingsFile(file, base, { now = Date.now(), startCommand = null, extra = {} } = {}) {
202
+ export function installSettingsFile(file, base, { now = Date.now(), startCommand = null, hooks = true, extra = {} } = {}) {
193
203
  const before = readJson(file, {});
194
204
  const st = readJson(statePath(file), null);
195
- const merged = mergeSettings(before, base, st, { startCommand });
205
+ const merged = mergeSettings(before, base, st, { startCommand, hooks });
196
206
  const { settings, skipped } = merged;
197
207
  const state = { ...merged.state, ...extra };
198
208
  // keep the first backup we made, so `remove` can point at the pre-AgentGlow settings
@@ -208,6 +218,11 @@ export function installSettingsFile(file, base, { now = Date.now(), startCommand
208
218
  return { changed: true, backup: bak, skipped, file };
209
219
  }
210
220
 
221
+ /** enabledAgentglowPlugins() of a settings file ([] when it is missing or unreadable). */
222
+ export function pluginIdsIn(file) {
223
+ try { return enabledAgentglowPlugins(readJson(file, {})); } catch { return []; }
224
+ }
225
+
211
226
  /** Read the install sidecar (null when AgentGlow is not installed in `file`). */
212
227
  export function readState(file) {
213
228
  return readJson(statePath(file), null);