@nkmc/agent-fs 0.1.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 (42) hide show
  1. package/dist/chunk-7LIZT7L3.js +966 -0
  2. package/dist/index.cjs +1278 -0
  3. package/dist/index.d.cts +96 -0
  4. package/dist/index.d.ts +96 -0
  5. package/dist/index.js +419 -0
  6. package/dist/rpc-D1IHpjF_.d.cts +330 -0
  7. package/dist/rpc-D1IHpjF_.d.ts +330 -0
  8. package/dist/testing.cjs +842 -0
  9. package/dist/testing.d.cts +29 -0
  10. package/dist/testing.d.ts +29 -0
  11. package/dist/testing.js +10 -0
  12. package/package.json +25 -0
  13. package/src/agent-fs.ts +151 -0
  14. package/src/backends/http.ts +835 -0
  15. package/src/backends/memory.ts +183 -0
  16. package/src/backends/rpc.ts +456 -0
  17. package/src/index.ts +36 -0
  18. package/src/mount.ts +84 -0
  19. package/src/parser.ts +162 -0
  20. package/src/server.ts +158 -0
  21. package/src/testing.ts +3 -0
  22. package/src/types.ts +52 -0
  23. package/test/agent-fs.test.ts +325 -0
  24. package/test/http-204.test.ts +102 -0
  25. package/test/http-auth-prefix.test.ts +79 -0
  26. package/test/http-cloudflare.test.ts +533 -0
  27. package/test/http-form-encoding.test.ts +119 -0
  28. package/test/http-github.test.ts +580 -0
  29. package/test/http-listkey.test.ts +128 -0
  30. package/test/http-oauth2.test.ts +174 -0
  31. package/test/http-pagination.test.ts +200 -0
  32. package/test/http-param-styles.test.ts +98 -0
  33. package/test/http-passthrough.test.ts +282 -0
  34. package/test/http-retry.test.ts +132 -0
  35. package/test/http.test.ts +360 -0
  36. package/test/memory.test.ts +120 -0
  37. package/test/mount.test.ts +94 -0
  38. package/test/parser.test.ts +100 -0
  39. package/test/rpc-crud.test.ts +627 -0
  40. package/test/rpc-evm.test.ts +390 -0
  41. package/tsconfig.json +8 -0
  42. package/tsup.config.ts +8 -0
@@ -0,0 +1,29 @@
1
+ import { b as FsBackend } from './rpc-D1IHpjF_.cjs';
2
+ export { f as HttpBackend, R as RpcBackend } from './rpc-D1IHpjF_.cjs';
3
+
4
+ interface Record {
5
+ id: string;
6
+ [key: string]: unknown;
7
+ }
8
+ /**
9
+ * In-memory FsBackend for testing and prototyping.
10
+ * Stores data as a map of collections, each collection is a map of id → record.
11
+ */
12
+ declare class MemoryBackend implements FsBackend {
13
+ private collections;
14
+ private nextId;
15
+ /** Pre-seed a collection with data */
16
+ seed(collection: string, records: Record[]): void;
17
+ list(path: string): Promise<string[]>;
18
+ read(path: string): Promise<unknown>;
19
+ write(path: string, data: unknown): Promise<{
20
+ id: string;
21
+ }>;
22
+ remove(path: string): Promise<void>;
23
+ search(path: string, pattern: string): Promise<unknown[]>;
24
+ private parsePath;
25
+ private getOrCreateCollection;
26
+ private getSchema;
27
+ }
28
+
29
+ export { MemoryBackend };
@@ -0,0 +1,29 @@
1
+ import { b as FsBackend } from './rpc-D1IHpjF_.js';
2
+ export { f as HttpBackend, R as RpcBackend } from './rpc-D1IHpjF_.js';
3
+
4
+ interface Record {
5
+ id: string;
6
+ [key: string]: unknown;
7
+ }
8
+ /**
9
+ * In-memory FsBackend for testing and prototyping.
10
+ * Stores data as a map of collections, each collection is a map of id → record.
11
+ */
12
+ declare class MemoryBackend implements FsBackend {
13
+ private collections;
14
+ private nextId;
15
+ /** Pre-seed a collection with data */
16
+ seed(collection: string, records: Record[]): void;
17
+ list(path: string): Promise<string[]>;
18
+ read(path: string): Promise<unknown>;
19
+ write(path: string, data: unknown): Promise<{
20
+ id: string;
21
+ }>;
22
+ remove(path: string): Promise<void>;
23
+ search(path: string, pattern: string): Promise<unknown[]>;
24
+ private parsePath;
25
+ private getOrCreateCollection;
26
+ private getSchema;
27
+ }
28
+
29
+ export { MemoryBackend };
@@ -0,0 +1,10 @@
1
+ import {
2
+ HttpBackend,
3
+ MemoryBackend,
4
+ RpcBackend
5
+ } from "./chunk-7LIZT7L3.js";
6
+ export {
7
+ HttpBackend,
8
+ MemoryBackend,
9
+ RpcBackend
10
+ };
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@nkmc/agent-fs",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ },
14
+ "./testing": {
15
+ "types": "./dist/testing.d.ts",
16
+ "import": "./dist/testing.js",
17
+ "require": "./dist/testing.cjs"
18
+ }
19
+ },
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "dev": "tsup --watch"
23
+ },
24
+ "dependencies": {}
25
+ }
@@ -0,0 +1,151 @@
1
+ import type { AgentContext, FsCommand, FsResult, Mount } from "./types.js";
2
+ import { parseCommand } from "./parser.js";
3
+ import { MountResolver } from "./mount.js";
4
+ import { NotFoundError } from "./backends/memory.js";
5
+
6
+ export interface AgentFsOptions {
7
+ mounts: Mount[];
8
+ /** Called when a path doesn't match any static mount. Return true if you added a new mount. */
9
+ onMiss?: (path: string, addMount: (mount: Mount) => void, agent?: AgentContext) => Promise<boolean>;
10
+ /** Called on "ls /" to include dynamically-known domains. */
11
+ listDomains?: () => Promise<string[]>;
12
+ /** Called on "grep /" to search across registered services. */
13
+ searchDomains?: (query: string) => Promise<unknown[]>;
14
+ /** Called on "grep pattern /domain/" to search endpoint metadata within a single domain. */
15
+ searchEndpoints?: (domain: string, query: string) => Promise<unknown[]>;
16
+ }
17
+
18
+ /**
19
+ * The core engine: takes a raw command string, parses it,
20
+ * routes to the correct backend, and returns a result.
21
+ */
22
+ export class AgentFs {
23
+ private resolver: MountResolver;
24
+ private _listDomains?: () => Promise<string[]>;
25
+ private _searchDomains?: (query: string) => Promise<unknown[]>;
26
+ private _searchEndpoints?: (domain: string, query: string) => Promise<unknown[]>;
27
+
28
+ constructor(options: AgentFsOptions) {
29
+ this.resolver = new MountResolver();
30
+ for (const mount of options.mounts) {
31
+ this.resolver.add(mount);
32
+ }
33
+ if (options.onMiss) {
34
+ const addMount = (mount: Mount) => this.resolver.add(mount);
35
+ this.resolver.onMiss = (path, agent) => options.onMiss!(path, addMount, agent);
36
+ }
37
+ this._listDomains = options.listDomains;
38
+ this._searchDomains = options.searchDomains;
39
+ this._searchEndpoints = options.searchEndpoints;
40
+ }
41
+
42
+ /** Execute a raw command string like "ls /db/users/" */
43
+ async execute(input: string, roles: string[] = ["agent"], agent?: AgentContext): Promise<FsResult> {
44
+ // 1. Parse
45
+ const parsed = parseCommand(input);
46
+ if (!parsed.ok) return parsed;
47
+
48
+ const cmd = parsed.data as FsCommand;
49
+ return this.executeCommand(cmd, roles, agent);
50
+ }
51
+
52
+ /** Execute a pre-parsed FsCommand */
53
+ async executeCommand(
54
+ cmd: FsCommand,
55
+ roles: string[] = ["agent"],
56
+ agent?: AgentContext,
57
+ ): Promise<FsResult> {
58
+ // 2. Handle root listing
59
+ if (cmd.path === "/" && cmd.op === "ls") {
60
+ const staticEntries = this.resolver
61
+ .listMounts()
62
+ .map((p) => p.slice(1) + "/");
63
+ if (this._listDomains) {
64
+ const dynamicDomains = await this._listDomains();
65
+ const dynamicEntries = dynamicDomains.map((d) => d + "/");
66
+ const merged = [...new Set([...staticEntries, ...dynamicEntries])];
67
+ return { ok: true, data: merged };
68
+ }
69
+ return { ok: true, data: staticEntries };
70
+ }
71
+
72
+ // 2b. Handle root grep
73
+ if (cmd.path === "/" && cmd.op === "grep" && this._searchDomains) {
74
+ const results = await this._searchDomains(cmd.pattern!);
75
+ return { ok: true, data: results };
76
+ }
77
+
78
+ // 2c. Handle domain-level grep (e.g. "grep alerts /api.weather.gov/")
79
+ if (cmd.op === "grep" && this._searchEndpoints) {
80
+ const segments = cmd.path.split("/").filter(Boolean);
81
+ if (segments.length === 1) {
82
+ const domain = segments[0];
83
+ const results = await this._searchEndpoints(domain, cmd.pattern!);
84
+ return { ok: true, data: results };
85
+ }
86
+ }
87
+
88
+ // 3. Resolve mount (async to support lazy loading via onMiss)
89
+ const resolved = await this.resolver.resolveAsync(cmd.path, agent);
90
+ if (!resolved) {
91
+ return {
92
+ ok: false,
93
+ error: { code: "NO_MOUNT", message: `No mount for path: ${cmd.path}` },
94
+ };
95
+ }
96
+
97
+ // 4. Check permissions
98
+ const permType = cmd.op === "write" || cmd.op === "rm" ? "write" : "read";
99
+ const permError = this.resolver.checkPermission(
100
+ resolved.mount,
101
+ permType,
102
+ roles,
103
+ );
104
+ if (permError) {
105
+ return { ok: false, error: permError };
106
+ }
107
+
108
+ // 5. Execute on backend
109
+ const backend = resolved.mount.backend;
110
+ const relPath = resolved.relativePath;
111
+
112
+ try {
113
+ switch (cmd.op) {
114
+ case "ls": {
115
+ const entries = await backend.list(relPath);
116
+ return { ok: true, data: entries };
117
+ }
118
+ case "cat": {
119
+ const data = await backend.read(relPath);
120
+ return { ok: true, data };
121
+ }
122
+ case "write": {
123
+ const result = await backend.write(relPath, cmd.data);
124
+ return { ok: true, data: result };
125
+ }
126
+ case "rm": {
127
+ await backend.remove(relPath);
128
+ return { ok: true, data: { deleted: cmd.path } };
129
+ }
130
+ case "grep": {
131
+ const results = await backend.search(relPath, cmd.pattern!);
132
+ return { ok: true, data: results };
133
+ }
134
+ }
135
+ } catch (err) {
136
+ if (err instanceof NotFoundError) {
137
+ return {
138
+ ok: false,
139
+ error: { code: "NOT_FOUND", message: err.message },
140
+ };
141
+ }
142
+ return {
143
+ ok: false,
144
+ error: {
145
+ code: "BACKEND_ERROR",
146
+ message: err instanceof Error ? err.message : String(err),
147
+ },
148
+ };
149
+ }
150
+ }
151
+ }