@fabricorg/cli 0.3.4 → 0.3.6
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 +1927 -862
- 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.6",
|
|
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,1903 @@ 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
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
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.`
|
|
1187
1377
|
);
|
|
1188
|
-
} catch (err) {
|
|
1189
|
-
printError(err.message, 1);
|
|
1190
1378
|
}
|
|
1191
|
-
const
|
|
1192
|
-
if (
|
|
1193
|
-
|
|
1194
|
-
|
|
1379
|
+
const afterOpen = yield assertWritableTarget(root, relativePath);
|
|
1380
|
+
if (!afterOpen.ok) {
|
|
1381
|
+
throw new Error(
|
|
1382
|
+
`Refusing to sync: ${describeRejection(afterOpen)}.`
|
|
1383
|
+
);
|
|
1195
1384
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
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
|
+
);
|
|
1222
1451
|
}
|
|
1452
|
+
}));
|
|
1453
|
+
continue;
|
|
1454
|
+
}
|
|
1455
|
+
if (stats.isSymbolicLink()) {
|
|
1456
|
+
throw new Error(
|
|
1457
|
+
`Refusing to sync: ${describeRejection({ ok: false, reason: "symlink", detail: current })}.`
|
|
1223
1458
|
);
|
|
1224
|
-
} catch (err) {
|
|
1225
|
-
printError(err.message, 1);
|
|
1226
1459
|
}
|
|
1227
|
-
if (
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
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;
|
|
1233
1606
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
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()
|
|
1244
1759
|
);
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
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;
|
|
1262
1852
|
}
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
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
|
+
}
|
|
1271
1880
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
);
|
|
1281
|
-
return features;
|
|
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
|
+
});
|
|
1282
1889
|
}
|
|
1283
1890
|
|
|
1284
|
-
// src/
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
)
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
)
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
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"
|
|
1303
1944
|
});
|
|
1304
|
-
|
|
1305
|
-
printError(err.message, 1);
|
|
1945
|
+
continue;
|
|
1306
1946
|
}
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
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;
|
|
1313
1955
|
}
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
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
|
|
1324
1967
|
});
|
|
1325
|
-
}
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
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
|
|
1336
1987
|
});
|
|
1337
|
-
|
|
1338
|
-
printError(err.message, 1);
|
|
1339
|
-
}
|
|
1340
|
-
const fmt = opts.format;
|
|
1341
|
-
if (fmt === "json" || fmt === "yaml") {
|
|
1342
|
-
printOutput(frame, { format: fmt });
|
|
1343
|
-
return;
|
|
1988
|
+
continue;
|
|
1344
1989
|
}
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
"Share Token": (_b = frame.shareToken) != null ? _b : "(none)",
|
|
1353
|
-
Description: (_c = frame.description) != null ? _c : "(none)",
|
|
1354
|
-
Updated: frame.updatedAt.slice(0, 10)
|
|
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";
|
|
1995
|
+
}
|
|
1996
|
+
throw error;
|
|
1355
1997
|
});
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
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
|
|
2025
|
+
);
|
|
2026
|
+
}
|
|
2027
|
+
present.add(entry.sourcePath);
|
|
1383
2028
|
}
|
|
1384
|
-
|
|
1385
|
-
|
|
2029
|
+
for (const skip of skipped) {
|
|
2030
|
+
const check = normalizeContextSourcePath(skip.path);
|
|
2031
|
+
if (check.ok) {
|
|
2032
|
+
present.add(check.sourcePath);
|
|
2033
|
+
}
|
|
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
|
+
unchangedLocal.sort(byPath2);
|
|
2052
|
+
removed.sort(byPath2);
|
|
2053
|
+
forgotten.sort(byPath2);
|
|
2054
|
+
skipped.sort((a, b) => byPath2(a.path, b.path));
|
|
2055
|
+
return { push, unchangedLocal, removed, forgotten, skipped };
|
|
2056
|
+
});
|
|
1386
2057
|
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
return { ok: false, reason: "absolute", detail: input };
|
|
2058
|
+
|
|
2059
|
+
// src/lib/context-sync/push.ts
|
|
2060
|
+
var RUN_LEVEL_STATUSES = /* @__PURE__ */ new Set([401, 403, 404, 429]);
|
|
2061
|
+
var RUN_LEVEL_CODES = /* @__PURE__ */ new Set(["NETWORK_ERROR", "TIMEOUT"]);
|
|
2062
|
+
function stopsTheRun(error) {
|
|
2063
|
+
const { status, code } = error;
|
|
2064
|
+
return typeof status === "number" && RUN_LEVEL_STATUSES.has(status) || typeof code === "string" && RUN_LEVEL_CODES.has(code);
|
|
2065
|
+
}
|
|
2066
|
+
function conflictOf(error) {
|
|
2067
|
+
if (typeof error !== "object" || error === null) {
|
|
2068
|
+
return null;
|
|
1399
2069
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
2070
|
+
const conflict = error.conflict;
|
|
2071
|
+
if (typeof conflict === "object" && conflict !== null && conflict.status === "conflict" && "current" in conflict && // An object, or `null` for a named version deleted on the server.
|
|
2072
|
+
typeof conflict.current === "object") {
|
|
2073
|
+
return conflict;
|
|
1402
2074
|
}
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
2075
|
+
return null;
|
|
2076
|
+
}
|
|
2077
|
+
function readPlannedContent(root, entry) {
|
|
2078
|
+
return __async(this, null, function* () {
|
|
2079
|
+
const read = yield readFileSafely(root, entry.diskPath, {
|
|
2080
|
+
maxBytes: MAX_CONTEXT_FILE_BYTES
|
|
2081
|
+
}).catch((error) => {
|
|
2082
|
+
if (error instanceof Error && /too large/.test(error.message)) {
|
|
2083
|
+
return null;
|
|
2084
|
+
}
|
|
2085
|
+
throw error;
|
|
2086
|
+
});
|
|
2087
|
+
if (read === null) {
|
|
2088
|
+
return null;
|
|
2089
|
+
}
|
|
2090
|
+
const verdict = classifyContextBytes(read.bytes);
|
|
2091
|
+
if (!verdict.ok || hashContextContent(verdict.content) !== entry.sha256) {
|
|
2092
|
+
return null;
|
|
2093
|
+
}
|
|
2094
|
+
return verdict.content;
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
function fromResult(sourcePath, result) {
|
|
2098
|
+
if (result.status === "duplicate") {
|
|
2099
|
+
return {
|
|
2100
|
+
sourcePath,
|
|
2101
|
+
status: "duplicate",
|
|
2102
|
+
contentHash: result.contentHash,
|
|
2103
|
+
duplicateOfContextId: result.duplicateOfContextId,
|
|
2104
|
+
duplicateOfSourcePath: result.duplicateOfSourcePath
|
|
2105
|
+
};
|
|
1406
2106
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
2107
|
+
return {
|
|
2108
|
+
sourcePath,
|
|
2109
|
+
status: result.status,
|
|
2110
|
+
contextId: result.contextId,
|
|
2111
|
+
contentHash: result.contentHash
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
2114
|
+
function pushContextPlan(input) {
|
|
2115
|
+
return __async(this, null, function* () {
|
|
2116
|
+
var _a2;
|
|
2117
|
+
const results = [];
|
|
2118
|
+
const send = (entry, content, expectedContentHash) => input.client.contexts.upsertSyncedFile(
|
|
2119
|
+
input.projectId,
|
|
2120
|
+
__spreadValues({
|
|
2121
|
+
sourcePath: entry.sourcePath,
|
|
2122
|
+
content
|
|
2123
|
+
}, expectedContentHash !== void 0 ? { expectedContentHash } : {}),
|
|
2124
|
+
{ org: input.org }
|
|
2125
|
+
);
|
|
2126
|
+
for (const entry of input.plan.push) {
|
|
2127
|
+
try {
|
|
2128
|
+
const content = yield readPlannedContent(input.root, entry);
|
|
2129
|
+
if (content === null) {
|
|
2130
|
+
results.push({
|
|
2131
|
+
sourcePath: entry.sourcePath,
|
|
2132
|
+
status: "changed-during-run"
|
|
2133
|
+
});
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
try {
|
|
2137
|
+
results.push(
|
|
2138
|
+
fromResult(
|
|
2139
|
+
entry.sourcePath,
|
|
2140
|
+
yield send(entry, content, entry.expectedContentHash)
|
|
2141
|
+
)
|
|
2142
|
+
);
|
|
2143
|
+
} catch (error) {
|
|
2144
|
+
const conflict = conflictOf(error);
|
|
2145
|
+
if (conflict === null) {
|
|
2146
|
+
throw error;
|
|
2147
|
+
}
|
|
2148
|
+
const { current } = conflict;
|
|
2149
|
+
if (!input.force || current !== null && current.contentHash === null) {
|
|
2150
|
+
results.push({
|
|
2151
|
+
sourcePath: entry.sourcePath,
|
|
2152
|
+
status: "conflict",
|
|
2153
|
+
contentHash: conflict.contentHash,
|
|
2154
|
+
wasLocked: entry.expectedContentHash !== void 0,
|
|
2155
|
+
afterForce: false,
|
|
2156
|
+
current
|
|
2157
|
+
});
|
|
2158
|
+
continue;
|
|
2159
|
+
}
|
|
2160
|
+
const forcedContent = yield readPlannedContent(
|
|
2161
|
+
input.root,
|
|
2162
|
+
entry
|
|
2163
|
+
);
|
|
2164
|
+
if (forcedContent === null) {
|
|
2165
|
+
results.push({
|
|
2166
|
+
sourcePath: entry.sourcePath,
|
|
2167
|
+
status: "changed-during-run"
|
|
2168
|
+
});
|
|
2169
|
+
continue;
|
|
2170
|
+
}
|
|
2171
|
+
try {
|
|
2172
|
+
const forced = fromResult(
|
|
2173
|
+
entry.sourcePath,
|
|
2174
|
+
yield send(
|
|
2175
|
+
entry,
|
|
2176
|
+
forcedContent,
|
|
2177
|
+
current === null ? void 0 : (_a2 = current.contentHash) != null ? _a2 : void 0
|
|
2178
|
+
)
|
|
2179
|
+
);
|
|
2180
|
+
results.push(
|
|
2181
|
+
forced.status === "updated" && current !== null ? __spreadProps(__spreadValues({}, forced), { overwrote: current }) : forced
|
|
2182
|
+
);
|
|
2183
|
+
} catch (retryError) {
|
|
2184
|
+
const again = conflictOf(retryError);
|
|
2185
|
+
if (again === null) {
|
|
2186
|
+
throw retryError;
|
|
2187
|
+
}
|
|
2188
|
+
results.push({
|
|
2189
|
+
sourcePath: entry.sourcePath,
|
|
2190
|
+
status: "conflict",
|
|
2191
|
+
contentHash: again.contentHash,
|
|
2192
|
+
wasLocked: entry.expectedContentHash !== void 0,
|
|
2193
|
+
afterForce: true,
|
|
2194
|
+
current: again.current
|
|
2195
|
+
});
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
} catch (error) {
|
|
2199
|
+
const failure = asCliFailure(error);
|
|
2200
|
+
if (stopsTheRun(error)) {
|
|
2201
|
+
return { results, stoppedBy: failure };
|
|
2202
|
+
}
|
|
2203
|
+
results.push({
|
|
2204
|
+
sourcePath: entry.sourcePath,
|
|
2205
|
+
status: "failed",
|
|
2206
|
+
error: describeError(error),
|
|
2207
|
+
exitCode: failure.exitCode
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
return { results, stoppedBy: null };
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
function nextContextLock(input) {
|
|
2215
|
+
var _a2, _b, _c;
|
|
2216
|
+
const files = __spreadValues({}, (_b = (_a2 = input.previous) == null ? void 0 : _a2.files) != null ? _b : {});
|
|
2217
|
+
let changed = false;
|
|
2218
|
+
for (const sourcePath of (_c = input.forgotten) != null ? _c : []) {
|
|
2219
|
+
if (sourcePath in files) {
|
|
2220
|
+
delete files[sourcePath];
|
|
2221
|
+
changed = true;
|
|
2222
|
+
}
|
|
1409
2223
|
}
|
|
1410
|
-
for (const
|
|
1411
|
-
if (
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
2224
|
+
for (const result of input.results) {
|
|
2225
|
+
if (result.status === "duplicate") {
|
|
2226
|
+
const before2 = files[result.sourcePath];
|
|
2227
|
+
if ((before2 == null ? void 0 : before2.state) === "duplicate" && before2.sha256 === result.contentHash) {
|
|
2228
|
+
continue;
|
|
2229
|
+
}
|
|
2230
|
+
files[result.sourcePath] = {
|
|
2231
|
+
sha256: result.contentHash,
|
|
2232
|
+
state: "duplicate"
|
|
1416
2233
|
};
|
|
2234
|
+
changed = true;
|
|
2235
|
+
continue;
|
|
1417
2236
|
}
|
|
1418
|
-
if (
|
|
1419
|
-
|
|
1420
|
-
ok: false,
|
|
1421
|
-
reason: "reserved_device_name",
|
|
1422
|
-
detail: input
|
|
1423
|
-
};
|
|
2237
|
+
if (result.status !== "created" && result.status !== "updated" && result.status !== "unchanged") {
|
|
2238
|
+
continue;
|
|
1424
2239
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
reason: "forbidden_character",
|
|
1429
|
-
detail: input
|
|
1430
|
-
};
|
|
2240
|
+
const before = files[result.sourcePath];
|
|
2241
|
+
if ((before == null ? void 0 : before.state) === void 0 && (before == null ? void 0 : before.sha256) === result.contentHash && before.contextId === result.contextId) {
|
|
2242
|
+
continue;
|
|
1431
2243
|
}
|
|
2244
|
+
files[result.sourcePath] = {
|
|
2245
|
+
sha256: result.contentHash,
|
|
2246
|
+
contextId: result.contextId
|
|
2247
|
+
};
|
|
2248
|
+
changed = true;
|
|
1432
2249
|
}
|
|
1433
|
-
|
|
2250
|
+
if (!changed) {
|
|
2251
|
+
return null;
|
|
2252
|
+
}
|
|
2253
|
+
return {
|
|
2254
|
+
version: CONTEXT_LOCK_VERSION,
|
|
2255
|
+
projectId: input.projectId,
|
|
2256
|
+
pushedAt: input.now.toISOString(),
|
|
2257
|
+
files
|
|
2258
|
+
};
|
|
1434
2259
|
}
|
|
1435
|
-
|
|
2260
|
+
|
|
2261
|
+
// src/lib/context-sync/report.ts
|
|
2262
|
+
function countOutcomes(plan, results) {
|
|
2263
|
+
const count = (status) => results.filter((r) => r.status === status).length;
|
|
2264
|
+
return {
|
|
2265
|
+
created: count("created"),
|
|
2266
|
+
updated: count("updated"),
|
|
2267
|
+
unchanged: count("unchanged") + plan.unchangedLocal.length,
|
|
2268
|
+
duplicate: count("duplicate"),
|
|
2269
|
+
conflict: count("conflict"),
|
|
2270
|
+
changedDuringRun: count("changed-during-run"),
|
|
2271
|
+
failed: count("failed"),
|
|
2272
|
+
removed: plan.removed.length,
|
|
2273
|
+
skipped: plan.skipped.length
|
|
2274
|
+
};
|
|
2275
|
+
}
|
|
2276
|
+
function who(current) {
|
|
2277
|
+
var _a2, _b;
|
|
2278
|
+
return (_b = (_a2 = current.contentUpdatedBy) == null ? void 0 : _a2.name) != null ? _b : "someone";
|
|
2279
|
+
}
|
|
2280
|
+
function when(current) {
|
|
1436
2281
|
var _a2;
|
|
1437
|
-
|
|
1438
|
-
if (RESERVED_ROOTS.some((root) => first === root)) {
|
|
1439
|
-
return true;
|
|
1440
|
-
}
|
|
1441
|
-
const key = collisionKey(input);
|
|
1442
|
-
return RESERVED_EXACT_PATHS.some(
|
|
1443
|
-
(reserved) => collisionKey(reserved) === key
|
|
1444
|
-
);
|
|
2282
|
+
return (_a2 = current.contentUpdatedAt) != null ? _a2 : "an unknown time";
|
|
1445
2283
|
}
|
|
1446
|
-
function
|
|
1447
|
-
|
|
2284
|
+
function conflictLine(result) {
|
|
2285
|
+
const { current } = result;
|
|
2286
|
+
if (current === null) {
|
|
2287
|
+
return `${result.sourcePath}: deleted on the server since your last push`;
|
|
2288
|
+
}
|
|
2289
|
+
let text;
|
|
2290
|
+
if (result.afterForce) {
|
|
2291
|
+
text = `changed on the server again by ${who(current)} at ${when(current)} while --force was replacing it`;
|
|
2292
|
+
} else if (result.wasLocked) {
|
|
2293
|
+
text = `changed on the server by ${who(current)} at ${when(current)} since your last push`;
|
|
2294
|
+
} else {
|
|
2295
|
+
text = `already on the server with different content, last changed by ${who(current)} at ${when(current)}`;
|
|
2296
|
+
}
|
|
2297
|
+
if (current.contentHash === null) {
|
|
2298
|
+
text += "; that version has no content hash, so --force cannot replace it";
|
|
2299
|
+
}
|
|
2300
|
+
return `${result.sourcePath}: ${text}`;
|
|
1448
2301
|
}
|
|
1449
|
-
function
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
const key = collisionKey(candidate);
|
|
1453
|
-
const previous = seen.get(key);
|
|
1454
|
-
if (previous !== void 0) {
|
|
1455
|
-
return { first: previous, second: candidate };
|
|
1456
|
-
}
|
|
1457
|
-
seen.set(key, candidate);
|
|
2302
|
+
function storedLine(result) {
|
|
2303
|
+
if (result.overwrote) {
|
|
2304
|
+
return `${result.sourcePath}: replaced the version ${who(result.overwrote)} changed at ${when(result.overwrote)} (--force)`;
|
|
1458
2305
|
}
|
|
1459
|
-
return
|
|
2306
|
+
return result.sourcePath;
|
|
1460
2307
|
}
|
|
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}`;
|
|
2308
|
+
function skippedLine(skip) {
|
|
2309
|
+
if (skip.reason === "ignored") {
|
|
2310
|
+
return `${skip.path}: now excluded by the ignore rules; server entry kept`;
|
|
1493
2311
|
}
|
|
2312
|
+
return skip.detail ? `${skip.path}: ${skip.reason} (${skip.detail})` : `${skip.path}: ${skip.reason}`;
|
|
1494
2313
|
}
|
|
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;
|
|
2314
|
+
function group(lines, label, items) {
|
|
2315
|
+
if (items.length === 0) {
|
|
2316
|
+
return;
|
|
1513
2317
|
}
|
|
1514
|
-
|
|
1515
|
-
|
|
2318
|
+
lines.push(`${label} (${items.length})`);
|
|
2319
|
+
for (const item of items) {
|
|
2320
|
+
lines.push(` ${item}`);
|
|
1516
2321
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
2322
|
+
}
|
|
2323
|
+
function formatPushReport(input) {
|
|
2324
|
+
const { plan, results } = input;
|
|
2325
|
+
const of = (status) => results.filter((r) => r.status === status);
|
|
2326
|
+
const lines = [
|
|
2327
|
+
`Context push from ${input.directory} to project ${input.projectId}:`
|
|
2328
|
+
];
|
|
2329
|
+
group(lines, "created", of("created").map(storedLine));
|
|
2330
|
+
group(lines, "updated", of("updated").map(storedLine));
|
|
2331
|
+
group(
|
|
2332
|
+
lines,
|
|
2333
|
+
"duplicate",
|
|
2334
|
+
of("duplicate").map(
|
|
2335
|
+
(r) => {
|
|
2336
|
+
var _a2;
|
|
2337
|
+
return `${r.sourcePath}: duplicate of ${(_a2 = r.duplicateOfSourcePath) != null ? _a2 : "another source in this project"}`;
|
|
2338
|
+
}
|
|
2339
|
+
)
|
|
2340
|
+
);
|
|
2341
|
+
group(lines, "conflict", of("conflict").map(conflictLine));
|
|
2342
|
+
group(
|
|
2343
|
+
lines,
|
|
2344
|
+
"changed during the run",
|
|
2345
|
+
of("changed-during-run").map(
|
|
2346
|
+
(r) => `${r.sourcePath}: changed-during-run; not sent, the next push sends the new content`
|
|
2347
|
+
)
|
|
2348
|
+
);
|
|
2349
|
+
group(
|
|
2350
|
+
lines,
|
|
2351
|
+
"failed",
|
|
2352
|
+
of("failed").map((r) => `${r.sourcePath}: ${r.error}`)
|
|
2353
|
+
);
|
|
2354
|
+
group(
|
|
2355
|
+
lines,
|
|
2356
|
+
"removed",
|
|
2357
|
+
plan.removed.map((p) => `${p}: removed locally; server entry kept`)
|
|
2358
|
+
);
|
|
2359
|
+
group(lines, "skipped", plan.skipped.map(skippedLine));
|
|
2360
|
+
const unchanged = countOutcomes(plan, results).unchanged;
|
|
2361
|
+
if (unchanged > 0) {
|
|
2362
|
+
lines.push(`unchanged (${unchanged})`);
|
|
1520
2363
|
}
|
|
1521
|
-
return
|
|
2364
|
+
return lines;
|
|
2365
|
+
}
|
|
2366
|
+
function formatDryRunReport(input) {
|
|
2367
|
+
const { plan } = input;
|
|
2368
|
+
const lines = [
|
|
2369
|
+
`Dry run of a context push from ${input.directory} to project ${input.projectId}: nothing was sent and the lock was not written.`
|
|
2370
|
+
];
|
|
2371
|
+
group(
|
|
2372
|
+
lines,
|
|
2373
|
+
"new",
|
|
2374
|
+
plan.push.filter((entry) => entry.expectedContentHash === void 0).map((entry) => entry.sourcePath)
|
|
2375
|
+
);
|
|
2376
|
+
group(
|
|
2377
|
+
lines,
|
|
2378
|
+
"changed",
|
|
2379
|
+
plan.push.filter((entry) => entry.expectedContentHash !== void 0).map((entry) => entry.sourcePath)
|
|
2380
|
+
);
|
|
2381
|
+
group(
|
|
2382
|
+
lines,
|
|
2383
|
+
"removed",
|
|
2384
|
+
plan.removed.map(
|
|
2385
|
+
(p) => `${p}: removed locally; server entry would be kept`
|
|
2386
|
+
)
|
|
2387
|
+
);
|
|
2388
|
+
group(lines, "skipped", plan.skipped.map(skippedLine));
|
|
2389
|
+
if (plan.unchangedLocal.length > 0) {
|
|
2390
|
+
lines.push(`unchanged (${plan.unchangedLocal.length})`);
|
|
2391
|
+
}
|
|
2392
|
+
return lines;
|
|
2393
|
+
}
|
|
2394
|
+
function contextPushJson(input) {
|
|
2395
|
+
return {
|
|
2396
|
+
projectId: input.projectId,
|
|
2397
|
+
directory: input.directory,
|
|
2398
|
+
dryRun: input.dryRun,
|
|
2399
|
+
force: input.force,
|
|
2400
|
+
lock: input.lockPath,
|
|
2401
|
+
lockWritten: input.lockWritten,
|
|
2402
|
+
plan: {
|
|
2403
|
+
push: input.plan.push.map((entry) => {
|
|
2404
|
+
var _a2;
|
|
2405
|
+
return {
|
|
2406
|
+
sourcePath: entry.sourcePath,
|
|
2407
|
+
sha256: entry.sha256,
|
|
2408
|
+
bytes: entry.bytes,
|
|
2409
|
+
expectedContentHash: (_a2 = entry.expectedContentHash) != null ? _a2 : null
|
|
2410
|
+
};
|
|
2411
|
+
}),
|
|
2412
|
+
unchangedLocal: input.plan.unchangedLocal,
|
|
2413
|
+
removed: input.plan.removed,
|
|
2414
|
+
forgotten: input.plan.forgotten,
|
|
2415
|
+
skipped: input.plan.skipped
|
|
2416
|
+
},
|
|
2417
|
+
results: input.results,
|
|
2418
|
+
counts: countOutcomes(input.plan, input.results)
|
|
2419
|
+
};
|
|
1522
2420
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
2421
|
+
|
|
2422
|
+
// src/commands/context/index.ts
|
|
2423
|
+
var HOOK_DEADLINE_MS = 1e4;
|
|
2424
|
+
var PUSH_TIMEOUT_MS = 6e4;
|
|
2425
|
+
function collectPattern(value, previous) {
|
|
2426
|
+
return [...previous, value];
|
|
1528
2427
|
}
|
|
1529
|
-
function
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
2428
|
+
function buildContextCommand() {
|
|
2429
|
+
const context = new Command8("context").description(
|
|
2430
|
+
"Sync local knowledge files into a project's Context"
|
|
2431
|
+
);
|
|
2432
|
+
context.command("push").description(
|
|
2433
|
+
"Push every text file under <dir> into the project's Context by its relative path"
|
|
2434
|
+
).argument(
|
|
2435
|
+
"<dir>",
|
|
2436
|
+
"Folder to push (required; never the working directory by default)"
|
|
2437
|
+
).requiredOption("--project <id>", "Project ID").option("--org <slug>", "Organization context").option(
|
|
2438
|
+
"--force",
|
|
2439
|
+
"On a conflict, replace the server's version once with this folder's"
|
|
2440
|
+
).option("--dry-run", "Print the plan; send nothing and write no lock").option(
|
|
2441
|
+
"--exclude <pattern>",
|
|
2442
|
+
"Also leave out paths matching this gitignore pattern (repeatable)",
|
|
2443
|
+
collectPattern,
|
|
2444
|
+
[]
|
|
2445
|
+
).option(
|
|
2446
|
+
"--hook",
|
|
2447
|
+
"Hook mode: never fails, never blocks, one line on stderr on failure"
|
|
2448
|
+
).option("--format <format>", "Output format: text|json").action(function(dir, opts) {
|
|
2449
|
+
return __async(this, null, function* () {
|
|
2450
|
+
yield run(opts, () => runPush(dir, opts, outputFormatFor(this)));
|
|
2451
|
+
});
|
|
1533
2452
|
});
|
|
2453
|
+
return context;
|
|
1534
2454
|
}
|
|
1535
|
-
function
|
|
2455
|
+
function run(opts, body) {
|
|
1536
2456
|
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 };
|
|
2457
|
+
try {
|
|
2458
|
+
if (opts.hook) {
|
|
2459
|
+
yield withDeadline(HOOK_DEADLINE_MS, () => body());
|
|
2460
|
+
} else {
|
|
2461
|
+
yield body();
|
|
1556
2462
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
2463
|
+
} catch (error) {
|
|
2464
|
+
const message = describeError(error);
|
|
2465
|
+
if (opts.hook) {
|
|
2466
|
+
process.stderr.write(`fabric: context push failed: ${message}
|
|
2467
|
+
`);
|
|
2468
|
+
process.exit(0);
|
|
1559
2469
|
}
|
|
1560
|
-
|
|
2470
|
+
process.stderr.write(`\u2717 ${message}
|
|
2471
|
+
`);
|
|
2472
|
+
process.exit(error instanceof CliFailure ? error.exitCode : 1);
|
|
1561
2473
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
2474
|
+
});
|
|
2475
|
+
}
|
|
2476
|
+
function contextClient(opts) {
|
|
2477
|
+
if (!getApiKey()) {
|
|
2478
|
+
throw new CliFailure(
|
|
2479
|
+
"Not authenticated. Run: fabric auth login --key <api-key>",
|
|
2480
|
+
3
|
|
2481
|
+
);
|
|
2482
|
+
}
|
|
2483
|
+
return getClient(
|
|
2484
|
+
opts.hook ? { timeoutMs: HOOK_DEADLINE_MS, retry: { maxRetries: 0 } } : { timeoutMs: PUSH_TIMEOUT_MS }
|
|
2485
|
+
).withoutContext();
|
|
2486
|
+
}
|
|
2487
|
+
function resolveFolder(dir) {
|
|
2488
|
+
return __async(this, null, function* () {
|
|
2489
|
+
const directory = path5.resolve(dir);
|
|
2490
|
+
const root = yield resolveExistingRoot(directory);
|
|
2491
|
+
const stats = yield lstat3(root).catch(() => null);
|
|
2492
|
+
if (stats === null || !stats.isDirectory()) {
|
|
2493
|
+
throw new CliFailure(`${directory} is not a directory.`, 2);
|
|
1571
2494
|
}
|
|
1572
|
-
return
|
|
2495
|
+
return root;
|
|
1573
2496
|
});
|
|
1574
2497
|
}
|
|
1575
|
-
function
|
|
2498
|
+
function readLockForProject(root, projectId) {
|
|
1576
2499
|
return __async(this, null, function* () {
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
2500
|
+
let lock;
|
|
2501
|
+
try {
|
|
2502
|
+
lock = yield readContextLock(root);
|
|
2503
|
+
} catch (error) {
|
|
2504
|
+
throw new CliFailure(describeError(error), 7);
|
|
1580
2505
|
}
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
2506
|
+
if (lock !== null && lock.projectId !== projectId) {
|
|
2507
|
+
throw new CliFailure(
|
|
2508
|
+
`${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}.`,
|
|
2509
|
+
7
|
|
2510
|
+
);
|
|
1584
2511
|
}
|
|
1585
|
-
|
|
1586
|
-
|
|
2512
|
+
return lock;
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
function print(lines) {
|
|
2516
|
+
process.stdout.write(`${lines.join("\n")}
|
|
2517
|
+
`);
|
|
2518
|
+
}
|
|
2519
|
+
function runPush(dir, opts, format) {
|
|
2520
|
+
return __async(this, null, function* () {
|
|
2521
|
+
const root = yield resolveFolder(dir);
|
|
2522
|
+
const client = opts.dryRun ? null : contextClient(opts);
|
|
2523
|
+
const lock = yield readLockForProject(root, opts.project);
|
|
2524
|
+
const plan = yield computeContextPlan({
|
|
2525
|
+
root,
|
|
2526
|
+
lock,
|
|
2527
|
+
excludes: opts.exclude
|
|
2528
|
+
});
|
|
2529
|
+
if (client === null) {
|
|
2530
|
+
if (format === "json") {
|
|
2531
|
+
printOutput(
|
|
2532
|
+
jsonOutcome({
|
|
2533
|
+
root,
|
|
2534
|
+
opts,
|
|
2535
|
+
plan,
|
|
2536
|
+
results: [],
|
|
2537
|
+
lockWritten: false
|
|
2538
|
+
}),
|
|
2539
|
+
{ format: "json" }
|
|
2540
|
+
);
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2543
|
+
print(
|
|
2544
|
+
formatDryRunReport({
|
|
2545
|
+
projectId: opts.project,
|
|
2546
|
+
directory: root,
|
|
2547
|
+
plan
|
|
2548
|
+
})
|
|
2549
|
+
);
|
|
2550
|
+
return;
|
|
1587
2551
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
2552
|
+
const pushed = yield pushContextPlan({
|
|
2553
|
+
client,
|
|
2554
|
+
projectId: opts.project,
|
|
2555
|
+
org: opts.org,
|
|
2556
|
+
root,
|
|
2557
|
+
plan,
|
|
2558
|
+
force: Boolean(opts.force)
|
|
2559
|
+
});
|
|
2560
|
+
const next = nextContextLock({
|
|
2561
|
+
previous: lock,
|
|
2562
|
+
projectId: opts.project,
|
|
2563
|
+
results: pushed.results,
|
|
2564
|
+
forgotten: plan.forgotten,
|
|
2565
|
+
now: /* @__PURE__ */ new Date()
|
|
2566
|
+
});
|
|
2567
|
+
if (next !== null) {
|
|
2568
|
+
yield writeContextLock(root, next);
|
|
1590
2569
|
}
|
|
1591
|
-
|
|
2570
|
+
if (format === "json") {
|
|
2571
|
+
printOutput(
|
|
2572
|
+
jsonOutcome({
|
|
2573
|
+
root,
|
|
2574
|
+
opts,
|
|
2575
|
+
plan,
|
|
2576
|
+
results: pushed.results,
|
|
2577
|
+
lockWritten: next !== null
|
|
2578
|
+
}),
|
|
2579
|
+
{ format: "json" }
|
|
2580
|
+
);
|
|
2581
|
+
} else {
|
|
2582
|
+
print(
|
|
2583
|
+
formatPushReport({
|
|
2584
|
+
projectId: opts.project,
|
|
2585
|
+
directory: root,
|
|
2586
|
+
plan,
|
|
2587
|
+
results: pushed.results
|
|
2588
|
+
})
|
|
2589
|
+
);
|
|
2590
|
+
}
|
|
2591
|
+
if (pushed.stoppedBy !== null) {
|
|
2592
|
+
throw pushed.stoppedBy;
|
|
2593
|
+
}
|
|
2594
|
+
throwIfIncomplete(pushed.results);
|
|
1592
2595
|
});
|
|
1593
2596
|
}
|
|
1594
|
-
function
|
|
1595
|
-
return
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
2597
|
+
function jsonOutcome(input) {
|
|
2598
|
+
return contextPushJson({
|
|
2599
|
+
projectId: input.opts.project,
|
|
2600
|
+
directory: input.root,
|
|
2601
|
+
lockPath: contextLockPath(input.root),
|
|
2602
|
+
lockWritten: input.lockWritten,
|
|
2603
|
+
dryRun: Boolean(input.opts.dryRun),
|
|
2604
|
+
force: Boolean(input.opts.force),
|
|
2605
|
+
plan: input.plan,
|
|
2606
|
+
results: input.results
|
|
2607
|
+
});
|
|
2608
|
+
}
|
|
2609
|
+
function throwIfIncomplete(results) {
|
|
2610
|
+
const failed = results.filter((r) => r.status === "failed");
|
|
2611
|
+
const conflicts = results.filter((r) => r.status === "conflict");
|
|
2612
|
+
if (failed.length === 0 && conflicts.length === 0) {
|
|
2613
|
+
return;
|
|
2614
|
+
}
|
|
2615
|
+
const parts = [];
|
|
2616
|
+
if (conflicts.length > 0) {
|
|
2617
|
+
parts.push(
|
|
2618
|
+
`${conflicts.length} conflict${conflicts.length === 1 ? "" : "s"}`
|
|
2619
|
+
);
|
|
2620
|
+
}
|
|
2621
|
+
const first = failed[0];
|
|
2622
|
+
if (first && first.status === "failed") {
|
|
2623
|
+
parts.push(
|
|
2624
|
+
`${failed.length} failed, first ${first.sourcePath}: ${first.error}`
|
|
2625
|
+
);
|
|
2626
|
+
}
|
|
2627
|
+
const total = conflicts.length + failed.length;
|
|
2628
|
+
let message = `${total} of ${results.length} file${results.length === 1 ? "" : "s"} sent ${total === 1 ? "was" : "were"} not stored (${parts.join(", ")}).`;
|
|
2629
|
+
if (conflicts.length > 0) {
|
|
2630
|
+
message += " 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).";
|
|
2631
|
+
}
|
|
2632
|
+
throw new CliFailure(
|
|
2633
|
+
message,
|
|
2634
|
+
first && first.status === "failed" ? first.exitCode : 1
|
|
2635
|
+
);
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
// src/commands/ctx/index.ts
|
|
2639
|
+
import { Command as Command9 } from "commander";
|
|
2640
|
+
function buildCtxCommand() {
|
|
2641
|
+
const ctx = new Command9("ctx").description(
|
|
2642
|
+
"Manage the organization commands run in"
|
|
2643
|
+
);
|
|
2644
|
+
ctx.command("current").description("Show the active default context").action(() => {
|
|
2645
|
+
const stored = getDefaultContext();
|
|
2646
|
+
if (!stored) {
|
|
2647
|
+
printWarning(
|
|
2648
|
+
"No default context set. Use --org <slug> on each command, or run:\n fabric ctx use org <slug>"
|
|
2649
|
+
);
|
|
2650
|
+
return;
|
|
1599
2651
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
check.path,
|
|
1604
|
-
constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK
|
|
2652
|
+
if (stored.type === "personal") {
|
|
2653
|
+
printWarning(
|
|
2654
|
+
"Context: personal \u2014 retired, and no command will accept it.\nReplace it with:\n fabric ctx use org <slug>"
|
|
1605
2655
|
);
|
|
1606
|
-
|
|
1607
|
-
if (error.code === "ENOENT") {
|
|
1608
|
-
return null;
|
|
1609
|
-
}
|
|
1610
|
-
throw error;
|
|
2656
|
+
return;
|
|
1611
2657
|
}
|
|
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
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
2658
|
+
process.stdout.write(`Context: org:${stored.slug}
|
|
2659
|
+
`);
|
|
2660
|
+
});
|
|
2661
|
+
const use = new Command9("use").description(
|
|
2662
|
+
"Set the default organization for commands"
|
|
2663
|
+
);
|
|
2664
|
+
use.command("personal").description("Retired \u2014 context is organization-only").action(() => {
|
|
2665
|
+
printError(
|
|
2666
|
+
"Personal context no longer exists \u2014 every command runs inside an organization.\nSet one with:\n fabric ctx use org <slug>",
|
|
2667
|
+
2
|
|
2668
|
+
);
|
|
2669
|
+
});
|
|
2670
|
+
use.command("org <slug>").description("Use an org context by default").action((slug) => {
|
|
2671
|
+
saveDefaultContext({ type: "org", slug });
|
|
2672
|
+
printSuccess(`Default context set to: org:${slug}`);
|
|
2673
|
+
});
|
|
2674
|
+
ctx.addCommand(use);
|
|
2675
|
+
return ctx;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
// src/commands/features/index.ts
|
|
2679
|
+
import { Command as Command10 } from "commander";
|
|
2680
|
+
function buildFeaturesCommand() {
|
|
2681
|
+
const features = new Command10("features").description(
|
|
2682
|
+
"Manage project features"
|
|
2683
|
+
);
|
|
2684
|
+
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(
|
|
2685
|
+
"--priority <priority>",
|
|
2686
|
+
"Filter: P0_CRITICAL|P1_HIGH|P2_MEDIUM|P3_LOW"
|
|
2687
|
+
).option("--search <query>", "Search by title").option("--limit <n>", "Max results (default 50)", "50").option(
|
|
2688
|
+
"--format <format>",
|
|
2689
|
+
"Output format: table|json|yaml|csv",
|
|
2690
|
+
"table"
|
|
2691
|
+
).option("-q, --quiet", "Print IDs only").action(
|
|
2692
|
+
(opts) => __async(null, null, function* () {
|
|
2693
|
+
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
2694
|
+
const client = getClient();
|
|
2695
|
+
let list;
|
|
2696
|
+
try {
|
|
2697
|
+
list = yield client.projects.listFeatures(opts.project, {
|
|
2698
|
+
org: (ctx == null ? void 0 : ctx.type) === "org" ? ctx.slug : void 0,
|
|
2699
|
+
priority: opts.priority,
|
|
2700
|
+
search: opts.search,
|
|
2701
|
+
limit: Number(opts.limit)
|
|
2702
|
+
});
|
|
2703
|
+
} catch (err) {
|
|
2704
|
+
printError(err.message, 1);
|
|
1657
2705
|
}
|
|
1658
|
-
if (
|
|
1659
|
-
|
|
1660
|
-
`${
|
|
1661
|
-
|
|
2706
|
+
if (opts.quiet) {
|
|
2707
|
+
for (const f of list) {
|
|
2708
|
+
process.stdout.write(`${f.id}
|
|
2709
|
+
`);
|
|
2710
|
+
}
|
|
2711
|
+
return;
|
|
1662
2712
|
}
|
|
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
|
-
);
|
|
2713
|
+
printOutput(list, {
|
|
2714
|
+
format: opts.format,
|
|
2715
|
+
columns: ["identifier", "title", "priority", "createdAt"]
|
|
2716
|
+
});
|
|
2717
|
+
})
|
|
2718
|
+
);
|
|
2719
|
+
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(
|
|
2720
|
+
(id, opts) => __async(null, null, function* () {
|
|
2721
|
+
var _a2, _b, _c;
|
|
2722
|
+
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
2723
|
+
const client = getClient();
|
|
2724
|
+
let feature;
|
|
2725
|
+
try {
|
|
2726
|
+
feature = yield client.projects.getFeature(
|
|
2727
|
+
opts.project,
|
|
2728
|
+
id,
|
|
2729
|
+
{
|
|
2730
|
+
org: (ctx == null ? void 0 : ctx.type) === "org" ? ctx.slug : void 0
|
|
1691
2731
|
}
|
|
1692
|
-
}));
|
|
1693
|
-
continue;
|
|
1694
|
-
}
|
|
1695
|
-
if (stats.isSymbolicLink()) {
|
|
1696
|
-
throw new Error(
|
|
1697
|
-
`Refusing to sync: ${describeRejection({ ok: false, reason: "symlink", detail: current })}.`
|
|
1698
2732
|
);
|
|
2733
|
+
} catch (err) {
|
|
2734
|
+
printError(err.message, 1);
|
|
1699
2735
|
}
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
2736
|
+
const fmt = opts.format;
|
|
2737
|
+
if (fmt === "json" || fmt === "yaml") {
|
|
2738
|
+
printOutput(feature, { format: fmt });
|
|
2739
|
+
return;
|
|
2740
|
+
}
|
|
2741
|
+
printRecord({
|
|
2742
|
+
ID: feature.id,
|
|
2743
|
+
Identifier: feature.identifier,
|
|
2744
|
+
Title: feature.title,
|
|
2745
|
+
Priority: feature.priority,
|
|
2746
|
+
Description: (_a2 = feature.description) != null ? _a2 : "(none)",
|
|
2747
|
+
"Acceptance Criteria": (_b = feature.acceptanceCriteria) != null ? _b : "(none)",
|
|
2748
|
+
Project: feature.projectId,
|
|
2749
|
+
Assignee: (_c = feature.assigneeId) != null ? _c : "(none)",
|
|
2750
|
+
Created: feature.createdAt.slice(0, 10)
|
|
2751
|
+
});
|
|
2752
|
+
})
|
|
2753
|
+
);
|
|
2754
|
+
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(
|
|
2755
|
+
"--priority <priority>",
|
|
2756
|
+
"Priority: P0_CRITICAL|P1_HIGH|P2_MEDIUM|P3_LOW (default: P2_MEDIUM)"
|
|
2757
|
+
).option("--format <format>", "Output format: table|json|yaml", "table").action(
|
|
2758
|
+
(title, opts) => __async(null, null, function* () {
|
|
2759
|
+
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
2760
|
+
const client = getClient();
|
|
2761
|
+
let feature;
|
|
2762
|
+
try {
|
|
2763
|
+
feature = yield client.projects.createFeature(
|
|
2764
|
+
opts.project,
|
|
2765
|
+
{
|
|
2766
|
+
title,
|
|
2767
|
+
description: opts.description,
|
|
2768
|
+
priority: opts.priority,
|
|
2769
|
+
org: (ctx == null ? void 0 : ctx.type) === "org" ? ctx.slug : void 0
|
|
2770
|
+
}
|
|
1703
2771
|
);
|
|
2772
|
+
} catch (err) {
|
|
2773
|
+
printError(err.message, 1);
|
|
1704
2774
|
}
|
|
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);
|
|
2775
|
+
const fmt = opts.format;
|
|
2776
|
+
if (fmt === "json" || fmt === "yaml") {
|
|
2777
|
+
printOutput(feature, { format: fmt });
|
|
2778
|
+
return;
|
|
1735
2779
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
2780
|
+
printSuccess(`Feature ${feature.identifier} created`);
|
|
2781
|
+
printRecord({
|
|
2782
|
+
ID: feature.id,
|
|
2783
|
+
Identifier: feature.identifier,
|
|
2784
|
+
Title: feature.title,
|
|
2785
|
+
Priority: feature.priority,
|
|
2786
|
+
Project: feature.projectId,
|
|
2787
|
+
Created: feature.createdAt.slice(0, 10)
|
|
2788
|
+
});
|
|
2789
|
+
})
|
|
2790
|
+
);
|
|
2791
|
+
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(
|
|
2792
|
+
"--format <format>",
|
|
2793
|
+
"Output format: table|json|yaml|csv",
|
|
2794
|
+
"table"
|
|
2795
|
+
).option("-q, --quiet", "Print IDs only").action(
|
|
2796
|
+
(opts) => __async(null, null, function* () {
|
|
2797
|
+
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
2798
|
+
const client = getClient();
|
|
2799
|
+
let tasks;
|
|
2800
|
+
try {
|
|
2801
|
+
tasks = yield client.projects.listTasks(
|
|
2802
|
+
opts.project,
|
|
2803
|
+
opts.feature,
|
|
2804
|
+
{
|
|
2805
|
+
org: (ctx == null ? void 0 : ctx.type) === "org" ? ctx.slug : void 0
|
|
2806
|
+
}
|
|
1742
2807
|
);
|
|
2808
|
+
} catch (err) {
|
|
2809
|
+
printError(err.message, 1);
|
|
1743
2810
|
}
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
2811
|
+
if (opts.quiet) {
|
|
2812
|
+
for (const t of tasks) {
|
|
2813
|
+
process.stdout.write(`${t.id}
|
|
2814
|
+
`);
|
|
2815
|
+
}
|
|
2816
|
+
return;
|
|
1749
2817
|
}
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
2818
|
+
printOutput(tasks, {
|
|
2819
|
+
format: opts.format,
|
|
2820
|
+
columns: [
|
|
2821
|
+
"identifier",
|
|
2822
|
+
"title",
|
|
2823
|
+
"status",
|
|
2824
|
+
"estimatedHours"
|
|
2825
|
+
]
|
|
1753
2826
|
});
|
|
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";
|
|
2827
|
+
})
|
|
2828
|
+
);
|
|
2829
|
+
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(
|
|
2830
|
+
"--priority <priority>",
|
|
2831
|
+
"Priority: P0_CRITICAL|P1_HIGH|P2_MEDIUM|P3_LOW"
|
|
2832
|
+
).option("--format <format>", "Output format: table|json|yaml", "table").action(
|
|
2833
|
+
(id, opts) => __async(null, null, function* () {
|
|
2834
|
+
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
2835
|
+
const client = getClient();
|
|
2836
|
+
let feature;
|
|
2837
|
+
try {
|
|
2838
|
+
feature = yield client.projects.updateFeature(
|
|
2839
|
+
opts.project,
|
|
2840
|
+
id,
|
|
2841
|
+
{
|
|
2842
|
+
title: opts.title,
|
|
2843
|
+
description: opts.description,
|
|
2844
|
+
priority: opts.priority,
|
|
2845
|
+
org: (ctx == null ? void 0 : ctx.type) === "org" ? ctx.slug : void 0
|
|
2846
|
+
}
|
|
2847
|
+
);
|
|
2848
|
+
} catch (err) {
|
|
2849
|
+
printError(err.message, 1);
|
|
1797
2850
|
}
|
|
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";
|
|
2851
|
+
const fmt = opts.format;
|
|
2852
|
+
if (fmt === "json" || fmt === "yaml") {
|
|
2853
|
+
printOutput(feature, { format: fmt });
|
|
2854
|
+
return;
|
|
1809
2855
|
}
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
2856
|
+
printRecord({
|
|
2857
|
+
ID: feature.id,
|
|
2858
|
+
Identifier: feature.identifier,
|
|
2859
|
+
Title: feature.title,
|
|
2860
|
+
Priority: feature.priority,
|
|
2861
|
+
Updated: feature.updatedAt.slice(0, 10)
|
|
2862
|
+
});
|
|
2863
|
+
})
|
|
2864
|
+
);
|
|
2865
|
+
return features;
|
|
1813
2866
|
}
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
2867
|
+
|
|
2868
|
+
// src/commands/frames/index.ts
|
|
2869
|
+
import { Command as Command11 } from "commander";
|
|
2870
|
+
function buildFramesCommand() {
|
|
2871
|
+
const frames = new Command11("frames").description(
|
|
2872
|
+
"Manage AI-generated frames and slideshows"
|
|
2873
|
+
);
|
|
2874
|
+
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(
|
|
2875
|
+
"--format <format>",
|
|
2876
|
+
"Output format: table|json|yaml|csv",
|
|
2877
|
+
"table"
|
|
2878
|
+
).option("-q, --quiet", "Print IDs only").action(
|
|
2879
|
+
(opts) => __async(null, null, function* () {
|
|
2880
|
+
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
2881
|
+
const client = getClient();
|
|
2882
|
+
let list;
|
|
2883
|
+
try {
|
|
2884
|
+
list = yield client.frames.list({
|
|
2885
|
+
org: (ctx == null ? void 0 : ctx.type) === "org" ? ctx.slug : void 0,
|
|
2886
|
+
limit: Number(opts.limit)
|
|
2887
|
+
});
|
|
2888
|
+
} catch (err) {
|
|
2889
|
+
printError(err.message, 1);
|
|
1821
2890
|
}
|
|
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);
|
|
2891
|
+
if (opts.quiet) {
|
|
2892
|
+
for (const f of list) {
|
|
2893
|
+
process.stdout.write(`${f.id}
|
|
2894
|
+
`);
|
|
2895
|
+
}
|
|
2896
|
+
return;
|
|
1842
2897
|
}
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
2898
|
+
printOutput(list, {
|
|
2899
|
+
format: opts.format,
|
|
2900
|
+
columns: [
|
|
2901
|
+
"id",
|
|
2902
|
+
"title",
|
|
2903
|
+
"fileType",
|
|
2904
|
+
"status",
|
|
2905
|
+
"isPublic",
|
|
2906
|
+
"updatedAt"
|
|
2907
|
+
]
|
|
2908
|
+
});
|
|
2909
|
+
})
|
|
2910
|
+
);
|
|
2911
|
+
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(
|
|
2912
|
+
(id, opts) => __async(null, null, function* () {
|
|
2913
|
+
var _a2, _b, _c;
|
|
2914
|
+
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
2915
|
+
const client = getClient();
|
|
2916
|
+
let frame;
|
|
2917
|
+
try {
|
|
2918
|
+
frame = yield client.frames.get(id, {
|
|
2919
|
+
org: (ctx == null ? void 0 : ctx.type) === "org" ? ctx.slug : void 0
|
|
2920
|
+
});
|
|
2921
|
+
} catch (err) {
|
|
2922
|
+
printError(err.message, 1);
|
|
1846
2923
|
}
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
2924
|
+
const fmt = opts.format;
|
|
2925
|
+
if (fmt === "json" || fmt === "yaml") {
|
|
2926
|
+
printOutput(frame, { format: fmt });
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
printRecord({
|
|
2930
|
+
ID: frame.id,
|
|
2931
|
+
Title: frame.title,
|
|
2932
|
+
Kind: (_a2 = frame.kind) != null ? _a2 : "(none)",
|
|
2933
|
+
"File Type": frame.fileType,
|
|
2934
|
+
Status: frame.status,
|
|
2935
|
+
Public: frame.isPublic ? "yes" : "no",
|
|
2936
|
+
"Share Token": (_b = frame.shareToken) != null ? _b : "(none)",
|
|
2937
|
+
Description: (_c = frame.description) != null ? _c : "(none)",
|
|
2938
|
+
Updated: frame.updatedAt.slice(0, 10)
|
|
2939
|
+
});
|
|
2940
|
+
})
|
|
2941
|
+
);
|
|
2942
|
+
return frames;
|
|
1851
2943
|
}
|
|
1852
2944
|
|
|
2945
|
+
// src/commands/instructions/index.ts
|
|
2946
|
+
import path8 from "path";
|
|
2947
|
+
import { Command as Command12 } from "commander";
|
|
2948
|
+
|
|
1853
2949
|
// src/lib/instructions/apply.ts
|
|
2950
|
+
import { createHash as createHash3 } from "crypto";
|
|
2951
|
+
import { chmod } from "fs/promises";
|
|
1854
2952
|
function applyPlan(input) {
|
|
1855
2953
|
return __async(this, null, function* () {
|
|
1856
2954
|
var _a2, _b;
|
|
@@ -1880,7 +2978,7 @@ function applyPlan(input) {
|
|
|
1880
2978
|
`The downloaded bundle is missing ${entry.path}. Nothing was written.`
|
|
1881
2979
|
);
|
|
1882
2980
|
}
|
|
1883
|
-
const actual =
|
|
2981
|
+
const actual = createHash3("sha256").update(bytes).digest("hex");
|
|
1884
2982
|
if (actual !== entry.sha256) {
|
|
1885
2983
|
throw new Error(
|
|
1886
2984
|
`Checksum mismatch for ${entry.path}: the published snapshot expects ${entry.sha256} and the downloaded bundle contains ${actual}. Nothing was written.`
|
|
@@ -2081,12 +3179,12 @@ function extractBundle(archive, wanted) {
|
|
|
2081
3179
|
}
|
|
2082
3180
|
|
|
2083
3181
|
// src/lib/instructions/hook.ts
|
|
2084
|
-
import
|
|
2085
|
-
var CLAUDE_SETTINGS_RELATIVE_PATH =
|
|
3182
|
+
import path6 from "path";
|
|
3183
|
+
var CLAUDE_SETTINGS_RELATIVE_PATH = path6.join(
|
|
2086
3184
|
".claude",
|
|
2087
3185
|
"settings.local.json"
|
|
2088
3186
|
);
|
|
2089
|
-
var CODEX_HOOKS_RELATIVE_PATH =
|
|
3187
|
+
var CODEX_HOOKS_RELATIVE_PATH = path6.join(".codex", "hooks.json");
|
|
2090
3188
|
var CLAUDE_SETTINGS_POSIX_PATH = ".claude/settings.local.json";
|
|
2091
3189
|
var CODEX_HOOKS_POSIX_PATH = ".codex/hooks.json";
|
|
2092
3190
|
var HOOK_TIMEOUT_SECONDS = 15;
|
|
@@ -2110,7 +3208,7 @@ function mergeSessionStartHook(input) {
|
|
|
2110
3208
|
return __async(this, null, function* () {
|
|
2111
3209
|
var _a2;
|
|
2112
3210
|
const hookTarget = hookTargetFor((_a2 = input.tool) != null ? _a2 : "claude-code");
|
|
2113
|
-
const settingsPath =
|
|
3211
|
+
const settingsPath = path6.join(input.root, hookTarget.relativePath);
|
|
2114
3212
|
let existingRaw;
|
|
2115
3213
|
try {
|
|
2116
3214
|
const existing = yield readFileSafely(
|
|
@@ -2150,14 +3248,14 @@ function mergeSessionStartHook(input) {
|
|
|
2150
3248
|
throw refuseShape(settingsPath, '"hooks.SessionStart" is not an array');
|
|
2151
3249
|
}
|
|
2152
3250
|
const rawGroups = Array.isArray(hooks.SessionStart) ? hooks.SessionStart : [];
|
|
2153
|
-
for (const [index,
|
|
2154
|
-
if (!isRecord(
|
|
3251
|
+
for (const [index, group2] of rawGroups.entries()) {
|
|
3252
|
+
if (!isRecord(group2)) {
|
|
2155
3253
|
throw refuseShape(
|
|
2156
3254
|
settingsPath,
|
|
2157
3255
|
`"hooks.SessionStart[${index}]" is not an object`
|
|
2158
3256
|
);
|
|
2159
3257
|
}
|
|
2160
|
-
if (
|
|
3258
|
+
if (group2.hooks !== void 0 && !Array.isArray(group2.hooks)) {
|
|
2161
3259
|
throw refuseShape(
|
|
2162
3260
|
settingsPath,
|
|
2163
3261
|
`"hooks.SessionStart[${index}].hooks" is not an array`
|
|
@@ -2165,8 +3263,8 @@ function mergeSessionStartHook(input) {
|
|
|
2165
3263
|
}
|
|
2166
3264
|
}
|
|
2167
3265
|
const sessionStart = rawGroups.map(
|
|
2168
|
-
(
|
|
2169
|
-
hooks: Array.isArray(
|
|
3266
|
+
(group2) => __spreadProps(__spreadValues({}, group2), {
|
|
3267
|
+
hooks: Array.isArray(group2 == null ? void 0 : group2.hooks) ? [...group2.hooks] : []
|
|
2170
3268
|
})
|
|
2171
3269
|
);
|
|
2172
3270
|
const replacement = {
|
|
@@ -2176,16 +3274,16 @@ function mergeSessionStartHook(input) {
|
|
|
2176
3274
|
};
|
|
2177
3275
|
let replacedCount = 0;
|
|
2178
3276
|
const pruned = [];
|
|
2179
|
-
for (const
|
|
2180
|
-
const kept =
|
|
3277
|
+
for (const group2 of sessionStart) {
|
|
3278
|
+
const kept = group2.hooks.filter(
|
|
2181
3279
|
(hook) => !isFabricHookFor(hook, input.projectId)
|
|
2182
3280
|
);
|
|
2183
|
-
const removed =
|
|
3281
|
+
const removed = group2.hooks.length - kept.length;
|
|
2184
3282
|
replacedCount += removed;
|
|
2185
3283
|
if (kept.length === 0 && removed > 0) {
|
|
2186
3284
|
continue;
|
|
2187
3285
|
}
|
|
2188
|
-
pruned.push(__spreadProps(__spreadValues({},
|
|
3286
|
+
pruned.push(__spreadProps(__spreadValues({}, group2), { hooks: kept }));
|
|
2189
3287
|
}
|
|
2190
3288
|
pruned.push({ hooks: [replacement] });
|
|
2191
3289
|
const next = __spreadProps(__spreadValues({}, settings), {
|
|
@@ -2258,14 +3356,14 @@ function isRecord(value) {
|
|
|
2258
3356
|
|
|
2259
3357
|
// src/lib/instructions/lock.ts
|
|
2260
3358
|
import { readFile as readFile2 } from "fs/promises";
|
|
2261
|
-
import
|
|
3359
|
+
import path7 from "path";
|
|
2262
3360
|
var LOCK_DIRECTORY = ".fabric";
|
|
2263
3361
|
var LOCK_FILENAME = "instructions.lock";
|
|
2264
3362
|
var LOCK_VERSION = 1;
|
|
2265
3363
|
var LOCK_RELATIVE_PATH = `${LOCK_DIRECTORY}/${LOCK_FILENAME}`;
|
|
2266
|
-
var
|
|
3364
|
+
var SHA256_HEX2 = /^[0-9a-f]{64}$/;
|
|
2267
3365
|
function lockPath(destination) {
|
|
2268
|
-
return
|
|
3366
|
+
return path7.join(destination, LOCK_DIRECTORY, LOCK_FILENAME);
|
|
2269
3367
|
}
|
|
2270
3368
|
function readLock(destination) {
|
|
2271
3369
|
return __async(this, null, function* () {
|
|
@@ -2287,7 +3385,7 @@ function readLock(destination) {
|
|
|
2287
3385
|
`${file} is unreadable: it is not valid JSON. Delete it to start from a clean sync.`
|
|
2288
3386
|
);
|
|
2289
3387
|
}
|
|
2290
|
-
const problem =
|
|
3388
|
+
const problem = lockProblem2(parsed);
|
|
2291
3389
|
if (problem !== null) {
|
|
2292
3390
|
throw new Error(
|
|
2293
3391
|
`${file} is unreadable: ${problem}. Delete it to start from a clean sync.`
|
|
@@ -2296,7 +3394,7 @@ function readLock(destination) {
|
|
|
2296
3394
|
return parsed;
|
|
2297
3395
|
});
|
|
2298
3396
|
}
|
|
2299
|
-
function
|
|
3397
|
+
function serializeLock2(lock) {
|
|
2300
3398
|
return `${JSON.stringify(lock, null, 2).replace(/\r\n/g, "\n")}
|
|
2301
3399
|
`;
|
|
2302
3400
|
}
|
|
@@ -2305,11 +3403,11 @@ function writeLock(root, lock) {
|
|
|
2305
3403
|
yield writeFileSafely({
|
|
2306
3404
|
root,
|
|
2307
3405
|
relativePath: LOCK_RELATIVE_PATH,
|
|
2308
|
-
bytes: new TextEncoder().encode(
|
|
3406
|
+
bytes: new TextEncoder().encode(serializeLock2(lock))
|
|
2309
3407
|
});
|
|
2310
3408
|
});
|
|
2311
3409
|
}
|
|
2312
|
-
function
|
|
3410
|
+
function lockProblem2(value) {
|
|
2313
3411
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2314
3412
|
return "it is not a JSON object";
|
|
2315
3413
|
}
|
|
@@ -2341,7 +3439,7 @@ function lockProblem(value) {
|
|
|
2341
3439
|
return `its entry for ${JSON.stringify(filePath)} is not an object`;
|
|
2342
3440
|
}
|
|
2343
3441
|
const record = entry;
|
|
2344
|
-
if (typeof record.sha256 !== "string" || !
|
|
3442
|
+
if (typeof record.sha256 !== "string" || !SHA256_HEX2.test(record.sha256)) {
|
|
2345
3443
|
return `its entry for ${JSON.stringify(filePath)} has no 64-character hex sha256`;
|
|
2346
3444
|
}
|
|
2347
3445
|
if (record.mode !== null && (typeof record.mode !== "number" || !Number.isInteger(record.mode) || record.mode < 0)) {
|
|
@@ -2352,8 +3450,8 @@ function lockProblem(value) {
|
|
|
2352
3450
|
}
|
|
2353
3451
|
|
|
2354
3452
|
// src/lib/instructions/manifest.ts
|
|
2355
|
-
import { createHash as
|
|
2356
|
-
var
|
|
3453
|
+
import { createHash as createHash4 } from "crypto";
|
|
3454
|
+
var SHA256_HEX3 = /^[0-9a-f]{64}$/;
|
|
2357
3455
|
var MANIFEST_LIMITS = {
|
|
2358
3456
|
maxFiles: 5e3,
|
|
2359
3457
|
maxTotalBytes: 52428800,
|
|
@@ -2372,7 +3470,7 @@ function maxArchiveBytes(entries) {
|
|
|
2372
3470
|
function computeSnapshotDigest(entries) {
|
|
2373
3471
|
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
3472
|
`).join("");
|
|
2375
|
-
return
|
|
3473
|
+
return createHash4("sha256").update(Buffer.from(lines, "utf8")).digest("hex");
|
|
2376
3474
|
}
|
|
2377
3475
|
function assertValidManifest(input) {
|
|
2378
3476
|
const { manifest, snapshot } = input;
|
|
@@ -2458,7 +3556,7 @@ function validateEntry(raw) {
|
|
|
2458
3556
|
})}.`
|
|
2459
3557
|
);
|
|
2460
3558
|
}
|
|
2461
|
-
if (typeof entry.sha256 !== "string" || !
|
|
3559
|
+
if (typeof entry.sha256 !== "string" || !SHA256_HEX3.test(entry.sha256)) {
|
|
2462
3560
|
throw new Error(
|
|
2463
3561
|
`The manifest entry for ${entry.path} has no 64-character hex sha256. Nothing was changed.`
|
|
2464
3562
|
);
|
|
@@ -2493,11 +3591,11 @@ function validateEntry(raw) {
|
|
|
2493
3591
|
}
|
|
2494
3592
|
|
|
2495
3593
|
// src/lib/instructions/plan.ts
|
|
2496
|
-
import { createHash as
|
|
3594
|
+
import { createHash as createHash5 } from "crypto";
|
|
2497
3595
|
function hashLocalFile(root, relativePath) {
|
|
2498
3596
|
return __async(this, null, function* () {
|
|
2499
3597
|
const read = yield readFileSafely(root, relativePath);
|
|
2500
|
-
return read === null ? null :
|
|
3598
|
+
return read === null ? null : createHash5("sha256").update(read.bytes).digest("hex");
|
|
2501
3599
|
});
|
|
2502
3600
|
}
|
|
2503
3601
|
function lockPaths(lock) {
|
|
@@ -2550,7 +3648,7 @@ function verifyLedger(input) {
|
|
|
2550
3648
|
drifted.push(`${lockedPath} (missing)`);
|
|
2551
3649
|
continue;
|
|
2552
3650
|
}
|
|
2553
|
-
const actual =
|
|
3651
|
+
const actual = createHash5("sha256").update(read.bytes).digest("hex");
|
|
2554
3652
|
if (actual !== locked.sha256) {
|
|
2555
3653
|
drifted.push(`${lockedPath} (edited)`);
|
|
2556
3654
|
continue;
|
|
@@ -2658,7 +3756,7 @@ function nextLock(input) {
|
|
|
2658
3756
|
}
|
|
2659
3757
|
|
|
2660
3758
|
// src/lib/instructions/push.ts
|
|
2661
|
-
import { createHash as
|
|
3759
|
+
import { createHash as createHash6 } from "crypto";
|
|
2662
3760
|
var MAX_PUSH_CHANGES = 50;
|
|
2663
3761
|
function assertPushablePath(candidate, source) {
|
|
2664
3762
|
const check = checkRelativePath(candidate);
|
|
@@ -2697,16 +3795,16 @@ function assertLockMatchesManifest(lock, manifest) {
|
|
|
2697
3795
|
const published = new Map(
|
|
2698
3796
|
manifest.map((entry) => [entry.path, entry.sha256])
|
|
2699
3797
|
);
|
|
2700
|
-
for (const [
|
|
2701
|
-
const expected = published.get(
|
|
3798
|
+
for (const [path9, entry] of Object.entries(lock.files)) {
|
|
3799
|
+
const expected = published.get(path9);
|
|
2702
3800
|
if (expected === void 0) {
|
|
2703
3801
|
refuseStaleLedger(
|
|
2704
|
-
`it lists ${
|
|
3802
|
+
`it lists ${path9}, which version ${lock.snapshotVersion} does not contain`
|
|
2705
3803
|
);
|
|
2706
3804
|
}
|
|
2707
3805
|
if (expected !== entry.sha256) {
|
|
2708
3806
|
refuseStaleLedger(
|
|
2709
|
-
`its record of ${
|
|
3807
|
+
`its record of ${path9} is not the published file`
|
|
2710
3808
|
);
|
|
2711
3809
|
}
|
|
2712
3810
|
}
|
|
@@ -2772,7 +3870,7 @@ function computePushPlan(input) {
|
|
|
2772
3870
|
changes.push({ op: "delete", path: lockedPath });
|
|
2773
3871
|
continue;
|
|
2774
3872
|
}
|
|
2775
|
-
const actual =
|
|
3873
|
+
const actual = createHash6("sha256").update(read.bytes).digest("hex");
|
|
2776
3874
|
if (actual === ((_b = lock.files[lockedPath]) == null ? void 0 : _b.sha256)) {
|
|
2777
3875
|
unchanged.push(lockedPath);
|
|
2778
3876
|
continue;
|
|
@@ -2801,23 +3899,16 @@ function computePushPlan(input) {
|
|
|
2801
3899
|
}
|
|
2802
3900
|
|
|
2803
3901
|
// src/commands/instructions/index.ts
|
|
2804
|
-
var
|
|
3902
|
+
var HOOK_DEADLINE_MS2 = 1e4;
|
|
2805
3903
|
var CHECK_TIMEOUT_MS = 5e3;
|
|
2806
3904
|
var SYNC_TIMEOUT_MS = 15e3;
|
|
2807
3905
|
var BUNDLE_TIMEOUT_MS = 6e4;
|
|
2808
|
-
var
|
|
3906
|
+
var PUSH_TIMEOUT_MS2 = 6e4;
|
|
2809
3907
|
function collectAdded(value, previous) {
|
|
2810
3908
|
return [...previous, value];
|
|
2811
3909
|
}
|
|
2812
|
-
var CliFailure = class extends Error {
|
|
2813
|
-
constructor(message, exitCode) {
|
|
2814
|
-
super(message);
|
|
2815
|
-
this.exitCode = exitCode;
|
|
2816
|
-
this.name = "CliFailure";
|
|
2817
|
-
}
|
|
2818
|
-
};
|
|
2819
3910
|
function buildInstructionsCommand() {
|
|
2820
|
-
const instructions = new
|
|
3911
|
+
const instructions = new Command12("instructions").description(
|
|
2821
3912
|
"Keep a checkout current with a project's published coding instructions"
|
|
2822
3913
|
);
|
|
2823
3914
|
instructions.command("check").description(
|
|
@@ -2830,7 +3921,11 @@ function buildInstructionsCommand() {
|
|
|
2830
3921
|
"Session-hook mode: plain text, never fails, never blocks a session"
|
|
2831
3922
|
).option("--format <format>", "Output format: text|json").action(function(opts) {
|
|
2832
3923
|
return __async(this, null, function* () {
|
|
2833
|
-
yield
|
|
3924
|
+
yield run2(
|
|
3925
|
+
opts,
|
|
3926
|
+
"check",
|
|
3927
|
+
() => runCheck(opts, outputFormatFor(this))
|
|
3928
|
+
);
|
|
2834
3929
|
});
|
|
2835
3930
|
});
|
|
2836
3931
|
instructions.command("sync").description(
|
|
@@ -2840,22 +3935,25 @@ function buildInstructionsCommand() {
|
|
|
2840
3935
|
"Session-hook mode: plain text, never fails, never blocks a session"
|
|
2841
3936
|
).option("--format <format>", "Output format: text|json").action(function(opts) {
|
|
2842
3937
|
return __async(this, null, function* () {
|
|
2843
|
-
yield
|
|
3938
|
+
yield run2(opts, "sync", () => runSync(opts, outputFormatFor(this)));
|
|
2844
3939
|
});
|
|
2845
3940
|
});
|
|
2846
3941
|
instructions.command("push").description(
|
|
2847
|
-
"
|
|
3942
|
+
"Propose this checkout's edits to the project's coding instructions, or publish them with --publish"
|
|
2848
3943
|
).requiredOption("--project <id>", "Project ID").option("--dest <dir>", "Destination directory (default: cwd)").option("--org <slug>", "Organization context").option(
|
|
2849
3944
|
"--add <path>",
|
|
2850
3945
|
"Also send a file the last sync did not write (repeatable)",
|
|
2851
3946
|
collectAdded,
|
|
2852
3947
|
[]
|
|
3948
|
+
).option(
|
|
3949
|
+
"--publish",
|
|
3950
|
+
"Publish the change as a new version instead of proposing it (needs a key with instructions:publish)"
|
|
2853
3951
|
).option("--dry-run", "Print the change set and send nothing").option("--format <format>", "Output format: text|json").action(function(opts) {
|
|
2854
3952
|
return __async(this, null, function* () {
|
|
2855
|
-
yield
|
|
3953
|
+
yield run2(
|
|
2856
3954
|
__spreadProps(__spreadValues({}, opts), { hook: false }),
|
|
2857
3955
|
"push",
|
|
2858
|
-
() =>
|
|
3956
|
+
() => runPush2(opts, outputFormatFor(this))
|
|
2859
3957
|
);
|
|
2860
3958
|
});
|
|
2861
3959
|
});
|
|
@@ -2869,24 +3967,25 @@ function buildInstructionsCommand() {
|
|
|
2869
3967
|
"Let the hook apply changes instead of only reporting them"
|
|
2870
3968
|
).option("--format <format>", "Output format: text|json").action(function(opts) {
|
|
2871
3969
|
return __async(this, null, function* () {
|
|
2872
|
-
yield
|
|
3970
|
+
yield run2(
|
|
2873
3971
|
__spreadProps(__spreadValues({}, opts), { hook: false }),
|
|
2874
3972
|
"init",
|
|
2875
|
-
() => runInit(opts,
|
|
3973
|
+
() => runInit(opts, outputFormatFor(this))
|
|
2876
3974
|
);
|
|
2877
3975
|
});
|
|
2878
3976
|
});
|
|
2879
3977
|
return instructions;
|
|
2880
3978
|
}
|
|
2881
|
-
function
|
|
2882
|
-
const resolved = command.optsWithGlobals();
|
|
2883
|
-
return resolved.format === "json" ? "json" : "text";
|
|
2884
|
-
}
|
|
2885
|
-
function run(opts, verb, body) {
|
|
3979
|
+
function run2(opts, verb, body) {
|
|
2886
3980
|
return __async(this, null, function* () {
|
|
2887
3981
|
try {
|
|
2888
3982
|
if (opts.hook) {
|
|
2889
|
-
yield withDeadline(
|
|
3983
|
+
yield withDeadline(HOOK_DEADLINE_MS2, (signal) => {
|
|
3984
|
+
activeDeadline = signal;
|
|
3985
|
+
return body();
|
|
3986
|
+
}).finally(() => {
|
|
3987
|
+
activeDeadline = void 0;
|
|
3988
|
+
});
|
|
2890
3989
|
} else {
|
|
2891
3990
|
yield body();
|
|
2892
3991
|
}
|
|
@@ -2905,63 +4004,10 @@ function run(opts, verb, body) {
|
|
|
2905
4004
|
}
|
|
2906
4005
|
});
|
|
2907
4006
|
}
|
|
2908
|
-
function withDeadline(totalMs, body) {
|
|
2909
|
-
return __async(this, null, function* () {
|
|
2910
|
-
const controller = new AbortController();
|
|
2911
|
-
let timer;
|
|
2912
|
-
const deadline = new Promise((_resolve, reject) => {
|
|
2913
|
-
timer = setTimeout(() => {
|
|
2914
|
-
controller.abort();
|
|
2915
|
-
reject(
|
|
2916
|
-
new Error(
|
|
2917
|
-
`gave up after ${totalMs}ms so the session is not held up`
|
|
2918
|
-
)
|
|
2919
|
-
);
|
|
2920
|
-
}, totalMs);
|
|
2921
|
-
});
|
|
2922
|
-
activeDeadline = controller.signal;
|
|
2923
|
-
try {
|
|
2924
|
-
yield Promise.race([body(), deadline]);
|
|
2925
|
-
} finally {
|
|
2926
|
-
activeDeadline = void 0;
|
|
2927
|
-
if (timer) {
|
|
2928
|
-
clearTimeout(timer);
|
|
2929
|
-
}
|
|
2930
|
-
}
|
|
2931
|
-
});
|
|
2932
|
-
}
|
|
2933
4007
|
var activeDeadline;
|
|
2934
|
-
function describeError(error) {
|
|
2935
|
-
if (error instanceof Error) {
|
|
2936
|
-
return error.message.replace(/\s*\n\s*/g, " ");
|
|
2937
|
-
}
|
|
2938
|
-
return String(error);
|
|
2939
|
-
}
|
|
2940
|
-
function asCliFailure(error) {
|
|
2941
|
-
if (error instanceof CliFailure) {
|
|
2942
|
-
return error;
|
|
2943
|
-
}
|
|
2944
|
-
const status = error.status;
|
|
2945
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2946
|
-
switch (status) {
|
|
2947
|
-
case 401:
|
|
2948
|
-
return new CliFailure(message, 3);
|
|
2949
|
-
case 403:
|
|
2950
|
-
return new CliFailure(message, 5);
|
|
2951
|
-
case 404:
|
|
2952
|
-
return new CliFailure(message, 4);
|
|
2953
|
-
case 429:
|
|
2954
|
-
return new CliFailure(message, 6);
|
|
2955
|
-
case 400:
|
|
2956
|
-
case 422:
|
|
2957
|
-
return new CliFailure(message, 7);
|
|
2958
|
-
default:
|
|
2959
|
-
return new CliFailure(message, 1);
|
|
2960
|
-
}
|
|
2961
|
-
}
|
|
2962
4008
|
function destinationOf(opts) {
|
|
2963
4009
|
var _a2;
|
|
2964
|
-
return
|
|
4010
|
+
return path8.resolve((_a2 = opts.dest) != null ? _a2 : process.cwd());
|
|
2965
4011
|
}
|
|
2966
4012
|
function instructionsClient(opts, timeoutMs, { neverRetry = false } = {}) {
|
|
2967
4013
|
if (!getApiKey()) {
|
|
@@ -2976,7 +4022,7 @@ function instructionsClient(opts, timeoutMs, { neverRetry = false } = {}) {
|
|
|
2976
4022
|
// call it makes, so it cannot spend it on retries: a
|
|
2977
4023
|
// session that will not start is worse than instructions
|
|
2978
4024
|
// one version stale.
|
|
2979
|
-
timeoutMs: Math.min(timeoutMs,
|
|
4025
|
+
timeoutMs: Math.min(timeoutMs, HOOK_DEADLINE_MS2),
|
|
2980
4026
|
retry: { maxRetries: 0 }
|
|
2981
4027
|
} : neverRetry ? { timeoutMs, retry: { maxRetries: 0 } } : { timeoutMs }
|
|
2982
4028
|
).withoutContext();
|
|
@@ -2984,7 +4030,7 @@ function instructionsClient(opts, timeoutMs, { neverRetry = false } = {}) {
|
|
|
2984
4030
|
function downloadUrlClient(opts) {
|
|
2985
4031
|
return instructionsClient(
|
|
2986
4032
|
opts,
|
|
2987
|
-
opts.hook ?
|
|
4033
|
+
opts.hook ? HOOK_DEADLINE_MS2 : BUNDLE_TIMEOUT_MS,
|
|
2988
4034
|
{ neverRetry: true }
|
|
2989
4035
|
);
|
|
2990
4036
|
}
|
|
@@ -3015,7 +4061,7 @@ function assertHookMayAct(opts, published) {
|
|
|
3015
4061
|
);
|
|
3016
4062
|
}
|
|
3017
4063
|
}
|
|
3018
|
-
function
|
|
4064
|
+
function readLockForProject2(destination, projectId) {
|
|
3019
4065
|
return __async(this, null, function* () {
|
|
3020
4066
|
const lock = yield readLock(destination);
|
|
3021
4067
|
if (lock !== null && lock.projectId !== projectId) {
|
|
@@ -3045,7 +4091,7 @@ function runCheck(opts, format) {
|
|
|
3045
4091
|
var _a2;
|
|
3046
4092
|
const destination = destinationOf(opts);
|
|
3047
4093
|
const root = yield resolveExistingRoot(destination);
|
|
3048
|
-
const lock = yield
|
|
4094
|
+
const lock = yield readLockForProject2(destination, opts.project);
|
|
3049
4095
|
const client = instructionsClient(opts, CHECK_TIMEOUT_MS);
|
|
3050
4096
|
const published = yield fetchPublished(client, opts, lock == null ? void 0 : lock.digest);
|
|
3051
4097
|
const drifted = opts.verify && lock !== null ? yield verifyLedger({ root, lock }) : [];
|
|
@@ -3154,7 +4200,7 @@ function syncOnce(opts) {
|
|
|
3154
4200
|
return __async(this, null, function* () {
|
|
3155
4201
|
const root = yield resolveDestinationRoot(destinationOf(opts));
|
|
3156
4202
|
const outcome = emptyOutcome(opts, root);
|
|
3157
|
-
const lock = yield
|
|
4203
|
+
const lock = yield readLockForProject2(root, opts.project);
|
|
3158
4204
|
const client = instructionsClient(opts, SYNC_TIMEOUT_MS);
|
|
3159
4205
|
let published = yield fetchPublished(client, opts, lock == null ? void 0 : lock.digest);
|
|
3160
4206
|
assertInitSyncMayAct(opts, published);
|
|
@@ -3208,7 +4254,7 @@ function syncOnce(opts) {
|
|
|
3208
4254
|
manifest.map((entry) => [entry.path, entry.size])
|
|
3209
4255
|
);
|
|
3210
4256
|
const archive = yield fetchBundle(download.url, {
|
|
3211
|
-
timeoutMs: opts.hook ?
|
|
4257
|
+
timeoutMs: opts.hook ? HOOK_DEADLINE_MS2 : BUNDLE_TIMEOUT_MS,
|
|
3212
4258
|
// Bounded by what this manifest says it holds, not by what the
|
|
3213
4259
|
// response claims. The manifest has already been checked against
|
|
3214
4260
|
// the published snapshot limits, so this is a number the client
|
|
@@ -3296,12 +4342,12 @@ function reportSync(outcome, opts) {
|
|
|
3296
4342
|
line(`Everything in ${outcome.destination} already matched.`);
|
|
3297
4343
|
}
|
|
3298
4344
|
}
|
|
3299
|
-
function
|
|
4345
|
+
function runPush2(opts, format) {
|
|
3300
4346
|
return __async(this, null, function* () {
|
|
3301
4347
|
var _a2;
|
|
3302
4348
|
const destination = destinationOf(opts);
|
|
3303
4349
|
const root = yield resolveExistingRoot(destination);
|
|
3304
|
-
const lock = yield
|
|
4350
|
+
const lock = yield readLockForProject2(root, opts.project);
|
|
3305
4351
|
if (lock === null) {
|
|
3306
4352
|
throw new CliFailure(
|
|
3307
4353
|
`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.`,
|
|
@@ -3310,7 +4356,7 @@ function runPush(opts, format) {
|
|
|
3310
4356
|
}
|
|
3311
4357
|
const client = instructionsClient(
|
|
3312
4358
|
__spreadProps(__spreadValues({}, opts), { hook: false }),
|
|
3313
|
-
|
|
4359
|
+
PUSH_TIMEOUT_MS2
|
|
3314
4360
|
);
|
|
3315
4361
|
const published = yield fetchPublished(client, opts);
|
|
3316
4362
|
if (!published.published || !published.snapshot) {
|
|
@@ -3347,6 +4393,7 @@ function runPush(opts, format) {
|
|
|
3347
4393
|
projectId: opts.project,
|
|
3348
4394
|
destination: root,
|
|
3349
4395
|
dryRun: Boolean(opts.dryRun),
|
|
4396
|
+
publish: Boolean(opts.publish),
|
|
3350
4397
|
baseSnapshotId: lock.snapshotId,
|
|
3351
4398
|
baseVersion: lock.snapshotVersion,
|
|
3352
4399
|
put: plan.entries.filter((entry) => entry.action === "put").map((entry) => entry.path),
|
|
@@ -3355,11 +4402,17 @@ function runPush(opts, format) {
|
|
|
3355
4402
|
snapshotId: null,
|
|
3356
4403
|
version: null,
|
|
3357
4404
|
proposalStatus: null,
|
|
3358
|
-
status: null
|
|
4405
|
+
status: null,
|
|
4406
|
+
published: null
|
|
3359
4407
|
};
|
|
3360
4408
|
if (!opts.dryRun) {
|
|
3361
4409
|
try {
|
|
3362
|
-
const submitted = yield client.instructions.
|
|
4410
|
+
const submitted = opts.publish ? yield client.instructions.publishChange(
|
|
4411
|
+
opts.project,
|
|
4412
|
+
lock.snapshotId,
|
|
4413
|
+
plan.changes,
|
|
4414
|
+
{ org: orgSlugFor(opts) }
|
|
4415
|
+
) : yield client.instructions.submitChange(
|
|
3363
4416
|
opts.project,
|
|
3364
4417
|
lock.snapshotId,
|
|
3365
4418
|
plan.changes,
|
|
@@ -3369,6 +4422,7 @@ function runPush(opts, format) {
|
|
|
3369
4422
|
outcome.version = submitted.version;
|
|
3370
4423
|
outcome.proposalStatus = submitted.proposalStatus;
|
|
3371
4424
|
outcome.status = submitted.status;
|
|
4425
|
+
outcome.published = submitted.published;
|
|
3372
4426
|
} catch (error) {
|
|
3373
4427
|
throw asPushFailure(error);
|
|
3374
4428
|
}
|
|
@@ -3408,6 +4462,16 @@ function asPushFailure(error) {
|
|
|
3408
4462
|
}
|
|
3409
4463
|
function pushVerdict(outcome) {
|
|
3410
4464
|
var _a2;
|
|
4465
|
+
if (outcome.publish) {
|
|
4466
|
+
if (outcome.published) {
|
|
4467
|
+
return `Published version ${outcome.version}. Run \`fabric instructions sync\` to bring this checkout onto it.`;
|
|
4468
|
+
}
|
|
4469
|
+
if (outcome.status === "FAILED" || outcome.status === "REJECTED") {
|
|
4470
|
+
return `Version ${outcome.version} did not pass its checks (${outcome.status}), so nothing was published; open the project's Coding Instructions tab to see why.`;
|
|
4471
|
+
}
|
|
4472
|
+
const sent = `Sent as version ${outcome.version}. It publishes on its own once its checks pass (${outcome.status}) \u2014 then run \`fabric instructions sync\` to bring this checkout onto it.`;
|
|
4473
|
+
return outcome.status === "READY" ? `${sent} It has passed its checks; its publication state is shown in the project's Coding Instructions tab.` : sent;
|
|
4474
|
+
}
|
|
3411
4475
|
if (outcome.proposalStatus === "PENDING" && outcome.status === "RECEIVING") {
|
|
3412
4476
|
return `An earlier attempt is still sending this change; if version ${outcome.version} stays unfinished, cancel it in the project's Coding Instructions tab, or leave it and it is closed out automatically after six hours.`;
|
|
3413
4477
|
}
|
|
@@ -3434,13 +4498,13 @@ function reportPush(outcome) {
|
|
|
3434
4498
|
listPaths("deleted", outcome.deleted);
|
|
3435
4499
|
if (outcome.dryRun) {
|
|
3436
4500
|
line(
|
|
3437
|
-
"Nothing was sent. Without --dry-run this would open a proposal for review."
|
|
4501
|
+
outcome.publish ? "Nothing was sent. Without --dry-run this would create a new version that publishes on its own once its checks pass, with no review." : "Nothing was sent. Without --dry-run this would open a proposal for review."
|
|
3438
4502
|
);
|
|
3439
4503
|
return;
|
|
3440
4504
|
}
|
|
3441
4505
|
line(pushVerdict(outcome));
|
|
3442
4506
|
line(
|
|
3443
|
-
"Your lock was not changed: it still names the published version, which is what `fabric instructions sync` compares against."
|
|
4507
|
+
outcome.publish ? `Your lock was not changed: it still names version ${outcome.baseVersion}, which is what \`fabric instructions sync\` compares against.` : "Your lock was not changed: it still names the published version, which is what `fabric instructions sync` compares against."
|
|
3444
4508
|
);
|
|
3445
4509
|
}
|
|
3446
4510
|
function runInit(opts, format) {
|
|
@@ -3453,7 +4517,7 @@ function runInit(opts, format) {
|
|
|
3453
4517
|
}
|
|
3454
4518
|
const tool = opts.tool;
|
|
3455
4519
|
const root = yield resolveDestinationRoot(destinationOf(opts));
|
|
3456
|
-
yield
|
|
4520
|
+
yield readLockForProject2(root, opts.project);
|
|
3457
4521
|
try {
|
|
3458
4522
|
yield assertKeyStaysOutside(root, getConfigPath());
|
|
3459
4523
|
} catch (error) {
|
|
@@ -3534,9 +4598,9 @@ function hookPathFor(tool) {
|
|
|
3534
4598
|
}
|
|
3535
4599
|
|
|
3536
4600
|
// src/commands/mcp/index.ts
|
|
3537
|
-
import { Command as
|
|
4601
|
+
import { Command as Command13 } from "commander";
|
|
3538
4602
|
function buildMcpCommand() {
|
|
3539
|
-
const mcp = new
|
|
4603
|
+
const mcp = new Command13("mcp").description(
|
|
3540
4604
|
"Manage MCP servers and configurations"
|
|
3541
4605
|
);
|
|
3542
4606
|
mcp.command("servers").description("List accessible MCP servers (system + custom)").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option(
|
|
@@ -3574,7 +4638,7 @@ function buildMcpCommand() {
|
|
|
3574
4638
|
});
|
|
3575
4639
|
})
|
|
3576
4640
|
);
|
|
3577
|
-
const configs = new
|
|
4641
|
+
const configs = new Command13("configs").description(
|
|
3578
4642
|
"Manage MCP configurations"
|
|
3579
4643
|
);
|
|
3580
4644
|
configs.command("list", { isDefault: true }).description("List your MCP configurations").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option(
|
|
@@ -3679,9 +4743,9 @@ function buildMcpCommand() {
|
|
|
3679
4743
|
}
|
|
3680
4744
|
|
|
3681
4745
|
// src/commands/orgs/index.ts
|
|
3682
|
-
import { Command as
|
|
4746
|
+
import { Command as Command14 } from "commander";
|
|
3683
4747
|
function buildOrgsCommand() {
|
|
3684
|
-
const orgs = new
|
|
4748
|
+
const orgs = new Command14("orgs").description("Manage organizations");
|
|
3685
4749
|
orgs.command("list").description("List organizations you belong to").option(
|
|
3686
4750
|
"--format <format>",
|
|
3687
4751
|
"Output format: table|json|yaml|csv",
|
|
@@ -3738,9 +4802,9 @@ function buildOrgsCommand() {
|
|
|
3738
4802
|
}
|
|
3739
4803
|
|
|
3740
4804
|
// src/commands/projects/index.ts
|
|
3741
|
-
import { Command as
|
|
4805
|
+
import { Command as Command15 } from "commander";
|
|
3742
4806
|
function buildProjectsCommand() {
|
|
3743
|
-
const projects = new
|
|
4807
|
+
const projects = new Command15("projects").description("Manage projects");
|
|
3744
4808
|
projects.command("list").description("List projects").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option(
|
|
3745
4809
|
"--status <status>",
|
|
3746
4810
|
"Filter by status: ACTIVE|DRAFT|COMPLETED|ARCHIVED"
|
|
@@ -3875,9 +4939,9 @@ function buildProjectsCommand() {
|
|
|
3875
4939
|
}
|
|
3876
4940
|
|
|
3877
4941
|
// src/commands/prompts/index.ts
|
|
3878
|
-
import { Command as
|
|
4942
|
+
import { Command as Command16 } from "commander";
|
|
3879
4943
|
function buildPromptsCommand() {
|
|
3880
|
-
const prompts = new
|
|
4944
|
+
const prompts = new Command16("prompts").description(
|
|
3881
4945
|
"Manage prompt templates"
|
|
3882
4946
|
);
|
|
3883
4947
|
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(
|
|
@@ -4030,12 +5094,12 @@ ${content}
|
|
|
4030
5094
|
}
|
|
4031
5095
|
|
|
4032
5096
|
// src/commands/reports/index.ts
|
|
4033
|
-
import { Command as
|
|
5097
|
+
import { Command as Command17 } from "commander";
|
|
4034
5098
|
function buildReportsCommand() {
|
|
4035
|
-
const reports = new
|
|
5099
|
+
const reports = new Command17("reports").description(
|
|
4036
5100
|
"Manage report templates and executions"
|
|
4037
5101
|
);
|
|
4038
|
-
const templates = new
|
|
5102
|
+
const templates = new Command17("templates").description(
|
|
4039
5103
|
"Manage report templates"
|
|
4040
5104
|
);
|
|
4041
5105
|
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(
|
|
@@ -4109,7 +5173,7 @@ function buildReportsCommand() {
|
|
|
4109
5173
|
})
|
|
4110
5174
|
);
|
|
4111
5175
|
reports.addCommand(templates);
|
|
4112
|
-
const executions = new
|
|
5176
|
+
const executions = new Command17("executions").description(
|
|
4113
5177
|
"View report executions"
|
|
4114
5178
|
);
|
|
4115
5179
|
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(
|
|
@@ -4153,9 +5217,9 @@ function buildReportsCommand() {
|
|
|
4153
5217
|
}
|
|
4154
5218
|
|
|
4155
5219
|
// src/commands/skills/index.ts
|
|
4156
|
-
import { Command as
|
|
5220
|
+
import { Command as Command18 } from "commander";
|
|
4157
5221
|
function buildSkillsCommand() {
|
|
4158
|
-
const skills = new
|
|
5222
|
+
const skills = new Command18("skills").description(
|
|
4159
5223
|
"Browse skill definitions"
|
|
4160
5224
|
);
|
|
4161
5225
|
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(
|
|
@@ -4222,9 +5286,9 @@ function buildSkillsCommand() {
|
|
|
4222
5286
|
}
|
|
4223
5287
|
|
|
4224
5288
|
// src/commands/workflows/index.ts
|
|
4225
|
-
import { Command as
|
|
5289
|
+
import { Command as Command19 } from "commander";
|
|
4226
5290
|
function buildWorkflowsCommand() {
|
|
4227
|
-
const workflows = new
|
|
5291
|
+
const workflows = new Command19("workflows").description("Manage workflows");
|
|
4228
5292
|
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(
|
|
4229
5293
|
"--format <format>",
|
|
4230
5294
|
"Output format: table|json|yaml|csv",
|
|
@@ -4352,9 +5416,9 @@ function buildWorkflowsCommand() {
|
|
|
4352
5416
|
}
|
|
4353
5417
|
|
|
4354
5418
|
// src/commands/workspaces/index.ts
|
|
4355
|
-
import { Command as
|
|
5419
|
+
import { Command as Command20 } from "commander";
|
|
4356
5420
|
function buildWorkspacesCommand() {
|
|
4357
|
-
const workspaces = new
|
|
5421
|
+
const workspaces = new Command20("workspaces").description(
|
|
4358
5422
|
"Manage workspaces"
|
|
4359
5423
|
);
|
|
4360
5424
|
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(
|
|
@@ -4430,14 +5494,14 @@ function buildWorkspacesCommand() {
|
|
|
4430
5494
|
}
|
|
4431
5495
|
|
|
4432
5496
|
// src/bin/fabric.ts
|
|
4433
|
-
var program = new
|
|
5497
|
+
var program = new Command21();
|
|
4434
5498
|
var _a;
|
|
4435
5499
|
program.name("fabric").description("Fabric AI platform CLI").version(package_default.version).option(
|
|
4436
5500
|
"--format <format>",
|
|
4437
5501
|
"Output format: table|json|yaml|csv",
|
|
4438
5502
|
(_a = process.env.FABRIC_FORMAT) != null ? _a : "table"
|
|
4439
5503
|
);
|
|
4440
|
-
var auth = new
|
|
5504
|
+
var auth = new Command21("auth").description(
|
|
4441
5505
|
"Authenticate and manage API keys"
|
|
4442
5506
|
);
|
|
4443
5507
|
auth.addCommand(buildLoginCommand());
|
|
@@ -4454,13 +5518,14 @@ program.addCommand(buildPromptsCommand());
|
|
|
4454
5518
|
program.addCommand(buildAgentsCommand());
|
|
4455
5519
|
program.addCommand(buildSkillsCommand());
|
|
4456
5520
|
program.addCommand(buildInstructionsCommand());
|
|
5521
|
+
program.addCommand(buildContextCommand());
|
|
4457
5522
|
program.addCommand(buildMcpCommand());
|
|
4458
5523
|
program.addCommand(buildFramesCommand());
|
|
4459
5524
|
program.addCommand(buildWorkspacesCommand());
|
|
4460
5525
|
program.addCommand(buildChatsCommand());
|
|
4461
5526
|
program.addCommand(buildReportsCommand());
|
|
4462
5527
|
program.addCommand(buildCompletionCommand());
|
|
4463
|
-
var config = new
|
|
5528
|
+
var config = new Command21("config").description("Show CLI configuration");
|
|
4464
5529
|
config.command("path").description("Print the path to the config file").action(() => {
|
|
4465
5530
|
process.stdout.write(`${getConfigPath()}
|
|
4466
5531
|
`);
|