@rubytech/create-maxy-code 0.1.348 → 0.1.350
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/package.json +1 -1
- package/payload/platform/docs/superpowers/plans/2026-06-23-account-filesystem-schema.md +544 -0
- package/payload/platform/docs/superpowers/specs/2026-06-23-account-filesystem-schema-design.md +170 -0
- package/payload/platform/lib/graph-style/dist/index.d.ts +1 -1
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-style/dist/index.js +20 -12
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +9 -9
- package/payload/platform/lib/graph-style/src/index.ts +20 -12
- package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-write/dist/index.js +3 -2
- package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-write/src/index.ts +3 -2
- package/payload/platform/neo4j/schema.cypher +110 -31
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh +68 -0
- package/payload/platform/plugins/admin/hooks/fs-schema-guard.sh +108 -0
- package/payload/platform/plugins/admin/hooks/lib/maxy-mcp-plugins.txt +1 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +36 -3
- package/payload/platform/plugins/docs/references/calendar-booking.md +1 -1
- package/payload/platform/plugins/docs/references/joblogic.md +28 -0
- package/payload/platform/plugins/docs/references/memory-guide.md +1 -1
- package/payload/platform/plugins/joblogic/.claude-plugin/plugin.json +21 -0
- package/payload/platform/plugins/joblogic/PLUGIN.md +182 -0
- package/payload/platform/plugins/joblogic/lib/mcp-spawn-tee/index.js +193 -0
- package/payload/platform/plugins/joblogic/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/joblogic/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/joblogic/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/index.js +229 -0
- package/payload/platform/plugins/joblogic/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.d.ts +31 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.d.ts.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.js +78 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.js.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/client.d.ts +35 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/client.d.ts.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/client.js +106 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/client.js.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.d.ts +8 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.d.ts.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.js +41 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.js.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.d.ts +21 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.d.ts.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.js +47 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.js.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/package.json +10 -0
- package/payload/platform/plugins/joblogic/skills/joblogic/SKILL.md +32 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relationship-patterns.test.js +28 -22
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relationship-patterns.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js +8 -4
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-write-path-task802.test.js +39 -10
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-write-path-task802.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/relationship-patterns.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/relationship-patterns.js +1 -1
- package/payload/platform/plugins/memory/references/schema-construction.md +152 -57
- package/payload/platform/plugins/memory/references/schema-trades.md +22 -24
- package/payload/platform/scripts/lib/provision-account-dir.sh +22 -3
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +46 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +45 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +11 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +18 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +1 -0
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js +16 -0
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
- package/payload/platform/templates/account-schema/SCHEMA.md +54 -0
- package/payload/server/public/assets/{AdminLoginScreens-B_5wYcR-.js → AdminLoginScreens-BEzoTk56.js} +1 -1
- package/payload/server/public/assets/{AdminShell-CMV3XY-s.js → AdminShell-BG7YD4UO.js} +1 -1
- package/payload/server/public/assets/{Checkbox-LplFUKpb.js → Checkbox-CvTFOczj.js} +1 -1
- package/payload/server/public/assets/{OperatorConversations-C4Cx814-.css → OperatorConversations-CnP9Y6g2.css} +1 -1
- package/payload/server/public/assets/{admin-3QQtd7j2.js → admin-Bhxa1HOy.js} +1 -1
- package/payload/server/public/assets/{browser-D5q26eZL.js → browser-BAQFuyma.js} +1 -1
- package/payload/server/public/assets/calendar-sSr6zUjW.js +1 -0
- package/payload/server/public/assets/chat-ZH4Fsyu0.js +1 -0
- package/payload/server/public/assets/data-CBrgiyPM.js +1 -0
- package/payload/server/public/assets/{graph-DFQbqmi9.js → graph-DbGjFYPS.js} +1 -1
- package/payload/server/public/assets/graph-labels-IGIEr-uc.js +1 -0
- package/payload/server/public/assets/{operator-BlCyq4q1.js → operator-Ba5afDyM.js} +1 -1
- package/payload/server/public/assets/{page-drg1V-yA.js → page-D7uwMUOy.js} +1 -1
- package/payload/server/public/assets/{public-BVjeYfsD.js → public-DikYWzOd.js} +1 -1
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +4 -4
- package/payload/server/public/chat.html +5 -5
- package/payload/server/public/data.html +4 -4
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +6 -6
- package/payload/server/public/operator.html +7 -7
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +92 -31
- package/payload/server/public/assets/calendar-Csbdj7rp.js +0 -1
- package/payload/server/public/assets/chat-Ct86qtMI.js +0 -1
- package/payload/server/public/assets/data-HwT5wPmj.js +0 -1
- package/payload/server/public/assets/graph-labels-kCti2CaC.js +0 -1
- /package/payload/server/public/assets/{OperatorConversations-Bp-g80PM.js → OperatorConversations-DU8CqO-z.js} +0 -0
|
@@ -447,7 +447,8 @@ DROP INDEX entity_search_admin IF EXISTS;
|
|
|
447
447
|
CREATE FULLTEXT INDEX entity_search_admin IF NOT EXISTS
|
|
448
448
|
FOR (n:LocalBusiness|Service|PriceSpecification|OpeningHoursSpecification|Organization
|
|
449
449
|
|Listing|Property|Viewing|Offer|PostalAddress
|
|
450
|
-
|Job|
|
|
450
|
+
|Job|Quote|QuoteLine|Valuation|Milestone|VariationNote|Invoice|InboundInvoice|JobCost|WhatsAppGroup
|
|
451
|
+
|Customer|Site|Asset|Visit|Part|PurchaseOrder|PpmContract|Contact|PurchaseOrderLine|InvoiceLine|InvoicePayment|Credit
|
|
451
452
|
|Person|UserProfile|Preference|AdminUser|AccessGrant
|
|
452
453
|
|KnowledgeDocument|ConversationArchive|Section|Chunk|DigitalDocument|CreativeWork|Question|FAQPage|DefinedTerm|Review|ImageObject
|
|
453
454
|
|Conversation|AdminConversation|PublicConversation|Message|UserMessage|AssistantMessage|ToolCall
|
|
@@ -1348,22 +1349,27 @@ CREATE INDEX postal_address_postcode IF NOT EXISTS
|
|
|
1348
1349
|
FOR (a:PostalAddress) ON (a.postalCode);
|
|
1349
1350
|
|
|
1350
1351
|
// ----------------------------------------------------------
|
|
1351
|
-
// Construction
|
|
1352
|
-
//
|
|
1352
|
+
// Construction / field-service ontology — the canonical SiteDesk
|
|
1353
|
+
// service-delivery model, authored from JobLogic's field-service domain
|
|
1354
|
+
// (Task 1084; supersedes the Task 650 homegrown set). Documented at
|
|
1353
1355
|
// platform/plugins/memory/references/schema-construction.md.
|
|
1354
1356
|
//
|
|
1355
1357
|
// Active vertical: brand.json#vertical = "schema-construction". Declared
|
|
1356
1358
|
// here so a fresh deploy registers each label before its first write
|
|
1357
1359
|
// (same fresh-install bootstrap contract as the estate-agent block above).
|
|
1358
1360
|
//
|
|
1359
|
-
//
|
|
1360
|
-
//
|
|
1361
|
-
//
|
|
1362
|
-
//
|
|
1363
|
-
//
|
|
1364
|
-
//
|
|
1365
|
-
//
|
|
1366
|
-
//
|
|
1361
|
+
// Top-level natural-key labels: Job, Customer, Site, Asset, Visit, Quote,
|
|
1362
|
+
// Invoice (customer, outbound), InboundInvoice (supplier, on a PO), Part,
|
|
1363
|
+
// PurchaseOrder, PpmContract, WhatsAppGroup. Child
|
|
1364
|
+
// write-time-MERGE labels (accountId-index only, mirrors :ImageObject):
|
|
1365
|
+
// QuoteLine, Valuation, Milestone, VariationNote, JobCost, Contact,
|
|
1366
|
+
// PurchaseOrderLine, InvoiceLine, InvoicePayment, Credit. :Engineer (on
|
|
1367
|
+
// :Person) and :Supplier (on :Organization) are additional labels on a base
|
|
1368
|
+
// node — no standalone line here, same as :Applicant in the estate-agent
|
|
1369
|
+
// block.
|
|
1370
|
+
//
|
|
1371
|
+
// Search quality (Task 652; renamed Task 1084). The four text-bearing labels —
|
|
1372
|
+
// :Job, :QuoteLine (trade + description), its :Quote, :VariationNote
|
|
1367
1373
|
// (description) — carry a vector index on `.embedding` (below), so the
|
|
1368
1374
|
// graph-write chokepoint auto-embeds them on write and they rank in the
|
|
1369
1375
|
// vector half of memory-search. The four are mirrored in
|
|
@@ -1394,11 +1400,16 @@ OPTIONS {
|
|
|
1394
1400
|
}
|
|
1395
1401
|
};
|
|
1396
1402
|
|
|
1397
|
-
|
|
1398
|
-
|
|
1403
|
+
// :LineItem -> :QuoteLine (Task 1084). A priced row of the client :Quote
|
|
1404
|
+
// schedule, not an incurred cost. The old-named indexes are dropped so a
|
|
1405
|
+
// database seeded before the rename does not keep a dead :LineItem index.
|
|
1406
|
+
DROP INDEX line_item_account IF EXISTS;
|
|
1407
|
+
DROP INDEX line_item_embedding IF EXISTS;
|
|
1408
|
+
CREATE INDEX quote_line_account IF NOT EXISTS
|
|
1409
|
+
FOR (ql:QuoteLine) ON (ql.accountId);
|
|
1399
1410
|
|
|
1400
|
-
CREATE VECTOR INDEX
|
|
1401
|
-
FOR (
|
|
1411
|
+
CREATE VECTOR INDEX quote_line_embedding IF NOT EXISTS
|
|
1412
|
+
FOR (ql:QuoteLine) ON (ql.embedding)
|
|
1402
1413
|
OPTIONS {
|
|
1403
1414
|
indexConfig: {
|
|
1404
1415
|
`vector.dimensions`: 768,
|
|
@@ -1415,14 +1426,20 @@ FOR (v:Valuation) ON (v.accountId);
|
|
|
1415
1426
|
CREATE INDEX milestone_account IF NOT EXISTS
|
|
1416
1427
|
FOR (m:Milestone) ON (m.accountId);
|
|
1417
1428
|
|
|
1418
|
-
|
|
1419
|
-
|
|
1429
|
+
// :QuoteDocument -> :Quote (Task 1084) — JobLogic's canonical term for the
|
|
1430
|
+
// priced estimate/contract. New constraint+index names (IF NOT EXISTS matches
|
|
1431
|
+
// by name); old names dropped so the rename takes on pre-seeded databases.
|
|
1432
|
+
DROP CONSTRAINT quote_document_account_ref_unique IF EXISTS;
|
|
1433
|
+
DROP INDEX quote_document_account IF EXISTS;
|
|
1434
|
+
DROP INDEX quote_document_embedding IF EXISTS;
|
|
1435
|
+
CREATE CONSTRAINT quote_account_ref_unique IF NOT EXISTS
|
|
1436
|
+
FOR (q:Quote) REQUIRE (q.accountId, q.ref) IS UNIQUE;
|
|
1420
1437
|
|
|
1421
|
-
CREATE INDEX
|
|
1422
|
-
FOR (q:
|
|
1438
|
+
CREATE INDEX quote_account IF NOT EXISTS
|
|
1439
|
+
FOR (q:Quote) ON (q.accountId);
|
|
1423
1440
|
|
|
1424
|
-
CREATE VECTOR INDEX
|
|
1425
|
-
FOR (q:
|
|
1441
|
+
CREATE VECTOR INDEX quote_embedding IF NOT EXISTS
|
|
1442
|
+
FOR (q:Quote) ON (q.embedding)
|
|
1426
1443
|
OPTIONS {
|
|
1427
1444
|
indexConfig: {
|
|
1428
1445
|
`vector.dimensions`: 768,
|
|
@@ -1445,22 +1462,31 @@ OPTIONS {
|
|
|
1445
1462
|
}
|
|
1446
1463
|
};
|
|
1447
1464
|
|
|
1448
|
-
//
|
|
1449
|
-
//
|
|
1450
|
-
//
|
|
1451
|
-
//
|
|
1452
|
-
//
|
|
1453
|
-
//
|
|
1454
|
-
//
|
|
1465
|
+
// Two distinct invoice documents (Task 1084). :InboundInvoice is the received
|
|
1466
|
+
// supplier invoice, keyed (accountId, supplier, confirmationNumber) and now
|
|
1467
|
+
// linked to a :PurchaseOrder. The customer-facing outbound invoice is the base
|
|
1468
|
+
// :Invoice (schema-base.md), registered here so it surfaces on /graph and in
|
|
1469
|
+
// search alongside the construction spine; its billing chain is the child
|
|
1470
|
+
// :InvoiceLine / :InvoicePayment / :Credit. The two labels coexist because they
|
|
1471
|
+
// carry different required fields (supplier vs customer). The Task-802 DROP of
|
|
1472
|
+
// the old invoiceNumber-keyed constraint is retained for pre-rename databases.
|
|
1455
1473
|
DROP CONSTRAINT inbound_invoice_account_supplier_num_unique IF EXISTS;
|
|
1456
1474
|
CREATE CONSTRAINT inbound_invoice_account_supplier_confirmation_unique IF NOT EXISTS
|
|
1457
1475
|
FOR (ii:InboundInvoice) REQUIRE (ii.accountId, ii.supplier, ii.confirmationNumber) IS UNIQUE;
|
|
1458
|
-
|
|
1459
1476
|
CREATE INDEX inbound_invoice_account IF NOT EXISTS
|
|
1460
1477
|
FOR (ii:InboundInvoice) ON (ii.accountId);
|
|
1461
1478
|
|
|
1462
|
-
CREATE INDEX
|
|
1463
|
-
FOR (
|
|
1479
|
+
CREATE INDEX invoice_account IF NOT EXISTS
|
|
1480
|
+
FOR (i:Invoice) ON (i.accountId);
|
|
1481
|
+
|
|
1482
|
+
// :CheckIn -> :JobCost (Task 1084). A logged labour day is an incurred labour
|
|
1483
|
+
// cost (costType:'labour'); JobCost also carries material/travel/mileage/
|
|
1484
|
+
// expense/subcontractor/scheduleOfRates lines. Clean natural key (costId).
|
|
1485
|
+
DROP INDEX checkin_account IF EXISTS;
|
|
1486
|
+
CREATE CONSTRAINT job_cost_account_costid_unique IF NOT EXISTS
|
|
1487
|
+
FOR (jc:JobCost) REQUIRE (jc.accountId, jc.costId) IS UNIQUE;
|
|
1488
|
+
CREATE INDEX job_cost_account IF NOT EXISTS
|
|
1489
|
+
FOR (jc:JobCost) ON (jc.accountId);
|
|
1464
1490
|
|
|
1465
1491
|
CREATE CONSTRAINT whatsapp_group_account_groupid_unique IF NOT EXISTS
|
|
1466
1492
|
FOR (wg:WhatsAppGroup) REQUIRE (wg.accountId, wg.groupId) IS UNIQUE;
|
|
@@ -1468,6 +1494,59 @@ FOR (wg:WhatsAppGroup) REQUIRE (wg.accountId, wg.groupId) IS UNIQUE;
|
|
|
1468
1494
|
CREATE INDEX whatsapp_group_account IF NOT EXISTS
|
|
1469
1495
|
FOR (wg:WhatsAppGroup) ON (wg.accountId);
|
|
1470
1496
|
|
|
1497
|
+
// JobLogic service-delivery spine (Task 1084). Top-level natural-key labels.
|
|
1498
|
+
CREATE CONSTRAINT customer_account_customerid_unique IF NOT EXISTS
|
|
1499
|
+
FOR (c:Customer) REQUIRE (c.accountId, c.customerId) IS UNIQUE;
|
|
1500
|
+
CREATE INDEX customer_account IF NOT EXISTS
|
|
1501
|
+
FOR (c:Customer) ON (c.accountId);
|
|
1502
|
+
|
|
1503
|
+
CREATE CONSTRAINT site_account_siteid_unique IF NOT EXISTS
|
|
1504
|
+
FOR (s:Site) REQUIRE (s.accountId, s.siteId) IS UNIQUE;
|
|
1505
|
+
CREATE INDEX site_account IF NOT EXISTS
|
|
1506
|
+
FOR (s:Site) ON (s.accountId);
|
|
1507
|
+
|
|
1508
|
+
CREATE CONSTRAINT asset_account_assetid_unique IF NOT EXISTS
|
|
1509
|
+
FOR (a:Asset) REQUIRE (a.accountId, a.assetId) IS UNIQUE;
|
|
1510
|
+
CREATE INDEX asset_account IF NOT EXISTS
|
|
1511
|
+
FOR (a:Asset) ON (a.accountId);
|
|
1512
|
+
|
|
1513
|
+
CREATE CONSTRAINT visit_account_visitid_unique IF NOT EXISTS
|
|
1514
|
+
FOR (v:Visit) REQUIRE (v.accountId, v.visitId) IS UNIQUE;
|
|
1515
|
+
CREATE INDEX visit_account IF NOT EXISTS
|
|
1516
|
+
FOR (v:Visit) ON (v.accountId);
|
|
1517
|
+
|
|
1518
|
+
CREATE CONSTRAINT part_account_partid_unique IF NOT EXISTS
|
|
1519
|
+
FOR (p:Part) REQUIRE (p.accountId, p.partId) IS UNIQUE;
|
|
1520
|
+
CREATE INDEX part_account IF NOT EXISTS
|
|
1521
|
+
FOR (p:Part) ON (p.accountId);
|
|
1522
|
+
|
|
1523
|
+
CREATE CONSTRAINT purchase_order_account_ponumber_unique IF NOT EXISTS
|
|
1524
|
+
FOR (po:PurchaseOrder) REQUIRE (po.accountId, po.poNumber) IS UNIQUE;
|
|
1525
|
+
CREATE INDEX purchase_order_account IF NOT EXISTS
|
|
1526
|
+
FOR (po:PurchaseOrder) ON (po.accountId);
|
|
1527
|
+
|
|
1528
|
+
CREATE CONSTRAINT ppm_contract_account_contractid_unique IF NOT EXISTS
|
|
1529
|
+
FOR (pc:PpmContract) REQUIRE (pc.accountId, pc.contractId) IS UNIQUE;
|
|
1530
|
+
CREATE INDEX ppm_contract_account IF NOT EXISTS
|
|
1531
|
+
FOR (pc:PpmContract) ON (pc.accountId);
|
|
1532
|
+
|
|
1533
|
+
// Child write-time-MERGE labels (accountId-index only; uniqueness is the
|
|
1534
|
+
// writer's MERGE key, mirrors :ImageObject / :Milestone).
|
|
1535
|
+
CREATE INDEX contact_account IF NOT EXISTS
|
|
1536
|
+
FOR (ct:Contact) ON (ct.accountId);
|
|
1537
|
+
|
|
1538
|
+
CREATE INDEX purchase_order_line_account IF NOT EXISTS
|
|
1539
|
+
FOR (pol:PurchaseOrderLine) ON (pol.accountId);
|
|
1540
|
+
|
|
1541
|
+
CREATE INDEX invoice_line_account IF NOT EXISTS
|
|
1542
|
+
FOR (il:InvoiceLine) ON (il.accountId);
|
|
1543
|
+
|
|
1544
|
+
CREATE INDEX invoice_payment_account IF NOT EXISTS
|
|
1545
|
+
FOR (ip:InvoicePayment) ON (ip.accountId);
|
|
1546
|
+
|
|
1547
|
+
CREATE INDEX credit_account IF NOT EXISTS
|
|
1548
|
+
FOR (cr:Credit) ON (cr.accountId);
|
|
1549
|
+
|
|
1471
1550
|
// ----------------------------------------------------------
|
|
1472
1551
|
// Voice Mirror (Task 356 + Task 462) — authorial-voice capture + application
|
|
1473
1552
|
// ----------------------------------------------------------
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Regression test for fs-schema-guard.sh.
|
|
3
|
+
#
|
|
4
|
+
# Covers:
|
|
5
|
+
# 1. Non-Write/Edit/NotebookEdit tool -> ALLOW (early exit)
|
|
6
|
+
# 2. Write to projects/Acme/a.txt (flat) -> ALLOW
|
|
7
|
+
# 3. Write to documents/a.pdf (loose) -> ALLOW
|
|
8
|
+
# 4. Write to output/deep/nested/x.png (tool-owned) -> ALLOW (any depth)
|
|
9
|
+
# 5. Write to projects/Acme/deploy/sources/a.txt -> BLOCK over-deep
|
|
10
|
+
# 6. Write to projects/Acme/deploy/a.txt -> BLOCK over-deep
|
|
11
|
+
# 7. Write to whim/x.txt (novel top-level) -> BLOCK top-level
|
|
12
|
+
# 8. Write to an absolute path outside the account dir -> ALLOW (not our concern)
|
|
13
|
+
# 9. NotebookEdit over-deep (notebook_path) -> BLOCK over-deep
|
|
14
|
+
# 10. Empty stdin (cannot inspect) -> BLOCK fail-closed
|
|
15
|
+
# 11. Allowed-set parse matches the documented set -> assertion
|
|
16
|
+
set -u
|
|
17
|
+
|
|
18
|
+
HOOK="$(cd "$(dirname "$0")/.." && pwd)/fs-schema-guard.sh"
|
|
19
|
+
[ -x "$HOOK" ] || { echo "FAIL: $HOOK not executable" >&2; exit 1; }
|
|
20
|
+
|
|
21
|
+
# A scratch account dir with a seeded SCHEMA.md (copied from the shipped template).
|
|
22
|
+
REPO_ROOT="$(cd "$(dirname "$0")/../../../.." && pwd)"
|
|
23
|
+
TEMPLATE="$REPO_ROOT/templates/account-schema/SCHEMA.md"
|
|
24
|
+
[ -f "$TEMPLATE" ] || { echo "FAIL: template missing at $TEMPLATE" >&2; exit 1; }
|
|
25
|
+
ACCT=$(mktemp -d)
|
|
26
|
+
trap 'rm -rf "$ACCT"' EXIT
|
|
27
|
+
cp "$TEMPLATE" "$ACCT/SCHEMA.md"
|
|
28
|
+
mkdir -p "$ACCT/projects/Acme" "$ACCT/documents" "$ACCT/output"
|
|
29
|
+
|
|
30
|
+
PASS=0; FAIL=0
|
|
31
|
+
# Run one case in the account dir as cwd.
|
|
32
|
+
# $1 name $2 stdin $3 expected_exit $4 expected_log_re ("" = none)
|
|
33
|
+
run_case() {
|
|
34
|
+
local name="$1" stdin="$2" exp="$3" re="$4" ef actual
|
|
35
|
+
ef=$(mktemp)
|
|
36
|
+
( cd "$ACCT" && printf '%s' "$stdin" | bash "$HOOK" >/dev/null 2>"$ef" )
|
|
37
|
+
actual=$?
|
|
38
|
+
local sc; sc=$(cat "$ef"); rm -f "$ef"
|
|
39
|
+
local ok=1
|
|
40
|
+
[ "$actual" -eq "$exp" ] || ok=0
|
|
41
|
+
if [ -n "$re" ] && ! printf '%s' "$sc" | grep -Eq "$re"; then ok=0; fi
|
|
42
|
+
if [ $ok -eq 1 ]; then echo "PASS: $name (exit=$actual)"; PASS=$((PASS+1));
|
|
43
|
+
else echo "FAIL: $name (exit=$actual, want $exp; log want /$re/)" >&2; FAIL=$((FAIL+1)); fi
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
mkenv() { printf '{"hook_event_name":"PreToolUse","tool_name":"%s","tool_input":{"%s":"%s"}}' "$1" "$2" "$3"; }
|
|
47
|
+
|
|
48
|
+
run_case "non-write allow" "$(mkenv Bash command 'ls')" 0 ""
|
|
49
|
+
run_case "flat project allow" "$(mkenv Write file_path 'projects/Acme/a.txt')" 0 ""
|
|
50
|
+
run_case "loose document allow" "$(mkenv Write file_path 'documents/a.pdf')" 0 ""
|
|
51
|
+
run_case "tool-owned deep allow" "$(mkenv Write file_path 'output/deep/nested/x.png')" 0 ""
|
|
52
|
+
run_case "over-deep block" "$(mkenv Write file_path 'projects/Acme/deploy/sources/a.txt')" 2 "fs-guard. blocked path=projects/Acme/deploy/sources/a.txt reason=over-deep"
|
|
53
|
+
run_case "over-deep one block" "$(mkenv Write file_path 'projects/Acme/deploy/a.txt')" 2 "reason=over-deep"
|
|
54
|
+
run_case "top-level block" "$(mkenv Write file_path 'whim/x.txt')" 2 "fs-guard. blocked path=whim/x.txt reason=top-level"
|
|
55
|
+
run_case "outside acct allow" "$(mkenv Write file_path '/etc/passwd')" 0 ""
|
|
56
|
+
run_case "notebook over-deep" "$(mkenv NotebookEdit notebook_path 'projects/Acme/deploy/n.ipynb')" 2 "reason=over-deep"
|
|
57
|
+
run_case "empty stdin block" "" 2 "no stdin"
|
|
58
|
+
# JSON null file_path must coerce to empty (allow), not the literal string "None".
|
|
59
|
+
run_case "null path allow" '{"hook_event_name":"PreToolUse","tool_name":"Write","tool_input":{"file_path":null}}' 0 ""
|
|
60
|
+
|
|
61
|
+
# Allowed-set parse == documented set.
|
|
62
|
+
EXPECT="projects contacts documents url-get output generated extracted uploads agents specialists sites public cache secrets state logs tmp SCHEMA.md account.json AGENTS.md .claude .git"
|
|
63
|
+
GOT=$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$ACCT/SCHEMA.md" | tr '\n' ' ' | sed 's/ *$//')
|
|
64
|
+
if [ "$GOT" = "$EXPECT" ]; then echo "PASS: allowed-set parse"; PASS=$((PASS+1));
|
|
65
|
+
else echo "FAIL: allowed-set parse: got [$GOT]" >&2; FAIL=$((FAIL+1)); fi
|
|
66
|
+
|
|
67
|
+
echo "----- $PASS passed, $FAIL failed -----"
|
|
68
|
+
[ $FAIL -eq 0 ]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# fs-schema-guard — PreToolUse Write/Edit/NotebookEdit path guard.
|
|
3
|
+
#
|
|
4
|
+
# Enforces the seeded account-dir file schema (see <accountDir>/SCHEMA.md):
|
|
5
|
+
# - the target's first path segment must be in the allowed top-level set
|
|
6
|
+
# (parsed from the fenced ```allowed-top-level block of the account's
|
|
7
|
+
# SCHEMA.md — our own structured data, not CLI prose);
|
|
8
|
+
# - a target under an operator-data bucket (projects/ contacts/) may be at
|
|
9
|
+
# most <bucket>/<entity>/<file> deep; documents/ may be at most
|
|
10
|
+
# <bucket>/<folder>/<file> deep. Tool-owned dirs pass at any depth.
|
|
11
|
+
#
|
|
12
|
+
# The hook governs the account dir only. A path resolving outside the account
|
|
13
|
+
# dir (cwd) is allowed — platform-source writes are PLATFORM_BOUNDARY's concern.
|
|
14
|
+
#
|
|
15
|
+
# Exit codes: 0 = allow, 2 = block (stderr shown to the agent). Fail closed when
|
|
16
|
+
# the tool call cannot be inspected (tty or empty stdin). Every block logs
|
|
17
|
+
# [fs-guard] blocked path=<rel> reason=<top-level|over-deep>
|
|
18
|
+
# No task numbers / internal refs in any operator-visible string.
|
|
19
|
+
|
|
20
|
+
set -uo pipefail
|
|
21
|
+
|
|
22
|
+
# Fail closed if attached to a terminal (no JSON envelope coming).
|
|
23
|
+
if [ -t 0 ]; then
|
|
24
|
+
echo "Blocked: fs-schema-guard received no stdin (cannot inspect the write). Failing closed." >&2
|
|
25
|
+
exit 2
|
|
26
|
+
fi
|
|
27
|
+
INPUT=$(cat)
|
|
28
|
+
if [ -z "$INPUT" ]; then
|
|
29
|
+
echo "Blocked: fs-schema-guard received no stdin (cannot inspect the write). Failing closed." >&2
|
|
30
|
+
exit 2
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
field() {
|
|
34
|
+
printf '%s' "$INPUT" | python3 -c "
|
|
35
|
+
import sys, json
|
|
36
|
+
try:
|
|
37
|
+
d = json.load(sys.stdin)
|
|
38
|
+
v = (d.get('$1', {}) or {}).get('$2', '') if '$1' else d.get('$2', '')
|
|
39
|
+
print(v if isinstance(v, str) else '')
|
|
40
|
+
except Exception:
|
|
41
|
+
print('')
|
|
42
|
+
" 2>/dev/null || echo ""
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
TOOL_NAME=$(field '' tool_name)
|
|
46
|
+
case "$TOOL_NAME" in
|
|
47
|
+
Write|Edit) FILE_PATH=$(field tool_input file_path) ;;
|
|
48
|
+
NotebookEdit) FILE_PATH=$(field tool_input notebook_path) ;;
|
|
49
|
+
*) exit 0 ;;
|
|
50
|
+
esac
|
|
51
|
+
[ -z "$FILE_PATH" ] && exit 0
|
|
52
|
+
|
|
53
|
+
ACCOUNT_DIR="$PWD"
|
|
54
|
+
|
|
55
|
+
# Resolve to an account-relative path; allow anything outside the account dir.
|
|
56
|
+
REL=$(FILE_PATH="$FILE_PATH" ACCOUNT_DIR="$ACCOUNT_DIR" python3 -c '
|
|
57
|
+
import os, sys
|
|
58
|
+
fp = os.environ["FILE_PATH"]; acct = os.environ["ACCOUNT_DIR"]
|
|
59
|
+
ab = fp if os.path.isabs(fp) else os.path.join(acct, fp)
|
|
60
|
+
ab = os.path.normpath(ab); acct = os.path.normpath(acct)
|
|
61
|
+
if ab == acct or not ab.startswith(acct + os.sep):
|
|
62
|
+
print("") # outside the account dir (or the dir itself)
|
|
63
|
+
else:
|
|
64
|
+
print(os.path.relpath(ab, acct))
|
|
65
|
+
' 2>/dev/null || echo "")
|
|
66
|
+
[ -z "$REL" ] && exit 0
|
|
67
|
+
|
|
68
|
+
# Parse the allowed top-level set from the account's seeded SCHEMA.md.
|
|
69
|
+
ALLOWED=""
|
|
70
|
+
if [ -f "$ACCOUNT_DIR/SCHEMA.md" ]; then
|
|
71
|
+
ALLOWED=$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$ACCOUNT_DIR/SCHEMA.md")
|
|
72
|
+
fi
|
|
73
|
+
# Fail open on a missing/empty schema (an un-seeded legacy account must not have
|
|
74
|
+
# every write blocked). A re-provision seeds SCHEMA.md, after which the guard
|
|
75
|
+
# applies.
|
|
76
|
+
[ -z "$ALLOWED" ] && exit 0
|
|
77
|
+
|
|
78
|
+
SEG0=${REL%%/*}
|
|
79
|
+
|
|
80
|
+
# Top-level check.
|
|
81
|
+
if ! printf '%s\n' "$ALLOWED" | grep -qxF "$SEG0"; then
|
|
82
|
+
echo "[fs-guard] blocked path=$REL reason=top-level" >&2
|
|
83
|
+
echo "Blocked: '$SEG0' is not an allowed top-level entry in this account's data directory. The layout is fixed by SCHEMA.md — place operator data under projects/, contacts/, or documents/, and route any reorganisation through the data-manager specialist." >&2
|
|
84
|
+
exit 2
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
# Over-deep check for operator-data buckets. Count path segments.
|
|
88
|
+
depth=$(printf '%s' "$REL" | awk -F/ '{print NF}')
|
|
89
|
+
case "$SEG0" in
|
|
90
|
+
projects|contacts)
|
|
91
|
+
# Allowed: <bucket>/<entity>/<file> = 3 segments max; <bucket>/<file> = 2 ok.
|
|
92
|
+
if [ "$depth" -gt 3 ]; then
|
|
93
|
+
echo "[fs-guard] blocked path=$REL reason=over-deep" >&2
|
|
94
|
+
echo "Blocked: $SEG0/ is flat — one folder per entity, then files. '$REL' nests a subtree under an entity folder, which has no basis in the graph ontology. Keep files directly under $SEG0/<name>/, or route a reorganisation through the data-manager specialist." >&2
|
|
95
|
+
exit 2
|
|
96
|
+
fi
|
|
97
|
+
;;
|
|
98
|
+
documents)
|
|
99
|
+
# Allowed: documents/<file> = 2, documents/<folder>/<file> = 3 max.
|
|
100
|
+
if [ "$depth" -gt 3 ]; then
|
|
101
|
+
echo "[fs-guard] blocked path=$REL reason=over-deep" >&2
|
|
102
|
+
echo "Blocked: documents/ holds files or one folder deep. '$REL' nests deeper, which has no basis in the graph ontology. Route a reorganisation through the data-manager specialist." >&2
|
|
103
|
+
exit 2
|
|
104
|
+
fi
|
|
105
|
+
;;
|
|
106
|
+
esac
|
|
107
|
+
|
|
108
|
+
exit 0
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:33a6a5683cd3a9f34b0fcbd1d108c7fe20aece1d7d64ecc6da321ff0a8742f08
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -1556,7 +1556,7 @@ Every node also carries a provenance stamp — which agent wrote it, in which se
|
|
|
1556
1556
|
|
|
1557
1557
|
## Vertical schemas
|
|
1558
1558
|
|
|
1559
|
-
On top of the base graph, each brand boots one optional **vertical** — an extra set of entity types tailored to a trade. The vertical is named by `brand.json#vertical` and defined in a `schema-<name>.md` reference; the memory plugin loads it at startup and validates every write against base + the active vertical. Real Agent boots `schema-estate-agent` (Listing, Property, Viewing, Offer). SiteDesk boots `schema-construction`, which adds the
|
|
1559
|
+
On top of the base graph, each brand boots one optional **vertical** — an extra set of entity types tailored to a trade. The vertical is named by `brand.json#vertical` and defined in a `schema-<name>.md` reference; the memory plugin loads it at startup and validates every write against base + the active vertical. Real Agent boots `schema-estate-agent` (Listing, Property, Viewing, Offer). SiteDesk boots `schema-construction`, the canonical JobLogic-derived field-service ontology, which adds the service-delivery entities — `Job`, `Customer`, `Site`, `Asset`, `Visit`, `Quote`, `QuoteLine`, `Valuation`, `VariationNote`, `JobCost`, `Part`, `PurchaseOrder`, `Invoice`, `InboundInvoice`, `PpmContract`, `WhatsAppGroup` (with `Engineer` and `Supplier` as additional labels on `Person`/`Organization`) — grounded in real builder job folders so a job's customer, site, quote, valuations, variations, costs, and received supplier invoices all hang off one `Job` node. The default Maxy brand boots no vertical (base graph only).
|
|
1560
1560
|
|
|
1561
1561
|
## Public-facing summaries for customer-readable subjects
|
|
1562
1562
|
|
|
@@ -1880,6 +1880,39 @@ A QuickBooks connection goes stale if it sits unused for about 100 days, or if y
|
|
|
1880
1880
|
|
|
1881
1881
|
Switching your Intuit app from sandbox to production requires passing Intuit's own production security review. That is between you and Intuit; it is not something the agent does for you.
|
|
1882
1882
|
|
|
1883
|
+
---
|
|
1884
|
+
# JobLogic
|
|
1885
|
+
Source: https://docs.getmaxy.com/joblogic.md
|
|
1886
|
+
|
|
1887
|
+
# JobLogic
|
|
1888
|
+
|
|
1889
|
+
Connect JobLogic, the field-service management system, so the agent can read and update your jobs end to end — customers, sites, jobs, engineers, visits, quotes, invoices, assets, and timesheets — over JobLogic's REST API.
|
|
1890
|
+
|
|
1891
|
+
This connector is built for one team that already runs JobLogic as its system of record. Your field staff send a voice note or message to Maxy from the field; Maxy works out what they mean and makes the JobLogic update for them. Your staff never touch the JobLogic API themselves, so only Maxy's own connection talks to JobLogic.
|
|
1892
|
+
|
|
1893
|
+
## What you do once
|
|
1894
|
+
|
|
1895
|
+
**1. Ask JobLogic for API access.** Raise a ticket with JobLogic Support for API credentials. Ask for a test (UAT) account first, then production. They give you a client ID, a client secret, and your company tenant id.
|
|
1896
|
+
|
|
1897
|
+
**2. Whitelist your connection.** JobLogic only accepts API calls from network addresses you have registered with them. Maxy makes its calls from the machine it runs on, so that machine's public address is the one to register. If the machine moves between networks its address can change, so either run it from one place with a stable address, send its traffic through a fixed address, or ask JobLogic whether production needs this at all. If the address is not registered, calls are refused and the agent will tell you the connection was blocked.
|
|
1898
|
+
|
|
1899
|
+
**3. Hand the keys to the agent.** Tell the agent your client ID, secret, tenant id, and whether they are test or production. The agent stores them on your install only and never repeats them back.
|
|
1900
|
+
|
|
1901
|
+
## What the agent does after that
|
|
1902
|
+
|
|
1903
|
+
Everything else is automatic. The agent keeps the connection alive on its own, refreshing access behind the scenes, and can:
|
|
1904
|
+
|
|
1905
|
+
- **Read** anything — "what jobs are open for this site", "show this engineer's visits this week", "list unpaid invoices".
|
|
1906
|
+
- **Write** from a field instruction — create or reschedule a visit, log labour and materials against a job, set a job's status, approve a quote, raise an invoice, add a note or a photo to a job.
|
|
1907
|
+
|
|
1908
|
+
Because JobLogic's credential belongs to the company rather than to a person, the agent stamps each update with the engineer who reported it. You give the agent a list that maps each staff member's phone number to their JobLogic engineer record once, and it does the rest.
|
|
1909
|
+
|
|
1910
|
+
Each create carries a safety key, so if a message is retried or a reply goes missing the same instruction never creates a duplicate record.
|
|
1911
|
+
|
|
1912
|
+
## Testing
|
|
1913
|
+
|
|
1914
|
+
Do all your testing against the JobLogic test (UAT) environment first. Switch to production only once the test environment behaves the way you expect and your production keys and whitelisting are in place.
|
|
1915
|
+
|
|
1883
1916
|
---
|
|
1884
1917
|
# Calendar and Booking
|
|
1885
1918
|
Source: https://docs.getmaxy.com/calendar-booking.md
|
|
@@ -1898,7 +1931,7 @@ Open `/calendar` on your Maxy address (the same sign-in as the dashboard). It sh
|
|
|
1898
1931
|
|
|
1899
1932
|
Use **Today** and the arrows to move around, and the view buttons to switch. The calendar reads everything time-bound in your graph: meetings imported from a calendar export or created by a booking, appointments Maxy schedules for you in chat, and tasks that have a due date (shown on their day).
|
|
1900
1933
|
|
|
1901
|
-
Click any entry to open its details: the full title, time, location, and who is attending, with each attendee on its own line (name, and email beneath it). For a booked meeting you can also edit its title, time, or location, or delete it, and the grid updates straight away. You can **Download** a booked meeting as a calendar file (`.ics`) to add it to Apple, Google, or Outlook calendars;
|
|
1934
|
+
Click any entry to open its details: the full title, time, location, and who is attending, with each attendee on its own line (name, and email beneath it). For a booked meeting you can also edit its title, time, or location, or delete it, and the grid updates straight away. You can **Download** a booked meeting as a calendar file (`.ics`) to add it to Apple, Google, or Outlook calendars; when the meeting has attendees, Download asks whether to include them in the file before it saves, and a meeting with no attendees downloads straight away. The private note is never written into the downloaded file, since the file can travel outside your admin. In the edit view you can add an attendee (give a name, an email, or both) and remove one; adding someone who is already a contact reuses that contact rather than making a duplicate, and removing them takes them off the meeting but keeps the contact. The edit view also has a private note for that meeting, a place for your own reminders that only you and your team ever see; it never appears on the public booking page or anywhere outside your admin. Appointments Maxy schedules and due-dated tasks are shown for detail only and are changed where they are created, not here. New meetings are still made by a booking or by asking Maxy in chat, not from the calendar.
|
|
1902
1935
|
|
|
1903
1936
|
Everything on the calendar comes from records the rest of Maxy already uses, scoped to your account.
|
|
1904
1937
|
|
|
@@ -12,7 +12,7 @@ Open `/calendar` on your Maxy address (the same sign-in as the dashboard). It sh
|
|
|
12
12
|
|
|
13
13
|
Use **Today** and the arrows to move around, and the view buttons to switch. The calendar reads everything time-bound in your graph: meetings imported from a calendar export or created by a booking, appointments Maxy schedules for you in chat, and tasks that have a due date (shown on their day).
|
|
14
14
|
|
|
15
|
-
Click any entry to open its details: the full title, time, location, and who is attending, with each attendee on its own line (name, and email beneath it). For a booked meeting you can also edit its title, time, or location, or delete it, and the grid updates straight away. You can **Download** a booked meeting as a calendar file (`.ics`) to add it to Apple, Google, or Outlook calendars;
|
|
15
|
+
Click any entry to open its details: the full title, time, location, and who is attending, with each attendee on its own line (name, and email beneath it). For a booked meeting you can also edit its title, time, or location, or delete it, and the grid updates straight away. You can **Download** a booked meeting as a calendar file (`.ics`) to add it to Apple, Google, or Outlook calendars; when the meeting has attendees, Download asks whether to include them in the file before it saves, and a meeting with no attendees downloads straight away. The private note is never written into the downloaded file, since the file can travel outside your admin. In the edit view you can add an attendee (give a name, an email, or both) and remove one; adding someone who is already a contact reuses that contact rather than making a duplicate, and removing them takes them off the meeting but keeps the contact. The edit view also has a private note for that meeting, a place for your own reminders that only you and your team ever see; it never appears on the public booking page or anywhere outside your admin. Appointments Maxy schedules and due-dated tasks are shown for detail only and are changed where they are created, not here. New meetings are still made by a booking or by asking Maxy in chat, not from the calendar.
|
|
16
16
|
|
|
17
17
|
Everything on the calendar comes from records the rest of Maxy already uses, scoped to your account.
|
|
18
18
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# JobLogic
|
|
2
|
+
|
|
3
|
+
Connect JobLogic, the field-service management system, so the agent can read and update your jobs end to end — customers, sites, jobs, engineers, visits, quotes, invoices, assets, and timesheets — over JobLogic's REST API.
|
|
4
|
+
|
|
5
|
+
This connector is built for one team that already runs JobLogic as its system of record. Your field staff send a voice note or message to {{productName}} from the field; {{productName}} works out what they mean and makes the JobLogic update for them. Your staff never touch the JobLogic API themselves, so only {{productName}}'s own connection talks to JobLogic.
|
|
6
|
+
|
|
7
|
+
## What you do once
|
|
8
|
+
|
|
9
|
+
**1. Ask JobLogic for API access.** Raise a ticket with JobLogic Support for API credentials. Ask for a test (UAT) account first, then production. They give you a client ID, a client secret, and your company tenant id.
|
|
10
|
+
|
|
11
|
+
**2. Whitelist your connection.** JobLogic only accepts API calls from network addresses you have registered with them. {{productName}} makes its calls from the machine it runs on, so that machine's public address is the one to register. If the machine moves between networks its address can change, so either run it from one place with a stable address, send its traffic through a fixed address, or ask JobLogic whether production needs this at all. If the address is not registered, calls are refused and the agent will tell you the connection was blocked.
|
|
12
|
+
|
|
13
|
+
**3. Hand the keys to the agent.** Tell the agent your client ID, secret, tenant id, and whether they are test or production. The agent stores them on your install only and never repeats them back.
|
|
14
|
+
|
|
15
|
+
## What the agent does after that
|
|
16
|
+
|
|
17
|
+
Everything else is automatic. The agent keeps the connection alive on its own, refreshing access behind the scenes, and can:
|
|
18
|
+
|
|
19
|
+
- **Read** anything — "what jobs are open for this site", "show this engineer's visits this week", "list unpaid invoices".
|
|
20
|
+
- **Write** from a field instruction — create or reschedule a visit, log labour and materials against a job, set a job's status, approve a quote, raise an invoice, add a note or a photo to a job.
|
|
21
|
+
|
|
22
|
+
Because JobLogic's credential belongs to the company rather than to a person, the agent stamps each update with the engineer who reported it. You give the agent a list that maps each staff member's phone number to their JobLogic engineer record once, and it does the rest.
|
|
23
|
+
|
|
24
|
+
Each create carries a safety key, so if a message is retried or a reply goes missing the same instruction never creates a duplicate record.
|
|
25
|
+
|
|
26
|
+
## Testing
|
|
27
|
+
|
|
28
|
+
Do all your testing against the JobLogic test (UAT) environment first. Switch to production only once the test environment behaves the way you expect and your production keys and whitelisting are in place.
|
|
@@ -144,7 +144,7 @@ Every node also carries a provenance stamp — which agent wrote it, in which se
|
|
|
144
144
|
|
|
145
145
|
## Vertical schemas
|
|
146
146
|
|
|
147
|
-
On top of the base graph, each brand boots one optional **vertical** — an extra set of entity types tailored to a trade. The vertical is named by `brand.json#vertical` and defined in a `schema-<name>.md` reference; the memory plugin loads it at startup and validates every write against base + the active vertical. Real Agent boots `schema-estate-agent` (Listing, Property, Viewing, Offer). SiteDesk boots `schema-construction`, which adds the
|
|
147
|
+
On top of the base graph, each brand boots one optional **vertical** — an extra set of entity types tailored to a trade. The vertical is named by `brand.json#vertical` and defined in a `schema-<name>.md` reference; the memory plugin loads it at startup and validates every write against base + the active vertical. Real Agent boots `schema-estate-agent` (Listing, Property, Viewing, Offer). SiteDesk boots `schema-construction`, the canonical JobLogic-derived field-service ontology, which adds the service-delivery entities — `Job`, `Customer`, `Site`, `Asset`, `Visit`, `Quote`, `QuoteLine`, `Valuation`, `VariationNote`, `JobCost`, `Part`, `PurchaseOrder`, `Invoice`, `InboundInvoice`, `PpmContract`, `WhatsAppGroup` (with `Engineer` and `Supplier` as additional labels on `Person`/`Organization`) — grounded in real builder job folders so a job's customer, site, quote, valuations, variations, costs, and received supplier invoices all hang off one `Job` node. The default Maxy brand boots no vertical (base graph only).
|
|
148
148
|
|
|
149
149
|
## Public-facing summaries for customer-readable subjects
|
|
150
150
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "joblogic",
|
|
3
|
+
"description": "JobLogic field-service connector (bespoke, one account) — OAuth client_credentials, tenant-scoped read/write across the job lifecycle.",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Rubytech LLC"
|
|
7
|
+
},
|
|
8
|
+
"mcpServers": {
|
|
9
|
+
"joblogic": {
|
|
10
|
+
"type": "stdio",
|
|
11
|
+
"command": "node",
|
|
12
|
+
"args": [
|
|
13
|
+
"${CLAUDE_PLUGIN_ROOT}/lib/mcp-spawn-tee/index.js",
|
|
14
|
+
"${CLAUDE_PLUGIN_ROOT}/mcp/dist/index.js"
|
|
15
|
+
],
|
|
16
|
+
"env": {
|
|
17
|
+
"MCP_SPAWN_TEE_NAME": "joblogic"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|