@ours.network/install 1.2.1-nightly.8 → 1.2.1-nightly.9
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/assets/release-lock.json +46 -46
- package/assets/release.json +29 -19
- package/assets/scripts/maintenance/release-graph.mjs +18 -1
- package/assets/sources.json +38 -28
- package/lib/client-cli.mjs +117 -0
- package/lib/effects.mjs +23 -7
- package/lib/orchestrate.mjs +3 -3
- package/lib/setup.mjs +9 -9
- package/package.json +1 -1
package/assets/release-lock.json
CHANGED
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
"name": "ours-release-set-verification",
|
|
9
9
|
"version": "0.0.0",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@ours.network/claude-code": "1.2.0-nightly.
|
|
12
|
-
"@ours.network/cli": "2.8.1-nightly.
|
|
13
|
-
"@ours.network/codex": "1.2.0-nightly.
|
|
14
|
-
"@ours.network/cowork": "1.3.2-nightly.
|
|
15
|
-
"@ours.network/fleet": "1.2.0-nightly.
|
|
16
|
-
"@ours.network/mcp": "1.2.0-nightly.
|
|
17
|
-
"@ours.network/messenger-server": "1.0.31-nightly.
|
|
18
|
-
"@ours.network/sdk": "3.8.1-nightly.
|
|
19
|
-
"@ours.network/tg-connector": "1.0.1-nightly.
|
|
11
|
+
"@ours.network/claude-code": "1.2.0-nightly.4",
|
|
12
|
+
"@ours.network/cli": "2.8.1-nightly.2",
|
|
13
|
+
"@ours.network/codex": "1.2.0-nightly.4",
|
|
14
|
+
"@ours.network/cowork": "1.3.2-nightly.20260919.5e6e5fd",
|
|
15
|
+
"@ours.network/fleet": "1.2.0-nightly.2",
|
|
16
|
+
"@ours.network/mcp": "1.2.0-nightly.4",
|
|
17
|
+
"@ours.network/messenger-server": "1.0.31-nightly.2",
|
|
18
|
+
"@ours.network/sdk": "3.8.1-nightly.4",
|
|
19
|
+
"@ours.network/tg-connector": "1.0.1-nightly.2"
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"node_modules/@adapt-toolkit/sdk": {
|
|
@@ -1051,13 +1051,13 @@
|
|
|
1051
1051
|
}
|
|
1052
1052
|
},
|
|
1053
1053
|
"node_modules/@ours.network/claude-code": {
|
|
1054
|
-
"version": "1.2.0-nightly.
|
|
1055
|
-
"resolved": "https://registry.npmjs.org/@ours.network/claude-code/-/claude-code-1.2.0-nightly.
|
|
1056
|
-
"integrity": "sha512-
|
|
1054
|
+
"version": "1.2.0-nightly.4",
|
|
1055
|
+
"resolved": "https://registry.npmjs.org/@ours.network/claude-code/-/claude-code-1.2.0-nightly.4.tgz",
|
|
1056
|
+
"integrity": "sha512-JNDhTMtxO2Xm0NNL/5Kgra/ovr62mP0YFcuvYJRRA+SH6+2Ick8eegeSVmzU97HPLBCTi8DwNqfkUxcCQUpwXA==",
|
|
1057
1057
|
"license": "FSL-1.1-Apache-2.0",
|
|
1058
1058
|
"dependencies": {
|
|
1059
1059
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
1060
|
-
"@ours.network/sdk": "3.8.1-nightly.
|
|
1060
|
+
"@ours.network/sdk": "3.8.1-nightly.4",
|
|
1061
1061
|
"ws": "^8.21.0",
|
|
1062
1062
|
"zod": "^3.25.76"
|
|
1063
1063
|
},
|
|
@@ -1066,14 +1066,14 @@
|
|
|
1066
1066
|
}
|
|
1067
1067
|
},
|
|
1068
1068
|
"node_modules/@ours.network/cli": {
|
|
1069
|
-
"version": "2.8.1-nightly.
|
|
1070
|
-
"resolved": "https://registry.npmjs.org/@ours.network/cli/-/cli-2.8.1-nightly.
|
|
1071
|
-
"integrity": "sha512-
|
|
1069
|
+
"version": "2.8.1-nightly.2",
|
|
1070
|
+
"resolved": "https://registry.npmjs.org/@ours.network/cli/-/cli-2.8.1-nightly.2.tgz",
|
|
1071
|
+
"integrity": "sha512-YirjiR6/Xs1p2LKOLVtao74xLXCsxgHz3gwbKyZ4Qtd9w6MF+kS/OEOLKozmIjWJ5FoqUxgvnnaudl5vhaeUkA==",
|
|
1072
1072
|
"license": "FSL-1.1-Apache-2.0",
|
|
1073
1073
|
"dependencies": {
|
|
1074
1074
|
"@adapt-toolkit/sdk": "0.10.13-nightly.1",
|
|
1075
1075
|
"@adapt-toolkit/sdk-native": "0.10.13-nightly.1",
|
|
1076
|
-
"@ours.network/sdk": "3.8.1-nightly.
|
|
1076
|
+
"@ours.network/sdk": "3.8.1-nightly.4",
|
|
1077
1077
|
"pg": "^8.16.3"
|
|
1078
1078
|
},
|
|
1079
1079
|
"bin": {
|
|
@@ -1087,13 +1087,13 @@
|
|
|
1087
1087
|
}
|
|
1088
1088
|
},
|
|
1089
1089
|
"node_modules/@ours.network/codex": {
|
|
1090
|
-
"version": "1.2.0-nightly.
|
|
1091
|
-
"resolved": "https://registry.npmjs.org/@ours.network/codex/-/codex-1.2.0-nightly.
|
|
1092
|
-
"integrity": "sha512-
|
|
1090
|
+
"version": "1.2.0-nightly.4",
|
|
1091
|
+
"resolved": "https://registry.npmjs.org/@ours.network/codex/-/codex-1.2.0-nightly.4.tgz",
|
|
1092
|
+
"integrity": "sha512-ZYzqWWcPv9snZH86AZxGfkT2lc9djIJuvBBWdDC7j7YJRjXVQfTVGNFtc5NoGesQYiiQFhiumSppMxfc3vHVDA==",
|
|
1093
1093
|
"license": "FSL-1.1-Apache-2.0",
|
|
1094
1094
|
"dependencies": {
|
|
1095
1095
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
1096
|
-
"@ours.network/sdk": "3.8.1-nightly.
|
|
1096
|
+
"@ours.network/sdk": "3.8.1-nightly.4",
|
|
1097
1097
|
"ws": "^8.21.0",
|
|
1098
1098
|
"zod": "^3.25.76"
|
|
1099
1099
|
},
|
|
@@ -1106,12 +1106,12 @@
|
|
|
1106
1106
|
}
|
|
1107
1107
|
},
|
|
1108
1108
|
"node_modules/@ours.network/cowork": {
|
|
1109
|
-
"version": "1.3.2-nightly.
|
|
1110
|
-
"resolved": "https://registry.npmjs.org/@ours.network/cowork/-/cowork-1.3.2-nightly.
|
|
1111
|
-
"integrity": "sha512
|
|
1109
|
+
"version": "1.3.2-nightly.20260919.5e6e5fd",
|
|
1110
|
+
"resolved": "https://registry.npmjs.org/@ours.network/cowork/-/cowork-1.3.2-nightly.20260919.5e6e5fd.tgz",
|
|
1111
|
+
"integrity": "sha512-/3KEKXqriBsPiK8POyL/XgbP4ELsRs6quv0Q47cFCaesXAR4W44Q3NVs43brnj1AwMLroip0iXzqwlsJvpoaTw==",
|
|
1112
1112
|
"license": "FSL-1.1-Apache-2.0",
|
|
1113
1113
|
"dependencies": {
|
|
1114
|
-
"@ours.network/sdk": "3.8.1-nightly.
|
|
1114
|
+
"@ours.network/sdk": "3.8.1-nightly.4",
|
|
1115
1115
|
"ajv": "8.20.0",
|
|
1116
1116
|
"better-sqlite3": "13.0.3",
|
|
1117
1117
|
"react": "18.3.1",
|
|
@@ -1126,16 +1126,16 @@
|
|
|
1126
1126
|
}
|
|
1127
1127
|
},
|
|
1128
1128
|
"node_modules/@ours.network/fleet": {
|
|
1129
|
-
"version": "1.2.0-nightly.
|
|
1130
|
-
"resolved": "https://registry.npmjs.org/@ours.network/fleet/-/fleet-1.2.0-nightly.
|
|
1131
|
-
"integrity": "sha512-
|
|
1129
|
+
"version": "1.2.0-nightly.2",
|
|
1130
|
+
"resolved": "https://registry.npmjs.org/@ours.network/fleet/-/fleet-1.2.0-nightly.2.tgz",
|
|
1131
|
+
"integrity": "sha512-vnTGjjl2ycd7FN4CKGblemFcq5/MWocTR0CQhGEbspYXRYiojGa3MRyLzJPOVJQbP0/zqIwSG5Vk9czMWHpjow==",
|
|
1132
1132
|
"license": "FSL-1.1-Apache-2.0",
|
|
1133
1133
|
"dependencies": {
|
|
1134
1134
|
"@agentclientprotocol/sdk": "^1.3.0",
|
|
1135
1135
|
"@fastify/static": "^10.1.2",
|
|
1136
1136
|
"@fastify/websocket": "^11.2.0",
|
|
1137
|
-
"@ours.network/cli": "2.8.1-nightly.
|
|
1138
|
-
"@ours.network/sdk": "3.8.1-nightly.
|
|
1137
|
+
"@ours.network/cli": "2.8.1-nightly.2",
|
|
1138
|
+
"@ours.network/sdk": "3.8.1-nightly.4",
|
|
1139
1139
|
"commander": "^12.1.0",
|
|
1140
1140
|
"fastify": "^5.4.0",
|
|
1141
1141
|
"react": "^19.1.1",
|
|
@@ -1183,13 +1183,13 @@
|
|
|
1183
1183
|
"license": "MIT"
|
|
1184
1184
|
},
|
|
1185
1185
|
"node_modules/@ours.network/mcp": {
|
|
1186
|
-
"version": "1.2.0-nightly.
|
|
1187
|
-
"resolved": "https://registry.npmjs.org/@ours.network/mcp/-/mcp-1.2.0-nightly.
|
|
1188
|
-
"integrity": "sha512-
|
|
1186
|
+
"version": "1.2.0-nightly.4",
|
|
1187
|
+
"resolved": "https://registry.npmjs.org/@ours.network/mcp/-/mcp-1.2.0-nightly.4.tgz",
|
|
1188
|
+
"integrity": "sha512-NcVDRmwOHx5zAwcIs6cMstSPyIEhl9f0mQNNsSbXYi+oGkamzdEPH02RHWrmoYCgvqiWjO8bhCqB43Mum8ozYg==",
|
|
1189
1189
|
"license": "FSL-1.1-Apache-2.0",
|
|
1190
1190
|
"dependencies": {
|
|
1191
|
-
"@ours.network/cli": "2.8.1-nightly.
|
|
1192
|
-
"@ours.network/sdk": "3.8.1-nightly.
|
|
1191
|
+
"@ours.network/cli": "2.8.1-nightly.2",
|
|
1192
|
+
"@ours.network/sdk": "3.8.1-nightly.4"
|
|
1193
1193
|
},
|
|
1194
1194
|
"bin": {
|
|
1195
1195
|
"ours-mcp": "dist/cli.js"
|
|
@@ -1199,14 +1199,14 @@
|
|
|
1199
1199
|
}
|
|
1200
1200
|
},
|
|
1201
1201
|
"node_modules/@ours.network/messenger-server": {
|
|
1202
|
-
"version": "1.0.31-nightly.
|
|
1203
|
-
"resolved": "https://registry.npmjs.org/@ours.network/messenger-server/-/messenger-server-1.0.31-nightly.
|
|
1204
|
-
"integrity": "sha512-
|
|
1202
|
+
"version": "1.0.31-nightly.2",
|
|
1203
|
+
"resolved": "https://registry.npmjs.org/@ours.network/messenger-server/-/messenger-server-1.0.31-nightly.2.tgz",
|
|
1204
|
+
"integrity": "sha512-yWBFSzUwoKHfn7z//uesWmEzGOYFfgFsjBr7KXhGGAeXRvGbbbt1OHOg+ZPNhastXipxOrg+oeY94pQwJClxkA==",
|
|
1205
1205
|
"license": "FSL-1.1-Apache-2.0",
|
|
1206
1206
|
"dependencies": {
|
|
1207
1207
|
"@eslint-community/regexpp": "4.12.2",
|
|
1208
1208
|
"@fontsource-variable/jetbrains-mono": "^5.3.0",
|
|
1209
|
-
"@ours.network/sdk": "3.8.1-nightly.
|
|
1209
|
+
"@ours.network/sdk": "3.8.1-nightly.4",
|
|
1210
1210
|
"@radix-ui/react-dialog": "^1.1.23",
|
|
1211
1211
|
"framer-motion": "^12.43.0",
|
|
1212
1212
|
"jsqr": "^1.4.0",
|
|
@@ -1232,9 +1232,9 @@
|
|
|
1232
1232
|
}
|
|
1233
1233
|
},
|
|
1234
1234
|
"node_modules/@ours.network/sdk": {
|
|
1235
|
-
"version": "3.8.1-nightly.
|
|
1236
|
-
"resolved": "https://registry.npmjs.org/@ours.network/sdk/-/sdk-3.8.1-nightly.
|
|
1237
|
-
"integrity": "sha512-
|
|
1235
|
+
"version": "3.8.1-nightly.4",
|
|
1236
|
+
"resolved": "https://registry.npmjs.org/@ours.network/sdk/-/sdk-3.8.1-nightly.4.tgz",
|
|
1237
|
+
"integrity": "sha512-h0euEcW9VDEhSHV5ubB1wC6KCXV89EqNCVf05F2kUx4SSvEwPNLPHtN9i21H6qlBEq3RHrHnsl1/uo1l51LCMA==",
|
|
1238
1238
|
"license": "FSL-1.1-Apache-2.0",
|
|
1239
1239
|
"workspaces": [
|
|
1240
1240
|
".",
|
|
@@ -1254,12 +1254,12 @@
|
|
|
1254
1254
|
}
|
|
1255
1255
|
},
|
|
1256
1256
|
"node_modules/@ours.network/tg-connector": {
|
|
1257
|
-
"version": "1.0.1-nightly.
|
|
1258
|
-
"resolved": "https://registry.npmjs.org/@ours.network/tg-connector/-/tg-connector-1.0.1-nightly.
|
|
1259
|
-
"integrity": "sha512
|
|
1257
|
+
"version": "1.0.1-nightly.2",
|
|
1258
|
+
"resolved": "https://registry.npmjs.org/@ours.network/tg-connector/-/tg-connector-1.0.1-nightly.2.tgz",
|
|
1259
|
+
"integrity": "sha512-XTA2vXMLrXNq7PKVXHp8HluaMzTf2tx4kDy+EOu5sWwvfiP2f4qSCx+ESeebP//TNnSHfrNnGZZiCV0uVsO49g==",
|
|
1260
1260
|
"license": "FSL-1.1-Apache-2.0",
|
|
1261
1261
|
"dependencies": {
|
|
1262
|
-
"@ours.network/sdk": "3.8.1-nightly.
|
|
1262
|
+
"@ours.network/sdk": "3.8.1-nightly.4",
|
|
1263
1263
|
"undici": "^7.28.0"
|
|
1264
1264
|
},
|
|
1265
1265
|
"bin": {
|
package/assets/release.json
CHANGED
|
@@ -1,43 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 1,
|
|
3
3
|
"channel": "nightly",
|
|
4
|
-
"installerVersion": "1.2.1-nightly.
|
|
4
|
+
"installerVersion": "1.2.1-nightly.9",
|
|
5
5
|
"packages": {
|
|
6
6
|
"@ours.network/sdk": {
|
|
7
|
-
"version": "3.8.1-nightly.
|
|
8
|
-
"integrity": "sha512-
|
|
7
|
+
"version": "3.8.1-nightly.4",
|
|
8
|
+
"integrity": "sha512-h0euEcW9VDEhSHV5ubB1wC6KCXV89EqNCVf05F2kUx4SSvEwPNLPHtN9i21H6qlBEq3RHrHnsl1/uo1l51LCMA=="
|
|
9
9
|
},
|
|
10
10
|
"@ours.network/cli": {
|
|
11
|
-
"version": "2.8.1-nightly.
|
|
12
|
-
"integrity": "sha512-
|
|
11
|
+
"version": "2.8.1-nightly.2",
|
|
12
|
+
"integrity": "sha512-YirjiR6/Xs1p2LKOLVtao74xLXCsxgHz3gwbKyZ4Qtd9w6MF+kS/OEOLKozmIjWJ5FoqUxgvnnaudl5vhaeUkA=="
|
|
13
13
|
},
|
|
14
14
|
"@ours.network/tg-connector": {
|
|
15
|
-
"version": "1.0.1-nightly.
|
|
16
|
-
"integrity": "sha512
|
|
15
|
+
"version": "1.0.1-nightly.2",
|
|
16
|
+
"integrity": "sha512-XTA2vXMLrXNq7PKVXHp8HluaMzTf2tx4kDy+EOu5sWwvfiP2f4qSCx+ESeebP//TNnSHfrNnGZZiCV0uVsO49g=="
|
|
17
17
|
},
|
|
18
18
|
"@ours.network/cowork": {
|
|
19
|
-
"version": "1.3.2-nightly.
|
|
20
|
-
"integrity": "sha512
|
|
19
|
+
"version": "1.3.2-nightly.20260919.5e6e5fd",
|
|
20
|
+
"integrity": "sha512-/3KEKXqriBsPiK8POyL/XgbP4ELsRs6quv0Q47cFCaesXAR4W44Q3NVs43brnj1AwMLroip0iXzqwlsJvpoaTw=="
|
|
21
21
|
},
|
|
22
22
|
"@ours.network/messenger-server": {
|
|
23
|
-
"version": "1.0.31-nightly.
|
|
24
|
-
"integrity": "sha512-
|
|
23
|
+
"version": "1.0.31-nightly.2",
|
|
24
|
+
"integrity": "sha512-yWBFSzUwoKHfn7z//uesWmEzGOYFfgFsjBr7KXhGGAeXRvGbbbt1OHOg+ZPNhastXipxOrg+oeY94pQwJClxkA=="
|
|
25
25
|
},
|
|
26
26
|
"@ours.network/fleet": {
|
|
27
|
-
"version": "1.2.0-nightly.
|
|
28
|
-
"integrity": "sha512-
|
|
27
|
+
"version": "1.2.0-nightly.2",
|
|
28
|
+
"integrity": "sha512-vnTGjjl2ycd7FN4CKGblemFcq5/MWocTR0CQhGEbspYXRYiojGa3MRyLzJPOVJQbP0/zqIwSG5Vk9czMWHpjow=="
|
|
29
29
|
},
|
|
30
30
|
"@ours.network/mcp": {
|
|
31
|
-
"version": "1.2.0-nightly.
|
|
32
|
-
"integrity": "sha512-
|
|
31
|
+
"version": "1.2.0-nightly.4",
|
|
32
|
+
"integrity": "sha512-NcVDRmwOHx5zAwcIs6cMstSPyIEhl9f0mQNNsSbXYi+oGkamzdEPH02RHWrmoYCgvqiWjO8bhCqB43Mum8ozYg=="
|
|
33
33
|
},
|
|
34
34
|
"@ours.network/codex": {
|
|
35
|
-
"version": "1.2.0-nightly.
|
|
36
|
-
"integrity": "sha512-
|
|
35
|
+
"version": "1.2.0-nightly.4",
|
|
36
|
+
"integrity": "sha512-ZYzqWWcPv9snZH86AZxGfkT2lc9djIJuvBBWdDC7j7YJRjXVQfTVGNFtc5NoGesQYiiQFhiumSppMxfc3vHVDA=="
|
|
37
37
|
},
|
|
38
38
|
"@ours.network/claude-code": {
|
|
39
|
-
"version": "1.2.0-nightly.
|
|
40
|
-
"integrity": "sha512-
|
|
39
|
+
"version": "1.2.0-nightly.4",
|
|
40
|
+
"integrity": "sha512-JNDhTMtxO2Xm0NNL/5Kgra/ovr62mP0YFcuvYJRRA+SH6+2Ick8eegeSVmzU97HPLBCTi8DwNqfkUxcCQUpwXA=="
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"hostCli": {
|
|
44
|
+
"@ours.network/sdk": {
|
|
45
|
+
"version": "3.8.1-nightly.6",
|
|
46
|
+
"integrity": "sha512-kNCR1/0X5sTKCDe1oaEvxXHBkRa0B4N3mosbYZAzY8mu0sTY+TKx2u0ltQ4PRZ50ZuwzhxNe7BVbphu9b/87xw=="
|
|
47
|
+
},
|
|
48
|
+
"@ours.network/cli": {
|
|
49
|
+
"version": "2.8.1-nightly.4",
|
|
50
|
+
"integrity": "sha512-Lw9DVmY4vpAAm+ps8tD/eqJr4jwGSxGfpOtkvHvgjLzY442zM1od8/ZNyCV8d4YVxGOxuT72Fss9qgXnVR6bvQ=="
|
|
41
51
|
}
|
|
42
52
|
}
|
|
43
53
|
}
|
|
@@ -20,10 +20,19 @@ export function releaseBinding(policy) {
|
|
|
20
20
|
if (release?.schema !== 1 || !['stable', 'nightly'].includes(release.channel)) fail('invalid release binding');
|
|
21
21
|
const pattern = release.channel === 'nightly' ? nightly : stable;
|
|
22
22
|
if (typeof release.installerVersion !== 'string' || !pattern.test(release.installerVersion)) fail('invalid installer version/channel');
|
|
23
|
-
if (
|
|
23
|
+
if (release.scope !== undefined && release.scope !== 'host-cli') fail('invalid release scope');
|
|
24
|
+
const required = release.scope === 'host-cli' ? ['@ours.network/cli', '@ours.network/sdk'] : names;
|
|
25
|
+
if (!release.packages || JSON.stringify(Object.keys(release.packages).sort()) !== JSON.stringify([...required].sort())) fail('release must select exactly the required ours packages');
|
|
26
|
+
if (release.scope === 'host-cli' && Object.hasOwn(release, 'hostCli')) fail('nested host CLI selection');
|
|
24
27
|
for (const [name, entry] of Object.entries(release.packages)) {
|
|
25
28
|
if (typeof entry?.version !== 'string' || !pattern.test(entry.version) || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(entry.integrity ?? '')) fail(`invalid release artifact: ${name}`);
|
|
26
29
|
}
|
|
30
|
+
if (Object.hasOwn(release, 'hostCli')) {
|
|
31
|
+
if (!release.hostCli || Object.keys(release.hostCli).sort().join(',') !== '@ours.network/cli,@ours.network/sdk') fail('host CLI must select exactly CLI and SDK');
|
|
32
|
+
for (const [name, entry] of Object.entries(release.hostCli)) {
|
|
33
|
+
if (typeof entry?.version !== 'string' || !pattern.test(entry.version) || !/^sha512-[A-Za-z0-9+/]{86}==$/.test(entry.integrity ?? '')) fail(`invalid host CLI artifact: ${name}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
27
36
|
if (!policy.packages || !Object.keys(policy.packages).length) fail('missing release source selection');
|
|
28
37
|
for (const [name, entry] of Object.entries(policy.packages)) {
|
|
29
38
|
if (entry?.type !== 'npm' || entry.version !== release.packages[name]?.version || Object.keys(entry).sort().join(',') !== 'type,version') fail(`source selection differs from release: ${name}`);
|
|
@@ -92,3 +101,11 @@ export function verifyRuntimeRelease(root) {
|
|
|
92
101
|
if (!existsSync(path)) return { verified: false, reason: 'legacy runtime without sources' };
|
|
93
102
|
return verifyReleaseGraph(root, json(path));
|
|
94
103
|
}
|
|
104
|
+
|
|
105
|
+
/** The client command has its own exact dependency selection, never a graph bypass. */
|
|
106
|
+
export function hostCliPolicy(policy) {
|
|
107
|
+
const release = releaseBinding(policy);
|
|
108
|
+
if (!release?.hostCli) return null;
|
|
109
|
+
const { hostCli, ...base } = release;
|
|
110
|
+
return { release: { ...base, scope: 'host-cli', packages: hostCli }, packages: Object.fromEntries(Object.entries(hostCli).map(([name, p]) => [name, { type: 'npm', version: p.version }])) };
|
|
111
|
+
}
|
package/assets/sources.json
CHANGED
|
@@ -2,82 +2,92 @@
|
|
|
2
2
|
"release": {
|
|
3
3
|
"schema": 1,
|
|
4
4
|
"channel": "nightly",
|
|
5
|
-
"installerVersion": "1.2.1-nightly.
|
|
5
|
+
"installerVersion": "1.2.1-nightly.9",
|
|
6
6
|
"packages": {
|
|
7
7
|
"@ours.network/sdk": {
|
|
8
|
-
"version": "3.8.1-nightly.
|
|
9
|
-
"integrity": "sha512-
|
|
8
|
+
"version": "3.8.1-nightly.4",
|
|
9
|
+
"integrity": "sha512-h0euEcW9VDEhSHV5ubB1wC6KCXV89EqNCVf05F2kUx4SSvEwPNLPHtN9i21H6qlBEq3RHrHnsl1/uo1l51LCMA=="
|
|
10
10
|
},
|
|
11
11
|
"@ours.network/cli": {
|
|
12
|
-
"version": "2.8.1-nightly.
|
|
13
|
-
"integrity": "sha512-
|
|
12
|
+
"version": "2.8.1-nightly.2",
|
|
13
|
+
"integrity": "sha512-YirjiR6/Xs1p2LKOLVtao74xLXCsxgHz3gwbKyZ4Qtd9w6MF+kS/OEOLKozmIjWJ5FoqUxgvnnaudl5vhaeUkA=="
|
|
14
14
|
},
|
|
15
15
|
"@ours.network/tg-connector": {
|
|
16
|
-
"version": "1.0.1-nightly.
|
|
17
|
-
"integrity": "sha512
|
|
16
|
+
"version": "1.0.1-nightly.2",
|
|
17
|
+
"integrity": "sha512-XTA2vXMLrXNq7PKVXHp8HluaMzTf2tx4kDy+EOu5sWwvfiP2f4qSCx+ESeebP//TNnSHfrNnGZZiCV0uVsO49g=="
|
|
18
18
|
},
|
|
19
19
|
"@ours.network/cowork": {
|
|
20
|
-
"version": "1.3.2-nightly.
|
|
21
|
-
"integrity": "sha512
|
|
20
|
+
"version": "1.3.2-nightly.20260919.5e6e5fd",
|
|
21
|
+
"integrity": "sha512-/3KEKXqriBsPiK8POyL/XgbP4ELsRs6quv0Q47cFCaesXAR4W44Q3NVs43brnj1AwMLroip0iXzqwlsJvpoaTw=="
|
|
22
22
|
},
|
|
23
23
|
"@ours.network/messenger-server": {
|
|
24
|
-
"version": "1.0.31-nightly.
|
|
25
|
-
"integrity": "sha512-
|
|
24
|
+
"version": "1.0.31-nightly.2",
|
|
25
|
+
"integrity": "sha512-yWBFSzUwoKHfn7z//uesWmEzGOYFfgFsjBr7KXhGGAeXRvGbbbt1OHOg+ZPNhastXipxOrg+oeY94pQwJClxkA=="
|
|
26
26
|
},
|
|
27
27
|
"@ours.network/fleet": {
|
|
28
|
-
"version": "1.2.0-nightly.
|
|
29
|
-
"integrity": "sha512-
|
|
28
|
+
"version": "1.2.0-nightly.2",
|
|
29
|
+
"integrity": "sha512-vnTGjjl2ycd7FN4CKGblemFcq5/MWocTR0CQhGEbspYXRYiojGa3MRyLzJPOVJQbP0/zqIwSG5Vk9czMWHpjow=="
|
|
30
30
|
},
|
|
31
31
|
"@ours.network/mcp": {
|
|
32
|
-
"version": "1.2.0-nightly.
|
|
33
|
-
"integrity": "sha512-
|
|
32
|
+
"version": "1.2.0-nightly.4",
|
|
33
|
+
"integrity": "sha512-NcVDRmwOHx5zAwcIs6cMstSPyIEhl9f0mQNNsSbXYi+oGkamzdEPH02RHWrmoYCgvqiWjO8bhCqB43Mum8ozYg=="
|
|
34
34
|
},
|
|
35
35
|
"@ours.network/codex": {
|
|
36
|
-
"version": "1.2.0-nightly.
|
|
37
|
-
"integrity": "sha512-
|
|
36
|
+
"version": "1.2.0-nightly.4",
|
|
37
|
+
"integrity": "sha512-ZYzqWWcPv9snZH86AZxGfkT2lc9djIJuvBBWdDC7j7YJRjXVQfTVGNFtc5NoGesQYiiQFhiumSppMxfc3vHVDA=="
|
|
38
38
|
},
|
|
39
39
|
"@ours.network/claude-code": {
|
|
40
|
-
"version": "1.2.0-nightly.
|
|
41
|
-
"integrity": "sha512-
|
|
40
|
+
"version": "1.2.0-nightly.4",
|
|
41
|
+
"integrity": "sha512-JNDhTMtxO2Xm0NNL/5Kgra/ovr62mP0YFcuvYJRRA+SH6+2Ick8eegeSVmzU97HPLBCTi8DwNqfkUxcCQUpwXA=="
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"hostCli": {
|
|
45
|
+
"@ours.network/sdk": {
|
|
46
|
+
"version": "3.8.1-nightly.6",
|
|
47
|
+
"integrity": "sha512-kNCR1/0X5sTKCDe1oaEvxXHBkRa0B4N3mosbYZAzY8mu0sTY+TKx2u0ltQ4PRZ50ZuwzhxNe7BVbphu9b/87xw=="
|
|
48
|
+
},
|
|
49
|
+
"@ours.network/cli": {
|
|
50
|
+
"version": "2.8.1-nightly.4",
|
|
51
|
+
"integrity": "sha512-Lw9DVmY4vpAAm+ps8tD/eqJr4jwGSxGfpOtkvHvgjLzY442zM1od8/ZNyCV8d4YVxGOxuT72Fss9qgXnVR6bvQ=="
|
|
42
52
|
}
|
|
43
53
|
}
|
|
44
54
|
},
|
|
45
55
|
"packages": {
|
|
46
56
|
"@ours.network/sdk": {
|
|
47
57
|
"type": "npm",
|
|
48
|
-
"version": "3.8.1-nightly.
|
|
58
|
+
"version": "3.8.1-nightly.4"
|
|
49
59
|
},
|
|
50
60
|
"@ours.network/cli": {
|
|
51
61
|
"type": "npm",
|
|
52
|
-
"version": "2.8.1-nightly.
|
|
62
|
+
"version": "2.8.1-nightly.2"
|
|
53
63
|
},
|
|
54
64
|
"@ours.network/tg-connector": {
|
|
55
65
|
"type": "npm",
|
|
56
|
-
"version": "1.0.1-nightly.
|
|
66
|
+
"version": "1.0.1-nightly.2"
|
|
57
67
|
},
|
|
58
68
|
"@ours.network/cowork": {
|
|
59
69
|
"type": "npm",
|
|
60
|
-
"version": "1.3.2-nightly.
|
|
70
|
+
"version": "1.3.2-nightly.20260919.5e6e5fd"
|
|
61
71
|
},
|
|
62
72
|
"@ours.network/messenger-server": {
|
|
63
73
|
"type": "npm",
|
|
64
|
-
"version": "1.0.31-nightly.
|
|
74
|
+
"version": "1.0.31-nightly.2"
|
|
65
75
|
},
|
|
66
76
|
"@ours.network/fleet": {
|
|
67
77
|
"type": "npm",
|
|
68
|
-
"version": "1.2.0-nightly.
|
|
78
|
+
"version": "1.2.0-nightly.2"
|
|
69
79
|
},
|
|
70
80
|
"@ours.network/mcp": {
|
|
71
81
|
"type": "npm",
|
|
72
|
-
"version": "1.2.0-nightly.
|
|
82
|
+
"version": "1.2.0-nightly.4"
|
|
73
83
|
},
|
|
74
84
|
"@ours.network/codex": {
|
|
75
85
|
"type": "npm",
|
|
76
|
-
"version": "1.2.0-nightly.
|
|
86
|
+
"version": "1.2.0-nightly.4"
|
|
77
87
|
},
|
|
78
88
|
"@ours.network/claude-code": {
|
|
79
89
|
"type": "npm",
|
|
80
|
-
"version": "1.2.0-nightly.
|
|
90
|
+
"version": "1.2.0-nightly.4"
|
|
81
91
|
}
|
|
82
92
|
}
|
|
83
93
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { lstatSync, readFileSync, writeFileSync, realpathSync, unlinkSync, chmodSync, symlinkSync, renameSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join, resolve, isAbsolute, dirname } from 'node:path';
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
+
import { buildManagedCli } from './managed-cli.mjs';
|
|
5
|
+
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { releaseBinding, verifyReleaseGraph } from '../assets/scripts/maintenance/release-graph.mjs';
|
|
8
|
+
|
|
9
|
+
const marker = '// ours-managed-cli-v1 ';
|
|
10
|
+
const hash = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
11
|
+
function stat(path) {
|
|
12
|
+
try { return lstatSync(path); } catch (error) { if (error.code === 'ENOENT') return null; throw error; }
|
|
13
|
+
}
|
|
14
|
+
function privateAcquisition(home, root) {
|
|
15
|
+
for (const path of [join(home, '.ours-client-install'), root]) {
|
|
16
|
+
const value = lstatSync(path);
|
|
17
|
+
if (!value.isDirectory() || value.uid !== process.getuid() || (value.mode & 0o077) || realpathSync(path) !== path) throw new Error('Unsafe private CLI directory');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Publish the native client, retaining a verified migration launcher for rollback. */
|
|
21
|
+
export async function publishClientCli(effects, packagePath, { policy = {}, isolated = false } = {}) {
|
|
22
|
+
const prefix = (await effects.run('npm', ['prefix', '--global'])).stdout.trim();
|
|
23
|
+
if (!isAbsolute(prefix) || resolve(prefix) !== prefix) throw new Error('npm global prefix must be an absolute normalized path');
|
|
24
|
+
const entry = join(prefix, 'bin', 'ours');
|
|
25
|
+
const before = stat(entry);
|
|
26
|
+
let previous;
|
|
27
|
+
if (before) {
|
|
28
|
+
if (before.uid !== process.getuid()) throw new Error('Refusing to replace a CLI owned by another user');
|
|
29
|
+
if (before.isSymbolicLink()) {
|
|
30
|
+
const resolved = realpathSync(entry);
|
|
31
|
+
const privateBase = join(effects.home, '.ours-client-install') + '/';
|
|
32
|
+
if (resolved.startsWith(privateBase)) {
|
|
33
|
+
const packageRoot = dirname(dirname(resolved));
|
|
34
|
+
const acquisitionRoot = dirname(dirname(dirname(packageRoot)));
|
|
35
|
+
privateAcquisition(effects.home, acquisitionRoot);
|
|
36
|
+
const retained = JSON.parse(readFileSync(join(acquisitionRoot, 'sources.json')));
|
|
37
|
+
if (releaseBinding(retained)?.scope !== 'host-cli') throw new Error('Unrecognized private CLI entry');
|
|
38
|
+
verifyReleaseGraph(acquisitionRoot, retained);
|
|
39
|
+
const pkg = JSON.parse(readFileSync(join(packageRoot, 'package.json')));
|
|
40
|
+
if (pkg.name !== '@ours.network/cli' || resolve(packageRoot, pkg.bin?.ours ?? '') !== resolved) throw new Error('Unrecognized private CLI target');
|
|
41
|
+
} else {
|
|
42
|
+
const npmRoot = (await effects.run('npm', ['root', '--global'])).stdout.trim();
|
|
43
|
+
if (!isAbsolute(npmRoot)) throw new Error('Invalid npm global package root');
|
|
44
|
+
const root = realpathSync(join(npmRoot, '@ours.network/cli'));
|
|
45
|
+
const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
46
|
+
if (manifest.name !== '@ours.network/cli' || typeof manifest.bin?.ours !== 'string' || resolved !== realpathSync(resolve(root, manifest.bin.ours))) throw new Error('Refusing to replace an unrelated ours symlink');
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
if (!before.isFile() || before.nlink !== 1 || (before.mode & 0o022)) throw new Error('Refusing to replace an unsafe CLI entry');
|
|
50
|
+
previous = readFileSync(entry);
|
|
51
|
+
const line = previous.toString().split('\n')[1];
|
|
52
|
+
if (!line?.startsWith(marker)) throw new Error('Refusing to replace an unknown ours executable');
|
|
53
|
+
const binding = JSON.parse(line.slice(marker.length));
|
|
54
|
+
if (binding.schema !== 1 || previous.toString() !== buildManagedCli(binding.recordPath, binding.installerPath)) throw new Error('Managed CLI launcher was modified; no replacement performed');
|
|
55
|
+
const backup = join(effects.home, '.ours-client', `previous-cli-${hash(previous)}.cjs`);
|
|
56
|
+
const backupDir = lstatSync(join(effects.home, '.ours-client'));
|
|
57
|
+
if (!backupDir.isDirectory() || backupDir.uid !== process.getuid() || (backupDir.mode & 0o7777) !== 0o700) throw new Error('Managed CLI backup requires a private owned directory');
|
|
58
|
+
const saved = stat(backup);
|
|
59
|
+
if (!saved) writeFileSync(backup, previous, { mode: 0o600, flag: 'wx' });
|
|
60
|
+
else if (!saved.isFile() || saved.nlink !== 1 || saved.uid !== process.getuid() || (saved.mode & 0o7777) !== 0o600 || saved.size !== previous.length || !readFileSync(backup).equals(previous)) throw new Error('Managed CLI backup is unsafe or differs');
|
|
61
|
+
const current = lstatSync(entry);
|
|
62
|
+
if (current.dev !== before.dev || current.ino !== before.ino || !readFileSync(entry).equals(previous)) throw new Error('CLI changed during publication');
|
|
63
|
+
if (!isolated) unlinkSync(entry);
|
|
64
|
+
effects.out(`Previous managed CLI saved at ${backup}; server maintenance remains available through ours-install server.`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (isolated) {
|
|
68
|
+
const root = dirname(dirname(dirname(packagePath)));
|
|
69
|
+
if (!realpathSync(root).startsWith(join(effects.home, '.ours-client-install') + '/') || releaseBinding(policy)?.scope !== 'host-cli') throw new Error('Invalid private CLI acquisition');
|
|
70
|
+
privateAcquisition(effects.home, root);
|
|
71
|
+
verifyReleaseGraph(root, policy);
|
|
72
|
+
const selected = JSON.parse(readFileSync(join(packagePath, 'package.json')));
|
|
73
|
+
const target = realpathSync(resolve(packagePath, selected.bin.ours));
|
|
74
|
+
if (!target.startsWith(realpathSync(packagePath) + '/')) throw new Error('CLI entry escapes selected package');
|
|
75
|
+
const sdkEntry = realpathSync(createRequire(target).resolve('@ours.network/sdk'));
|
|
76
|
+
if (!sdkEntry.startsWith(root + '/')) throw new Error('Private CLI SDK escaped verified acquisition');
|
|
77
|
+
const sdk = JSON.parse(readFileSync(join(dirname(dirname(sdkEntry)), 'package.json')));
|
|
78
|
+
if (sdk.name !== '@ours.network/sdk' || sdk.version !== policy.release.packages['@ours.network/sdk'].version) throw new Error('Private CLI SDK differs from selection');
|
|
79
|
+
mkdirSync(dirname(entry), { recursive: true });
|
|
80
|
+
const binDir = lstatSync(dirname(entry));
|
|
81
|
+
if (!binDir.isDirectory() || binDir.uid !== process.getuid() || (binDir.mode & 0o022)) throw new Error('CLI bin directory must be owned and not writable by others');
|
|
82
|
+
const current = stat(entry);
|
|
83
|
+
if (before ? !current || current.dev !== before.dev || current.ino !== before.ino : current) throw new Error('CLI entry changed during publication');
|
|
84
|
+
const staged = entry + '.' + randomUUID();
|
|
85
|
+
try {
|
|
86
|
+
symlinkSync(target, staged);
|
|
87
|
+
renameSync(staged, entry);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (stat(staged)) unlinkSync(staged);
|
|
90
|
+
if (previous && !stat(entry)) writeFileSync(entry, previous, { mode: before.mode & 0o777, flag: 'wx' });
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
if (realpathSync(entry) !== target) throw new Error('Private CLI entry changed during publication');
|
|
94
|
+
verifyReleaseGraph(root, policy);
|
|
95
|
+
return entry;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
await effects.run('npm', ['install', '--global', '--install-links=false', '--offline', '--ignore-scripts', '--no-audit', '--no-fund', packagePath]);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (previous && !stat(entry)) {
|
|
101
|
+
writeFileSync(entry, previous, { mode: before.mode & 0o777, flag: 'wx' });
|
|
102
|
+
chmodSync(entry, before.mode & 0o777);
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
const published = stat(entry);
|
|
107
|
+
if (!published?.isSymbolicLink() || published.uid !== process.getuid()) throw new Error('CLI publication did not create the expected owned npm entry');
|
|
108
|
+
const npmRoot = (await effects.run('npm', ['root', '--global'])).stdout.trim();
|
|
109
|
+
if (!isAbsolute(npmRoot) || resolve(npmRoot) !== npmRoot) throw new Error('Invalid npm global package root');
|
|
110
|
+
const root = realpathSync(join(npmRoot, '@ours.network/cli'));
|
|
111
|
+
const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
112
|
+
const selected = JSON.parse(readFileSync(join(packagePath, 'package.json'), 'utf8'));
|
|
113
|
+
const target = typeof manifest.bin?.ours === 'string' ? resolve(root, manifest.bin.ours) : '';
|
|
114
|
+
if (manifest.name !== '@ours.network/cli' || manifest.version !== selected.version || !target.startsWith(root + '/') || realpathSync(entry) !== realpathSync(target) || !stat(target)?.isFile()) throw new Error('Published CLI does not match the selected package');
|
|
115
|
+
if (!readFileSync(target).equals(readFileSync(resolve(packagePath, selected.bin.ours)))) throw new Error('Published CLI entry differs from the selected artifact');
|
|
116
|
+
return entry;
|
|
117
|
+
}
|
package/lib/effects.mjs
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// state-directory guard and the enable/reload. The installer never touches a
|
|
12
12
|
// unit file or the service manager directly.
|
|
13
13
|
|
|
14
|
+
import { publishClientCli } from './client-cli.mjs';
|
|
14
15
|
import { spawnSync, execFileSync } from 'node:child_process';
|
|
15
16
|
import { chmodSync, closeSync, constants, cpSync, existsSync, fstatSync, lstatSync, openSync, readFileSync, mkdirSync, mkdtempSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
16
17
|
import { homedir, userInfo, platform as osPlatform, release as osRelease, arch as osArch } from 'node:os';
|
|
@@ -26,7 +27,7 @@ import { classifyHarnessProbe } from './logic.mjs';
|
|
|
26
27
|
import { qualifyDockerRuntime, refreshDockerPolicyCopy } from './docker-runtime-repair.mjs';
|
|
27
28
|
import { classifyStateDir } from './detect.mjs';
|
|
28
29
|
import { BASE_RECORDS, CONTEXT, readBuildRecords, equalBuildRecords, initializeBuildMarker } from '../assets/scripts/maintenance/build-context.mjs';
|
|
29
|
-
import { releaseBinding, verifyReleaseGraph, verifyRuntimeRelease } from '../assets/scripts/maintenance/release-graph.mjs';
|
|
30
|
+
import { hostCliPolicy, releaseBinding, verifyReleaseGraph, verifyRuntimeRelease } from '../assets/scripts/maintenance/release-graph.mjs';
|
|
30
31
|
|
|
31
32
|
/** GET http://127.0.0.1:<port>/state-dir — the unauthenticated identity probe. */
|
|
32
33
|
async function probePort(port, { timeoutMs = 1500 } = {}) {
|
|
@@ -1195,18 +1196,20 @@ export function networkEffects(effects) {
|
|
|
1195
1196
|
atomicWriteConfig(configPath, JSON.stringify(saved, null, 2) + '\n');
|
|
1196
1197
|
return { configPath, profile: validateHostProfile(saved), settings };
|
|
1197
1198
|
},
|
|
1198
|
-
async acquireClientPackages(configPath, sourcesPath, integrations, { refresh = false } = {}) {
|
|
1199
|
+
async acquireClientPackages(configPath, sourcesPath, integrations, { refresh = false, hostCliOnly = false } = {}) {
|
|
1199
1200
|
const manifest = JSON.parse(readFileSync(sourcesPath, 'utf8'));
|
|
1200
|
-
//
|
|
1201
|
-
const selected = [...new Set(['sdk',
|
|
1201
|
+
// Every client installation includes the native CLI and its SDK dependency.
|
|
1202
|
+
const selected = [...new Set(['sdk', 'cli', ...integrations])];
|
|
1202
1203
|
const packages = selectSourcePackages(manifest, 'client', selected);
|
|
1203
|
-
const selectionKey =
|
|
1204
|
+
const selectionKey = JSON.stringify([hostCliOnly ? 'host-cli-private-v1' : 'host-cli-v1', configPath, ...(hostCliOnly ? [manifest] : []), ...(refresh ? [manifest, integrations] : [])]);
|
|
1204
1205
|
const root = join(home, '.ours-client-install', createHash('sha256').update(selectionKey).digest('hex').slice(0, 16));
|
|
1205
1206
|
const hasGit = Object.values(packages).some(selection => selection.source);
|
|
1206
1207
|
await effects.run('npm', ['--version']);
|
|
1207
1208
|
if (hasGit) {
|
|
1208
1209
|
for (const command of ['python3', 'git', 'make', 'cc']) await effects.run(command, ['--version']);
|
|
1209
1210
|
}
|
|
1211
|
+
// This directory retains active executables, not disposable download cache.
|
|
1212
|
+
ensurePrivateDirectory(join(home, '.ours-client-install'));
|
|
1210
1213
|
ensurePrivateDirectory(root);
|
|
1211
1214
|
const retained = join(root, 'sources.json');
|
|
1212
1215
|
const bytes = readFileSync(sourcesPath);
|
|
@@ -1231,13 +1234,26 @@ export function networkEffects(effects) {
|
|
|
1231
1234
|
writePrivateNew(join(root, '.packages-ready'), 'ready\n');
|
|
1232
1235
|
}
|
|
1233
1236
|
verifyReleaseGraph(root, manifest, { requiredPackages: Object.keys(packages) });
|
|
1237
|
+
const cliPolicy = hostCliOnly ? null : hostCliPolicy(manifest);
|
|
1234
1238
|
// Local acquisition alone does not publish native commands. Use the user's
|
|
1235
1239
|
// configured npm prefix and retained dependency closure, including on retry.
|
|
1236
1240
|
for (const name of integrations.filter(name => name === 'fleet' || name === 'codex')) {
|
|
1237
1241
|
await effects.run('npm', ['install', '--global', '--install-links=false', '--offline', '--ignore-scripts', '--no-audit', '--no-fund', join(root, 'node_modules', '@ours.network', name)]);
|
|
1238
1242
|
}
|
|
1243
|
+
let cliBin = join(root, 'node_modules/.bin/ours');
|
|
1244
|
+
if (cliPolicy) {
|
|
1245
|
+
const cliSources = join(root, 'host-cli-sources.json');
|
|
1246
|
+
const cliBytes = JSON.stringify(cliPolicy, null, 2) + '\n';
|
|
1247
|
+
if (!existsSync(cliSources)) writePrivateNew(cliSources, cliBytes);
|
|
1248
|
+
else if (readFileSync(cliSources, 'utf8') !== cliBytes) throw new Error('Retained host CLI source selection differs');
|
|
1249
|
+
const acquired = await effects.acquireClientPackages(configPath, cliSources, [], { refresh, hostCliOnly: true });
|
|
1250
|
+
cliBin = acquired.cliBin;
|
|
1251
|
+
} else {
|
|
1252
|
+
// Publish last so optional integration installation cannot downgrade ours.
|
|
1253
|
+
await publishClientCli(effects, join(root, 'node_modules', '@ours.network/cli'), { policy: manifest, isolated: hostCliOnly });
|
|
1254
|
+
}
|
|
1239
1255
|
const localPackages = Object.fromEntries(integrations.filter(n => n !== 'fleet').map(name => [name, join(root, 'node_modules', '@ours.network', name)]));
|
|
1240
|
-
return { localPackages, packages: {}, fleetBin: integrations.includes('fleet') ? join(root, 'node_modules/.bin/ours-fleet') : null };
|
|
1256
|
+
return { localPackages, packages: {}, cliBin, fleetBin: integrations.includes('fleet') ? join(root, 'node_modules/.bin/ours-fleet') : null };
|
|
1241
1257
|
},
|
|
1242
1258
|
async prepareClientMarketplace(name, packagePath) {
|
|
1243
1259
|
const acquisitionRoot = dirname(dirname(dirname(packagePath)));
|
|
@@ -1246,7 +1262,7 @@ export function networkEffects(effects) {
|
|
|
1246
1262
|
const release = releaseBinding(policy);
|
|
1247
1263
|
const integrationsPath = join(acquisitionRoot, 'integrations.json');
|
|
1248
1264
|
const integrations = existsSync(integrationsPath) ? JSON.parse(readFileSync(integrationsPath, 'utf8')) : null;
|
|
1249
|
-
const requiredPackages = integrations ? [...new Set(['sdk',
|
|
1265
|
+
const requiredPackages = integrations ? [...new Set(['sdk', 'cli', ...integrations])].map(name => '@ours.network/' + name) : Object.keys(policy.packages ?? {});
|
|
1250
1266
|
verifyReleaseGraph(acquisitionRoot, policy, { requiredPackages });
|
|
1251
1267
|
const root = join(acquisitionRoot, 'marketplaces', name);
|
|
1252
1268
|
const plugin = join(root, 'plugins', 'ours');
|
package/lib/orchestrate.mjs
CHANGED
|
@@ -101,7 +101,7 @@ function clientRetry(effects, imported, integrations) {
|
|
|
101
101
|
effects.out(info('Saved profile and settings retained. Run ours-install, choose Connect to an existing server, and reuse the saved profile to finish the interactive Fleet configuration.'));
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
|
-
const args = ['client', 'install', '--config', imported.configPath, '--integrations', integrations.join(','), '--sources', imported.settings.sourcesPath];
|
|
104
|
+
const args = ['client', 'install', '--config', imported.configPath, '--integrations', integrations.join(',') || 'none', '--sources', imported.settings.sourcesPath];
|
|
105
105
|
if (imported.settings.fleetSettingsPath) args.push('--fleet-settings', imported.settings.fleetSettingsPath);
|
|
106
106
|
const command = `ours-install client install ${args.slice(2).map((value, i) => i % 2 === 0 ? value : shellQuote(value)).join(' ')}`;
|
|
107
107
|
effects.out(info(`Saved profile and settings retained; re-run ${command}`));
|
|
@@ -1449,13 +1449,13 @@ export async function runClientCommand(command, effects) {
|
|
|
1449
1449
|
integrations = [];
|
|
1450
1450
|
for (const name of ['codex', 'claude-code', 'fleet']) if (await effects.ask(`Install ${name}?`, name !== 'fleet')) integrations.push(name);
|
|
1451
1451
|
}
|
|
1452
|
-
if (!Array.isArray(integrations) ||
|
|
1452
|
+
if (!Array.isArray(integrations) || integrations.some(name => !['codex', 'claude-code', 'fleet'].includes(name)) || new Set(integrations).size !== integrations.length) throw new InstallUsageError('installer.integrations must be an array of codex, claude-code and/or fleet; use an empty array for CLI only');
|
|
1453
1453
|
let fleetSettingsPath = command.preset ? command.fleetSettingsPath : settings?.fleetSettingsPath;
|
|
1454
1454
|
if (command.nonInteractive && integrations.includes('fleet') && !fleetSettingsPath) throw new InstallUsageError('Fleet in CLI mode requires --fleet-settings; no interactive wizard will be opened');
|
|
1455
1455
|
if (fleetSettingsPath !== undefined && (typeof fleetSettingsPath !== 'string' || !fleetSettingsPath))
|
|
1456
1456
|
throw new InstallUsageError('installer.fleetSettingsPath must be a non-empty path when supplied');
|
|
1457
1457
|
if (fleetSettingsPath) fleetSettingsPath = resolve(settingsBase, fleetSettingsPath);
|
|
1458
|
-
const selectedClients = [...new Set(['sdk',
|
|
1458
|
+
const selectedClients = [...new Set(['sdk', 'cli', ...integrations])];
|
|
1459
1459
|
let sourcesPath = settings?.sourcesPath;
|
|
1460
1460
|
let resolvedSources;
|
|
1461
1461
|
if (command.sourcePolicy) {
|
package/lib/setup.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { validateIdentityName } from './server-onboarding.mjs';
|
|
|
11
11
|
import { validateFleetSettings } from './fleet-settings.mjs';
|
|
12
12
|
|
|
13
13
|
const maintenance = new Set(['status', 'start', 'stop', 'restart', 'rebuild', 'access-issue', 'access-replace', 'backup', 'restore', 'reset']);
|
|
14
|
-
const clientPackages = integrations => [...new Set(['sdk',
|
|
14
|
+
const clientPackages = integrations => [...new Set(['sdk', 'cli', ...integrations])];
|
|
15
15
|
|
|
16
16
|
export function completeReleasePolicy(retained, supplied) {
|
|
17
17
|
if (retained?.release) {
|
|
@@ -32,7 +32,7 @@ export async function prepareSetupPlan(options, effects) {
|
|
|
32
32
|
options = validateSetupOptions(options, { interactive: options.interactive });
|
|
33
33
|
if (effects.platform?.platform === 'win32') throw new InstallUsageError('Run ours-install inside WSL with Docker Desktop integration on Windows. Direct Windows Node installations are not supported.');
|
|
34
34
|
const plan = { ...options };
|
|
35
|
-
if (options.scope !== 'server'
|
|
35
|
+
if (options.scope !== 'server') {
|
|
36
36
|
for (const name of ['OURS_API_TOKEN', 'OURS_PORT', 'OURS_STATE_DIR', 'OURS_DAEMON_ID']) {
|
|
37
37
|
if (effects.env?.[name]?.trim()) throw new InstallUsageError(`${name} conflicts with the selected client profile. Clear this override before full-stack/client setup; nothing was changed.`);
|
|
38
38
|
}
|
|
@@ -61,13 +61,13 @@ export async function prepareSetupPlan(options, effects) {
|
|
|
61
61
|
}
|
|
62
62
|
if (options.operation === 'install' && !options.migrateFrom) {
|
|
63
63
|
const retained = readObject(effects, plan.existing.sourcesPath, 'Retained source policy');
|
|
64
|
-
plan.sourcePolicy = options.scope === 'server'
|
|
64
|
+
plan.sourcePolicy = options.scope === 'server' ? retained : completeReleasePolicy(retained, options.sources ? policy : null);
|
|
65
65
|
}
|
|
66
66
|
} else if (options.operation === 'update') {
|
|
67
67
|
throw new InstallUsageError('Update requires an existing installation.json; choose install for a new installation');
|
|
68
68
|
}
|
|
69
69
|
// Reject a local client already attached to another server before changing the server.
|
|
70
|
-
const saved = options.scope === 'all'
|
|
70
|
+
const saved = options.scope === 'all' ? effects.readManagedClientProfile() : null;
|
|
71
71
|
if (saved && (!plan.existing || saved.expectedInstanceId !== plan.existing.instanceId || saved.endpoint !== `http://127.0.0.1:${plan.port}`)) {
|
|
72
72
|
throw new InstallUsageError('This user already has clients attached to a different server; their saved connection was not changed');
|
|
73
73
|
}
|
|
@@ -82,12 +82,12 @@ export async function prepareSetupPlan(options, effects) {
|
|
|
82
82
|
}
|
|
83
83
|
// Dry-run never spawns a resolver or acquires an installation lock.
|
|
84
84
|
if (!options.dryRun) {
|
|
85
|
-
if (options.scope === 'all' && options.operation === 'update'
|
|
85
|
+
if (options.scope === 'all' && options.operation === 'update') {
|
|
86
86
|
const running = await effects.serverLifecycle(plan.existing, 'status', ['daemon']);
|
|
87
87
|
if (!running.includes('daemon')) throw new InstallUsageError('Full-stack update requires the selected daemon to be running for client verification. Start it first, or use server update to preserve its stopped state.');
|
|
88
88
|
}
|
|
89
89
|
if (options.scope !== 'client') await effects.resolveSourcePolicy(plan.sourcePolicy, 'server');
|
|
90
|
-
if (options.scope !== 'server'
|
|
90
|
+
if (options.scope !== 'server') await effects.resolveSourcePolicy(plan.sourcePolicy, 'client', clientPackages(options.integrations));
|
|
91
91
|
}
|
|
92
92
|
return plan;
|
|
93
93
|
}
|
|
@@ -100,7 +100,7 @@ export async function executeSetupPlan(plan, effects, { server = runServerComman
|
|
|
100
100
|
if (plan.dryRun) {
|
|
101
101
|
effects.out(info('Preview only. No packages, identities, credentials or services will be changed.'));
|
|
102
102
|
if (plan.scope !== 'client') effects.out(info('Server: prerequisites → runtime preparation/update → retained identity/state restoration → readiness.'));
|
|
103
|
-
if (plan.scope !== 'server'
|
|
103
|
+
if (plan.scope !== 'server') effects.out(info('Clients: private connection → exact packages → selected integrations → Fleet settings when selected.'));
|
|
104
104
|
return 0;
|
|
105
105
|
}
|
|
106
106
|
let clientConfig = plan.config;
|
|
@@ -118,14 +118,14 @@ export async function executeSetupPlan(plan, effects, { server = runServerComman
|
|
|
118
118
|
effects.out(ok('Retained identities verified.'));
|
|
119
119
|
} else effects.out(info('Daemon remains stopped; stored identities are retained and will restore on the next start.'));
|
|
120
120
|
}
|
|
121
|
-
if (plan.scope === 'all'
|
|
121
|
+
if (plan.scope === 'all') {
|
|
122
122
|
clientPolicy = completeReleasePolicy(readObject(effects, record.sourcesPath, 'Server source policy'), plan.sourcePolicy);
|
|
123
123
|
effects.out(heading('Connect local clients'));
|
|
124
124
|
const handoff = await effects.prepareLocalClient(record, plan.integrations, plan.fleetSettingsPath);
|
|
125
125
|
clientConfig = handoff.configPath;
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
if (plan.scope !== 'server'
|
|
128
|
+
if (plan.scope !== 'server') {
|
|
129
129
|
const result = await client({ role: 'client', operation: 'install', config: clientConfig,
|
|
130
130
|
integrations: plan.integrations, fleetSettingsPath: plan.fleetSettingsPath, sourcePolicy: clientPolicy,
|
|
131
131
|
preset: true, nonInteractive: !plan.interactive }, effects);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ours.network/install",
|
|
3
|
-
"version": "1.2.1-nightly.
|
|
3
|
+
"version": "1.2.1-nightly.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The all-in-one ours.network installer: one shared daemon, MCP, cowork, Telegram, Fleet initialization, harness plugins, Human identity, progress UI, and guided next steps.",
|
|
6
6
|
"type": "module",
|