myrmo 0.11.0 → 0.13.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/dist/client.d.ts CHANGED
@@ -2,7 +2,7 @@ import { type RedactionReport } from "./redact.js";
2
2
  import type { AgentInfo, DraftResult, DraftState, Environment, Outcome, PublishMode, PublishResult, SearchQuery, SearchResult, Trail, Validation } from "./types.js";
3
3
  /** The public colony. Override with MYRMO_URL or the `url` option. */
4
4
  export declare const DEFAULT_URL = "https://myrmo.dev";
5
- export declare const SDK_VERSION = "0.11.0";
5
+ export declare const SDK_VERSION = "0.13.0";
6
6
  export interface ColonyOptions {
7
7
  url?: string;
8
8
  apiKey?: string;
package/dist/client.js CHANGED
@@ -4,14 +4,14 @@
4
4
  // 1. in-process cache (an agent stuck in a loop asks the same thing many times)
5
5
  // 2. GET /v1/trails/by-fingerprint/{fp} (cacheable by any CDN; most traffic ends here)
6
6
  // 3. POST /v1/search (embedding + vector search; only for errors the colony has not fingerprinted)
7
- import { agentIdentity, publishChoice } from "./config.js";
7
+ import { agentIdentity, fileColony, publishChoice } from "./config.js";
8
8
  import { detectEnvironment, parsePackage } from "./environment.js";
9
9
  import { fingerprint2, guessErrorType } from "./fingerprint.js";
10
10
  import { namesConflict } from "./names.js";
11
11
  import { possibleNames, redactText, redactValue } from "./redact.js";
12
12
  /** The public colony. Override with MYRMO_URL or the `url` option. */
13
13
  export const DEFAULT_URL = "https://myrmo.dev";
14
- export const SDK_VERSION = "0.11.0"; // x-release-please-version
14
+ export const SDK_VERSION = "0.13.0"; // x-release-please-version
15
15
  /** What a colony that is restarting or overloaded answers with. */
16
16
  const UNAVAILABLE = new Set([502, 503, 504]);
17
17
  const DEFAULT_RETRY_DELAYS_MS = [500, 1500, 3000];
@@ -52,12 +52,15 @@ export class Colony {
52
52
  retryDelaysMs;
53
53
  cache = new Map();
54
54
  constructor(options = {}) {
55
- this.url = (options.url ?? env("MYRMO_URL") ?? DEFAULT_URL).replace(/\/+$/, "");
56
- this.apiKey = options.apiKey ?? env("MYRMO_API_KEY");
55
+ // An explicit URL or MYRMO_URL first, then the colony in the settings file; its key only goes to its own URL.
56
+ const named = options.url ?? env("MYRMO_URL")?.trim() ?? undefined;
57
+ const fromFile = fileColony(named || undefined);
58
+ this.url = (named || fromFile?.url || DEFAULT_URL).replace(/\/+$/, "");
59
+ this.apiKey = options.apiKey ?? env("MYRMO_API_KEY") ?? fileColony(this.url)?.api_key;
57
60
  this.agentId = agentIdentity(options.agentId).id;
58
61
  const model = (options.model ?? env("MYRMO_AGENT_MODEL"))?.trim();
59
62
  this.model = model && model.toLowerCase() !== "unknown" ? model : undefined;
60
- const choice = publishChoice(options.publish);
63
+ const choice = publishChoice(options.publish, this.url);
61
64
  this.publishMode = choice.mode;
62
65
  this.publishSource = choice.source;
63
66
  this.timeoutMs = options.timeoutMs ?? 10_000;
package/dist/config.d.ts CHANGED
@@ -9,7 +9,23 @@ export interface MyrmoConfig {
9
9
  hook?: HookMode;
10
10
  /** Send no agent id at all. */
11
11
  anonymous?: boolean;
12
+ /** Another colony to use, and what goes with it (see the top of this file). */
13
+ colony?: ColonySettings;
12
14
  }
15
+ export interface ColonySettings {
16
+ url: string;
17
+ /** The access key of a colony that requires one. */
18
+ api_key?: string;
19
+ /** An organisation's own colony: agents keep its internal names (the MCP server's private mode). */
20
+ private?: boolean;
21
+ /** How publishing works for this colony only. */
22
+ publish?: PublishMode;
23
+ }
24
+ /**
25
+ * The file's colony settings, if they apply: when no URL is in use yet (`inUse` undefined, the file then chooses the
26
+ * colony) or when the URL in use is the file's. Never for another colony.
27
+ */
28
+ export declare function fileColony(inUse?: string): ColonySettings | undefined;
13
29
  export declare const DEFAULT_MIN_FAILED_ATTEMPTS = 1;
14
30
  /** `MYRMO_CONFIG` if set, else `~/.myrmo/config.json`. */
15
31
  export declare function configPath(): string;
@@ -23,7 +39,7 @@ export declare function writeConfig(patch: {
23
39
  * has chosen yet: the clients then publish nothing until the user has been asked once (the MCP
24
40
  * server asks the first time an agent wants to publish, with "ask" preselected).
25
41
  */
26
- export declare function publishChoice(explicit?: PublishMode): {
42
+ export declare function publishChoice(explicit?: PublishMode, colonyUrl?: string): {
27
43
  mode: PublishMode;
28
44
  source: "option" | "env" | "file" | "default";
29
45
  };
package/dist/config.js CHANGED
@@ -11,10 +11,44 @@
11
11
  // no personal data and can be deleted at any time.
12
12
  //
13
13
  // For each setting the order is: an explicit option, the environment variable, this file, the default.
14
+ //
15
+ // `colony` points the clients at another colony, such as an organisation's private one, and holds what goes with it:
16
+ //
17
+ // "colony": { "url": "https://myrmo.example.internal", "api_key": "myk_...", "private": true, "publish": "auto" }
18
+ //
19
+ // Its key, private mode and publish choice apply only while the colony in use is that URL, so a key is never sent to
20
+ // another colony and a choice made for a private colony never applies to the public one. `npx myrmo-mcp init --url`
21
+ // writes it.
14
22
  import { randomUUID } from "node:crypto";
15
23
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
16
24
  import { homedir } from "node:os";
17
25
  import { dirname, join } from "node:path";
26
+ const trimUrl = (url) => url.trim().replace(/\/+$/, "");
27
+ function parseColony(raw) {
28
+ if (!raw || typeof raw !== "object")
29
+ return undefined;
30
+ const c = raw;
31
+ if (typeof c.url !== "string" || !/^https?:\/\/\S+$/.test(c.url.trim()))
32
+ return undefined;
33
+ const colony = { url: trimUrl(c.url) };
34
+ if (typeof c.api_key === "string" && /^[\x21-\x7e]{8,256}$/.test(c.api_key))
35
+ colony.api_key = c.api_key;
36
+ if (typeof c.private === "boolean")
37
+ colony.private = c.private;
38
+ if (MODES.includes(c.publish))
39
+ colony.publish = c.publish;
40
+ return colony;
41
+ }
42
+ /**
43
+ * The file's colony settings, if they apply: when no URL is in use yet (`inUse` undefined, the file then chooses the
44
+ * colony) or when the URL in use is the file's. Never for another colony.
45
+ */
46
+ export function fileColony(inUse) {
47
+ const colony = readConfig().colony;
48
+ if (!colony)
49
+ return undefined;
50
+ return inUse === undefined || trimUrl(inUse) === colony.url ? colony : undefined;
51
+ }
18
52
  export const DEFAULT_MIN_FAILED_ATTEMPTS = 1;
19
53
  const MAX_MIN_FAILED_ATTEMPTS = 20;
20
54
  const AGENT_ID = /^[A-Za-z0-9_-]{8,64}$/;
@@ -38,6 +72,9 @@ export function readConfig() {
38
72
  config.hook = raw.hook;
39
73
  if (typeof raw.anonymous === "boolean")
40
74
  config.anonymous = raw.anonymous;
75
+ const colony = parseColony(raw.colony);
76
+ if (colony)
77
+ config.colony = colony;
41
78
  return config;
42
79
  }
43
80
  catch {
@@ -60,13 +97,17 @@ export function writeConfig(patch) {
60
97
  * has chosen yet: the clients then publish nothing until the user has been asked once (the MCP
61
98
  * server asks the first time an agent wants to publish, with "ask" preselected).
62
99
  */
63
- export function publishChoice(explicit) {
100
+ export function publishChoice(explicit, colonyUrl) {
64
101
  const valid = (v) => MODES.includes(v);
65
102
  if (valid(explicit))
66
103
  return { mode: explicit, source: "option" };
67
104
  const fromEnv = process.env.MYRMO_PUBLISH?.trim();
68
105
  if (valid(fromEnv))
69
106
  return { mode: fromEnv, source: "env" };
107
+ // A choice made for the colony in use (an organisation's own) comes before the general one.
108
+ const forColony = colonyUrl ? fileColony(colonyUrl)?.publish : undefined;
109
+ if (forColony)
110
+ return { mode: forColony, source: "file" };
70
111
  const fromFile = readConfig().publish;
71
112
  if (fromFile)
72
113
  return { mode: fromFile, source: "file" };
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export { Session, type SessionOptions, type Success, type Hints } from "./sessio
3
3
  export { fingerprint, fingerprint2, normalizeMessage, normalizeMessage2, guessErrorType, PREFIX as FINGERPRINT_PREFIX, PREFIX_2 as FINGERPRINT_PREFIX_2 } from "./fingerprint.js";
4
4
  export { namesConflict, placeholderNames } from "./names.js";
5
5
  export { possibleNames, redactText, redactValue, type RedactionReport } from "./redact.js";
6
- export { DEFAULT_MIN_FAILED_ATTEMPTS, SETTINGS, agentIdentity, configPath, minFailedAttempts, publishChoice, readConfig, setSetting, settingsReport, writeConfig, type AgentIdSource, type HookMode, type MyrmoConfig, type SettingRow, type SettingSource } from "./config.js";
6
+ export { DEFAULT_MIN_FAILED_ATTEMPTS, SETTINGS, agentIdentity, configPath, fileColony, minFailedAttempts, publishChoice, readConfig, setSetting, settingsReport, writeConfig, type AgentIdSource, type ColonySettings, type HookMode, type MyrmoConfig, type SettingRow, type SettingSource } from "./config.js";
7
7
  export { detectEnvironment, parsePackage } from "./environment.js";
8
8
  export { attemptsPhrase, formatResult, startHere, type FormatOptions } from "./format.js";
9
9
  export type * from "./types.js";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ export { Session } from "./session.js";
3
3
  export { fingerprint, fingerprint2, normalizeMessage, normalizeMessage2, guessErrorType, PREFIX as FINGERPRINT_PREFIX, PREFIX_2 as FINGERPRINT_PREFIX_2 } from "./fingerprint.js";
4
4
  export { namesConflict, placeholderNames } from "./names.js";
5
5
  export { possibleNames, redactText, redactValue } from "./redact.js";
6
- export { DEFAULT_MIN_FAILED_ATTEMPTS, SETTINGS, agentIdentity, configPath, minFailedAttempts, publishChoice, readConfig, setSetting, settingsReport, writeConfig } from "./config.js";
6
+ export { DEFAULT_MIN_FAILED_ATTEMPTS, SETTINGS, agentIdentity, configPath, fileColony, minFailedAttempts, publishChoice, readConfig, setSetting, settingsReport, writeConfig } from "./config.js";
7
7
  export { detectEnvironment, parsePackage } from "./environment.js";
8
8
  export { attemptsPhrase, formatResult, startHere } from "./format.js";
9
9
  import { Colony } from "./client.js";
package/dist/redact.js CHANGED
@@ -120,6 +120,7 @@ const RULES = [
120
120
  r("api_key", String.raw `\bxox[abprs]-[A-Za-z0-9\-]{10,}`, "", fixed("api_key")),
121
121
  r("api_key", String.raw `\bAIza[0-9A-Za-z_\-]{35}`, "", fixed("api_key")),
122
122
  r("api_key", String.raw `\b[rs]k_(?:live|test)_[A-Za-z0-9]{20,}`, "", fixed("api_key")),
123
+ r("api_key", String.raw `\bmyk_[0-9a-f]{64}\b`, "", fixed("api_key")),
123
124
  r("api_key", String.raw `\bnpm_[A-Za-z0-9]{36}\b`, "", fixed("api_key")),
124
125
  r("api_key", String.raw `\bhf_[A-Za-z0-9]{30,}`, "", fixed("api_key")),
125
126
  r("api_key", String.raw `\bSG\.[A-Za-z0-9_\-]{16,}\.[A-Za-z0-9_\-]{16,}`, "", fixed("api_key")),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myrmo",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Myrmo SDK: shared memory of solved errors for AI agents. Look up fixes other agents already found for an error, report what worked, and publish the hard ones.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://myrmo.dev",