@fabricorg/cli 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fabric.js +125 -15
- package/dist/fabric.js.map +1 -1
- package/dist/packages/cli/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
package/dist/fabric.js
CHANGED
|
@@ -7,7 +7,7 @@ import { Command as Command25 } from "commander";
|
|
|
7
7
|
// package.json
|
|
8
8
|
var package_default = {
|
|
9
9
|
name: "@fabricorg/cli",
|
|
10
|
-
version: "0.9.
|
|
10
|
+
version: "0.9.1",
|
|
11
11
|
description: "CLI for the Fabric AI platform",
|
|
12
12
|
keywords: [
|
|
13
13
|
"fabric",
|
|
@@ -1171,6 +1171,20 @@ var ContextsResource = class {
|
|
|
1171
1171
|
throw asConflictError(error);
|
|
1172
1172
|
}
|
|
1173
1173
|
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Which of up to 500 paths hold no synced file in the project, for example
|
|
1176
|
+
* after a Living Memory reset. Writes nothing.
|
|
1177
|
+
*
|
|
1178
|
+
* Requires a key with `projects:write`, and the key's creator must still
|
|
1179
|
+
* hold permission to read context sources on that project.
|
|
1180
|
+
*/
|
|
1181
|
+
findMissingSyncedFiles(projectId, sourcePaths, options = {}) {
|
|
1182
|
+
const query2 = options.org ? `?org=${encodeURIComponent(options.org)}` : "";
|
|
1183
|
+
return this.http.post(
|
|
1184
|
+
`/projects/${encodeURIComponent(projectId)}/contexts/synced-files/missing${query2}`,
|
|
1185
|
+
{ sourcePaths }
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1174
1188
|
/**
|
|
1175
1189
|
* Reset the project's Living Memory: delete EVERY file synced into its
|
|
1176
1190
|
* Context at once, with their search-index entries queued for removal.
|
|
@@ -6753,6 +6767,9 @@ function asCliFailure(error) {
|
|
|
6753
6767
|
}
|
|
6754
6768
|
|
|
6755
6769
|
// src/lib/missing-scope.ts
|
|
6770
|
+
function signInCanGrant(scope) {
|
|
6771
|
+
return REQUESTED_SCOPES.some((requested) => requested === scope);
|
|
6772
|
+
}
|
|
6756
6773
|
function readProperty(error, name) {
|
|
6757
6774
|
return typeof error === "object" && error !== null && name in error ? Reflect.get(error, name) : void 0;
|
|
6758
6775
|
}
|
|
@@ -6776,7 +6793,10 @@ function missingScopeOf(error) {
|
|
|
6776
6793
|
// src/lib/context-sync/failure.ts
|
|
6777
6794
|
function missingScopeSentence(scope) {
|
|
6778
6795
|
const named = scope ?? "projects:write";
|
|
6779
|
-
|
|
6796
|
+
if (signInCanGrant(named)) {
|
|
6797
|
+
return `This credential is missing the ${named} scope. Create an API key with the ${named} scope and run again, or sign in with: ${fabricCommand("auth login")}`;
|
|
6798
|
+
}
|
|
6799
|
+
return `This credential is missing the ${named} scope, which a browser sign-in does not carry. Create an API key with the ${named} scope and sign in with it: ${fabricCommand("auth login")} --key <api-key>`;
|
|
6780
6800
|
}
|
|
6781
6801
|
function contextCliFailure(error) {
|
|
6782
6802
|
if (error instanceof CliFailure) {
|
|
@@ -7462,6 +7482,32 @@ function lockProblem(value) {
|
|
|
7462
7482
|
return null;
|
|
7463
7483
|
}
|
|
7464
7484
|
|
|
7485
|
+
// src/lib/context-sync/missing-on-server.ts
|
|
7486
|
+
var PATHS_PER_REQUEST = 500;
|
|
7487
|
+
var NOTHING_MISSING = {
|
|
7488
|
+
paths: [],
|
|
7489
|
+
incomplete: false
|
|
7490
|
+
};
|
|
7491
|
+
async function findMissingOnServer(input) {
|
|
7492
|
+
const confirmed = input.unchanged.filter(
|
|
7493
|
+
(sourcePath) => input.lock?.files[sourcePath]?.state !== "duplicate"
|
|
7494
|
+
);
|
|
7495
|
+
const paths = [];
|
|
7496
|
+
try {
|
|
7497
|
+
for (let i = 0; i < confirmed.length; i += PATHS_PER_REQUEST) {
|
|
7498
|
+
const answer = await input.client.contexts.findMissingSyncedFiles(
|
|
7499
|
+
input.projectId,
|
|
7500
|
+
confirmed.slice(i, i + PATHS_PER_REQUEST),
|
|
7501
|
+
{ org: input.org }
|
|
7502
|
+
);
|
|
7503
|
+
paths.push(...answer.missing);
|
|
7504
|
+
}
|
|
7505
|
+
} catch {
|
|
7506
|
+
return { paths, incomplete: true };
|
|
7507
|
+
}
|
|
7508
|
+
return { paths, incomplete: false };
|
|
7509
|
+
}
|
|
7510
|
+
|
|
7465
7511
|
// src/lib/context-sync/classify.ts
|
|
7466
7512
|
import { createHash as createHash3 } from "crypto";
|
|
7467
7513
|
import path4 from "path";
|
|
@@ -8481,12 +8527,12 @@ function managedSkips(results) {
|
|
|
8481
8527
|
detail: r.message
|
|
8482
8528
|
}));
|
|
8483
8529
|
}
|
|
8484
|
-
function countOutcomes(plan, results) {
|
|
8530
|
+
function countOutcomes(plan, results, missingOnServer) {
|
|
8485
8531
|
const count = (status2) => results.filter((r) => r.status === status2).length;
|
|
8486
8532
|
return {
|
|
8487
8533
|
created: count("created"),
|
|
8488
8534
|
updated: count("updated"),
|
|
8489
|
-
unchanged: count("unchanged") + plan.unchangedLocal.length,
|
|
8535
|
+
unchanged: count("unchanged") + plan.unchangedLocal.length - missingOnServer.length,
|
|
8490
8536
|
duplicate: count("duplicate"),
|
|
8491
8537
|
moved: count("moved"),
|
|
8492
8538
|
moveUnsupported: count("move-unsupported"),
|
|
@@ -8609,6 +8655,7 @@ function group(lines2, label, items) {
|
|
|
8609
8655
|
}
|
|
8610
8656
|
function formatPushReport(input) {
|
|
8611
8657
|
const { plan, results } = input;
|
|
8658
|
+
const missing = input.missingOnServer ?? NOTHING_MISSING;
|
|
8612
8659
|
const prune2 = Boolean(input.prune);
|
|
8613
8660
|
const of = (status2) => results.filter(
|
|
8614
8661
|
(r) => r.status === status2 && !isMoveFallback(r)
|
|
@@ -8693,7 +8740,18 @@ function formatPushReport(input) {
|
|
|
8693
8740
|
"skipped",
|
|
8694
8741
|
[...plan.skipped, ...managedSkips(results)].map(skippedLine)
|
|
8695
8742
|
);
|
|
8696
|
-
|
|
8743
|
+
group(lines2, "missing on the server", missing.paths);
|
|
8744
|
+
if (missing.paths.length > 0) {
|
|
8745
|
+
lines2.push(
|
|
8746
|
+
`These files are in this folder's lock but no longer on the server, so nothing was sent for them: a Living Memory reset or a delete removed them. To send them again, run: ${fabricCommand(`context push --init <dir> --project ${input.projectId}`)}`
|
|
8747
|
+
);
|
|
8748
|
+
}
|
|
8749
|
+
if (missing.incomplete) {
|
|
8750
|
+
lines2.push(
|
|
8751
|
+
"Could not check whether the server still holds the files listed as unchanged, so some may be missing. Run this push again to check."
|
|
8752
|
+
);
|
|
8753
|
+
}
|
|
8754
|
+
const unchanged = countOutcomes(plan, results, missing.paths).unchanged;
|
|
8697
8755
|
if (unchanged > 0) {
|
|
8698
8756
|
lines2.push(`unchanged (${unchanged})`);
|
|
8699
8757
|
}
|
|
@@ -8764,7 +8822,13 @@ function contextPushJson(input) {
|
|
|
8764
8822
|
skipped: input.plan.skipped
|
|
8765
8823
|
},
|
|
8766
8824
|
results: input.results,
|
|
8767
|
-
|
|
8825
|
+
missingOnServer: input.missingOnServer.paths,
|
|
8826
|
+
missingCheckIncomplete: input.missingOnServer.incomplete,
|
|
8827
|
+
counts: countOutcomes(
|
|
8828
|
+
input.plan,
|
|
8829
|
+
input.results,
|
|
8830
|
+
input.missingOnServer.paths
|
|
8831
|
+
)
|
|
8768
8832
|
};
|
|
8769
8833
|
}
|
|
8770
8834
|
|
|
@@ -9636,6 +9700,13 @@ async function runPush(dir, opts, format) {
|
|
|
9636
9700
|
if (next !== null) {
|
|
9637
9701
|
await writeContextLock(root, next);
|
|
9638
9702
|
}
|
|
9703
|
+
const missingOnServer = pushed.stoppedBy === null ? await findMissingOnServer({
|
|
9704
|
+
client,
|
|
9705
|
+
projectId: opts.project,
|
|
9706
|
+
org: opts.org,
|
|
9707
|
+
lock,
|
|
9708
|
+
unchanged: plan.unchangedLocal
|
|
9709
|
+
}) : NOTHING_MISSING;
|
|
9639
9710
|
if (format === "json") {
|
|
9640
9711
|
printOutput(
|
|
9641
9712
|
jsonOutcome({
|
|
@@ -9643,7 +9714,8 @@ async function runPush(dir, opts, format) {
|
|
|
9643
9714
|
opts,
|
|
9644
9715
|
plan,
|
|
9645
9716
|
results: pushed.results,
|
|
9646
|
-
lockWritten: next !== null
|
|
9717
|
+
lockWritten: next !== null,
|
|
9718
|
+
missingOnServer
|
|
9647
9719
|
}),
|
|
9648
9720
|
{ format: "json" }
|
|
9649
9721
|
);
|
|
@@ -9655,7 +9727,8 @@ async function runPush(dir, opts, format) {
|
|
|
9655
9727
|
plan,
|
|
9656
9728
|
results: pushed.results,
|
|
9657
9729
|
prune: Boolean(opts.prune),
|
|
9658
|
-
pruneTargets: pushed.pruneTargets
|
|
9730
|
+
pruneTargets: pushed.pruneTargets,
|
|
9731
|
+
missingOnServer
|
|
9659
9732
|
})
|
|
9660
9733
|
);
|
|
9661
9734
|
}
|
|
@@ -9678,7 +9751,8 @@ function jsonOutcome(input) {
|
|
|
9678
9751
|
force: Boolean(input.opts.force),
|
|
9679
9752
|
prune: Boolean(input.opts.prune),
|
|
9680
9753
|
plan: input.plan,
|
|
9681
|
-
results: input.results
|
|
9754
|
+
results: input.results,
|
|
9755
|
+
missingOnServer: input.missingOnServer ?? NOTHING_MISSING
|
|
9682
9756
|
});
|
|
9683
9757
|
}
|
|
9684
9758
|
function pruneSummary(results, targets) {
|
|
@@ -11623,7 +11697,7 @@ var LINES = {
|
|
|
11623
11697
|
"lock-of-other-project": () => "This folder was synced from a different project, so nothing was changed. Use another --dest, or delete .fabric/instructions.lock to start over.",
|
|
11624
11698
|
"sign-in-expired": ({ origin, project }) => `Your sign-in to ${origin} has expired. Run: ${loginCommand(origin, project)}`,
|
|
11625
11699
|
forbidden: () => "You do not have access to this project's coding instructions. Ask a project maintainer for access.",
|
|
11626
|
-
"missing-scope": ({ scope, origin }) => `This credential is missing ${scope === null ? "a permission this needs" : `the ${scope} permission`}. Create a key that carries it, or run: ${loginCommand(origin)}`,
|
|
11700
|
+
"missing-scope": ({ scope, origin }) => scope !== null && !signInCanGrant(scope) ? `This credential is missing the ${scope} permission, which a browser sign-in does not carry. Create a key that carries it and sign in with it: ${loginCommand(origin)} --key <api-key>` : `This credential is missing ${scope === null ? "a permission this needs" : `the ${scope} permission`}. Create a key that carries it, or run: ${loginCommand(origin)}`,
|
|
11627
11701
|
"proposal-limit-proposer": () => "You already have five active coding-instructions proposals on this project. Wait for one to be reviewed, or withdraw one. Nothing was sent.",
|
|
11628
11702
|
"proposal-limit-project": () => "This project already has as many active coding-instructions proposals as it allows. Wait for some to be reviewed. Nothing was sent.",
|
|
11629
11703
|
"note-rejected": () => "The note was refused: it is too long or holds text that is not allowed. Change --message and push again; nothing was sent.",
|
|
@@ -19468,6 +19542,13 @@ var REBASE_STATE_PATH = `${LOCK_DIRECTORY}/rebase.json`;
|
|
|
19468
19542
|
var REBASE_STATE_VERSION = 1;
|
|
19469
19543
|
var VERSION_FILES_CHUNK = 200;
|
|
19470
19544
|
var DEFAULT_MODE2 = 33188;
|
|
19545
|
+
var BaseVersionRemovedError = class extends Error {
|
|
19546
|
+
constructor(version) {
|
|
19547
|
+
super(`Version ${version} is no longer kept.`);
|
|
19548
|
+
this.version = version;
|
|
19549
|
+
this.name = "BaseVersionRemovedError";
|
|
19550
|
+
}
|
|
19551
|
+
};
|
|
19471
19552
|
function sha256Of2(bytes) {
|
|
19472
19553
|
return createHash14("sha256").update(bytes).digest("hex");
|
|
19473
19554
|
}
|
|
@@ -19494,7 +19575,14 @@ async function readBaseRecord(input) {
|
|
|
19494
19575
|
input.lock.snapshotId,
|
|
19495
19576
|
chunk,
|
|
19496
19577
|
{ org: input.org }
|
|
19497
|
-
)
|
|
19578
|
+
).catch((error) => {
|
|
19579
|
+
if (error.status === 404) {
|
|
19580
|
+
throw new BaseVersionRemovedError(
|
|
19581
|
+
input.lock.snapshotVersion
|
|
19582
|
+
);
|
|
19583
|
+
}
|
|
19584
|
+
throw error;
|
|
19585
|
+
});
|
|
19498
19586
|
if (response.snapshotId !== input.lock.snapshotId) {
|
|
19499
19587
|
throw new Error(
|
|
19500
19588
|
"The server answered for a different version than the one this checkout last synced. Nothing was sent."
|
|
@@ -24978,7 +25066,12 @@ async function runCheck(opts, format) {
|
|
|
24978
25066
|
published
|
|
24979
25067
|
));
|
|
24980
25068
|
}
|
|
24981
|
-
const
|
|
25069
|
+
const ledgerDrift = opts.verify && lock !== null ? await findLedgerDrift({ root, lock }) : [];
|
|
25070
|
+
const isOverridden = overrideMatcher(
|
|
25071
|
+
ledgerDrift.length === 0 ? NO_OVERRIDES : await readLocalOverrides(root)
|
|
25072
|
+
);
|
|
25073
|
+
const drift = ledgerDrift.filter((entry) => !isOverridden(entry.path));
|
|
25074
|
+
const overriddenDrift = ledgerDrift.filter((entry) => isOverridden(entry.path)).map((entry) => entry.path);
|
|
24982
25075
|
if (format === "json") {
|
|
24983
25076
|
printOutput(
|
|
24984
25077
|
{
|
|
@@ -24988,6 +25081,7 @@ async function runCheck(opts, format) {
|
|
|
24988
25081
|
localDigest: lock?.digest ?? null,
|
|
24989
25082
|
verified: Boolean(opts.verify),
|
|
24990
25083
|
drifted: drift.map(describeLedgerDrift),
|
|
25084
|
+
localOverrides: overriddenDrift,
|
|
24991
25085
|
// Spec §6.4: the edits an earlier sync saw and kept.
|
|
24992
25086
|
keptEdited: drift.filter((entry) => entry.reason === "edited" && entry.kept).map((entry) => entry.path),
|
|
24993
25087
|
...published,
|
|
@@ -25009,7 +25103,7 @@ async function runCheck(opts, format) {
|
|
|
25009
25103
|
);
|
|
25010
25104
|
}
|
|
25011
25105
|
if (opts.verify) {
|
|
25012
|
-
reportDrift(opts, drift);
|
|
25106
|
+
reportDrift(opts, drift, overriddenDrift);
|
|
25013
25107
|
}
|
|
25014
25108
|
if (checkout?.line) {
|
|
25015
25109
|
line(checkout.line);
|
|
@@ -25071,11 +25165,15 @@ function describeRepositorySource(source, repository) {
|
|
|
25071
25165
|
}
|
|
25072
25166
|
return repository ? `${base} (${repository.path})` : base;
|
|
25073
25167
|
}
|
|
25074
|
-
function reportDrift(opts, drift) {
|
|
25168
|
+
function reportDrift(opts, drift, overridden) {
|
|
25075
25169
|
if (drift.length === 0) {
|
|
25076
|
-
line(
|
|
25170
|
+
line(
|
|
25171
|
+
overridden.length === 0 ? "Every file in this folder matches the lock." : "Every file in this folder matches the lock, apart from your local overrides:"
|
|
25172
|
+
);
|
|
25173
|
+
listPaths("local override", overridden);
|
|
25077
25174
|
return;
|
|
25078
25175
|
}
|
|
25176
|
+
listPaths("local override", overridden);
|
|
25079
25177
|
line(`${drift.length} local file(s) no longer match the lock:`);
|
|
25080
25178
|
listPaths("drifted", drift.map(describeLedgerDrift));
|
|
25081
25179
|
const edits = drift.filter((entry) => entry.reason === "edited").length;
|
|
@@ -25889,6 +25987,12 @@ async function rebaseOntoPublished(input) {
|
|
|
25889
25987
|
7
|
|
25890
25988
|
);
|
|
25891
25989
|
}
|
|
25990
|
+
if (error instanceof BaseVersionRemovedError) {
|
|
25991
|
+
throw new CliFailure(
|
|
25992
|
+
`Version ${error.version}, which this folder last synced, is no longer kept, so your edits cannot be merged automatically. Nothing was sent. Run \`${fabricCommand("instructions sync")}\`, which keeps your edits. A push then sends each edited file whole, so first check that those files still hold what was published since, then run \`${fabricCommand("instructions push")}\`.`,
|
|
25993
|
+
7
|
|
25994
|
+
);
|
|
25995
|
+
}
|
|
25892
25996
|
throw error;
|
|
25893
25997
|
}
|
|
25894
25998
|
}
|
|
@@ -25988,6 +26092,12 @@ function reportPush(outcome) {
|
|
|
25988
26092
|
return;
|
|
25989
26093
|
}
|
|
25990
26094
|
line(pushVerdict(outcome));
|
|
26095
|
+
if (outcome.rebasedFromVersion !== null) {
|
|
26096
|
+
line(
|
|
26097
|
+
`Your lock was not changed: it still names version ${outcome.rebasedFromVersion}. \`${fabricCommand("instructions sync")}\` moves it to the published version and keeps your merged edits.`
|
|
26098
|
+
);
|
|
26099
|
+
return;
|
|
26100
|
+
}
|
|
25991
26101
|
line(
|
|
25992
26102
|
outcome.publish ? `Your lock was not changed: it still names version ${outcome.baseVersion}, which is what \`${fabricCommand("instructions sync")}\` compares against.` : `Your lock was not changed: it still names the published version, which is what \`${fabricCommand("instructions sync")}\` compares against.`
|
|
25993
26103
|
);
|