@amalgm/tools 0.1.3 → 0.1.5
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/PURPOSE.md +5 -7
- package/dist/artifact-files.d.ts +0 -2
- package/dist/artifact-files.js +0 -7
- package/dist/artifacts.d.ts +1 -3
- package/dist/artifacts.js +1 -14
- package/dist/toolbox.js +8 -7
- package/package.json +4 -4
package/PURPOSE.md
CHANGED
|
@@ -33,6 +33,9 @@ registry or execution implementation.
|
|
|
33
33
|
|
|
34
34
|
## Axioms
|
|
35
35
|
|
|
36
|
+
Managed releases bind to one exact published Core version and build from the
|
|
37
|
+
locked dependency graph on the supported Node toolchain.
|
|
38
|
+
|
|
36
39
|
1. Every action belongs to exactly one tool and has the canonical id
|
|
37
40
|
`<tool-id>.<action-name>`.
|
|
38
41
|
2. Tool ids are stable, globally unique within one Toolbox, and never change
|
|
@@ -81,16 +84,11 @@ registry or execution implementation.
|
|
|
81
84
|
20. Notification channels are host capabilities. Tools owns validation and
|
|
82
85
|
result semantics; an injected channel adapter owns formatting, recipient
|
|
83
86
|
lookup, credentials, and delivery.
|
|
84
|
-
21. Reopening a Toolbox preserves the temporal fields of every semantically
|
|
85
|
-
unchanged system projection already present in its portable index; boot
|
|
86
|
-
never rewrites tracked bytes merely to stamp the current machine's time.
|
|
87
87
|
|
|
88
88
|
## Predictable behavior
|
|
89
89
|
|
|
90
|
-
Applying the same definition twice produces the same catalog.
|
|
91
|
-
|
|
92
|
-
machine. Applying a changed action set replaces the old set atomically.
|
|
93
|
-
Selecting a whole tool
|
|
90
|
+
Applying the same definition twice produces the same catalog. Applying a
|
|
91
|
+
changed action set replaces the old set atomically. Selecting a whole tool
|
|
94
92
|
grants all of its enabled actions; selecting one action grants only that
|
|
95
93
|
action. Disabling or deleting a tool immediately removes all of its actions
|
|
96
94
|
from list and call surfaces. CLI arguments are passed directly to a process,
|
package/dist/artifact-files.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ToolboxIndexDocument } from './artifacts.js';
|
|
2
1
|
import type { ActionRecord, Catalog, ToolRecord } from './types.js';
|
|
3
2
|
declare class ArtifactFiles {
|
|
4
3
|
private readonly directory;
|
|
@@ -10,7 +9,6 @@ declare class ArtifactFiles {
|
|
|
10
9
|
}): string;
|
|
11
10
|
removeTool(toolId: string): void;
|
|
12
11
|
writeIndex(catalog: Catalog): void;
|
|
13
|
-
readIndex(): ToolboxIndexDocument | null;
|
|
14
12
|
/** Give every persisted user tool its portable artifact and refresh the
|
|
15
13
|
* index. Additive only: a file for a tool this catalog does not know is
|
|
16
14
|
* never deleted here — removal is an explicit mutation. */
|
package/dist/artifact-files.js
CHANGED
|
@@ -51,13 +51,6 @@ class ArtifactFiles {
|
|
|
51
51
|
writeIndex(catalog) {
|
|
52
52
|
writeJsonIfChanged(this.file(TOOLBOX_INDEX_FILE_NAME), catalogIndexDocument(catalog));
|
|
53
53
|
}
|
|
54
|
-
readIndex() {
|
|
55
|
-
const value = readJson(this.file(TOOLBOX_INDEX_FILE_NAME));
|
|
56
|
-
if (!value || value.version !== 1 || !value.tools || typeof value.tools !== 'object'
|
|
57
|
-
|| !value.toolActions || typeof value.toolActions !== 'object')
|
|
58
|
-
return null;
|
|
59
|
-
return value;
|
|
60
|
-
}
|
|
61
54
|
/** Give every persisted user tool its portable artifact and refresh the
|
|
62
55
|
* index. Additive only: a file for a tool this catalog does not know is
|
|
63
56
|
* never deleted here — removal is an explicit mutation. */
|
package/dist/artifacts.d.ts
CHANGED
|
@@ -57,12 +57,10 @@ declare function artifactDocument(input: {
|
|
|
57
57
|
declare function isToolArtifactDocument(value: unknown): value is ToolArtifactDocument;
|
|
58
58
|
declare function toolboxIndexDocument(tools: Record<string, unknown>, toolActions: Record<string, unknown>): ToolboxIndexDocument;
|
|
59
59
|
declare function catalogIndexDocument(catalog: Pick<Catalog, 'tools' | 'actions'>): ToolboxIndexDocument;
|
|
60
|
-
declare function portableRecordSemantic(value: ToolRecord | ActionRecord): string;
|
|
61
|
-
declare function preserveProjectionTime<T extends ToolRecord | ActionRecord>(candidate: T, previous: unknown): T;
|
|
62
60
|
/** The migration of one legacy aggregate `toolbox.json` catalog, as a pure
|
|
63
61
|
* plan: every user tool becomes a per-tool artifact write, the whole legacy
|
|
64
62
|
* catalog becomes the index, and the aggregate file is renamed out of the
|
|
65
63
|
* way only after each record has a per-tool replacement. */
|
|
66
64
|
declare function legacyMigrationPlan(legacy: unknown): LegacyMigrationPlan;
|
|
67
|
-
export { LEGACY_TOOLBOX_BACKUP_FILE_NAME, LEGACY_TOOLBOX_FILE_NAME, TOOL_ARTIFACT_KIND, TOOL_ARTIFACT_SCHEMA_VERSION, TOOLBOX_INDEX_FILE_NAME, artifactDocument, catalogIndexDocument, isToolArtifactDocument, legacyMigrationPlan, safeToolId,
|
|
65
|
+
export { LEGACY_TOOLBOX_BACKUP_FILE_NAME, LEGACY_TOOLBOX_FILE_NAME, TOOL_ARTIFACT_KIND, TOOL_ARTIFACT_SCHEMA_VERSION, TOOLBOX_INDEX_FILE_NAME, artifactDocument, catalogIndexDocument, isToolArtifactDocument, legacyMigrationPlan, safeToolId, toolboxIndexDocument, userArtifactFileName, };
|
|
68
66
|
export type { ArtifactFileWrite, LegacyMigrationPlan, ToolArtifactDocument, ToolboxIndexDocument };
|
package/dist/artifacts.js
CHANGED
|
@@ -81,19 +81,6 @@ function toolboxIndexDocument(tools, toolActions) {
|
|
|
81
81
|
function catalogIndexDocument(catalog) {
|
|
82
82
|
return toolboxIndexDocument(Object.fromEntries(catalog.tools.map((tool) => [tool.id, tool])), Object.fromEntries(catalog.actions.map((action) => [action.id, action])));
|
|
83
83
|
}
|
|
84
|
-
function portableRecordSemantic(value) {
|
|
85
|
-
const { createdAt, updatedAt, ...portable } = value;
|
|
86
|
-
return JSON.stringify(portable);
|
|
87
|
-
}
|
|
88
|
-
function preserveProjectionTime(candidate, previous) {
|
|
89
|
-
if (!previous || typeof previous !== 'object')
|
|
90
|
-
return candidate;
|
|
91
|
-
const record = previous;
|
|
92
|
-
if (typeof record.createdAt !== 'string' || typeof record.updatedAt !== 'string'
|
|
93
|
-
|| portableRecordSemantic(record) !== portableRecordSemantic(candidate))
|
|
94
|
-
return candidate;
|
|
95
|
-
return { ...candidate, createdAt: record.createdAt, updatedAt: record.updatedAt };
|
|
96
|
-
}
|
|
97
84
|
/** The migration of one legacy aggregate `toolbox.json` catalog, as a pure
|
|
98
85
|
* plan: every user tool becomes a per-tool artifact write, the whole legacy
|
|
99
86
|
* catalog becomes the index, and the aggregate file is renamed out of the
|
|
@@ -122,4 +109,4 @@ function legacyMigrationPlan(legacy) {
|
|
|
122
109
|
rename: { from: LEGACY_TOOLBOX_FILE_NAME, to: LEGACY_TOOLBOX_BACKUP_FILE_NAME },
|
|
123
110
|
};
|
|
124
111
|
}
|
|
125
|
-
export { LEGACY_TOOLBOX_BACKUP_FILE_NAME, LEGACY_TOOLBOX_FILE_NAME, TOOL_ARTIFACT_KIND, TOOL_ARTIFACT_SCHEMA_VERSION, TOOLBOX_INDEX_FILE_NAME, artifactDocument, catalogIndexDocument, isToolArtifactDocument, legacyMigrationPlan, safeToolId,
|
|
112
|
+
export { LEGACY_TOOLBOX_BACKUP_FILE_NAME, LEGACY_TOOLBOX_FILE_NAME, TOOL_ARTIFACT_KIND, TOOL_ARTIFACT_SCHEMA_VERSION, TOOLBOX_INDEX_FILE_NAME, artifactDocument, catalogIndexDocument, isToolArtifactDocument, legacyMigrationPlan, safeToolId, toolboxIndexDocument, userArtifactFileName, };
|
package/dist/toolbox.js
CHANGED
|
@@ -3,7 +3,6 @@ import path from 'node:path';
|
|
|
3
3
|
import { resolveProductStateDir } from '@amalgm/core/identity';
|
|
4
4
|
import { apiDriver } from './api-driver.js';
|
|
5
5
|
import { ArtifactFiles } from './artifact-files.js';
|
|
6
|
-
import { portableRecordSemantic, preserveProjectionTime } from './artifacts.js';
|
|
7
6
|
import { cliDriver } from './cli-driver.js';
|
|
8
7
|
import { normalizeDefinition } from './definition.js';
|
|
9
8
|
import { assertMcpNamesUnique, id, mcpName } from './ids.js';
|
|
@@ -12,6 +11,10 @@ import { callableActions, queryTools, resolveAction, resolveCallableAction } fro
|
|
|
12
11
|
import { findSelected, selected } from './selection.js';
|
|
13
12
|
import { Store } from './store.js';
|
|
14
13
|
import { updateAction, updateTool, upsertAction } from './updates.js';
|
|
14
|
+
function semantic(value) {
|
|
15
|
+
const { createdAt, updatedAt, ...record } = value;
|
|
16
|
+
return JSON.stringify(record);
|
|
17
|
+
}
|
|
15
18
|
function defaultDatabase(options) {
|
|
16
19
|
if (options.databaseFile)
|
|
17
20
|
return path.resolve(options.databaseFile);
|
|
@@ -37,7 +40,6 @@ class Toolbox {
|
|
|
37
40
|
const databaseFile = defaultDatabase(options);
|
|
38
41
|
this.store = new Store(databaseFile);
|
|
39
42
|
this.artifacts = new ArtifactFiles(path.dirname(databaseFile));
|
|
40
|
-
const priorIndex = this.artifacts.readIndex();
|
|
41
43
|
this.onChange = options.onChange;
|
|
42
44
|
for (const driver of [cliDriver, apiDriver, ...(options.drivers || [])])
|
|
43
45
|
this.drivers.set(driver.type, driver);
|
|
@@ -47,8 +49,8 @@ class Toolbox {
|
|
|
47
49
|
const normalized = normalizeDefinition({ ...definition, origin: 'system' });
|
|
48
50
|
if (tools.some((tool) => tool.id === normalized.tool.id))
|
|
49
51
|
throw new Error(`Duplicate system tool: ${normalized.tool.id}`);
|
|
50
|
-
tools.push(
|
|
51
|
-
actions.push(...normalized.actions
|
|
52
|
+
tools.push(normalized.tool);
|
|
53
|
+
actions.push(...normalized.actions);
|
|
52
54
|
}
|
|
53
55
|
assertMcpNamesUnique(actions);
|
|
54
56
|
this.system = { version: 1, revision: 0, tools, actions };
|
|
@@ -114,10 +116,9 @@ class Toolbox {
|
|
|
114
116
|
...normalized.actions,
|
|
115
117
|
];
|
|
116
118
|
assertMcpNamesUnique(candidateActions);
|
|
117
|
-
const unchanged = existing &&
|
|
119
|
+
const unchanged = existing && semantic(existing) === semantic(normalized.tool)
|
|
118
120
|
&& normalized.actions.length === oldActions.size
|
|
119
|
-
&& normalized.actions.every((action) =>
|
|
120
|
-
=== portableRecordSemantic(oldActions.get(action.id)));
|
|
121
|
+
&& normalized.actions.every((action) => semantic(action) === semantic(oldActions.get(action.id)));
|
|
121
122
|
if (unchanged)
|
|
122
123
|
return { tool: existing, actions: [...oldActions.values()].sort((a, b) => a.id.localeCompare(b.id)) };
|
|
123
124
|
const result = this.store.apply(normalized);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amalgm/tools",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Local-first tool definitions, Toolbox registry, and agent execution surfaces.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|
|
@@ -39,17 +39,17 @@
|
|
|
39
39
|
"README.md"
|
|
40
40
|
],
|
|
41
41
|
"scripts": {
|
|
42
|
-
"build": "
|
|
42
|
+
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && tsx scripts/mark-executables.ts",
|
|
43
43
|
"check": "tsx scripts/check-tree.ts && tsc -p tsconfig.json && tsc -p tsconfig.test.json",
|
|
44
44
|
"test": "tsx --test --test-concurrency=1 --test-timeout=30000 test/*.test.ts",
|
|
45
45
|
"verify": "npm run check && npm run build && npm test",
|
|
46
46
|
"prepack": "npm run build"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
|
-
"node": ">=
|
|
49
|
+
"node": ">=24"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@amalgm/core": "0.
|
|
52
|
+
"@amalgm/core": "0.4.7",
|
|
53
53
|
"better-sqlite3": "^12.6.2"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|