@knowsuchagency/fulcrum 4.14.2 → 5.0.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/README.md +1 -16
- package/bin/fulcrum.js +6 -574
- package/dist/assets/{index-BRRVbzA_.js → index-C0A-0TCb.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/server/index.js +14592 -13710
package/bin/fulcrum.js
CHANGED
|
@@ -951,10 +951,6 @@ var init_errors = __esm(() => {
|
|
|
951
951
|
});
|
|
952
952
|
|
|
953
953
|
// cli/src/client.ts
|
|
954
|
-
var exports_client = {};
|
|
955
|
-
__export(exports_client, {
|
|
956
|
-
FulcrumClient: () => FulcrumClient
|
|
957
|
-
});
|
|
958
954
|
import { readFileSync } from "fs";
|
|
959
955
|
import { basename } from "path";
|
|
960
956
|
|
|
@@ -43618,8 +43614,7 @@ var init_types4 = __esm(() => {
|
|
|
43618
43614
|
"messaging",
|
|
43619
43615
|
"assistant",
|
|
43620
43616
|
"caldav",
|
|
43621
|
-
"memory"
|
|
43622
|
-
"board"
|
|
43617
|
+
"memory"
|
|
43623
43618
|
]);
|
|
43624
43619
|
AgentTypeSchema = exports_external.enum(["claude", "opencode"]);
|
|
43625
43620
|
});
|
|
@@ -44581,27 +44576,6 @@ var init_registry = __esm(() => {
|
|
|
44581
44576
|
category: "jobs",
|
|
44582
44577
|
keywords: ["job", "timer", "run", "trigger", "execute", "now"],
|
|
44583
44578
|
defer_loading: true
|
|
44584
|
-
},
|
|
44585
|
-
{
|
|
44586
|
-
name: "board_read",
|
|
44587
|
-
description: "Read the agent coordination board for recent messages from other agents",
|
|
44588
|
-
category: "board",
|
|
44589
|
-
keywords: ["board", "coordination", "agent", "read", "messages", "status", "conflict"],
|
|
44590
|
-
defer_loading: false
|
|
44591
|
-
},
|
|
44592
|
-
{
|
|
44593
|
-
name: "board_post",
|
|
44594
|
-
description: "Post a message to the agent coordination board (claims, status, warnings)",
|
|
44595
|
-
category: "board",
|
|
44596
|
-
keywords: ["board", "coordination", "agent", "post", "claim", "resource", "port", "announce"],
|
|
44597
|
-
defer_loading: false
|
|
44598
|
-
},
|
|
44599
|
-
{
|
|
44600
|
-
name: "board_check",
|
|
44601
|
-
description: "Check if a resource is claimed by another agent on the coordination board",
|
|
44602
|
-
category: "board",
|
|
44603
|
-
keywords: ["board", "coordination", "agent", "check", "claim", "resource", "port", "conflict"],
|
|
44604
|
-
defer_loading: false
|
|
44605
44579
|
}
|
|
44606
44580
|
];
|
|
44607
44581
|
});
|
|
@@ -46792,326 +46766,6 @@ var init_jobs = __esm(() => {
|
|
|
46792
46766
|
JobScopeSchema = exports_external.enum(["all", "user", "system"]);
|
|
46793
46767
|
});
|
|
46794
46768
|
|
|
46795
|
-
// cli/src/board/types.ts
|
|
46796
|
-
var DEFAULT_TTLS;
|
|
46797
|
-
var init_types6 = __esm(() => {
|
|
46798
|
-
DEFAULT_TTLS = {
|
|
46799
|
-
claim: 7200,
|
|
46800
|
-
release: 300,
|
|
46801
|
-
info: 3600,
|
|
46802
|
-
warning: 7200,
|
|
46803
|
-
request: 14400
|
|
46804
|
-
};
|
|
46805
|
-
});
|
|
46806
|
-
|
|
46807
|
-
// cli/src/board/index.ts
|
|
46808
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readdirSync, readFileSync as readFileSync6, writeFileSync as writeFileSync5, unlinkSync as unlinkSync3, renameSync as renameSync3 } from "fs";
|
|
46809
|
-
import { join as join7 } from "path";
|
|
46810
|
-
import { homedir as homedir4, tmpdir } from "os";
|
|
46811
|
-
function getGlobalFulcrumDir() {
|
|
46812
|
-
if (process.env.FULCRUM_DIR) {
|
|
46813
|
-
return process.env.FULCRUM_DIR.replace(/^~/, homedir4());
|
|
46814
|
-
}
|
|
46815
|
-
return join7(homedir4(), ".fulcrum");
|
|
46816
|
-
}
|
|
46817
|
-
function getBoardDir() {
|
|
46818
|
-
return join7(getGlobalFulcrumDir(), "board", "messages");
|
|
46819
|
-
}
|
|
46820
|
-
function getRefsDir() {
|
|
46821
|
-
return join7(getGlobalFulcrumDir(), "board", "refs");
|
|
46822
|
-
}
|
|
46823
|
-
function ensureBoardDirs() {
|
|
46824
|
-
mkdirSync5(getBoardDir(), { recursive: true });
|
|
46825
|
-
mkdirSync5(getRefsDir(), { recursive: true });
|
|
46826
|
-
}
|
|
46827
|
-
function generateId() {
|
|
46828
|
-
return Math.random().toString(36).substring(2, 6);
|
|
46829
|
-
}
|
|
46830
|
-
function detectAgent() {
|
|
46831
|
-
if (process.env.CLAUDE_CODE)
|
|
46832
|
-
return "claude";
|
|
46833
|
-
if (process.env.CLAUDE_CODE_ENTRY_POINT)
|
|
46834
|
-
return "claude";
|
|
46835
|
-
return "unknown";
|
|
46836
|
-
}
|
|
46837
|
-
async function getTaskContext() {
|
|
46838
|
-
const taskId = process.env.FULCRUM_TASK_ID;
|
|
46839
|
-
if (!taskId)
|
|
46840
|
-
return {};
|
|
46841
|
-
try {
|
|
46842
|
-
const { FulcrumClient: FulcrumClient2 } = await Promise.resolve().then(() => (init_client(), exports_client));
|
|
46843
|
-
const client = new FulcrumClient2;
|
|
46844
|
-
const task = await client.getTask(taskId);
|
|
46845
|
-
return {
|
|
46846
|
-
taskId,
|
|
46847
|
-
taskTitle: task.title,
|
|
46848
|
-
project: task.projectId ?? undefined,
|
|
46849
|
-
repository: task.repoName ?? undefined,
|
|
46850
|
-
worktree: task.worktreePath ?? undefined
|
|
46851
|
-
};
|
|
46852
|
-
} catch {
|
|
46853
|
-
return { taskId };
|
|
46854
|
-
}
|
|
46855
|
-
}
|
|
46856
|
-
function parseDuration(duration5) {
|
|
46857
|
-
let seconds = 0;
|
|
46858
|
-
const hourMatch = duration5.match(/(\d+)h/);
|
|
46859
|
-
const minuteMatch = duration5.match(/(\d+)m/);
|
|
46860
|
-
const secondMatch = duration5.match(/(\d+)s/);
|
|
46861
|
-
if (hourMatch)
|
|
46862
|
-
seconds += parseInt(hourMatch[1], 10) * 3600;
|
|
46863
|
-
if (minuteMatch)
|
|
46864
|
-
seconds += parseInt(minuteMatch[1], 10) * 60;
|
|
46865
|
-
if (secondMatch)
|
|
46866
|
-
seconds += parseInt(secondMatch[1], 10);
|
|
46867
|
-
if (seconds === 0 && /^\d+$/.test(duration5)) {
|
|
46868
|
-
seconds = parseInt(duration5, 10);
|
|
46869
|
-
}
|
|
46870
|
-
return seconds || 3600;
|
|
46871
|
-
}
|
|
46872
|
-
function readBoard(options = {}) {
|
|
46873
|
-
const dir = getBoardDir();
|
|
46874
|
-
if (!existsSync7(dir))
|
|
46875
|
-
return [];
|
|
46876
|
-
const now = Date.now();
|
|
46877
|
-
const sinceMs = options.since ? now - parseDuration(options.since) * 1000 : now - 3600000;
|
|
46878
|
-
const limit = options.limit ?? 50;
|
|
46879
|
-
let entries;
|
|
46880
|
-
try {
|
|
46881
|
-
entries = readdirSync(dir).filter((f3) => f3.endsWith(".json")).sort();
|
|
46882
|
-
} catch {
|
|
46883
|
-
return [];
|
|
46884
|
-
}
|
|
46885
|
-
const messages = [];
|
|
46886
|
-
const expired = [];
|
|
46887
|
-
for (const filename of entries) {
|
|
46888
|
-
const tsStr = filename.split("-")[0];
|
|
46889
|
-
const fileTs = parseInt(tsStr, 10);
|
|
46890
|
-
const filePath = join7(dir, filename);
|
|
46891
|
-
try {
|
|
46892
|
-
const content = readFileSync6(filePath, "utf-8");
|
|
46893
|
-
const msg = JSON.parse(content);
|
|
46894
|
-
const msgTs = new Date(msg.timestamp).getTime();
|
|
46895
|
-
const expiresAt = msgTs + msg.ttl * 1000;
|
|
46896
|
-
if (expiresAt < now) {
|
|
46897
|
-
expired.push(filePath);
|
|
46898
|
-
continue;
|
|
46899
|
-
}
|
|
46900
|
-
if (fileTs < sinceMs)
|
|
46901
|
-
continue;
|
|
46902
|
-
if (options.type && msg.type !== options.type)
|
|
46903
|
-
continue;
|
|
46904
|
-
if (options.project && msg.project !== options.project)
|
|
46905
|
-
continue;
|
|
46906
|
-
if (options.tag && (!msg.tags || !msg.tags.includes(options.tag)))
|
|
46907
|
-
continue;
|
|
46908
|
-
messages.push(msg);
|
|
46909
|
-
} catch {}
|
|
46910
|
-
}
|
|
46911
|
-
for (const path of expired) {
|
|
46912
|
-
try {
|
|
46913
|
-
unlinkSync3(path);
|
|
46914
|
-
} catch {}
|
|
46915
|
-
}
|
|
46916
|
-
return messages.reverse().slice(0, limit);
|
|
46917
|
-
}
|
|
46918
|
-
async function postMessage(input) {
|
|
46919
|
-
ensureBoardDirs();
|
|
46920
|
-
const id = generateId();
|
|
46921
|
-
const now = new Date;
|
|
46922
|
-
const type = input.type ?? "info";
|
|
46923
|
-
const ttl = input.ttl ?? DEFAULT_TTLS[type];
|
|
46924
|
-
const context = await getTaskContext();
|
|
46925
|
-
const agent = detectAgent();
|
|
46926
|
-
const message = {
|
|
46927
|
-
id,
|
|
46928
|
-
timestamp: now.toISOString(),
|
|
46929
|
-
type,
|
|
46930
|
-
agent,
|
|
46931
|
-
...context,
|
|
46932
|
-
ttl,
|
|
46933
|
-
body: input.body,
|
|
46934
|
-
tags: input.tags
|
|
46935
|
-
};
|
|
46936
|
-
if (input.refContent) {
|
|
46937
|
-
const refFilename = `${id}-ref.txt`;
|
|
46938
|
-
const refPath = join7(getRefsDir(), refFilename);
|
|
46939
|
-
writeFileSync5(refPath, input.refContent, "utf-8");
|
|
46940
|
-
message.refs = [refFilename];
|
|
46941
|
-
}
|
|
46942
|
-
const filename = `${now.getTime()}-${id}.json`;
|
|
46943
|
-
const finalPath = join7(getBoardDir(), filename);
|
|
46944
|
-
const tempPath = join7(tmpdir(), `fulcrum-board-${filename}`);
|
|
46945
|
-
writeFileSync5(tempPath, JSON.stringify(message, null, 2), "utf-8");
|
|
46946
|
-
renameSync3(tempPath, finalPath);
|
|
46947
|
-
return message;
|
|
46948
|
-
}
|
|
46949
|
-
function checkResource(resource) {
|
|
46950
|
-
const dir = getBoardDir();
|
|
46951
|
-
if (!existsSync7(dir))
|
|
46952
|
-
return null;
|
|
46953
|
-
const now = Date.now();
|
|
46954
|
-
let entries;
|
|
46955
|
-
try {
|
|
46956
|
-
entries = readdirSync(dir).filter((f3) => f3.endsWith(".json")).sort().reverse();
|
|
46957
|
-
} catch {
|
|
46958
|
-
return null;
|
|
46959
|
-
}
|
|
46960
|
-
for (const filename of entries) {
|
|
46961
|
-
const filePath = join7(dir, filename);
|
|
46962
|
-
try {
|
|
46963
|
-
const content = readFileSync6(filePath, "utf-8");
|
|
46964
|
-
const msg = JSON.parse(content);
|
|
46965
|
-
if (msg.type !== "claim")
|
|
46966
|
-
continue;
|
|
46967
|
-
const msgTs = new Date(msg.timestamp).getTime();
|
|
46968
|
-
if (msgTs + msg.ttl * 1000 < now)
|
|
46969
|
-
continue;
|
|
46970
|
-
if (msg.tags && msg.tags.includes(resource)) {
|
|
46971
|
-
return msg;
|
|
46972
|
-
}
|
|
46973
|
-
} catch {}
|
|
46974
|
-
}
|
|
46975
|
-
return null;
|
|
46976
|
-
}
|
|
46977
|
-
function releaseAllByTask(taskId) {
|
|
46978
|
-
const dir = getBoardDir();
|
|
46979
|
-
if (!existsSync7(dir))
|
|
46980
|
-
return 0;
|
|
46981
|
-
let entries;
|
|
46982
|
-
try {
|
|
46983
|
-
entries = readdirSync(dir).filter((f3) => f3.endsWith(".json"));
|
|
46984
|
-
} catch {
|
|
46985
|
-
return 0;
|
|
46986
|
-
}
|
|
46987
|
-
let deleted = 0;
|
|
46988
|
-
for (const filename of entries) {
|
|
46989
|
-
const filePath = join7(dir, filename);
|
|
46990
|
-
try {
|
|
46991
|
-
const content = readFileSync6(filePath, "utf-8");
|
|
46992
|
-
const msg = JSON.parse(content);
|
|
46993
|
-
if (msg.type === "claim" && msg.taskId === taskId) {
|
|
46994
|
-
unlinkSync3(filePath);
|
|
46995
|
-
deleted++;
|
|
46996
|
-
}
|
|
46997
|
-
} catch {}
|
|
46998
|
-
}
|
|
46999
|
-
return deleted;
|
|
47000
|
-
}
|
|
47001
|
-
function cleanBoard(all) {
|
|
47002
|
-
const dir = getBoardDir();
|
|
47003
|
-
if (!existsSync7(dir))
|
|
47004
|
-
return 0;
|
|
47005
|
-
let entries;
|
|
47006
|
-
try {
|
|
47007
|
-
entries = readdirSync(dir).filter((f3) => f3.endsWith(".json"));
|
|
47008
|
-
} catch {
|
|
47009
|
-
return 0;
|
|
47010
|
-
}
|
|
47011
|
-
const now = Date.now();
|
|
47012
|
-
let deleted = 0;
|
|
47013
|
-
for (const filename of entries) {
|
|
47014
|
-
const filePath = join7(dir, filename);
|
|
47015
|
-
if (all) {
|
|
47016
|
-
try {
|
|
47017
|
-
unlinkSync3(filePath);
|
|
47018
|
-
deleted++;
|
|
47019
|
-
} catch {}
|
|
47020
|
-
continue;
|
|
47021
|
-
}
|
|
47022
|
-
try {
|
|
47023
|
-
const content = readFileSync6(filePath, "utf-8");
|
|
47024
|
-
const msg = JSON.parse(content);
|
|
47025
|
-
const msgTs = new Date(msg.timestamp).getTime();
|
|
47026
|
-
if (msgTs + msg.ttl * 1000 < now) {
|
|
47027
|
-
unlinkSync3(filePath);
|
|
47028
|
-
deleted++;
|
|
47029
|
-
}
|
|
47030
|
-
} catch {
|
|
47031
|
-
try {
|
|
47032
|
-
unlinkSync3(filePath);
|
|
47033
|
-
deleted++;
|
|
47034
|
-
} catch {}
|
|
47035
|
-
}
|
|
47036
|
-
}
|
|
47037
|
-
if (all) {
|
|
47038
|
-
const refsDir = getRefsDir();
|
|
47039
|
-
if (existsSync7(refsDir)) {
|
|
47040
|
-
try {
|
|
47041
|
-
for (const ref of readdirSync(refsDir)) {
|
|
47042
|
-
try {
|
|
47043
|
-
unlinkSync3(join7(refsDir, ref));
|
|
47044
|
-
deleted++;
|
|
47045
|
-
} catch {}
|
|
47046
|
-
}
|
|
47047
|
-
} catch {}
|
|
47048
|
-
}
|
|
47049
|
-
}
|
|
47050
|
-
return deleted;
|
|
47051
|
-
}
|
|
47052
|
-
var init_board = __esm(() => {
|
|
47053
|
-
init_types6();
|
|
47054
|
-
});
|
|
47055
|
-
|
|
47056
|
-
// cli/src/mcp/tools/board.ts
|
|
47057
|
-
var registerBoardTools = (server, _client) => {
|
|
47058
|
-
server.tool("board_read", "Read the agent coordination board. Returns recent messages from other agents working in the same project. Use before claiming resources (ports, services) to avoid conflicts.", {
|
|
47059
|
-
since: exports_external.optional(exports_external.string()).describe('Time window, e.g. "1h", "30m", "2h". Default: "1h"'),
|
|
47060
|
-
type: exports_external.optional(exports_external.enum(["claim", "release", "info", "warning", "request"])).describe("Filter by message type"),
|
|
47061
|
-
project: exports_external.optional(exports_external.string()).describe("Filter by project name"),
|
|
47062
|
-
tag: exports_external.optional(exports_external.string()).describe('Filter by tag, e.g. "port:5173"'),
|
|
47063
|
-
limit: exports_external.optional(exports_external.number().min(1).max(200)).describe("Max messages to return (default: 50)")
|
|
47064
|
-
}, async ({ since, type, project, tag, limit }) => {
|
|
47065
|
-
try {
|
|
47066
|
-
const messages = readBoard({
|
|
47067
|
-
since,
|
|
47068
|
-
type,
|
|
47069
|
-
project,
|
|
47070
|
-
tag,
|
|
47071
|
-
limit
|
|
47072
|
-
});
|
|
47073
|
-
return formatSuccess(messages);
|
|
47074
|
-
} catch (err) {
|
|
47075
|
-
return handleToolError(err);
|
|
47076
|
-
}
|
|
47077
|
-
});
|
|
47078
|
-
server.tool("board_post", "Post a message to the agent coordination board. Announce resource claims, share status updates, or coordinate with other agents working on the same project.", {
|
|
47079
|
-
body: exports_external.string().describe("Message body describing what you are doing or claiming"),
|
|
47080
|
-
type: exports_external.optional(exports_external.enum(["claim", "release", "info", "warning", "request"])).describe('Message type. Default: "info". Use "claim" to reserve resources, "release" to free them.'),
|
|
47081
|
-
tags: exports_external.optional(exports_external.array(exports_external.string())).describe('Tags for categorization and resource matching, e.g. ["port:5173", "dev-server"]'),
|
|
47082
|
-
ttl: exports_external.optional(exports_external.number()).describe("Time-to-live in seconds. Defaults vary by type: claim=7200, release=300, info=3600"),
|
|
47083
|
-
refContent: exports_external.optional(exports_external.string()).describe("Large content to store as a ref file (e.g., error logs)")
|
|
47084
|
-
}, async ({ body, type, tags, ttl, refContent }) => {
|
|
47085
|
-
try {
|
|
47086
|
-
const message = await postMessage({
|
|
47087
|
-
body,
|
|
47088
|
-
type,
|
|
47089
|
-
tags,
|
|
47090
|
-
ttl,
|
|
47091
|
-
refContent
|
|
47092
|
-
});
|
|
47093
|
-
return formatSuccess(message);
|
|
47094
|
-
} catch (err) {
|
|
47095
|
-
return handleToolError(err);
|
|
47096
|
-
}
|
|
47097
|
-
});
|
|
47098
|
-
server.tool("board_check", "Check if a resource is claimed by another agent. Returns the active claim if found, null if the resource is free. Use before starting dev servers, database operations, etc.", {
|
|
47099
|
-
resource: exports_external.string().describe('Resource tag to check, e.g. "port:5173", "db:migration"')
|
|
47100
|
-
}, async ({ resource }) => {
|
|
47101
|
-
try {
|
|
47102
|
-
const claim = checkResource(resource);
|
|
47103
|
-
return formatSuccess(claim ? { claimed: true, claim } : { claimed: false });
|
|
47104
|
-
} catch (err) {
|
|
47105
|
-
return handleToolError(err);
|
|
47106
|
-
}
|
|
47107
|
-
});
|
|
47108
|
-
};
|
|
47109
|
-
var init_board2 = __esm(() => {
|
|
47110
|
-
init_zod2();
|
|
47111
|
-
init_utils();
|
|
47112
|
-
init_board();
|
|
47113
|
-
});
|
|
47114
|
-
|
|
47115
46769
|
// cli/src/mcp/tools/index.ts
|
|
47116
46770
|
function registerTools(server, client) {
|
|
47117
46771
|
registerCoreTools(server, client);
|
|
@@ -47134,7 +46788,6 @@ function registerTools(server, client) {
|
|
|
47134
46788
|
registerMemoryFileTools(server, client);
|
|
47135
46789
|
registerSearchTools(server, client);
|
|
47136
46790
|
registerJobTools(server, client);
|
|
47137
|
-
registerBoardTools(server, client);
|
|
47138
46791
|
}
|
|
47139
46792
|
var init_tools = __esm(() => {
|
|
47140
46793
|
init_core5();
|
|
@@ -47157,7 +46810,6 @@ var init_tools = __esm(() => {
|
|
|
47157
46810
|
init_messaging();
|
|
47158
46811
|
init_search();
|
|
47159
46812
|
init_jobs();
|
|
47160
|
-
init_board2();
|
|
47161
46813
|
init_types4();
|
|
47162
46814
|
});
|
|
47163
46815
|
|
|
@@ -47176,7 +46828,7 @@ async function runMcpServer(urlOverride, portOverride) {
|
|
|
47176
46828
|
const client = new FulcrumClient(urlOverride, portOverride);
|
|
47177
46829
|
const server = new McpServer({
|
|
47178
46830
|
name: "fulcrum",
|
|
47179
|
-
version: "
|
|
46831
|
+
version: "5.0.0"
|
|
47180
46832
|
});
|
|
47181
46833
|
registerTools(server, client);
|
|
47182
46834
|
const transport = new StdioServerTransport;
|
|
@@ -48599,15 +48251,6 @@ function setJsonOutput(value) {
|
|
|
48599
48251
|
function isJsonOutput() {
|
|
48600
48252
|
return jsonOutput;
|
|
48601
48253
|
}
|
|
48602
|
-
function prettyLog(type, message) {
|
|
48603
|
-
const prefixes = {
|
|
48604
|
-
success: "\u2713",
|
|
48605
|
-
info: "\u2192",
|
|
48606
|
-
error: "\u2717",
|
|
48607
|
-
warning: "\u26A0"
|
|
48608
|
-
};
|
|
48609
|
-
console.log(`${prefixes[type]} ${message}`);
|
|
48610
|
-
}
|
|
48611
48254
|
function output(data) {
|
|
48612
48255
|
const response = {
|
|
48613
48256
|
success: true,
|
|
@@ -49534,7 +49177,7 @@ var marketplace_default = `{
|
|
|
49534
49177
|
"name": "fulcrum",
|
|
49535
49178
|
"source": "./",
|
|
49536
49179
|
"description": "Task orchestration for Claude Code",
|
|
49537
|
-
"version": "
|
|
49180
|
+
"version": "5.0.0",
|
|
49538
49181
|
"skills": [
|
|
49539
49182
|
"./skills/fulcrum"
|
|
49540
49183
|
],
|
|
@@ -49557,7 +49200,7 @@ var marketplace_default = `{
|
|
|
49557
49200
|
var plugin_default = `{
|
|
49558
49201
|
"name": "fulcrum",
|
|
49559
49202
|
"description": "Fulcrum task orchestration for Claude Code",
|
|
49560
|
-
"version": "
|
|
49203
|
+
"version": "5.0.0",
|
|
49561
49204
|
"author": {
|
|
49562
49205
|
"name": "Fulcrum"
|
|
49563
49206
|
},
|
|
@@ -49573,10 +49216,6 @@ var hooks_default = `{
|
|
|
49573
49216
|
"Stop": [
|
|
49574
49217
|
{
|
|
49575
49218
|
"hooks": [
|
|
49576
|
-
{
|
|
49577
|
-
"type": "command",
|
|
49578
|
-
"command": "fulcrum board release-all 2>/dev/null || true"
|
|
49579
|
-
},
|
|
49580
49219
|
{
|
|
49581
49220
|
"type": "command",
|
|
49582
49221
|
"command": "fulcrum current-task review 2>/dev/null || true"
|
|
@@ -49726,42 +49365,6 @@ fulcrum notify "Task Complete" "Implemented the new feature and created PR #123"
|
|
|
49726
49365
|
fulcrum notify "Need Input" "Which approach should I use for the database migration?"
|
|
49727
49366
|
\`\`\`
|
|
49728
49367
|
|
|
49729
|
-
## Agent Coordination Board
|
|
49730
|
-
|
|
49731
|
-
When multiple agents work on the same project in separate worktrees, use the coordination board to avoid conflicts (port collisions, concurrent migrations, etc.).
|
|
49732
|
-
|
|
49733
|
-
### When to Use
|
|
49734
|
-
|
|
49735
|
-
- **Before starting a dev server** \u2014 check if the port is already claimed
|
|
49736
|
-
- **Before running database migrations** \u2014 check if another agent is migrating
|
|
49737
|
-
- **When using shared resources** \u2014 claim them first, release when done
|
|
49738
|
-
|
|
49739
|
-
### CLI Commands
|
|
49740
|
-
|
|
49741
|
-
\`\`\`bash
|
|
49742
|
-
fulcrum board # Read recent messages (last 1h)
|
|
49743
|
-
fulcrum board read --since 2h # Custom time window
|
|
49744
|
-
fulcrum board read --type claim # Filter by type
|
|
49745
|
-
fulcrum board read --tag port:5173 # Filter by tag
|
|
49746
|
-
|
|
49747
|
-
fulcrum board post "Using port 5173" --type claim --tag port:5173
|
|
49748
|
-
fulcrum board post "Migration complete" --type info
|
|
49749
|
-
|
|
49750
|
-
fulcrum board check port:5173 # Check if resource is claimed
|
|
49751
|
-
|
|
49752
|
-
fulcrum board release-all # Release all your claims (auto-runs on Stop)
|
|
49753
|
-
|
|
49754
|
-
fulcrum board clean # Remove expired messages
|
|
49755
|
-
fulcrum board clean --all # Remove ALL messages
|
|
49756
|
-
\`\`\`
|
|
49757
|
-
|
|
49758
|
-
### Best Practices
|
|
49759
|
-
|
|
49760
|
-
1. **Always check before claiming** \u2014 \`fulcrum board check port:<N>\` before starting a dev server
|
|
49761
|
-
2. **Always release when done** \u2014 Post a \`release\` message or use \`fulcrum board release-all\`
|
|
49762
|
-
3. **Claims auto-expire** \u2014 TTL is 2 hours for claims, so crashes won't permanently block resources
|
|
49763
|
-
4. **The Stop hook auto-releases** \u2014 When your session ends, claims are released automatically
|
|
49764
|
-
|
|
49765
49368
|
## Global Options
|
|
49766
49369
|
|
|
49767
49370
|
- \`--port=<port>\` \u2014 Server port (default: 7777)
|
|
@@ -50609,7 +50212,7 @@ function compareVersions(v1, v2) {
|
|
|
50609
50212
|
var package_default = {
|
|
50610
50213
|
name: "@knowsuchagency/fulcrum",
|
|
50611
50214
|
private: true,
|
|
50612
|
-
version: "
|
|
50215
|
+
version: "5.0.0",
|
|
50613
50216
|
description: "Harness Attention. Orchestrate Agents. Ship.",
|
|
50614
50217
|
license: "PolyForm-Perimeter-1.0.0",
|
|
50615
50218
|
type: "module",
|
|
@@ -50625,7 +50228,7 @@ var package_default = {
|
|
|
50625
50228
|
"db:studio": "drizzle-kit studio"
|
|
50626
50229
|
},
|
|
50627
50230
|
dependencies: {
|
|
50628
|
-
"@anthropic-ai/claude-agent-sdk": "0.2.
|
|
50231
|
+
"@anthropic-ai/claude-agent-sdk": "0.2.114",
|
|
50629
50232
|
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
|
50630
50233
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
|
|
50631
50234
|
"@azurity/pure-nerd-font": "^3.0.5",
|
|
@@ -51353,176 +50956,6 @@ var mcpCommand = defineCommand({
|
|
|
51353
50956
|
}
|
|
51354
50957
|
});
|
|
51355
50958
|
|
|
51356
|
-
// cli/src/commands/board.ts
|
|
51357
|
-
init_board();
|
|
51358
|
-
function formatMessage(msg) {
|
|
51359
|
-
const age = getAge(msg.timestamp);
|
|
51360
|
-
const typeColor = {
|
|
51361
|
-
claim: "[CLAIM]",
|
|
51362
|
-
release: "[RELEASE]",
|
|
51363
|
-
info: "[INFO]",
|
|
51364
|
-
warning: "[WARNING]",
|
|
51365
|
-
request: "[REQUEST]"
|
|
51366
|
-
};
|
|
51367
|
-
const prefix = typeColor[msg.type] ?? `[${msg.type.toUpperCase()}]`;
|
|
51368
|
-
const agent = msg.agent !== "unknown" ? ` (${msg.agent})` : "";
|
|
51369
|
-
const task = msg.taskTitle ? ` | ${msg.taskTitle}` : "";
|
|
51370
|
-
const tags = msg.tags?.length ? ` [${msg.tags.join(", ")}]` : "";
|
|
51371
|
-
console.log(`${prefix} ${msg.body}${agent}${task}${tags} (${age} ago)`);
|
|
51372
|
-
}
|
|
51373
|
-
function getAge(timestamp) {
|
|
51374
|
-
const ms = Date.now() - new Date(timestamp).getTime();
|
|
51375
|
-
const seconds = Math.floor(ms / 1000);
|
|
51376
|
-
if (seconds < 60)
|
|
51377
|
-
return `${seconds}s`;
|
|
51378
|
-
const minutes = Math.floor(seconds / 60);
|
|
51379
|
-
if (minutes < 60)
|
|
51380
|
-
return `${minutes}m`;
|
|
51381
|
-
const hours = Math.floor(minutes / 60);
|
|
51382
|
-
const remainingMinutes = minutes % 60;
|
|
51383
|
-
if (remainingMinutes > 0)
|
|
51384
|
-
return `${hours}h${remainingMinutes}m`;
|
|
51385
|
-
return `${hours}h`;
|
|
51386
|
-
}
|
|
51387
|
-
var readCommand = defineCommand({
|
|
51388
|
-
meta: { name: "read", description: "Read recent messages from the coordination board" },
|
|
51389
|
-
args: {
|
|
51390
|
-
since: { type: "string", description: "Time window (e.g., 1h, 30m, 2h). Default: 1h" },
|
|
51391
|
-
type: { type: "string", description: "Filter by type: claim, release, info, warning, request" },
|
|
51392
|
-
project: { type: "string", description: "Filter by project name" },
|
|
51393
|
-
tag: { type: "string", description: "Filter by tag (e.g., port:5173)" },
|
|
51394
|
-
limit: { type: "string", description: "Max messages to return (default: 50)" },
|
|
51395
|
-
json: { type: "boolean", description: "Output as JSON" }
|
|
51396
|
-
},
|
|
51397
|
-
async run({ args }) {
|
|
51398
|
-
setupJsonOutput(args);
|
|
51399
|
-
const messages = readBoard({
|
|
51400
|
-
since: args.since,
|
|
51401
|
-
type: args.type,
|
|
51402
|
-
project: args.project,
|
|
51403
|
-
tag: args.tag,
|
|
51404
|
-
limit: args.limit ? parseInt(args.limit, 10) : undefined
|
|
51405
|
-
});
|
|
51406
|
-
if (isJsonOutput()) {
|
|
51407
|
-
output(messages);
|
|
51408
|
-
} else {
|
|
51409
|
-
if (messages.length === 0) {
|
|
51410
|
-
prettyLog("info", "No messages on the board");
|
|
51411
|
-
} else {
|
|
51412
|
-
for (const msg of messages) {
|
|
51413
|
-
formatMessage(msg);
|
|
51414
|
-
}
|
|
51415
|
-
}
|
|
51416
|
-
}
|
|
51417
|
-
}
|
|
51418
|
-
});
|
|
51419
|
-
var postCommand = defineCommand({
|
|
51420
|
-
meta: { name: "post", description: "Post a message to the coordination board" },
|
|
51421
|
-
args: {
|
|
51422
|
-
body: { type: "positional", description: "Message body", required: true },
|
|
51423
|
-
type: { type: "string", description: "Message type: claim, release, info, warning, request. Default: info" },
|
|
51424
|
-
tag: { type: "string", description: "Add a tag (repeatable with comma separation, e.g., port:5173,dev-server)" },
|
|
51425
|
-
ttl: { type: "string", description: "TTL in seconds (overrides default for type)" },
|
|
51426
|
-
json: { type: "boolean", description: "Output as JSON" }
|
|
51427
|
-
},
|
|
51428
|
-
async run({ args }) {
|
|
51429
|
-
setupJsonOutput(args);
|
|
51430
|
-
const tags = args.tag ? args.tag.split(",").map((t2) => t2.trim()).filter(Boolean) : undefined;
|
|
51431
|
-
const message = await postMessage({
|
|
51432
|
-
body: args.body,
|
|
51433
|
-
type: args.type ?? "info",
|
|
51434
|
-
tags,
|
|
51435
|
-
ttl: args.ttl ? parseInt(args.ttl, 10) : undefined
|
|
51436
|
-
});
|
|
51437
|
-
if (isJsonOutput()) {
|
|
51438
|
-
output(message);
|
|
51439
|
-
} else {
|
|
51440
|
-
prettyLog("success", `Posted: ${message.body}`);
|
|
51441
|
-
}
|
|
51442
|
-
}
|
|
51443
|
-
});
|
|
51444
|
-
var checkCommand = defineCommand({
|
|
51445
|
-
meta: { name: "check", description: "Check if a resource is claimed by another agent" },
|
|
51446
|
-
args: {
|
|
51447
|
-
resource: { type: "positional", description: "Resource tag to check (e.g., port:5173)", required: true },
|
|
51448
|
-
json: { type: "boolean", description: "Output as JSON" }
|
|
51449
|
-
},
|
|
51450
|
-
async run({ args }) {
|
|
51451
|
-
setupJsonOutput(args);
|
|
51452
|
-
const claim = checkResource(args.resource);
|
|
51453
|
-
if (isJsonOutput()) {
|
|
51454
|
-
output(claim ? { claimed: true, claim } : { claimed: false });
|
|
51455
|
-
} else {
|
|
51456
|
-
if (claim) {
|
|
51457
|
-
prettyLog("warning", `Resource "${args.resource}" is claimed`);
|
|
51458
|
-
formatMessage(claim);
|
|
51459
|
-
process.exit(0);
|
|
51460
|
-
} else {
|
|
51461
|
-
prettyLog("success", `Resource "${args.resource}" is free`);
|
|
51462
|
-
process.exit(1);
|
|
51463
|
-
}
|
|
51464
|
-
}
|
|
51465
|
-
}
|
|
51466
|
-
});
|
|
51467
|
-
var releaseAllCommand = defineCommand({
|
|
51468
|
-
meta: { name: "release-all", description: "Release all claims by the current task" },
|
|
51469
|
-
args: {
|
|
51470
|
-
json: { type: "boolean", description: "Output as JSON" }
|
|
51471
|
-
},
|
|
51472
|
-
async run({ args }) {
|
|
51473
|
-
setupJsonOutput(args);
|
|
51474
|
-
const taskId = process.env.FULCRUM_TASK_ID;
|
|
51475
|
-
if (!taskId) {
|
|
51476
|
-
if (isJsonOutput()) {
|
|
51477
|
-
output({ released: 0, message: "No FULCRUM_TASK_ID set" });
|
|
51478
|
-
} else {
|
|
51479
|
-
prettyLog("info", "No FULCRUM_TASK_ID set \u2014 nothing to release");
|
|
51480
|
-
}
|
|
51481
|
-
return;
|
|
51482
|
-
}
|
|
51483
|
-
const count = releaseAllByTask(taskId);
|
|
51484
|
-
if (isJsonOutput()) {
|
|
51485
|
-
output({ released: count, taskId });
|
|
51486
|
-
} else {
|
|
51487
|
-
if (count > 0) {
|
|
51488
|
-
prettyLog("success", `Released ${count} claim(s) for task ${taskId}`);
|
|
51489
|
-
} else {
|
|
51490
|
-
prettyLog("info", "No active claims to release");
|
|
51491
|
-
}
|
|
51492
|
-
}
|
|
51493
|
-
}
|
|
51494
|
-
});
|
|
51495
|
-
var cleanCommand = defineCommand({
|
|
51496
|
-
meta: { name: "clean", description: "Remove expired messages from the board" },
|
|
51497
|
-
args: {
|
|
51498
|
-
all: { type: "boolean", description: "Remove ALL messages (not just expired)" },
|
|
51499
|
-
json: { type: "boolean", description: "Output as JSON" }
|
|
51500
|
-
},
|
|
51501
|
-
async run({ args }) {
|
|
51502
|
-
setupJsonOutput(args);
|
|
51503
|
-
const count = cleanBoard(args.all);
|
|
51504
|
-
if (isJsonOutput()) {
|
|
51505
|
-
output({ deleted: count });
|
|
51506
|
-
} else {
|
|
51507
|
-
if (count > 0) {
|
|
51508
|
-
prettyLog("success", `Removed ${count} message(s)`);
|
|
51509
|
-
} else {
|
|
51510
|
-
prettyLog("info", "Nothing to clean");
|
|
51511
|
-
}
|
|
51512
|
-
}
|
|
51513
|
-
}
|
|
51514
|
-
});
|
|
51515
|
-
var boardCommand = defineCommand({
|
|
51516
|
-
meta: { name: "board", description: "Agent coordination board for multi-agent environments" },
|
|
51517
|
-
subCommands: {
|
|
51518
|
-
read: readCommand,
|
|
51519
|
-
post: postCommand,
|
|
51520
|
-
check: checkCommand,
|
|
51521
|
-
"release-all": releaseAllCommand,
|
|
51522
|
-
clean: cleanCommand
|
|
51523
|
-
}
|
|
51524
|
-
});
|
|
51525
|
-
|
|
51526
50959
|
// cli/src/commands/migrate-from-vibora.ts
|
|
51527
50960
|
init_server();
|
|
51528
50961
|
async function handleMigrateFromViboraCommand(flags) {
|
|
@@ -51617,7 +51050,6 @@ var main = defineCommand({
|
|
|
51617
51050
|
config: configCommand,
|
|
51618
51051
|
opencode: opencodeCommand,
|
|
51619
51052
|
claude: claudeCommand,
|
|
51620
|
-
board: boardCommand,
|
|
51621
51053
|
notifications: notificationsCommand,
|
|
51622
51054
|
notify: notifyCommand,
|
|
51623
51055
|
up: upCommand,
|