grix-connector 1.0.2
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 +149 -0
- package/dist/adapter/acp/acp-adapter.js +13 -0
- package/dist/adapter/acp/index.js +1 -0
- package/dist/adapter/acp/usage-parser.js +1 -0
- package/dist/adapter/claude/activity-status-manager.js +1 -0
- package/dist/adapter/claude/channel-notification.js +1 -0
- package/dist/adapter/claude/claude-adapter.js +15 -0
- package/dist/adapter/claude/claude-bridge-server.js +1 -0
- package/dist/adapter/claude/claude-tools.js +1 -0
- package/dist/adapter/claude/claude-worker-client.js +1 -0
- package/dist/adapter/claude/index.js +1 -0
- package/dist/adapter/claude/interaction-protocol.js +1 -0
- package/dist/adapter/claude/mcp-http-launcher.js +2 -0
- package/dist/adapter/claude/model-list.js +1 -0
- package/dist/adapter/claude/protocol-contract.js +1 -0
- package/dist/adapter/claude/result-timeout.js +1 -0
- package/dist/adapter/claude/skill-scanner.js +2 -0
- package/dist/adapter/claude/usage-parser.js +3 -0
- package/dist/adapter/codewhale/codewhale-adapter.js +6 -0
- package/dist/adapter/codewhale/index.js +1 -0
- package/dist/adapter/codex/codex-bridge.js +10 -0
- package/dist/adapter/codex/codex-trust.js +8 -0
- package/dist/adapter/codex/index.js +1 -0
- package/dist/adapter/codex/usage-parser.js +1 -0
- package/dist/adapter/cursor/cursor-adapter.js +8 -0
- package/dist/adapter/cursor/index.js +1 -0
- package/dist/adapter/deepseek/deepseek-adapter.js +6 -0
- package/dist/adapter/deepseek/index.js +1 -0
- package/dist/adapter/index.js +1 -0
- package/dist/adapter/opencode/index.js +1 -0
- package/dist/adapter/opencode/opencode-adapter.js +8 -0
- package/dist/adapter/opencode/opencode-transport.js +5 -0
- package/dist/adapter/opencode/opencode-types.js +0 -0
- package/dist/adapter/openhuman/index.js +1 -0
- package/dist/adapter/openhuman/openhuman-adapter.js +7 -0
- package/dist/adapter/openhuman/openhuman-transport.js +1 -0
- package/dist/adapter/openhuman/openhuman-types.js +0 -0
- package/dist/adapter/pi/index.js +1 -0
- package/dist/adapter/pi/pi-adapter.js +10 -0
- package/dist/adapter/pi/pi-transport.js +4 -0
- package/dist/adapter/pi/pi-types.js +0 -0
- package/dist/adapter/pi/usage-parser.js +1 -0
- package/dist/adapter/qwen/index.js +1 -0
- package/dist/adapter/qwen/qwen-adapter.js +4 -0
- package/dist/adapter/types.js +1 -0
- package/dist/agent/index.js +1 -0
- package/dist/agent/process.js +2 -0
- package/dist/aibot/client.js +1 -0
- package/dist/aibot/index.js +1 -0
- package/dist/aibot/types.js +0 -0
- package/dist/bridge/adapter-pool.js +1 -0
- package/dist/bridge/bridge.js +10 -0
- package/dist/bridge/deferred-events.js +1 -0
- package/dist/bridge/event-queue.js +1 -0
- package/dist/bridge/index.js +1 -0
- package/dist/bridge/respawn-manager.js +1 -0
- package/dist/bridge/revoke-handler.js +1 -0
- package/dist/bridge/runtime-config.js +1 -0
- package/dist/bridge/send-controller.js +1 -0
- package/dist/bridge/session-controller.js +9 -0
- package/dist/bridge/tool-card-utils.js +1 -0
- package/dist/core/access/allowlist-gate.js +1 -0
- package/dist/core/access/allowlist-store.js +1 -0
- package/dist/core/access/index.js +1 -0
- package/dist/core/aibot/client.js +1 -0
- package/dist/core/aibot/connection-handle.js +1 -0
- package/dist/core/aibot/connection-manager.js +1 -0
- package/dist/core/aibot/event-lifecycle-types.js +0 -0
- package/dist/core/aibot/index.js +1 -0
- package/dist/core/aibot/types.js +0 -0
- package/dist/core/config/index.js +1 -0
- package/dist/core/config/paths.js +1 -0
- package/dist/core/context/channel-context-resolution.js +1 -0
- package/dist/core/context/channel-context-store.js +1 -0
- package/dist/core/context/index.js +1 -0
- package/dist/core/context/transcript-channel-context.js +1 -0
- package/dist/core/file-ops/handler.js +1 -0
- package/dist/core/file-ops/list-files.js +1 -0
- package/dist/core/file-ops/types.js +0 -0
- package/dist/core/files/create-folder.js +1 -0
- package/dist/core/files/index.js +1 -0
- package/dist/core/files/list-files.js +1 -0
- package/dist/core/files/list-handler.js +1 -0
- package/dist/core/files/types.js +0 -0
- package/dist/core/files/utils.js +1 -0
- package/dist/core/hooks/hook-signal-store.js +2 -0
- package/dist/core/hooks/index.js +1 -0
- package/dist/core/log/bridge-event-log.js +2 -0
- package/dist/core/log/conversation-log.js +3 -0
- package/dist/core/log/index.js +1 -0
- package/dist/core/log/logger.js +6 -0
- package/dist/core/log/packet-log.js +2 -0
- package/dist/core/log/rotation.js +2 -0
- package/dist/core/mcp/event-tool-executor.js +1 -0
- package/dist/core/mcp/index.js +1 -0
- package/dist/core/mcp/internal-api-server.js +1 -0
- package/dist/core/mcp/tool-schemas.js +1 -0
- package/dist/core/mcp/tools.js +1 -0
- package/dist/core/persistence/active-event-store.js +1 -0
- package/dist/core/persistence/agent-global-config-store.js +1 -0
- package/dist/core/persistence/elicitation-store.js +1 -0
- package/dist/core/persistence/event-results-store.js +1 -0
- package/dist/core/persistence/permission-store.js +1 -0
- package/dist/core/persistence/question-store.js +1 -0
- package/dist/core/persistence/session-binding-store.js +1 -0
- package/dist/core/protocol/agent-api-media.js +1 -0
- package/dist/core/protocol/attachment-file.js +1 -0
- package/dist/core/protocol/index.js +1 -0
- package/dist/core/protocol/interaction-parser.js +1 -0
- package/dist/core/protocol/message-metadata.js +2 -0
- package/dist/core/protocol/message-reference.js +2 -0
- package/dist/core/protocol/payload-parser.js +11 -0
- package/dist/core/protocol/protocol-descriptor.js +1 -0
- package/dist/core/protocol/protocol-text.js +1 -0
- package/dist/core/provider-quota/index.js +1 -0
- package/dist/core/provider-quota/kiro.js +1 -0
- package/dist/core/provider-quota/providers.js +1 -0
- package/dist/core/provider-quota/types.js +0 -0
- package/dist/core/runtime/health.js +1 -0
- package/dist/core/runtime/index.js +1 -0
- package/dist/core/runtime/pidfile.js +2 -0
- package/dist/core/runtime/spawn.js +1 -0
- package/dist/core/text-segmentation/index.js +1 -0
- package/dist/core/text-segmentation/safe-markdown-stream-segmenter.js +6 -0
- package/dist/core/transport/index.js +1 -0
- package/dist/core/transport/json-rpc.js +3 -0
- package/dist/core/upgrade/npm-upgrader.js +2 -0
- package/dist/core/upgrade/upgrade-checker.js +1 -0
- package/dist/core/util/client-version.js +1 -0
- package/dist/core/util/codex-output-policy.js +1 -0
- package/dist/core/util/event-buffer.js +1 -0
- package/dist/core/util/index.js +1 -0
- package/dist/core/util/json-file.js +2 -0
- package/dist/core/util/normalize-string.js +1 -0
- package/dist/core/util/quoted-message-stream.js +3 -0
- package/dist/grix.js +28 -0
- package/dist/index.js +1 -0
- package/dist/log.js +3 -0
- package/dist/main.js +31 -0
- package/dist/manager.js +1 -0
- package/dist/mcp/acp-mcp-server.js +5 -0
- package/dist/mcp/stdio/server.js +10 -0
- package/dist/mcp/stream-http/config.js +1 -0
- package/dist/mcp/stream-http/connection-binding.js +1 -0
- package/dist/mcp/stream-http/event-tool-executor.js +1 -0
- package/dist/mcp/stream-http/gateway.js +1 -0
- package/dist/mcp/stream-http/index.js +1 -0
- package/dist/mcp/stream-http/security.js +1 -0
- package/dist/mcp/stream-http/session-manager.js +1 -0
- package/dist/mcp/stream-http/tool-executor.js +1 -0
- package/dist/mcp/stream-http/tool-registry.js +1 -0
- package/dist/mcp/stream-http/tool-schemas.js +1 -0
- package/dist/protocol/acp-client.js +1 -0
- package/dist/protocol/event-mapper.js +5 -0
- package/dist/protocol/index.js +1 -0
- package/dist/runtime/daemon-lock.js +2 -0
- package/dist/runtime/service-state.js +2 -0
- package/dist/scripts/approve-plan-hook.js +2 -0
- package/dist/scripts/elicitation-hook.js +6 -0
- package/dist/scripts/lib/read-stdin.js +1 -0
- package/dist/scripts/lifecycle-hook.js +2 -0
- package/dist/scripts/notification-hook.js +4 -0
- package/dist/scripts/permission-hook.js +5 -0
- package/dist/scripts/status-line-forwarder.js +2 -0
- package/dist/scripts/user-prompt-submit-hook.js +2 -0
- package/dist/service/platform-adapter.js +45 -0
- package/dist/service/process-control.js +1 -0
- package/dist/service/service-install-store.js +1 -0
- package/dist/service/service-manager.js +1 -0
- package/dist/service/service-paths.js +1 -0
- package/dist/session/index.js +1 -0
- package/dist/session/manager.js +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/json-rpc.js +3 -0
- package/dist/types/events.js +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/protocol.js +0 -0
- package/dist/types/session-state.js +0 -0
- package/dist/types/usage.js +0 -0
- package/openclaw-plugin/index.js +11271 -0
- package/openclaw-plugin/skills/grix-admin/SKILL.md +202 -0
- package/openclaw-plugin/skills/grix-admin/references/api-contract.md +210 -0
- package/openclaw-plugin/skills/grix-egg/SKILL.md +81 -0
- package/openclaw-plugin/skills/grix-egg/references/api-contract.md +40 -0
- package/openclaw-plugin/skills/grix-group/SKILL.md +164 -0
- package/openclaw-plugin/skills/grix-group/references/api-contract.md +97 -0
- package/openclaw-plugin/skills/grix-query/SKILL.md +247 -0
- package/openclaw-plugin/skills/grix-register/SKILL.md +86 -0
- package/openclaw-plugin/skills/grix-register/references/api-contract.md +76 -0
- package/openclaw-plugin/skills/grix-register/references/grix-concepts.md +26 -0
- package/openclaw-plugin/skills/grix-register/references/handoff-contract.md +24 -0
- package/openclaw-plugin/skills/grix-register/references/openclaw-setup.md +6 -0
- package/openclaw-plugin/skills/grix-register/references/user-replies.md +25 -0
- package/openclaw-plugin/skills/grix-register/scripts/grix_auth.ts +599 -0
- package/openclaw-plugin/skills/grix-update/SKILL.md +310 -0
- package/openclaw-plugin/skills/grix-update/references/cron-setup.md +56 -0
- package/openclaw-plugin/skills/grix-update/references/update-contract.md +149 -0
- package/openclaw-plugin/skills/message-send/SKILL.md +197 -0
- package/openclaw-plugin/skills/message-unsend/SKILL.md +186 -0
- package/openclaw-plugin/skills/message-unsend/flowchart.mermaid +27 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/SKILL.md +282 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/references/case-study-macpro.md +52 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/references/host-readiness.md +147 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/scripts/bench_ollama_embeddings.ts +326 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/scripts/set_openclaw_memory_model.ts +385 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/scripts/survey_host_readiness.ts +294 -0
- package/openclaw.plugin.json +24 -0
- package/package.json +114 -0
- package/scripts/install-guardian.mjs +30 -0
- package/scripts/install-guardian.sh +30 -0
- package/scripts/upgrade-guardian.sh +98 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Benchmark Ollama embedding models on the current machine.
|
|
4
|
+
*
|
|
5
|
+
* Emits a readable table by default, or JSON with --json.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { performance } from "node:perf_hooks";
|
|
9
|
+
import { parseArgs } from "node:util";
|
|
10
|
+
|
|
11
|
+
const DEFAULT_TEXT =
|
|
12
|
+
"Generate a stable semantic embedding for this short bilingual memory sample. " +
|
|
13
|
+
"It should be suitable for retrieval, recall, and near-duplicate detection.";
|
|
14
|
+
|
|
15
|
+
type BenchmarkOk = {
|
|
16
|
+
model: string;
|
|
17
|
+
ok: true;
|
|
18
|
+
single_wall_seconds: number;
|
|
19
|
+
single_api_seconds: number | null;
|
|
20
|
+
batch_wall_seconds: number;
|
|
21
|
+
batch_api_seconds: number | null;
|
|
22
|
+
batch_size: number;
|
|
23
|
+
vector_dims: number;
|
|
24
|
+
rounds: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type BenchmarkErr = { model: string; ok: false; error: string };
|
|
28
|
+
type BenchmarkResult = BenchmarkOk | BenchmarkErr;
|
|
29
|
+
|
|
30
|
+
function usageText(): string {
|
|
31
|
+
return [
|
|
32
|
+
"Usage:",
|
|
33
|
+
" node scripts/bench_ollama_embeddings.ts [options] <model...>",
|
|
34
|
+
"",
|
|
35
|
+
"Options:",
|
|
36
|
+
" --host <url> Ollama host (default http://127.0.0.1:11434)",
|
|
37
|
+
" --rounds <n> Measured rounds per test after warmup (default 1)",
|
|
38
|
+
" --batch-size <n> Inputs per batch test (default 4)",
|
|
39
|
+
" --timeout <seconds> Request timeout (default 300)",
|
|
40
|
+
" --single-text <text> Input used for the single-input benchmark",
|
|
41
|
+
" --skip-warmup Skip warmup request per model",
|
|
42
|
+
" --json Emit JSON results",
|
|
43
|
+
].join("\n");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseNumber(raw: unknown, name: string): number {
|
|
47
|
+
const value = Number(raw);
|
|
48
|
+
if (!Number.isFinite(value)) {
|
|
49
|
+
throw new Error(`Invalid --${name}: ${String(raw)}`);
|
|
50
|
+
}
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function median(values: number[]): number {
|
|
55
|
+
if (!values.length) {
|
|
56
|
+
throw new Error("median() requires at least one value");
|
|
57
|
+
}
|
|
58
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
59
|
+
const mid = Math.floor(sorted.length / 2);
|
|
60
|
+
if (sorted.length % 2 === 0) {
|
|
61
|
+
return (sorted[mid - 1]! + sorted[mid]!) / 2;
|
|
62
|
+
}
|
|
63
|
+
return sorted[mid]!;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function medianOrNull(values: Array<number | null>): number | null {
|
|
67
|
+
const usable = values.filter((v): v is number => typeof v === "number" && Number.isFinite(v));
|
|
68
|
+
return usable.length ? median(usable) : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildBatchInputs(text: string, batchSize: number): string[] {
|
|
72
|
+
return Array.from({ length: batchSize }, (_, index) => `${text} [sample ${index + 1}]`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function fetchWithTimeout(url: string, init: RequestInit, timeoutMs: number): Promise<Response> {
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
78
|
+
try {
|
|
79
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
80
|
+
} finally {
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function postEmbed(params: {
|
|
86
|
+
host: string;
|
|
87
|
+
model: string;
|
|
88
|
+
inputs: string[];
|
|
89
|
+
timeoutSeconds: number;
|
|
90
|
+
}): Promise<{ embeddings: unknown; total_duration?: unknown }> {
|
|
91
|
+
const url = params.host.replace(/\/+$/, "") + "/api/embed";
|
|
92
|
+
const payload = {
|
|
93
|
+
model: params.model,
|
|
94
|
+
input: params.inputs.length > 1 ? params.inputs : params.inputs[0],
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const response = await fetchWithTimeout(
|
|
98
|
+
url,
|
|
99
|
+
{
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: { "Content-Type": "application/json" },
|
|
102
|
+
body: JSON.stringify(payload),
|
|
103
|
+
},
|
|
104
|
+
Math.max(1, params.timeoutSeconds) * 1000,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const text = await response.text();
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let data: unknown;
|
|
113
|
+
try {
|
|
114
|
+
data = JSON.parse(text);
|
|
115
|
+
} catch {
|
|
116
|
+
throw new Error(`Unexpected JSON from Ollama: ${text.slice(0, 256)}`);
|
|
117
|
+
}
|
|
118
|
+
return data as { embeddings: unknown; total_duration?: unknown };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function runOne(params: {
|
|
122
|
+
host: string;
|
|
123
|
+
model: string;
|
|
124
|
+
inputs: string[];
|
|
125
|
+
timeoutSeconds: number;
|
|
126
|
+
}): Promise<{ wall_seconds: number; api_seconds: number | null; items: number; vector_dims: number }> {
|
|
127
|
+
const started = performance.now();
|
|
128
|
+
const response = await postEmbed(params);
|
|
129
|
+
const elapsed = (performance.now() - started) / 1000;
|
|
130
|
+
|
|
131
|
+
const totalDuration = (response as { total_duration?: unknown }).total_duration;
|
|
132
|
+
const api_seconds =
|
|
133
|
+
typeof totalDuration === "number" && Number.isFinite(totalDuration)
|
|
134
|
+
? totalDuration / 1_000_000_000
|
|
135
|
+
: null;
|
|
136
|
+
|
|
137
|
+
const embeddings = (response as { embeddings?: unknown }).embeddings;
|
|
138
|
+
if (!Array.isArray(embeddings)) {
|
|
139
|
+
throw new Error(`Unexpected Ollama response for ${params.model}: ${JSON.stringify(response)}`);
|
|
140
|
+
}
|
|
141
|
+
const first = embeddings[0];
|
|
142
|
+
const vector_dims = Array.isArray(first) ? first.length : 0;
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
wall_seconds: elapsed,
|
|
146
|
+
api_seconds,
|
|
147
|
+
items: embeddings.length,
|
|
148
|
+
vector_dims,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function benchmarkModel(params: {
|
|
153
|
+
host: string;
|
|
154
|
+
model: string;
|
|
155
|
+
singleText: string;
|
|
156
|
+
batchInputs: string[];
|
|
157
|
+
rounds: number;
|
|
158
|
+
timeoutSeconds: number;
|
|
159
|
+
skipWarmup: boolean;
|
|
160
|
+
}): Promise<BenchmarkOk> {
|
|
161
|
+
if (!params.skipWarmup) {
|
|
162
|
+
await runOne({ host: params.host, model: params.model, inputs: [params.singleText], timeoutSeconds: params.timeoutSeconds });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const singleRuns = [];
|
|
166
|
+
const batchRuns = [];
|
|
167
|
+
for (let index = 0; index < params.rounds; index += 1) {
|
|
168
|
+
singleRuns.push(await runOne({ host: params.host, model: params.model, inputs: [params.singleText], timeoutSeconds: params.timeoutSeconds }));
|
|
169
|
+
}
|
|
170
|
+
for (let index = 0; index < params.rounds; index += 1) {
|
|
171
|
+
batchRuns.push(await runOne({ host: params.host, model: params.model, inputs: params.batchInputs, timeoutSeconds: params.timeoutSeconds }));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
model: params.model,
|
|
176
|
+
ok: true,
|
|
177
|
+
single_wall_seconds: median(singleRuns.map((run) => run.wall_seconds)),
|
|
178
|
+
single_api_seconds: medianOrNull(singleRuns.map((run) => run.api_seconds)),
|
|
179
|
+
batch_wall_seconds: median(batchRuns.map((run) => run.wall_seconds)),
|
|
180
|
+
batch_api_seconds: medianOrNull(batchRuns.map((run) => run.api_seconds)),
|
|
181
|
+
batch_size: params.batchInputs.length,
|
|
182
|
+
vector_dims: singleRuns.at(-1)?.vector_dims ?? 0,
|
|
183
|
+
rounds: params.rounds,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function formatSeconds(value: number | null): string {
|
|
188
|
+
return value === null ? "-" : value.toFixed(2);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function printTable(results: BenchmarkResult[], batchSize: number): void {
|
|
192
|
+
const headers = [
|
|
193
|
+
"Model",
|
|
194
|
+
"Single(s)",
|
|
195
|
+
`Batch${batchSize}(s)`,
|
|
196
|
+
"API Single(s)",
|
|
197
|
+
`API Batch${batchSize}(s)`,
|
|
198
|
+
"Dims",
|
|
199
|
+
"Status",
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
const rows: string[][] = results.map((result) => {
|
|
203
|
+
if (!result.ok) {
|
|
204
|
+
return [result.model, "-", "-", "-", "-", "-", result.error];
|
|
205
|
+
}
|
|
206
|
+
return [
|
|
207
|
+
result.model,
|
|
208
|
+
formatSeconds(result.single_wall_seconds),
|
|
209
|
+
formatSeconds(result.batch_wall_seconds),
|
|
210
|
+
formatSeconds(result.single_api_seconds),
|
|
211
|
+
formatSeconds(result.batch_api_seconds),
|
|
212
|
+
String(result.vector_dims),
|
|
213
|
+
"ok",
|
|
214
|
+
];
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const widths = headers.map((header, index) =>
|
|
218
|
+
Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)),
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const emit = (cols: string[]) => {
|
|
222
|
+
process.stdout.write(
|
|
223
|
+
`${cols.map((value, index) => value.padEnd(widths[index] ?? value.length)).join(" ")}\n`,
|
|
224
|
+
);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
emit(headers);
|
|
228
|
+
emit(widths.map((width) => "-".repeat(width)));
|
|
229
|
+
for (const row of rows) {
|
|
230
|
+
emit(row);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const successful = results.filter((r): r is BenchmarkOk => r.ok);
|
|
234
|
+
if (successful.length) {
|
|
235
|
+
const ranked = [...successful].sort((a, b) => {
|
|
236
|
+
if (a.batch_wall_seconds !== b.batch_wall_seconds) return a.batch_wall_seconds - b.batch_wall_seconds;
|
|
237
|
+
if (a.single_wall_seconds !== b.single_wall_seconds) return a.single_wall_seconds - b.single_wall_seconds;
|
|
238
|
+
return a.model.localeCompare(b.model);
|
|
239
|
+
});
|
|
240
|
+
process.stdout.write(`\nRecommended by speed: ${ranked[0]!.model}\n`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function main(): Promise<number> {
|
|
245
|
+
const parsed = parseArgs({
|
|
246
|
+
args: process.argv.slice(2),
|
|
247
|
+
allowPositionals: true,
|
|
248
|
+
options: {
|
|
249
|
+
host: { type: "string", default: "http://127.0.0.1:11434" },
|
|
250
|
+
rounds: { type: "string", default: "1" },
|
|
251
|
+
"batch-size": { type: "string", default: "4" },
|
|
252
|
+
timeout: { type: "string", default: "300" },
|
|
253
|
+
"single-text": { type: "string", default: DEFAULT_TEXT },
|
|
254
|
+
"skip-warmup": { type: "boolean", default: false },
|
|
255
|
+
json: { type: "boolean", default: false },
|
|
256
|
+
help: { type: "boolean", default: false },
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
if (parsed.values.help) {
|
|
261
|
+
process.stdout.write(`${usageText()}\n`);
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const models = parsed.positionals.map((value) => String(value).trim()).filter(Boolean);
|
|
266
|
+
if (!models.length) {
|
|
267
|
+
process.stderr.write(`${usageText()}\n`);
|
|
268
|
+
return 1;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const host = String(parsed.values.host ?? "").trim() || "http://127.0.0.1:11434";
|
|
272
|
+
const rounds = parseNumber(parsed.values.rounds, "rounds");
|
|
273
|
+
const batchSize = parseNumber(parsed.values["batch-size"], "batch-size");
|
|
274
|
+
const timeoutSeconds = parseNumber(parsed.values.timeout, "timeout");
|
|
275
|
+
const singleText = String(parsed.values["single-text"] ?? DEFAULT_TEXT);
|
|
276
|
+
const skipWarmup = Boolean(parsed.values["skip-warmup"]);
|
|
277
|
+
const asJson = Boolean(parsed.values.json);
|
|
278
|
+
|
|
279
|
+
if (rounds < 1) {
|
|
280
|
+
throw new Error("--rounds must be at least 1");
|
|
281
|
+
}
|
|
282
|
+
if (batchSize < 1) {
|
|
283
|
+
throw new Error("--batch-size must be at least 1");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const batchInputs = buildBatchInputs(singleText, batchSize);
|
|
287
|
+
const results: BenchmarkResult[] = [];
|
|
288
|
+
|
|
289
|
+
for (const model of models) {
|
|
290
|
+
try {
|
|
291
|
+
results.push(
|
|
292
|
+
await benchmarkModel({
|
|
293
|
+
host,
|
|
294
|
+
model,
|
|
295
|
+
singleText,
|
|
296
|
+
batchInputs,
|
|
297
|
+
rounds,
|
|
298
|
+
timeoutSeconds,
|
|
299
|
+
skipWarmup,
|
|
300
|
+
}),
|
|
301
|
+
);
|
|
302
|
+
} catch (err) {
|
|
303
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
304
|
+
results.push({ model, ok: false, error: message });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const successful = results.some((result) => result.ok);
|
|
309
|
+
if (asJson) {
|
|
310
|
+
process.stdout.write(`${JSON.stringify({ results }, null, 2)}\n`);
|
|
311
|
+
} else {
|
|
312
|
+
printTable(results, batchSize);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return successful ? 0 : 1;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
void main()
|
|
319
|
+
.then((code) => {
|
|
320
|
+
process.exitCode = code;
|
|
321
|
+
})
|
|
322
|
+
.catch((err) => {
|
|
323
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
324
|
+
process.stderr.write(`${message}\n`);
|
|
325
|
+
process.exitCode = 1;
|
|
326
|
+
});
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Preview or update OpenClaw memory model settings in openclaw.json files.
|
|
4
|
+
*
|
|
5
|
+
* Without --write, prints a preview only.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { copyFile, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
9
|
+
import os from "node:os";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
import { parseArgs } from "node:util";
|
|
12
|
+
|
|
13
|
+
type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue };
|
|
14
|
+
|
|
15
|
+
const SENSITIVE_KEY_MARKERS = [
|
|
16
|
+
"apikey",
|
|
17
|
+
"api_key",
|
|
18
|
+
"token",
|
|
19
|
+
"secret",
|
|
20
|
+
"authorization",
|
|
21
|
+
"password",
|
|
22
|
+
"passwd",
|
|
23
|
+
"credential",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function usageText(): string {
|
|
27
|
+
return [
|
|
28
|
+
"Usage:",
|
|
29
|
+
" node scripts/set_openclaw_memory_model.ts --model <model> [--provider <provider>] [--set KEY=VALUE...] [--write] <target...>",
|
|
30
|
+
"",
|
|
31
|
+
"Targets:",
|
|
32
|
+
" Profile directories (containing openclaw.json) or direct openclaw.json paths.",
|
|
33
|
+
"",
|
|
34
|
+
"Options:",
|
|
35
|
+
" --model <model> Required, e.g. embeddinggemma:300m-qat-q8_0",
|
|
36
|
+
" --provider <provider> Default: ollama",
|
|
37
|
+
" --set KEY=VALUE Keys are relative to agents.defaults.memorySearch; repeatable",
|
|
38
|
+
" --write Apply the change (otherwise preview only)",
|
|
39
|
+
].join("\n");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function expandUser(rawPath: string): string {
|
|
43
|
+
const text = String(rawPath ?? "");
|
|
44
|
+
if (text === "~") return os.homedir();
|
|
45
|
+
if (text.startsWith("~/")) return join(os.homedir(), text.slice(2));
|
|
46
|
+
return text;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
50
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function fileExists(path: string): Promise<boolean> {
|
|
54
|
+
try {
|
|
55
|
+
await stat(path);
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function resolveConfigPath(rawTarget: string): Promise<string> {
|
|
63
|
+
const target = expandUser(rawTarget);
|
|
64
|
+
if (target.endsWith("/")) {
|
|
65
|
+
return resolveConfigPath(target.slice(0, -1));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const looksLikeConfig = target.endsWith("openclaw.json");
|
|
69
|
+
if (looksLikeConfig) {
|
|
70
|
+
if (!target.endsWith("/openclaw.json") && target.split("/").at(-1) !== "openclaw.json") {
|
|
71
|
+
throw new Error(`Refusing to edit non-OpenClaw config file: ${target}`);
|
|
72
|
+
}
|
|
73
|
+
return target;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const asDirConfig = join(target, "openclaw.json");
|
|
77
|
+
if (await fileExists(asDirConfig)) {
|
|
78
|
+
return asDirConfig;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (await fileExists(target)) {
|
|
82
|
+
throw new Error(`Refusing to edit non-OpenClaw config file: ${target}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
throw new Error(`Config not found: ${asDirConfig}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function loadConfig(path: string): Promise<Record<string, unknown>> {
|
|
89
|
+
let raw: string;
|
|
90
|
+
try {
|
|
91
|
+
raw = await readFile(path, "utf8");
|
|
92
|
+
} catch (err) {
|
|
93
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
94
|
+
throw new Error(`Config not found: ${path} (${message})`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
99
|
+
if (!isRecord(parsed)) {
|
|
100
|
+
throw new Error("Top-level JSON must be an object");
|
|
101
|
+
}
|
|
102
|
+
return parsed;
|
|
103
|
+
} catch (err) {
|
|
104
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
105
|
+
throw new Error(`Invalid JSON in ${path}: ${message}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function ensureMemorySearch(config: Record<string, unknown>): Record<string, unknown> {
|
|
110
|
+
if (!("agents" in config)) {
|
|
111
|
+
throw new Error("Refusing to edit a config without a top-level 'agents' object");
|
|
112
|
+
}
|
|
113
|
+
const agents = config.agents;
|
|
114
|
+
if (!isRecord(agents)) {
|
|
115
|
+
throw new Error("Top-level 'agents' must be a JSON object");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const defaults = agents.defaults;
|
|
119
|
+
if (defaults === undefined) {
|
|
120
|
+
agents.defaults = {};
|
|
121
|
+
} else if (!isRecord(defaults)) {
|
|
122
|
+
throw new Error("'agents.defaults' must be a JSON object");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const ensuredDefaults = agents.defaults as Record<string, unknown>;
|
|
126
|
+
const memorySearch = ensuredDefaults.memorySearch;
|
|
127
|
+
if (memorySearch === undefined) {
|
|
128
|
+
ensuredDefaults.memorySearch = {};
|
|
129
|
+
} else if (!isRecord(memorySearch)) {
|
|
130
|
+
throw new Error("'agents.defaults.memorySearch' must be a JSON object");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return ensuredDefaults.memorySearch as Record<string, unknown>;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function deepClone<T>(value: T): T {
|
|
137
|
+
return JSON.parse(JSON.stringify(value)) as T;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function parseExtraSettings(entries: string[]): Array<{ keyParts: string[]; value: unknown }> {
|
|
141
|
+
return entries.map((entry) => {
|
|
142
|
+
const index = entry.indexOf("=");
|
|
143
|
+
if (index < 0) {
|
|
144
|
+
throw new Error(`Invalid --set value (expected KEY=VALUE): ${entry}`);
|
|
145
|
+
}
|
|
146
|
+
const keyText = entry.slice(0, index);
|
|
147
|
+
const valueText = entry.slice(index + 1);
|
|
148
|
+
const keyParts = keyText
|
|
149
|
+
.split(".")
|
|
150
|
+
.map((part) => part.trim())
|
|
151
|
+
.filter(Boolean);
|
|
152
|
+
if (!keyParts.length) {
|
|
153
|
+
throw new Error(`Invalid --set key: ${entry}`);
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
return { keyParts, value: JSON.parse(valueText) };
|
|
157
|
+
} catch {
|
|
158
|
+
return { keyParts, value: valueText };
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function setDottedValue(root: Record<string, unknown>, keyParts: string[], value: unknown): void {
|
|
164
|
+
let current: Record<string, unknown> = root;
|
|
165
|
+
for (const part of keyParts.slice(0, -1)) {
|
|
166
|
+
const existing = current[part];
|
|
167
|
+
if (existing === undefined) {
|
|
168
|
+
current[part] = {};
|
|
169
|
+
} else if (!isRecord(existing)) {
|
|
170
|
+
throw new Error(`Cannot set ${keyParts.join(".")}: ${part} is not a JSON object`);
|
|
171
|
+
}
|
|
172
|
+
current = current[part] as Record<string, unknown>;
|
|
173
|
+
}
|
|
174
|
+
current[keyParts[keyParts.length - 1]!] = value;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function jsonDeepEqual(a: unknown, b: unknown): boolean {
|
|
178
|
+
if (a === b) return true;
|
|
179
|
+
if (typeof a !== typeof b) return false;
|
|
180
|
+
if (a === null || b === null) return false;
|
|
181
|
+
|
|
182
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
183
|
+
if (a.length !== b.length) return false;
|
|
184
|
+
for (let index = 0; index < a.length; index += 1) {
|
|
185
|
+
if (!jsonDeepEqual(a[index], b[index])) return false;
|
|
186
|
+
}
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (isRecord(a) && isRecord(b)) {
|
|
191
|
+
const keysA = Object.keys(a).sort();
|
|
192
|
+
const keysB = Object.keys(b).sort();
|
|
193
|
+
if (keysA.length !== keysB.length) return false;
|
|
194
|
+
for (let index = 0; index < keysA.length; index += 1) {
|
|
195
|
+
if (keysA[index] !== keysB[index]) return false;
|
|
196
|
+
}
|
|
197
|
+
for (const key of keysA) {
|
|
198
|
+
if (!jsonDeepEqual(a[key], b[key])) return false;
|
|
199
|
+
}
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function updateConfig(params: {
|
|
207
|
+
config: Record<string, unknown>;
|
|
208
|
+
provider: string;
|
|
209
|
+
model: string;
|
|
210
|
+
extraSettings: string[];
|
|
211
|
+
}): { before: unknown; after: unknown } {
|
|
212
|
+
const memorySearch = ensureMemorySearch(params.config);
|
|
213
|
+
const before = deepClone(memorySearch);
|
|
214
|
+
|
|
215
|
+
const providerChanged = String(memorySearch.provider ?? "") !== params.provider;
|
|
216
|
+
if (providerChanged) {
|
|
217
|
+
for (const key of Object.keys(memorySearch)) {
|
|
218
|
+
delete memorySearch[key];
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
memorySearch.provider = params.provider;
|
|
223
|
+
memorySearch.model = params.model;
|
|
224
|
+
for (const entry of parseExtraSettings(params.extraSettings)) {
|
|
225
|
+
setDottedValue(memorySearch, entry.keyParts, entry.value);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const after = deepClone(memorySearch);
|
|
229
|
+
return { before, after };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function keyLooksSensitive(key: string): boolean {
|
|
233
|
+
const normalized = key.toLowerCase().replace(/[-_]/g, "");
|
|
234
|
+
return SENSITIVE_KEY_MARKERS.some((marker) => normalized.includes(marker.replace(/_/g, "")));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function redactForDisplay(value: unknown): unknown {
|
|
238
|
+
if (Array.isArray(value)) {
|
|
239
|
+
return value.map((item) => redactForDisplay(item));
|
|
240
|
+
}
|
|
241
|
+
if (isRecord(value)) {
|
|
242
|
+
const redacted: Record<string, unknown> = {};
|
|
243
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
244
|
+
redacted[key] = keyLooksSensitive(key) ? "<redacted>" : redactForDisplay(nested);
|
|
245
|
+
}
|
|
246
|
+
return redacted;
|
|
247
|
+
}
|
|
248
|
+
return value;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function sortKeysRecursively(value: unknown): unknown {
|
|
252
|
+
if (Array.isArray(value)) {
|
|
253
|
+
return value.map((item) => sortKeysRecursively(item));
|
|
254
|
+
}
|
|
255
|
+
if (isRecord(value)) {
|
|
256
|
+
const out: Record<string, unknown> = {};
|
|
257
|
+
for (const key of Object.keys(value).sort()) {
|
|
258
|
+
out[key] = sortKeysRecursively(value[key]);
|
|
259
|
+
}
|
|
260
|
+
return out;
|
|
261
|
+
}
|
|
262
|
+
return value;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function stableStringify(value: unknown): string {
|
|
266
|
+
return JSON.stringify(sortKeysRecursively(value));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function formatStamp(date: Date): string {
|
|
270
|
+
const pad2 = (n: number) => String(n).padStart(2, "0");
|
|
271
|
+
return (
|
|
272
|
+
`${date.getFullYear()}` +
|
|
273
|
+
`${pad2(date.getMonth() + 1)}` +
|
|
274
|
+
`${pad2(date.getDate())}-` +
|
|
275
|
+
`${pad2(date.getHours())}` +
|
|
276
|
+
`${pad2(date.getMinutes())}` +
|
|
277
|
+
`${pad2(date.getSeconds())}`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async function backupPath(configPath: string, stamp: string): Promise<string> {
|
|
282
|
+
let candidate = `${configPath}.bak.${stamp}`;
|
|
283
|
+
let index = 1;
|
|
284
|
+
while (await fileExists(candidate)) {
|
|
285
|
+
candidate = `${configPath}.bak.${stamp}-${index}`;
|
|
286
|
+
index += 1;
|
|
287
|
+
}
|
|
288
|
+
return candidate;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function writeConfig(configPath: string, config: Record<string, unknown>, stamp: string): Promise<string> {
|
|
292
|
+
const backup = await backupPath(configPath, stamp);
|
|
293
|
+
await copyFile(configPath, backup);
|
|
294
|
+
|
|
295
|
+
const tempPath = join(dirname(configPath), `openclaw.json.tmp.${stamp}.${Math.random().toString(16).slice(2)}`);
|
|
296
|
+
await writeFile(tempPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
297
|
+
await rename(tempPath, configPath);
|
|
298
|
+
|
|
299
|
+
return backup;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async function main(): Promise<number> {
|
|
303
|
+
const parsed = parseArgs({
|
|
304
|
+
args: process.argv.slice(2),
|
|
305
|
+
allowPositionals: true,
|
|
306
|
+
options: {
|
|
307
|
+
model: { type: "string" },
|
|
308
|
+
provider: { type: "string", default: "ollama" },
|
|
309
|
+
set: { type: "string", multiple: true, default: [] },
|
|
310
|
+
write: { type: "boolean", default: false },
|
|
311
|
+
help: { type: "boolean", default: false },
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
if (parsed.values.help) {
|
|
316
|
+
process.stdout.write(`${usageText()}\n`);
|
|
317
|
+
return 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const model = String(parsed.values.model ?? "").trim();
|
|
321
|
+
if (!model) {
|
|
322
|
+
process.stderr.write(`${usageText()}\n`);
|
|
323
|
+
return 1;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const targets = parsed.positionals.map((value) => String(value)).filter(Boolean);
|
|
327
|
+
if (!targets.length) {
|
|
328
|
+
process.stderr.write(`${usageText()}\n`);
|
|
329
|
+
return 1;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const provider = String(parsed.values.provider ?? "ollama").trim() || "ollama";
|
|
333
|
+
const extraSettings = (parsed.values.set ?? []) as unknown as string[];
|
|
334
|
+
const doWrite = Boolean(parsed.values.write);
|
|
335
|
+
const stamp = formatStamp(new Date());
|
|
336
|
+
|
|
337
|
+
let exitCode = 0;
|
|
338
|
+
|
|
339
|
+
for (const rawTarget of targets) {
|
|
340
|
+
try {
|
|
341
|
+
const configPath = await resolveConfigPath(rawTarget);
|
|
342
|
+
const config = await loadConfig(configPath);
|
|
343
|
+
const { before, after } = updateConfig({
|
|
344
|
+
config,
|
|
345
|
+
provider,
|
|
346
|
+
model,
|
|
347
|
+
extraSettings,
|
|
348
|
+
});
|
|
349
|
+
const changed = !jsonDeepEqual(before, after);
|
|
350
|
+
|
|
351
|
+
process.stdout.write(`${configPath}\n`);
|
|
352
|
+
process.stdout.write(` before: ${stableStringify(redactForDisplay(before))}\n`);
|
|
353
|
+
process.stdout.write(` after: ${stableStringify(redactForDisplay(after))}\n`);
|
|
354
|
+
|
|
355
|
+
if (!changed) {
|
|
356
|
+
process.stdout.write(" result: unchanged\n");
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (doWrite) {
|
|
361
|
+
const backup = await writeConfig(configPath, config, stamp);
|
|
362
|
+
process.stdout.write(" result: written\n");
|
|
363
|
+
process.stdout.write(` backup: ${backup}\n`);
|
|
364
|
+
} else {
|
|
365
|
+
process.stdout.write(" result: preview only (add --write to apply)\n");
|
|
366
|
+
}
|
|
367
|
+
} catch (err) {
|
|
368
|
+
exitCode = 1;
|
|
369
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
370
|
+
process.stdout.write(`${rawTarget}\n error: ${message}\n`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return exitCode;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
void main()
|
|
378
|
+
.then((code) => {
|
|
379
|
+
process.exitCode = code;
|
|
380
|
+
})
|
|
381
|
+
.catch((err) => {
|
|
382
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
383
|
+
process.stderr.write(`${message}\n`);
|
|
384
|
+
process.exitCode = 1;
|
|
385
|
+
});
|