@fabricorg/cli 0.3.5 → 0.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fabric.js +2422 -854
- package/dist/fabric.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
package/dist/fabric.js
CHANGED
|
@@ -52,12 +52,12 @@ var __async = (__this, __arguments, generator) => {
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
// src/bin/fabric.ts
|
|
55
|
-
import { Command as
|
|
55
|
+
import { Command as Command21 } from "commander";
|
|
56
56
|
|
|
57
57
|
// package.json
|
|
58
58
|
var package_default = {
|
|
59
59
|
name: "@fabricorg/cli",
|
|
60
|
-
version: "0.3.
|
|
60
|
+
version: "0.3.7",
|
|
61
61
|
description: "CLI for the Fabric AI platform",
|
|
62
62
|
keywords: [
|
|
63
63
|
"fabric",
|
|
@@ -96,7 +96,8 @@ var package_default = {
|
|
|
96
96
|
"cli-table3": "^0.6.5",
|
|
97
97
|
commander: "^13.1.0",
|
|
98
98
|
conf: "^13.1.0",
|
|
99
|
-
fflate: "^0.8.3"
|
|
99
|
+
fflate: "^0.8.3",
|
|
100
|
+
ignore: "7.0.9"
|
|
100
101
|
},
|
|
101
102
|
devDependencies: {
|
|
102
103
|
"@repo/tsconfig": "workspace:*",
|
|
@@ -1051,806 +1052,2427 @@ function buildCompletionCommand() {
|
|
|
1051
1052
|
return completion;
|
|
1052
1053
|
}
|
|
1053
1054
|
|
|
1054
|
-
// src/commands/
|
|
1055
|
+
// src/commands/context/index.ts
|
|
1056
|
+
import { lstat as lstat3 } from "fs/promises";
|
|
1057
|
+
import path5 from "path";
|
|
1055
1058
|
import { Command as Command8 } from "commander";
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
);
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1059
|
+
|
|
1060
|
+
// src/lib/command-boundary.ts
|
|
1061
|
+
function outputFormatFor(command) {
|
|
1062
|
+
const resolved = command.optsWithGlobals();
|
|
1063
|
+
return resolved.format === "json" ? "json" : "text";
|
|
1064
|
+
}
|
|
1065
|
+
var CliFailure = class extends Error {
|
|
1066
|
+
constructor(message, exitCode) {
|
|
1067
|
+
super(message);
|
|
1068
|
+
this.exitCode = exitCode;
|
|
1069
|
+
this.name = "CliFailure";
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
function withDeadline(totalMs, body) {
|
|
1073
|
+
return __async(this, null, function* () {
|
|
1074
|
+
const controller = new AbortController();
|
|
1075
|
+
let timer;
|
|
1076
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
1077
|
+
timer = setTimeout(() => {
|
|
1078
|
+
controller.abort();
|
|
1079
|
+
reject(
|
|
1080
|
+
new Error(
|
|
1081
|
+
`gave up after ${totalMs}ms so the session is not held up`
|
|
1082
|
+
)
|
|
1083
|
+
);
|
|
1084
|
+
}, totalMs);
|
|
1085
|
+
});
|
|
1086
|
+
try {
|
|
1087
|
+
yield Promise.race([body(controller.signal), deadline]);
|
|
1088
|
+
} finally {
|
|
1089
|
+
if (timer) {
|
|
1090
|
+
clearTimeout(timer);
|
|
1091
|
+
}
|
|
1073
1092
|
}
|
|
1074
|
-
process.stdout.write(`Context: org:${stored.slug}
|
|
1075
|
-
`);
|
|
1076
|
-
});
|
|
1077
|
-
const use = new Command8("use").description(
|
|
1078
|
-
"Set the default organization for commands"
|
|
1079
|
-
);
|
|
1080
|
-
use.command("personal").description("Retired \u2014 context is organization-only").action(() => {
|
|
1081
|
-
printError(
|
|
1082
|
-
"Personal context no longer exists \u2014 every command runs inside an organization.\nSet one with:\n fabric ctx use org <slug>",
|
|
1083
|
-
2
|
|
1084
|
-
);
|
|
1085
|
-
});
|
|
1086
|
-
use.command("org <slug>").description("Use an org context by default").action((slug) => {
|
|
1087
|
-
saveDefaultContext({ type: "org", slug });
|
|
1088
|
-
printSuccess(`Default context set to: org:${slug}`);
|
|
1089
1093
|
});
|
|
1090
|
-
|
|
1091
|
-
|
|
1094
|
+
}
|
|
1095
|
+
function describeError(error) {
|
|
1096
|
+
if (error instanceof Error) {
|
|
1097
|
+
return error.message.replace(/\s*\n\s*/g, " ");
|
|
1098
|
+
}
|
|
1099
|
+
return String(error);
|
|
1100
|
+
}
|
|
1101
|
+
function asCliFailure(error) {
|
|
1102
|
+
if (error instanceof CliFailure) {
|
|
1103
|
+
return error;
|
|
1104
|
+
}
|
|
1105
|
+
const status = error.status;
|
|
1106
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1107
|
+
switch (status) {
|
|
1108
|
+
case 401:
|
|
1109
|
+
return new CliFailure(message, 3);
|
|
1110
|
+
case 403:
|
|
1111
|
+
return new CliFailure(message, 5);
|
|
1112
|
+
case 404:
|
|
1113
|
+
return new CliFailure(message, 4);
|
|
1114
|
+
case 429:
|
|
1115
|
+
return new CliFailure(message, 6);
|
|
1116
|
+
case 400:
|
|
1117
|
+
case 422:
|
|
1118
|
+
return new CliFailure(message, 7);
|
|
1119
|
+
default:
|
|
1120
|
+
return new CliFailure(message, 1);
|
|
1121
|
+
}
|
|
1092
1122
|
}
|
|
1093
1123
|
|
|
1094
|
-
// src/
|
|
1095
|
-
import
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1124
|
+
// src/lib/context-sync/lock.ts
|
|
1125
|
+
import path2 from "path";
|
|
1126
|
+
|
|
1127
|
+
// src/lib/instructions/safe-write.ts
|
|
1128
|
+
import { createHash, randomBytes } from "crypto";
|
|
1129
|
+
import { constants } from "fs";
|
|
1130
|
+
import {
|
|
1131
|
+
lstat,
|
|
1132
|
+
mkdir,
|
|
1133
|
+
open,
|
|
1134
|
+
readFile,
|
|
1135
|
+
realpath,
|
|
1136
|
+
rename,
|
|
1137
|
+
unlink
|
|
1138
|
+
} from "fs/promises";
|
|
1139
|
+
import path from "path";
|
|
1140
|
+
|
|
1141
|
+
// src/lib/instructions/paths.ts
|
|
1142
|
+
var DRIVE_LETTER = /^[A-Za-z]:/;
|
|
1143
|
+
var RESERVED_DEVICE_NAME = /^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\.|$)/i;
|
|
1144
|
+
var FORBIDDEN_NAME_CHARACTER = /[<>:"|?*]/;
|
|
1145
|
+
var RESERVED_ROOTS = [".git", ".fabric"];
|
|
1146
|
+
var RESERVED_EXACT_PATHS = [
|
|
1147
|
+
".claude/settings.local.json",
|
|
1148
|
+
".codex/hooks.json"
|
|
1149
|
+
];
|
|
1150
|
+
function hasControlCharacter(input) {
|
|
1151
|
+
for (let index = 0; index < input.length; index++) {
|
|
1152
|
+
const code = input.charCodeAt(index);
|
|
1153
|
+
if (code <= 31 || code === 127) {
|
|
1154
|
+
return true;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
return false;
|
|
1158
|
+
}
|
|
1159
|
+
function checkRelativePath(input) {
|
|
1160
|
+
if (input.length === 0) {
|
|
1161
|
+
return { ok: false, reason: "empty", detail: input };
|
|
1162
|
+
}
|
|
1163
|
+
if (hasControlCharacter(input)) {
|
|
1164
|
+
return { ok: false, reason: "control_char", detail: input };
|
|
1165
|
+
}
|
|
1166
|
+
if (input.includes("\\")) {
|
|
1167
|
+
return { ok: false, reason: "backslash", detail: input };
|
|
1168
|
+
}
|
|
1169
|
+
if (input.startsWith("/") || DRIVE_LETTER.test(input)) {
|
|
1170
|
+
return { ok: false, reason: "absolute", detail: input };
|
|
1171
|
+
}
|
|
1172
|
+
if (input.endsWith("/")) {
|
|
1173
|
+
return { ok: false, reason: "trailing_separator", detail: input };
|
|
1174
|
+
}
|
|
1175
|
+
const segments = input.split("/");
|
|
1176
|
+
if (segments.some((s) => s.length === 0)) {
|
|
1177
|
+
return { ok: false, reason: "empty", detail: input };
|
|
1178
|
+
}
|
|
1179
|
+
if (segments.some((s) => s === "." || s === "..")) {
|
|
1180
|
+
return { ok: false, reason: "traversal", detail: input };
|
|
1181
|
+
}
|
|
1182
|
+
for (const segment of segments) {
|
|
1183
|
+
if (/[. ]$/.test(segment)) {
|
|
1184
|
+
return {
|
|
1185
|
+
ok: false,
|
|
1186
|
+
reason: "trailing_dot_or_space",
|
|
1187
|
+
detail: input
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
if (RESERVED_DEVICE_NAME.test(segment)) {
|
|
1191
|
+
return {
|
|
1192
|
+
ok: false,
|
|
1193
|
+
reason: "reserved_device_name",
|
|
1194
|
+
detail: input
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
if (FORBIDDEN_NAME_CHARACTER.test(segment)) {
|
|
1198
|
+
return {
|
|
1199
|
+
ok: false,
|
|
1200
|
+
reason: "forbidden_character",
|
|
1201
|
+
detail: input
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
return { ok: true, path: input };
|
|
1206
|
+
}
|
|
1207
|
+
function isReservedPath(input) {
|
|
1208
|
+
var _a2;
|
|
1209
|
+
const first = (_a2 = input.split("/")[0]) == null ? void 0 : _a2.toLowerCase();
|
|
1210
|
+
if (RESERVED_ROOTS.some((root) => first === root)) {
|
|
1211
|
+
return true;
|
|
1212
|
+
}
|
|
1213
|
+
const key = collisionKey(input);
|
|
1214
|
+
return RESERVED_EXACT_PATHS.some(
|
|
1215
|
+
(reserved) => collisionKey(reserved) === key
|
|
1134
1216
|
);
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1217
|
+
}
|
|
1218
|
+
function collisionKey(input) {
|
|
1219
|
+
return input.normalize("NFC").toLowerCase();
|
|
1220
|
+
}
|
|
1221
|
+
function findCollision(paths) {
|
|
1222
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1223
|
+
for (const candidate of paths) {
|
|
1224
|
+
const key = collisionKey(candidate);
|
|
1225
|
+
const previous = seen.get(key);
|
|
1226
|
+
if (previous !== void 0) {
|
|
1227
|
+
return { first: previous, second: candidate };
|
|
1228
|
+
}
|
|
1229
|
+
seen.set(key, candidate);
|
|
1230
|
+
}
|
|
1231
|
+
return null;
|
|
1232
|
+
}
|
|
1233
|
+
function describeRejection(rejection) {
|
|
1234
|
+
switch (rejection.reason) {
|
|
1235
|
+
case "empty":
|
|
1236
|
+
return `empty or malformed path (${JSON.stringify(rejection.detail)})`;
|
|
1237
|
+
case "absolute":
|
|
1238
|
+
return `absolute path refused: ${rejection.detail}`;
|
|
1239
|
+
case "traversal":
|
|
1240
|
+
return `path traversal refused: ${rejection.detail}`;
|
|
1241
|
+
case "backslash":
|
|
1242
|
+
return `backslash in path refused: ${rejection.detail}`;
|
|
1243
|
+
case "control_char":
|
|
1244
|
+
return `control character in path refused: ${JSON.stringify(rejection.detail)}`;
|
|
1245
|
+
case "trailing_separator":
|
|
1246
|
+
return `path names a directory, not a file: ${rejection.detail}`;
|
|
1247
|
+
case "trailing_dot_or_space":
|
|
1248
|
+
return `path segment ending in a dot or space refused: ${rejection.detail}`;
|
|
1249
|
+
case "reserved_device_name":
|
|
1250
|
+
return `reserved device name refused: ${rejection.detail}`;
|
|
1251
|
+
case "forbidden_character":
|
|
1252
|
+
return `path contains a character Windows will not put in a filename (< > : " | ? *): ${rejection.detail}`;
|
|
1253
|
+
case "reserved_path":
|
|
1254
|
+
return `this tool never writes or deletes ${RESERVED_ROOTS.join(", ")} or ${RESERVED_EXACT_PATHS.join(", ")}: ${rejection.detail}`;
|
|
1255
|
+
case "not_a_regular_file":
|
|
1256
|
+
return `path is not a regular file: ${rejection.detail}`;
|
|
1257
|
+
case "escapes_destination":
|
|
1258
|
+
return `path resolves outside the destination: ${rejection.detail}`;
|
|
1259
|
+
case "symlink":
|
|
1260
|
+
return `refusing to follow a symlink: ${rejection.detail}`;
|
|
1261
|
+
case "not_a_directory":
|
|
1262
|
+
return `a path component is not a directory: ${rejection.detail}`;
|
|
1263
|
+
case "collision":
|
|
1264
|
+
return `two paths name the same file on a case- or Unicode-insensitive filesystem: ${rejection.detail}`;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// src/lib/instructions/safe-write.ts
|
|
1269
|
+
var ALLOWED_MODES = /* @__PURE__ */ new Set([420, 493]);
|
|
1270
|
+
function isAllowedMode(mode) {
|
|
1271
|
+
if (mode === null || mode === void 0) {
|
|
1272
|
+
return true;
|
|
1273
|
+
}
|
|
1274
|
+
if (!Number.isInteger(mode) || mode < 0) {
|
|
1275
|
+
return false;
|
|
1276
|
+
}
|
|
1277
|
+
const type = mode & 61440;
|
|
1278
|
+
if (type !== 0 && type !== 32768) {
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1281
|
+
return ALLOWED_MODES.has(mode & 4095);
|
|
1282
|
+
}
|
|
1283
|
+
function permissionBits(mode) {
|
|
1284
|
+
if (mode === null || mode === void 0) {
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
return mode & 4095;
|
|
1288
|
+
}
|
|
1289
|
+
function resolveDestinationRoot(destination) {
|
|
1290
|
+
return __async(this, null, function* () {
|
|
1291
|
+
yield mkdir(destination, { recursive: true });
|
|
1292
|
+
return realpath(destination);
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
function assertSafeTarget(root, relativePath) {
|
|
1296
|
+
return __async(this, null, function* () {
|
|
1297
|
+
const target = path.resolve(root, relativePath);
|
|
1298
|
+
if (target === root || !target.startsWith(root + path.sep)) {
|
|
1299
|
+
return {
|
|
1300
|
+
ok: false,
|
|
1301
|
+
reason: "escapes_destination",
|
|
1302
|
+
detail: relativePath
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
const segments = relativePath.split("/");
|
|
1306
|
+
let current = root;
|
|
1307
|
+
let deepestExisting = root;
|
|
1308
|
+
for (const [index, segment] of segments.entries()) {
|
|
1309
|
+
current = path.join(current, segment);
|
|
1310
|
+
const stats = yield lstatOrNull(current);
|
|
1311
|
+
if (stats === null) {
|
|
1312
|
+
break;
|
|
1313
|
+
}
|
|
1314
|
+
if (stats.isSymbolicLink()) {
|
|
1315
|
+
return { ok: false, reason: "symlink", detail: current };
|
|
1316
|
+
}
|
|
1317
|
+
if (index < segments.length - 1 && !stats.isDirectory()) {
|
|
1318
|
+
return { ok: false, reason: "not_a_directory", detail: current };
|
|
1319
|
+
}
|
|
1320
|
+
deepestExisting = current;
|
|
1321
|
+
}
|
|
1322
|
+
if (deepestExisting !== root) {
|
|
1323
|
+
const canonical = yield realpath(deepestExisting).catch(() => null);
|
|
1324
|
+
if (canonical !== null && canonical !== root && !canonical.startsWith(root + path.sep)) {
|
|
1325
|
+
return {
|
|
1326
|
+
ok: false,
|
|
1327
|
+
reason: "escapes_destination",
|
|
1328
|
+
detail: relativePath
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
return { ok: true, path: target };
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
function assertWritableTarget(root, relativePath) {
|
|
1336
|
+
return __async(this, null, function* () {
|
|
1337
|
+
const check = yield assertSafeTarget(root, relativePath);
|
|
1338
|
+
if (!check.ok) {
|
|
1339
|
+
return check;
|
|
1340
|
+
}
|
|
1341
|
+
const stats = yield lstatOrNull(check.path);
|
|
1342
|
+
if (stats === null) {
|
|
1343
|
+
return check;
|
|
1344
|
+
}
|
|
1345
|
+
if (stats.isSymbolicLink()) {
|
|
1346
|
+
return { ok: false, reason: "symlink", detail: check.path };
|
|
1347
|
+
}
|
|
1348
|
+
if (!stats.isFile()) {
|
|
1349
|
+
return { ok: false, reason: "not_a_regular_file", detail: check.path };
|
|
1350
|
+
}
|
|
1351
|
+
return check;
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
function readFileSafely(_0, _1) {
|
|
1355
|
+
return __async(this, arguments, function* (root, relativePath, options = {}) {
|
|
1356
|
+
const check = yield assertWritableTarget(root, relativePath);
|
|
1357
|
+
if (!check.ok) {
|
|
1358
|
+
throw new Error(`Refusing to sync: ${describeRejection(check)}.`);
|
|
1359
|
+
}
|
|
1360
|
+
let handle;
|
|
1361
|
+
try {
|
|
1362
|
+
handle = yield open(
|
|
1363
|
+
check.path,
|
|
1364
|
+
constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK
|
|
1365
|
+
);
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
if (error.code === "ENOENT") {
|
|
1368
|
+
return null;
|
|
1369
|
+
}
|
|
1370
|
+
throw error;
|
|
1371
|
+
}
|
|
1372
|
+
try {
|
|
1373
|
+
const opened = yield handle.stat();
|
|
1374
|
+
if (!opened.isFile()) {
|
|
1375
|
+
throw new Error(
|
|
1376
|
+
`Refusing to sync: ${relativePath} is not a regular file.`
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
const afterOpen = yield assertWritableTarget(root, relativePath);
|
|
1380
|
+
if (!afterOpen.ok) {
|
|
1381
|
+
throw new Error(
|
|
1382
|
+
`Refusing to sync: ${describeRejection(afterOpen)}.`
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
const current = yield lstatOrNull(afterOpen.path);
|
|
1386
|
+
if (current === null || !current.isFile() || current.dev !== opened.dev || current.ino !== opened.ino) {
|
|
1387
|
+
throw new Error(
|
|
1388
|
+
`Refusing to sync: ${relativePath} changed while it was being opened.`
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
if (options.maxBytes === void 0) {
|
|
1392
|
+
return { bytes: yield handle.readFile(), mode: opened.mode };
|
|
1393
|
+
}
|
|
1394
|
+
if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 0) {
|
|
1395
|
+
throw new Error(
|
|
1396
|
+
"A safe file-read limit must be a non-negative integer."
|
|
1397
|
+
);
|
|
1398
|
+
}
|
|
1399
|
+
if (opened.size > options.maxBytes) {
|
|
1400
|
+
throw new Error(
|
|
1401
|
+
`${relativePath} is too large to read safely (maximum ${options.maxBytes} bytes).`
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
const buffer = Buffer.allocUnsafe(options.maxBytes + 1);
|
|
1405
|
+
let total = 0;
|
|
1406
|
+
while (total <= options.maxBytes) {
|
|
1407
|
+
const { bytesRead } = yield handle.read(
|
|
1408
|
+
buffer,
|
|
1409
|
+
total,
|
|
1410
|
+
options.maxBytes + 1 - total,
|
|
1411
|
+
total
|
|
1412
|
+
);
|
|
1413
|
+
if (bytesRead === 0) {
|
|
1414
|
+
break;
|
|
1415
|
+
}
|
|
1416
|
+
total += bytesRead;
|
|
1417
|
+
}
|
|
1418
|
+
if (total > options.maxBytes) {
|
|
1419
|
+
throw new Error(
|
|
1420
|
+
`${relativePath} is too large to read safely (maximum ${options.maxBytes} bytes).`
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
return { bytes: buffer.subarray(0, total), mode: opened.mode };
|
|
1424
|
+
} finally {
|
|
1425
|
+
yield handle.close();
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
function resolveExistingRoot(destination) {
|
|
1430
|
+
return __async(this, null, function* () {
|
|
1431
|
+
return realpath(destination).catch(() => path.resolve(destination));
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
function makeParents(root, relativePath) {
|
|
1435
|
+
return __async(this, null, function* () {
|
|
1436
|
+
const segments = relativePath.split("/").slice(0, -1);
|
|
1437
|
+
let current = root;
|
|
1438
|
+
for (const segment of segments) {
|
|
1439
|
+
current = path.join(current, segment);
|
|
1440
|
+
const stats = yield lstatOrNull(current);
|
|
1441
|
+
if (stats === null) {
|
|
1442
|
+
yield mkdir(current).catch((error) => __async(null, null, function* () {
|
|
1443
|
+
if (error.code !== "EEXIST") {
|
|
1444
|
+
throw error;
|
|
1445
|
+
}
|
|
1446
|
+
const raced = yield lstatOrNull(current);
|
|
1447
|
+
if (raced == null ? void 0 : raced.isSymbolicLink()) {
|
|
1448
|
+
throw new Error(
|
|
1449
|
+
`Refusing to sync: ${describeRejection({ ok: false, reason: "symlink", detail: current })}.`
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
}));
|
|
1453
|
+
continue;
|
|
1454
|
+
}
|
|
1455
|
+
if (stats.isSymbolicLink()) {
|
|
1456
|
+
throw new Error(
|
|
1457
|
+
`Refusing to sync: ${describeRejection({ ok: false, reason: "symlink", detail: current })}.`
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
if (!stats.isDirectory()) {
|
|
1461
|
+
throw new Error(
|
|
1462
|
+
`Refusing to sync: ${describeRejection({ ok: false, reason: "not_a_directory", detail: current })}.`
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
function writeFileSafely(input) {
|
|
1469
|
+
return __async(this, null, function* () {
|
|
1470
|
+
var _a2;
|
|
1471
|
+
const check = yield assertSafeTarget(input.root, input.relativePath);
|
|
1472
|
+
if (!check.ok) {
|
|
1473
|
+
throw new Error(`Refusing to sync: ${describeRejection(check)}.`);
|
|
1474
|
+
}
|
|
1475
|
+
if (!isAllowedMode(input.mode)) {
|
|
1476
|
+
throw new Error(
|
|
1477
|
+
`Refusing to sync: ${input.relativePath} asks for file mode ${((_a2 = input.mode) != null ? _a2 : 0).toString(8)}, and only 644 and 755 are accepted.`
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
yield makeParents(input.root, input.relativePath);
|
|
1481
|
+
const target = check.path;
|
|
1482
|
+
const directory = path.dirname(target);
|
|
1483
|
+
const directoryBefore = yield lstatOrNull(directory);
|
|
1484
|
+
const temporary = path.join(
|
|
1485
|
+
directory,
|
|
1486
|
+
`.${path.basename(target)}.${randomBytes(8).toString("hex")}.tmp`
|
|
1487
|
+
);
|
|
1488
|
+
let handle = null;
|
|
1489
|
+
try {
|
|
1490
|
+
handle = yield open(temporary, "wx", 384);
|
|
1491
|
+
yield handle.writeFile(input.bytes);
|
|
1492
|
+
const bits = permissionBits(input.mode);
|
|
1493
|
+
if (bits !== null && process.platform !== "win32") {
|
|
1494
|
+
yield handle.chmod(bits);
|
|
1495
|
+
}
|
|
1496
|
+
yield handle.close();
|
|
1497
|
+
handle = null;
|
|
1498
|
+
const parentStats = yield lstatOrNull(directory);
|
|
1499
|
+
if (parentStats === null || parentStats.isSymbolicLink()) {
|
|
1500
|
+
throw new Error(
|
|
1501
|
+
`Refusing to sync: ${describeRejection({ ok: false, reason: "symlink", detail: directory })}.`
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
const targetStats = yield lstatOrNull(target);
|
|
1505
|
+
if (targetStats == null ? void 0 : targetStats.isSymbolicLink()) {
|
|
1506
|
+
throw new Error(
|
|
1507
|
+
`Refusing to sync: ${describeRejection({ ok: false, reason: "symlink", detail: target })}.`
|
|
1508
|
+
);
|
|
1509
|
+
}
|
|
1510
|
+
yield rename(temporary, target);
|
|
1511
|
+
} catch (error) {
|
|
1512
|
+
yield handle == null ? void 0 : handle.close().catch(() => {
|
|
1513
|
+
});
|
|
1514
|
+
if (yield isSameDirectory(directory, directoryBefore)) {
|
|
1515
|
+
yield unlink(temporary).catch(() => {
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
throw error;
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
function isSameDirectory(directory, before) {
|
|
1523
|
+
return __async(this, null, function* () {
|
|
1524
|
+
if (before === null) {
|
|
1525
|
+
return false;
|
|
1526
|
+
}
|
|
1527
|
+
const now = yield lstatOrNull(directory);
|
|
1528
|
+
return now !== null && now.dev === before.dev && now.ino === before.ino;
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
function deleteFileSafely(root, relativePath, expectedSha256) {
|
|
1532
|
+
return __async(this, null, function* () {
|
|
1533
|
+
const check = yield assertSafeTarget(root, relativePath);
|
|
1534
|
+
if (!check.ok) {
|
|
1535
|
+
throw new Error(`Refusing to sync: ${describeRejection(check)}.`);
|
|
1536
|
+
}
|
|
1537
|
+
const stats = yield lstatOrNull(check.path);
|
|
1538
|
+
if (stats === null) {
|
|
1539
|
+
return "missing";
|
|
1540
|
+
}
|
|
1541
|
+
if (stats.isSymbolicLink()) {
|
|
1542
|
+
throw new Error(
|
|
1543
|
+
`Refusing to sync: ${describeRejection({ ok: false, reason: "symlink", detail: check.path })}.`
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
if (!stats.isFile()) {
|
|
1547
|
+
throw new Error(
|
|
1548
|
+
`Refusing to sync: ${describeRejection({ ok: false, reason: "not_a_regular_file", detail: check.path })}.`
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
let actual;
|
|
1552
|
+
try {
|
|
1553
|
+
actual = createHash("sha256").update(yield readFile(check.path)).digest("hex");
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
if (error.code === "ENOENT") {
|
|
1556
|
+
return "missing";
|
|
1557
|
+
}
|
|
1558
|
+
throw error;
|
|
1559
|
+
}
|
|
1560
|
+
if (actual !== expectedSha256) {
|
|
1561
|
+
return "modified";
|
|
1562
|
+
}
|
|
1563
|
+
try {
|
|
1564
|
+
yield unlink(check.path);
|
|
1565
|
+
return "deleted";
|
|
1566
|
+
} catch (error) {
|
|
1567
|
+
if (error.code === "ENOENT") {
|
|
1568
|
+
return "missing";
|
|
1569
|
+
}
|
|
1570
|
+
throw error;
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
function lstatOrNull(target) {
|
|
1575
|
+
return __async(this, null, function* () {
|
|
1576
|
+
try {
|
|
1577
|
+
return yield lstat(target);
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
if (error.code === "ENOENT") {
|
|
1580
|
+
return null;
|
|
1581
|
+
}
|
|
1582
|
+
throw error;
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
function resolvesInside(root, candidate) {
|
|
1587
|
+
return __async(this, null, function* () {
|
|
1588
|
+
const canonicalRoot = yield realpath(root).catch(() => path.resolve(root));
|
|
1589
|
+
const canonicalCandidate = yield realpathDeepest(candidate);
|
|
1590
|
+
return canonicalCandidate === canonicalRoot || canonicalCandidate.startsWith(canonicalRoot + path.sep);
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
function realpathDeepest(candidate) {
|
|
1594
|
+
return __async(this, null, function* () {
|
|
1595
|
+
const absolute = path.resolve(candidate);
|
|
1596
|
+
const tail = [];
|
|
1597
|
+
let current = absolute;
|
|
1598
|
+
for (; ; ) {
|
|
1599
|
+
const resolved = yield realpath(current).catch(() => null);
|
|
1600
|
+
if (resolved !== null) {
|
|
1601
|
+
return tail.length === 0 ? resolved : path.join(resolved, ...tail);
|
|
1602
|
+
}
|
|
1603
|
+
const parent = path.dirname(current);
|
|
1604
|
+
if (parent === current) {
|
|
1605
|
+
return absolute;
|
|
1606
|
+
}
|
|
1607
|
+
tail.unshift(path.basename(current));
|
|
1608
|
+
current = parent;
|
|
1609
|
+
}
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
// src/lib/context-sync/source-path.ts
|
|
1614
|
+
var MAX_CONTEXT_SOURCE_PATH_LENGTH = 512;
|
|
1615
|
+
var CONTROL_OR_FORMAT_CHARACTER = /[\u0000-\u001f\u007f-\u009f\p{Cf}]/u;
|
|
1616
|
+
var DRIVE_LETTER_PREFIX = /^[A-Za-z]:/;
|
|
1617
|
+
function hasControlOrFormatCharacter(value) {
|
|
1618
|
+
return CONTROL_OR_FORMAT_CHARACTER.test(value);
|
|
1619
|
+
}
|
|
1620
|
+
function normalizeContextSourcePath(input) {
|
|
1621
|
+
const slashed = input.normalize("NFC").replace(/\\/g, "/");
|
|
1622
|
+
if (hasControlOrFormatCharacter(slashed)) {
|
|
1623
|
+
return { ok: false, reason: "control-character" };
|
|
1624
|
+
}
|
|
1625
|
+
if (slashed.startsWith("/") || DRIVE_LETTER_PREFIX.test(slashed)) {
|
|
1626
|
+
return { ok: false, reason: "absolute" };
|
|
1627
|
+
}
|
|
1628
|
+
let path9 = slashed.replace(/\/{2,}/g, "/");
|
|
1629
|
+
while (path9.startsWith("./")) {
|
|
1630
|
+
path9 = path9.slice(2);
|
|
1631
|
+
}
|
|
1632
|
+
if (path9.length === 0) {
|
|
1633
|
+
return { ok: false, reason: "empty" };
|
|
1634
|
+
}
|
|
1635
|
+
if (path9.endsWith("/")) {
|
|
1636
|
+
return { ok: false, reason: "trailing-slash" };
|
|
1637
|
+
}
|
|
1638
|
+
if (path9.split("/").some((segment) => segment === "." || segment === "..")) {
|
|
1639
|
+
return { ok: false, reason: "dot-segment" };
|
|
1640
|
+
}
|
|
1641
|
+
if (path9.length > MAX_CONTEXT_SOURCE_PATH_LENGTH) {
|
|
1642
|
+
return { ok: false, reason: "too-long" };
|
|
1643
|
+
}
|
|
1644
|
+
return { ok: true, sourcePath: path9 };
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// src/lib/context-sync/lock.ts
|
|
1648
|
+
var CONTEXT_LOCK_DIRECTORY = ".fabric";
|
|
1649
|
+
var CONTEXT_LOCK_FILENAME = "context.lock";
|
|
1650
|
+
var CONTEXT_LOCK_VERSION = 1;
|
|
1651
|
+
var CONTEXT_LOCK_RELATIVE_PATH = `${CONTEXT_LOCK_DIRECTORY}/${CONTEXT_LOCK_FILENAME}`;
|
|
1652
|
+
var MAX_CONTEXT_LOCK_BYTES = 32 * 1024 * 1024;
|
|
1653
|
+
var SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
1654
|
+
function contextLockPath(root) {
|
|
1655
|
+
return path2.join(root, CONTEXT_LOCK_DIRECTORY, CONTEXT_LOCK_FILENAME);
|
|
1656
|
+
}
|
|
1657
|
+
function readContextLock(root) {
|
|
1658
|
+
return __async(this, null, function* () {
|
|
1659
|
+
const file = contextLockPath(root);
|
|
1660
|
+
const read = yield readFileSafely(root, CONTEXT_LOCK_RELATIVE_PATH, {
|
|
1661
|
+
maxBytes: MAX_CONTEXT_LOCK_BYTES
|
|
1662
|
+
});
|
|
1663
|
+
if (read === null) {
|
|
1664
|
+
return null;
|
|
1665
|
+
}
|
|
1666
|
+
let parsed;
|
|
1667
|
+
try {
|
|
1668
|
+
parsed = JSON.parse(new TextDecoder().decode(read.bytes));
|
|
1669
|
+
} catch (e) {
|
|
1670
|
+
throw new Error(
|
|
1671
|
+
`${file} is unreadable: it is not valid JSON. Delete it to push every file again with no expected version.`
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
const problem = lockProblem(parsed);
|
|
1675
|
+
if (problem !== null) {
|
|
1676
|
+
throw new Error(
|
|
1677
|
+
`${file} is unreadable: ${problem}. Delete it to push every file again with no expected version.`
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
return parsed;
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
function serializeLock(lock) {
|
|
1684
|
+
return `${JSON.stringify(lock, null, 2).replace(/\r\n/g, "\n")}
|
|
1685
|
+
`;
|
|
1686
|
+
}
|
|
1687
|
+
function writeContextLock(root, lock) {
|
|
1688
|
+
return __async(this, null, function* () {
|
|
1689
|
+
yield writeFileSafely({
|
|
1690
|
+
root,
|
|
1691
|
+
relativePath: CONTEXT_LOCK_RELATIVE_PATH,
|
|
1692
|
+
bytes: new TextEncoder().encode(serializeLock(lock))
|
|
1693
|
+
});
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
function lockProblem(value) {
|
|
1697
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
1698
|
+
return "it is not a JSON object";
|
|
1699
|
+
}
|
|
1700
|
+
const candidate = value;
|
|
1701
|
+
if (candidate.version !== CONTEXT_LOCK_VERSION) {
|
|
1702
|
+
return `its version is ${JSON.stringify(candidate.version)} and this build writes version ${CONTEXT_LOCK_VERSION}`;
|
|
1703
|
+
}
|
|
1704
|
+
for (const field of ["projectId", "pushedAt"]) {
|
|
1705
|
+
const actual = candidate[field];
|
|
1706
|
+
if (typeof actual !== "string" || actual.length === 0) {
|
|
1707
|
+
return `its "${field}" is not a non-empty string`;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
if (typeof candidate.files !== "object" || candidate.files === null || Array.isArray(candidate.files)) {
|
|
1711
|
+
return 'its "files" is not an object';
|
|
1712
|
+
}
|
|
1713
|
+
for (const [filePath, entry] of Object.entries(
|
|
1714
|
+
candidate.files
|
|
1715
|
+
)) {
|
|
1716
|
+
const normalized = normalizeContextSourcePath(filePath);
|
|
1717
|
+
if (!normalized.ok || normalized.sourcePath !== filePath) {
|
|
1718
|
+
return `it names ${JSON.stringify(filePath)}, which is not a path the server stores`;
|
|
1719
|
+
}
|
|
1720
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
1721
|
+
return `its entry for ${JSON.stringify(filePath)} is not an object`;
|
|
1722
|
+
}
|
|
1723
|
+
const record = entry;
|
|
1724
|
+
if (typeof record.sha256 !== "string" || !SHA256_HEX.test(record.sha256)) {
|
|
1725
|
+
return `its entry for ${JSON.stringify(filePath)} has no 64-character hex sha256`;
|
|
1726
|
+
}
|
|
1727
|
+
if (record.state === "duplicate") {
|
|
1728
|
+
if (record.contextId !== void 0) {
|
|
1729
|
+
return `its duplicate entry for ${JSON.stringify(filePath)} names a contextId, which a duplicate never has`;
|
|
1730
|
+
}
|
|
1731
|
+
continue;
|
|
1732
|
+
}
|
|
1733
|
+
if (record.state !== void 0) {
|
|
1734
|
+
return `its entry for ${JSON.stringify(filePath)} has an unknown state ${JSON.stringify(record.state)}`;
|
|
1735
|
+
}
|
|
1736
|
+
if (typeof record.contextId !== "string" || record.contextId.length === 0) {
|
|
1737
|
+
return `its entry for ${JSON.stringify(filePath)} has no contextId`;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
return null;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// src/lib/context-sync/classify.ts
|
|
1744
|
+
import { createHash as createHash2 } from "crypto";
|
|
1745
|
+
import path3 from "path";
|
|
1746
|
+
var MAX_CONTEXT_FILE_BYTES = 2 * 1024 * 1024;
|
|
1747
|
+
var CONTEXT_TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1748
|
+
".md",
|
|
1749
|
+
".markdown",
|
|
1750
|
+
".txt",
|
|
1751
|
+
".json",
|
|
1752
|
+
".yaml",
|
|
1753
|
+
".yml"
|
|
1754
|
+
]);
|
|
1755
|
+
var HAS_NON_WHITESPACE = /\S/;
|
|
1756
|
+
function hasTextExtension(relativePath) {
|
|
1757
|
+
return CONTEXT_TEXT_EXTENSIONS.has(
|
|
1758
|
+
path3.posix.extname(relativePath).toLowerCase()
|
|
1169
1759
|
);
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1760
|
+
}
|
|
1761
|
+
function classifyContextBytes(bytes) {
|
|
1762
|
+
if (bytes.byteLength > MAX_CONTEXT_FILE_BYTES) {
|
|
1763
|
+
return { ok: false, reason: "too-large" };
|
|
1764
|
+
}
|
|
1765
|
+
let content;
|
|
1766
|
+
try {
|
|
1767
|
+
content = new TextDecoder("utf-8", {
|
|
1768
|
+
fatal: true,
|
|
1769
|
+
ignoreBOM: true
|
|
1770
|
+
}).decode(bytes);
|
|
1771
|
+
} catch (e) {
|
|
1772
|
+
return { ok: false, reason: "binary" };
|
|
1773
|
+
}
|
|
1774
|
+
if (content.includes("\0")) {
|
|
1775
|
+
return { ok: false, reason: "binary" };
|
|
1776
|
+
}
|
|
1777
|
+
if (!HAS_NON_WHITESPACE.test(content)) {
|
|
1778
|
+
return { ok: false, reason: "empty" };
|
|
1779
|
+
}
|
|
1780
|
+
return { ok: true, content };
|
|
1781
|
+
}
|
|
1782
|
+
function hashContextContent(content) {
|
|
1783
|
+
return createHash2("sha256").update(content, "utf8").digest("hex");
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
// src/lib/context-sync/ignore.ts
|
|
1787
|
+
import ignore from "ignore";
|
|
1788
|
+
var CONTEXT_IGNORE_FILENAME = ".contextignore";
|
|
1789
|
+
var DEFAULT_CONTEXT_IGNORE_PATTERNS = [
|
|
1790
|
+
".git/",
|
|
1791
|
+
".fabric/",
|
|
1792
|
+
".claude/",
|
|
1793
|
+
".cursor/",
|
|
1794
|
+
".codex/",
|
|
1795
|
+
"node_modules/",
|
|
1796
|
+
"CLAUDE.md",
|
|
1797
|
+
"AGENTS.md",
|
|
1798
|
+
"GEMINI.md",
|
|
1799
|
+
"skills/",
|
|
1800
|
+
"agents/",
|
|
1801
|
+
"hooks/",
|
|
1802
|
+
"rules/",
|
|
1803
|
+
"scripts/",
|
|
1804
|
+
CONTEXT_IGNORE_FILENAME
|
|
1805
|
+
];
|
|
1806
|
+
function buildContextIgnoreRules(input = {}) {
|
|
1807
|
+
const defaults = ignore().add([...DEFAULT_CONTEXT_IGNORE_PATTERNS]);
|
|
1808
|
+
const user = ignore();
|
|
1809
|
+
if (input.contextIgnore) {
|
|
1810
|
+
user.add(input.contextIgnore);
|
|
1811
|
+
}
|
|
1812
|
+
if (input.excludes && input.excludes.length > 0) {
|
|
1813
|
+
user.add([...input.excludes]);
|
|
1814
|
+
}
|
|
1815
|
+
const ignores = (candidate) => defaults.ignores(candidate) || user.ignores(candidate);
|
|
1816
|
+
return {
|
|
1817
|
+
ignoresFile: (relativePath) => ignores(relativePath),
|
|
1818
|
+
// The trailing slash is what makes a directory-only pattern
|
|
1819
|
+
// (`drafts/`) match: gitignore semantics, as the package implements
|
|
1820
|
+
// them.
|
|
1821
|
+
ignoresDirectory: (relativePath) => ignores(`${relativePath}/`)
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
// src/lib/context-sync/walk.ts
|
|
1826
|
+
import { lstat as lstat2, readdir } from "fs/promises";
|
|
1827
|
+
import path4 from "path";
|
|
1828
|
+
function byPath(a, b) {
|
|
1829
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
1830
|
+
}
|
|
1831
|
+
function walkContextDirectory(root, rules) {
|
|
1832
|
+
return __async(this, null, function* () {
|
|
1833
|
+
const walk = {
|
|
1834
|
+
files: [],
|
|
1835
|
+
symlinks: [],
|
|
1836
|
+
special: [],
|
|
1837
|
+
unmatchable: [],
|
|
1838
|
+
ignored: []
|
|
1839
|
+
};
|
|
1840
|
+
const pending = [""];
|
|
1841
|
+
while (pending.length > 0) {
|
|
1842
|
+
const relativeDirectory = pending.pop();
|
|
1843
|
+
const names = yield readdir(path4.join(root, relativeDirectory));
|
|
1844
|
+
for (const name of names) {
|
|
1845
|
+
const relative = relativeDirectory ? `${relativeDirectory}/${name}` : name;
|
|
1846
|
+
let stats;
|
|
1847
|
+
try {
|
|
1848
|
+
stats = yield lstat2(path4.join(root, relative));
|
|
1849
|
+
} catch (error) {
|
|
1850
|
+
if (error.code === "ENOENT") {
|
|
1851
|
+
continue;
|
|
1186
1852
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1853
|
+
throw error;
|
|
1854
|
+
}
|
|
1855
|
+
let ignored;
|
|
1856
|
+
try {
|
|
1857
|
+
ignored = stats.isDirectory() ? rules.ignoresDirectory(relative) : stats.isSymbolicLink() ? rules.ignoresFile(relative) || rules.ignoresDirectory(relative) : rules.ignoresFile(relative);
|
|
1858
|
+
} catch (e) {
|
|
1859
|
+
walk.unmatchable.push(relative);
|
|
1860
|
+
continue;
|
|
1861
|
+
}
|
|
1862
|
+
if (ignored) {
|
|
1863
|
+
walk.ignored.push({
|
|
1864
|
+
path: relative,
|
|
1865
|
+
// A link was matched as a directory too, so what would sit
|
|
1866
|
+
// beneath it counts as ignored with it.
|
|
1867
|
+
directory: stats.isDirectory() || stats.isSymbolicLink()
|
|
1868
|
+
});
|
|
1869
|
+
continue;
|
|
1870
|
+
}
|
|
1871
|
+
if (stats.isSymbolicLink()) {
|
|
1872
|
+
walk.symlinks.push(relative);
|
|
1873
|
+
} else if (stats.isDirectory()) {
|
|
1874
|
+
pending.push(relative);
|
|
1875
|
+
} else if (stats.isFile()) {
|
|
1876
|
+
walk.files.push({ path: relative, size: stats.size });
|
|
1877
|
+
} else {
|
|
1878
|
+
walk.special.push(relative);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
walk.files.sort((a, b) => byPath(a.path, b.path));
|
|
1883
|
+
walk.symlinks.sort(byPath);
|
|
1884
|
+
walk.special.sort(byPath);
|
|
1885
|
+
walk.unmatchable.sort(byPath);
|
|
1886
|
+
walk.ignored.sort((a, b) => byPath(a.path, b.path));
|
|
1887
|
+
return walk;
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
// src/lib/context-sync/plan.ts
|
|
1892
|
+
function byPath2(a, b) {
|
|
1893
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
1894
|
+
}
|
|
1895
|
+
function readContextIgnore(root) {
|
|
1896
|
+
return __async(this, null, function* () {
|
|
1897
|
+
const read = yield readFileSafely(root, CONTEXT_IGNORE_FILENAME, {
|
|
1898
|
+
maxBytes: 1024 * 1024
|
|
1899
|
+
});
|
|
1900
|
+
return read === null ? null : new TextDecoder().decode(read.bytes);
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
function ignoredByWalk(ignored) {
|
|
1904
|
+
const exact = new Set(ignored.map((entry) => entry.path.normalize("NFC")));
|
|
1905
|
+
const directories = ignored.filter((entry) => entry.directory).map((entry) => `${entry.path.normalize("NFC")}/`);
|
|
1906
|
+
return (sourcePath) => exact.has(sourcePath) || directories.some((prefix) => sourcePath.startsWith(prefix));
|
|
1907
|
+
}
|
|
1908
|
+
function sharedPathProblem(entry, group2) {
|
|
1909
|
+
const others = group2.filter((other) => other.diskPath !== entry.diskPath);
|
|
1910
|
+
if (others.length === 0) {
|
|
1911
|
+
return null;
|
|
1912
|
+
}
|
|
1913
|
+
const names = others.map((other) => other.diskPath).join(", ");
|
|
1914
|
+
return others.every((other) => other.sourcePath === entry.sourcePath) ? `names the same source as ${names}` : `case-only collision with ${names}`;
|
|
1915
|
+
}
|
|
1916
|
+
function computeContextPlan(input) {
|
|
1917
|
+
return __async(this, null, function* () {
|
|
1918
|
+
var _a2, _b, _c;
|
|
1919
|
+
const { root, lock } = input;
|
|
1920
|
+
const rules = buildContextIgnoreRules({
|
|
1921
|
+
contextIgnore: yield readContextIgnore(root),
|
|
1922
|
+
excludes: input.excludes
|
|
1923
|
+
});
|
|
1924
|
+
const walk = yield walkContextDirectory(root, rules);
|
|
1925
|
+
const skipped = [
|
|
1926
|
+
...walk.symlinks.map((p) => ({ path: p, reason: "symlink" })),
|
|
1927
|
+
...walk.special.map((p) => ({
|
|
1928
|
+
path: p,
|
|
1929
|
+
reason: "unsupported-type"
|
|
1930
|
+
})),
|
|
1931
|
+
...walk.unmatchable.map((p) => ({
|
|
1932
|
+
path: p,
|
|
1933
|
+
reason: "invalid-path",
|
|
1934
|
+
detail: "cannot be matched against the ignore rules"
|
|
1935
|
+
}))
|
|
1936
|
+
];
|
|
1937
|
+
const readable = [];
|
|
1938
|
+
for (const file of walk.files) {
|
|
1939
|
+
if (file.path.includes("\\")) {
|
|
1940
|
+
skipped.push({
|
|
1941
|
+
path: file.path,
|
|
1942
|
+
reason: "invalid-path",
|
|
1943
|
+
detail: "backslash"
|
|
1944
|
+
});
|
|
1945
|
+
continue;
|
|
1190
1946
|
}
|
|
1191
|
-
const
|
|
1192
|
-
if (
|
|
1193
|
-
|
|
1194
|
-
|
|
1947
|
+
const normalized = normalizeContextSourcePath(file.path);
|
|
1948
|
+
if (!normalized.ok) {
|
|
1949
|
+
skipped.push({
|
|
1950
|
+
path: file.path,
|
|
1951
|
+
reason: "invalid-path",
|
|
1952
|
+
detail: normalized.reason
|
|
1953
|
+
});
|
|
1954
|
+
continue;
|
|
1195
1955
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1956
|
+
if (!hasTextExtension(file.path)) {
|
|
1957
|
+
skipped.push({ path: file.path, reason: "unsupported-type" });
|
|
1958
|
+
continue;
|
|
1959
|
+
}
|
|
1960
|
+
if (file.size > MAX_CONTEXT_FILE_BYTES) {
|
|
1961
|
+
skipped.push({ path: file.path, reason: "too-large" });
|
|
1962
|
+
continue;
|
|
1963
|
+
}
|
|
1964
|
+
readable.push({
|
|
1965
|
+
diskPath: file.path,
|
|
1966
|
+
sourcePath: normalized.sourcePath
|
|
1204
1967
|
});
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1968
|
+
}
|
|
1969
|
+
const byFoldedPath = /* @__PURE__ */ new Map();
|
|
1970
|
+
for (const entry of readable) {
|
|
1971
|
+
const key = entry.sourcePath.toLowerCase();
|
|
1972
|
+
byFoldedPath.set(key, [...(_a2 = byFoldedPath.get(key)) != null ? _a2 : [], entry]);
|
|
1973
|
+
}
|
|
1974
|
+
const push = [];
|
|
1975
|
+
const unchangedLocal = [];
|
|
1976
|
+
const present = /* @__PURE__ */ new Set();
|
|
1977
|
+
for (const entry of readable) {
|
|
1978
|
+
const problem = sharedPathProblem(
|
|
1979
|
+
entry,
|
|
1980
|
+
(_b = byFoldedPath.get(entry.sourcePath.toLowerCase())) != null ? _b : []
|
|
1981
|
+
);
|
|
1982
|
+
if (problem !== null) {
|
|
1983
|
+
skipped.push({
|
|
1984
|
+
path: entry.diskPath,
|
|
1985
|
+
reason: "invalid-path",
|
|
1986
|
+
detail: problem
|
|
1987
|
+
});
|
|
1988
|
+
continue;
|
|
1226
1989
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1990
|
+
const read = yield readFileSafely(root, entry.diskPath, {
|
|
1991
|
+
maxBytes: MAX_CONTEXT_FILE_BYTES
|
|
1992
|
+
}).catch((error) => {
|
|
1993
|
+
if (error instanceof Error && /too large/.test(error.message)) {
|
|
1994
|
+
return "too-large";
|
|
1231
1995
|
}
|
|
1232
|
-
|
|
1233
|
-
}
|
|
1234
|
-
printOutput(tasks, {
|
|
1235
|
-
format: opts.format,
|
|
1236
|
-
columns: [
|
|
1237
|
-
"identifier",
|
|
1238
|
-
"title",
|
|
1239
|
-
"status",
|
|
1240
|
-
"estimatedHours"
|
|
1241
|
-
]
|
|
1996
|
+
throw error;
|
|
1242
1997
|
});
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
const
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1998
|
+
if (read === null) {
|
|
1999
|
+
continue;
|
|
2000
|
+
}
|
|
2001
|
+
if (read === "too-large") {
|
|
2002
|
+
skipped.push({ path: entry.diskPath, reason: "too-large" });
|
|
2003
|
+
continue;
|
|
2004
|
+
}
|
|
2005
|
+
const verdict = classifyContextBytes(read.bytes);
|
|
2006
|
+
if (!verdict.ok) {
|
|
2007
|
+
skipped.push({ path: entry.diskPath, reason: verdict.reason });
|
|
2008
|
+
continue;
|
|
2009
|
+
}
|
|
2010
|
+
const sha256 = hashContextContent(verdict.content);
|
|
2011
|
+
const locked = lock == null ? void 0 : lock.files[entry.sourcePath];
|
|
2012
|
+
const plannedFile = {
|
|
2013
|
+
sourcePath: entry.sourcePath,
|
|
2014
|
+
diskPath: entry.diskPath,
|
|
2015
|
+
sha256,
|
|
2016
|
+
bytes: read.bytes.byteLength
|
|
2017
|
+
};
|
|
2018
|
+
if ((locked == null ? void 0 : locked.sha256) === sha256) {
|
|
2019
|
+
unchangedLocal.push(entry.sourcePath);
|
|
2020
|
+
} else {
|
|
2021
|
+
push.push(
|
|
2022
|
+
// A duplicate never had a row of its own, so there is no
|
|
2023
|
+
// version to name: it is sent like a new path.
|
|
2024
|
+
locked && locked.state !== "duplicate" ? __spreadProps(__spreadValues({}, plannedFile), { expectedContentHash: locked.sha256 }) : plannedFile
|
|
1263
2025
|
);
|
|
1264
|
-
} catch (err) {
|
|
1265
|
-
printError(err.message, 1);
|
|
1266
2026
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
2027
|
+
present.add(entry.sourcePath);
|
|
2028
|
+
}
|
|
2029
|
+
for (const skip of skipped) {
|
|
2030
|
+
const check = normalizeContextSourcePath(skip.path);
|
|
2031
|
+
if (check.ok) {
|
|
2032
|
+
present.add(check.sourcePath);
|
|
1271
2033
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
2034
|
+
}
|
|
2035
|
+
const isIgnored = ignoredByWalk(walk.ignored);
|
|
2036
|
+
const removed = [];
|
|
2037
|
+
const forgotten = [];
|
|
2038
|
+
for (const [sourcePath, entry] of Object.entries((_c = lock == null ? void 0 : lock.files) != null ? _c : {})) {
|
|
2039
|
+
if (present.has(sourcePath)) {
|
|
2040
|
+
continue;
|
|
2041
|
+
}
|
|
2042
|
+
if (entry.state === "duplicate") {
|
|
2043
|
+
forgotten.push(sourcePath);
|
|
2044
|
+
} else if (isIgnored(sourcePath)) {
|
|
2045
|
+
skipped.push({ path: sourcePath, reason: "ignored" });
|
|
2046
|
+
} else {
|
|
2047
|
+
removed.push(sourcePath);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
push.sort((a, b) => byPath2(a.sourcePath, b.sourcePath));
|
|
2051
|
+
removed.sort(byPath2);
|
|
2052
|
+
const moves = pairMoves(lock, removed, push);
|
|
2053
|
+
const moved = new Set(moves.map((move) => move.from));
|
|
2054
|
+
const arrived = new Set(moves.map((move) => move.to));
|
|
2055
|
+
unchangedLocal.sort(byPath2);
|
|
2056
|
+
forgotten.sort(byPath2);
|
|
2057
|
+
skipped.sort((a, b) => byPath2(a.path, b.path));
|
|
2058
|
+
return {
|
|
2059
|
+
push: push.filter((entry) => !arrived.has(entry.sourcePath)),
|
|
2060
|
+
moves,
|
|
2061
|
+
unchangedLocal,
|
|
2062
|
+
removed: removed.filter((sourcePath) => !moved.has(sourcePath)),
|
|
2063
|
+
forgotten,
|
|
2064
|
+
skipped
|
|
2065
|
+
};
|
|
2066
|
+
});
|
|
2067
|
+
}
|
|
2068
|
+
function pairMoves(lock, removed, push) {
|
|
2069
|
+
const added = push.filter(
|
|
2070
|
+
(entry) => (lock == null ? void 0 : lock.files[entry.sourcePath]) === void 0
|
|
1280
2071
|
);
|
|
1281
|
-
|
|
2072
|
+
const taken = /* @__PURE__ */ new Set();
|
|
2073
|
+
const moves = [];
|
|
2074
|
+
for (const from of removed) {
|
|
2075
|
+
const entry = lock == null ? void 0 : lock.files[from];
|
|
2076
|
+
if (!entry || entry.state === "duplicate") {
|
|
2077
|
+
continue;
|
|
2078
|
+
}
|
|
2079
|
+
const match = added.find(
|
|
2080
|
+
(candidate) => !taken.has(candidate.sourcePath) && candidate.sha256 === entry.sha256
|
|
2081
|
+
);
|
|
2082
|
+
if (!match) {
|
|
2083
|
+
continue;
|
|
2084
|
+
}
|
|
2085
|
+
taken.add(match.sourcePath);
|
|
2086
|
+
moves.push({
|
|
2087
|
+
from,
|
|
2088
|
+
to: match.sourcePath,
|
|
2089
|
+
diskPath: match.diskPath,
|
|
2090
|
+
sha256: match.sha256,
|
|
2091
|
+
bytes: match.bytes,
|
|
2092
|
+
contextId: entry.contextId
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
return moves;
|
|
1282
2096
|
}
|
|
1283
2097
|
|
|
1284
|
-
// src/
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
2098
|
+
// src/lib/context-sync/push.ts
|
|
2099
|
+
var DELETE_STATUSES = /* @__PURE__ */ new Set([
|
|
2100
|
+
"deleted",
|
|
2101
|
+
"already-gone",
|
|
2102
|
+
"delete-in-progress",
|
|
2103
|
+
"delete-conflict",
|
|
2104
|
+
"delete-failed"
|
|
2105
|
+
]);
|
|
2106
|
+
var RUN_LEVEL_STATUSES = /* @__PURE__ */ new Set([401, 403, 404, 429]);
|
|
2107
|
+
var RUN_LEVEL_CODES = /* @__PURE__ */ new Set(["NETWORK_ERROR", "TIMEOUT"]);
|
|
2108
|
+
function stopsTheRun(error) {
|
|
2109
|
+
const { status, code } = error;
|
|
2110
|
+
return typeof status === "number" && RUN_LEVEL_STATUSES.has(status) || typeof code === "string" && RUN_LEVEL_CODES.has(code);
|
|
2111
|
+
}
|
|
2112
|
+
function conflictOf(error) {
|
|
2113
|
+
if (typeof error !== "object" || error === null) {
|
|
2114
|
+
return null;
|
|
2115
|
+
}
|
|
2116
|
+
const conflict = error.conflict;
|
|
2117
|
+
if (typeof conflict === "object" && conflict !== null && conflict.status === "conflict" && "current" in conflict && // An object, or `null` for a named version deleted on the server.
|
|
2118
|
+
typeof conflict.current === "object") {
|
|
2119
|
+
return conflict;
|
|
2120
|
+
}
|
|
2121
|
+
return null;
|
|
2122
|
+
}
|
|
2123
|
+
function readPlannedContent(root, entry) {
|
|
2124
|
+
return __async(this, null, function* () {
|
|
2125
|
+
const read = yield readFileSafely(root, entry.diskPath, {
|
|
2126
|
+
maxBytes: MAX_CONTEXT_FILE_BYTES
|
|
2127
|
+
}).catch((error) => {
|
|
2128
|
+
if (error instanceof Error && /too large/.test(error.message)) {
|
|
2129
|
+
return null;
|
|
2130
|
+
}
|
|
2131
|
+
throw error;
|
|
2132
|
+
});
|
|
2133
|
+
if (read === null) {
|
|
2134
|
+
return null;
|
|
2135
|
+
}
|
|
2136
|
+
const verdict = classifyContextBytes(read.bytes);
|
|
2137
|
+
if (!verdict.ok || hashContextContent(verdict.content) !== entry.sha256) {
|
|
2138
|
+
return null;
|
|
2139
|
+
}
|
|
2140
|
+
return verdict.content;
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
function fromResult(sourcePath, result, movedFrom) {
|
|
2144
|
+
if (result.status === "moved") {
|
|
2145
|
+
return {
|
|
2146
|
+
sourcePath,
|
|
2147
|
+
status: "moved",
|
|
2148
|
+
movedFrom: movedFrom != null ? movedFrom : result.movedFromSourcePath,
|
|
2149
|
+
contextId: result.contextId,
|
|
2150
|
+
contentHash: result.contentHash
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
2153
|
+
const move = movedFrom === void 0 ? {} : __spreadValues({
|
|
2154
|
+
movedFrom
|
|
2155
|
+
}, result.moveNotApplied ? { moveNotApplied: result.moveNotApplied.reason } : {});
|
|
2156
|
+
if (result.status === "duplicate") {
|
|
2157
|
+
return __spreadValues({
|
|
2158
|
+
sourcePath,
|
|
2159
|
+
status: "duplicate",
|
|
2160
|
+
contentHash: result.contentHash,
|
|
2161
|
+
duplicateOfContextId: result.duplicateOfContextId,
|
|
2162
|
+
duplicateOfSourcePath: result.duplicateOfSourcePath
|
|
2163
|
+
}, move);
|
|
2164
|
+
}
|
|
2165
|
+
return __spreadValues({
|
|
2166
|
+
sourcePath,
|
|
2167
|
+
status: result.status,
|
|
2168
|
+
contextId: result.contextId,
|
|
2169
|
+
contentHash: result.contentHash
|
|
2170
|
+
}, move);
|
|
2171
|
+
}
|
|
2172
|
+
function moveKeptOldPath(result) {
|
|
2173
|
+
if ((result.status === "created" || result.status === "updated" || result.status === "unchanged" || result.status === "duplicate") && result.movedFrom !== void 0 && result.moveNotApplied !== "source-missing") {
|
|
2174
|
+
return result.movedFrom;
|
|
2175
|
+
}
|
|
2176
|
+
return null;
|
|
2177
|
+
}
|
|
2178
|
+
function pushContextPlan(input) {
|
|
2179
|
+
return __async(this, null, function* () {
|
|
2180
|
+
var _a2;
|
|
2181
|
+
const results = [];
|
|
2182
|
+
const pruneTargets = [];
|
|
2183
|
+
const { force } = input;
|
|
2184
|
+
const send = (sourcePath, content, expectedContentHash, movedFromSourcePath) => input.client.contexts.upsertSyncedFile(
|
|
2185
|
+
input.projectId,
|
|
2186
|
+
__spreadValues(__spreadValues({
|
|
2187
|
+
sourcePath,
|
|
2188
|
+
content
|
|
2189
|
+
}, expectedContentHash !== void 0 ? { expectedContentHash } : {}), movedFromSourcePath !== void 0 ? { movedFromSourcePath } : {}),
|
|
2190
|
+
{ org: input.org }
|
|
2191
|
+
);
|
|
2192
|
+
const pushEntry = (entry) => __async(null, null, function* () {
|
|
2193
|
+
var _a3;
|
|
2194
|
+
const content = yield readPlannedContent(input.root, entry);
|
|
2195
|
+
if (content === null) {
|
|
2196
|
+
return {
|
|
2197
|
+
sourcePath: entry.sourcePath,
|
|
2198
|
+
status: "changed-during-run"
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
1299
2201
|
try {
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
2202
|
+
return fromResult(
|
|
2203
|
+
entry.sourcePath,
|
|
2204
|
+
yield send(
|
|
2205
|
+
entry.sourcePath,
|
|
2206
|
+
content,
|
|
2207
|
+
entry.expectedContentHash
|
|
2208
|
+
)
|
|
2209
|
+
);
|
|
2210
|
+
} catch (error) {
|
|
2211
|
+
const conflict = conflictOf(error);
|
|
2212
|
+
if (conflict === null) {
|
|
2213
|
+
throw error;
|
|
2214
|
+
}
|
|
2215
|
+
const { current } = conflict;
|
|
2216
|
+
if (!force || current !== null && current.contentHash === null) {
|
|
2217
|
+
return {
|
|
2218
|
+
sourcePath: entry.sourcePath,
|
|
2219
|
+
status: "conflict",
|
|
2220
|
+
contentHash: conflict.contentHash,
|
|
2221
|
+
wasLocked: entry.expectedContentHash !== void 0,
|
|
2222
|
+
afterForce: false,
|
|
2223
|
+
current
|
|
2224
|
+
};
|
|
2225
|
+
}
|
|
2226
|
+
const forcedContent = yield readPlannedContent(input.root, entry);
|
|
2227
|
+
if (forcedContent === null) {
|
|
2228
|
+
return {
|
|
2229
|
+
sourcePath: entry.sourcePath,
|
|
2230
|
+
status: "changed-during-run"
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
try {
|
|
2234
|
+
const forced = fromResult(
|
|
2235
|
+
entry.sourcePath,
|
|
2236
|
+
yield send(
|
|
2237
|
+
entry.sourcePath,
|
|
2238
|
+
forcedContent,
|
|
2239
|
+
current === null ? void 0 : (_a3 = current.contentHash) != null ? _a3 : void 0
|
|
2240
|
+
)
|
|
2241
|
+
);
|
|
2242
|
+
return forced.status === "updated" && current !== null ? __spreadProps(__spreadValues({}, forced), { overwrote: current }) : forced;
|
|
2243
|
+
} catch (retryError) {
|
|
2244
|
+
const again = conflictOf(retryError);
|
|
2245
|
+
if (again === null) {
|
|
2246
|
+
throw retryError;
|
|
2247
|
+
}
|
|
2248
|
+
return {
|
|
2249
|
+
sourcePath: entry.sourcePath,
|
|
2250
|
+
status: "conflict",
|
|
2251
|
+
contentHash: again.contentHash,
|
|
2252
|
+
wasLocked: entry.expectedContentHash !== void 0,
|
|
2253
|
+
afterForce: true,
|
|
2254
|
+
current: again.current
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
1306
2257
|
}
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
2258
|
+
});
|
|
2259
|
+
const pushMove = (move) => __async(null, null, function* () {
|
|
2260
|
+
var _a3, _b, _c, _d;
|
|
2261
|
+
const entry = {
|
|
2262
|
+
sourcePath: move.to,
|
|
2263
|
+
diskPath: move.diskPath,
|
|
2264
|
+
sha256: move.sha256,
|
|
2265
|
+
bytes: move.bytes
|
|
2266
|
+
};
|
|
2267
|
+
const moveFields = { movedFrom: move.from };
|
|
2268
|
+
const content = yield readPlannedContent(input.root, entry);
|
|
2269
|
+
if (content === null) {
|
|
2270
|
+
return __spreadValues({
|
|
2271
|
+
sourcePath: move.to,
|
|
2272
|
+
status: "changed-during-run"
|
|
2273
|
+
}, moveFields);
|
|
2274
|
+
}
|
|
2275
|
+
const asConflict = (conflict, afterForce) => {
|
|
2276
|
+
var _a4;
|
|
2277
|
+
return __spreadValues(__spreadValues({
|
|
2278
|
+
sourcePath: move.to,
|
|
2279
|
+
status: "conflict",
|
|
2280
|
+
contentHash: conflict.contentHash,
|
|
2281
|
+
// About the old path (which the lock names) or the new one.
|
|
2282
|
+
wasLocked: ((_a4 = conflict.moveNotApplied) == null ? void 0 : _a4.reason) === "source-changed",
|
|
2283
|
+
afterForce,
|
|
2284
|
+
current: conflict.current
|
|
2285
|
+
}, moveFields), conflict.moveNotApplied ? { moveNotApplied: conflict.moveNotApplied.reason } : {});
|
|
2286
|
+
};
|
|
2287
|
+
try {
|
|
2288
|
+
return fromResult(
|
|
2289
|
+
move.to,
|
|
2290
|
+
yield send(move.to, content, move.sha256, move.from),
|
|
2291
|
+
move.from
|
|
2292
|
+
);
|
|
2293
|
+
} catch (error) {
|
|
2294
|
+
const conflict = conflictOf(error);
|
|
2295
|
+
if (conflict === null) {
|
|
2296
|
+
throw error;
|
|
2297
|
+
}
|
|
2298
|
+
const { current } = conflict;
|
|
2299
|
+
if (current === null && conflict.moveNotApplied === void 0) {
|
|
2300
|
+
return {
|
|
2301
|
+
sourcePath: move.to,
|
|
2302
|
+
status: "move-unsupported",
|
|
2303
|
+
movedFrom: move.from
|
|
2304
|
+
};
|
|
2305
|
+
}
|
|
2306
|
+
if (!force || current !== null && current.contentHash === null) {
|
|
2307
|
+
return asConflict(conflict, false);
|
|
2308
|
+
}
|
|
2309
|
+
const forcedContent = yield readPlannedContent(input.root, entry);
|
|
2310
|
+
if (forcedContent === null) {
|
|
2311
|
+
return __spreadValues({
|
|
2312
|
+
sourcePath: move.to,
|
|
2313
|
+
status: "changed-during-run"
|
|
2314
|
+
}, moveFields);
|
|
2315
|
+
}
|
|
2316
|
+
const aboutOldPath = ((_a3 = conflict.moveNotApplied) == null ? void 0 : _a3.reason) === "source-changed";
|
|
2317
|
+
try {
|
|
2318
|
+
if (aboutOldPath && current === null) {
|
|
2319
|
+
const created = fromResult(
|
|
2320
|
+
move.to,
|
|
2321
|
+
yield send(move.to, forcedContent)
|
|
2322
|
+
);
|
|
2323
|
+
return created.status === "moved" ? created : __spreadProps(__spreadValues(__spreadValues({}, created), moveFields), {
|
|
2324
|
+
moveNotApplied: "source-missing"
|
|
2325
|
+
});
|
|
2326
|
+
}
|
|
2327
|
+
if (aboutOldPath) {
|
|
2328
|
+
return fromResult(
|
|
2329
|
+
move.to,
|
|
2330
|
+
yield send(
|
|
2331
|
+
move.to,
|
|
2332
|
+
forcedContent,
|
|
2333
|
+
(_b = current == null ? void 0 : current.contentHash) != null ? _b : void 0,
|
|
2334
|
+
move.from
|
|
2335
|
+
),
|
|
2336
|
+
move.from
|
|
2337
|
+
);
|
|
2338
|
+
}
|
|
2339
|
+
const forced = fromResult(
|
|
2340
|
+
move.to,
|
|
2341
|
+
yield send(
|
|
2342
|
+
move.to,
|
|
2343
|
+
forcedContent,
|
|
2344
|
+
(_c = current == null ? void 0 : current.contentHash) != null ? _c : void 0
|
|
2345
|
+
)
|
|
2346
|
+
);
|
|
2347
|
+
if (forced.status === "moved") {
|
|
2348
|
+
return forced;
|
|
2349
|
+
}
|
|
2350
|
+
return __spreadValues(__spreadValues(__spreadValues({}, forced.status === "updated" && current !== null ? __spreadProps(__spreadValues({}, forced), { overwrote: current }) : forced), moveFields), conflict.moveNotApplied ? { moveNotApplied: conflict.moveNotApplied.reason } : {});
|
|
2351
|
+
} catch (retryError) {
|
|
2352
|
+
const again = conflictOf(retryError);
|
|
2353
|
+
if (again === null) {
|
|
2354
|
+
throw retryError;
|
|
2355
|
+
}
|
|
2356
|
+
if (again.moveNotApplied !== void 0) {
|
|
2357
|
+
return asConflict(again, true);
|
|
2358
|
+
}
|
|
2359
|
+
const reason = aboutOldPath ? current === null ? "source-missing" : void 0 : (_d = conflict.moveNotApplied) == null ? void 0 : _d.reason;
|
|
2360
|
+
return asConflict(
|
|
2361
|
+
reason === void 0 ? again : __spreadProps(__spreadValues({}, again), {
|
|
2362
|
+
moveNotApplied: {
|
|
2363
|
+
movedFromSourcePath: move.from,
|
|
2364
|
+
reason
|
|
2365
|
+
}
|
|
2366
|
+
}),
|
|
2367
|
+
true
|
|
2368
|
+
);
|
|
1311
2369
|
}
|
|
1312
|
-
return;
|
|
1313
2370
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
2371
|
+
});
|
|
2372
|
+
const deleteEntry = (sourcePath, sha256) => __async(null, null, function* () {
|
|
2373
|
+
const remove = (expectedContentHash) => input.client.contexts.deleteSyncedFile(
|
|
2374
|
+
input.projectId,
|
|
2375
|
+
{ sourcePath, expectedContentHash },
|
|
2376
|
+
{ org: input.org }
|
|
2377
|
+
);
|
|
2378
|
+
const fromAnswer = (answer, overwrote) => {
|
|
2379
|
+
switch (answer.status) {
|
|
2380
|
+
case "deleted":
|
|
2381
|
+
return __spreadValues({
|
|
2382
|
+
sourcePath,
|
|
2383
|
+
status: "deleted",
|
|
2384
|
+
contextId: answer.contextId,
|
|
2385
|
+
contentHash: answer.contentHash
|
|
2386
|
+
}, overwrote ? { overwrote } : {});
|
|
2387
|
+
case "absent":
|
|
2388
|
+
return { sourcePath, status: "already-gone" };
|
|
2389
|
+
case "in-progress":
|
|
2390
|
+
return { sourcePath, status: "delete-in-progress" };
|
|
2391
|
+
}
|
|
2392
|
+
};
|
|
1333
2393
|
try {
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
2394
|
+
return fromAnswer(yield remove(sha256));
|
|
2395
|
+
} catch (error) {
|
|
2396
|
+
const conflict = conflictOf(error);
|
|
2397
|
+
if (conflict === null || conflict.current === null) {
|
|
2398
|
+
throw error;
|
|
2399
|
+
}
|
|
2400
|
+
const { current } = conflict;
|
|
2401
|
+
if (!force || current.contentHash === null) {
|
|
2402
|
+
return {
|
|
2403
|
+
sourcePath,
|
|
2404
|
+
status: "delete-conflict",
|
|
2405
|
+
afterForce: false,
|
|
2406
|
+
current
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
try {
|
|
2410
|
+
return fromAnswer(yield remove(current.contentHash), current);
|
|
2411
|
+
} catch (retryError) {
|
|
2412
|
+
const again = conflictOf(retryError);
|
|
2413
|
+
if (again === null || again.current === null) {
|
|
2414
|
+
throw retryError;
|
|
2415
|
+
}
|
|
2416
|
+
return {
|
|
2417
|
+
sourcePath,
|
|
2418
|
+
status: "delete-conflict",
|
|
2419
|
+
afterForce: true,
|
|
2420
|
+
current: again.current
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
});
|
|
2425
|
+
const step = (run3, failed) => __async(null, null, function* () {
|
|
2426
|
+
try {
|
|
2427
|
+
results.push(yield run3());
|
|
2428
|
+
return null;
|
|
2429
|
+
} catch (error) {
|
|
2430
|
+
const failure = asCliFailure(error);
|
|
2431
|
+
if (stopsTheRun(error)) {
|
|
2432
|
+
return failure;
|
|
2433
|
+
}
|
|
2434
|
+
results.push(failed(error, failure.exitCode));
|
|
2435
|
+
return null;
|
|
1339
2436
|
}
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
2437
|
+
});
|
|
2438
|
+
const pruneTargetsSoFar = () => {
|
|
2439
|
+
const keptByMoves = results.map(moveKeptOldPath).filter((path9) => path9 !== null);
|
|
2440
|
+
return [.../* @__PURE__ */ new Set([...input.plan.removed, ...keptByMoves])].filter((path9) => {
|
|
2441
|
+
var _a3;
|
|
2442
|
+
const locked = (_a3 = input.lock) == null ? void 0 : _a3.files[path9];
|
|
2443
|
+
return locked !== void 0 && locked.state !== "duplicate";
|
|
2444
|
+
}).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
2445
|
+
};
|
|
2446
|
+
const stopped = (stoppedBy) => {
|
|
2447
|
+
if (input.prune && pruneTargets.length === 0) {
|
|
2448
|
+
pruneTargets.push(...pruneTargetsSoFar());
|
|
1344
2449
|
}
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
2450
|
+
return { results, stoppedBy, pruneTargets };
|
|
2451
|
+
};
|
|
2452
|
+
for (const move of input.plan.moves) {
|
|
2453
|
+
const stoppedBy = yield step(
|
|
2454
|
+
() => pushMove(move),
|
|
2455
|
+
(error, exitCode) => ({
|
|
2456
|
+
sourcePath: move.to,
|
|
2457
|
+
status: "failed",
|
|
2458
|
+
error: describeError(error),
|
|
2459
|
+
exitCode,
|
|
2460
|
+
movedFrom: move.from
|
|
2461
|
+
})
|
|
2462
|
+
);
|
|
2463
|
+
if (stoppedBy) {
|
|
2464
|
+
return stopped(stoppedBy);
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
for (const entry of input.plan.push) {
|
|
2468
|
+
const stoppedBy = yield step(
|
|
2469
|
+
() => pushEntry(entry),
|
|
2470
|
+
(error, exitCode) => ({
|
|
2471
|
+
sourcePath: entry.sourcePath,
|
|
2472
|
+
status: "failed",
|
|
2473
|
+
error: describeError(error),
|
|
2474
|
+
exitCode
|
|
2475
|
+
})
|
|
2476
|
+
);
|
|
2477
|
+
if (stoppedBy) {
|
|
2478
|
+
return stopped(stoppedBy);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
if (input.prune) {
|
|
2482
|
+
pruneTargets.push(...pruneTargetsSoFar());
|
|
2483
|
+
for (const sourcePath of pruneTargets) {
|
|
2484
|
+
const locked = (_a2 = input.lock) == null ? void 0 : _a2.files[sourcePath];
|
|
2485
|
+
if (!locked || locked.state === "duplicate") {
|
|
2486
|
+
continue;
|
|
2487
|
+
}
|
|
2488
|
+
const stoppedBy = yield step(
|
|
2489
|
+
() => deleteEntry(sourcePath, locked.sha256),
|
|
2490
|
+
(error, exitCode) => ({
|
|
2491
|
+
sourcePath,
|
|
2492
|
+
status: "delete-failed",
|
|
2493
|
+
error: describeError(error),
|
|
2494
|
+
exitCode
|
|
2495
|
+
})
|
|
2496
|
+
);
|
|
2497
|
+
if (stoppedBy) {
|
|
2498
|
+
return stopped(stoppedBy);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
return { results, stoppedBy: null, pruneTargets };
|
|
2503
|
+
});
|
|
1359
2504
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
var RESERVED_EXACT_PATHS = [
|
|
1375
|
-
".claude/settings.local.json",
|
|
1376
|
-
".codex/hooks.json"
|
|
1377
|
-
];
|
|
1378
|
-
function hasControlCharacter(input) {
|
|
1379
|
-
for (let index = 0; index < input.length; index++) {
|
|
1380
|
-
const code = input.charCodeAt(index);
|
|
1381
|
-
if (code <= 31 || code === 127) {
|
|
1382
|
-
return true;
|
|
2505
|
+
function nextContextLock(input) {
|
|
2506
|
+
var _a2, _b, _c;
|
|
2507
|
+
const files = __spreadValues({}, (_b = (_a2 = input.previous) == null ? void 0 : _a2.files) != null ? _b : {});
|
|
2508
|
+
let changed = false;
|
|
2509
|
+
const drop = (sourcePath) => {
|
|
2510
|
+
if (sourcePath in files) {
|
|
2511
|
+
delete files[sourcePath];
|
|
2512
|
+
changed = true;
|
|
2513
|
+
}
|
|
2514
|
+
};
|
|
2515
|
+
const confirm = (sourcePath, sha256, contextId) => {
|
|
2516
|
+
const before = files[sourcePath];
|
|
2517
|
+
if ((before == null ? void 0 : before.state) === void 0 && (before == null ? void 0 : before.sha256) === sha256 && before.contextId === contextId) {
|
|
2518
|
+
return;
|
|
1383
2519
|
}
|
|
2520
|
+
files[sourcePath] = { sha256, contextId };
|
|
2521
|
+
changed = true;
|
|
2522
|
+
};
|
|
2523
|
+
for (const sourcePath of (_c = input.forgotten) != null ? _c : []) {
|
|
2524
|
+
drop(sourcePath);
|
|
1384
2525
|
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
2526
|
+
for (const result of input.results) {
|
|
2527
|
+
switch (result.status) {
|
|
2528
|
+
case "deleted":
|
|
2529
|
+
case "already-gone":
|
|
2530
|
+
drop(result.sourcePath);
|
|
2531
|
+
break;
|
|
2532
|
+
case "delete-in-progress":
|
|
2533
|
+
break;
|
|
2534
|
+
case "moved":
|
|
2535
|
+
drop(result.movedFrom);
|
|
2536
|
+
confirm(
|
|
2537
|
+
result.sourcePath,
|
|
2538
|
+
result.contentHash,
|
|
2539
|
+
result.contextId
|
|
2540
|
+
);
|
|
2541
|
+
break;
|
|
2542
|
+
case "duplicate": {
|
|
2543
|
+
const before = files[result.sourcePath];
|
|
2544
|
+
if ((before == null ? void 0 : before.state) !== "duplicate" || before.sha256 !== result.contentHash) {
|
|
2545
|
+
files[result.sourcePath] = {
|
|
2546
|
+
sha256: result.contentHash,
|
|
2547
|
+
state: "duplicate"
|
|
2548
|
+
};
|
|
2549
|
+
changed = true;
|
|
2550
|
+
}
|
|
2551
|
+
if (result.moveNotApplied === "source-missing" && result.movedFrom) {
|
|
2552
|
+
drop(result.movedFrom);
|
|
2553
|
+
}
|
|
2554
|
+
break;
|
|
2555
|
+
}
|
|
2556
|
+
case "created":
|
|
2557
|
+
case "updated":
|
|
2558
|
+
case "unchanged":
|
|
2559
|
+
confirm(
|
|
2560
|
+
result.sourcePath,
|
|
2561
|
+
result.contentHash,
|
|
2562
|
+
result.contextId
|
|
2563
|
+
);
|
|
2564
|
+
if (result.moveNotApplied === "source-missing" && result.movedFrom) {
|
|
2565
|
+
drop(result.movedFrom);
|
|
2566
|
+
}
|
|
2567
|
+
break;
|
|
2568
|
+
case "conflict":
|
|
2569
|
+
if (result.moveNotApplied === "source-missing" && result.movedFrom) {
|
|
2570
|
+
drop(result.movedFrom);
|
|
2571
|
+
}
|
|
2572
|
+
break;
|
|
2573
|
+
default:
|
|
2574
|
+
break;
|
|
2575
|
+
}
|
|
1390
2576
|
}
|
|
1391
|
-
if (
|
|
1392
|
-
return
|
|
2577
|
+
if (!changed) {
|
|
2578
|
+
return null;
|
|
1393
2579
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
2580
|
+
return {
|
|
2581
|
+
version: CONTEXT_LOCK_VERSION,
|
|
2582
|
+
projectId: input.projectId,
|
|
2583
|
+
pushedAt: input.now.toISOString(),
|
|
2584
|
+
files
|
|
2585
|
+
};
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2588
|
+
// src/lib/context-sync/report.ts
|
|
2589
|
+
function countOutcomes(plan, results) {
|
|
2590
|
+
const count = (status) => results.filter((r) => r.status === status).length;
|
|
2591
|
+
return {
|
|
2592
|
+
created: count("created"),
|
|
2593
|
+
updated: count("updated"),
|
|
2594
|
+
unchanged: count("unchanged") + plan.unchangedLocal.length,
|
|
2595
|
+
duplicate: count("duplicate"),
|
|
2596
|
+
moved: count("moved"),
|
|
2597
|
+
moveUnsupported: count("move-unsupported"),
|
|
2598
|
+
conflict: count("conflict"),
|
|
2599
|
+
changedDuringRun: count("changed-during-run"),
|
|
2600
|
+
failed: count("failed"),
|
|
2601
|
+
removed: plan.removed.length,
|
|
2602
|
+
skipped: plan.skipped.length,
|
|
2603
|
+
deleted: count("deleted"),
|
|
2604
|
+
alreadyGone: count("already-gone"),
|
|
2605
|
+
deleteInProgress: count("delete-in-progress"),
|
|
2606
|
+
deleteConflict: count("delete-conflict"),
|
|
2607
|
+
deleteFailed: count("delete-failed")
|
|
2608
|
+
};
|
|
2609
|
+
}
|
|
2610
|
+
function who(current) {
|
|
2611
|
+
var _a2, _b;
|
|
2612
|
+
return (_b = (_a2 = current.contentUpdatedBy) == null ? void 0 : _a2.name) != null ? _b : "someone";
|
|
2613
|
+
}
|
|
2614
|
+
function when(current) {
|
|
2615
|
+
var _a2;
|
|
2616
|
+
return (_a2 = current.contentUpdatedAt) != null ? _a2 : "an unknown time";
|
|
2617
|
+
}
|
|
2618
|
+
function moveSourceConflictLine(result, from) {
|
|
2619
|
+
const { current } = result;
|
|
2620
|
+
if (current === null) {
|
|
2621
|
+
return `${from}: deleted on the server since your last push; not moved to ${result.sourcePath}`;
|
|
1396
2622
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
2623
|
+
let text = result.afterForce ? `changed on the server again by ${who(current)} at ${when(current)} while --force was moving it` : `changed on the server by ${who(current)} at ${when(current)} since your last push`;
|
|
2624
|
+
if (current.contentHash === null) {
|
|
2625
|
+
text += "; that version has no content hash, so --force cannot move it";
|
|
1399
2626
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
2627
|
+
return `${from}: ${text}; not moved to ${result.sourcePath}`;
|
|
2628
|
+
}
|
|
2629
|
+
function conflictLine(result) {
|
|
2630
|
+
if (result.movedFrom !== void 0) {
|
|
2631
|
+
if (result.moveNotApplied === "source-changed") {
|
|
2632
|
+
return moveSourceConflictLine(result, result.movedFrom);
|
|
2633
|
+
}
|
|
2634
|
+
if (result.moveNotApplied === "source-missing") {
|
|
2635
|
+
return `${pushConflictLine(result)}; ${result.movedFrom} gone on the server`;
|
|
2636
|
+
}
|
|
2637
|
+
return `${pushConflictLine(result)}; ${result.movedFrom} not moved`;
|
|
1402
2638
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
2639
|
+
return pushConflictLine(result);
|
|
2640
|
+
}
|
|
2641
|
+
function pushConflictLine(result) {
|
|
2642
|
+
const { current } = result;
|
|
2643
|
+
if (current === null) {
|
|
2644
|
+
return `${result.sourcePath}: deleted on the server since your last push`;
|
|
1406
2645
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
2646
|
+
let text;
|
|
2647
|
+
if (result.afterForce) {
|
|
2648
|
+
text = `changed on the server again by ${who(current)} at ${when(current)} while --force was replacing it`;
|
|
2649
|
+
} else if (result.wasLocked) {
|
|
2650
|
+
text = `changed on the server by ${who(current)} at ${when(current)} since your last push`;
|
|
2651
|
+
} else {
|
|
2652
|
+
text = `already on the server with different content, last changed by ${who(current)} at ${when(current)}`;
|
|
1409
2653
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
return {
|
|
1413
|
-
ok: false,
|
|
1414
|
-
reason: "trailing_dot_or_space",
|
|
1415
|
-
detail: input
|
|
1416
|
-
};
|
|
1417
|
-
}
|
|
1418
|
-
if (RESERVED_DEVICE_NAME.test(segment)) {
|
|
1419
|
-
return {
|
|
1420
|
-
ok: false,
|
|
1421
|
-
reason: "reserved_device_name",
|
|
1422
|
-
detail: input
|
|
1423
|
-
};
|
|
1424
|
-
}
|
|
1425
|
-
if (FORBIDDEN_NAME_CHARACTER.test(segment)) {
|
|
1426
|
-
return {
|
|
1427
|
-
ok: false,
|
|
1428
|
-
reason: "forbidden_character",
|
|
1429
|
-
detail: input
|
|
1430
|
-
};
|
|
1431
|
-
}
|
|
2654
|
+
if (current.contentHash === null) {
|
|
2655
|
+
text += "; that version has no content hash, so --force cannot replace it";
|
|
1432
2656
|
}
|
|
1433
|
-
return {
|
|
2657
|
+
return `${result.sourcePath}: ${text}`;
|
|
1434
2658
|
}
|
|
1435
|
-
function
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
if (
|
|
1439
|
-
|
|
2659
|
+
function deleteConflictLine(result) {
|
|
2660
|
+
const { current } = result;
|
|
2661
|
+
let text = result.afterForce ? `changed on the server again by ${who(current)} at ${when(current)} while --force was deleting it` : `changed on the server by ${who(current)} at ${when(current)} since your last push`;
|
|
2662
|
+
if (current.contentHash === null) {
|
|
2663
|
+
text += "; that version has no content hash, so --force cannot delete it";
|
|
1440
2664
|
}
|
|
1441
|
-
|
|
1442
|
-
return RESERVED_EXACT_PATHS.some(
|
|
1443
|
-
(reserved) => collisionKey(reserved) === key
|
|
1444
|
-
);
|
|
2665
|
+
return `${result.sourcePath}: ${text}; not deleted`;
|
|
1445
2666
|
}
|
|
1446
|
-
function
|
|
1447
|
-
|
|
2667
|
+
function storedLine(result) {
|
|
2668
|
+
if (result.overwrote) {
|
|
2669
|
+
return `${result.sourcePath}: replaced the version ${who(result.overwrote)} changed at ${when(result.overwrote)} (--force)`;
|
|
2670
|
+
}
|
|
2671
|
+
return result.sourcePath;
|
|
1448
2672
|
}
|
|
1449
|
-
function
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
2673
|
+
function isMoveFallback(result) {
|
|
2674
|
+
return (result.status === "created" || result.status === "updated" || result.status === "unchanged" || result.status === "duplicate") && result.movedFrom !== void 0;
|
|
2675
|
+
}
|
|
2676
|
+
function moveFallbackLine(result, prune) {
|
|
2677
|
+
var _a2, _b;
|
|
2678
|
+
const from = (_a2 = result.movedFrom) != null ? _a2 : "";
|
|
2679
|
+
const to = result.sourcePath;
|
|
2680
|
+
let pushed;
|
|
2681
|
+
if (result.status === "duplicate") {
|
|
2682
|
+
pushed = `${to} pushed as a duplicate of ${(_b = result.duplicateOfSourcePath) != null ? _b : "another source in this project"}`;
|
|
2683
|
+
} else if (result.overwrote) {
|
|
2684
|
+
pushed = `${to} replaced the version ${who(result.overwrote)} changed at ${when(result.overwrote)} (--force)`;
|
|
2685
|
+
} else {
|
|
2686
|
+
pushed = `${to} pushed as ${result.status}`;
|
|
2687
|
+
}
|
|
2688
|
+
const kept = prune ? "" : `; ${from} kept`;
|
|
2689
|
+
switch (result.moveNotApplied) {
|
|
2690
|
+
case "source-missing":
|
|
2691
|
+
return `${from}: gone on the server; ${pushed}`;
|
|
2692
|
+
case "target-exists":
|
|
2693
|
+
return `${from}: not moved, ${to} is already on the server; ${pushed}${kept}`;
|
|
2694
|
+
case "content-differs":
|
|
2695
|
+
return `${from}: not moved, the server's version of it differs from ${to}; ${pushed}${kept}`;
|
|
2696
|
+
default:
|
|
2697
|
+
return `${from}: not moved; ${pushed}${kept}`;
|
|
1458
2698
|
}
|
|
1459
|
-
return null;
|
|
1460
2699
|
}
|
|
1461
|
-
function
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
return `empty or malformed path (${JSON.stringify(rejection.detail)})`;
|
|
1465
|
-
case "absolute":
|
|
1466
|
-
return `absolute path refused: ${rejection.detail}`;
|
|
1467
|
-
case "traversal":
|
|
1468
|
-
return `path traversal refused: ${rejection.detail}`;
|
|
1469
|
-
case "backslash":
|
|
1470
|
-
return `backslash in path refused: ${rejection.detail}`;
|
|
1471
|
-
case "control_char":
|
|
1472
|
-
return `control character in path refused: ${JSON.stringify(rejection.detail)}`;
|
|
1473
|
-
case "trailing_separator":
|
|
1474
|
-
return `path names a directory, not a file: ${rejection.detail}`;
|
|
1475
|
-
case "trailing_dot_or_space":
|
|
1476
|
-
return `path segment ending in a dot or space refused: ${rejection.detail}`;
|
|
1477
|
-
case "reserved_device_name":
|
|
1478
|
-
return `reserved device name refused: ${rejection.detail}`;
|
|
1479
|
-
case "forbidden_character":
|
|
1480
|
-
return `path contains a character Windows will not put in a filename (< > : " | ? *): ${rejection.detail}`;
|
|
1481
|
-
case "reserved_path":
|
|
1482
|
-
return `this tool never writes or deletes ${RESERVED_ROOTS.join(", ")} or ${RESERVED_EXACT_PATHS.join(", ")}: ${rejection.detail}`;
|
|
1483
|
-
case "not_a_regular_file":
|
|
1484
|
-
return `path is not a regular file: ${rejection.detail}`;
|
|
1485
|
-
case "escapes_destination":
|
|
1486
|
-
return `path resolves outside the destination: ${rejection.detail}`;
|
|
1487
|
-
case "symlink":
|
|
1488
|
-
return `refusing to follow a symlink: ${rejection.detail}`;
|
|
1489
|
-
case "not_a_directory":
|
|
1490
|
-
return `a path component is not a directory: ${rejection.detail}`;
|
|
1491
|
-
case "collision":
|
|
1492
|
-
return `two paths name the same file on a case- or Unicode-insensitive filesystem: ${rejection.detail}`;
|
|
2700
|
+
function skippedLine(skip) {
|
|
2701
|
+
if (skip.reason === "ignored") {
|
|
2702
|
+
return `${skip.path}: now excluded by the ignore rules; server entry kept`;
|
|
1493
2703
|
}
|
|
2704
|
+
return skip.detail ? `${skip.path}: ${skip.reason} (${skip.detail})` : `${skip.path}: ${skip.reason}`;
|
|
1494
2705
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
import { constants } from "fs";
|
|
1499
|
-
import {
|
|
1500
|
-
lstat,
|
|
1501
|
-
mkdir,
|
|
1502
|
-
open,
|
|
1503
|
-
readFile,
|
|
1504
|
-
realpath,
|
|
1505
|
-
rename,
|
|
1506
|
-
unlink
|
|
1507
|
-
} from "fs/promises";
|
|
1508
|
-
import path from "path";
|
|
1509
|
-
var ALLOWED_MODES = /* @__PURE__ */ new Set([420, 493]);
|
|
1510
|
-
function isAllowedMode(mode) {
|
|
1511
|
-
if (mode === null || mode === void 0) {
|
|
1512
|
-
return true;
|
|
2706
|
+
function group(lines, label, items) {
|
|
2707
|
+
if (items.length === 0) {
|
|
2708
|
+
return;
|
|
1513
2709
|
}
|
|
1514
|
-
|
|
1515
|
-
|
|
2710
|
+
lines.push(`${label} (${items.length})`);
|
|
2711
|
+
for (const item of items) {
|
|
2712
|
+
lines.push(` ${item}`);
|
|
1516
2713
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
2714
|
+
}
|
|
2715
|
+
function formatPushReport(input) {
|
|
2716
|
+
var _a2;
|
|
2717
|
+
const { plan, results } = input;
|
|
2718
|
+
const prune = Boolean(input.prune);
|
|
2719
|
+
const of = (status) => results.filter(
|
|
2720
|
+
(r) => r.status === status && !isMoveFallback(r)
|
|
2721
|
+
);
|
|
2722
|
+
const lines = [
|
|
2723
|
+
`Context push from ${input.directory} to project ${input.projectId}:`
|
|
2724
|
+
];
|
|
2725
|
+
group(lines, "created", of("created").map(storedLine));
|
|
2726
|
+
group(lines, "updated", of("updated").map(storedLine));
|
|
2727
|
+
group(
|
|
2728
|
+
lines,
|
|
2729
|
+
"moved",
|
|
2730
|
+
of("moved").map((r) => `${r.movedFrom} -> ${r.sourcePath}`)
|
|
2731
|
+
);
|
|
2732
|
+
group(lines, "not moved", [
|
|
2733
|
+
...results.filter(isMoveFallback).map((r) => moveFallbackLine(r, prune)),
|
|
2734
|
+
...of("move-unsupported").map(
|
|
2735
|
+
(r) => `${r.movedFrom} -> ${r.sourcePath}: the server does not support moves yet; ${r.movedFrom} kept, ${r.sourcePath} not pushed`
|
|
2736
|
+
)
|
|
2737
|
+
]);
|
|
2738
|
+
group(
|
|
2739
|
+
lines,
|
|
2740
|
+
"duplicate",
|
|
2741
|
+
of("duplicate").map(
|
|
2742
|
+
(r) => {
|
|
2743
|
+
var _a3;
|
|
2744
|
+
return `${r.sourcePath}: duplicate of ${(_a3 = r.duplicateOfSourcePath) != null ? _a3 : "another source in this project"}`;
|
|
2745
|
+
}
|
|
2746
|
+
)
|
|
2747
|
+
);
|
|
2748
|
+
group(lines, "conflict", [
|
|
2749
|
+
...of("conflict").map(conflictLine),
|
|
2750
|
+
...of("delete-conflict").map(deleteConflictLine)
|
|
2751
|
+
]);
|
|
2752
|
+
group(
|
|
2753
|
+
lines,
|
|
2754
|
+
"changed during the run",
|
|
2755
|
+
of("changed-during-run").map(
|
|
2756
|
+
(r) => `${r.sourcePath}: changed-during-run; not sent, the next push sends the new content`
|
|
2757
|
+
)
|
|
2758
|
+
);
|
|
2759
|
+
group(lines, "failed", [
|
|
2760
|
+
...of("failed").map((r) => `${r.sourcePath}: ${r.error}`),
|
|
2761
|
+
...of("delete-failed").map(
|
|
2762
|
+
(r) => `${r.sourcePath}: ${r.error}; not deleted`
|
|
2763
|
+
)
|
|
2764
|
+
]);
|
|
2765
|
+
group(
|
|
2766
|
+
lines,
|
|
2767
|
+
"deleted",
|
|
2768
|
+
of("deleted").map(
|
|
2769
|
+
(r) => r.overwrote ? `${r.sourcePath}: deleted the version ${who(r.overwrote)} changed at ${when(r.overwrote)} (--force)` : r.sourcePath
|
|
2770
|
+
)
|
|
2771
|
+
);
|
|
2772
|
+
group(
|
|
2773
|
+
lines,
|
|
2774
|
+
"already gone",
|
|
2775
|
+
of("already-gone").map(
|
|
2776
|
+
(r) => `${r.sourcePath}: already gone on the server`
|
|
2777
|
+
)
|
|
2778
|
+
);
|
|
2779
|
+
group(
|
|
2780
|
+
lines,
|
|
2781
|
+
"deletion still running",
|
|
2782
|
+
of("delete-in-progress").map(
|
|
2783
|
+
(r) => `${r.sourcePath}: deletion still running on the server; run again to confirm`
|
|
2784
|
+
)
|
|
2785
|
+
);
|
|
2786
|
+
const pruned = new Set(
|
|
2787
|
+
results.filter((r) => DELETE_STATUSES.has(r.status)).map((r) => r.sourcePath)
|
|
2788
|
+
);
|
|
2789
|
+
const kept = [
|
|
2790
|
+
...plan.removed,
|
|
2791
|
+
...((_a2 = input.pruneTargets) != null ? _a2 : []).filter((p) => !plan.removed.includes(p))
|
|
2792
|
+
];
|
|
2793
|
+
group(
|
|
2794
|
+
lines,
|
|
2795
|
+
"removed",
|
|
2796
|
+
kept.filter((p) => !pruned.has(p)).map((p) => `${p}: removed locally; server entry kept`)
|
|
2797
|
+
);
|
|
2798
|
+
group(lines, "skipped", plan.skipped.map(skippedLine));
|
|
2799
|
+
const unchanged = countOutcomes(plan, results).unchanged;
|
|
2800
|
+
if (unchanged > 0) {
|
|
2801
|
+
lines.push(`unchanged (${unchanged})`);
|
|
1520
2802
|
}
|
|
1521
|
-
return
|
|
2803
|
+
return lines;
|
|
2804
|
+
}
|
|
2805
|
+
function formatDryRunReport(input) {
|
|
2806
|
+
const { plan } = input;
|
|
2807
|
+
const lines = [
|
|
2808
|
+
`Dry run of a context push from ${input.directory} to project ${input.projectId}: nothing was sent and the lock was not written.`
|
|
2809
|
+
];
|
|
2810
|
+
group(
|
|
2811
|
+
lines,
|
|
2812
|
+
"moved",
|
|
2813
|
+
plan.moves.map((move) => `${move.from} -> ${move.to}`)
|
|
2814
|
+
);
|
|
2815
|
+
group(
|
|
2816
|
+
lines,
|
|
2817
|
+
"new",
|
|
2818
|
+
plan.push.filter((entry) => entry.expectedContentHash === void 0).map((entry) => entry.sourcePath)
|
|
2819
|
+
);
|
|
2820
|
+
group(
|
|
2821
|
+
lines,
|
|
2822
|
+
"changed",
|
|
2823
|
+
plan.push.filter((entry) => entry.expectedContentHash !== void 0).map((entry) => entry.sourcePath)
|
|
2824
|
+
);
|
|
2825
|
+
if (input.prune) {
|
|
2826
|
+
group(lines, "would delete", plan.removed);
|
|
2827
|
+
} else {
|
|
2828
|
+
group(
|
|
2829
|
+
lines,
|
|
2830
|
+
"removed",
|
|
2831
|
+
plan.removed.map(
|
|
2832
|
+
(p) => `${p}: removed locally; server entry would be kept`
|
|
2833
|
+
)
|
|
2834
|
+
);
|
|
2835
|
+
}
|
|
2836
|
+
group(lines, "skipped", plan.skipped.map(skippedLine));
|
|
2837
|
+
if (plan.unchangedLocal.length > 0) {
|
|
2838
|
+
lines.push(`unchanged (${plan.unchangedLocal.length})`);
|
|
2839
|
+
}
|
|
2840
|
+
return lines;
|
|
2841
|
+
}
|
|
2842
|
+
function contextPushJson(input) {
|
|
2843
|
+
return {
|
|
2844
|
+
projectId: input.projectId,
|
|
2845
|
+
directory: input.directory,
|
|
2846
|
+
dryRun: input.dryRun,
|
|
2847
|
+
force: input.force,
|
|
2848
|
+
prune: input.prune,
|
|
2849
|
+
lock: input.lockPath,
|
|
2850
|
+
lockWritten: input.lockWritten,
|
|
2851
|
+
plan: {
|
|
2852
|
+
push: input.plan.push.map((entry) => {
|
|
2853
|
+
var _a2;
|
|
2854
|
+
return {
|
|
2855
|
+
sourcePath: entry.sourcePath,
|
|
2856
|
+
sha256: entry.sha256,
|
|
2857
|
+
bytes: entry.bytes,
|
|
2858
|
+
expectedContentHash: (_a2 = entry.expectedContentHash) != null ? _a2 : null
|
|
2859
|
+
};
|
|
2860
|
+
}),
|
|
2861
|
+
moves: input.plan.moves.map((move) => ({
|
|
2862
|
+
from: move.from,
|
|
2863
|
+
to: move.to,
|
|
2864
|
+
sha256: move.sha256,
|
|
2865
|
+
contextId: move.contextId
|
|
2866
|
+
})),
|
|
2867
|
+
unchangedLocal: input.plan.unchangedLocal,
|
|
2868
|
+
removed: input.plan.removed,
|
|
2869
|
+
forgotten: input.plan.forgotten,
|
|
2870
|
+
skipped: input.plan.skipped
|
|
2871
|
+
},
|
|
2872
|
+
results: input.results,
|
|
2873
|
+
counts: countOutcomes(input.plan, input.results)
|
|
2874
|
+
};
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
// src/commands/context/index.ts
|
|
2878
|
+
var HOOK_DEADLINE_MS = 1e4;
|
|
2879
|
+
var PUSH_TIMEOUT_MS = 6e4;
|
|
2880
|
+
function collectPattern(value, previous) {
|
|
2881
|
+
return [...previous, value];
|
|
2882
|
+
}
|
|
2883
|
+
function buildContextCommand() {
|
|
2884
|
+
const context = new Command8("context").description(
|
|
2885
|
+
"Sync local knowledge files into a project's Context"
|
|
2886
|
+
);
|
|
2887
|
+
context.command("push").description(
|
|
2888
|
+
"Push every text file under <dir> into the project's Context by its relative path"
|
|
2889
|
+
).argument(
|
|
2890
|
+
"<dir>",
|
|
2891
|
+
"Folder to push (required; never the working directory by default)"
|
|
2892
|
+
).requiredOption("--project <id>", "Project ID").option("--org <slug>", "Organization context").option(
|
|
2893
|
+
"--force",
|
|
2894
|
+
"On a conflict, replace the server's version once with this folder's"
|
|
2895
|
+
).option(
|
|
2896
|
+
"--prune",
|
|
2897
|
+
"Delete the server entries of files removed locally, only in the version last pushed"
|
|
2898
|
+
).option("--dry-run", "Print the plan; send nothing and write no lock").option(
|
|
2899
|
+
"--exclude <pattern>",
|
|
2900
|
+
"Also leave out paths matching this gitignore pattern (repeatable)",
|
|
2901
|
+
collectPattern,
|
|
2902
|
+
[]
|
|
2903
|
+
).option(
|
|
2904
|
+
"--hook",
|
|
2905
|
+
"Hook mode: never fails, never blocks, one line on stderr on failure"
|
|
2906
|
+
).option("--format <format>", "Output format: text|json").action(function(dir, opts) {
|
|
2907
|
+
return __async(this, null, function* () {
|
|
2908
|
+
yield run(opts, () => runPush(dir, opts, outputFormatFor(this)));
|
|
2909
|
+
});
|
|
2910
|
+
});
|
|
2911
|
+
return context;
|
|
1522
2912
|
}
|
|
1523
|
-
function
|
|
1524
|
-
|
|
1525
|
-
|
|
2913
|
+
function run(opts, body) {
|
|
2914
|
+
return __async(this, null, function* () {
|
|
2915
|
+
try {
|
|
2916
|
+
if (opts.hook) {
|
|
2917
|
+
yield withDeadline(HOOK_DEADLINE_MS, () => body());
|
|
2918
|
+
} else {
|
|
2919
|
+
yield body();
|
|
2920
|
+
}
|
|
2921
|
+
} catch (error) {
|
|
2922
|
+
const message = describeError(error);
|
|
2923
|
+
if (opts.hook) {
|
|
2924
|
+
process.stderr.write(`fabric: context push failed: ${message}
|
|
2925
|
+
`);
|
|
2926
|
+
process.exit(0);
|
|
2927
|
+
}
|
|
2928
|
+
process.stderr.write(`\u2717 ${message}
|
|
2929
|
+
`);
|
|
2930
|
+
process.exit(error instanceof CliFailure ? error.exitCode : 1);
|
|
2931
|
+
}
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
function contextClient(opts) {
|
|
2935
|
+
if (!getApiKey()) {
|
|
2936
|
+
throw new CliFailure(
|
|
2937
|
+
"Not authenticated. Run: fabric auth login --key <api-key>",
|
|
2938
|
+
3
|
|
2939
|
+
);
|
|
1526
2940
|
}
|
|
1527
|
-
return
|
|
2941
|
+
return getClient(
|
|
2942
|
+
opts.hook ? { timeoutMs: HOOK_DEADLINE_MS, retry: { maxRetries: 0 } } : { timeoutMs: PUSH_TIMEOUT_MS }
|
|
2943
|
+
).withoutContext();
|
|
1528
2944
|
}
|
|
1529
|
-
function
|
|
2945
|
+
function resolveFolder(dir) {
|
|
1530
2946
|
return __async(this, null, function* () {
|
|
1531
|
-
|
|
1532
|
-
|
|
2947
|
+
const directory = path5.resolve(dir);
|
|
2948
|
+
const root = yield resolveExistingRoot(directory);
|
|
2949
|
+
const stats = yield lstat3(root).catch(() => null);
|
|
2950
|
+
if (stats === null || !stats.isDirectory()) {
|
|
2951
|
+
throw new CliFailure(`${directory} is not a directory.`, 2);
|
|
2952
|
+
}
|
|
2953
|
+
return root;
|
|
1533
2954
|
});
|
|
1534
2955
|
}
|
|
1535
|
-
function
|
|
2956
|
+
function readLockForProject(root, projectId) {
|
|
1536
2957
|
return __async(this, null, function* () {
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
detail: relativePath
|
|
1543
|
-
};
|
|
1544
|
-
}
|
|
1545
|
-
const segments = relativePath.split("/");
|
|
1546
|
-
let current = root;
|
|
1547
|
-
let deepestExisting = root;
|
|
1548
|
-
for (const [index, segment] of segments.entries()) {
|
|
1549
|
-
current = path.join(current, segment);
|
|
1550
|
-
const stats = yield lstatOrNull(current);
|
|
1551
|
-
if (stats === null) {
|
|
1552
|
-
break;
|
|
1553
|
-
}
|
|
1554
|
-
if (stats.isSymbolicLink()) {
|
|
1555
|
-
return { ok: false, reason: "symlink", detail: current };
|
|
1556
|
-
}
|
|
1557
|
-
if (index < segments.length - 1 && !stats.isDirectory()) {
|
|
1558
|
-
return { ok: false, reason: "not_a_directory", detail: current };
|
|
1559
|
-
}
|
|
1560
|
-
deepestExisting = current;
|
|
2958
|
+
let lock;
|
|
2959
|
+
try {
|
|
2960
|
+
lock = yield readContextLock(root);
|
|
2961
|
+
} catch (error) {
|
|
2962
|
+
throw new CliFailure(describeError(error), 7);
|
|
1561
2963
|
}
|
|
1562
|
-
if (
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
reason: "escapes_destination",
|
|
1568
|
-
detail: relativePath
|
|
1569
|
-
};
|
|
1570
|
-
}
|
|
2964
|
+
if (lock !== null && lock.projectId !== projectId) {
|
|
2965
|
+
throw new CliFailure(
|
|
2966
|
+
`${contextLockPath(root)} belongs to project ${lock.projectId}, not ${projectId}. Push this folder to ${lock.projectId}, or remove that lock if this folder should now feed ${projectId}.`,
|
|
2967
|
+
7
|
|
2968
|
+
);
|
|
1571
2969
|
}
|
|
1572
|
-
return
|
|
2970
|
+
return lock;
|
|
1573
2971
|
});
|
|
1574
2972
|
}
|
|
1575
|
-
function
|
|
2973
|
+
function print(lines) {
|
|
2974
|
+
process.stdout.write(`${lines.join("\n")}
|
|
2975
|
+
`);
|
|
2976
|
+
}
|
|
2977
|
+
function runPush(dir, opts, format) {
|
|
1576
2978
|
return __async(this, null, function* () {
|
|
1577
|
-
const
|
|
1578
|
-
|
|
1579
|
-
|
|
2979
|
+
const root = yield resolveFolder(dir);
|
|
2980
|
+
const client = opts.dryRun ? null : contextClient(opts);
|
|
2981
|
+
const lock = yield readLockForProject(root, opts.project);
|
|
2982
|
+
const plan = yield computeContextPlan({
|
|
2983
|
+
root,
|
|
2984
|
+
lock,
|
|
2985
|
+
excludes: opts.exclude
|
|
2986
|
+
});
|
|
2987
|
+
if (client === null) {
|
|
2988
|
+
if (format === "json") {
|
|
2989
|
+
printOutput(
|
|
2990
|
+
jsonOutcome({
|
|
2991
|
+
root,
|
|
2992
|
+
opts,
|
|
2993
|
+
plan,
|
|
2994
|
+
results: [],
|
|
2995
|
+
lockWritten: false
|
|
2996
|
+
}),
|
|
2997
|
+
{ format: "json" }
|
|
2998
|
+
);
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
print(
|
|
3002
|
+
formatDryRunReport({
|
|
3003
|
+
projectId: opts.project,
|
|
3004
|
+
directory: root,
|
|
3005
|
+
plan,
|
|
3006
|
+
prune: Boolean(opts.prune)
|
|
3007
|
+
})
|
|
3008
|
+
);
|
|
3009
|
+
return;
|
|
1580
3010
|
}
|
|
1581
|
-
const
|
|
1582
|
-
|
|
1583
|
-
|
|
3011
|
+
const pushed = yield pushContextPlan({
|
|
3012
|
+
client,
|
|
3013
|
+
projectId: opts.project,
|
|
3014
|
+
org: opts.org,
|
|
3015
|
+
root,
|
|
3016
|
+
plan,
|
|
3017
|
+
force: Boolean(opts.force),
|
|
3018
|
+
prune: Boolean(opts.prune),
|
|
3019
|
+
lock
|
|
3020
|
+
});
|
|
3021
|
+
const next = nextContextLock({
|
|
3022
|
+
previous: lock,
|
|
3023
|
+
projectId: opts.project,
|
|
3024
|
+
results: pushed.results,
|
|
3025
|
+
forgotten: plan.forgotten,
|
|
3026
|
+
now: /* @__PURE__ */ new Date()
|
|
3027
|
+
});
|
|
3028
|
+
if (next !== null) {
|
|
3029
|
+
yield writeContextLock(root, next);
|
|
1584
3030
|
}
|
|
1585
|
-
if (
|
|
1586
|
-
|
|
3031
|
+
if (format === "json") {
|
|
3032
|
+
printOutput(
|
|
3033
|
+
jsonOutcome({
|
|
3034
|
+
root,
|
|
3035
|
+
opts,
|
|
3036
|
+
plan,
|
|
3037
|
+
results: pushed.results,
|
|
3038
|
+
lockWritten: next !== null
|
|
3039
|
+
}),
|
|
3040
|
+
{ format: "json" }
|
|
3041
|
+
);
|
|
3042
|
+
} else {
|
|
3043
|
+
print(
|
|
3044
|
+
formatPushReport({
|
|
3045
|
+
projectId: opts.project,
|
|
3046
|
+
directory: root,
|
|
3047
|
+
plan,
|
|
3048
|
+
results: pushed.results,
|
|
3049
|
+
prune: Boolean(opts.prune),
|
|
3050
|
+
pruneTargets: pushed.pruneTargets
|
|
3051
|
+
})
|
|
3052
|
+
);
|
|
1587
3053
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
3054
|
+
const prune = Boolean(opts.prune);
|
|
3055
|
+
if (pushed.stoppedBy !== null) {
|
|
3056
|
+
throw prune ? new CliFailure(
|
|
3057
|
+
`${pushed.stoppedBy.message} (${pruneSummary(pushed.results, pushed.pruneTargets)})`,
|
|
3058
|
+
pushed.stoppedBy.exitCode
|
|
3059
|
+
) : pushed.stoppedBy;
|
|
1590
3060
|
}
|
|
1591
|
-
|
|
3061
|
+
throwIfIncomplete(pushed.results, prune, pushed.pruneTargets);
|
|
1592
3062
|
});
|
|
1593
3063
|
}
|
|
1594
|
-
function
|
|
1595
|
-
return
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
3064
|
+
function jsonOutcome(input) {
|
|
3065
|
+
return contextPushJson({
|
|
3066
|
+
projectId: input.opts.project,
|
|
3067
|
+
directory: input.root,
|
|
3068
|
+
lockPath: contextLockPath(input.root),
|
|
3069
|
+
lockWritten: input.lockWritten,
|
|
3070
|
+
dryRun: Boolean(input.opts.dryRun),
|
|
3071
|
+
force: Boolean(input.opts.force),
|
|
3072
|
+
prune: Boolean(input.opts.prune),
|
|
3073
|
+
plan: input.plan,
|
|
3074
|
+
results: input.results
|
|
3075
|
+
});
|
|
3076
|
+
}
|
|
3077
|
+
function pruneSummary(results, targets) {
|
|
3078
|
+
const count = (status) => results.filter((r) => r.status === status).length;
|
|
3079
|
+
const settled = new Set(
|
|
3080
|
+
results.filter(
|
|
3081
|
+
(r) => r.status === "deleted" || r.status === "already-gone"
|
|
3082
|
+
).map((r) => r.sourcePath)
|
|
3083
|
+
);
|
|
3084
|
+
const notDeleted = targets.filter((path9) => !settled.has(path9)).length;
|
|
3085
|
+
let text = `--prune: ${count("deleted")} deleted, ${count("already-gone")} already gone, ${notDeleted} not deleted`;
|
|
3086
|
+
const first = results.find((r) => r.status === "delete-failed");
|
|
3087
|
+
if (first && first.status === "delete-failed") {
|
|
3088
|
+
text += `, first ${first.sourcePath}: ${first.error}`;
|
|
3089
|
+
}
|
|
3090
|
+
return text;
|
|
3091
|
+
}
|
|
3092
|
+
function throwIfIncomplete(results, prune, pruneTargets) {
|
|
3093
|
+
var _a2;
|
|
3094
|
+
const pushes = results.filter((r) => !DELETE_STATUSES.has(r.status));
|
|
3095
|
+
const failed = pushes.filter((r) => r.status === "failed");
|
|
3096
|
+
const conflicts = pushes.filter((r) => r.status === "conflict");
|
|
3097
|
+
const unsupported = pushes.filter((r) => r.status === "move-unsupported");
|
|
3098
|
+
const deleteFailed = results.filter((r) => r.status === "delete-failed");
|
|
3099
|
+
const deleteConflicts = results.filter(
|
|
3100
|
+
(r) => r.status === "delete-conflict"
|
|
3101
|
+
);
|
|
3102
|
+
const stillDeleting = results.filter(
|
|
3103
|
+
(r) => r.status === "delete-in-progress"
|
|
3104
|
+
);
|
|
3105
|
+
if (failed.length === 0 && conflicts.length === 0 && unsupported.length === 0 && deleteFailed.length === 0 && deleteConflicts.length === 0 && stillDeleting.length === 0) {
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
const sentences = [];
|
|
3109
|
+
const total = conflicts.length + failed.length + unsupported.length;
|
|
3110
|
+
if (total > 0) {
|
|
3111
|
+
const parts = [];
|
|
3112
|
+
if (conflicts.length > 0) {
|
|
3113
|
+
parts.push(
|
|
3114
|
+
`${conflicts.length} conflict${conflicts.length === 1 ? "" : "s"}`
|
|
3115
|
+
);
|
|
1599
3116
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
check.path,
|
|
1604
|
-
constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK
|
|
3117
|
+
if (unsupported.length > 0) {
|
|
3118
|
+
parts.push(
|
|
3119
|
+
`${unsupported.length} move${unsupported.length === 1 ? "" : "s"} the server does not support yet`
|
|
1605
3120
|
);
|
|
1606
|
-
} catch (error) {
|
|
1607
|
-
if (error.code === "ENOENT") {
|
|
1608
|
-
return null;
|
|
1609
|
-
}
|
|
1610
|
-
throw error;
|
|
1611
3121
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
)
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
3122
|
+
const first = failed[0];
|
|
3123
|
+
if (first && first.status === "failed") {
|
|
3124
|
+
parts.push(
|
|
3125
|
+
`${failed.length} failed, first ${first.sourcePath}: ${first.error}`
|
|
3126
|
+
);
|
|
3127
|
+
}
|
|
3128
|
+
sentences.push(
|
|
3129
|
+
`${total} of ${pushes.length} file${pushes.length === 1 ? "" : "s"} sent ${total === 1 ? "was" : "were"} not stored (${parts.join(", ")}).`
|
|
3130
|
+
);
|
|
3131
|
+
if (conflicts.length > 0) {
|
|
3132
|
+
sentences.push(
|
|
3133
|
+
"A conflict means the server holds a version this folder did not name, or deleted the one it named: compare the two in the project's Context tab, and use --force to replace the version the conflict reported with this folder's (or to send a deleted file again with no version, which recreates it, or answers duplicate if that content is already stored under another path)."
|
|
3134
|
+
);
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
if (unsupported.length > 0) {
|
|
3138
|
+
sentences.push(
|
|
3139
|
+
"This server does not support moves yet: a moved file's old path stays on the server and in the lock, its new path is not pushed, and the move is sent again on the next push."
|
|
3140
|
+
);
|
|
3141
|
+
}
|
|
3142
|
+
if (prune) {
|
|
3143
|
+
sentences.push(`${pruneSummary(results, pruneTargets)}.`);
|
|
3144
|
+
if (deleteConflicts.length > 0) {
|
|
3145
|
+
sentences.push(
|
|
3146
|
+
"A file changed on the server since your last push is not deleted: compare it in the project's Context tab, and use --force to delete the version the conflict reported."
|
|
3147
|
+
);
|
|
3148
|
+
}
|
|
3149
|
+
if (stillDeleting.length > 0) {
|
|
3150
|
+
sentences.push(
|
|
3151
|
+
"A deletion still running on the server keeps its lock entry: run again to confirm it."
|
|
3152
|
+
);
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
const firstFailure = (_a2 = failed[0]) != null ? _a2 : deleteFailed[0];
|
|
3156
|
+
throw new CliFailure(
|
|
3157
|
+
sentences.join(" "),
|
|
3158
|
+
firstFailure && (firstFailure.status === "failed" || firstFailure.status === "delete-failed") ? firstFailure.exitCode : 1
|
|
3159
|
+
);
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
// src/commands/ctx/index.ts
|
|
3163
|
+
import { Command as Command9 } from "commander";
|
|
3164
|
+
function buildCtxCommand() {
|
|
3165
|
+
const ctx = new Command9("ctx").description(
|
|
3166
|
+
"Manage the organization commands run in"
|
|
3167
|
+
);
|
|
3168
|
+
ctx.command("current").description("Show the active default context").action(() => {
|
|
3169
|
+
const stored = getDefaultContext();
|
|
3170
|
+
if (!stored) {
|
|
3171
|
+
printWarning(
|
|
3172
|
+
"No default context set. Use --org <slug> on each command, or run:\n fabric ctx use org <slug>"
|
|
3173
|
+
);
|
|
3174
|
+
return;
|
|
3175
|
+
}
|
|
3176
|
+
if (stored.type === "personal") {
|
|
3177
|
+
printWarning(
|
|
3178
|
+
"Context: personal \u2014 retired, and no command will accept it.\nReplace it with:\n fabric ctx use org <slug>"
|
|
3179
|
+
);
|
|
3180
|
+
return;
|
|
3181
|
+
}
|
|
3182
|
+
process.stdout.write(`Context: org:${stored.slug}
|
|
3183
|
+
`);
|
|
3184
|
+
});
|
|
3185
|
+
const use = new Command9("use").description(
|
|
3186
|
+
"Set the default organization for commands"
|
|
3187
|
+
);
|
|
3188
|
+
use.command("personal").description("Retired \u2014 context is organization-only").action(() => {
|
|
3189
|
+
printError(
|
|
3190
|
+
"Personal context no longer exists \u2014 every command runs inside an organization.\nSet one with:\n fabric ctx use org <slug>",
|
|
3191
|
+
2
|
|
3192
|
+
);
|
|
3193
|
+
});
|
|
3194
|
+
use.command("org <slug>").description("Use an org context by default").action((slug) => {
|
|
3195
|
+
saveDefaultContext({ type: "org", slug });
|
|
3196
|
+
printSuccess(`Default context set to: org:${slug}`);
|
|
3197
|
+
});
|
|
3198
|
+
ctx.addCommand(use);
|
|
3199
|
+
return ctx;
|
|
3200
|
+
}
|
|
3201
|
+
|
|
3202
|
+
// src/commands/features/index.ts
|
|
3203
|
+
import { Command as Command10 } from "commander";
|
|
3204
|
+
function buildFeaturesCommand() {
|
|
3205
|
+
const features = new Command10("features").description(
|
|
3206
|
+
"Manage project features"
|
|
3207
|
+
);
|
|
3208
|
+
features.command("list").description("List features in a project").requiredOption("-p, --project <id>", "Project ID").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option(
|
|
3209
|
+
"--priority <priority>",
|
|
3210
|
+
"Filter: P0_CRITICAL|P1_HIGH|P2_MEDIUM|P3_LOW"
|
|
3211
|
+
).option("--search <query>", "Search by title").option("--limit <n>", "Max results (default 50)", "50").option(
|
|
3212
|
+
"--format <format>",
|
|
3213
|
+
"Output format: table|json|yaml|csv",
|
|
3214
|
+
"table"
|
|
3215
|
+
).option("-q, --quiet", "Print IDs only").action(
|
|
3216
|
+
(opts) => __async(null, null, function* () {
|
|
3217
|
+
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
3218
|
+
const client = getClient();
|
|
3219
|
+
let list;
|
|
3220
|
+
try {
|
|
3221
|
+
list = yield client.projects.listFeatures(opts.project, {
|
|
3222
|
+
org: (ctx == null ? void 0 : ctx.type) === "org" ? ctx.slug : void 0,
|
|
3223
|
+
priority: opts.priority,
|
|
3224
|
+
search: opts.search,
|
|
3225
|
+
limit: Number(opts.limit)
|
|
3226
|
+
});
|
|
3227
|
+
} catch (err) {
|
|
3228
|
+
printError(err.message, 1);
|
|
1643
3229
|
}
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
buffer,
|
|
1649
|
-
total,
|
|
1650
|
-
options.maxBytes + 1 - total,
|
|
1651
|
-
total
|
|
1652
|
-
);
|
|
1653
|
-
if (bytesRead === 0) {
|
|
1654
|
-
break;
|
|
3230
|
+
if (opts.quiet) {
|
|
3231
|
+
for (const f of list) {
|
|
3232
|
+
process.stdout.write(`${f.id}
|
|
3233
|
+
`);
|
|
1655
3234
|
}
|
|
1656
|
-
|
|
1657
|
-
}
|
|
1658
|
-
if (total > options.maxBytes) {
|
|
1659
|
-
throw new Error(
|
|
1660
|
-
`${relativePath} is too large to read safely (maximum ${options.maxBytes} bytes).`
|
|
1661
|
-
);
|
|
3235
|
+
return;
|
|
1662
3236
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
if (stats === null) {
|
|
1682
|
-
yield mkdir(current).catch((error) => __async(null, null, function* () {
|
|
1683
|
-
if (error.code !== "EEXIST") {
|
|
1684
|
-
throw error;
|
|
1685
|
-
}
|
|
1686
|
-
const raced = yield lstatOrNull(current);
|
|
1687
|
-
if (raced == null ? void 0 : raced.isSymbolicLink()) {
|
|
1688
|
-
throw new Error(
|
|
1689
|
-
`Refusing to sync: ${describeRejection({ ok: false, reason: "symlink", detail: current })}.`
|
|
1690
|
-
);
|
|
3237
|
+
printOutput(list, {
|
|
3238
|
+
format: opts.format,
|
|
3239
|
+
columns: ["identifier", "title", "priority", "createdAt"]
|
|
3240
|
+
});
|
|
3241
|
+
})
|
|
3242
|
+
);
|
|
3243
|
+
features.command("show <id>").description("Show feature details").requiredOption("-p, --project <projectId>", "Project ID").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--format <format>", "Output format: table|json|yaml", "table").action(
|
|
3244
|
+
(id, opts) => __async(null, null, function* () {
|
|
3245
|
+
var _a2, _b, _c;
|
|
3246
|
+
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
3247
|
+
const client = getClient();
|
|
3248
|
+
let feature;
|
|
3249
|
+
try {
|
|
3250
|
+
feature = yield client.projects.getFeature(
|
|
3251
|
+
opts.project,
|
|
3252
|
+
id,
|
|
3253
|
+
{
|
|
3254
|
+
org: (ctx == null ? void 0 : ctx.type) === "org" ? ctx.slug : void 0
|
|
1691
3255
|
}
|
|
1692
|
-
}));
|
|
1693
|
-
continue;
|
|
1694
|
-
}
|
|
1695
|
-
if (stats.isSymbolicLink()) {
|
|
1696
|
-
throw new Error(
|
|
1697
|
-
`Refusing to sync: ${describeRejection({ ok: false, reason: "symlink", detail: current })}.`
|
|
1698
3256
|
);
|
|
3257
|
+
} catch (err) {
|
|
3258
|
+
printError(err.message, 1);
|
|
1699
3259
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
3260
|
+
const fmt = opts.format;
|
|
3261
|
+
if (fmt === "json" || fmt === "yaml") {
|
|
3262
|
+
printOutput(feature, { format: fmt });
|
|
3263
|
+
return;
|
|
3264
|
+
}
|
|
3265
|
+
printRecord({
|
|
3266
|
+
ID: feature.id,
|
|
3267
|
+
Identifier: feature.identifier,
|
|
3268
|
+
Title: feature.title,
|
|
3269
|
+
Priority: feature.priority,
|
|
3270
|
+
Description: (_a2 = feature.description) != null ? _a2 : "(none)",
|
|
3271
|
+
"Acceptance Criteria": (_b = feature.acceptanceCriteria) != null ? _b : "(none)",
|
|
3272
|
+
Project: feature.projectId,
|
|
3273
|
+
Assignee: (_c = feature.assigneeId) != null ? _c : "(none)",
|
|
3274
|
+
Created: feature.createdAt.slice(0, 10)
|
|
3275
|
+
});
|
|
3276
|
+
})
|
|
3277
|
+
);
|
|
3278
|
+
features.command("create <title>").description("Create a feature in a project").requiredOption("-p, --project <projectId>", "Project ID").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("-d, --description <text>", "Feature description").option(
|
|
3279
|
+
"--priority <priority>",
|
|
3280
|
+
"Priority: P0_CRITICAL|P1_HIGH|P2_MEDIUM|P3_LOW (default: P2_MEDIUM)"
|
|
3281
|
+
).option("--format <format>", "Output format: table|json|yaml", "table").action(
|
|
3282
|
+
(title, opts) => __async(null, null, function* () {
|
|
3283
|
+
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
3284
|
+
const client = getClient();
|
|
3285
|
+
let feature;
|
|
3286
|
+
try {
|
|
3287
|
+
feature = yield client.projects.createFeature(
|
|
3288
|
+
opts.project,
|
|
3289
|
+
{
|
|
3290
|
+
title,
|
|
3291
|
+
description: opts.description,
|
|
3292
|
+
priority: opts.priority,
|
|
3293
|
+
org: (ctx == null ? void 0 : ctx.type) === "org" ? ctx.slug : void 0
|
|
3294
|
+
}
|
|
1703
3295
|
);
|
|
3296
|
+
} catch (err) {
|
|
3297
|
+
printError(err.message, 1);
|
|
1704
3298
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
return __async(this, null, function* () {
|
|
1710
|
-
var _a2;
|
|
1711
|
-
const check = yield assertSafeTarget(input.root, input.relativePath);
|
|
1712
|
-
if (!check.ok) {
|
|
1713
|
-
throw new Error(`Refusing to sync: ${describeRejection(check)}.`);
|
|
1714
|
-
}
|
|
1715
|
-
if (!isAllowedMode(input.mode)) {
|
|
1716
|
-
throw new Error(
|
|
1717
|
-
`Refusing to sync: ${input.relativePath} asks for file mode ${((_a2 = input.mode) != null ? _a2 : 0).toString(8)}, and only 644 and 755 are accepted.`
|
|
1718
|
-
);
|
|
1719
|
-
}
|
|
1720
|
-
yield makeParents(input.root, input.relativePath);
|
|
1721
|
-
const target = check.path;
|
|
1722
|
-
const directory = path.dirname(target);
|
|
1723
|
-
const directoryBefore = yield lstatOrNull(directory);
|
|
1724
|
-
const temporary = path.join(
|
|
1725
|
-
directory,
|
|
1726
|
-
`.${path.basename(target)}.${randomBytes(8).toString("hex")}.tmp`
|
|
1727
|
-
);
|
|
1728
|
-
let handle = null;
|
|
1729
|
-
try {
|
|
1730
|
-
handle = yield open(temporary, "wx", 384);
|
|
1731
|
-
yield handle.writeFile(input.bytes);
|
|
1732
|
-
const bits = permissionBits(input.mode);
|
|
1733
|
-
if (bits !== null && process.platform !== "win32") {
|
|
1734
|
-
yield handle.chmod(bits);
|
|
3299
|
+
const fmt = opts.format;
|
|
3300
|
+
if (fmt === "json" || fmt === "yaml") {
|
|
3301
|
+
printOutput(feature, { format: fmt });
|
|
3302
|
+
return;
|
|
1735
3303
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
3304
|
+
printSuccess(`Feature ${feature.identifier} created`);
|
|
3305
|
+
printRecord({
|
|
3306
|
+
ID: feature.id,
|
|
3307
|
+
Identifier: feature.identifier,
|
|
3308
|
+
Title: feature.title,
|
|
3309
|
+
Priority: feature.priority,
|
|
3310
|
+
Project: feature.projectId,
|
|
3311
|
+
Created: feature.createdAt.slice(0, 10)
|
|
3312
|
+
});
|
|
3313
|
+
})
|
|
3314
|
+
);
|
|
3315
|
+
features.command("tasks").description("List tasks (sub-items) for a feature").requiredOption("-p, --project <projectId>", "Project ID").requiredOption("-f, --feature <featureId>", "Feature ID").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option(
|
|
3316
|
+
"--format <format>",
|
|
3317
|
+
"Output format: table|json|yaml|csv",
|
|
3318
|
+
"table"
|
|
3319
|
+
).option("-q, --quiet", "Print IDs only").action(
|
|
3320
|
+
(opts) => __async(null, null, function* () {
|
|
3321
|
+
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
3322
|
+
const client = getClient();
|
|
3323
|
+
let tasks;
|
|
3324
|
+
try {
|
|
3325
|
+
tasks = yield client.projects.listTasks(
|
|
3326
|
+
opts.project,
|
|
3327
|
+
opts.feature,
|
|
3328
|
+
{
|
|
3329
|
+
org: (ctx == null ? void 0 : ctx.type) === "org" ? ctx.slug : void 0
|
|
3330
|
+
}
|
|
1742
3331
|
);
|
|
3332
|
+
} catch (err) {
|
|
3333
|
+
printError(err.message, 1);
|
|
1743
3334
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
3335
|
+
if (opts.quiet) {
|
|
3336
|
+
for (const t of tasks) {
|
|
3337
|
+
process.stdout.write(`${t.id}
|
|
3338
|
+
`);
|
|
3339
|
+
}
|
|
3340
|
+
return;
|
|
1749
3341
|
}
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
3342
|
+
printOutput(tasks, {
|
|
3343
|
+
format: opts.format,
|
|
3344
|
+
columns: [
|
|
3345
|
+
"identifier",
|
|
3346
|
+
"title",
|
|
3347
|
+
"status",
|
|
3348
|
+
"estimatedHours"
|
|
3349
|
+
]
|
|
1753
3350
|
});
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
const stats = yield lstatOrNull(check.path);
|
|
1778
|
-
if (stats === null) {
|
|
1779
|
-
return "missing";
|
|
1780
|
-
}
|
|
1781
|
-
if (stats.isSymbolicLink()) {
|
|
1782
|
-
throw new Error(
|
|
1783
|
-
`Refusing to sync: ${describeRejection({ ok: false, reason: "symlink", detail: check.path })}.`
|
|
1784
|
-
);
|
|
1785
|
-
}
|
|
1786
|
-
if (!stats.isFile()) {
|
|
1787
|
-
throw new Error(
|
|
1788
|
-
`Refusing to sync: ${describeRejection({ ok: false, reason: "not_a_regular_file", detail: check.path })}.`
|
|
1789
|
-
);
|
|
1790
|
-
}
|
|
1791
|
-
let actual;
|
|
1792
|
-
try {
|
|
1793
|
-
actual = createHash("sha256").update(yield readFile(check.path)).digest("hex");
|
|
1794
|
-
} catch (error) {
|
|
1795
|
-
if (error.code === "ENOENT") {
|
|
1796
|
-
return "missing";
|
|
3351
|
+
})
|
|
3352
|
+
);
|
|
3353
|
+
features.command("update <id>").description("Update a feature").requiredOption("-p, --project <projectId>", "Project ID").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--title <title>", "New title").option("-d, --description <text>", "New description").option(
|
|
3354
|
+
"--priority <priority>",
|
|
3355
|
+
"Priority: P0_CRITICAL|P1_HIGH|P2_MEDIUM|P3_LOW"
|
|
3356
|
+
).option("--format <format>", "Output format: table|json|yaml", "table").action(
|
|
3357
|
+
(id, opts) => __async(null, null, function* () {
|
|
3358
|
+
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
3359
|
+
const client = getClient();
|
|
3360
|
+
let feature;
|
|
3361
|
+
try {
|
|
3362
|
+
feature = yield client.projects.updateFeature(
|
|
3363
|
+
opts.project,
|
|
3364
|
+
id,
|
|
3365
|
+
{
|
|
3366
|
+
title: opts.title,
|
|
3367
|
+
description: opts.description,
|
|
3368
|
+
priority: opts.priority,
|
|
3369
|
+
org: (ctx == null ? void 0 : ctx.type) === "org" ? ctx.slug : void 0
|
|
3370
|
+
}
|
|
3371
|
+
);
|
|
3372
|
+
} catch (err) {
|
|
3373
|
+
printError(err.message, 1);
|
|
1797
3374
|
}
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
}
|
|
1803
|
-
try {
|
|
1804
|
-
yield unlink(check.path);
|
|
1805
|
-
return "deleted";
|
|
1806
|
-
} catch (error) {
|
|
1807
|
-
if (error.code === "ENOENT") {
|
|
1808
|
-
return "missing";
|
|
3375
|
+
const fmt = opts.format;
|
|
3376
|
+
if (fmt === "json" || fmt === "yaml") {
|
|
3377
|
+
printOutput(feature, { format: fmt });
|
|
3378
|
+
return;
|
|
1809
3379
|
}
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
3380
|
+
printRecord({
|
|
3381
|
+
ID: feature.id,
|
|
3382
|
+
Identifier: feature.identifier,
|
|
3383
|
+
Title: feature.title,
|
|
3384
|
+
Priority: feature.priority,
|
|
3385
|
+
Updated: feature.updatedAt.slice(0, 10)
|
|
3386
|
+
});
|
|
3387
|
+
})
|
|
3388
|
+
);
|
|
3389
|
+
return features;
|
|
1813
3390
|
}
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
3391
|
+
|
|
3392
|
+
// src/commands/frames/index.ts
|
|
3393
|
+
import { Command as Command11 } from "commander";
|
|
3394
|
+
function buildFramesCommand() {
|
|
3395
|
+
const frames = new Command11("frames").description(
|
|
3396
|
+
"Manage AI-generated frames and slideshows"
|
|
3397
|
+
);
|
|
3398
|
+
frames.command("list").description("List frames and slideshows").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--limit <n>", "Max results (default 50)", "50").option(
|
|
3399
|
+
"--format <format>",
|
|
3400
|
+
"Output format: table|json|yaml|csv",
|
|
3401
|
+
"table"
|
|
3402
|
+
).option("-q, --quiet", "Print IDs only").action(
|
|
3403
|
+
(opts) => __async(null, null, function* () {
|
|
3404
|
+
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
3405
|
+
const client = getClient();
|
|
3406
|
+
let list;
|
|
3407
|
+
try {
|
|
3408
|
+
list = yield client.frames.list({
|
|
3409
|
+
org: (ctx == null ? void 0 : ctx.type) === "org" ? ctx.slug : void 0,
|
|
3410
|
+
limit: Number(opts.limit)
|
|
3411
|
+
});
|
|
3412
|
+
} catch (err) {
|
|
3413
|
+
printError(err.message, 1);
|
|
1821
3414
|
}
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
const canonicalRoot = yield realpath(root).catch(() => path.resolve(root));
|
|
1829
|
-
const canonicalCandidate = yield realpathDeepest(candidate);
|
|
1830
|
-
return canonicalCandidate === canonicalRoot || canonicalCandidate.startsWith(canonicalRoot + path.sep);
|
|
1831
|
-
});
|
|
1832
|
-
}
|
|
1833
|
-
function realpathDeepest(candidate) {
|
|
1834
|
-
return __async(this, null, function* () {
|
|
1835
|
-
const absolute = path.resolve(candidate);
|
|
1836
|
-
const tail = [];
|
|
1837
|
-
let current = absolute;
|
|
1838
|
-
for (; ; ) {
|
|
1839
|
-
const resolved = yield realpath(current).catch(() => null);
|
|
1840
|
-
if (resolved !== null) {
|
|
1841
|
-
return tail.length === 0 ? resolved : path.join(resolved, ...tail);
|
|
3415
|
+
if (opts.quiet) {
|
|
3416
|
+
for (const f of list) {
|
|
3417
|
+
process.stdout.write(`${f.id}
|
|
3418
|
+
`);
|
|
3419
|
+
}
|
|
3420
|
+
return;
|
|
1842
3421
|
}
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
3422
|
+
printOutput(list, {
|
|
3423
|
+
format: opts.format,
|
|
3424
|
+
columns: [
|
|
3425
|
+
"id",
|
|
3426
|
+
"title",
|
|
3427
|
+
"fileType",
|
|
3428
|
+
"status",
|
|
3429
|
+
"isPublic",
|
|
3430
|
+
"updatedAt"
|
|
3431
|
+
]
|
|
3432
|
+
});
|
|
3433
|
+
})
|
|
3434
|
+
);
|
|
3435
|
+
frames.command("show <id>").description("Show frame details").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--format <format>", "Output format: table|json|yaml", "table").action(
|
|
3436
|
+
(id, opts) => __async(null, null, function* () {
|
|
3437
|
+
var _a2, _b, _c;
|
|
3438
|
+
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
3439
|
+
const client = getClient();
|
|
3440
|
+
let frame;
|
|
3441
|
+
try {
|
|
3442
|
+
frame = yield client.frames.get(id, {
|
|
3443
|
+
org: (ctx == null ? void 0 : ctx.type) === "org" ? ctx.slug : void 0
|
|
3444
|
+
});
|
|
3445
|
+
} catch (err) {
|
|
3446
|
+
printError(err.message, 1);
|
|
1846
3447
|
}
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
3448
|
+
const fmt = opts.format;
|
|
3449
|
+
if (fmt === "json" || fmt === "yaml") {
|
|
3450
|
+
printOutput(frame, { format: fmt });
|
|
3451
|
+
return;
|
|
3452
|
+
}
|
|
3453
|
+
printRecord({
|
|
3454
|
+
ID: frame.id,
|
|
3455
|
+
Title: frame.title,
|
|
3456
|
+
Kind: (_a2 = frame.kind) != null ? _a2 : "(none)",
|
|
3457
|
+
"File Type": frame.fileType,
|
|
3458
|
+
Status: frame.status,
|
|
3459
|
+
Public: frame.isPublic ? "yes" : "no",
|
|
3460
|
+
"Share Token": (_b = frame.shareToken) != null ? _b : "(none)",
|
|
3461
|
+
Description: (_c = frame.description) != null ? _c : "(none)",
|
|
3462
|
+
Updated: frame.updatedAt.slice(0, 10)
|
|
3463
|
+
});
|
|
3464
|
+
})
|
|
3465
|
+
);
|
|
3466
|
+
return frames;
|
|
1851
3467
|
}
|
|
1852
3468
|
|
|
3469
|
+
// src/commands/instructions/index.ts
|
|
3470
|
+
import path8 from "path";
|
|
3471
|
+
import { Command as Command12 } from "commander";
|
|
3472
|
+
|
|
1853
3473
|
// src/lib/instructions/apply.ts
|
|
3474
|
+
import { createHash as createHash3 } from "crypto";
|
|
3475
|
+
import { chmod } from "fs/promises";
|
|
1854
3476
|
function applyPlan(input) {
|
|
1855
3477
|
return __async(this, null, function* () {
|
|
1856
3478
|
var _a2, _b;
|
|
@@ -1880,7 +3502,7 @@ function applyPlan(input) {
|
|
|
1880
3502
|
`The downloaded bundle is missing ${entry.path}. Nothing was written.`
|
|
1881
3503
|
);
|
|
1882
3504
|
}
|
|
1883
|
-
const actual =
|
|
3505
|
+
const actual = createHash3("sha256").update(bytes).digest("hex");
|
|
1884
3506
|
if (actual !== entry.sha256) {
|
|
1885
3507
|
throw new Error(
|
|
1886
3508
|
`Checksum mismatch for ${entry.path}: the published snapshot expects ${entry.sha256} and the downloaded bundle contains ${actual}. Nothing was written.`
|
|
@@ -2081,12 +3703,12 @@ function extractBundle(archive, wanted) {
|
|
|
2081
3703
|
}
|
|
2082
3704
|
|
|
2083
3705
|
// src/lib/instructions/hook.ts
|
|
2084
|
-
import
|
|
2085
|
-
var CLAUDE_SETTINGS_RELATIVE_PATH =
|
|
3706
|
+
import path6 from "path";
|
|
3707
|
+
var CLAUDE_SETTINGS_RELATIVE_PATH = path6.join(
|
|
2086
3708
|
".claude",
|
|
2087
3709
|
"settings.local.json"
|
|
2088
3710
|
);
|
|
2089
|
-
var CODEX_HOOKS_RELATIVE_PATH =
|
|
3711
|
+
var CODEX_HOOKS_RELATIVE_PATH = path6.join(".codex", "hooks.json");
|
|
2090
3712
|
var CLAUDE_SETTINGS_POSIX_PATH = ".claude/settings.local.json";
|
|
2091
3713
|
var CODEX_HOOKS_POSIX_PATH = ".codex/hooks.json";
|
|
2092
3714
|
var HOOK_TIMEOUT_SECONDS = 15;
|
|
@@ -2110,7 +3732,7 @@ function mergeSessionStartHook(input) {
|
|
|
2110
3732
|
return __async(this, null, function* () {
|
|
2111
3733
|
var _a2;
|
|
2112
3734
|
const hookTarget = hookTargetFor((_a2 = input.tool) != null ? _a2 : "claude-code");
|
|
2113
|
-
const settingsPath =
|
|
3735
|
+
const settingsPath = path6.join(input.root, hookTarget.relativePath);
|
|
2114
3736
|
let existingRaw;
|
|
2115
3737
|
try {
|
|
2116
3738
|
const existing = yield readFileSafely(
|
|
@@ -2150,14 +3772,14 @@ function mergeSessionStartHook(input) {
|
|
|
2150
3772
|
throw refuseShape(settingsPath, '"hooks.SessionStart" is not an array');
|
|
2151
3773
|
}
|
|
2152
3774
|
const rawGroups = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
|
|
2153
|
-
for (const [index,
|
|
2154
|
-
if (!isRecord(
|
|
3775
|
+
for (const [index, group2] of rawGroups.entries()) {
|
|
3776
|
+
if (!isRecord(group2)) {
|
|
2155
3777
|
throw refuseShape(
|
|
2156
3778
|
settingsPath,
|
|
2157
3779
|
`"hooks.SessionStart[${index}]" is not an object`
|
|
2158
3780
|
);
|
|
2159
3781
|
}
|
|
2160
|
-
if (
|
|
3782
|
+
if (group2.hooks !== void 0 && !Array.isArray(group2.hooks)) {
|
|
2161
3783
|
throw refuseShape(
|
|
2162
3784
|
settingsPath,
|
|
2163
3785
|
`"hooks.SessionStart[${index}].hooks" is not an array`
|
|
@@ -2165,8 +3787,8 @@ function mergeSessionStartHook(input) {
|
|
|
2165
3787
|
}
|
|
2166
3788
|
}
|
|
2167
3789
|
const sessionStart = rawGroups.map(
|
|
2168
|
-
(
|
|
2169
|
-
hooks: Array.isArray(
|
|
3790
|
+
(group2) => __spreadProps(__spreadValues({}, group2), {
|
|
3791
|
+
hooks: Array.isArray(group2 == null ? void 0 : group2.hooks) ? [...group2.hooks] : []
|
|
2170
3792
|
})
|
|
2171
3793
|
);
|
|
2172
3794
|
const replacement = {
|
|
@@ -2176,16 +3798,16 @@ function mergeSessionStartHook(input) {
|
|
|
2176
3798
|
};
|
|
2177
3799
|
let replacedCount = 0;
|
|
2178
3800
|
const pruned = [];
|
|
2179
|
-
for (const
|
|
2180
|
-
const kept =
|
|
3801
|
+
for (const group2 of sessionStart) {
|
|
3802
|
+
const kept = group2.hooks.filter(
|
|
2181
3803
|
(hook) => !isFabricHookFor(hook, input.projectId)
|
|
2182
3804
|
);
|
|
2183
|
-
const removed =
|
|
3805
|
+
const removed = group2.hooks.length - kept.length;
|
|
2184
3806
|
replacedCount += removed;
|
|
2185
3807
|
if (kept.length === 0 && removed > 0) {
|
|
2186
3808
|
continue;
|
|
2187
3809
|
}
|
|
2188
|
-
pruned.push(__spreadProps(__spreadValues({},
|
|
3810
|
+
pruned.push(__spreadProps(__spreadValues({}, group2), { hooks: kept }));
|
|
2189
3811
|
}
|
|
2190
3812
|
pruned.push({ hooks: [replacement] });
|
|
2191
3813
|
const next = __spreadProps(__spreadValues({}, settings), {
|
|
@@ -2258,14 +3880,14 @@ function isRecord(value) {
|
|
|
2258
3880
|
|
|
2259
3881
|
// src/lib/instructions/lock.ts
|
|
2260
3882
|
import { readFile as readFile2 } from "fs/promises";
|
|
2261
|
-
import
|
|
3883
|
+
import path7 from "path";
|
|
2262
3884
|
var LOCK_DIRECTORY = ".fabric";
|
|
2263
3885
|
var LOCK_FILENAME = "instructions.lock";
|
|
2264
3886
|
var LOCK_VERSION = 1;
|
|
2265
3887
|
var LOCK_RELATIVE_PATH = `${LOCK_DIRECTORY}/${LOCK_FILENAME}`;
|
|
2266
|
-
var
|
|
3888
|
+
var SHA256_HEX2 = /^[0-9a-f]{64}$/;
|
|
2267
3889
|
function lockPath(destination) {
|
|
2268
|
-
return
|
|
3890
|
+
return path7.join(destination, LOCK_DIRECTORY, LOCK_FILENAME);
|
|
2269
3891
|
}
|
|
2270
3892
|
function readLock(destination) {
|
|
2271
3893
|
return __async(this, null, function* () {
|
|
@@ -2287,7 +3909,7 @@ function readLock(destination) {
|
|
|
2287
3909
|
`${file} is unreadable: it is not valid JSON. Delete it to start from a clean sync.`
|
|
2288
3910
|
);
|
|
2289
3911
|
}
|
|
2290
|
-
const problem =
|
|
3912
|
+
const problem = lockProblem2(parsed);
|
|
2291
3913
|
if (problem !== null) {
|
|
2292
3914
|
throw new Error(
|
|
2293
3915
|
`${file} is unreadable: ${problem}. Delete it to start from a clean sync.`
|
|
@@ -2296,7 +3918,7 @@ function readLock(destination) {
|
|
|
2296
3918
|
return parsed;
|
|
2297
3919
|
});
|
|
2298
3920
|
}
|
|
2299
|
-
function
|
|
3921
|
+
function serializeLock2(lock) {
|
|
2300
3922
|
return `${JSON.stringify(lock, null, 2).replace(/\r\n/g, "\n")}
|
|
2301
3923
|
`;
|
|
2302
3924
|
}
|
|
@@ -2305,11 +3927,11 @@ function writeLock(root, lock) {
|
|
|
2305
3927
|
yield writeFileSafely({
|
|
2306
3928
|
root,
|
|
2307
3929
|
relativePath: LOCK_RELATIVE_PATH,
|
|
2308
|
-
bytes: new TextEncoder().encode(
|
|
3930
|
+
bytes: new TextEncoder().encode(serializeLock2(lock))
|
|
2309
3931
|
});
|
|
2310
3932
|
});
|
|
2311
3933
|
}
|
|
2312
|
-
function
|
|
3934
|
+
function lockProblem2(value) {
|
|
2313
3935
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2314
3936
|
return "it is not a JSON object";
|
|
2315
3937
|
}
|
|
@@ -2341,7 +3963,7 @@ function lockProblem(value) {
|
|
|
2341
3963
|
return `its entry for ${JSON.stringify(filePath)} is not an object`;
|
|
2342
3964
|
}
|
|
2343
3965
|
const record = entry;
|
|
2344
|
-
if (typeof record.sha256 !== "string" || !
|
|
3966
|
+
if (typeof record.sha256 !== "string" || !SHA256_HEX2.test(record.sha256)) {
|
|
2345
3967
|
return `its entry for ${JSON.stringify(filePath)} has no 64-character hex sha256`;
|
|
2346
3968
|
}
|
|
2347
3969
|
if (record.mode !== null && (typeof record.mode !== "number" || !Number.isInteger(record.mode) || record.mode < 0)) {
|
|
@@ -2352,8 +3974,8 @@ function lockProblem(value) {
|
|
|
2352
3974
|
}
|
|
2353
3975
|
|
|
2354
3976
|
// src/lib/instructions/manifest.ts
|
|
2355
|
-
import { createHash as
|
|
2356
|
-
var
|
|
3977
|
+
import { createHash as createHash4 } from "crypto";
|
|
3978
|
+
var SHA256_HEX3 = /^[0-9a-f]{64}$/;
|
|
2357
3979
|
var MANIFEST_LIMITS = {
|
|
2358
3980
|
maxFiles: 5e3,
|
|
2359
3981
|
maxTotalBytes: 52428800,
|
|
@@ -2372,7 +3994,7 @@ function maxArchiveBytes(entries) {
|
|
|
2372
3994
|
function computeSnapshotDigest(entries) {
|
|
2373
3995
|
const lines = [...entries].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0).map((entry) => `${entry.path}\0${entry.sha256}
|
|
2374
3996
|
`).join("");
|
|
2375
|
-
return
|
|
3997
|
+
return createHash4("sha256").update(Buffer.from(lines, "utf8")).digest("hex");
|
|
2376
3998
|
}
|
|
2377
3999
|
function assertValidManifest(input) {
|
|
2378
4000
|
const { manifest, snapshot } = input;
|
|
@@ -2458,7 +4080,7 @@ function validateEntry(raw) {
|
|
|
2458
4080
|
})}.`
|
|
2459
4081
|
);
|
|
2460
4082
|
}
|
|
2461
|
-
if (typeof entry.sha256 !== "string" || !
|
|
4083
|
+
if (typeof entry.sha256 !== "string" || !SHA256_HEX3.test(entry.sha256)) {
|
|
2462
4084
|
throw new Error(
|
|
2463
4085
|
`The manifest entry for ${entry.path} has no 64-character hex sha256. Nothing was changed.`
|
|
2464
4086
|
);
|
|
@@ -2493,11 +4115,11 @@ function validateEntry(raw) {
|
|
|
2493
4115
|
}
|
|
2494
4116
|
|
|
2495
4117
|
// src/lib/instructions/plan.ts
|
|
2496
|
-
import { createHash as
|
|
4118
|
+
import { createHash as createHash5 } from "crypto";
|
|
2497
4119
|
function hashLocalFile(root, relativePath) {
|
|
2498
4120
|
return __async(this, null, function* () {
|
|
2499
4121
|
const read = yield readFileSafely(root, relativePath);
|
|
2500
|
-
return read === null ? null :
|
|
4122
|
+
return read === null ? null : createHash5("sha256").update(read.bytes).digest("hex");
|
|
2501
4123
|
});
|
|
2502
4124
|
}
|
|
2503
4125
|
function lockPaths(lock) {
|
|
@@ -2550,7 +4172,7 @@ function verifyLedger(input) {
|
|
|
2550
4172
|
drifted.push(`${lockedPath} (missing)`);
|
|
2551
4173
|
continue;
|
|
2552
4174
|
}
|
|
2553
|
-
const actual =
|
|
4175
|
+
const actual = createHash5("sha256").update(read.bytes).digest("hex");
|
|
2554
4176
|
if (actual !== locked.sha256) {
|
|
2555
4177
|
drifted.push(`${lockedPath} (edited)`);
|
|
2556
4178
|
continue;
|
|
@@ -2658,7 +4280,7 @@ function nextLock(input) {
|
|
|
2658
4280
|
}
|
|
2659
4281
|
|
|
2660
4282
|
// src/lib/instructions/push.ts
|
|
2661
|
-
import { createHash as
|
|
4283
|
+
import { createHash as createHash6 } from "crypto";
|
|
2662
4284
|
var MAX_PUSH_CHANGES = 50;
|
|
2663
4285
|
function assertPushablePath(candidate, source) {
|
|
2664
4286
|
const check = checkRelativePath(candidate);
|
|
@@ -2697,16 +4319,16 @@ function assertLockMatchesManifest(lock, manifest) {
|
|
|
2697
4319
|
const published = new Map(
|
|
2698
4320
|
manifest.map((entry) => [entry.path, entry.sha256])
|
|
2699
4321
|
);
|
|
2700
|
-
for (const [
|
|
2701
|
-
const expected = published.get(
|
|
4322
|
+
for (const [path9, entry] of Object.entries(lock.files)) {
|
|
4323
|
+
const expected = published.get(path9);
|
|
2702
4324
|
if (expected === void 0) {
|
|
2703
4325
|
refuseStaleLedger(
|
|
2704
|
-
`it lists ${
|
|
4326
|
+
`it lists ${path9}, which version ${lock.snapshotVersion} does not contain`
|
|
2705
4327
|
);
|
|
2706
4328
|
}
|
|
2707
4329
|
if (expected !== entry.sha256) {
|
|
2708
4330
|
refuseStaleLedger(
|
|
2709
|
-
`its record of ${
|
|
4331
|
+
`its record of ${path9} is not the published file`
|
|
2710
4332
|
);
|
|
2711
4333
|
}
|
|
2712
4334
|
}
|
|
@@ -2772,7 +4394,7 @@ function computePushPlan(input) {
|
|
|
2772
4394
|
changes.push({ op: "delete", path: lockedPath });
|
|
2773
4395
|
continue;
|
|
2774
4396
|
}
|
|
2775
|
-
const actual =
|
|
4397
|
+
const actual = createHash6("sha256").update(read.bytes).digest("hex");
|
|
2776
4398
|
if (actual === ((_b = lock.files[lockedPath]) == null ? void 0 : _b.sha256)) {
|
|
2777
4399
|
unchanged.push(lockedPath);
|
|
2778
4400
|
continue;
|
|
@@ -2801,23 +4423,16 @@ function computePushPlan(input) {
|
|
|
2801
4423
|
}
|
|
2802
4424
|
|
|
2803
4425
|
// src/commands/instructions/index.ts
|
|
2804
|
-
var
|
|
4426
|
+
var HOOK_DEADLINE_MS2 = 1e4;
|
|
2805
4427
|
var CHECK_TIMEOUT_MS = 5e3;
|
|
2806
4428
|
var SYNC_TIMEOUT_MS = 15e3;
|
|
2807
4429
|
var BUNDLE_TIMEOUT_MS = 6e4;
|
|
2808
|
-
var
|
|
4430
|
+
var PUSH_TIMEOUT_MS2 = 6e4;
|
|
2809
4431
|
function collectAdded(value, previous) {
|
|
2810
4432
|
return [...previous, value];
|
|
2811
4433
|
}
|
|
2812
|
-
var CliFailure = class extends Error {
|
|
2813
|
-
constructor(message, exitCode) {
|
|
2814
|
-
super(message);
|
|
2815
|
-
this.exitCode = exitCode;
|
|
2816
|
-
this.name = "CliFailure";
|
|
2817
|
-
}
|
|
2818
|
-
};
|
|
2819
4434
|
function buildInstructionsCommand() {
|
|
2820
|
-
const instructions = new
|
|
4435
|
+
const instructions = new Command12("instructions").description(
|
|
2821
4436
|
"Keep a checkout current with a project's published coding instructions"
|
|
2822
4437
|
);
|
|
2823
4438
|
instructions.command("check").description(
|
|
@@ -2830,7 +4445,11 @@ function buildInstructionsCommand() {
|
|
|
2830
4445
|
"Session-hook mode: plain text, never fails, never blocks a session"
|
|
2831
4446
|
).option("--format <format>", "Output format: text|json").action(function(opts) {
|
|
2832
4447
|
return __async(this, null, function* () {
|
|
2833
|
-
yield
|
|
4448
|
+
yield run2(
|
|
4449
|
+
opts,
|
|
4450
|
+
"check",
|
|
4451
|
+
() => runCheck(opts, outputFormatFor(this))
|
|
4452
|
+
);
|
|
2834
4453
|
});
|
|
2835
4454
|
});
|
|
2836
4455
|
instructions.command("sync").description(
|
|
@@ -2840,7 +4459,7 @@ function buildInstructionsCommand() {
|
|
|
2840
4459
|
"Session-hook mode: plain text, never fails, never blocks a session"
|
|
2841
4460
|
).option("--format <format>", "Output format: text|json").action(function(opts) {
|
|
2842
4461
|
return __async(this, null, function* () {
|
|
2843
|
-
yield
|
|
4462
|
+
yield run2(opts, "sync", () => runSync(opts, outputFormatFor(this)));
|
|
2844
4463
|
});
|
|
2845
4464
|
});
|
|
2846
4465
|
instructions.command("push").description(
|
|
@@ -2855,10 +4474,10 @@ function buildInstructionsCommand() {
|
|
|
2855
4474
|
"Publish the change as a new version instead of proposing it (needs a key with instructions:publish)"
|
|
2856
4475
|
).option("--dry-run", "Print the change set and send nothing").option("--format <format>", "Output format: text|json").action(function(opts) {
|
|
2857
4476
|
return __async(this, null, function* () {
|
|
2858
|
-
yield
|
|
4477
|
+
yield run2(
|
|
2859
4478
|
__spreadProps(__spreadValues({}, opts), { hook: false }),
|
|
2860
4479
|
"push",
|
|
2861
|
-
() =>
|
|
4480
|
+
() => runPush2(opts, outputFormatFor(this))
|
|
2862
4481
|
);
|
|
2863
4482
|
});
|
|
2864
4483
|
});
|
|
@@ -2872,24 +4491,25 @@ function buildInstructionsCommand() {
|
|
|
2872
4491
|
"Let the hook apply changes instead of only reporting them"
|
|
2873
4492
|
).option("--format <format>", "Output format: text|json").action(function(opts) {
|
|
2874
4493
|
return __async(this, null, function* () {
|
|
2875
|
-
yield
|
|
4494
|
+
yield run2(
|
|
2876
4495
|
__spreadProps(__spreadValues({}, opts), { hook: false }),
|
|
2877
4496
|
"init",
|
|
2878
|
-
() => runInit(opts,
|
|
4497
|
+
() => runInit(opts, outputFormatFor(this))
|
|
2879
4498
|
);
|
|
2880
4499
|
});
|
|
2881
4500
|
});
|
|
2882
4501
|
return instructions;
|
|
2883
4502
|
}
|
|
2884
|
-
function
|
|
2885
|
-
const resolved = command.optsWithGlobals();
|
|
2886
|
-
return resolved.format === "json" ? "json" : "text";
|
|
2887
|
-
}
|
|
2888
|
-
function run(opts, verb, body) {
|
|
4503
|
+
function run2(opts, verb, body) {
|
|
2889
4504
|
return __async(this, null, function* () {
|
|
2890
4505
|
try {
|
|
2891
4506
|
if (opts.hook) {
|
|
2892
|
-
yield withDeadline(
|
|
4507
|
+
yield withDeadline(HOOK_DEADLINE_MS2, (signal) => {
|
|
4508
|
+
activeDeadline = signal;
|
|
4509
|
+
return body();
|
|
4510
|
+
}).finally(() => {
|
|
4511
|
+
activeDeadline = void 0;
|
|
4512
|
+
});
|
|
2893
4513
|
} else {
|
|
2894
4514
|
yield body();
|
|
2895
4515
|
}
|
|
@@ -2908,63 +4528,10 @@ function run(opts, verb, body) {
|
|
|
2908
4528
|
}
|
|
2909
4529
|
});
|
|
2910
4530
|
}
|
|
2911
|
-
function withDeadline(totalMs, body) {
|
|
2912
|
-
return __async(this, null, function* () {
|
|
2913
|
-
const controller = new AbortController();
|
|
2914
|
-
let timer;
|
|
2915
|
-
const deadline = new Promise((_resolve, reject) => {
|
|
2916
|
-
timer = setTimeout(() => {
|
|
2917
|
-
controller.abort();
|
|
2918
|
-
reject(
|
|
2919
|
-
new Error(
|
|
2920
|
-
`gave up after ${totalMs}ms so the session is not held up`
|
|
2921
|
-
)
|
|
2922
|
-
);
|
|
2923
|
-
}, totalMs);
|
|
2924
|
-
});
|
|
2925
|
-
activeDeadline = controller.signal;
|
|
2926
|
-
try {
|
|
2927
|
-
yield Promise.race([body(), deadline]);
|
|
2928
|
-
} finally {
|
|
2929
|
-
activeDeadline = void 0;
|
|
2930
|
-
if (timer) {
|
|
2931
|
-
clearTimeout(timer);
|
|
2932
|
-
}
|
|
2933
|
-
}
|
|
2934
|
-
});
|
|
2935
|
-
}
|
|
2936
4531
|
var activeDeadline;
|
|
2937
|
-
function describeError(error) {
|
|
2938
|
-
if (error instanceof Error) {
|
|
2939
|
-
return error.message.replace(/\s*\n\s*/g, " ");
|
|
2940
|
-
}
|
|
2941
|
-
return String(error);
|
|
2942
|
-
}
|
|
2943
|
-
function asCliFailure(error) {
|
|
2944
|
-
if (error instanceof CliFailure) {
|
|
2945
|
-
return error;
|
|
2946
|
-
}
|
|
2947
|
-
const status = error.status;
|
|
2948
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2949
|
-
switch (status) {
|
|
2950
|
-
case 401:
|
|
2951
|
-
return new CliFailure(message, 3);
|
|
2952
|
-
case 403:
|
|
2953
|
-
return new CliFailure(message, 5);
|
|
2954
|
-
case 404:
|
|
2955
|
-
return new CliFailure(message, 4);
|
|
2956
|
-
case 429:
|
|
2957
|
-
return new CliFailure(message, 6);
|
|
2958
|
-
case 400:
|
|
2959
|
-
case 422:
|
|
2960
|
-
return new CliFailure(message, 7);
|
|
2961
|
-
default:
|
|
2962
|
-
return new CliFailure(message, 1);
|
|
2963
|
-
}
|
|
2964
|
-
}
|
|
2965
4532
|
function destinationOf(opts) {
|
|
2966
4533
|
var _a2;
|
|
2967
|
-
return
|
|
4534
|
+
return path8.resolve((_a2 = opts.dest) != null ? _a2 : process.cwd());
|
|
2968
4535
|
}
|
|
2969
4536
|
function instructionsClient(opts, timeoutMs, { neverRetry = false } = {}) {
|
|
2970
4537
|
if (!getApiKey()) {
|
|
@@ -2979,7 +4546,7 @@ function instructionsClient(opts, timeoutMs, { neverRetry = false } = {}) {
|
|
|
2979
4546
|
// call it makes, so it cannot spend it on retries: a
|
|
2980
4547
|
// session that will not start is worse than instructions
|
|
2981
4548
|
// one version stale.
|
|
2982
|
-
timeoutMs: Math.min(timeoutMs,
|
|
4549
|
+
timeoutMs: Math.min(timeoutMs, HOOK_DEADLINE_MS2),
|
|
2983
4550
|
retry: { maxRetries: 0 }
|
|
2984
4551
|
} : neverRetry ? { timeoutMs, retry: { maxRetries: 0 } } : { timeoutMs }
|
|
2985
4552
|
).withoutContext();
|
|
@@ -2987,7 +4554,7 @@ function instructionsClient(opts, timeoutMs, { neverRetry = false } = {}) {
|
|
|
2987
4554
|
function downloadUrlClient(opts) {
|
|
2988
4555
|
return instructionsClient(
|
|
2989
4556
|
opts,
|
|
2990
|
-
opts.hook ?
|
|
4557
|
+
opts.hook ? HOOK_DEADLINE_MS2 : BUNDLE_TIMEOUT_MS,
|
|
2991
4558
|
{ neverRetry: true }
|
|
2992
4559
|
);
|
|
2993
4560
|
}
|
|
@@ -3018,7 +4585,7 @@ function assertHookMayAct(opts, published) {
|
|
|
3018
4585
|
);
|
|
3019
4586
|
}
|
|
3020
4587
|
}
|
|
3021
|
-
function
|
|
4588
|
+
function readLockForProject2(destination, projectId) {
|
|
3022
4589
|
return __async(this, null, function* () {
|
|
3023
4590
|
const lock = yield readLock(destination);
|
|
3024
4591
|
if (lock !== null && lock.projectId !== projectId) {
|
|
@@ -3048,7 +4615,7 @@ function runCheck(opts, format) {
|
|
|
3048
4615
|
var _a2;
|
|
3049
4616
|
const destination = destinationOf(opts);
|
|
3050
4617
|
const root = yield resolveExistingRoot(destination);
|
|
3051
|
-
const lock = yield
|
|
4618
|
+
const lock = yield readLockForProject2(destination, opts.project);
|
|
3052
4619
|
const client = instructionsClient(opts, CHECK_TIMEOUT_MS);
|
|
3053
4620
|
const published = yield fetchPublished(client, opts, lock == null ? void 0 : lock.digest);
|
|
3054
4621
|
const drifted = opts.verify && lock !== null ? yield verifyLedger({ root, lock }) : [];
|
|
@@ -3157,7 +4724,7 @@ function syncOnce(opts) {
|
|
|
3157
4724
|
return __async(this, null, function* () {
|
|
3158
4725
|
const root = yield resolveDestinationRoot(destinationOf(opts));
|
|
3159
4726
|
const outcome = emptyOutcome(opts, root);
|
|
3160
|
-
const lock = yield
|
|
4727
|
+
const lock = yield readLockForProject2(root, opts.project);
|
|
3161
4728
|
const client = instructionsClient(opts, SYNC_TIMEOUT_MS);
|
|
3162
4729
|
let published = yield fetchPublished(client, opts, lock == null ? void 0 : lock.digest);
|
|
3163
4730
|
assertInitSyncMayAct(opts, published);
|
|
@@ -3211,7 +4778,7 @@ function syncOnce(opts) {
|
|
|
3211
4778
|
manifest.map((entry) => [entry.path, entry.size])
|
|
3212
4779
|
);
|
|
3213
4780
|
const archive = yield fetchBundle(download.url, {
|
|
3214
|
-
timeoutMs: opts.hook ?
|
|
4781
|
+
timeoutMs: opts.hook ? HOOK_DEADLINE_MS2 : BUNDLE_TIMEOUT_MS,
|
|
3215
4782
|
// Bounded by what this manifest says it holds, not by what the
|
|
3216
4783
|
// response claims. The manifest has already been checked against
|
|
3217
4784
|
// the published snapshot limits, so this is a number the client
|
|
@@ -3299,12 +4866,12 @@ function reportSync(outcome, opts) {
|
|
|
3299
4866
|
line(`Everything in ${outcome.destination} already matched.`);
|
|
3300
4867
|
}
|
|
3301
4868
|
}
|
|
3302
|
-
function
|
|
4869
|
+
function runPush2(opts, format) {
|
|
3303
4870
|
return __async(this, null, function* () {
|
|
3304
4871
|
var _a2;
|
|
3305
4872
|
const destination = destinationOf(opts);
|
|
3306
4873
|
const root = yield resolveExistingRoot(destination);
|
|
3307
|
-
const lock = yield
|
|
4874
|
+
const lock = yield readLockForProject2(root, opts.project);
|
|
3308
4875
|
if (lock === null) {
|
|
3309
4876
|
throw new CliFailure(
|
|
3310
4877
|
`No coding-instructions lock in ${destination}. Run \`fabric instructions sync --project ${opts.project}\` first: a push is a diff against the version that was last applied here, and without that ledger there is nothing to diff against.`,
|
|
@@ -3313,7 +4880,7 @@ function runPush(opts, format) {
|
|
|
3313
4880
|
}
|
|
3314
4881
|
const client = instructionsClient(
|
|
3315
4882
|
__spreadProps(__spreadValues({}, opts), { hook: false }),
|
|
3316
|
-
|
|
4883
|
+
PUSH_TIMEOUT_MS2
|
|
3317
4884
|
);
|
|
3318
4885
|
const published = yield fetchPublished(client, opts);
|
|
3319
4886
|
if (!published.published || !published.snapshot) {
|
|
@@ -3474,7 +5041,7 @@ function runInit(opts, format) {
|
|
|
3474
5041
|
}
|
|
3475
5042
|
const tool = opts.tool;
|
|
3476
5043
|
const root = yield resolveDestinationRoot(destinationOf(opts));
|
|
3477
|
-
yield
|
|
5044
|
+
yield readLockForProject2(root, opts.project);
|
|
3478
5045
|
try {
|
|
3479
5046
|
yield assertKeyStaysOutside(root, getConfigPath());
|
|
3480
5047
|
} catch (error) {
|
|
@@ -3555,9 +5122,9 @@ function hookPathFor(tool) {
|
|
|
3555
5122
|
}
|
|
3556
5123
|
|
|
3557
5124
|
// src/commands/mcp/index.ts
|
|
3558
|
-
import { Command as
|
|
5125
|
+
import { Command as Command13 } from "commander";
|
|
3559
5126
|
function buildMcpCommand() {
|
|
3560
|
-
const mcp = new
|
|
5127
|
+
const mcp = new Command13("mcp").description(
|
|
3561
5128
|
"Manage MCP servers and configurations"
|
|
3562
5129
|
);
|
|
3563
5130
|
mcp.command("servers").description("List accessible MCP servers (system + custom)").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option(
|
|
@@ -3595,7 +5162,7 @@ function buildMcpCommand() {
|
|
|
3595
5162
|
});
|
|
3596
5163
|
})
|
|
3597
5164
|
);
|
|
3598
|
-
const configs = new
|
|
5165
|
+
const configs = new Command13("configs").description(
|
|
3599
5166
|
"Manage MCP configurations"
|
|
3600
5167
|
);
|
|
3601
5168
|
configs.command("list", { isDefault: true }).description("List your MCP configurations").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option(
|
|
@@ -3700,9 +5267,9 @@ function buildMcpCommand() {
|
|
|
3700
5267
|
}
|
|
3701
5268
|
|
|
3702
5269
|
// src/commands/orgs/index.ts
|
|
3703
|
-
import { Command as
|
|
5270
|
+
import { Command as Command14 } from "commander";
|
|
3704
5271
|
function buildOrgsCommand() {
|
|
3705
|
-
const orgs = new
|
|
5272
|
+
const orgs = new Command14("orgs").description("Manage organizations");
|
|
3706
5273
|
orgs.command("list").description("List organizations you belong to").option(
|
|
3707
5274
|
"--format <format>",
|
|
3708
5275
|
"Output format: table|json|yaml|csv",
|
|
@@ -3759,9 +5326,9 @@ function buildOrgsCommand() {
|
|
|
3759
5326
|
}
|
|
3760
5327
|
|
|
3761
5328
|
// src/commands/projects/index.ts
|
|
3762
|
-
import { Command as
|
|
5329
|
+
import { Command as Command15 } from "commander";
|
|
3763
5330
|
function buildProjectsCommand() {
|
|
3764
|
-
const projects = new
|
|
5331
|
+
const projects = new Command15("projects").description("Manage projects");
|
|
3765
5332
|
projects.command("list").description("List projects").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option(
|
|
3766
5333
|
"--status <status>",
|
|
3767
5334
|
"Filter by status: ACTIVE|DRAFT|COMPLETED|ARCHIVED"
|
|
@@ -3896,9 +5463,9 @@ function buildProjectsCommand() {
|
|
|
3896
5463
|
}
|
|
3897
5464
|
|
|
3898
5465
|
// src/commands/prompts/index.ts
|
|
3899
|
-
import { Command as
|
|
5466
|
+
import { Command as Command16 } from "commander";
|
|
3900
5467
|
function buildPromptsCommand() {
|
|
3901
|
-
const prompts = new
|
|
5468
|
+
const prompts = new Command16("prompts").description(
|
|
3902
5469
|
"Manage prompt templates"
|
|
3903
5470
|
);
|
|
3904
5471
|
prompts.command("list").description("List prompt templates").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--search <query>", "Search by name").option("--category <cat>", "Filter by category").option("--limit <n>", "Max results", "50").option(
|
|
@@ -4051,12 +5618,12 @@ ${content}
|
|
|
4051
5618
|
}
|
|
4052
5619
|
|
|
4053
5620
|
// src/commands/reports/index.ts
|
|
4054
|
-
import { Command as
|
|
5621
|
+
import { Command as Command17 } from "commander";
|
|
4055
5622
|
function buildReportsCommand() {
|
|
4056
|
-
const reports = new
|
|
5623
|
+
const reports = new Command17("reports").description(
|
|
4057
5624
|
"Manage report templates and executions"
|
|
4058
5625
|
);
|
|
4059
|
-
const templates = new
|
|
5626
|
+
const templates = new Command17("templates").description(
|
|
4060
5627
|
"Manage report templates"
|
|
4061
5628
|
);
|
|
4062
5629
|
templates.command("list", { isDefault: true }).description("List report templates").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--search <query>", "Search by name").option("--category <cat>", "Filter by category").option("--limit <n>", "Max results (default 20)", "20").option(
|
|
@@ -4130,7 +5697,7 @@ function buildReportsCommand() {
|
|
|
4130
5697
|
})
|
|
4131
5698
|
);
|
|
4132
5699
|
reports.addCommand(templates);
|
|
4133
|
-
const executions = new
|
|
5700
|
+
const executions = new Command17("executions").description(
|
|
4134
5701
|
"View report executions"
|
|
4135
5702
|
);
|
|
4136
5703
|
executions.command("list", { isDefault: true }).description("List report executions").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--limit <n>", "Max results (default 20)", "20").option(
|
|
@@ -4174,9 +5741,9 @@ function buildReportsCommand() {
|
|
|
4174
5741
|
}
|
|
4175
5742
|
|
|
4176
5743
|
// src/commands/skills/index.ts
|
|
4177
|
-
import { Command as
|
|
5744
|
+
import { Command as Command18 } from "commander";
|
|
4178
5745
|
function buildSkillsCommand() {
|
|
4179
|
-
const skills = new
|
|
5746
|
+
const skills = new Command18("skills").description(
|
|
4180
5747
|
"Browse skill definitions"
|
|
4181
5748
|
);
|
|
4182
5749
|
skills.command("list").description("List skills").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--search <query>", "Search by name").option("--category <cat>", "Filter by category").option("--limit <n>", "Max results", "50").option(
|
|
@@ -4243,9 +5810,9 @@ function buildSkillsCommand() {
|
|
|
4243
5810
|
}
|
|
4244
5811
|
|
|
4245
5812
|
// src/commands/workflows/index.ts
|
|
4246
|
-
import { Command as
|
|
5813
|
+
import { Command as Command19 } from "commander";
|
|
4247
5814
|
function buildWorkflowsCommand() {
|
|
4248
|
-
const workflows = new
|
|
5815
|
+
const workflows = new Command19("workflows").description("Manage workflows");
|
|
4249
5816
|
workflows.command("list").description("List workflows").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--status <status>", "Filter: DRAFT|ACTIVE|PAUSED|ARCHIVED").option("--search <query>", "Search by name").option("--limit <n>", "Max results", "20").option(
|
|
4250
5817
|
"--format <format>",
|
|
4251
5818
|
"Output format: table|json|yaml|csv",
|
|
@@ -4373,9 +5940,9 @@ function buildWorkflowsCommand() {
|
|
|
4373
5940
|
}
|
|
4374
5941
|
|
|
4375
5942
|
// src/commands/workspaces/index.ts
|
|
4376
|
-
import { Command as
|
|
5943
|
+
import { Command as Command20 } from "commander";
|
|
4377
5944
|
function buildWorkspacesCommand() {
|
|
4378
|
-
const workspaces = new
|
|
5945
|
+
const workspaces = new Command20("workspaces").description(
|
|
4379
5946
|
"Manage workspaces"
|
|
4380
5947
|
);
|
|
4381
5948
|
workspaces.command("list").description("List workspaces").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--search <query>", "Search by name").option("--status <status>", "Filter: ACTIVE|ARCHIVED").option("--limit <n>", "Max results (default 20)", "20").option(
|
|
@@ -4451,14 +6018,14 @@ function buildWorkspacesCommand() {
|
|
|
4451
6018
|
}
|
|
4452
6019
|
|
|
4453
6020
|
// src/bin/fabric.ts
|
|
4454
|
-
var program = new
|
|
6021
|
+
var program = new Command21();
|
|
4455
6022
|
var _a;
|
|
4456
6023
|
program.name("fabric").description("Fabric AI platform CLI").version(package_default.version).option(
|
|
4457
6024
|
"--format <format>",
|
|
4458
6025
|
"Output format: table|json|yaml|csv",
|
|
4459
6026
|
(_a = process.env.FABRIC_FORMAT) != null ? _a : "table"
|
|
4460
6027
|
);
|
|
4461
|
-
var auth = new
|
|
6028
|
+
var auth = new Command21("auth").description(
|
|
4462
6029
|
"Authenticate and manage API keys"
|
|
4463
6030
|
);
|
|
4464
6031
|
auth.addCommand(buildLoginCommand());
|
|
@@ -4475,13 +6042,14 @@ program.addCommand(buildPromptsCommand());
|
|
|
4475
6042
|
program.addCommand(buildAgentsCommand());
|
|
4476
6043
|
program.addCommand(buildSkillsCommand());
|
|
4477
6044
|
program.addCommand(buildInstructionsCommand());
|
|
6045
|
+
program.addCommand(buildContextCommand());
|
|
4478
6046
|
program.addCommand(buildMcpCommand());
|
|
4479
6047
|
program.addCommand(buildFramesCommand());
|
|
4480
6048
|
program.addCommand(buildWorkspacesCommand());
|
|
4481
6049
|
program.addCommand(buildChatsCommand());
|
|
4482
6050
|
program.addCommand(buildReportsCommand());
|
|
4483
6051
|
program.addCommand(buildCompletionCommand());
|
|
4484
|
-
var config = new
|
|
6052
|
+
var config = new Command21("config").description("Show CLI configuration");
|
|
4485
6053
|
config.command("path").description("Print the path to the config file").action(() => {
|
|
4486
6054
|
process.stdout.write(`${getConfigPath()}
|
|
4487
6055
|
`);
|