arcroom 0.0.1-beta → 0.0.2-beta

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/dist/home.js CHANGED
@@ -41,6 +41,15 @@ export function loadConfig() {
41
41
  return { url: DEFAULT_URL, apiKey: '', ...(existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : {}) };
42
42
  }
43
43
  const LOCAL = new Set(['localhost', '127.0.0.1', '[::1]']);
44
+ /** Whether `url` points at this machine (a developer's local API). */
45
+ export const isLocalUrl = (url) => {
46
+ try {
47
+ return LOCAL.has(new URL(url).hostname);
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ };
44
53
  /** The API URL, refused unless it is https:// or this machine: the key and the traces never cross a network in clear text. */
45
54
  export function apiUrl(url) {
46
55
  const u = new URL(url);
@@ -54,11 +63,17 @@ export function apiUrl(url) {
54
63
  * secret to the team.
55
64
  */
56
65
  export async function api(cfg, path, body, timeoutMs = 10_000) {
57
- const res = await fetch(apiUrl(cfg.url) + path, {
66
+ const base = apiUrl(cfg.url);
67
+ const res = await fetch(base + path, {
58
68
  method: body ? 'POST' : 'GET',
59
69
  headers: { Authorization: `Bearer ${cfg.apiKey}`, 'Content-Type': 'application/json' },
60
70
  body: body ? JSON.stringify(redact(body)) : undefined,
61
71
  signal: AbortSignal.timeout(timeoutMs),
72
+ }).catch((e) => {
73
+ // a timeout stays as it is (lookup and the MCP tools check for TimeoutError); a refused or unknown host reads as one line
74
+ const cause = e.cause;
75
+ const why = cause?.code ?? cause?.errors?.[0]?.code ?? cause?.message ?? e.message;
76
+ throw e.name === 'TypeError' ? new Error(`could not reach ${base} (${why})`) : e;
62
77
  });
63
78
  const json = (await res.json());
64
79
  // the key was revoked, its person removed or its account deleted: ArcRoom stops on this machine until a new key
package/dist/index.js CHANGED
@@ -13,6 +13,12 @@ import { mcp } from './mcp.js';
13
13
  import { readStats, statusText } from './signs.js';
14
14
  import { sessionUsage, tokensOf } from './usage.js';
15
15
  const [cmd, ...args] = process.argv.slice(2);
16
+ // A command someone typed that fails prints one line, not a stack trace (the hook, daemon and MCP server keep their own handling).
17
+ if (!['hook', 'daemon', 'mcp', 'status'].includes(cmd))
18
+ process.on('uncaughtException', (e) => {
19
+ console.error(`arcroom${cmd ? ` ${cmd}` : ''}: ${e.message}`);
20
+ process.exit(1);
21
+ });
16
22
  if (cmd === 'init')
17
23
  await init(args);
18
24
  else if (cmd === 'hook')
package/dist/init.js CHANGED
@@ -6,7 +6,7 @@ import { createInterface } from 'node:readline/promises';
6
6
  import { AGENTS, CURSOR_HOOKS, opencodePlugin } from './agents.js';
7
7
  import { stopDaemon } from './daemon.js';
8
8
  import { onPath } from './git.js';
9
- import { apiUrl, ARCROOM_HOME, DEFAULT_URL } from './home.js';
9
+ import { apiUrl, ARCROOM_HOME, DEFAULT_URL, isLocalUrl, keyRefused } from './home.js';
10
10
  const EVENTS = ['SessionStart', 'UserPromptSubmit', 'PreToolUse', 'PostToolUse', 'Stop', 'SessionEnd'];
11
11
  const TOOL_EVENTS = new Set(['PreToolUse', 'PostToolUse']);
12
12
  const MARK = '.arcroom/runtime'; // identifies our hook entries in a settings file
@@ -43,6 +43,21 @@ const writeJson = (file, value) => {
43
43
  mkdirSync(dirname(file), { recursive: true });
44
44
  writeFileSync(file, JSON.stringify(value, null, 2) + '\n');
45
45
  };
46
+ /**
47
+ * The key and API URL init uses. Re-running init reuses what was saved, except a key the API refused (otherwise a new
48
+ * key could never get in) and a local URL left from development (it moves to the live API and says so).
49
+ */
50
+ export function initChoice(o) {
51
+ const refused = !!o.saved.apiKey && keyRefused({ apiKey: o.saved.apiKey, url: o.saved.url ?? DEFAULT_URL });
52
+ const key = o.argKey ?? o.envKey ?? (refused ? undefined : o.saved.apiKey);
53
+ const dropLocal = !o.argUrl && !!o.saved.url && isLocalUrl(o.saved.url);
54
+ const url = o.argUrl ?? (dropLocal ? DEFAULT_URL : o.saved.url ?? DEFAULT_URL);
55
+ const notes = [
56
+ ...(refused && !o.argKey && !o.envKey ? ['The saved key was refused by the API. Paste a new key (web app, Settings → API keys).'] : []),
57
+ ...(dropLocal ? [`The saved API was ${o.saved.url}; using ${DEFAULT_URL} now (arcroom init --url ${o.saved.url} keeps a local API).`] : []),
58
+ ];
59
+ return { key, url, notes };
60
+ }
46
61
  export async function init(args) {
47
62
  const arg = (name) => (args.includes(name) ? args[args.indexOf(name) + 1] : undefined);
48
63
  const agent = (arg('--agent') ?? 'claude');
@@ -52,10 +67,13 @@ export async function init(args) {
52
67
  }
53
68
  const configFile = join(ARCROOM_HOME, 'config.json');
54
69
  const saved = existsSync(configFile) ? JSON.parse(readFileSync(configFile, 'utf8')) : {}; // re-running init reuses saved values
55
- let key = arg('--key') ?? process.env.ARCROOM_API_KEY ?? saved.apiKey;
70
+ const choice = initChoice({ argKey: arg('--key'), envKey: process.env.ARCROOM_API_KEY, argUrl: arg('--url'), saved });
71
+ let key = choice.key;
72
+ const url = choice.url;
56
73
  if (arg('--key'))
57
74
  console.error('Note: a key typed on the command line stays in your shell history. Next time run arcroom init and paste it when asked, or set ARCROOM_API_KEY.');
58
- const url = arg('--url') ?? saved.url ?? DEFAULT_URL;
75
+ for (const note of choice.notes)
76
+ console.error(note);
59
77
  try {
60
78
  apiUrl(url);
61
79
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcroom",
3
- "version": "0.0.1-beta",
3
+ "version": "0.0.2-beta",
4
4
  "description": "Shared team memory and run control for coding agents: Claude Code, Codex, Cursor, OpenCode",
5
5
  "license": "Apache-2.0",
6
6
  "author": "ArcRoom AI",