@fabricorg/cli 0.7.4 → 0.8.0
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 +566 -31
- 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
|
@@ -6,7 +6,7 @@ import { Command as Command23 } from "commander";
|
|
|
6
6
|
// package.json
|
|
7
7
|
var package_default = {
|
|
8
8
|
name: "@fabricorg/cli",
|
|
9
|
-
version: "0.
|
|
9
|
+
version: "0.8.0",
|
|
10
10
|
description: "CLI for the Fabric AI platform",
|
|
11
11
|
keywords: [
|
|
12
12
|
"fabric",
|
|
@@ -57,7 +57,7 @@ var package_default = {
|
|
|
57
57
|
tsup: "^8.0.0",
|
|
58
58
|
tsx: "^4.21.0",
|
|
59
59
|
typescript: "5.9.3",
|
|
60
|
-
vitest: "^4.1.
|
|
60
|
+
vitest: "^4.1.12"
|
|
61
61
|
}
|
|
62
62
|
};
|
|
63
63
|
|
|
@@ -2402,7 +2402,7 @@ function getDefaultContext() {
|
|
|
2402
2402
|
return profileFor().defaultContext;
|
|
2403
2403
|
}
|
|
2404
2404
|
function getOutputFormat() {
|
|
2405
|
-
if (process.env.FABRIC_FORMAT === "json" || process.env.FABRIC_FORMAT === "yaml" || process.env.FABRIC_FORMAT === "csv") {
|
|
2405
|
+
if (process.env.FABRIC_FORMAT === "json" || process.env.FABRIC_FORMAT === "yaml" || process.env.FABRIC_FORMAT === "csv" || process.env.FABRIC_FORMAT === "plain" || process.env.FABRIC_FORMAT === "quiet") {
|
|
2406
2406
|
return process.env.FABRIC_FORMAT;
|
|
2407
2407
|
}
|
|
2408
2408
|
return getConfig().defaultFormat;
|
|
@@ -3467,8 +3467,34 @@ async function revokeOAuthSession(credentials, fetchImpl = (input, init) => fetc
|
|
|
3467
3467
|
|
|
3468
3468
|
// src/lib/output.ts
|
|
3469
3469
|
import Table from "cli-table3";
|
|
3470
|
+
var OUTPUT_FORMATS = /* @__PURE__ */ new Set([
|
|
3471
|
+
"table",
|
|
3472
|
+
"json",
|
|
3473
|
+
"yaml",
|
|
3474
|
+
"csv",
|
|
3475
|
+
"plain",
|
|
3476
|
+
"quiet"
|
|
3477
|
+
]);
|
|
3478
|
+
function resolveOutputFormat(format) {
|
|
3479
|
+
return format && OUTPUT_FORMATS.has(format) ? format : getOutputFormat();
|
|
3480
|
+
}
|
|
3481
|
+
function shouldDisableColor(env2 = process.env, isTTY = Boolean(process.stdout?.isTTY)) {
|
|
3482
|
+
if (env2.NO_COLOR !== void 0 && env2.NO_COLOR !== "") {
|
|
3483
|
+
return true;
|
|
3484
|
+
}
|
|
3485
|
+
if (env2.FABRIC_NO_COLOR === "1" || env2.FABRIC_NO_COLOR === "true") {
|
|
3486
|
+
return true;
|
|
3487
|
+
}
|
|
3488
|
+
if (env2.TERM === "dumb") {
|
|
3489
|
+
return true;
|
|
3490
|
+
}
|
|
3491
|
+
if (!isTTY) {
|
|
3492
|
+
return true;
|
|
3493
|
+
}
|
|
3494
|
+
return false;
|
|
3495
|
+
}
|
|
3470
3496
|
function printOutput(data, opts = {}) {
|
|
3471
|
-
const format = opts.format
|
|
3497
|
+
const format = resolveOutputFormat(opts.format);
|
|
3472
3498
|
if (format === "json") {
|
|
3473
3499
|
process.stdout.write(`${JSON.stringify(data, null, 2)}
|
|
3474
3500
|
`);
|
|
@@ -3491,6 +3517,20 @@ function printOutput(data, opts = {}) {
|
|
|
3491
3517
|
}
|
|
3492
3518
|
return;
|
|
3493
3519
|
}
|
|
3520
|
+
if (format === "plain") {
|
|
3521
|
+
const rows2 = Array.isArray(data) ? data : [data];
|
|
3522
|
+
if (rows2.length === 0) {
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
const cols2 = opts.columns ?? Object.keys(rows2[0]);
|
|
3526
|
+
for (const row of rows2) {
|
|
3527
|
+
process.stdout.write(
|
|
3528
|
+
`${cols2.map((c) => formatCell(row[c])).join(" ")}
|
|
3529
|
+
`
|
|
3530
|
+
);
|
|
3531
|
+
}
|
|
3532
|
+
return;
|
|
3533
|
+
}
|
|
3494
3534
|
if (format === "csv") {
|
|
3495
3535
|
const rows2 = Array.isArray(data) ? data : [data];
|
|
3496
3536
|
if (rows2.length === 0) {
|
|
@@ -3517,7 +3557,14 @@ function printOutput(data, opts = {}) {
|
|
|
3517
3557
|
return;
|
|
3518
3558
|
}
|
|
3519
3559
|
const cols = opts.columns ?? Object.keys(rows[0]);
|
|
3520
|
-
const
|
|
3560
|
+
const noColor = shouldDisableColor();
|
|
3561
|
+
const table = new Table({
|
|
3562
|
+
head: cols,
|
|
3563
|
+
style: {
|
|
3564
|
+
head: noColor ? [] : ["cyan"],
|
|
3565
|
+
border: noColor ? [] : void 0
|
|
3566
|
+
}
|
|
3567
|
+
});
|
|
3521
3568
|
for (const row of rows) {
|
|
3522
3569
|
table.push(
|
|
3523
3570
|
cols.map((c) => formatCell(row[c]))
|
|
@@ -3527,16 +3574,18 @@ function printOutput(data, opts = {}) {
|
|
|
3527
3574
|
`);
|
|
3528
3575
|
}
|
|
3529
3576
|
function printRecord(data, opts = {}) {
|
|
3530
|
-
const format = opts.format
|
|
3531
|
-
if (format === "json") {
|
|
3577
|
+
const format = resolveOutputFormat(opts.format);
|
|
3578
|
+
if (format === "json" || format === "yaml" || format === "plain") {
|
|
3532
3579
|
printOutput(data, opts);
|
|
3533
3580
|
return;
|
|
3534
3581
|
}
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3582
|
+
const noColor = shouldDisableColor();
|
|
3583
|
+
const table = new Table({
|
|
3584
|
+
style: {
|
|
3585
|
+
head: noColor ? [] : ["cyan"],
|
|
3586
|
+
border: noColor ? [] : void 0
|
|
3587
|
+
}
|
|
3588
|
+
});
|
|
3540
3589
|
for (const [key, val] of Object.entries(data)) {
|
|
3541
3590
|
table.push({ [key]: formatCell(val) });
|
|
3542
3591
|
}
|
|
@@ -4138,13 +4187,17 @@ function takeUpgradeNotice() {
|
|
|
4138
4187
|
}
|
|
4139
4188
|
|
|
4140
4189
|
// src/lib/client.ts
|
|
4141
|
-
function
|
|
4190
|
+
function resolveClientOrigin(overrides) {
|
|
4142
4191
|
const origin = normalizeOrigin(
|
|
4143
4192
|
overrides.baseUrl ?? getBaseUrl() ?? DEFAULT_ORIGIN
|
|
4144
4193
|
);
|
|
4145
4194
|
if (origin === null) {
|
|
4146
4195
|
printError(BAD_BASE_URL_LINE, 2);
|
|
4147
4196
|
}
|
|
4197
|
+
return origin;
|
|
4198
|
+
}
|
|
4199
|
+
function resolveClientAuth(overrides) {
|
|
4200
|
+
const origin = resolveClientOrigin(overrides);
|
|
4148
4201
|
const projectOAuth = overrides.project === void 0 || process.env.FABRIC_API_KEY ? void 0 : getOAuth(origin, overrides.project);
|
|
4149
4202
|
const apiKey = projectOAuth ? void 0 : hasStoredApiKey(origin) ? getApiKey(origin) : void 0;
|
|
4150
4203
|
const oauth = projectOAuth ?? (apiKey ? void 0 : getOAuth(origin));
|
|
@@ -4157,6 +4210,25 @@ or, for CI:
|
|
|
4157
4210
|
3
|
|
4158
4211
|
);
|
|
4159
4212
|
}
|
|
4213
|
+
return { origin, apiKey, oauth, projectOAuth };
|
|
4214
|
+
}
|
|
4215
|
+
function getMcpAuthenticatedFetch(overrides = {}) {
|
|
4216
|
+
const origin = resolveClientOrigin(overrides);
|
|
4217
|
+
const apiKey = hasStoredApiKey(origin) ? getApiKey(origin) : void 0;
|
|
4218
|
+
if (!apiKey) {
|
|
4219
|
+
printError(
|
|
4220
|
+
"Browser sign-in tokens are not valid for the MCP Gateway. Set FABRIC_API_KEY or run fabric auth login --key <api-key> before using fabric mcp tools or fabric mcp call.",
|
|
4221
|
+
3
|
|
4222
|
+
);
|
|
4223
|
+
}
|
|
4224
|
+
return {
|
|
4225
|
+
origin,
|
|
4226
|
+
fetch: globalThis.fetch,
|
|
4227
|
+
authorizationHeader: `Bearer ${apiKey}`
|
|
4228
|
+
};
|
|
4229
|
+
}
|
|
4230
|
+
function getClient(overrides = {}) {
|
|
4231
|
+
const { origin, apiKey, oauth, projectOAuth } = resolveClientAuth(overrides);
|
|
4160
4232
|
try {
|
|
4161
4233
|
return new FabricClient({
|
|
4162
4234
|
// A browser sign-in's token is refreshed per request by the fetch
|
|
@@ -4228,11 +4300,7 @@ function resolveContext(personal, org, required = true) {
|
|
|
4228
4300
|
// src/commands/agents/index.ts
|
|
4229
4301
|
function buildAgentsCommand() {
|
|
4230
4302
|
const agents = new Command("agents").description("Manage agents");
|
|
4231
|
-
agents.command("list").description("List agents").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--status <status>", "Filter: ACTIVE|INACTIVE|DEPRECATED").option("--limit <n>", "Max results", "50").option(
|
|
4232
|
-
"--format <format>",
|
|
4233
|
-
"Output format: table|json|yaml|csv",
|
|
4234
|
-
"table"
|
|
4235
|
-
).option("-q, --quiet", "Print IDs only").action(
|
|
4303
|
+
agents.command("list").description("List agents").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--status <status>", "Filter: ACTIVE|INACTIVE|DEPRECATED").option("--limit <n>", "Max results", "50").option("--format <format>", "Output format: table|json|yaml|csv").option("-q, --quiet", "Print IDs only").action(
|
|
4236
4304
|
async (opts) => {
|
|
4237
4305
|
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
4238
4306
|
const client = getClient();
|
|
@@ -4266,7 +4334,7 @@ function buildAgentsCommand() {
|
|
|
4266
4334
|
});
|
|
4267
4335
|
}
|
|
4268
4336
|
);
|
|
4269
|
-
agents.command("show <id>").description("Show agent details").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--format <format>", "Output format: table|json|yaml"
|
|
4337
|
+
agents.command("show <id>").description("Show agent details").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--format <format>", "Output format: table|json|yaml").action(
|
|
4270
4338
|
async (id, opts) => {
|
|
4271
4339
|
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
4272
4340
|
const client = getClient();
|
|
@@ -4278,7 +4346,7 @@ function buildAgentsCommand() {
|
|
|
4278
4346
|
} catch (err) {
|
|
4279
4347
|
printError(err.message, 1);
|
|
4280
4348
|
}
|
|
4281
|
-
const fmt = opts.format;
|
|
4349
|
+
const fmt = resolveOutputFormat(opts.format);
|
|
4282
4350
|
if (fmt === "json" || fmt === "yaml") {
|
|
4283
4351
|
printOutput(agent, { format: fmt });
|
|
4284
4352
|
return;
|
|
@@ -4295,7 +4363,7 @@ function buildAgentsCommand() {
|
|
|
4295
4363
|
});
|
|
4296
4364
|
}
|
|
4297
4365
|
);
|
|
4298
|
-
agents.command("execute <id>").description("Execute an agent").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--input <json>", `Input JSON (e.g. '{"key":"value"}')`).option("--format <format>", "Output format: table|json|yaml"
|
|
4366
|
+
agents.command("execute <id>").description("Execute an agent").option("--org <slug>", "Organization context").option("--personal", "Retired \u2014 context is organization-only").option("--input <json>", `Input JSON (e.g. '{"key":"value"}')`).option("--format <format>", "Output format: table|json|yaml").action(
|
|
4299
4367
|
async (id, opts) => {
|
|
4300
4368
|
const ctx = resolveContext(opts.personal, opts.org, false);
|
|
4301
4369
|
const client = getClient();
|
|
@@ -4316,7 +4384,7 @@ function buildAgentsCommand() {
|
|
|
4316
4384
|
} catch (err) {
|
|
4317
4385
|
printError(err.message, 1);
|
|
4318
4386
|
}
|
|
4319
|
-
const fmt = opts.format;
|
|
4387
|
+
const fmt = resolveOutputFormat(opts.format);
|
|
4320
4388
|
if (fmt === "json" || fmt === "yaml") {
|
|
4321
4389
|
printOutput(result, { format: fmt });
|
|
4322
4390
|
return;
|
|
@@ -4330,6 +4398,209 @@ function buildAgentsCommand() {
|
|
|
4330
4398
|
});
|
|
4331
4399
|
}
|
|
4332
4400
|
);
|
|
4401
|
+
agents.command("delegate").description(
|
|
4402
|
+
"Delegate a coding task to Fabric Desktop (fabricagents://) or Remote Fabric Agents Server using subscription harnesses"
|
|
4403
|
+
).option("--org <slug>", "Organization context").option("--project <id>", "Project ID to bind the delegated session to").option("--project-slug <slug>", "Project slug for workspace routing").option(
|
|
4404
|
+
"--target <target>",
|
|
4405
|
+
"Execution target: desktop|remote",
|
|
4406
|
+
"remote"
|
|
4407
|
+
).option(
|
|
4408
|
+
"--server-url <url>",
|
|
4409
|
+
"Remote Fabric Agents Server URL",
|
|
4410
|
+
process.env.FABRIC_AGENTS_SERVER_URL ?? "https://agents.example.com"
|
|
4411
|
+
).option(
|
|
4412
|
+
"--harness <harness>",
|
|
4413
|
+
"Subscription harness: auto|claude-max|codex|droid-cli|grok-cli|kimi-cli|antigravity-cli|cursor",
|
|
4414
|
+
"claude-max"
|
|
4415
|
+
).option("--model <model>", "Optional model override").option(
|
|
4416
|
+
"--fallback <policy>",
|
|
4417
|
+
"Quota fallback policy: subscription_harnesses|org_paid_api|pause_on_limit",
|
|
4418
|
+
"subscription_harnesses"
|
|
4419
|
+
).option("--prompt <text>", "Task prompt to dispatch").option(
|
|
4420
|
+
"--durable",
|
|
4421
|
+
"Request durable handling when the generated link is opened"
|
|
4422
|
+
).option("--format <format>", "Output format: table|json|yaml|plain").option("-q, --quiet", "Print delegation URL only").addHelpText(
|
|
4423
|
+
"after",
|
|
4424
|
+
`
|
|
4425
|
+
Examples:
|
|
4426
|
+
$ fabric agents delegate --target remote --harness claude-max --prompt "Refactor auth middleware"
|
|
4427
|
+
$ fabric agents delegate --target desktop --project-slug payments-v2 --prompt "Add unit tests" -q
|
|
4428
|
+
$ fabric agents delegate --target remote --harness codex --fallback org_paid_api --format json
|
|
4429
|
+
`
|
|
4430
|
+
).action(
|
|
4431
|
+
async (opts) => {
|
|
4432
|
+
const target = opts.target === "desktop" ? "desktop" : "remote";
|
|
4433
|
+
const query = new URLSearchParams();
|
|
4434
|
+
if (opts.projectSlug) {
|
|
4435
|
+
query.set("workspace", opts.projectSlug);
|
|
4436
|
+
query.set("project", opts.projectSlug);
|
|
4437
|
+
}
|
|
4438
|
+
if (opts.project) {
|
|
4439
|
+
query.set("projectId", opts.project);
|
|
4440
|
+
}
|
|
4441
|
+
query.set("harness", opts.harness);
|
|
4442
|
+
if (opts.model) {
|
|
4443
|
+
query.set("model", opts.model);
|
|
4444
|
+
}
|
|
4445
|
+
query.set("fallback", opts.fallback);
|
|
4446
|
+
if (opts.prompt) {
|
|
4447
|
+
query.set("input", opts.prompt);
|
|
4448
|
+
query.set("prompt", opts.prompt);
|
|
4449
|
+
query.set("send", "true");
|
|
4450
|
+
}
|
|
4451
|
+
if (opts.durable) {
|
|
4452
|
+
query.set("durable", "true");
|
|
4453
|
+
}
|
|
4454
|
+
const desktopDeepLink = `fabricagents://action/new-chat?${query.toString()}`;
|
|
4455
|
+
const cleanServer = opts.serverUrl.replace(/\/+$/, "");
|
|
4456
|
+
const remoteDelegationUrl = `${cleanServer}/?${query.toString()}`;
|
|
4457
|
+
const activeUrl = target === "desktop" ? desktopDeepLink : remoteDelegationUrl;
|
|
4458
|
+
if (opts.quiet) {
|
|
4459
|
+
process.stdout.write(`${activeUrl}
|
|
4460
|
+
`);
|
|
4461
|
+
return;
|
|
4462
|
+
}
|
|
4463
|
+
const payload = {
|
|
4464
|
+
status: "link_created",
|
|
4465
|
+
message: "A link was created locally. No task was dispatched by this command.",
|
|
4466
|
+
target,
|
|
4467
|
+
harness: opts.harness,
|
|
4468
|
+
model: opts.model ?? "default",
|
|
4469
|
+
fallbackPolicy: opts.fallback,
|
|
4470
|
+
billingMode: "subscription_tokens",
|
|
4471
|
+
durable: Boolean(opts.durable),
|
|
4472
|
+
projectId: opts.project ?? null,
|
|
4473
|
+
projectSlug: opts.projectSlug ?? null,
|
|
4474
|
+
delegationUrl: activeUrl,
|
|
4475
|
+
desktopDeepLink,
|
|
4476
|
+
remoteDelegationUrl
|
|
4477
|
+
};
|
|
4478
|
+
const fmt = resolveOutputFormat(opts.format);
|
|
4479
|
+
if (fmt === "json" || fmt === "yaml" || fmt === "plain") {
|
|
4480
|
+
printOutput(payload, { format: fmt });
|
|
4481
|
+
return;
|
|
4482
|
+
}
|
|
4483
|
+
printRecord({
|
|
4484
|
+
Status: payload.status,
|
|
4485
|
+
Target: target === "desktop" ? "Fabric Desktop (fabricagents://)" : `Remote Fabric Agents Server (${cleanServer})`,
|
|
4486
|
+
Harness: payload.harness,
|
|
4487
|
+
Model: payload.model,
|
|
4488
|
+
"Fallback Policy": payload.fallbackPolicy,
|
|
4489
|
+
"Billing Mode": "Subscription Credits (0 API Key Spend)",
|
|
4490
|
+
Durable: payload.durable ? "requested in link" : "no",
|
|
4491
|
+
"Delegation URL": payload.delegationUrl
|
|
4492
|
+
});
|
|
4493
|
+
}
|
|
4494
|
+
);
|
|
4495
|
+
agents.command("desktop").description(
|
|
4496
|
+
"Generate a 1-click Fabric Desktop (fabricagents://) deep link for a project or view"
|
|
4497
|
+
).option("--project <id>", "Project ID").option("--project-slug <slug>", "Project slug").option(
|
|
4498
|
+
"--view <view>",
|
|
4499
|
+
"Target view: allSessions|settings|sources|agents|skills",
|
|
4500
|
+
"allSessions"
|
|
4501
|
+
).option("--prompt <text>", "Pre-filled prompt for a new chat").option("--format <format>", "Output format: table|json|yaml|plain").option("-q, --quiet", "Print deep link URL only").action(
|
|
4502
|
+
async (opts) => {
|
|
4503
|
+
const query = new URLSearchParams();
|
|
4504
|
+
if (opts.projectSlug) {
|
|
4505
|
+
query.set("workspace", opts.projectSlug);
|
|
4506
|
+
}
|
|
4507
|
+
if (opts.project) {
|
|
4508
|
+
query.set("projectId", opts.project);
|
|
4509
|
+
}
|
|
4510
|
+
if (opts.prompt) {
|
|
4511
|
+
query.set("input", opts.prompt);
|
|
4512
|
+
query.set("send", "true");
|
|
4513
|
+
}
|
|
4514
|
+
const path42 = opts.prompt ? "action/new-chat" : opts.view || "allSessions";
|
|
4515
|
+
const qs = query.toString();
|
|
4516
|
+
const deepLink = `fabricagents://${path42}${qs ? `?${qs}` : ""}`;
|
|
4517
|
+
if (opts.quiet) {
|
|
4518
|
+
process.stdout.write(`${deepLink}
|
|
4519
|
+
`);
|
|
4520
|
+
return;
|
|
4521
|
+
}
|
|
4522
|
+
const payload = {
|
|
4523
|
+
deepLink,
|
|
4524
|
+
view: opts.view,
|
|
4525
|
+
projectId: opts.project ?? null,
|
|
4526
|
+
projectSlug: opts.projectSlug ?? null
|
|
4527
|
+
};
|
|
4528
|
+
const fmt = resolveOutputFormat(opts.format);
|
|
4529
|
+
if (fmt === "json" || fmt === "yaml" || fmt === "plain") {
|
|
4530
|
+
printOutput(payload, { format: fmt });
|
|
4531
|
+
return;
|
|
4532
|
+
}
|
|
4533
|
+
printRecord({
|
|
4534
|
+
"Deep Link": deepLink,
|
|
4535
|
+
View: opts.view,
|
|
4536
|
+
Project: opts.projectSlug ?? opts.project ?? "(workspace)"
|
|
4537
|
+
});
|
|
4538
|
+
}
|
|
4539
|
+
);
|
|
4540
|
+
agents.command("subscriptions").description(
|
|
4541
|
+
"List known Remote Fabric Agents Server harness choices without checking availability"
|
|
4542
|
+
).option(
|
|
4543
|
+
"--server-url <url>",
|
|
4544
|
+
"Remote Fabric Agents Server URL",
|
|
4545
|
+
process.env.FABRIC_AGENTS_SERVER_URL ?? "https://agents.example.com"
|
|
4546
|
+
).option("--format <format>", "Output format: table|json|yaml|plain").option("-q, --quiet", "Print harness IDs only").action(
|
|
4547
|
+
async (opts) => {
|
|
4548
|
+
const harnesses = [
|
|
4549
|
+
{
|
|
4550
|
+
id: "claude-max",
|
|
4551
|
+
name: "Claude (Opus 4.6 / Sonnet 4.6)",
|
|
4552
|
+
billing: "subscription_oauth",
|
|
4553
|
+
credits: "not_checked",
|
|
4554
|
+
fallbackOrder: 1
|
|
4555
|
+
},
|
|
4556
|
+
{
|
|
4557
|
+
id: "codex",
|
|
4558
|
+
name: "Codex (GPT-5.4 / o3)",
|
|
4559
|
+
billing: "subscription_oauth",
|
|
4560
|
+
credits: "not_checked",
|
|
4561
|
+
fallbackOrder: 2
|
|
4562
|
+
},
|
|
4563
|
+
{
|
|
4564
|
+
id: "droid-cli",
|
|
4565
|
+
name: "Droid (Factory Multi-Model)",
|
|
4566
|
+
billing: "subscription_oauth",
|
|
4567
|
+
credits: "not_checked",
|
|
4568
|
+
fallbackOrder: 3
|
|
4569
|
+
},
|
|
4570
|
+
{
|
|
4571
|
+
id: "grok-cli",
|
|
4572
|
+
name: "Grok (Grok 4 Heavy / Fast)",
|
|
4573
|
+
billing: "subscription_oauth",
|
|
4574
|
+
credits: "not_checked",
|
|
4575
|
+
fallbackOrder: 4
|
|
4576
|
+
},
|
|
4577
|
+
{
|
|
4578
|
+
id: "kimi-cli",
|
|
4579
|
+
name: "Kimi (Kimi K2.5 Thinking)",
|
|
4580
|
+
billing: "subscription_oauth",
|
|
4581
|
+
credits: "not_checked",
|
|
4582
|
+
fallbackOrder: 5
|
|
4583
|
+
}
|
|
4584
|
+
];
|
|
4585
|
+
if (opts.quiet) {
|
|
4586
|
+
for (const h of harnesses) {
|
|
4587
|
+
process.stdout.write(`${h.id}
|
|
4588
|
+
`);
|
|
4589
|
+
}
|
|
4590
|
+
return;
|
|
4591
|
+
}
|
|
4592
|
+
printOutput(harnesses, {
|
|
4593
|
+
format: opts.format,
|
|
4594
|
+
columns: [
|
|
4595
|
+
"id",
|
|
4596
|
+
"name",
|
|
4597
|
+
"billing",
|
|
4598
|
+
"credits",
|
|
4599
|
+
"fallbackOrder"
|
|
4600
|
+
]
|
|
4601
|
+
});
|
|
4602
|
+
}
|
|
4603
|
+
);
|
|
4333
4604
|
return agents;
|
|
4334
4605
|
}
|
|
4335
4606
|
|
|
@@ -8497,7 +8768,7 @@ function exitReason(result) {
|
|
|
8497
8768
|
if (/must be run in a work tree/i.test(text3)) {
|
|
8498
8769
|
return "not a working tree (a bare repository, or inside .git)";
|
|
8499
8770
|
}
|
|
8500
|
-
if (/not a git repository/i.test(text3)) {
|
|
8771
|
+
if (/not a git repository/i.test(text3) || /does not point to a valid repository/i.test(text3)) {
|
|
8501
8772
|
return "its .git points to a repository that does not exist";
|
|
8502
8773
|
}
|
|
8503
8774
|
if (/permission denied/i.test(text3)) {
|
|
@@ -10754,7 +11025,6 @@ function registrationOf(servers, name) {
|
|
|
10754
11025
|
// src/lib/instructions/hook.ts
|
|
10755
11026
|
import { createHash as createHash4 } from "crypto";
|
|
10756
11027
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
10757
|
-
import { tmpdir } from "os";
|
|
10758
11028
|
import path12 from "path";
|
|
10759
11029
|
|
|
10760
11030
|
// src/lib/instructions/identifiers.ts
|
|
@@ -10785,8 +11055,8 @@ function subcommandsForEvent(event) {
|
|
|
10785
11055
|
return event === "Stop" ? STOP_SUBCOMMANDS : SESSION_START_SUBCOMMANDS;
|
|
10786
11056
|
}
|
|
10787
11057
|
async function withHookConfigLock(root, posixPath, work) {
|
|
10788
|
-
const directory = path12.join(
|
|
10789
|
-
await mkdir3(directory, { recursive: true });
|
|
11058
|
+
const directory = path12.join(path12.dirname(getConfigPath()), "hook-locks");
|
|
11059
|
+
await mkdir3(directory, { recursive: true, mode: 448 });
|
|
10790
11060
|
const identity = createHash4("sha256").update(path12.resolve(root)).update("\0").update(posixPath).digest("hex");
|
|
10791
11061
|
const lockPath2 = path12.join(directory, `${identity}.lock`);
|
|
10792
11062
|
try {
|
|
@@ -12853,12 +13123,12 @@ function isTooLarge(error) {
|
|
|
12853
13123
|
// src/lib/instructions/staged-files.ts
|
|
12854
13124
|
import { createHash as createHash8 } from "crypto";
|
|
12855
13125
|
import { mkdtemp, realpath as realpath4, rm } from "fs/promises";
|
|
12856
|
-
import { tmpdir
|
|
13126
|
+
import { tmpdir } from "os";
|
|
12857
13127
|
import path16 from "path";
|
|
12858
13128
|
var IN_MEMORY_SNAPSHOT_BYTES = 50 * 1024 * 1024;
|
|
12859
13129
|
async function stageFilesByUrl(options) {
|
|
12860
13130
|
const root = await realpath4(
|
|
12861
|
-
await mkdtemp(path16.join(
|
|
13131
|
+
await mkdtemp(path16.join(tmpdir(), "fabric-instructions-"))
|
|
12862
13132
|
);
|
|
12863
13133
|
const files = /* @__PURE__ */ new Map();
|
|
12864
13134
|
const dispose = () => rm(root, { recursive: true, force: true });
|
|
@@ -22526,6 +22796,25 @@ async function runLessonPromptCommand(opts) {
|
|
|
22526
22796
|
|
|
22527
22797
|
// src/commands/mcp/index.ts
|
|
22528
22798
|
import { Command as Command14 } from "commander";
|
|
22799
|
+
async function resolveMcpOrganizationId(organization, client) {
|
|
22800
|
+
if (!organization) {
|
|
22801
|
+
return void 0;
|
|
22802
|
+
}
|
|
22803
|
+
const organizationClient = client ?? getClient();
|
|
22804
|
+
let organizations;
|
|
22805
|
+
try {
|
|
22806
|
+
organizations = await organizationClient.orgs.list();
|
|
22807
|
+
} catch (error) {
|
|
22808
|
+
printError(error.message, 1);
|
|
22809
|
+
}
|
|
22810
|
+
const match = organizations.find(
|
|
22811
|
+
(candidate) => candidate.id === organization || candidate.slug === organization
|
|
22812
|
+
);
|
|
22813
|
+
if (!match) {
|
|
22814
|
+
printError(`Organization not found: ${organization}`, 4);
|
|
22815
|
+
}
|
|
22816
|
+
return match.id;
|
|
22817
|
+
}
|
|
22529
22818
|
function buildMcpCommand() {
|
|
22530
22819
|
const mcp = new Command14("mcp").description(
|
|
22531
22820
|
"Manage MCP servers and configurations"
|
|
@@ -22665,6 +22954,219 @@ function buildMcpCommand() {
|
|
|
22665
22954
|
const { runStdio } = await import("@fabricorg/sdk-mcp/stdio");
|
|
22666
22955
|
await runStdio();
|
|
22667
22956
|
});
|
|
22957
|
+
mcp.command("tools").description(
|
|
22958
|
+
"List MCP tools exposed by the Fabric MCP Gateway (supports stateless MCP mode)"
|
|
22959
|
+
).option("--org <slug>", "Organization ID or slug").option("--project <id>", "Project ID to scope the gateway connection").option("--mode <mode>", "Tool list mode: full|discovery", "full").option(
|
|
22960
|
+
"--stateless",
|
|
22961
|
+
"Send stateless MCP JSON-RPC request (X-Mcp-Stateless: true)",
|
|
22962
|
+
true
|
|
22963
|
+
).option(
|
|
22964
|
+
"--no-stateless",
|
|
22965
|
+
"Use a persistent MCP session instead of stateless mode"
|
|
22966
|
+
).option("--format <format>", "Output format: table|json|yaml|plain").option("-q, --quiet", "Print tool names only").action(
|
|
22967
|
+
async (opts) => {
|
|
22968
|
+
const auth2 = getMcpAuthenticatedFetch({
|
|
22969
|
+
project: opts.project
|
|
22970
|
+
});
|
|
22971
|
+
const organizationId = await resolveMcpOrganizationId(opts.org);
|
|
22972
|
+
const base = auth2.origin.replace(/\/+$/, "");
|
|
22973
|
+
const path42 = opts.project ? `/api/mcp-gateway/projects/${encodeURIComponent(opts.project)}` : "/api/mcp-gateway";
|
|
22974
|
+
const url = new URL(`${base}${path42}`);
|
|
22975
|
+
if (opts.mode === "discovery") {
|
|
22976
|
+
url.searchParams.set("mode", "discovery");
|
|
22977
|
+
}
|
|
22978
|
+
if (opts.stateless) {
|
|
22979
|
+
url.searchParams.set("stateless", "1");
|
|
22980
|
+
}
|
|
22981
|
+
const headers = {
|
|
22982
|
+
"Content-Type": "application/json",
|
|
22983
|
+
Accept: "application/json"
|
|
22984
|
+
};
|
|
22985
|
+
headers.Authorization = auth2.authorizationHeader;
|
|
22986
|
+
if (organizationId) {
|
|
22987
|
+
headers["X-Organization-Id"] = organizationId;
|
|
22988
|
+
}
|
|
22989
|
+
if (opts.stateless) {
|
|
22990
|
+
headers["X-Mcp-Stateless"] = "true";
|
|
22991
|
+
}
|
|
22992
|
+
let res;
|
|
22993
|
+
try {
|
|
22994
|
+
res = await auth2.fetch(url.toString(), {
|
|
22995
|
+
method: "POST",
|
|
22996
|
+
headers,
|
|
22997
|
+
body: JSON.stringify({
|
|
22998
|
+
jsonrpc: "2.0",
|
|
22999
|
+
id: 1,
|
|
23000
|
+
method: "tools/list",
|
|
23001
|
+
params: {}
|
|
23002
|
+
})
|
|
23003
|
+
});
|
|
23004
|
+
} catch (err) {
|
|
23005
|
+
printError(err.message, 1);
|
|
23006
|
+
}
|
|
23007
|
+
const payload = await res.json();
|
|
23008
|
+
if (!res.ok || payload.error) {
|
|
23009
|
+
printError(
|
|
23010
|
+
payload.error?.message ?? `MCP Gateway returned HTTP ${res.status}`,
|
|
23011
|
+
1
|
|
23012
|
+
);
|
|
23013
|
+
}
|
|
23014
|
+
const tools = (payload.result?.tools ?? []).map((t) => ({
|
|
23015
|
+
name: t.name,
|
|
23016
|
+
uiApp: t._meta?.ui?.resourceUri ?? "",
|
|
23017
|
+
description: (t.description ?? "").slice(0, 72)
|
|
23018
|
+
}));
|
|
23019
|
+
if (opts.quiet) {
|
|
23020
|
+
for (const t of tools) {
|
|
23021
|
+
process.stdout.write(`${t.name}
|
|
23022
|
+
`);
|
|
23023
|
+
}
|
|
23024
|
+
return;
|
|
23025
|
+
}
|
|
23026
|
+
printOutput(tools, {
|
|
23027
|
+
format: opts.format,
|
|
23028
|
+
columns: ["name", "uiApp", "description"]
|
|
23029
|
+
});
|
|
23030
|
+
}
|
|
23031
|
+
);
|
|
23032
|
+
mcp.command("call <tool>").description(
|
|
23033
|
+
"Execute a Fabric MCP tool over Streamable HTTP with optional 24-hour idempotent durable calls"
|
|
23034
|
+
).option("--org <slug>", "Organization ID or slug").option("--project <id>", "Project ID to scope the gateway connection").option("--input <json>", "Tool arguments as JSON object", "{}").option(
|
|
23035
|
+
"--stateless",
|
|
23036
|
+
"Execute without persisting server session (X-Mcp-Stateless: true)",
|
|
23037
|
+
true
|
|
23038
|
+
).option(
|
|
23039
|
+
"--no-stateless",
|
|
23040
|
+
"Use a persistent MCP session instead of stateless mode"
|
|
23041
|
+
).option(
|
|
23042
|
+
"--durable",
|
|
23043
|
+
"Use the Fabric idempotency store (X-Mcp-Durable: true)"
|
|
23044
|
+
).option(
|
|
23045
|
+
"--idempotency-key <key>",
|
|
23046
|
+
"Explicit key for 24-hour durable-call deduplication"
|
|
23047
|
+
).option("--format <format>", "Output format: table|json|yaml|plain").action(
|
|
23048
|
+
async (tool, opts) => {
|
|
23049
|
+
let toolArgs = {};
|
|
23050
|
+
try {
|
|
23051
|
+
toolArgs = JSON.parse(opts.input);
|
|
23052
|
+
} catch {
|
|
23053
|
+
printError("--input must be valid JSON", 2);
|
|
23054
|
+
}
|
|
23055
|
+
const auth2 = getMcpAuthenticatedFetch({
|
|
23056
|
+
project: opts.project
|
|
23057
|
+
});
|
|
23058
|
+
const organizationId = await resolveMcpOrganizationId(opts.org);
|
|
23059
|
+
const base = auth2.origin.replace(/\/+$/, "");
|
|
23060
|
+
const path42 = opts.project ? `/api/mcp-gateway/projects/${encodeURIComponent(opts.project)}` : "/api/mcp-gateway";
|
|
23061
|
+
const url = new URL(`${base}${path42}`);
|
|
23062
|
+
if (opts.stateless) {
|
|
23063
|
+
url.searchParams.set("stateless", "1");
|
|
23064
|
+
}
|
|
23065
|
+
const headers = {
|
|
23066
|
+
"Content-Type": "application/json",
|
|
23067
|
+
Accept: "application/json"
|
|
23068
|
+
};
|
|
23069
|
+
headers.Authorization = auth2.authorizationHeader;
|
|
23070
|
+
if (organizationId) {
|
|
23071
|
+
headers["X-Organization-Id"] = organizationId;
|
|
23072
|
+
}
|
|
23073
|
+
if (opts.stateless) {
|
|
23074
|
+
headers["X-Mcp-Stateless"] = "true";
|
|
23075
|
+
}
|
|
23076
|
+
if (opts.durable) {
|
|
23077
|
+
headers["X-Mcp-Durable"] = "true";
|
|
23078
|
+
}
|
|
23079
|
+
if (opts.idempotencyKey) {
|
|
23080
|
+
headers["X-Mcp-Idempotency-Key"] = opts.idempotencyKey;
|
|
23081
|
+
}
|
|
23082
|
+
let res;
|
|
23083
|
+
try {
|
|
23084
|
+
res = await auth2.fetch(url.toString(), {
|
|
23085
|
+
method: "POST",
|
|
23086
|
+
headers,
|
|
23087
|
+
body: JSON.stringify({
|
|
23088
|
+
jsonrpc: "2.0",
|
|
23089
|
+
id: 1,
|
|
23090
|
+
method: "tools/call",
|
|
23091
|
+
params: {
|
|
23092
|
+
name: tool,
|
|
23093
|
+
arguments: toolArgs,
|
|
23094
|
+
...opts.durable || opts.idempotencyKey ? {
|
|
23095
|
+
_meta: {
|
|
23096
|
+
durable: Boolean(opts.durable),
|
|
23097
|
+
...opts.idempotencyKey ? {
|
|
23098
|
+
idempotencyKey: opts.idempotencyKey
|
|
23099
|
+
} : {}
|
|
23100
|
+
}
|
|
23101
|
+
} : {}
|
|
23102
|
+
}
|
|
23103
|
+
})
|
|
23104
|
+
});
|
|
23105
|
+
} catch (err) {
|
|
23106
|
+
printError(err.message, 1);
|
|
23107
|
+
}
|
|
23108
|
+
const payload = await res.json();
|
|
23109
|
+
if (!res.ok || payload.error) {
|
|
23110
|
+
printError(
|
|
23111
|
+
payload.error?.message ?? `MCP tool call failed with HTTP ${res.status}`,
|
|
23112
|
+
1
|
|
23113
|
+
);
|
|
23114
|
+
}
|
|
23115
|
+
printOutput(payload.result ?? payload, {
|
|
23116
|
+
format: opts.format
|
|
23117
|
+
});
|
|
23118
|
+
}
|
|
23119
|
+
);
|
|
23120
|
+
mcp.command("apps").description(
|
|
23121
|
+
"List or inspect built-in MCP Apps (ui://fabric/*) for native rendering in Fabric Desktop"
|
|
23122
|
+
).option("--uri <uri>", "Read a specific ui://fabric/* MCP App resource").option("--format <format>", "Output format: table|json|yaml|plain").option("-q, --quiet", "Print MCP App URIs only").action(
|
|
23123
|
+
async (opts) => {
|
|
23124
|
+
const apps = [
|
|
23125
|
+
{
|
|
23126
|
+
uri: "ui://fabric/project-dashboard",
|
|
23127
|
+
name: "Fabric Project Overview & Desktop Launcher",
|
|
23128
|
+
mimeType: "text/html;profile=mcp-app",
|
|
23129
|
+
linkedTool: "fabric_get_project"
|
|
23130
|
+
},
|
|
23131
|
+
{
|
|
23132
|
+
uri: "ui://fabric/agent-delegation",
|
|
23133
|
+
name: "Fabric Agents Delegation & Subscription Credits",
|
|
23134
|
+
mimeType: "text/html;profile=mcp-app",
|
|
23135
|
+
linkedTool: "fabric_list_features"
|
|
23136
|
+
},
|
|
23137
|
+
{
|
|
23138
|
+
uri: "ui://fabric/workflow-visualizer",
|
|
23139
|
+
name: "Fabric Visual Data Prep & Durable Workflow Inspector",
|
|
23140
|
+
mimeType: "text/html;profile=mcp-app",
|
|
23141
|
+
linkedTool: "fabric_execute_workflow"
|
|
23142
|
+
},
|
|
23143
|
+
{
|
|
23144
|
+
uri: "ui://fabric/genie-insights",
|
|
23145
|
+
name: "Fabric Genie Agent & Lakebase Query Explorer",
|
|
23146
|
+
mimeType: "text/html;profile=mcp-app",
|
|
23147
|
+
linkedTool: "fabric_query_workspace"
|
|
23148
|
+
},
|
|
23149
|
+
{
|
|
23150
|
+
uri: "ui://fabric/visualize",
|
|
23151
|
+
name: "Fabric Interactive A2UI Card Composer",
|
|
23152
|
+
mimeType: "text/html;profile=mcp-app",
|
|
23153
|
+
linkedTool: "fabric_visualize"
|
|
23154
|
+
}
|
|
23155
|
+
];
|
|
23156
|
+
const filtered = opts.uri ? apps.filter((a) => a.uri === opts.uri) : apps;
|
|
23157
|
+
if (opts.quiet) {
|
|
23158
|
+
for (const a of filtered) {
|
|
23159
|
+
process.stdout.write(`${a.uri}
|
|
23160
|
+
`);
|
|
23161
|
+
}
|
|
23162
|
+
return;
|
|
23163
|
+
}
|
|
23164
|
+
printOutput(filtered, {
|
|
23165
|
+
format: opts.format,
|
|
23166
|
+
columns: ["uri", "name", "mimeType", "linkedTool"]
|
|
23167
|
+
});
|
|
23168
|
+
}
|
|
23169
|
+
);
|
|
22668
23170
|
return mcp;
|
|
22669
23171
|
}
|
|
22670
23172
|
|
|
@@ -24813,11 +25315,44 @@ function buildWorkspacesCommand() {
|
|
|
24813
25315
|
hookTiming.processStartedAt = Math.round(Date.now() - process.uptime() * 1e3);
|
|
24814
25316
|
hookTiming.exitWhenDone = true;
|
|
24815
25317
|
var program = new Command23();
|
|
24816
|
-
program.name("fabric").description("Fabric AI platform CLI").version(package_default.version).option(
|
|
25318
|
+
program.name("fabric").description("Fabric AI platform CLI \u2014 portal, MCP, and agent delegation").version(package_default.version).option(
|
|
24817
25319
|
"--format <format>",
|
|
24818
|
-
"Output format: table|json|yaml|csv",
|
|
25320
|
+
"Output format: table|json|yaml|csv|plain|quiet",
|
|
24819
25321
|
process.env.FABRIC_FORMAT ?? "table"
|
|
24820
|
-
)
|
|
25322
|
+
).option("--json", "Shorthand for --format json").option(
|
|
25323
|
+
"--plain",
|
|
25324
|
+
"Shorthand for --format plain (tab-separated, pipe-friendly)"
|
|
25325
|
+
).option("--no-color", "Disable ANSI color output (also honors NO_COLOR)").addHelpText(
|
|
25326
|
+
"after",
|
|
25327
|
+
`
|
|
25328
|
+
Examples:
|
|
25329
|
+
$ fabric auth login
|
|
25330
|
+
$ fabric connect
|
|
25331
|
+
$ fabric agents delegate --target remote --harness claude-max --prompt "Implement OAuth callback"
|
|
25332
|
+
$ fabric agents desktop --project proj_123 --view allSessions
|
|
25333
|
+
$ fabric mcp tools --stateless
|
|
25334
|
+
$ fabric mcp call fabric_get_identity --stateless --durable
|
|
25335
|
+
|
|
25336
|
+
Environment Variables:
|
|
25337
|
+
FABRIC_API_KEY API key (fab_... or org_...) for non-interactive authentication
|
|
25338
|
+
FABRIC_BASE_URL Fabric instance URL (default: https://app.fabric.pro)
|
|
25339
|
+
FABRIC_FORMAT Default output format (table|json|yaml|csv|plain|quiet)
|
|
25340
|
+
NO_COLOR Set to any non-empty value to disable ANSI color output
|
|
25341
|
+
`
|
|
25342
|
+
).hook("preAction", (thisCommand) => {
|
|
25343
|
+
const opts = thisCommand.optsWithGlobals();
|
|
25344
|
+
if (typeof opts.format === "string") {
|
|
25345
|
+
process.env.FABRIC_FORMAT = opts.format;
|
|
25346
|
+
}
|
|
25347
|
+
if (opts.color === false) {
|
|
25348
|
+
process.env.FABRIC_NO_COLOR = "1";
|
|
25349
|
+
}
|
|
25350
|
+
if (opts.json) {
|
|
25351
|
+
process.env.FABRIC_FORMAT = "json";
|
|
25352
|
+
} else if (opts.plain) {
|
|
25353
|
+
process.env.FABRIC_FORMAT = "plain";
|
|
25354
|
+
}
|
|
25355
|
+
});
|
|
24821
25356
|
var auth = new Command23("auth").description(
|
|
24822
25357
|
"Authenticate and manage API keys"
|
|
24823
25358
|
);
|