@knowsuchagency/fulcrum 4.1.0 → 4.1.2
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 +3 -2
- package/bin/fulcrum.js +259 -3
- package/dist/apple-touch-icon.png +0 -0
- package/dist/assets/{index-D7fMaUxp.js → index-CRP4CHZo.js} +2 -2
- package/dist/icon-192.png +0 -0
- package/dist/icon-512.png +0 -0
- package/dist/index.html +6 -1
- package/dist/manifest.json +20 -0
- package/package.json +1 -1
- package/server/index.js +674 -38
package/README.md
CHANGED
|
@@ -270,6 +270,7 @@ Both plugins include an MCP server with 100+ tools:
|
|
|
270
270
|
| **Memory** | Read/update master memory file; store ephemeral knowledge with tags |
|
|
271
271
|
| **Calendar** | Manage CalDAV accounts, sync calendars, configure event copy rules |
|
|
272
272
|
| **Gmail** | List Google accounts, manage Gmail drafts, send emails |
|
|
273
|
+
| **Jobs** | List, create, update, delete, enable/disable, and run systemd timers and launchd jobs |
|
|
273
274
|
| **Assistant** | Send messages via channels (WhatsApp, Discord, Telegram, Slack, Gmail); query sweep history |
|
|
274
275
|
|
|
275
276
|
Use `search_tools` to discover available tools by keyword or category.
|
|
@@ -315,9 +316,9 @@ For browser-only access, use Tailscale or Cloudflare Tunnels to expose your serv
|
|
|
315
316
|
<details>
|
|
316
317
|
<summary><strong>Configuration</strong></summary>
|
|
317
318
|
|
|
318
|
-
|
|
319
|
+
All configuration is managed by [fnox](https://github.com/yarlson/fnox) — a single `fnox.toml` file stores both plain and encrypted settings. Sensitive credentials (API keys, tokens, webhook URLs) are encrypted with age; the age key (`age.txt`) lives alongside `fnox.toml` in the fulcrum directory. Existing `settings.json` files are automatically migrated to fnox on server start.
|
|
319
320
|
|
|
320
|
-
|
|
321
|
+
The fulcrum directory is resolved in this order:
|
|
321
322
|
|
|
322
323
|
1. `FULCRUM_DIR` environment variable
|
|
323
324
|
2. `.fulcrum` in current working directory
|
package/bin/fulcrum.js
CHANGED
|
@@ -1670,6 +1670,65 @@ class FulcrumClient {
|
|
|
1670
1670
|
body: JSON.stringify({ heading, content })
|
|
1671
1671
|
});
|
|
1672
1672
|
}
|
|
1673
|
+
async listJobs(scope) {
|
|
1674
|
+
const params = new URLSearchParams;
|
|
1675
|
+
if (scope)
|
|
1676
|
+
params.set("scope", scope);
|
|
1677
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1678
|
+
return this.fetch(`/api/jobs${query}`);
|
|
1679
|
+
}
|
|
1680
|
+
async getJob(name, scope) {
|
|
1681
|
+
const params = new URLSearchParams;
|
|
1682
|
+
if (scope)
|
|
1683
|
+
params.set("scope", scope);
|
|
1684
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1685
|
+
return this.fetch(`/api/jobs/${encodeURIComponent(name)}${query}`);
|
|
1686
|
+
}
|
|
1687
|
+
async getJobLogs(name, scope, lines) {
|
|
1688
|
+
const params = new URLSearchParams;
|
|
1689
|
+
if (scope)
|
|
1690
|
+
params.set("scope", scope);
|
|
1691
|
+
if (lines)
|
|
1692
|
+
params.set("lines", String(lines));
|
|
1693
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1694
|
+
return this.fetch(`/api/jobs/${encodeURIComponent(name)}/logs${query}`);
|
|
1695
|
+
}
|
|
1696
|
+
async createJob(data) {
|
|
1697
|
+
return this.fetch("/api/jobs", {
|
|
1698
|
+
method: "POST",
|
|
1699
|
+
body: JSON.stringify(data)
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
async updateJob(name, updates) {
|
|
1703
|
+
return this.fetch(`/api/jobs/${encodeURIComponent(name)}`, {
|
|
1704
|
+
method: "PATCH",
|
|
1705
|
+
body: JSON.stringify(updates)
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
async deleteJob(name) {
|
|
1709
|
+
return this.fetch(`/api/jobs/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
1710
|
+
}
|
|
1711
|
+
async enableJob(name, scope) {
|
|
1712
|
+
const params = new URLSearchParams;
|
|
1713
|
+
if (scope)
|
|
1714
|
+
params.set("scope", scope);
|
|
1715
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1716
|
+
return this.fetch(`/api/jobs/${encodeURIComponent(name)}/enable${query}`, { method: "POST" });
|
|
1717
|
+
}
|
|
1718
|
+
async disableJob(name, scope) {
|
|
1719
|
+
const params = new URLSearchParams;
|
|
1720
|
+
if (scope)
|
|
1721
|
+
params.set("scope", scope);
|
|
1722
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1723
|
+
return this.fetch(`/api/jobs/${encodeURIComponent(name)}/disable${query}`, { method: "POST" });
|
|
1724
|
+
}
|
|
1725
|
+
async runJobNow(name, scope) {
|
|
1726
|
+
const params = new URLSearchParams;
|
|
1727
|
+
if (scope)
|
|
1728
|
+
params.set("scope", scope);
|
|
1729
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1730
|
+
return this.fetch(`/api/jobs/${encodeURIComponent(name)}/run${query}`, { method: "POST" });
|
|
1731
|
+
}
|
|
1673
1732
|
async search(input) {
|
|
1674
1733
|
const params = new URLSearchParams({ q: input.query });
|
|
1675
1734
|
if (input.entities?.length)
|
|
@@ -44413,6 +44472,69 @@ var init_registry = __esm(() => {
|
|
|
44413
44472
|
category: "email",
|
|
44414
44473
|
keywords: ["gmail", "draft", "delete", "remove", "email", "google"],
|
|
44415
44474
|
defer_loading: true
|
|
44475
|
+
},
|
|
44476
|
+
{
|
|
44477
|
+
name: "list_jobs",
|
|
44478
|
+
description: "List scheduled jobs (systemd timers on Linux, launchd on macOS)",
|
|
44479
|
+
category: "jobs",
|
|
44480
|
+
keywords: ["job", "timer", "schedule", "cron", "systemd", "launchd", "list"],
|
|
44481
|
+
defer_loading: true
|
|
44482
|
+
},
|
|
44483
|
+
{
|
|
44484
|
+
name: "get_job",
|
|
44485
|
+
description: "Get details of a scheduled job including schedule, command, and execution stats",
|
|
44486
|
+
category: "jobs",
|
|
44487
|
+
keywords: ["job", "timer", "schedule", "details", "get"],
|
|
44488
|
+
defer_loading: true
|
|
44489
|
+
},
|
|
44490
|
+
{
|
|
44491
|
+
name: "get_job_logs",
|
|
44492
|
+
description: "Get execution logs for a scheduled job",
|
|
44493
|
+
category: "jobs",
|
|
44494
|
+
keywords: ["job", "timer", "logs", "output", "journalctl"],
|
|
44495
|
+
defer_loading: true
|
|
44496
|
+
},
|
|
44497
|
+
{
|
|
44498
|
+
name: "create_job",
|
|
44499
|
+
description: "Create a new scheduled job (Linux systemd only)",
|
|
44500
|
+
category: "jobs",
|
|
44501
|
+
keywords: ["job", "timer", "schedule", "create", "new", "systemd"],
|
|
44502
|
+
defer_loading: true
|
|
44503
|
+
},
|
|
44504
|
+
{
|
|
44505
|
+
name: "update_job",
|
|
44506
|
+
description: "Update a scheduled job (Linux systemd only)",
|
|
44507
|
+
category: "jobs",
|
|
44508
|
+
keywords: ["job", "timer", "schedule", "update", "edit", "modify"],
|
|
44509
|
+
defer_loading: true
|
|
44510
|
+
},
|
|
44511
|
+
{
|
|
44512
|
+
name: "delete_job",
|
|
44513
|
+
description: "Delete a scheduled job (Linux systemd user jobs only)",
|
|
44514
|
+
category: "jobs",
|
|
44515
|
+
keywords: ["job", "timer", "schedule", "delete", "remove"],
|
|
44516
|
+
defer_loading: true
|
|
44517
|
+
},
|
|
44518
|
+
{
|
|
44519
|
+
name: "enable_job",
|
|
44520
|
+
description: "Enable a scheduled job so it runs on schedule",
|
|
44521
|
+
category: "jobs",
|
|
44522
|
+
keywords: ["job", "timer", "enable", "start", "activate"],
|
|
44523
|
+
defer_loading: true
|
|
44524
|
+
},
|
|
44525
|
+
{
|
|
44526
|
+
name: "disable_job",
|
|
44527
|
+
description: "Disable a scheduled job so it stops running",
|
|
44528
|
+
category: "jobs",
|
|
44529
|
+
keywords: ["job", "timer", "disable", "stop", "deactivate"],
|
|
44530
|
+
defer_loading: true
|
|
44531
|
+
},
|
|
44532
|
+
{
|
|
44533
|
+
name: "run_job_now",
|
|
44534
|
+
description: "Trigger immediate execution of a scheduled job",
|
|
44535
|
+
category: "jobs",
|
|
44536
|
+
keywords: ["job", "timer", "run", "trigger", "execute", "now"],
|
|
44537
|
+
defer_loading: true
|
|
44416
44538
|
}
|
|
44417
44539
|
];
|
|
44418
44540
|
});
|
|
@@ -46395,6 +46517,138 @@ var init_search = __esm(() => {
|
|
|
46395
46517
|
EntityTypeSchema = exports_external.enum(["tasks", "projects", "messages", "events", "memories", "conversations", "gmail"]);
|
|
46396
46518
|
});
|
|
46397
46519
|
|
|
46520
|
+
// cli/src/mcp/tools/jobs.ts
|
|
46521
|
+
var JobScopeSchema, registerJobTools = (server, client) => {
|
|
46522
|
+
server.tool("list_jobs", "List scheduled jobs (systemd timers on Linux, launchd on macOS)", {
|
|
46523
|
+
scope: exports_external.optional(JobScopeSchema).describe("Filter by scope: all, user, or system (default: all)")
|
|
46524
|
+
}, async ({ scope }) => {
|
|
46525
|
+
try {
|
|
46526
|
+
const jobs = await client.listJobs(scope);
|
|
46527
|
+
return formatSuccess(jobs);
|
|
46528
|
+
} catch (err) {
|
|
46529
|
+
return handleToolError(err);
|
|
46530
|
+
}
|
|
46531
|
+
});
|
|
46532
|
+
server.tool("get_job", "Get details of a scheduled job including schedule, command, execution stats, and unit file contents", {
|
|
46533
|
+
name: exports_external.string().describe("Job/timer name"),
|
|
46534
|
+
scope: exports_external.optional(exports_external.enum(["user", "system"])).describe("Job scope (default: user)")
|
|
46535
|
+
}, async ({ name, scope }) => {
|
|
46536
|
+
try {
|
|
46537
|
+
const job = await client.getJob(name, scope);
|
|
46538
|
+
return formatSuccess(job);
|
|
46539
|
+
} catch (err) {
|
|
46540
|
+
return handleToolError(err);
|
|
46541
|
+
}
|
|
46542
|
+
});
|
|
46543
|
+
server.tool("get_job_logs", "Get execution logs for a scheduled job (from journalctl)", {
|
|
46544
|
+
name: exports_external.string().describe("Job/timer name"),
|
|
46545
|
+
scope: exports_external.optional(exports_external.enum(["user", "system"])).describe("Job scope (default: user)"),
|
|
46546
|
+
lines: exports_external.optional(exports_external.number()).describe("Number of log lines to return (default: 100)")
|
|
46547
|
+
}, async ({ name, scope, lines }) => {
|
|
46548
|
+
try {
|
|
46549
|
+
const result = await client.getJobLogs(name, scope, lines);
|
|
46550
|
+
return formatSuccess(result);
|
|
46551
|
+
} catch (err) {
|
|
46552
|
+
return handleToolError(err);
|
|
46553
|
+
}
|
|
46554
|
+
});
|
|
46555
|
+
server.tool("create_job", "Create a new scheduled job (Linux systemd only). Creates a .timer and .service unit file.", {
|
|
46556
|
+
name: exports_external.string().describe("Job name (alphanumeric, hyphens, underscores only)"),
|
|
46557
|
+
description: exports_external.string().describe("Human-readable description"),
|
|
46558
|
+
schedule: exports_external.string().describe('systemd OnCalendar schedule (e.g., "daily", "*-*-* 09:00:00", "Mon..Fri 09:00")'),
|
|
46559
|
+
command: exports_external.string().describe("Command to execute"),
|
|
46560
|
+
workingDirectory: exports_external.optional(exports_external.string()).describe("Working directory for the command"),
|
|
46561
|
+
environment: exports_external.optional(exports_external.record(exports_external.string())).describe("Environment variables as key-value pairs"),
|
|
46562
|
+
persistent: exports_external.optional(exports_external.boolean()).describe("Run missed executions on next boot (default: true)")
|
|
46563
|
+
}, async ({ name, description, schedule, command, workingDirectory, environment, persistent }) => {
|
|
46564
|
+
try {
|
|
46565
|
+
const result = await client.createJob({
|
|
46566
|
+
name,
|
|
46567
|
+
description,
|
|
46568
|
+
schedule,
|
|
46569
|
+
command,
|
|
46570
|
+
workingDirectory,
|
|
46571
|
+
environment,
|
|
46572
|
+
persistent
|
|
46573
|
+
});
|
|
46574
|
+
return formatSuccess(result);
|
|
46575
|
+
} catch (err) {
|
|
46576
|
+
return handleToolError(err);
|
|
46577
|
+
}
|
|
46578
|
+
});
|
|
46579
|
+
server.tool("update_job", "Update a scheduled job (Linux systemd only)", {
|
|
46580
|
+
name: exports_external.string().describe("Job name to update"),
|
|
46581
|
+
description: exports_external.optional(exports_external.string()).describe("New description"),
|
|
46582
|
+
schedule: exports_external.optional(exports_external.string()).describe("New schedule"),
|
|
46583
|
+
command: exports_external.optional(exports_external.string()).describe("New command"),
|
|
46584
|
+
workingDirectory: exports_external.optional(exports_external.string()).describe("New working directory"),
|
|
46585
|
+
environment: exports_external.optional(exports_external.record(exports_external.string())).describe("New environment variables"),
|
|
46586
|
+
persistent: exports_external.optional(exports_external.boolean()).describe("Run missed executions on next boot")
|
|
46587
|
+
}, async ({ name, description, schedule, command, workingDirectory, environment, persistent }) => {
|
|
46588
|
+
try {
|
|
46589
|
+
const result = await client.updateJob(name, {
|
|
46590
|
+
description,
|
|
46591
|
+
schedule,
|
|
46592
|
+
command,
|
|
46593
|
+
workingDirectory,
|
|
46594
|
+
environment,
|
|
46595
|
+
persistent
|
|
46596
|
+
});
|
|
46597
|
+
return formatSuccess(result);
|
|
46598
|
+
} catch (err) {
|
|
46599
|
+
return handleToolError(err);
|
|
46600
|
+
}
|
|
46601
|
+
});
|
|
46602
|
+
server.tool("delete_job", "Delete a scheduled job (Linux systemd user jobs only)", {
|
|
46603
|
+
name: exports_external.string().describe("Job name to delete")
|
|
46604
|
+
}, async ({ name }) => {
|
|
46605
|
+
try {
|
|
46606
|
+
const result = await client.deleteJob(name);
|
|
46607
|
+
return formatSuccess(result);
|
|
46608
|
+
} catch (err) {
|
|
46609
|
+
return handleToolError(err);
|
|
46610
|
+
}
|
|
46611
|
+
});
|
|
46612
|
+
server.tool("enable_job", "Enable a scheduled job's timer so it runs on schedule", {
|
|
46613
|
+
name: exports_external.string().describe("Job name"),
|
|
46614
|
+
scope: exports_external.optional(exports_external.enum(["user", "system"])).describe("Job scope (default: user)")
|
|
46615
|
+
}, async ({ name, scope }) => {
|
|
46616
|
+
try {
|
|
46617
|
+
const result = await client.enableJob(name, scope);
|
|
46618
|
+
return formatSuccess(result);
|
|
46619
|
+
} catch (err) {
|
|
46620
|
+
return handleToolError(err);
|
|
46621
|
+
}
|
|
46622
|
+
});
|
|
46623
|
+
server.tool("disable_job", "Disable a scheduled job's timer so it stops running", {
|
|
46624
|
+
name: exports_external.string().describe("Job name"),
|
|
46625
|
+
scope: exports_external.optional(exports_external.enum(["user", "system"])).describe("Job scope (default: user)")
|
|
46626
|
+
}, async ({ name, scope }) => {
|
|
46627
|
+
try {
|
|
46628
|
+
const result = await client.disableJob(name, scope);
|
|
46629
|
+
return formatSuccess(result);
|
|
46630
|
+
} catch (err) {
|
|
46631
|
+
return handleToolError(err);
|
|
46632
|
+
}
|
|
46633
|
+
});
|
|
46634
|
+
server.tool("run_job_now", "Trigger immediate execution of a scheduled job", {
|
|
46635
|
+
name: exports_external.string().describe("Job name"),
|
|
46636
|
+
scope: exports_external.optional(exports_external.enum(["user", "system"])).describe("Job scope (default: user)")
|
|
46637
|
+
}, async ({ name, scope }) => {
|
|
46638
|
+
try {
|
|
46639
|
+
const result = await client.runJobNow(name, scope);
|
|
46640
|
+
return formatSuccess(result);
|
|
46641
|
+
} catch (err) {
|
|
46642
|
+
return handleToolError(err);
|
|
46643
|
+
}
|
|
46644
|
+
});
|
|
46645
|
+
};
|
|
46646
|
+
var init_jobs = __esm(() => {
|
|
46647
|
+
init_zod2();
|
|
46648
|
+
init_utils();
|
|
46649
|
+
JobScopeSchema = exports_external.enum(["all", "user", "system"]);
|
|
46650
|
+
});
|
|
46651
|
+
|
|
46398
46652
|
// cli/src/mcp/tools/index.ts
|
|
46399
46653
|
function registerTools(server, client) {
|
|
46400
46654
|
registerCoreTools(server, client);
|
|
@@ -46415,6 +46669,7 @@ function registerTools(server, client) {
|
|
|
46415
46669
|
registerMemoryTools(server, client);
|
|
46416
46670
|
registerMemoryFileTools(server, client);
|
|
46417
46671
|
registerSearchTools(server, client);
|
|
46672
|
+
registerJobTools(server, client);
|
|
46418
46673
|
}
|
|
46419
46674
|
var init_tools = __esm(() => {
|
|
46420
46675
|
init_core5();
|
|
@@ -46435,6 +46690,7 @@ var init_tools = __esm(() => {
|
|
|
46435
46690
|
init_memory_file();
|
|
46436
46691
|
init_messaging();
|
|
46437
46692
|
init_search();
|
|
46693
|
+
init_jobs();
|
|
46438
46694
|
init_types4();
|
|
46439
46695
|
});
|
|
46440
46696
|
|
|
@@ -46453,7 +46709,7 @@ async function runMcpServer(urlOverride, portOverride) {
|
|
|
46453
46709
|
const client = new FulcrumClient(urlOverride, portOverride);
|
|
46454
46710
|
const server = new McpServer({
|
|
46455
46711
|
name: "fulcrum",
|
|
46456
|
-
version: "4.1.
|
|
46712
|
+
version: "4.1.2"
|
|
46457
46713
|
});
|
|
46458
46714
|
registerTools(server, client);
|
|
46459
46715
|
const transport = new StdioServerTransport;
|
|
@@ -48802,7 +49058,7 @@ var marketplace_default = `{
|
|
|
48802
49058
|
"name": "fulcrum",
|
|
48803
49059
|
"source": "./",
|
|
48804
49060
|
"description": "Task orchestration for Claude Code",
|
|
48805
|
-
"version": "4.1.
|
|
49061
|
+
"version": "4.1.2",
|
|
48806
49062
|
"skills": [
|
|
48807
49063
|
"./skills/fulcrum"
|
|
48808
49064
|
],
|
|
@@ -50115,7 +50371,7 @@ function compareVersions(v1, v2) {
|
|
|
50115
50371
|
var package_default = {
|
|
50116
50372
|
name: "@knowsuchagency/fulcrum",
|
|
50117
50373
|
private: true,
|
|
50118
|
-
version: "4.1.
|
|
50374
|
+
version: "4.1.2",
|
|
50119
50375
|
description: "Harness Attention. Orchestrate Agents. Ship.",
|
|
50120
50376
|
license: "PolyForm-Perimeter-1.0.0",
|
|
50121
50377
|
type: "module",
|
|
Binary file
|