@clawscarf/cli 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +3 -0
- package/THIRD_PARTY_NOTICES.md +102 -0
- package/deploy/execution/browser/LICENSE.playwright +202 -0
- package/deploy/execution/browser/seccomp.json +640 -0
- package/deploy/execution/browser-node/configuration.js +85 -0
- package/deploy/execution/browser-node/operator.js +167 -0
- package/deploy/execution/network/node-ingress.cfg +35 -0
- package/deploy/models/catalog.json +275 -0
- package/deploy/openshell/policy.yaml +24 -0
- package/generated/http/LICENSE.md +21 -0
- package/generated/http/client/client.gen.d.ts +2 -0
- package/generated/http/client/client.gen.js +216 -0
- package/generated/http/client/index.d.ts +10 -0
- package/generated/http/client/index.js +6 -0
- package/generated/http/client/types.gen.d.ts +120 -0
- package/generated/http/client/types.gen.js +2 -0
- package/generated/http/client/utils.gen.d.ts +37 -0
- package/generated/http/client/utils.gen.js +228 -0
- package/generated/http/core/auth.gen.d.ts +25 -0
- package/generated/http/core/auth.gen.js +14 -0
- package/generated/http/core/bodySerializer.gen.d.ts +25 -0
- package/generated/http/core/bodySerializer.gen.js +57 -0
- package/generated/http/core/params.gen.d.ts +43 -0
- package/generated/http/core/params.gen.js +109 -0
- package/generated/http/core/pathSerializer.gen.d.ts +33 -0
- package/generated/http/core/pathSerializer.gen.js +106 -0
- package/generated/http/core/queryKeySerializer.gen.d.ts +18 -0
- package/generated/http/core/queryKeySerializer.gen.js +92 -0
- package/generated/http/core/serverSentEvents.gen.d.ts +71 -0
- package/generated/http/core/serverSentEvents.gen.js +132 -0
- package/generated/http/core/types.gen.d.ts +83 -0
- package/generated/http/core/types.gen.js +2 -0
- package/generated/http/core/utils.gen.d.ts +19 -0
- package/generated/http/core/utils.gen.js +87 -0
- package/package.json +37 -0
- package/packs/README.md +130 -0
- package/packs/research-team/pack.json +20 -0
- package/packs/research-team/researcher/CLAW.md +19 -0
- package/packs/research-team/researcher/package.json +8 -0
- package/packs/research-team/researcher/profiles/openclaw.yml +6 -0
- package/packs/research-team/reviewer/CLAW.md +19 -0
- package/packs/research-team/reviewer/package.json +8 -0
- package/packs/research-team/reviewer/profiles/openclaw.yml +6 -0
- package/pnpm-lock.yaml +7158 -0
- package/recipes/README.md +37 -0
- package/recipes/team-server/recipe.json +28 -0
- package/release/README.md +137 -0
- package/release/components.json +29 -0
- package/release/operator.md +63 -0
- package/runtime/configuration.js +135 -0
- package/runtime/connections-configuration.js +70 -0
- package/runtime/model-contract.js +92 -0
- package/runtime/releases/0.1.0-alpha.1.json +37 -0
- package/scripts/clawscarf.js +92 -0
- package/scripts/cloud/login.js +134 -0
- package/scripts/cloud/registration.js +232 -0
- package/scripts/cloud/url.js +9 -0
- package/scripts/connections.js +193 -0
- package/scripts/controller.js +143 -0
- package/scripts/deployment/browser-node-compose.js +81 -0
- package/scripts/deployment/browser-node-helper.js +9 -0
- package/scripts/deployment/browser-node-pairing.js +165 -0
- package/scripts/deployment/browser-node.js +154 -0
- package/scripts/deployment/browser.js +143 -0
- package/scripts/deployment/certificates.js +76 -0
- package/scripts/deployment/compose.js +191 -0
- package/scripts/deployment/configuration.js +275 -0
- package/scripts/deployment/connection-policy.js +80 -0
- package/scripts/deployment/connection-settings.js +60 -0
- package/scripts/deployment/connections-runtime.js +31 -0
- package/scripts/deployment/connections.js +213 -0
- package/scripts/deployment/controller-compose.js +59 -0
- package/scripts/deployment/database.js +199 -0
- package/scripts/deployment/entrypoint.js +7 -0
- package/scripts/deployment/images.js +17 -0
- package/scripts/deployment/initial-services.js +53 -0
- package/scripts/deployment/launch.js +177 -0
- package/scripts/deployment/logs.js +15 -0
- package/scripts/deployment/model-gateway-compose.js +74 -0
- package/scripts/deployment/model-gateway.js +125 -0
- package/scripts/deployment/models.js +82 -0
- package/scripts/deployment/networks.js +354 -0
- package/scripts/deployment/policy.js +56 -0
- package/scripts/deployment/preflight.js +124 -0
- package/scripts/deployment/prepare.js +205 -0
- package/scripts/deployment/process.js +70 -0
- package/scripts/deployment/relay.js +58 -0
- package/scripts/deployment/runtime-binding.js +100 -0
- package/scripts/deployment/runtime.js +232 -0
- package/scripts/deployment/state.js +154 -0
- package/scripts/deployment/team.js +76 -0
- package/scripts/deployment/upgrade-rpc.py +148 -0
- package/scripts/deployment/upgrade-state.js +47 -0
- package/scripts/deployment/upgrade.js +298 -0
- package/scripts/deployment/volumes.js +24 -0
- package/scripts/errors.js +9 -0
- package/scripts/installation/administrator.js +23 -0
- package/scripts/installation/command.js +176 -0
- package/scripts/installation/configuration.js +104 -0
- package/scripts/installation/configure.js +122 -0
- package/scripts/installation/delete.js +111 -0
- package/scripts/installation/doctor.js +8 -0
- package/scripts/installation/errors.js +8 -0
- package/scripts/installation/files.js +51 -0
- package/scripts/installation/installer/cloud.js +22 -0
- package/scripts/installation/installer/collect.js +245 -0
- package/scripts/installation/installer/inputs.js +59 -0
- package/scripts/installation/installer/menu.js +49 -0
- package/scripts/installation/installer/prompts.js +139 -0
- package/scripts/installation/installer/run.js +251 -0
- package/scripts/installation/installer/secrets.js +16 -0
- package/scripts/installation/installer/sections/access.js +69 -0
- package/scripts/installation/installer/sections/certificates.js +18 -0
- package/scripts/installation/installer/sections/connections.js +4 -0
- package/scripts/installation/installer/sections/external-models.js +35 -0
- package/scripts/installation/installer/sections/models.js +119 -0
- package/scripts/installation/installer/sections/packs.js +82 -0
- package/scripts/installation/installer/sections/resources.js +10 -0
- package/scripts/installation/installer/settings.js +192 -0
- package/scripts/installation/installer/summary.js +44 -0
- package/scripts/installation/lifecycle.js +127 -0
- package/scripts/installation/location.js +3 -0
- package/scripts/installation/models.js +42 -0
- package/scripts/installation/options.js +360 -0
- package/scripts/installation/packs.js +150 -0
- package/scripts/installation/plan.js +121 -0
- package/scripts/installation/prerequisites.js +170 -0
- package/scripts/installation/recipes/catalog.js +55 -0
- package/scripts/installation/recipes/definition.js +32 -0
- package/scripts/installation/reconfigure.js +255 -0
- package/scripts/installation/requirements.js +23 -0
- package/scripts/installation/resolve.js +217 -0
- package/scripts/installation/runtime.js +87 -0
- package/scripts/installation/save.js +95 -0
- package/scripts/installation/setup.js +91 -0
- package/scripts/models/catalog.js +9 -0
- package/scripts/models/configuration.js +184 -0
- package/scripts/models/credentials.js +132 -0
- package/scripts/models/runtime.js +87 -0
- package/scripts/output.js +34 -0
- package/scripts/packs/connections.js +95 -0
- package/scripts/packs/lifecycle.js +125 -0
- package/scripts/packs/model.js +66 -0
- package/scripts/packs/native.js +69 -0
- package/scripts/packs/openshell.js +148 -0
- package/scripts/packs/policy.js +189 -0
- package/scripts/packs/requirements.in +1 -0
- package/scripts/packs/requirements.js +11 -0
- package/scripts/packs/requirements.txt +116 -0
- package/scripts/packs/source.js +90 -0
- package/scripts/packs/transport.py +68 -0
- package/scripts/people.js +52 -0
- package/scripts/release/create.js +62 -0
- package/scripts/release/definition.js +51 -0
- package/scripts/session.js +27 -0
- package/services/access/generated/client.gen.d.ts +12 -0
- package/services/access/generated/client.gen.js +3 -0
- package/services/access/generated/fastify.gen.d.ts +49 -0
- package/services/access/generated/fastify.gen.js +2 -0
- package/services/access/generated/index.d.ts +2 -0
- package/services/access/generated/index.js +2 -0
- package/services/access/generated/sdk.gen.d.ts +28 -0
- package/services/access/generated/sdk.gen.js +107 -0
- package/services/access/generated/types.gen.d.ts +349 -0
- package/services/access/generated/types.gen.js +2 -0
- package/services/access/migrations/001_access.sql +39 -0
- package/services/access/migrations/002_administrator_setup.sql +16 -0
- package/services/access/migrations/003_local_login_completion.sql +5 -0
- package/services/access/migrations/004_invitations.sql +21 -0
- package/services/access/repo/postgres.js +341 -0
- package/services/access/repo/session-logout.js +28 -0
- package/services/access/types/credential.js +2 -0
- package/services/access/types/errors.js +7 -0
- package/services/access/types/ingress.js +1 -0
- package/services/access/types/model.js +1 -0
- package/services/access/types/native-errors.js +7 -0
- package/services/access/types/native.js +1 -0
- package/services/cloud/generated/client.gen.d.ts +12 -0
- package/services/cloud/generated/client.gen.js +3 -0
- package/services/cloud/generated/index.d.ts +2 -0
- package/services/cloud/generated/index.js +2 -0
- package/services/cloud/generated/sdk.gen.d.ts +145 -0
- package/services/cloud/generated/sdk.gen.js +488 -0
- package/services/cloud/generated/types.gen.d.ts +2034 -0
- package/services/cloud/generated/types.gen.js +2 -0
- package/services/connections/cloud/generated/client.gen.d.ts +12 -0
- package/services/connections/cloud/generated/client.gen.js +3 -0
- package/services/connections/cloud/generated/fastify.gen.d.ts +68 -0
- package/services/connections/cloud/generated/fastify.gen.js +2 -0
- package/services/connections/cloud/generated/index.d.ts +2 -0
- package/services/connections/cloud/generated/index.js +2 -0
- package/services/connections/cloud/generated/sdk.gen.d.ts +95 -0
- package/services/connections/cloud/generated/sdk.gen.js +210 -0
- package/services/connections/cloud/generated/types.gen.d.ts +1044 -0
- package/services/connections/cloud/generated/types.gen.js +2 -0
- package/services/connections/migrations/001_connections.sql +220 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { Option } from "commander";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { installationSchema } from "./configuration.js";
|
|
5
|
+
import { cloudUrlSchema } from "../cloud/url.js";
|
|
6
|
+
import { recipeConfiguration, recipeModelFile, } from "./setup.js";
|
|
7
|
+
import { readInputFile } from "./files.js";
|
|
8
|
+
import { InstallationError } from "./errors.js";
|
|
9
|
+
import { configurationSchema, gatewayRoutesSchema, } from "../models/configuration.js";
|
|
10
|
+
import { selectModel } from "./models.js";
|
|
11
|
+
import { openPack } from "../packs/source.js";
|
|
12
|
+
const file = z
|
|
13
|
+
.string()
|
|
14
|
+
.min(1)
|
|
15
|
+
.transform((value) => resolve(value));
|
|
16
|
+
const port = z.coerce.number().int().min(1024).max(65535);
|
|
17
|
+
const text = z.string().min(1);
|
|
18
|
+
/** Public selections only; internal paths and bootstrap state are never CLI overrides. */
|
|
19
|
+
export const selectionSchema = z.object({
|
|
20
|
+
name: installationSchema.shape.name.optional(),
|
|
21
|
+
administratorName: text.optional(),
|
|
22
|
+
port: port.optional(),
|
|
23
|
+
widgetPort: port.optional(),
|
|
24
|
+
origin: cloudUrlSchema.optional(),
|
|
25
|
+
widgetOrigin: cloudUrlSchema.optional(),
|
|
26
|
+
tlsCertificate: file.optional(),
|
|
27
|
+
tlsKeyFile: file.optional(),
|
|
28
|
+
access: z.enum(["hosted", "oidc"]).optional(),
|
|
29
|
+
oidcIssuer: z.url().optional(),
|
|
30
|
+
oidcClientId: text.optional(),
|
|
31
|
+
oidcSecretFile: file.optional(),
|
|
32
|
+
administratorSubject: text.optional(),
|
|
33
|
+
administratorEmail: z.email().optional(),
|
|
34
|
+
cpu: installationSchema.shape.resources.shape.runtime.shape.cpu.optional(),
|
|
35
|
+
memory: installationSchema.shape.resources.shape.runtime.shape.memory.optional(),
|
|
36
|
+
connections: z.boolean().optional(),
|
|
37
|
+
connectionsCloudUrl: cloudUrlSchema.optional(),
|
|
38
|
+
browser: z.boolean().optional(),
|
|
39
|
+
model: text.optional(),
|
|
40
|
+
provider: text.optional(),
|
|
41
|
+
reasoning: z.enum(["low", "medium", "high"]).optional(),
|
|
42
|
+
llmKeyFile: file.optional(),
|
|
43
|
+
providerEnvFile: file.optional(),
|
|
44
|
+
modelCatalog: file.optional(),
|
|
45
|
+
modelGatewayUrl: z.url().optional(),
|
|
46
|
+
modelGatewayKeyFile: file.optional(),
|
|
47
|
+
modelGatewayCaFile: file.optional(),
|
|
48
|
+
pack: z.array(text).optional(),
|
|
49
|
+
packs: z
|
|
50
|
+
.literal(false)
|
|
51
|
+
.nullish()
|
|
52
|
+
.transform((value) => value ?? undefined),
|
|
53
|
+
packBindings: z.array(text).optional(),
|
|
54
|
+
packPython: file.optional(),
|
|
55
|
+
});
|
|
56
|
+
export function installationOptions(command) {
|
|
57
|
+
return command
|
|
58
|
+
.option("--name <name>", "Installation name")
|
|
59
|
+
.option("--administrator-name <name>", "First administrator display name")
|
|
60
|
+
.option("--port <number>", "Local application port (default: 18800)")
|
|
61
|
+
.option("--widget-port <number>", "Local widgets port (default: 18802)")
|
|
62
|
+
.option("--origin <url>", "Public HTTPS application origin")
|
|
63
|
+
.option("--widget-origin <url>", "Public HTTPS widgets origin")
|
|
64
|
+
.option("--tls-certificate <file>", "HTTPS certificate")
|
|
65
|
+
.option("--tls-key-file <file>", "Private HTTPS key file")
|
|
66
|
+
.addOption(new Option("--access <mode>", "Login provider").choices([
|
|
67
|
+
"hosted",
|
|
68
|
+
"oidc",
|
|
69
|
+
]))
|
|
70
|
+
.option("--oidc-issuer <url>", "Company OIDC issuer")
|
|
71
|
+
.option("--oidc-client-id <id>", "Company OIDC client ID")
|
|
72
|
+
.option("--oidc-secret-file <file>", "Private company OIDC client secret")
|
|
73
|
+
.option("--administrator-subject <subject>", "First administrator OIDC subject (for unattended setup)")
|
|
74
|
+
.option("--administrator-email <email>", "First administrator email (with --administrator-subject)")
|
|
75
|
+
.option("--cpu <count>", "Runtime CPU allocation")
|
|
76
|
+
.option("--memory <size>", "Runtime memory, for example 4Gi")
|
|
77
|
+
.option("--connections", "Enable Connections")
|
|
78
|
+
.option("--no-connections", "Disable Connections")
|
|
79
|
+
.option("--connections-cloud-url <url>", "Use a separate Connections service")
|
|
80
|
+
.option("--browser", "Enable experimental browser capability")
|
|
81
|
+
.option("--no-browser", "Disable browser capability")
|
|
82
|
+
.option("--model <id>", "Default model from the model catalog")
|
|
83
|
+
.option("--provider <id>", "Model provider, for example openai or openrouter")
|
|
84
|
+
.addOption(new Option("--reasoning <level>", "Model reasoning level").choices([
|
|
85
|
+
"low",
|
|
86
|
+
"medium",
|
|
87
|
+
"high",
|
|
88
|
+
]))
|
|
89
|
+
.option("--llm-key-file <file>", "Private file containing the selected provider's API key")
|
|
90
|
+
.option("--provider-env-file <file>", "Private provider credentials file for multiple model routes")
|
|
91
|
+
.option("--model-catalog <file>", "Advanced: import a model catalog")
|
|
92
|
+
.option("--model-gateway-url <url>", "Advanced: existing LiteLLM endpoint ending in /v1")
|
|
93
|
+
.option("--model-gateway-key-file <file>", "Private key for an existing LiteLLM gateway")
|
|
94
|
+
.option("--model-gateway-ca-file <file>", "Private CA for an existing LiteLLM gateway")
|
|
95
|
+
.option("--pack <id:members>", "Select bundled pack members; repeat for multiple packs", append)
|
|
96
|
+
.addOption(new Option("--no-packs", "Remove all recipe pack selections").default(null))
|
|
97
|
+
.option("--pack-bindings <id=file>", "Account bindings for a selected pack; repeat as needed", append)
|
|
98
|
+
.option("--pack-python <file>", "Python with the pinned OpenShell SDK for experimental packs");
|
|
99
|
+
}
|
|
100
|
+
function append(value, previous = []) {
|
|
101
|
+
return [...previous, value];
|
|
102
|
+
}
|
|
103
|
+
function invalid(message) {
|
|
104
|
+
throw new InstallationError("invalid_configuration", message);
|
|
105
|
+
}
|
|
106
|
+
export async function selectedDraft(context, recipe, selections, inputs, retained) {
|
|
107
|
+
const o = selectionSchema.parse(selections);
|
|
108
|
+
if (retained) {
|
|
109
|
+
const mutable = new Set([
|
|
110
|
+
"model",
|
|
111
|
+
"provider",
|
|
112
|
+
"reasoning",
|
|
113
|
+
"llmKeyFile",
|
|
114
|
+
"providerEnvFile",
|
|
115
|
+
"modelCatalog",
|
|
116
|
+
"modelGatewayKeyFile",
|
|
117
|
+
"modelGatewayCaFile",
|
|
118
|
+
"connections",
|
|
119
|
+
"connectionsCloudUrl",
|
|
120
|
+
"pack",
|
|
121
|
+
"packs",
|
|
122
|
+
"packBindings",
|
|
123
|
+
"packPython",
|
|
124
|
+
]);
|
|
125
|
+
if (Object.entries(o).some(([key, value]) => value !== undefined && !mutable.has(key)))
|
|
126
|
+
invalid("Existing installations support changes to models, keys, Connections and packs only.");
|
|
127
|
+
}
|
|
128
|
+
const config = structuredClone(retained ?? recipeConfiguration(context, recipe));
|
|
129
|
+
if (o.name !== undefined)
|
|
130
|
+
config.name = o.name;
|
|
131
|
+
if (o.administratorName !== undefined)
|
|
132
|
+
config.access.administratorName = o.administratorName;
|
|
133
|
+
if (o.cpu !== undefined)
|
|
134
|
+
config.resources.runtime.cpu = o.cpu;
|
|
135
|
+
if (o.memory !== undefined)
|
|
136
|
+
config.resources.runtime.memory = o.memory;
|
|
137
|
+
if (o.browser !== undefined)
|
|
138
|
+
config.browser.enabled = o.browser;
|
|
139
|
+
if (o.connections !== undefined)
|
|
140
|
+
config.connections.mode = o.connections ? "hosted" : "disabled";
|
|
141
|
+
if (o.connectionsCloudUrl)
|
|
142
|
+
config.connections.cloudUrl = o.connectionsCloudUrl;
|
|
143
|
+
const https = o.origin || o.widgetOrigin || o.tlsCertificate || o.tlsKeyFile;
|
|
144
|
+
if (https) {
|
|
145
|
+
if (o.port !== undefined || o.widgetPort !== undefined)
|
|
146
|
+
invalid("Use local ports or HTTPS origins, not both.");
|
|
147
|
+
if (!o.origin || !o.widgetOrigin || !o.tlsCertificate)
|
|
148
|
+
invalid("HTTPS requires --origin, --widget-origin and --tls-certificate.");
|
|
149
|
+
if (!o.origin.startsWith("https:") || !o.widgetOrigin.startsWith("https:"))
|
|
150
|
+
invalid("Public origins must use HTTPS.");
|
|
151
|
+
config.exposure = {
|
|
152
|
+
mode: "https",
|
|
153
|
+
applicationOrigin: o.origin,
|
|
154
|
+
widgetOrigin: o.widgetOrigin,
|
|
155
|
+
certificateFile: o.tlsCertificate,
|
|
156
|
+
keyFile: o.tlsKeyFile ?? "",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
else if (config.exposure.mode === "local") {
|
|
160
|
+
config.exposure.applicationPort = o.port ?? config.exposure.applicationPort;
|
|
161
|
+
config.exposure.widgetPort = o.widgetPort ?? config.exposure.widgetPort;
|
|
162
|
+
if (config.exposure.applicationPort === config.exposure.widgetPort)
|
|
163
|
+
invalid("Application and widgets must use different ports.");
|
|
164
|
+
}
|
|
165
|
+
const oidc = o.oidcIssuer || o.oidcClientId || o.oidcSecretFile;
|
|
166
|
+
if (oidc || o.access === "oidc") {
|
|
167
|
+
if (o.access === "hosted")
|
|
168
|
+
invalid("Company OIDC options conflict with --access hosted.");
|
|
169
|
+
if (!o.oidcIssuer || !o.oidcClientId)
|
|
170
|
+
invalid("Company login requires --oidc-issuer and --oidc-client-id.");
|
|
171
|
+
config.access = {
|
|
172
|
+
mode: "oidc",
|
|
173
|
+
administratorName: config.access.administratorName,
|
|
174
|
+
issuer: o.oidcIssuer,
|
|
175
|
+
clientId: o.oidcClientId,
|
|
176
|
+
clientSecretFile: o.oidcSecretFile ?? "",
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (Boolean(o.administratorSubject) !== Boolean(o.administratorEmail))
|
|
180
|
+
invalid("Supply both --administrator-subject and --administrator-email.");
|
|
181
|
+
if (o.administratorSubject && o.administratorEmail) {
|
|
182
|
+
config.access.administratorSubject = o.administratorSubject;
|
|
183
|
+
config.access.administratorEmail = o.administratorEmail;
|
|
184
|
+
}
|
|
185
|
+
if (o.pack && o.packs === false)
|
|
186
|
+
invalid("Use --pack or --no-packs, not both.");
|
|
187
|
+
if (o.packs === false)
|
|
188
|
+
config.packs = [];
|
|
189
|
+
if (o.pack) {
|
|
190
|
+
config.packs = [];
|
|
191
|
+
for (const choice of o.pack) {
|
|
192
|
+
const [id, members, extra] = choice.split(":");
|
|
193
|
+
if (!id ||
|
|
194
|
+
!members ||
|
|
195
|
+
extra !== undefined ||
|
|
196
|
+
!context.packs.some((pack) => pack.id === id))
|
|
197
|
+
invalid("Use --pack <pack-id:member,member>; list available packs with clawscarf recipes.");
|
|
198
|
+
const directory = context.packs.find((pack) => pack.id === id)?.directory;
|
|
199
|
+
if (!directory)
|
|
200
|
+
invalid(`Unknown pack: ${id}`);
|
|
201
|
+
const pack = await openPack(directory);
|
|
202
|
+
const selected = members.split(",");
|
|
203
|
+
if (config.packs.some((item) => item.directory === directory) ||
|
|
204
|
+
new Set(selected).size !== selected.length ||
|
|
205
|
+
selected.some((member) => !pack.manifest.members.some((item) => item.id === member)))
|
|
206
|
+
invalid("Select each pack once and use its declared member IDs.");
|
|
207
|
+
config.packs.push({ directory, members: selected });
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
for (const binding of o.packBindings ?? []) {
|
|
211
|
+
const separator = binding.indexOf("=");
|
|
212
|
+
const id = binding.slice(0, separator), path = binding.slice(separator + 1);
|
|
213
|
+
const selected = config.packs.find((pack) => pack.directory ===
|
|
214
|
+
context.packs.find((pack) => pack.id === id)?.directory);
|
|
215
|
+
if (separator < 1 || !path || !selected)
|
|
216
|
+
invalid("Use --pack-bindings <selected-pack-id=file>.");
|
|
217
|
+
selected.bindingsFile = resolve(path);
|
|
218
|
+
}
|
|
219
|
+
if (o.packPython && !config.packs.length)
|
|
220
|
+
invalid("--pack-python requires a selected pack.");
|
|
221
|
+
if (!config.packs.length)
|
|
222
|
+
delete config.packOperator;
|
|
223
|
+
else if (o.packPython)
|
|
224
|
+
config.packOperator = {
|
|
225
|
+
pythonExecutable: o.packPython,
|
|
226
|
+
experimentalClaws: true,
|
|
227
|
+
};
|
|
228
|
+
const preset = context.recipes.find((item) => item.id === recipe)?.models;
|
|
229
|
+
if (!config.models && preset)
|
|
230
|
+
config.models = {
|
|
231
|
+
mode: "litellm",
|
|
232
|
+
configurationFile: recipeModelFile(preset, inputs, context.modelCatalog),
|
|
233
|
+
upstreamEnvironmentFile: "",
|
|
234
|
+
};
|
|
235
|
+
if (o.modelCatalog)
|
|
236
|
+
config.models =
|
|
237
|
+
config.models?.mode === "external"
|
|
238
|
+
? { ...config.models, configurationFile: o.modelCatalog }
|
|
239
|
+
: {
|
|
240
|
+
mode: "litellm",
|
|
241
|
+
configurationFile: o.modelCatalog,
|
|
242
|
+
upstreamEnvironmentFile: "",
|
|
243
|
+
};
|
|
244
|
+
const current = config.models;
|
|
245
|
+
const catalog = current
|
|
246
|
+
? await inputs.readJson(current.configurationFile)
|
|
247
|
+
: undefined;
|
|
248
|
+
if (o.modelGatewayUrl) {
|
|
249
|
+
if (!catalog)
|
|
250
|
+
invalid("An existing gateway requires a recipe model or --model-catalog.");
|
|
251
|
+
const parsed = z
|
|
252
|
+
.union([gatewayRoutesSchema, configurationSchema])
|
|
253
|
+
.parse(catalog);
|
|
254
|
+
if ("mode" in parsed && parsed.mode === "disabled")
|
|
255
|
+
invalid("The model gateway must be enabled.");
|
|
256
|
+
config.models = {
|
|
257
|
+
mode: "external",
|
|
258
|
+
configurationFile: inputs.set("external-models.json", JSON.stringify(configurationSchema.parse({
|
|
259
|
+
...parsed,
|
|
260
|
+
mode: "external",
|
|
261
|
+
baseUrl: o.modelGatewayUrl,
|
|
262
|
+
}))),
|
|
263
|
+
credentialFile: o.modelGatewayKeyFile ?? "",
|
|
264
|
+
...(o.modelGatewayCaFile ? { caFile: o.modelGatewayCaFile } : {}),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
else if (o.modelGatewayKeyFile || o.modelGatewayCaFile) {
|
|
268
|
+
if (config.models?.mode !== "external")
|
|
269
|
+
invalid("Gateway credentials require --model-gateway-url.");
|
|
270
|
+
if (o.modelGatewayKeyFile)
|
|
271
|
+
config.models.credentialFile = o.modelGatewayKeyFile;
|
|
272
|
+
if (o.modelGatewayCaFile)
|
|
273
|
+
config.models.caFile = o.modelGatewayCaFile;
|
|
274
|
+
}
|
|
275
|
+
if (o.model || o.provider || o.reasoning) {
|
|
276
|
+
if (config.models?.mode === "external") {
|
|
277
|
+
if (o.provider)
|
|
278
|
+
invalid("Providers for an existing gateway are configured by its operator.");
|
|
279
|
+
const parsed = configurationSchema.parse(await inputs.readJson(config.models.configurationFile));
|
|
280
|
+
if (parsed.mode !== "external")
|
|
281
|
+
invalid("Expected an external model gateway.");
|
|
282
|
+
const id = o.model ?? parsed.defaultModel;
|
|
283
|
+
const model = parsed.models.find((item) => item.id === id && item.enabled);
|
|
284
|
+
if (!model)
|
|
285
|
+
invalid("Select an enabled model from the gateway catalog.");
|
|
286
|
+
if (o.reasoning && !model.reasoning)
|
|
287
|
+
invalid("This model does not support reasoning levels.");
|
|
288
|
+
const { thinkingDefault: previous, ...base } = parsed;
|
|
289
|
+
config.models.configurationFile = inputs.set("external-models.json", JSON.stringify({
|
|
290
|
+
...base,
|
|
291
|
+
defaultModel: id,
|
|
292
|
+
...(model.reasoning && (o.reasoning ?? previous)
|
|
293
|
+
? { thinkingDefault: o.reasoning ?? previous }
|
|
294
|
+
: {}),
|
|
295
|
+
}));
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
const routes = catalog ? gatewayRoutesSchema.parse(catalog) : undefined;
|
|
299
|
+
const id = o.model ?? routes?.defaultModel;
|
|
300
|
+
if (!id)
|
|
301
|
+
invalid("Select --model before choosing a provider or reasoning level.");
|
|
302
|
+
const existing = routes?.models.find((item) => item.id === id);
|
|
303
|
+
const offers = [...context.modelCatalog];
|
|
304
|
+
if (existing?.route &&
|
|
305
|
+
!offers.some((offer) => offer.model.id === id &&
|
|
306
|
+
offer.model.route.model === existing.route?.model))
|
|
307
|
+
offers.unshift({
|
|
308
|
+
provider: existing.route.model.split("/")[0] ?? "",
|
|
309
|
+
model: { ...existing, route: existing.route },
|
|
310
|
+
reasoningLevels: existing.reasoning ? ["low", "medium", "high"] : [],
|
|
311
|
+
});
|
|
312
|
+
const matching = offers.filter((offer) => offer.model.id === id &&
|
|
313
|
+
(!o.provider || offer.model.route.model.split("/")[0] === o.provider));
|
|
314
|
+
const offer = matching.find((item) => item.model.route.model === existing?.route?.model) ?? matching[0];
|
|
315
|
+
if (!offer)
|
|
316
|
+
invalid("Unknown model/provider selection. Use clawscarf recipes to see the model catalog.");
|
|
317
|
+
if (o.reasoning && !offer.reasoningLevels.includes(o.reasoning))
|
|
318
|
+
invalid("The selected model/provider does not support this reasoning level.");
|
|
319
|
+
const thinking = o.reasoning ??
|
|
320
|
+
(routes?.thinkingDefault &&
|
|
321
|
+
offer.reasoningLevels.includes(routes.thinkingDefault)
|
|
322
|
+
? routes.thinkingDefault
|
|
323
|
+
: offer.reasoningLevels.includes("medium")
|
|
324
|
+
? "medium"
|
|
325
|
+
: offer.reasoningLevels[0]);
|
|
326
|
+
config.models = selectModel(current, routes, offer, thinking, inputs, Boolean(retained));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (o.llmKeyFile && o.providerEnvFile)
|
|
330
|
+
invalid("Use --llm-key-file or --provider-env-file, not both.");
|
|
331
|
+
if (config.models?.mode === "external" && (o.llmKeyFile || o.providerEnvFile))
|
|
332
|
+
invalid("Use --model-gateway-key-file for an existing gateway.");
|
|
333
|
+
if (!config.models && (o.llmKeyFile || o.providerEnvFile))
|
|
334
|
+
invalid("Select a recipe model or --model before supplying credentials.");
|
|
335
|
+
if (config.models?.mode === "litellm") {
|
|
336
|
+
if (o.providerEnvFile)
|
|
337
|
+
config.models.upstreamEnvironmentFile = o.providerEnvFile;
|
|
338
|
+
if (o.llmKeyFile) {
|
|
339
|
+
const routes = gatewayRoutesSchema.parse(await inputs.readJson(config.models.configurationFile));
|
|
340
|
+
const variables = new Set(routes.models
|
|
341
|
+
.filter((model) => model.enabled)
|
|
342
|
+
.map((model) => model.route?.apiKeyEnv));
|
|
343
|
+
const [variable] = variables;
|
|
344
|
+
if (variables.size !== 1 || !variable)
|
|
345
|
+
invalid("Multiple provider keys require --provider-env-file.");
|
|
346
|
+
const key = (await readInputFile(o.llmKeyFile, true))
|
|
347
|
+
.toString("utf8")
|
|
348
|
+
.trim();
|
|
349
|
+
if (!key || /[\r\n'"`]/.test(key))
|
|
350
|
+
invalid("The LLM key file must contain one nonempty key without quotes.");
|
|
351
|
+
config.models.upstreamEnvironmentFile = inputs.set("models.env", `${variable}='${key}'\n`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (!retained) {
|
|
355
|
+
if (config.access.mode === "hosted")
|
|
356
|
+
config.access.registrationFile = resolve(inputs.directory, "secrets/hosted-login.json");
|
|
357
|
+
config.connections.registrationFile = resolve(inputs.directory, "secrets/connections-registration.json");
|
|
358
|
+
}
|
|
359
|
+
return config;
|
|
360
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { OperatorError } from "../errors.js";
|
|
2
|
+
import { mkdir, readFile, readdir, rm } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { openPack } from "../packs/source.js";
|
|
6
|
+
import { planPack, applyPack } from "../packs/lifecycle.js";
|
|
7
|
+
import { planSchema } from "../packs/model.js";
|
|
8
|
+
import { OpenShellClaws } from "../packs/openshell.js";
|
|
9
|
+
import { readState, resourceNames, writePrivate } from "../deployment/state.js";
|
|
10
|
+
import { fingerprint, readJson } from "./files.js";
|
|
11
|
+
export const selectionSchema = z.strictObject({
|
|
12
|
+
python: z.string().optional(),
|
|
13
|
+
packs: z.array(z.strictObject({
|
|
14
|
+
directory: z.string(),
|
|
15
|
+
digest: z.string(),
|
|
16
|
+
members: z.array(z.string()),
|
|
17
|
+
bindingsFile: z.string().optional(),
|
|
18
|
+
bindingsDigest: z.string().optional(),
|
|
19
|
+
})),
|
|
20
|
+
});
|
|
21
|
+
export const packOutcomeSchema = z.strictObject({
|
|
22
|
+
member: z.string(),
|
|
23
|
+
state: z.enum(["complete", "blocked", "unconfirmed"]),
|
|
24
|
+
});
|
|
25
|
+
/** Called within the installation's existing lifecycle lock, before readiness is announced. */
|
|
26
|
+
export async function activatePacks(directory, report, target) {
|
|
27
|
+
const selection = selectionSchema.parse(await readJson(join(directory, "packs.json")));
|
|
28
|
+
const receipts = join(directory, "pack-operations");
|
|
29
|
+
await mkdir(receipts, { recursive: true, mode: 0o700 });
|
|
30
|
+
const files = (await readdir(receipts)).filter((file) => file.endsWith(".json") && !file.endsWith("-plan.json"));
|
|
31
|
+
if (!selection.packs.length && !files.length)
|
|
32
|
+
return [];
|
|
33
|
+
if (!selection.python)
|
|
34
|
+
throw new OperatorError("Pack operator is required while removing retained packs.");
|
|
35
|
+
const state = await readState(directory);
|
|
36
|
+
const controller = join(directory, "controller");
|
|
37
|
+
const gateway = resourceNames(state).sandbox;
|
|
38
|
+
const native = target ??
|
|
39
|
+
new OpenShellClaws({
|
|
40
|
+
executable: state.input.openshellCli,
|
|
41
|
+
python: selection.python,
|
|
42
|
+
sandbox: gateway,
|
|
43
|
+
gateway,
|
|
44
|
+
env: {
|
|
45
|
+
...process.env,
|
|
46
|
+
OPENCLAW_EXPERIMENTAL_CLAWS: "1",
|
|
47
|
+
XDG_CONFIG_HOME: join(controller, "config"),
|
|
48
|
+
XDG_STATE_HOME: join(controller, "state"),
|
|
49
|
+
XDG_DATA_HOME: join(controller, "data"),
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
const operations = selection.packs.flatMap((pack) => pack.members.map((member) => ({ pack, member, remove: false })));
|
|
53
|
+
const selected = new Set(operations.map((item) => item.member));
|
|
54
|
+
for (const file of files) {
|
|
55
|
+
const member = file.slice(0, -5);
|
|
56
|
+
if (selected.has(member))
|
|
57
|
+
continue;
|
|
58
|
+
const previous = planSchema.parse(await readJson(join(receipts, member + "-plan.json")));
|
|
59
|
+
if (previous.member !== member)
|
|
60
|
+
throw new OperatorError("Pack receipt identity changed.");
|
|
61
|
+
operations.push({
|
|
62
|
+
member,
|
|
63
|
+
remove: true,
|
|
64
|
+
pack: {
|
|
65
|
+
directory: previous.pack,
|
|
66
|
+
digest: previous.packDigest,
|
|
67
|
+
members: [member],
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const outcomes = [];
|
|
72
|
+
for (const { pack, member, remove } of operations) {
|
|
73
|
+
let operation = remove ? "remove" : "add";
|
|
74
|
+
let attempted = false;
|
|
75
|
+
try {
|
|
76
|
+
const path = join(receipts, member + ".json");
|
|
77
|
+
const selectionDigest = fingerprint(JSON.stringify({
|
|
78
|
+
digest: pack.digest,
|
|
79
|
+
bindingsDigest: pack.bindingsDigest,
|
|
80
|
+
member,
|
|
81
|
+
}));
|
|
82
|
+
try {
|
|
83
|
+
const retained = z
|
|
84
|
+
.strictObject({
|
|
85
|
+
ownerId: z.literal(state.ownerId),
|
|
86
|
+
selectionDigest: z.string(),
|
|
87
|
+
state: z.enum(["pending", "complete"]),
|
|
88
|
+
})
|
|
89
|
+
.parse(await readJson(path));
|
|
90
|
+
if (retained.state === "complete" &&
|
|
91
|
+
!remove &&
|
|
92
|
+
retained.selectionDigest === selectionDigest) {
|
|
93
|
+
outcomes.push({ member, state: "complete" });
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (retained.state === "complete") {
|
|
97
|
+
operation = remove ? "remove" : "update";
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
attempted = true;
|
|
101
|
+
throw new OperatorError("A pack mutation has an unconfirmed outcome. Inspect native Claws before an explicit change; startup will not repeat it.");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (!(error instanceof Error &&
|
|
106
|
+
"code" in error &&
|
|
107
|
+
error.code === "ENOENT"))
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
if ((await openPack(pack.directory)).digest !== pack.digest ||
|
|
111
|
+
(pack.bindingsFile &&
|
|
112
|
+
fingerprint(await readFile(pack.bindingsFile)) !==
|
|
113
|
+
pack.bindingsDigest))
|
|
114
|
+
throw new OperatorError("Selected pack inputs changed. Review a new installation plan.");
|
|
115
|
+
report(`${operation === "remove" ? "Removing" : operation === "update" ? "Updating" : "Installing"} pack member ${member}…`);
|
|
116
|
+
const plan = await planPack({
|
|
117
|
+
directory: pack.directory,
|
|
118
|
+
member,
|
|
119
|
+
operation,
|
|
120
|
+
workspace: `/home/node/.openclaw/workspace-${member}`,
|
|
121
|
+
...(pack.bindingsFile ? { bindingsFile: pack.bindingsFile } : {}),
|
|
122
|
+
}, native);
|
|
123
|
+
await writePrivate(join(receipts, member + "-plan.json"), JSON.stringify(plan));
|
|
124
|
+
attempted = true;
|
|
125
|
+
await writePrivate(path, JSON.stringify({
|
|
126
|
+
ownerId: state.ownerId,
|
|
127
|
+
selectionDigest,
|
|
128
|
+
state: "pending",
|
|
129
|
+
}));
|
|
130
|
+
await applyPack(plan, native, pack.bindingsFile);
|
|
131
|
+
await writePrivate(path, JSON.stringify({
|
|
132
|
+
ownerId: state.ownerId,
|
|
133
|
+
selectionDigest,
|
|
134
|
+
state: "complete",
|
|
135
|
+
}));
|
|
136
|
+
if (remove) {
|
|
137
|
+
await rm(path);
|
|
138
|
+
await rm(join(receipts, member + "-plan.json"));
|
|
139
|
+
}
|
|
140
|
+
outcomes.push({ member, state: "complete" });
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
const state = attempted ? "unconfirmed" : "blocked";
|
|
144
|
+
outcomes.push({ member, state });
|
|
145
|
+
report(`Pack member ${member}: ${state}. Inspect its native Claws plan and prerequisites; no mutation was replayed. The team server remains available.`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
await writePrivate(join(directory, "pack-status.json"), JSON.stringify(outcomes));
|
|
149
|
+
return outcomes;
|
|
150
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { dirname, join, resolve } from "node:path";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { isDeepStrictEqual } from "node:util";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { prepareLocal } from "../deployment/prepare.js";
|
|
6
|
+
import { readState, ensurePrivateFile, withInstallationLock, } from "../deployment/state.js";
|
|
7
|
+
import { fingerprint, readJson } from "./files.js";
|
|
8
|
+
import { InstallationError } from "./errors.js";
|
|
9
|
+
import { allocatePorts, resolveInstallation } from "./resolve.js";
|
|
10
|
+
import { resolveConfigurationInputs } from "./configure.js";
|
|
11
|
+
import { installationSchema } from "./configuration.js";
|
|
12
|
+
export const planSchema = z.strictObject({
|
|
13
|
+
schemaVersion: z.literal(1),
|
|
14
|
+
stateDirectory: z.string(),
|
|
15
|
+
fingerprint: z.string(),
|
|
16
|
+
observedState: z.string().nullable(),
|
|
17
|
+
internalPorts: z.array(z.number().int().min(1024).max(65535)).length(7),
|
|
18
|
+
action: z.enum(["prepare", "resume"]),
|
|
19
|
+
release: z.string(),
|
|
20
|
+
browser: z.boolean(),
|
|
21
|
+
capabilities: z.strictObject({
|
|
22
|
+
models: z.enum(["external", "litellm"]),
|
|
23
|
+
connections: z.enum(["disabled", "hosted"]),
|
|
24
|
+
packs: z.array(z.strictObject({ directory: z.string(), members: z.array(z.string()) })),
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
async function observation(directory) {
|
|
28
|
+
try {
|
|
29
|
+
await readState(directory);
|
|
30
|
+
return fingerprint(await readFile(join(directory, "installation.json")));
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
34
|
+
return null;
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export async function planInstallation(configFile) {
|
|
39
|
+
const config = installationSchema.parse(await readJson(configFile));
|
|
40
|
+
const directory = resolve(dirname(resolve(configFile)), config.stateDirectory);
|
|
41
|
+
const observed = await observation(directory);
|
|
42
|
+
const state = observed ? await readState(directory) : null;
|
|
43
|
+
const input = state?.input;
|
|
44
|
+
const internalPorts = input
|
|
45
|
+
? [
|
|
46
|
+
input.ports.controller,
|
|
47
|
+
input.ports.management,
|
|
48
|
+
input.ports.native,
|
|
49
|
+
input.ports.nativeWidgets,
|
|
50
|
+
input.ports.database,
|
|
51
|
+
input.browser?.port ?? 65534,
|
|
52
|
+
input.modelGateway?.port ?? 65533,
|
|
53
|
+
]
|
|
54
|
+
: await allocatePorts();
|
|
55
|
+
const resolved = await resolveInstallation(configFile, internalPorts);
|
|
56
|
+
if (state && !isDeepStrictEqual(state.input, resolved.input))
|
|
57
|
+
throw new InstallationError("change_unsupported", "This change requires an explicit configuration or upgrade operation. Prepare never replaces retained settings.");
|
|
58
|
+
await verifyRetainedInputs(directory, resolved.fingerprint);
|
|
59
|
+
return planSchema.parse({
|
|
60
|
+
schemaVersion: 1,
|
|
61
|
+
stateDirectory: directory,
|
|
62
|
+
fingerprint: resolved.fingerprint,
|
|
63
|
+
observedState: observed,
|
|
64
|
+
internalPorts,
|
|
65
|
+
action: state ? "resume" : "prepare",
|
|
66
|
+
release: resolved.release.version,
|
|
67
|
+
browser: config.browser.enabled,
|
|
68
|
+
capabilities: {
|
|
69
|
+
models: config.models.mode,
|
|
70
|
+
connections: config.connections.mode,
|
|
71
|
+
packs: resolved.packSelection.packs.map(({ directory, members }) => ({
|
|
72
|
+
directory,
|
|
73
|
+
members,
|
|
74
|
+
})),
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export async function applyInstallation(configFile, planFile) {
|
|
79
|
+
const plan = planSchema.parse(await readJson(planFile));
|
|
80
|
+
const resolved = await resolveInstallation(configFile, plan.internalPorts);
|
|
81
|
+
if (resolved.fingerprint !== plan.fingerprint ||
|
|
82
|
+
resolved.stateDirectory !== plan.stateDirectory)
|
|
83
|
+
throw new InstallationError("stale_plan", "Configuration or release inputs changed. Create a new plan.");
|
|
84
|
+
return withInstallationLock(plan.stateDirectory, async () => {
|
|
85
|
+
if ((await observation(plan.stateDirectory)) !== plan.observedState)
|
|
86
|
+
throw new InstallationError("stale_plan", "Installation state changed. Create a new plan before resuming.");
|
|
87
|
+
await verifyRetainedInputs(resolved.stateDirectory, resolved.fingerprint);
|
|
88
|
+
// The existing operator records identity before effects and verifies retained resource ownership.
|
|
89
|
+
await prepareLocal(resolved.stateDirectory, resolved.input, {
|
|
90
|
+
inputFingerprint: resolved.fingerprint,
|
|
91
|
+
...(resolved.config.connections.mode === "disabled"
|
|
92
|
+
? {}
|
|
93
|
+
: {
|
|
94
|
+
connections: resolved.connectorCredentialFile
|
|
95
|
+
? { credentialFile: resolved.connectorCredentialFile }
|
|
96
|
+
: {},
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
await ensurePrivateFile(join(resolved.stateDirectory, "release.json"), JSON.stringify(resolved.release, null, 2));
|
|
100
|
+
await ensurePrivateFile(join(resolved.stateDirectory, "packs.json"), JSON.stringify(resolved.packSelection));
|
|
101
|
+
await ensurePrivateFile(join(resolved.stateDirectory, "settings.json"), JSON.stringify({
|
|
102
|
+
...resolveConfigurationInputs(resolved.config, dirname(resolve(configFile))),
|
|
103
|
+
stateDirectory: resolved.stateDirectory,
|
|
104
|
+
}, null, 2));
|
|
105
|
+
return {
|
|
106
|
+
state: "prepared",
|
|
107
|
+
directory: resolved.stateDirectory,
|
|
108
|
+
release: resolved.release.version,
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async function verifyRetainedInputs(directory, expected) {
|
|
113
|
+
try {
|
|
114
|
+
if ((await readFile(join(directory, "inputs.sha256"), "utf8")) !== expected)
|
|
115
|
+
throw new InstallationError("change_unsupported", "Saved capability inputs changed. Run clawscarf configure --directory to review changes; startup does not reapply or rotate native settings.");
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|