toolcraft 0.0.5 → 0.0.7
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 +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +77 -59
- package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.d.ts +2 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.js +15 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.d.ts +2 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.js +13 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/codex.d.ts +2 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/codex.js +14 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/goose.d.ts +2 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/goose.js +14 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/index.d.ts +7 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/index.js +7 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/kimi.d.ts +2 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/kimi.js +15 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/opencode.d.ts +2 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/opencode.js +14 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/poe-agent.d.ts +2 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/poe-agent.js +13 -0
- package/node_modules/@poe-code/agent-defs/dist/index.d.ts +5 -0
- package/node_modules/@poe-code/agent-defs/dist/index.js +3 -0
- package/node_modules/@poe-code/agent-defs/dist/registry.d.ts +3 -0
- package/node_modules/@poe-code/agent-defs/dist/registry.js +26 -0
- package/node_modules/@poe-code/agent-defs/dist/specifier.d.ts +7 -0
- package/node_modules/@poe-code/agent-defs/dist/specifier.js +27 -0
- package/node_modules/@poe-code/agent-defs/dist/types.d.ts +16 -0
- package/node_modules/@poe-code/agent-defs/dist/types.js +1 -0
- package/node_modules/@poe-code/agent-defs/package.json +20 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.d.ts +5 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +552 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.d.ts +17 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/path-utils.js +58 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/run-mutations.d.ts +7 -0
- package/node_modules/@poe-code/config-mutations/dist/execution/run-mutations.js +46 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/index.d.ts +13 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/index.js +49 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/json.d.ts +31 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/json.js +140 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/toml.d.ts +2 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/toml.js +72 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/yaml.d.ts +2 -0
- package/node_modules/@poe-code/config-mutations/dist/formats/yaml.js +73 -0
- package/node_modules/@poe-code/config-mutations/dist/fs-utils.d.ts +18 -0
- package/node_modules/@poe-code/config-mutations/dist/fs-utils.js +45 -0
- package/node_modules/@poe-code/config-mutations/dist/index.d.ts +8 -0
- package/node_modules/@poe-code/config-mutations/dist/index.js +8 -0
- package/node_modules/@poe-code/config-mutations/dist/mutations/config-mutation.d.ts +47 -0
- package/node_modules/@poe-code/config-mutations/dist/mutations/config-mutation.js +34 -0
- package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.d.ts +52 -0
- package/node_modules/@poe-code/config-mutations/dist/mutations/file-mutation.js +46 -0
- package/node_modules/@poe-code/config-mutations/dist/mutations/template-mutation.d.ts +40 -0
- package/node_modules/@poe-code/config-mutations/dist/mutations/template-mutation.js +32 -0
- package/node_modules/@poe-code/config-mutations/dist/template/render.d.ts +7 -0
- package/node_modules/@poe-code/config-mutations/dist/template/render.js +28 -0
- package/node_modules/@poe-code/config-mutations/dist/testing/format-utils.d.ts +7 -0
- package/node_modules/@poe-code/config-mutations/dist/testing/format-utils.js +21 -0
- package/node_modules/@poe-code/config-mutations/dist/testing/index.d.ts +3 -0
- package/node_modules/@poe-code/config-mutations/dist/testing/index.js +2 -0
- package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.d.ts +25 -0
- package/node_modules/@poe-code/config-mutations/dist/testing/mock-fs.js +170 -0
- package/node_modules/@poe-code/config-mutations/dist/types.d.ts +156 -0
- package/node_modules/@poe-code/config-mutations/dist/types.js +6 -0
- package/node_modules/@poe-code/config-mutations/package.json +33 -0
- package/node_modules/@poe-code/file-lock/README.md +52 -0
- package/node_modules/@poe-code/file-lock/dist/index.d.ts +1 -0
- package/node_modules/@poe-code/file-lock/dist/index.js +1 -0
- package/node_modules/@poe-code/file-lock/dist/lock.d.ts +27 -0
- package/node_modules/@poe-code/file-lock/dist/lock.js +203 -0
- package/node_modules/@poe-code/file-lock/package.json +23 -0
- package/node_modules/auth-store/README.md +47 -0
- package/node_modules/auth-store/dist/create-secret-store.d.ts +2 -0
- package/node_modules/auth-store/dist/create-secret-store.js +35 -0
- package/node_modules/auth-store/dist/encrypted-file-store.d.ts +39 -0
- package/node_modules/auth-store/dist/encrypted-file-store.js +156 -0
- package/node_modules/auth-store/dist/index.d.ts +7 -0
- package/node_modules/auth-store/dist/index.js +4 -0
- package/node_modules/auth-store/dist/keychain-store.d.ts +22 -0
- package/node_modules/auth-store/dist/keychain-store.js +111 -0
- package/node_modules/auth-store/dist/provider-store.d.ts +10 -0
- package/node_modules/auth-store/dist/provider-store.js +28 -0
- package/node_modules/auth-store/dist/types.d.ts +20 -0
- package/node_modules/auth-store/dist/types.js +1 -0
- package/node_modules/auth-store/package.json +25 -0
- package/node_modules/mcp-oauth/README.md +31 -0
- package/node_modules/mcp-oauth/dist/client/auth-store-session-store.d.ts +14 -0
- package/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +97 -0
- package/node_modules/mcp-oauth/dist/client/authorization-state.d.ts +8 -0
- package/node_modules/mcp-oauth/dist/client/authorization-state.js +34 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.d.ts +3 -0
- package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +491 -0
- package/node_modules/mcp-oauth/dist/client/loopback-authorization.d.ts +20 -0
- package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +169 -0
- package/node_modules/mcp-oauth/dist/client/pkce.d.ts +2 -0
- package/node_modules/mcp-oauth/dist/client/pkce.js +7 -0
- package/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +40 -0
- package/node_modules/mcp-oauth/dist/client/token-endpoint.js +143 -0
- package/node_modules/mcp-oauth/dist/client/types.d.ts +113 -0
- package/node_modules/mcp-oauth/dist/client/types.js +1 -0
- package/node_modules/mcp-oauth/dist/index.d.ts +10 -0
- package/node_modules/mcp-oauth/dist/index.js +7 -0
- package/node_modules/mcp-oauth/dist/resource-indicator.d.ts +1 -0
- package/node_modules/mcp-oauth/dist/resource-indicator.js +11 -0
- package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.d.ts +27 -0
- package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +259 -0
- package/node_modules/mcp-oauth/dist/types.compile-check.d.ts +1 -0
- package/node_modules/mcp-oauth/dist/types.compile-check.js +22 -0
- package/node_modules/mcp-oauth/package.json +31 -0
- package/node_modules/tiny-mcp-client/.turbo/turbo-build.log +4 -0
- package/node_modules/tiny-mcp-client/dist/index.d.ts +2 -0
- package/node_modules/tiny-mcp-client/dist/index.js +1 -0
- package/node_modules/tiny-mcp-client/dist/internal.d.ts +547 -0
- package/node_modules/tiny-mcp-client/dist/internal.js +2404 -0
- package/node_modules/tiny-mcp-client/dist/jsonrpc-types.compile-check.d.ts +1 -0
- package/node_modules/tiny-mcp-client/dist/jsonrpc-types.compile-check.js +37 -0
- package/node_modules/tiny-mcp-client/dist/mcp-lifecycle-types.compile-check.d.ts +1 -0
- package/node_modules/tiny-mcp-client/dist/mcp-lifecycle-types.compile-check.js +50 -0
- package/node_modules/tiny-mcp-client/dist/mcp-prompt-types.compile-check.d.ts +1 -0
- package/node_modules/tiny-mcp-client/dist/mcp-prompt-types.compile-check.js +50 -0
- package/node_modules/tiny-mcp-client/dist/mcp-resource-types.compile-check.d.ts +1 -0
- package/node_modules/tiny-mcp-client/dist/mcp-resource-types.compile-check.js +51 -0
- package/node_modules/tiny-mcp-client/dist/mcp-tool-types.compile-check.d.ts +1 -0
- package/node_modules/tiny-mcp-client/dist/mcp-tool-types.compile-check.js +89 -0
- package/node_modules/tiny-mcp-client/dist/mcp-transport-types.compile-check.d.ts +1 -0
- package/node_modules/tiny-mcp-client/dist/mcp-transport-types.compile-check.js +56 -0
- package/node_modules/tiny-mcp-client/dist/mcp-utility-types.compile-check.d.ts +1 -0
- package/node_modules/tiny-mcp-client/dist/mcp-utility-types.compile-check.js +145 -0
- package/node_modules/tiny-mcp-client/dist/oauth-discovery.d.ts +24 -0
- package/node_modules/tiny-mcp-client/dist/oauth-discovery.js +385 -0
- package/node_modules/tiny-mcp-client/package.json +22 -0
- package/node_modules/tiny-mcp-client/src/http-oauth.integration.test.ts +823 -0
- package/node_modules/tiny-mcp-client/src/http-oauth.test.ts +882 -0
- package/node_modules/tiny-mcp-client/src/index.ts +94 -0
- package/node_modules/tiny-mcp-client/src/internal.ts +3566 -0
- package/node_modules/tiny-mcp-client/src/jsonrpc-types.compile-check.ts +66 -0
- package/node_modules/tiny-mcp-client/src/mcp-client-http-transport.integration.test.ts +222 -0
- package/node_modules/tiny-mcp-client/src/mcp-client-sdk.test.ts +1294 -0
- package/node_modules/tiny-mcp-client/src/mcp-client-tiny-stdio-test-server-tools.test.ts +143 -0
- package/node_modules/tiny-mcp-client/src/mcp-lifecycle-types.compile-check.ts +65 -0
- package/node_modules/tiny-mcp-client/src/mcp-prompt-types.compile-check.ts +66 -0
- package/node_modules/tiny-mcp-client/src/mcp-resource-types.compile-check.ts +70 -0
- package/node_modules/tiny-mcp-client/src/mcp-tool-types.compile-check.ts +117 -0
- package/node_modules/tiny-mcp-client/src/mcp-transport-types.compile-check.ts +75 -0
- package/node_modules/tiny-mcp-client/src/mcp-utility-types.compile-check.ts +181 -0
- package/node_modules/tiny-mcp-client/src/mock-servers.test.ts +980 -0
- package/node_modules/tiny-mcp-client/src/oauth-discovery.ts +583 -0
- package/node_modules/tiny-mcp-client/src/transports.test.ts +8139 -0
- package/node_modules/tiny-mcp-client/src/utilities.test.ts +372 -0
- package/node_modules/tiny-mcp-client/tsconfig.json +11 -0
- package/package.json +24 -11
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
function write(options) {
|
|
2
|
+
return {
|
|
3
|
+
kind: "templateWrite",
|
|
4
|
+
target: options.target,
|
|
5
|
+
templateId: options.templateId,
|
|
6
|
+
context: options.context,
|
|
7
|
+
label: options.label
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function mergeToml(options) {
|
|
11
|
+
return {
|
|
12
|
+
kind: "templateMergeToml",
|
|
13
|
+
target: options.target,
|
|
14
|
+
templateId: options.templateId,
|
|
15
|
+
context: options.context,
|
|
16
|
+
label: options.label
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function mergeJson(options) {
|
|
20
|
+
return {
|
|
21
|
+
kind: "templateMergeJson",
|
|
22
|
+
target: options.target,
|
|
23
|
+
templateId: options.templateId,
|
|
24
|
+
context: options.context,
|
|
25
|
+
label: options.label
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export const templateMutation = {
|
|
29
|
+
write,
|
|
30
|
+
mergeToml,
|
|
31
|
+
mergeJson
|
|
32
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type TemplateVariables = Record<string, string | number | boolean | string[]>;
|
|
2
|
+
/**
|
|
3
|
+
* Render a mustache template with the given variables.
|
|
4
|
+
* Arrays are automatically joined with newlines.
|
|
5
|
+
* HTML escaping is disabled.
|
|
6
|
+
*/
|
|
7
|
+
export declare function renderTemplate(template: string, variables: TemplateVariables): string;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import Mustache from "mustache";
|
|
2
|
+
// Disable HTML escaping - we're rendering prompts, not HTML
|
|
3
|
+
const originalEscape = Mustache.escape;
|
|
4
|
+
/**
|
|
5
|
+
* Render a mustache template with the given variables.
|
|
6
|
+
* Arrays are automatically joined with newlines.
|
|
7
|
+
* HTML escaping is disabled.
|
|
8
|
+
*/
|
|
9
|
+
export function renderTemplate(template, variables) {
|
|
10
|
+
// Pre-process variables to handle arrays
|
|
11
|
+
const processed = {};
|
|
12
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
13
|
+
if (Array.isArray(value)) {
|
|
14
|
+
processed[key] = value.join("\n");
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
processed[key] = value;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// Temporarily disable HTML escaping
|
|
21
|
+
Mustache.escape = (text) => text;
|
|
22
|
+
try {
|
|
23
|
+
return Mustache.render(template, processed);
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
Mustache.escape = originalEscape;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ConfigObject } from "../types.js";
|
|
2
|
+
export declare function parseToml(content: string): ConfigObject;
|
|
3
|
+
export declare function serializeToml(obj: ConfigObject): string;
|
|
4
|
+
export declare function parseJson(content: string): ConfigObject;
|
|
5
|
+
export declare function serializeJson(obj: ConfigObject): string;
|
|
6
|
+
export declare function parseYaml(content: string): ConfigObject;
|
|
7
|
+
export declare function serializeYaml(obj: ConfigObject): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsonFormat } from "../formats/json.js";
|
|
2
|
+
import { tomlFormat } from "../formats/toml.js";
|
|
3
|
+
import { yamlFormat } from "../formats/yaml.js";
|
|
4
|
+
export function parseToml(content) {
|
|
5
|
+
return tomlFormat.parse(content);
|
|
6
|
+
}
|
|
7
|
+
export function serializeToml(obj) {
|
|
8
|
+
return tomlFormat.serialize(obj);
|
|
9
|
+
}
|
|
10
|
+
export function parseJson(content) {
|
|
11
|
+
return jsonFormat.parse(content);
|
|
12
|
+
}
|
|
13
|
+
export function serializeJson(obj) {
|
|
14
|
+
return jsonFormat.serialize(obj);
|
|
15
|
+
}
|
|
16
|
+
export function parseYaml(content) {
|
|
17
|
+
return yamlFormat.parse(content);
|
|
18
|
+
}
|
|
19
|
+
export function serializeYaml(obj) {
|
|
20
|
+
return yamlFormat.serialize(obj);
|
|
21
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { FileSystem } from "../types.js";
|
|
2
|
+
export interface MockFileSystem extends FileSystem {
|
|
3
|
+
/** Current file contents, keyed by absolute path */
|
|
4
|
+
files: Record<string, string>;
|
|
5
|
+
/** Created directories */
|
|
6
|
+
directories: Set<string>;
|
|
7
|
+
/** Check if a path exists (file or directory) */
|
|
8
|
+
exists(path: string): boolean;
|
|
9
|
+
/** Get file content or undefined if not found */
|
|
10
|
+
getContent(path: string): string | undefined;
|
|
11
|
+
/** Read file with encoding overloads for compatibility */
|
|
12
|
+
readFile(path: string, encoding: BufferEncoding): Promise<string>;
|
|
13
|
+
readFile(path: string): Promise<Buffer>;
|
|
14
|
+
}
|
|
15
|
+
export interface MockFsOptions {
|
|
16
|
+
/** Initial files - paths can use ~ which will be expanded to homeDir */
|
|
17
|
+
[path: string]: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create an in-memory mock filesystem for testing mutations.
|
|
21
|
+
*
|
|
22
|
+
* @param initialFiles - Initial files to populate the filesystem with
|
|
23
|
+
* @param homeDir - Home directory for ~ expansion (defaults to /home/test)
|
|
24
|
+
*/
|
|
25
|
+
export declare function createMockFs(initialFiles?: MockFsOptions, homeDir?: string): MockFileSystem;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
const DEFAULT_HOME_DIR = "/home/test";
|
|
3
|
+
/**
|
|
4
|
+
* Create an in-memory mock filesystem for testing mutations.
|
|
5
|
+
*
|
|
6
|
+
* @param initialFiles - Initial files to populate the filesystem with
|
|
7
|
+
* @param homeDir - Home directory for ~ expansion (defaults to /home/test)
|
|
8
|
+
*/
|
|
9
|
+
export function createMockFs(initialFiles, homeDir = DEFAULT_HOME_DIR) {
|
|
10
|
+
const files = {};
|
|
11
|
+
const directories = new Set();
|
|
12
|
+
// Initialize with provided files
|
|
13
|
+
if (initialFiles) {
|
|
14
|
+
for (const [filePath, content] of Object.entries(initialFiles)) {
|
|
15
|
+
const absolutePath = expandPath(filePath, homeDir);
|
|
16
|
+
files[absolutePath] = content;
|
|
17
|
+
// Ensure parent directories exist
|
|
18
|
+
const parentDir = path.dirname(absolutePath);
|
|
19
|
+
addDirectoryTree(parentDir, directories);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Ensure home directory exists
|
|
23
|
+
addDirectoryTree(homeDir, directories);
|
|
24
|
+
const mockFs = {
|
|
25
|
+
files,
|
|
26
|
+
directories,
|
|
27
|
+
exists(filePath) {
|
|
28
|
+
const absolutePath = expandPath(filePath, homeDir);
|
|
29
|
+
return absolutePath in files || directories.has(absolutePath);
|
|
30
|
+
},
|
|
31
|
+
getContent(filePath) {
|
|
32
|
+
const absolutePath = expandPath(filePath, homeDir);
|
|
33
|
+
return files[absolutePath];
|
|
34
|
+
},
|
|
35
|
+
async readFile(filePath, encoding) {
|
|
36
|
+
const absolutePath = expandPath(filePath, homeDir);
|
|
37
|
+
if (!(absolutePath in files)) {
|
|
38
|
+
const error = new Error(`ENOENT: no such file or directory, open '${absolutePath}'`);
|
|
39
|
+
error.code = "ENOENT";
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
const content = files[absolutePath];
|
|
43
|
+
if (encoding) {
|
|
44
|
+
return content;
|
|
45
|
+
}
|
|
46
|
+
return Buffer.from(content, "utf8");
|
|
47
|
+
},
|
|
48
|
+
async writeFile(filePath, content, options) {
|
|
49
|
+
void options; // TypeScript satisfaction
|
|
50
|
+
const absolutePath = expandPath(filePath, homeDir);
|
|
51
|
+
// Ensure parent directory exists
|
|
52
|
+
const parentDir = path.dirname(absolutePath);
|
|
53
|
+
if (!directories.has(parentDir)) {
|
|
54
|
+
const error = new Error(`ENOENT: no such file or directory, open '${absolutePath}'`);
|
|
55
|
+
error.code = "ENOENT";
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
if (typeof content === "string") {
|
|
59
|
+
files[absolutePath] = content;
|
|
60
|
+
}
|
|
61
|
+
else if (Buffer.isBuffer(content)) {
|
|
62
|
+
files[absolutePath] = content.toString("utf8");
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
files[absolutePath] = Buffer.from(content.buffer, content.byteOffset, content.byteLength).toString("utf8");
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
async mkdir(dirPath, options) {
|
|
69
|
+
const absolutePath = expandPath(dirPath, homeDir);
|
|
70
|
+
if (options?.recursive) {
|
|
71
|
+
addDirectoryTree(absolutePath, directories);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Check parent exists
|
|
75
|
+
const parentDir = path.dirname(absolutePath);
|
|
76
|
+
if (parentDir !== absolutePath && !directories.has(parentDir)) {
|
|
77
|
+
const error = new Error(`ENOENT: no such file or directory, mkdir '${absolutePath}'`);
|
|
78
|
+
error.code = "ENOENT";
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
directories.add(absolutePath);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
async unlink(filePath) {
|
|
85
|
+
const absolutePath = expandPath(filePath, homeDir);
|
|
86
|
+
if (!(absolutePath in files)) {
|
|
87
|
+
const error = new Error(`ENOENT: no such file or directory, unlink '${absolutePath}'`);
|
|
88
|
+
error.code = "ENOENT";
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
delete files[absolutePath];
|
|
92
|
+
},
|
|
93
|
+
async stat(filePath) {
|
|
94
|
+
const absolutePath = expandPath(filePath, homeDir);
|
|
95
|
+
if (absolutePath in files) {
|
|
96
|
+
return { mode: 0o644 };
|
|
97
|
+
}
|
|
98
|
+
if (directories.has(absolutePath)) {
|
|
99
|
+
return { mode: 0o755 };
|
|
100
|
+
}
|
|
101
|
+
const error = new Error(`ENOENT: no such file or directory, stat '${absolutePath}'`);
|
|
102
|
+
error.code = "ENOENT";
|
|
103
|
+
throw error;
|
|
104
|
+
},
|
|
105
|
+
async readdir(dirPath) {
|
|
106
|
+
const absolutePath = expandPath(dirPath, homeDir);
|
|
107
|
+
if (absolutePath in files) {
|
|
108
|
+
const error = new Error(`ENOTDIR: not a directory, scandir '${absolutePath}'`);
|
|
109
|
+
error.code = "ENOTDIR";
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
if (!directories.has(absolutePath)) {
|
|
113
|
+
const error = new Error(`ENOENT: no such file or directory, scandir '${absolutePath}'`);
|
|
114
|
+
error.code = "ENOENT";
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
const entries = new Set();
|
|
118
|
+
for (const filePath of Object.keys(files)) {
|
|
119
|
+
if (path.dirname(filePath) === absolutePath) {
|
|
120
|
+
entries.add(path.basename(filePath));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
for (const dir of directories) {
|
|
124
|
+
if (dir !== absolutePath && path.dirname(dir) === absolutePath) {
|
|
125
|
+
entries.add(path.basename(dir));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return Array.from(entries);
|
|
129
|
+
},
|
|
130
|
+
async chmod(filePath, mode) {
|
|
131
|
+
void mode; // In mock fs, we don't actually store mode changes
|
|
132
|
+
const absolutePath = expandPath(filePath, homeDir);
|
|
133
|
+
if (!(absolutePath in files) && !directories.has(absolutePath)) {
|
|
134
|
+
const error = new Error(`ENOENT: no such file or directory, chmod '${absolutePath}'`);
|
|
135
|
+
error.code = "ENOENT";
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
// Mode change is a no-op in mock fs but we don't throw
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
return mockFs;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Expand ~ to homeDir in a path.
|
|
145
|
+
*/
|
|
146
|
+
function expandPath(inputPath, homeDir) {
|
|
147
|
+
if (inputPath.startsWith("~/")) {
|
|
148
|
+
return path.join(homeDir, inputPath.slice(2));
|
|
149
|
+
}
|
|
150
|
+
if (inputPath === "~") {
|
|
151
|
+
return homeDir;
|
|
152
|
+
}
|
|
153
|
+
if (inputPath.startsWith("~")) {
|
|
154
|
+
// ~something (not ~/) - treat as relative path from home
|
|
155
|
+
return path.join(homeDir, inputPath.slice(1));
|
|
156
|
+
}
|
|
157
|
+
return inputPath;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Add a directory and all its parent directories to the set.
|
|
161
|
+
*/
|
|
162
|
+
function addDirectoryTree(dirPath, directories) {
|
|
163
|
+
const parts = dirPath.split(path.sep).filter(Boolean);
|
|
164
|
+
let current = "/";
|
|
165
|
+
directories.add(current);
|
|
166
|
+
for (const part of parts) {
|
|
167
|
+
current = path.join(current, part);
|
|
168
|
+
directories.add(current);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
export type ConfigPrimitive = string | number | boolean | null;
|
|
2
|
+
export type ConfigValue = ConfigPrimitive | ConfigObject | ConfigArray | Date;
|
|
3
|
+
export interface ConfigObject {
|
|
4
|
+
[key: string]: ConfigValue;
|
|
5
|
+
}
|
|
6
|
+
export type ConfigArray = ConfigValue[];
|
|
7
|
+
export declare function isConfigObject(value: unknown): value is ConfigObject;
|
|
8
|
+
export interface FileSystem {
|
|
9
|
+
readFile(path: string, encoding: "utf8"): Promise<string>;
|
|
10
|
+
writeFile(path: string, content: string, options?: {
|
|
11
|
+
encoding: "utf8";
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
mkdir(path: string, options?: {
|
|
14
|
+
recursive: boolean;
|
|
15
|
+
}): Promise<void>;
|
|
16
|
+
unlink(path: string): Promise<void>;
|
|
17
|
+
rm?(path: string, options?: {
|
|
18
|
+
recursive?: boolean;
|
|
19
|
+
force?: boolean;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
stat(path: string): Promise<{
|
|
22
|
+
mode?: number;
|
|
23
|
+
}>;
|
|
24
|
+
readdir(path: string): Promise<string[]>;
|
|
25
|
+
chmod?(path: string, mode: number): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export type TemplateLoader = (templateId: string) => Promise<string>;
|
|
28
|
+
export interface ConfigFormat {
|
|
29
|
+
/** Parse string content into object */
|
|
30
|
+
parse(content: string): ConfigObject;
|
|
31
|
+
/** Serialize object to string (with consistent formatting) */
|
|
32
|
+
serialize(obj: ConfigObject): string;
|
|
33
|
+
/** Deep merge patch into base, returning new object */
|
|
34
|
+
merge(base: ConfigObject, patch: ConfigObject): ConfigObject;
|
|
35
|
+
/** Remove keys matching shape from object, returning new object */
|
|
36
|
+
prune(obj: ConfigObject, shape: ConfigObject): {
|
|
37
|
+
changed: boolean;
|
|
38
|
+
result: ConfigObject;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export interface PathMapper {
|
|
42
|
+
/** Map a target directory to a different location (e.g., for isolated configs) */
|
|
43
|
+
mapTargetDirectory(input: {
|
|
44
|
+
targetDirectory: string;
|
|
45
|
+
}): string;
|
|
46
|
+
}
|
|
47
|
+
export interface MutationContext {
|
|
48
|
+
/** Filesystem interface - required */
|
|
49
|
+
fs: FileSystem;
|
|
50
|
+
/** Home directory for ~ expansion - required */
|
|
51
|
+
homeDir: string;
|
|
52
|
+
/** Optional dry-run mode */
|
|
53
|
+
dryRun?: boolean;
|
|
54
|
+
/** Optional observers for logging */
|
|
55
|
+
observers?: MutationObservers;
|
|
56
|
+
/** Required for template mutations */
|
|
57
|
+
templates?: TemplateLoader;
|
|
58
|
+
/** Optional path mapper for redirecting paths (used for isolated configs) */
|
|
59
|
+
pathMapper?: PathMapper;
|
|
60
|
+
}
|
|
61
|
+
export interface MutationOptions {
|
|
62
|
+
[key: string]: unknown;
|
|
63
|
+
}
|
|
64
|
+
export type ValueResolver<T> = T | ((ctx: MutationOptions) => T);
|
|
65
|
+
interface BaseMutation {
|
|
66
|
+
/** Human-readable label for logging */
|
|
67
|
+
label?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface ConfigMergeMutation extends BaseMutation {
|
|
70
|
+
kind: "configMerge";
|
|
71
|
+
target: ValueResolver<string>;
|
|
72
|
+
value: ValueResolver<ConfigObject>;
|
|
73
|
+
format?: "json" | "toml" | "yaml";
|
|
74
|
+
pruneByPrefix?: Record<string, string>;
|
|
75
|
+
}
|
|
76
|
+
export interface ConfigPruneMutation extends BaseMutation {
|
|
77
|
+
kind: "configPrune";
|
|
78
|
+
target: ValueResolver<string>;
|
|
79
|
+
shape: ValueResolver<ConfigObject>;
|
|
80
|
+
format?: "json" | "toml" | "yaml";
|
|
81
|
+
onlyIf?: (doc: ConfigObject, ctx: MutationOptions) => boolean;
|
|
82
|
+
}
|
|
83
|
+
export interface ConfigTransformMutation extends BaseMutation {
|
|
84
|
+
kind: "configTransform";
|
|
85
|
+
target: ValueResolver<string>;
|
|
86
|
+
format?: "json" | "toml" | "yaml";
|
|
87
|
+
transform: (content: ConfigObject, ctx: MutationOptions) => {
|
|
88
|
+
content: ConfigObject | null;
|
|
89
|
+
changed: boolean;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export interface EnsureDirectoryMutation extends BaseMutation {
|
|
93
|
+
kind: "ensureDirectory";
|
|
94
|
+
path: ValueResolver<string>;
|
|
95
|
+
}
|
|
96
|
+
export interface RemoveDirectoryMutation extends BaseMutation {
|
|
97
|
+
kind: "removeDirectory";
|
|
98
|
+
path: ValueResolver<string>;
|
|
99
|
+
force?: boolean;
|
|
100
|
+
}
|
|
101
|
+
export interface RemoveFileMutation extends BaseMutation {
|
|
102
|
+
kind: "removeFile";
|
|
103
|
+
target: ValueResolver<string>;
|
|
104
|
+
whenEmpty?: boolean;
|
|
105
|
+
whenContentMatches?: RegExp;
|
|
106
|
+
}
|
|
107
|
+
export interface ChmodMutation extends BaseMutation {
|
|
108
|
+
kind: "chmod";
|
|
109
|
+
target: ValueResolver<string>;
|
|
110
|
+
mode: number;
|
|
111
|
+
}
|
|
112
|
+
export interface BackupMutation extends BaseMutation {
|
|
113
|
+
kind: "backup";
|
|
114
|
+
target: ValueResolver<string>;
|
|
115
|
+
}
|
|
116
|
+
export interface TemplateWriteMutation extends BaseMutation {
|
|
117
|
+
kind: "templateWrite";
|
|
118
|
+
target: ValueResolver<string>;
|
|
119
|
+
templateId: string;
|
|
120
|
+
context?: ValueResolver<ConfigObject>;
|
|
121
|
+
}
|
|
122
|
+
export interface TemplateMergeTomlMutation extends BaseMutation {
|
|
123
|
+
kind: "templateMergeToml";
|
|
124
|
+
target: ValueResolver<string>;
|
|
125
|
+
templateId: string;
|
|
126
|
+
context?: ValueResolver<ConfigObject>;
|
|
127
|
+
}
|
|
128
|
+
export interface TemplateMergeJsonMutation extends BaseMutation {
|
|
129
|
+
kind: "templateMergeJson";
|
|
130
|
+
target: ValueResolver<string>;
|
|
131
|
+
templateId: string;
|
|
132
|
+
context?: ValueResolver<ConfigObject>;
|
|
133
|
+
}
|
|
134
|
+
export type Mutation = ConfigMergeMutation | ConfigPruneMutation | ConfigTransformMutation | EnsureDirectoryMutation | RemoveDirectoryMutation | RemoveFileMutation | ChmodMutation | BackupMutation | TemplateWriteMutation | TemplateMergeTomlMutation | TemplateMergeJsonMutation;
|
|
135
|
+
export type MutationEffect = "none" | "mkdir" | "write" | "delete" | "chmod" | "copy";
|
|
136
|
+
export type MutationDetail = "create" | "update" | "delete" | "noop" | "backup";
|
|
137
|
+
export interface MutationOutcome {
|
|
138
|
+
changed: boolean;
|
|
139
|
+
effect: MutationEffect;
|
|
140
|
+
detail?: MutationDetail;
|
|
141
|
+
}
|
|
142
|
+
export interface MutationDetails {
|
|
143
|
+
kind: string;
|
|
144
|
+
label: string;
|
|
145
|
+
targetPath?: string;
|
|
146
|
+
}
|
|
147
|
+
export interface MutationObservers {
|
|
148
|
+
onStart?(details: MutationDetails): void;
|
|
149
|
+
onComplete?(details: MutationDetails, outcome: MutationOutcome): void;
|
|
150
|
+
onError?(details: MutationDetails, error: unknown): void;
|
|
151
|
+
}
|
|
152
|
+
export interface MutationResult {
|
|
153
|
+
changed: boolean;
|
|
154
|
+
effects: MutationOutcome[];
|
|
155
|
+
}
|
|
156
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Config Object Types
|
|
3
|
+
// ============================================================================
|
|
4
|
+
export function isConfigObject(value) {
|
|
5
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@poe-code/config-mutations",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./testing": {
|
|
14
|
+
"types": "./dist/testing/index.d.ts",
|
|
15
|
+
"import": "./dist/testing/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"jsonc-parser": "^3.3.1",
|
|
26
|
+
"mustache": "^4.2.0",
|
|
27
|
+
"smol-toml": "^1.3.0",
|
|
28
|
+
"yaml": "^2.8.1"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/mustache": "^4.2.6"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# @poe-code/file-lock
|
|
2
|
+
|
|
3
|
+
Atomic file locking built on lockfile creation with `open(..., "wx")` and timestamped stale cleanup.
|
|
4
|
+
|
|
5
|
+
It creates a sibling lockfile at `<filePath>.lock`, retries with bounded backoff when the lock is already held, and automatically reclaims stale locks. Locks from the current host are reclaimed immediately when their recorded process is no longer running; other locks fall back to the lockfile timestamp and `staleMs`.
|
|
6
|
+
|
|
7
|
+
## Public API
|
|
8
|
+
|
|
9
|
+
### `acquireFileLock(filePath, options?)`
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
function acquireFileLock(
|
|
13
|
+
filePath: string,
|
|
14
|
+
options?: FileLockOptions
|
|
15
|
+
): Promise<ReleaseLock>;
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Attempts to acquire an exclusive lock for `filePath` and resolves to an async release function. If the lock already exists, acquisition retries until it succeeds, the lock is considered stale and cleaned up, the provided abort signal fires, or retries are exhausted. When retries are exhausted, it throws `LockTimeoutError`.
|
|
19
|
+
|
|
20
|
+
## Options
|
|
21
|
+
|
|
22
|
+
| Option | Type | Default | Behavior |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| `staleMs` | `number` | `1_000` | Lockfile age in milliseconds after which an existing lock is treated as stale and removed when live local PID metadata does not prove the owner is still running. |
|
|
25
|
+
| `retries` | `number` | `20` | Number of retry attempts after the initial acquire attempt. |
|
|
26
|
+
| `minTimeout` | `number` | `25` | Minimum retry backoff in milliseconds. |
|
|
27
|
+
| `maxTimeout` | `number` | `250` | Maximum retry backoff in milliseconds. |
|
|
28
|
+
| `isPidRunning` | `(pid: number) => boolean` | `process.kill(pid, 0)` check | Injectable process liveness check used to reclaim dead local lock owners. |
|
|
29
|
+
| `signal` | `AbortSignal` | none | Cancels acquisition while waiting between retries and rejects with an abort error. |
|
|
30
|
+
| `fs` | `FileLockFs` | `node:fs/promises` adapter | Injectable filesystem used for lock creation, metadata reading, inspection, and deletion. |
|
|
31
|
+
|
|
32
|
+
## Env vars
|
|
33
|
+
|
|
34
|
+
None.
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { acquireFileLock } from "@poe-code/file-lock";
|
|
40
|
+
|
|
41
|
+
const release = await acquireFileLock("/repo/workflow.md");
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
// Do work while holding the lock.
|
|
45
|
+
} finally {
|
|
46
|
+
await release();
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Notes
|
|
51
|
+
|
|
52
|
+
`@poe-code/agent-harness-tools` re-exports `acquireFileLock` as `lockWorkflow`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { acquireFileLock, LockTimeoutError, type FileLockFs, type FileLockOptions, type ReleaseLock } from "./lock.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { acquireFileLock, LockTimeoutError } from "./lock.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface FileLockFs {
|
|
2
|
+
open(path: string, flags: string): Promise<{
|
|
3
|
+
close(): Promise<void>;
|
|
4
|
+
writeFile(data: string, options?: BufferEncoding | {
|
|
5
|
+
encoding?: BufferEncoding;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
}>;
|
|
8
|
+
stat(path: string): Promise<{
|
|
9
|
+
mtimeMs: number;
|
|
10
|
+
}>;
|
|
11
|
+
readFile?(path: string, encoding: BufferEncoding): Promise<string>;
|
|
12
|
+
unlink(path: string): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export interface FileLockOptions {
|
|
15
|
+
staleMs?: number;
|
|
16
|
+
retries?: number;
|
|
17
|
+
minTimeout?: number;
|
|
18
|
+
maxTimeout?: number;
|
|
19
|
+
fs?: FileLockFs;
|
|
20
|
+
isPidRunning?: (pid: number) => boolean;
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
}
|
|
23
|
+
export type ReleaseLock = () => Promise<void>;
|
|
24
|
+
export declare class LockTimeoutError extends Error {
|
|
25
|
+
constructor(message: string);
|
|
26
|
+
}
|
|
27
|
+
export declare function acquireFileLock(filePath: string, options?: FileLockOptions): Promise<ReleaseLock>;
|