auggy 0.3.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.
Files changed (121) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/LICENSE +201 -0
  3. package/README.md +161 -0
  4. package/package.json +76 -0
  5. package/src/agent-card.ts +39 -0
  6. package/src/agent.ts +283 -0
  7. package/src/agentmail-client.ts +138 -0
  8. package/src/augments/bash/index.ts +463 -0
  9. package/src/augments/bash/skill/SKILL.md +156 -0
  10. package/src/augments/budgets/budget-store.ts +513 -0
  11. package/src/augments/budgets/index.ts +134 -0
  12. package/src/augments/budgets/preamble.ts +93 -0
  13. package/src/augments/budgets/types.ts +89 -0
  14. package/src/augments/file-memory/index.ts +71 -0
  15. package/src/augments/filesystem/index.ts +533 -0
  16. package/src/augments/filesystem/skill/SKILL.md +142 -0
  17. package/src/augments/filesystem/skill/references/mount-permissions.md +81 -0
  18. package/src/augments/layered-memory/extractor/buffer.ts +56 -0
  19. package/src/augments/layered-memory/extractor/frequency.ts +79 -0
  20. package/src/augments/layered-memory/extractor/inject-handler.ts +103 -0
  21. package/src/augments/layered-memory/extractor/parse.ts +75 -0
  22. package/src/augments/layered-memory/extractor/prompt.md +26 -0
  23. package/src/augments/layered-memory/index.ts +757 -0
  24. package/src/augments/layered-memory/skill/SKILL.md +153 -0
  25. package/src/augments/layered-memory/storage/migrations/README.md +16 -0
  26. package/src/augments/layered-memory/storage/migrations/supabase-add-fact-fields.sql +9 -0
  27. package/src/augments/layered-memory/storage/sqlite-store.ts +352 -0
  28. package/src/augments/layered-memory/storage/supabase-store.ts +263 -0
  29. package/src/augments/layered-memory/storage/types.ts +98 -0
  30. package/src/augments/link/index.ts +489 -0
  31. package/src/augments/link/translate.ts +261 -0
  32. package/src/augments/notify/adapters/agentmail.ts +70 -0
  33. package/src/augments/notify/adapters/telegram.ts +60 -0
  34. package/src/augments/notify/adapters/webhook.ts +55 -0
  35. package/src/augments/notify/index.ts +284 -0
  36. package/src/augments/notify/skill/SKILL.md +150 -0
  37. package/src/augments/org-context/index.ts +721 -0
  38. package/src/augments/org-context/skill/SKILL.md +96 -0
  39. package/src/augments/skills/index.ts +103 -0
  40. package/src/augments/supabase-memory/index.ts +151 -0
  41. package/src/augments/telegram-transport/index.ts +312 -0
  42. package/src/augments/telegram-transport/polling.ts +55 -0
  43. package/src/augments/telegram-transport/webhook.ts +56 -0
  44. package/src/augments/turn-control/index.ts +61 -0
  45. package/src/augments/turn-control/skill/SKILL.md +155 -0
  46. package/src/augments/visitor-auth/email-validation.ts +66 -0
  47. package/src/augments/visitor-auth/index.ts +779 -0
  48. package/src/augments/visitor-auth/rate-limiter.ts +90 -0
  49. package/src/augments/visitor-auth/skill/SKILL.md +55 -0
  50. package/src/augments/visitor-auth/storage/sqlite-store.ts +398 -0
  51. package/src/augments/visitor-auth/storage/types.ts +164 -0
  52. package/src/augments/visitor-auth/types.ts +123 -0
  53. package/src/augments/visitor-auth/verify-page.ts +179 -0
  54. package/src/augments/web-fetch/index.ts +331 -0
  55. package/src/augments/web-fetch/skill/SKILL.md +100 -0
  56. package/src/cli/agent-index.ts +289 -0
  57. package/src/cli/augment-catalog.ts +320 -0
  58. package/src/cli/augment-resolver.ts +597 -0
  59. package/src/cli/commands/add-skill.ts +194 -0
  60. package/src/cli/commands/add.ts +87 -0
  61. package/src/cli/commands/chat.ts +207 -0
  62. package/src/cli/commands/create.ts +462 -0
  63. package/src/cli/commands/dev.ts +139 -0
  64. package/src/cli/commands/eval.ts +180 -0
  65. package/src/cli/commands/ls.ts +66 -0
  66. package/src/cli/commands/remove.ts +95 -0
  67. package/src/cli/commands/restart.ts +40 -0
  68. package/src/cli/commands/start.ts +123 -0
  69. package/src/cli/commands/status.ts +104 -0
  70. package/src/cli/commands/stop.ts +84 -0
  71. package/src/cli/commands/visitors-revoke.ts +155 -0
  72. package/src/cli/commands/visitors.ts +101 -0
  73. package/src/cli/config-parser.ts +1034 -0
  74. package/src/cli/engine-resolver.ts +68 -0
  75. package/src/cli/index.ts +178 -0
  76. package/src/cli/model-picker.ts +89 -0
  77. package/src/cli/pid-registry.ts +146 -0
  78. package/src/cli/plist-generator.ts +117 -0
  79. package/src/cli/resolve-config.ts +56 -0
  80. package/src/cli/scaffold-skills.ts +158 -0
  81. package/src/cli/scaffold.ts +291 -0
  82. package/src/cli/skill-frontmatter.ts +51 -0
  83. package/src/cli/skill-validator.ts +151 -0
  84. package/src/cli/types.ts +228 -0
  85. package/src/cli/yaml-helpers.ts +66 -0
  86. package/src/engines/_shared/cost.ts +55 -0
  87. package/src/engines/_shared/schema-normalize.ts +75 -0
  88. package/src/engines/anthropic/pricing.ts +117 -0
  89. package/src/engines/anthropic.ts +483 -0
  90. package/src/engines/openai/pricing.ts +67 -0
  91. package/src/engines/openai.ts +446 -0
  92. package/src/engines/openrouter/pricing.ts +83 -0
  93. package/src/engines/openrouter.ts +185 -0
  94. package/src/helpers.ts +24 -0
  95. package/src/http.ts +387 -0
  96. package/src/index.ts +165 -0
  97. package/src/kernel/capability-table.ts +172 -0
  98. package/src/kernel/context-allocator.ts +161 -0
  99. package/src/kernel/history-manager.ts +198 -0
  100. package/src/kernel/lifecycle-manager.ts +106 -0
  101. package/src/kernel/output-validator.ts +35 -0
  102. package/src/kernel/preamble.ts +23 -0
  103. package/src/kernel/route-collector.ts +97 -0
  104. package/src/kernel/timeout.ts +21 -0
  105. package/src/kernel/tool-selector.ts +47 -0
  106. package/src/kernel/trace-emitter.ts +66 -0
  107. package/src/kernel/transport-queue.ts +147 -0
  108. package/src/kernel/turn-loop.ts +1148 -0
  109. package/src/memory/context-synthesis.ts +83 -0
  110. package/src/memory/memory-bus.ts +61 -0
  111. package/src/memory/registry.ts +80 -0
  112. package/src/memory/tools.ts +320 -0
  113. package/src/memory/types.ts +8 -0
  114. package/src/parts.ts +30 -0
  115. package/src/scaffold-templates/identity.md +31 -0
  116. package/src/telegram-client.ts +145 -0
  117. package/src/tokenizer.ts +14 -0
  118. package/src/transports/ag-ui-events.ts +253 -0
  119. package/src/transports/visitor-token.ts +82 -0
  120. package/src/transports/web-transport.ts +948 -0
  121. package/src/types.ts +1009 -0
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Agent index — `~/.auggy/agents.json`.
3
+ *
4
+ * Maps agent name → { localDir, createdAt, cloud }. Load-bearing facility
5
+ * metadata: every CLI command that finds an agent on disk goes through here.
6
+ *
7
+ * Pattern mirrors `pid-registry.ts`: atomic write via temp+rename, defensive
8
+ * recovery on corruption, no in-memory caching across invocations.
9
+ */
10
+
11
+ import {
12
+ closeSync,
13
+ fstatSync,
14
+ mkdirSync,
15
+ openSync,
16
+ readSync,
17
+ renameSync,
18
+ unlinkSync,
19
+ writeFileSync,
20
+ writeSync,
21
+ } from "node:fs";
22
+ import { homedir } from "node:os";
23
+ import { join } from "node:path";
24
+ import type { IndexFile, IndexEntry } from "./types";
25
+
26
+ const SCHEMA_VERSION = 1 as const;
27
+
28
+ // Lock acquisition tuning. Spin with short busy-waits up to LOCK_TIMEOUT_MS,
29
+ // matching the cadence in pid-registry's other file-coordination paths.
30
+ const LOCK_TIMEOUT_MS = 5_000;
31
+ const LOCK_POLL_MS = 50;
32
+
33
+ interface IndexOptions {
34
+ /** Override `~/.auggy/` for tests. Production callers omit. */
35
+ auggyDir?: string;
36
+ }
37
+
38
+ function getAuggyDir(opts: IndexOptions = {}): string {
39
+ return opts.auggyDir ?? join(homedir(), ".auggy");
40
+ }
41
+
42
+ function indexPath(opts: IndexOptions): string {
43
+ return join(getAuggyDir(opts), "agents.json");
44
+ }
45
+
46
+ function ensureDir(opts: IndexOptions): void {
47
+ mkdirSync(getAuggyDir(opts), { recursive: true });
48
+ }
49
+
50
+ function emptyIndex(): IndexFile {
51
+ return { version: SCHEMA_VERSION, agents: {} };
52
+ }
53
+
54
+ function lockPath(opts: IndexOptions): string {
55
+ return join(getAuggyDir(opts), "agents.json.lock");
56
+ }
57
+
58
+ interface LockHandle {
59
+ release: () => void;
60
+ }
61
+
62
+ interface LockFileContents {
63
+ pid: number;
64
+ acquired: string;
65
+ }
66
+
67
+ /**
68
+ * Try to atomically create the lock file. Returns a release handle on
69
+ * success, or null if the lock is already held (EEXIST). Any other I/O
70
+ * error propagates.
71
+ *
72
+ * Extracted from `acquireLock` so the happy path and the force-recovery
73
+ * retry path share a single implementation.
74
+ */
75
+ function tryAcquire(path: string, body: string): LockHandle | null {
76
+ try {
77
+ const fd = openSync(path, "wx");
78
+ try {
79
+ writeSync(fd, body);
80
+ } finally {
81
+ closeSync(fd);
82
+ }
83
+ return {
84
+ release: () => {
85
+ try {
86
+ unlinkSync(path);
87
+ } catch {
88
+ // Already gone — nothing to do.
89
+ }
90
+ },
91
+ };
92
+ } catch (err) {
93
+ if ((err as NodeJS.ErrnoException).code !== "EEXIST") throw err;
94
+ return null;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Acquire a cross-process advisory lock on `agents.json.lock`.
100
+ *
101
+ * Uses `openSync(path, "wx")` for atomic exclusive create — same primitive
102
+ * used by `pid-registry` for atomic per-agent manifests. On EEXIST: busy-wait
103
+ * `LOCK_POLL_MS` and retry until `LOCK_TIMEOUT_MS` elapses. Once the deadline
104
+ * passes we assume the previous holder crashed, force-unlink the lock, and
105
+ * retry once. If that retry still fails (e.g. permissions/IO error), throw.
106
+ *
107
+ * Why time-based recovery instead of PID liveness checks: reading the lock
108
+ * file's content to extract a holder PID requires a second `openSync(path, "r")`,
109
+ * which CodeQL flags as TOCTOU against the EEXIST signal even though we only
110
+ * extract a number. Time-based recovery uses a single primitive (`openSync(wx)`),
111
+ * so there is no path-based race surface to flag.
112
+ *
113
+ * Synchronous on purpose — the rest of this module is sync, and CLI mutators
114
+ * are short-running. Blocking the event loop for at most 5s is acceptable.
115
+ */
116
+ function acquireLock(opts: IndexOptions = {}): LockHandle {
117
+ ensureDir(opts);
118
+ const path = lockPath(opts);
119
+ const deadline = Date.now() + LOCK_TIMEOUT_MS;
120
+ const body = JSON.stringify({
121
+ pid: process.pid,
122
+ acquired: new Date().toISOString(),
123
+ } satisfies LockFileContents);
124
+
125
+ for (;;) {
126
+ const handle = tryAcquire(path, body);
127
+ if (handle) return handle;
128
+
129
+ if (Date.now() >= deadline) {
130
+ // Force-recover: assume the previous holder crashed.
131
+ try {
132
+ unlinkSync(path);
133
+ } catch {
134
+ // Race: someone else cleaned it up. Recovery retry below will succeed.
135
+ }
136
+ const recovered = tryAcquire(path, body);
137
+ if (recovered) return recovered;
138
+ throw new Error(
139
+ `Could not acquire agents.json lock at ${path} after ${LOCK_TIMEOUT_MS}ms ` +
140
+ `(another process holding it).`,
141
+ );
142
+ }
143
+
144
+ Bun.sleepSync(LOCK_POLL_MS);
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Read the index. Returns an empty index when the file doesn't exist.
150
+ *
151
+ * On JSON parse failure, backs up the corrupt file to
152
+ * `agents.json.corrupt-<ISO>` and returns an empty index — operator can
153
+ * recover from the backup if needed.
154
+ *
155
+ * Throws on unknown schema versions (forward-compat guard).
156
+ */
157
+ export function readIndex(opts: IndexOptions = {}): IndexFile {
158
+ const path = indexPath(opts);
159
+
160
+ // Open via fd to avoid existsSync+readFileSync TOCTOU.
161
+ let fd: number;
162
+ try {
163
+ fd = openSync(path, "r");
164
+ } catch (err) {
165
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
166
+ return emptyIndex();
167
+ }
168
+ throw err;
169
+ }
170
+
171
+ let raw: string;
172
+ try {
173
+ const stats = fstatSync(fd);
174
+ const buf = Buffer.alloc(stats.size);
175
+ if (stats.size > 0) {
176
+ readSync(fd, buf, 0, stats.size, 0);
177
+ }
178
+ raw = buf.toString("utf-8");
179
+ } finally {
180
+ try {
181
+ closeSync(fd);
182
+ } catch {
183
+ // best-effort
184
+ }
185
+ }
186
+
187
+ let parsed: unknown;
188
+ try {
189
+ parsed = JSON.parse(raw);
190
+ } catch (_err) {
191
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
192
+ const backupPath = join(getAuggyDir(opts), `agents.json.corrupt-${ts}`);
193
+ renameSync(path, backupPath);
194
+ console.warn(
195
+ `[agent-index] corrupt agents.json detected; backed up to ${backupPath}. Recreating empty index.`,
196
+ );
197
+ return emptyIndex();
198
+ }
199
+
200
+ if (
201
+ !parsed ||
202
+ typeof parsed !== "object" ||
203
+ (parsed as { version?: number }).version !== SCHEMA_VERSION
204
+ ) {
205
+ const got = (parsed as { version?: number } | null)?.version;
206
+ throw new Error(
207
+ `agents.json has unknown schema version ${got} (expected ${SCHEMA_VERSION}). ` +
208
+ `This file may be from a newer version of auggy.`,
209
+ );
210
+ }
211
+
212
+ return parsed as IndexFile;
213
+ }
214
+
215
+ /**
216
+ * Write the index atomically: write to `agents.json.tmp`, then rename
217
+ * over the target. Same hygiene as `pid-registry.ts`.
218
+ */
219
+ export function writeIndex(idx: IndexFile, opts: IndexOptions = {}): void {
220
+ ensureDir(opts);
221
+ const target = indexPath(opts);
222
+ const tmp = `${target}.tmp`;
223
+ writeFileSync(tmp, JSON.stringify(idx, null, 2));
224
+ renameSync(tmp, target);
225
+ }
226
+
227
+ /**
228
+ * Add an agent to the index. Throws if `name` is already registered.
229
+ *
230
+ * Holds an advisory lock for the read-modify-write window so two concurrent
231
+ * CLI invocations don't lose each other's updates.
232
+ */
233
+ export function addAgent(name: string, localDir: string, opts: IndexOptions = {}): void {
234
+ const lock = acquireLock(opts);
235
+ try {
236
+ const idx = readIndex(opts);
237
+ if (idx.agents[name]) {
238
+ throw new Error(
239
+ `Agent "${name}" already registered at ${idx.agents[name].localDir}. ` +
240
+ `Choose a different name or remove the existing one with \`auggy remove ${name}\`.`,
241
+ );
242
+ }
243
+ idx.agents[name] = {
244
+ localDir,
245
+ createdAt: new Date().toISOString(),
246
+ cloud: null,
247
+ };
248
+ writeIndex(idx, opts);
249
+ } finally {
250
+ lock.release();
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Remove an agent from the index. Idempotent — no-op when not present.
256
+ *
257
+ * Holds an advisory lock for the read-modify-write window. The lock is
258
+ * released even when no entry exists.
259
+ */
260
+ export function removeAgent(name: string, opts: IndexOptions = {}): void {
261
+ const lock = acquireLock(opts);
262
+ try {
263
+ const idx = readIndex(opts);
264
+ if (!idx.agents[name]) return;
265
+ delete idx.agents[name];
266
+ writeIndex(idx, opts);
267
+ } finally {
268
+ lock.release();
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Look up an agent by name. Returns null if not registered.
274
+ */
275
+ export function getAgent(name: string, opts: IndexOptions = {}): IndexEntry | null {
276
+ const idx = readIndex(opts);
277
+ return idx.agents[name] ?? null;
278
+ }
279
+
280
+ /**
281
+ * List all registered agents with their names.
282
+ */
283
+ export function listAgents(opts: IndexOptions = {}): Array<IndexEntry & { name: string }> {
284
+ const idx = readIndex(opts);
285
+ return Object.entries(idx.agents).map(([name, entry]) => ({
286
+ name,
287
+ ...entry,
288
+ }));
289
+ }
@@ -0,0 +1,320 @@
1
+ /**
2
+ * Augment catalog — metadata for all built-in augments.
3
+ *
4
+ * Used by `auggy create` (interactive selection) and `auggy add`
5
+ * (add to existing agent). Each entry describes what the augment
6
+ * does, its default config, and whether it ships with a bundled skill
7
+ * folder under `src/augments/<type>/skill/`.
8
+ *
9
+ * Skills are no longer carried as inline string templates here. Per
10
+ * ADR-025 + PR α task 4, scaffold copies `src/augments/<name>/skill/`
11
+ * into the agent dir directly; the catalog only records *whether* a
12
+ * bundled skill exists so the create UI can label entries accurately.
13
+ * The `scaffold-skills` module is the single source of truth for the
14
+ * type → folder mapping.
15
+ */
16
+
17
+ export interface CatalogEntry {
18
+ /** Display name for selection UI. */
19
+ label: string;
20
+ /** Short description shown in the selector. */
21
+ description: string;
22
+ /** The augment type identifier in agent.yaml. */
23
+ type: string;
24
+ /** Default instance name in agent.yaml. */
25
+ defaultName: string;
26
+ /** Default options for agent.yaml. */
27
+ defaultOptions: Record<string, unknown>;
28
+ /** Whether this augment is always included (not deselectable). */
29
+ required: boolean;
30
+ /** Env vars this augment needs (shown in .env.example). */
31
+ envVars?: string[];
32
+ /**
33
+ * Whether this augment ships a bundled `src/augments/<type>/skill/` folder
34
+ * the scaffold copies into the agent dir. Authoritative state lives on
35
+ * disk; this flag is informational for the create UI.
36
+ */
37
+ hasSkill: boolean;
38
+ }
39
+
40
+ export const AUGMENT_CATALOG: CatalogEntry[] = [
41
+ // NOTE on identity: the agent's identity preamble is mounted via the
42
+ // top-level `identity: ./identity.md` shorthand (see config-parser §α-5),
43
+ // not via a catalog entry. Keeping an explicit fileMemory@system entry
44
+ // here AND emitting the shorthand would trigger α-5's conflict check on
45
+ // the very first scaffold. The scaffold and the create command emit the
46
+ // shorthand directly; the catalog never carries an identity row.
47
+ {
48
+ label: "fileMemory (learned)",
49
+ description: "Mutable memory — agent writes learned behaviors here",
50
+ type: "fileMemory",
51
+ defaultName: "learned",
52
+ defaultOptions: {
53
+ label: "learned",
54
+ source: "./learned.md",
55
+ mutable: true,
56
+ origin: "system",
57
+ priority: "high",
58
+ placement: "preamble",
59
+ eviction: "drop",
60
+ },
61
+ required: true,
62
+ hasSkill: false,
63
+ },
64
+ {
65
+ label: "layeredMemory",
66
+ description: "Peer-scoped episodic memory with provenance (SQLite or Supabase)",
67
+ type: "layeredMemory",
68
+ defaultName: "memory",
69
+ defaultOptions: {
70
+ backend: "sqlite",
71
+ dbPath: "./memory.sqlite",
72
+ namespace: "ep",
73
+ retentionDays: 90,
74
+ },
75
+ required: true,
76
+ hasSkill: true,
77
+ },
78
+ {
79
+ label: "filesystem",
80
+ description: "Read/write files — skills directory + workspace",
81
+ type: "filesystem",
82
+ defaultName: "files",
83
+ defaultOptions: {
84
+ mounts: [
85
+ { name: "skills", path: "./skills", writable: false },
86
+ { name: "workspace", path: "./workspace", writable: true, deletable: true },
87
+ ],
88
+ },
89
+ required: true,
90
+ hasSkill: true,
91
+ },
92
+ {
93
+ label: "webTransport",
94
+ description: "AG-UI chat endpoint (HTTP + SSE)",
95
+ type: "webTransport",
96
+ defaultName: "web",
97
+ defaultOptions: {
98
+ port: 8080,
99
+ auth: { type: "bearer", token: "${AUGGY_WEB_TOKEN}" },
100
+ visitorTokens: {
101
+ // signingKey is NOT set here — visitorAuth is the single source of truth
102
+ // and the resolver injects it. Setting it here would trigger the
103
+ // duplicate-key warning on every boot.
104
+ agentBinding: "${AUGGY_AGENT_ID}",
105
+ },
106
+ },
107
+ required: false,
108
+ envVars: ["AUGGY_WEB_TOKEN", "AUGGY_AGENT_ID"],
109
+ hasSkill: false,
110
+ },
111
+ {
112
+ label: "webFetch",
113
+ description: "Fetch URLs, read web pages, call HTTP APIs",
114
+ type: "webFetch",
115
+ defaultName: "fetch",
116
+ defaultOptions: {
117
+ timeoutMs: 15000,
118
+ },
119
+ required: false,
120
+ hasSkill: true,
121
+ },
122
+ {
123
+ label: "supabaseMemory",
124
+ description: "Episodic memory backed by Supabase (visitor profiles, events)",
125
+ type: "supabaseMemory",
126
+ defaultName: "episodic",
127
+ defaultOptions: {
128
+ namespace: "episode",
129
+ table: "agent_memories",
130
+ mutable: true,
131
+ origin: "peer-derived",
132
+ priority: "normal",
133
+ placement: "preamble",
134
+ eviction: "drop",
135
+ supabaseUrl: "${SUPABASE_URL}",
136
+ supabaseKey: "${SUPABASE_SERVICE_KEY}",
137
+ },
138
+ required: false,
139
+ envVars: ["SUPABASE_URL", "SUPABASE_SERVICE_KEY"],
140
+ hasSkill: false,
141
+ },
142
+ {
143
+ label: "orgContext",
144
+ description: "Connect to org knowledge API (manifest + org_fetch)",
145
+ type: "orgContext",
146
+ defaultName: "org",
147
+ defaultOptions: {
148
+ // Default to file:// scheme pointing at the scaffolded example dir
149
+ // (per α-6 + spec §Decision 9). Operators wanting an HTTP-served
150
+ // manifest replace this with `${ORG_CONTEXT_URL}` and provide the env.
151
+ baseUrl: "file://./org-context",
152
+ },
153
+ required: false,
154
+ hasSkill: true,
155
+ },
156
+ {
157
+ // ADR-030: model-facing skill surface. Emits a single system-placement
158
+ // context block listing each mounted skill's name + description (read
159
+ // from each SKILL.md's YAML frontmatter, agentskills.io standard).
160
+ // Activation is fs_read via the filesystem augment. Required because
161
+ // without it no skills are surfaced to the model; operators wanting an
162
+ // agent with literally zero skill discovery can edit agent.yaml after
163
+ // scaffolding.
164
+ label: "skills",
165
+ description: "Lists mounted skills for the model (ADR-030 skill surface)",
166
+ type: "skills",
167
+ defaultName: "skills",
168
+ defaultOptions: {
169
+ dir: "./skills",
170
+ },
171
+ required: true,
172
+ // The augment itself carries no skill — it IS the skill surface.
173
+ hasSkill: false,
174
+ },
175
+ {
176
+ label: "bash",
177
+ description: "Execute shell commands with configurable risk levels",
178
+ type: "bash",
179
+ defaultName: "bash",
180
+ defaultOptions: {
181
+ risk: "restricted",
182
+ allowedCommands: ["echo", "ls", "cat", "pwd", "date"],
183
+ },
184
+ required: false,
185
+ hasSkill: true,
186
+ },
187
+ {
188
+ label: "budgets",
189
+ description: "Per-trust-level turn budgets + dailyBudgetUsd ceiling (SQLite)",
190
+ type: "budgets",
191
+ defaultName: "budgets",
192
+ defaultOptions: {
193
+ dbPath: "./budgets.db",
194
+ caps: {
195
+ public: {
196
+ recognized: { maxTurnsPerThread: 20, maxTurnsPerDay: 50, maxUsdPerDay: 1 },
197
+ anonymous: { maxTurnsPerThread: 5 },
198
+ },
199
+ },
200
+ anonymousGlobalLimit: 30,
201
+ dailyBudgetUsd: 5,
202
+ },
203
+ required: false,
204
+ hasSkill: false,
205
+ },
206
+ {
207
+ label: "notify",
208
+ description:
209
+ "Outbound messaging to operator-defined destinations (webhook + telegram + agentmail adapters)",
210
+ type: "notify",
211
+ defaultName: "notify",
212
+ defaultOptions: {
213
+ destinations: [{ name: "creator", transport: "webhook", url: "${ORG_NOTIFY_URL}" }],
214
+ rateLimit: {
215
+ cooldownMs: 120_000,
216
+ globalMaxPerHour: 5,
217
+ dedupWindowMs: 300_000,
218
+ dedupThreshold: 0.6,
219
+ perPeerCooldownMs: 30_000,
220
+ },
221
+ },
222
+ required: false,
223
+ envVars: ["ORG_NOTIFY_URL"],
224
+ hasSkill: true,
225
+ },
226
+ {
227
+ label: "telegramTransport",
228
+ description: "Bidirectional Telegram I/O — long-poll OR webhook inbound, four-path identity",
229
+ type: "telegramTransport",
230
+ defaultName: "telegram",
231
+ defaultOptions: {
232
+ botToken: "${TELEGRAM_BOT_TOKEN}",
233
+ inbound: {
234
+ mode: "polling",
235
+ polling: { timeoutSec: 30 },
236
+ // To switch to webhook mode, replace the polling block with:
237
+ // mode: "webhook"
238
+ // webhook: { publicUrl: "${TELEGRAM_WEBHOOK_URL}", port: 8081, secretToken: "${TELEGRAM_WEBHOOK_SECRET}" }
239
+ },
240
+ auth: {
241
+ creatorUserIds: [],
242
+ anonymousIdentityMode: "ephemeral",
243
+ },
244
+ },
245
+ required: false,
246
+ envVars: ["TELEGRAM_BOT_TOKEN"],
247
+ hasSkill: false,
248
+ },
249
+ {
250
+ label: "Turn Control",
251
+ description:
252
+ "Lets the agent pause and request input from the user. Recommended for chat-shaped agents (web/telegram).",
253
+ type: "turnControl",
254
+ defaultName: "turn-control",
255
+ defaultOptions: {},
256
+ required: false,
257
+ hasSkill: true,
258
+ },
259
+ {
260
+ label: "link",
261
+ description:
262
+ "Peer-to-peer A2A v0.2 transport (auggy ↔ auggy / A2A-speaking peers) via @auggy/link",
263
+ type: "link",
264
+ defaultName: "link",
265
+ defaultOptions: {
266
+ port: 8081,
267
+ dbPath: "./link.db",
268
+ agentCard: {
269
+ id: "${AUGGY_AGENT_ID}",
270
+ name: "${AUGGY_AGENT_NAME}",
271
+ description: "augment-1 link endpoint",
272
+ endpointUrl: "${AUGGY_LINK_PUBLIC_URL}",
273
+ },
274
+ peers: {},
275
+ },
276
+ required: false,
277
+ envVars: ["AUGGY_AGENT_ID", "AUGGY_AGENT_NAME", "AUGGY_LINK_PUBLIC_URL"],
278
+ hasSkill: false,
279
+ },
280
+ {
281
+ label: "Visitor Auth",
282
+ description:
283
+ "Email magic-link verification — promotes anonymous visitors to recognized identity",
284
+ type: "visitorAuth",
285
+ defaultName: "visitor-auth",
286
+ defaultOptions: {
287
+ publicUrl: "${AUGGY_PUBLIC_URL}",
288
+ dbPath: "./visitor-auth.db",
289
+ agentMail: {
290
+ apiKey: "${AGENTMAIL_API_KEY}",
291
+ inboxId: "${AGENTMAIL_INBOX_ID}",
292
+ subjectPrefix: "[Verify] ",
293
+ },
294
+ signingKey: "${VISITOR_SIGNING_KEY}",
295
+ agentBinding: "${AUGGY_AGENT_ID}",
296
+ rateLimit: { perHour: 1, perDay: 3 },
297
+ reverifyAfterDays: 90,
298
+ tokenTtlMinutes: 15,
299
+ layeredMemoryDbPath: "./memory.db",
300
+ },
301
+ required: false,
302
+ envVars: [
303
+ "AGENTMAIL_API_KEY",
304
+ "AGENTMAIL_INBOX_ID",
305
+ "AUGGY_PUBLIC_URL",
306
+ "VISITOR_SIGNING_KEY",
307
+ "AUGGY_AGENT_ID",
308
+ ],
309
+ hasSkill: true,
310
+ },
311
+ ];
312
+
313
+ /** Get catalog entries that are not yet installed (by type + defaultName). */
314
+ export function getAvailableAugments(
315
+ installed: Array<{ type: string; name: string }>,
316
+ ): CatalogEntry[] {
317
+ return AUGMENT_CATALOG.filter(
318
+ (entry) => !installed.some((i) => i.type === entry.type && i.name === entry.defaultName),
319
+ );
320
+ }