@oddessentials/agent-guild 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -36,6 +36,12 @@ coding tool, and see at a glance which sessions and agents are working.
36
36
  not survive a computer restart. **Stop manager** in the top bar stops the
37
37
  manager and ends every session; it asks first while any session is still
38
38
  running.
39
+ * **Stay current.** When a newer Agent Guild is on npm, an **Upgrade**
40
+ button appears in the top bar and runs `npm install -g` in a session you
41
+ can watch. Sessions keep running; stop the manager and run `agent-guild
42
+ open` to use the new version.
43
+ * **Light or dark.** The page follows your system theme and the top-bar
44
+ toggle switches it. The guild artwork is the dark theme.
39
45
 
40
46
  ## Requirements
41
47
 
@@ -95,6 +101,10 @@ one platform under a `win32` or `darwin` key. See
95
101
  | `modelPattern` | Regular expression that finds the model name on the tool's screen when the tool does not report it. |
96
102
  | `resumeArgs` | Arguments that resume the tool's own session, with `{id}` standing for the id, e.g. `["--resume", "{id}"]`. Without it the card has no **Existing** button. |
97
103
  | `env` | Extra environment variables for the tool. |
104
+ | `accounts` | Further sign-ins of the tool, each in its own home folder, e.g. `[{ "id": "work", "label": "Work", "dir": "~/.claude-work" }]`. Without `dir`, the folder is `accounts/<provider>/<account>` in the data folder. The card shows one chip per account with its own meters, and a session starts under the chip picked; the tool signs in from inside the first session, and its reporting hooks are copied into the folder on first use. An entry with id `default` renames the tool's own sign-in. Needs `homeVar`. |
105
+ | `homeVar` | The environment variable that moves the tool's home folder, e.g. `CLAUDE_CONFIG_DIR`. Set for Claude Code, Codex CLI, Gemini CLI and Grok Build by default. |
106
+ | `accountEnv` | Further variables set for every account other than the default, with `{dir}` standing for the account's folder. By default Claude Code's secure-storage folder follows the account, and Gemini CLI keeps the account's sign-in in a file rather than the shared OS keychain. |
107
+ | `hooks` | `{ "path", "example" }`: the hooks file inside the home folder and the file in `examples/` copied there for a new account. |
98
108
  | `color`, `monogram`, `icon` | Icon appearance. `icon` is a URL path; you can also drop `<id>.svg` into `web/icons/`. |
99
109
  | `install`, `docs` | Help shown when the tool is not installed. |
100
110
  | `usageUrl`, `billingUrl` | `https://` links to the vendor's usage and billing pages, shown on the card. The defaults point at the subscription pages; set your API console instead, or `null` to hide a link. Google's usage link opens AI Studio, which counts API-key usage only, not the Gemini CLI sign-in quota the card's meters show. |
@@ -102,10 +112,11 @@ one platform under a `win32` or `darwin` key. See
102
112
  The page's "Working folder" field sets where new sessions start. It defaults
103
113
  to your home folder.
104
114
 
105
- Version checks ask the registry from npm's global configuration (the one
106
- `npm install -g` uses) about once an hour, and installs use the same
107
- registry. Set `AGENT_GUILD_NPM_REGISTRY` to override it for both, or
108
- `AGENT_GUILD_NO_UPDATE_CHECK=1` to skip the checks.
115
+ Version checks, for the tools and for Agent Guild itself, ask the registry
116
+ from npm's global configuration (the one `npm install -g` uses) about once
117
+ an hour, and installs use the same registry. Set `AGENT_GUILD_NPM_REGISTRY`
118
+ to override it for both, or `AGENT_GUILD_NO_UPDATE_CHECK=1` to skip the
119
+ checks.
109
120
 
110
121
  ## Show agents and models
111
122
 
@@ -160,6 +171,8 @@ coding tool in `tests/fixtures`. CI runs them on Windows, macOS and Linux.
160
171
  ## Current limits
161
172
 
162
173
  * Sessions end when the manager stops or the computer restarts.
163
- * Gemini CLI usage meters need its sign-in in the OS keychain (macOS, or
164
- Linux with `secret-tool`) or in the older `oauth_creds.json`; Gemini CLI's
165
- encrypted-file storage cannot be read.
174
+ * Gemini CLI usage meters read its sign-in where Gemini CLI keeps it:
175
+ `oauth_creds.json`, or with `GEMINI_FORCE_ENCRYPTED_FILE_STORAGE=true`
176
+ the OS keychain (macOS, or Linux with `secret-tool`) or its encrypted
177
+ credentials file. A sign-in kept in the Windows Credential Manager
178
+ cannot be read.
@@ -8,6 +8,9 @@
8
8
  "package": "@anthropic-ai/claude-code",
9
9
  "versionArgs": ["--version"],
10
10
  "usage": "claude",
11
+ "homeVar": "CLAUDE_CONFIG_DIR",
12
+ "accountEnv": { "CLAUDE_SECURESTORAGE_CONFIG_DIR": "{dir}" },
13
+ "hooks": { "path": "settings.json", "example": "claude-code-settings.json" },
11
14
  "modelPattern": "claude-(?:opus|sonnet|haiku|fable|\\d)[a-z0-9.-]*|\\b(?:opus|sonnet|haiku|fable)\\s?\\d+(?:\\.\\d+)?",
12
15
  "args": [],
13
16
  "resumeArgs": ["--resume", "{id}"],
@@ -41,6 +44,8 @@
41
44
  "package": "@openai/codex",
42
45
  "versionArgs": ["--version"],
43
46
  "usage": "codex",
47
+ "homeVar": "CODEX_HOME",
48
+ "hooks": { "path": "hooks.json", "example": "codex-hooks.json" },
44
49
  "modelPattern": "\\bgpt-\\d[a-z0-9.-]*",
45
50
  "args": [],
46
51
  "resumeArgs": ["resume", "{id}"],
@@ -72,6 +77,9 @@
72
77
  "package": "@google/gemini-cli",
73
78
  "versionArgs": ["--version"],
74
79
  "usage": "gemini",
80
+ "homeVar": "GEMINI_CLI_HOME",
81
+ "accountEnv": { "GEMINI_FORCE_FILE_STORAGE": "true" },
82
+ "hooks": { "path": ".gemini/settings.json", "example": "gemini-settings.json" },
75
83
  "modelPattern": "\\bgemini-\\d[a-z0-9.-]*",
76
84
  "args": [],
77
85
  "resumeArgs": ["--resume", "{id}"],
@@ -92,6 +100,8 @@
92
100
  "command": "grok",
93
101
  "package": "@xai-official/grok",
94
102
  "versionArgs": ["--version"],
103
+ "homeVar": "GROK_HOME",
104
+ "hooks": { "path": "hooks/agent-guild.json", "example": "grok-hooks.json" },
95
105
  "modelPattern": "\\bgrok-(?:build|\\d)[a-z0-9.-]*",
96
106
  "args": [],
97
107
  "resumeArgs": ["--resume", "{id}"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oddessentials/agent-guild",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Launch and watch AI coding-assistant terminal sessions from one local web page. A bundled session manager owns the terminals so the page can close and reconnect.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -8,10 +8,15 @@ import fs from 'node:fs';
8
8
  import os from 'node:os';
9
9
  import path from 'node:path';
10
10
  import crypto from 'node:crypto';
11
+ import { fileURLToPath } from 'node:url';
11
12
 
12
13
  export const DEFAULT_PORT = 47821;
13
14
  export const DEFAULT_HOST = '127.0.0.1';
14
- export const VERSION = JSON.parse(fs.readFileSync(new URL('../../package.json', import.meta.url), 'utf8')).version;
15
+ /** The package.json the manager runs from; an upgrade replaces it in place. */
16
+ export const PACKAGE_FILE = fileURLToPath(new URL('../../package.json', import.meta.url));
17
+ const packageJson = JSON.parse(fs.readFileSync(PACKAGE_FILE, 'utf8'));
18
+ export const VERSION = packageJson.version;
19
+ export const PACKAGE_NAME = packageJson.name;
15
20
 
16
21
  /** Per-user data directory. Override with AGENT_GUILD_HOME (used by tests). */
17
22
  export function dataDir() {
@@ -37,6 +42,7 @@ export const paths = {
37
42
  get token() { return path.join(dataDir(), 'auth-token'); },
38
43
  get runtime() { return path.join(dataDir(), 'manager.json'); },
39
44
  get providers() { return path.join(dataDir(), 'providers.json'); },
45
+ get accounts() { return path.join(dataDir(), 'accounts'); },
40
46
  get log() { return path.join(dataDir(), 'manager.log'); },
41
47
  /** Launchers for agent-guild-report, put first on every session's PATH. */
42
48
  get shims() { return path.join(dataDir(), 'bin'); },
@@ -8,11 +8,15 @@ import { ProviderRegistry } from './providers.mjs';
8
8
  import { SessionManager } from './session-manager.mjs';
9
9
  import { UsageMonitor } from './usage.mjs';
10
10
  import { ModelStats } from './model-stats.mjs';
11
+ import { NewsFeed } from './news.mjs';
11
12
  import { createManagerServer } from './server.mjs';
13
+ import { SelfUpdate } from './self-update.mjs';
12
14
  import { resolveBaseEnv, pathReader } from './shell-env.mjs';
13
15
  import { writeReportShims } from './report-shims.mjs';
14
16
  import {
15
17
  DEFAULT_HOST,
18
+ PACKAGE_FILE,
19
+ PACKAGE_NAME,
16
20
  VERSION,
17
21
  ensureDataDir,
18
22
  loadOrCreateToken,
@@ -27,7 +31,8 @@ const rootDir = path.resolve(here, '../..');
27
31
 
28
32
  const VERSION_REFRESH_MS = 60 * 60 * 1000;
29
33
 
30
- export async function startManager({ port = resolvePort(), host = DEFAULT_HOST, sessionDefaults } = {}) {
34
+ /** `version` and `packageFile` stand in for the real ones in tests. */
35
+ export async function startManager({ port = resolvePort(), host = DEFAULT_HOST, sessionDefaults, version = VERSION, packageFile = PACKAGE_FILE } = {}) {
31
36
  ensureDataDir();
32
37
  const token = loadOrCreateToken();
33
38
  const baseEnv = resolveBaseEnv();
@@ -51,12 +56,18 @@ export async function startManager({ port = resolvePort(), host = DEFAULT_HOST,
51
56
  });
52
57
 
53
58
  let api;
54
- const manager = new SessionManager({ registry, baseEnv, getApiUrl: () => api.url, sessionDefaults, shimDir });
59
+ const selfUpdate = new SelfUpdate({ pkg: PACKAGE_NAME, version, packageFile, registry });
60
+ const manager = new SessionManager({ registry, baseEnv, getApiUrl: () => api.url, sessionDefaults, shimDir, selfUpdate });
55
61
  const usage = new UsageMonitor({ registry, env: baseEnv });
56
62
  const modelStats = new ModelStats({ registry });
63
+ const news = new NewsFeed({ registry });
57
64
  let closing = null;
58
65
 
59
- const versionTimer = setInterval(() => registry.refreshVersions().catch(() => {}), VERSION_REFRESH_MS);
66
+ const refreshVersions = () => {
67
+ registry.refreshVersions().catch(() => {});
68
+ selfUpdate.refresh().catch(() => {});
69
+ };
70
+ const versionTimer = setInterval(refreshVersions, VERSION_REFRESH_MS);
60
71
  versionTimer.unref();
61
72
 
62
73
  const shutdown = (reason = 'shutdown') => {
@@ -82,11 +93,13 @@ export async function startManager({ port = resolvePort(), host = DEFAULT_HOST,
82
93
  registry,
83
94
  usage,
84
95
  modelStats,
96
+ news,
85
97
  token,
86
98
  host,
87
99
  port,
88
100
  webDir,
89
- version: VERSION,
101
+ version,
102
+ selfUpdate,
90
103
  extraOrigins,
91
104
  onShutdownRequest: () => shutdown('requested via API').then(() => process.exit(0)),
92
105
  });
@@ -106,11 +119,11 @@ export async function startManager({ port = resolvePort(), host = DEFAULT_HOST,
106
119
  host,
107
120
  port: api.port,
108
121
  url: api.url,
109
- version: VERSION,
122
+ version,
110
123
  startedAt: new Date().toISOString(),
111
124
  });
112
- console.log(`[manager] Agent Guild ${VERSION} listening on ${api.url} (pid ${process.pid})`);
113
- registry.refreshVersions().catch(() => {});
125
+ console.log(`[manager] Agent Guild ${version} listening on ${api.url} (pid ${process.pid})`);
126
+ refreshVersions();
114
127
  return { api, manager, registry, token, shutdown };
115
128
  }
116
129