weftens 0.0.1 → 0.1.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.
Files changed (100) hide show
  1. package/README.md +138 -12
  2. package/agent-cores/amazon-ppc/package.json +17 -0
  3. package/agent-cores/amazon-ppc/src/cli.mjs +66 -0
  4. package/agent-cores/amazon-ppc/src/optimize.mjs +272 -0
  5. package/agent-cores/amazon-ppc/src/parse.mjs +153 -0
  6. package/agent-cores/amazon-ppc/test/fixtures/search-term-report.csv +8 -0
  7. package/agent-cores/amazon-ppc/test/fixtures/search-term-report.json +5 -0
  8. package/agent-cores/amazon-ppc/test/optimize.test.mjs +161 -0
  9. package/agent-cores/content-ops/package.json +17 -0
  10. package/agent-cores/content-ops/src/cli.mjs +51 -0
  11. package/agent-cores/content-ops/src/inventory.mjs +343 -0
  12. package/agent-cores/content-ops/src/parse.mjs +121 -0
  13. package/agent-cores/content-ops/test/fixtures/cms-export.csv +13 -0
  14. package/agent-cores/content-ops/test/fixtures/cms-export.json +31 -0
  15. package/agent-cores/content-ops/test/inventory.test.mjs +177 -0
  16. package/agent-cores/meta-ads/package.json +16 -0
  17. package/agent-cores/meta-ads/src/analyze.mjs +166 -0
  18. package/agent-cores/meta-ads/src/cli.mjs +39 -0
  19. package/agent-cores/meta-ads/src/parse.mjs +123 -0
  20. package/agent-cores/meta-ads/test/analyze.test.js +72 -0
  21. package/agent-cores/meta-ads/test/fixtures/meta-ads.csv +6 -0
  22. package/agent-cores/paid-search/package.json +17 -0
  23. package/agent-cores/paid-search/src/analyze.mjs +303 -0
  24. package/agent-cores/paid-search/src/cli.mjs +57 -0
  25. package/agent-cores/paid-search/src/parse.mjs +158 -0
  26. package/agent-cores/paid-search/test/analyze.test.mjs +120 -0
  27. package/agent-cores/paid-search/test/fixtures/campaigns.csv +7 -0
  28. package/agent-cores/paid-search/test/fixtures/sample.json +9 -0
  29. package/agent-cores/paid-search/test/fixtures/search-terms.csv +11 -0
  30. package/agent-cores/paid-search/test/parse.test.mjs +56 -0
  31. package/agent-cores/seo-content/package.json +17 -0
  32. package/agent-cores/seo-content/src/cli.mjs +41 -0
  33. package/agent-cores/seo-content/src/parse.mjs +141 -0
  34. package/agent-cores/seo-content/src/strategy.mjs +266 -0
  35. package/agent-cores/seo-content/test/fixtures/gsc-sample.csv +11 -0
  36. package/agent-cores/seo-content/test/parse.test.mjs +59 -0
  37. package/agent-cores/seo-content/test/strategy.test.mjs +108 -0
  38. package/agent-cores/seo-technical/package.json +17 -0
  39. package/agent-cores/seo-technical/src/audit.mjs +233 -0
  40. package/agent-cores/seo-technical/src/cli.mjs +48 -0
  41. package/agent-cores/seo-technical/src/fetch.mjs +51 -0
  42. package/agent-cores/seo-technical/test/audit.test.mjs +73 -0
  43. package/agent-cores/seo-technical/test/fixtures/sample.html +22 -0
  44. package/agent-cores/supply-verify/package.json +17 -0
  45. package/agent-cores/supply-verify/src/cli.mjs +44 -0
  46. package/agent-cores/supply-verify/src/verify.mjs +303 -0
  47. package/agent-cores/supply-verify/test/fixtures/placements.csv +8 -0
  48. package/agent-cores/supply-verify/test/fixtures/placements.json +4 -0
  49. package/agent-cores/supply-verify/test/verify.test.mjs +148 -0
  50. package/agents/agent2060.md +99 -0
  51. package/agents/amazon-ppc.md +42 -0
  52. package/agents/claims-content.md +35 -0
  53. package/agents/content-ops.md +46 -0
  54. package/agents/content-strategy.md +13 -0
  55. package/agents/disclosure-qc.md +13 -0
  56. package/agents/feed-optimizer.md +13 -0
  57. package/agents/growth-analyst.md +24 -0
  58. package/agents/hilbert.md +55 -0
  59. package/agents/ideator.md +13 -0
  60. package/agents/lippmann.md +68 -0
  61. package/agents/paid-search.md +35 -0
  62. package/agents/pio.md +34 -0
  63. package/agents/researcher.md +13 -0
  64. package/agents/richy.md +72 -0
  65. package/agents/script-editor.md +13 -0
  66. package/agents/seo-content.md +57 -0
  67. package/agents/seo-technical.md +53 -0
  68. package/agents/showrunner.md +13 -0
  69. package/agents/social-voice.md +38 -0
  70. package/agents/supply-verify.md +38 -0
  71. package/agents/video-editor.md +13 -0
  72. package/agents/writer.md +45 -0
  73. package/bin/weftens.js +226 -0
  74. package/content-org/.preflight/telemetry.jsonl +6 -0
  75. package/content-org/briefs/BRIEFS.md +47 -0
  76. package/content-org/config.mjs +78 -0
  77. package/content-org/cost.mjs +36 -0
  78. package/content-org/lead.js +64 -0
  79. package/content-org/metrics.mjs +166 -0
  80. package/content-org/orchestrator.mjs +246 -0
  81. package/content-org/package.json +14 -0
  82. package/content-org/producer.mjs +59 -0
  83. package/content-org/publish.mjs +34 -0
  84. package/content-org/review.mjs +32 -0
  85. package/content-org/studio/README.md +27 -0
  86. package/content-org/studio/assemble.sh +48 -0
  87. package/content-org/test.mjs +110 -0
  88. package/index.js +32 -0
  89. package/package.json +49 -10
  90. package/providers.json.example +9 -0
  91. package/src/agent-router.js +124 -0
  92. package/src/agent.js +123 -0
  93. package/src/invoke.js +129 -0
  94. package/src/pio-client.js +142 -0
  95. package/src/provider.js +81 -0
  96. package/src/registry.js +160 -0
  97. package/src/richy-client.js +216 -0
  98. package/src/router.js +34 -0
  99. package/src/synthesize.js +111 -0
  100. package/src/weftens.js +61 -0
@@ -0,0 +1,111 @@
1
+ // Synthesis — the reason Weftens exists. Given what Richy found about how an org is
2
+ // represented and what PIO knows about how it operates, produce ONE operator-facing
3
+ // read that joins them, not two blobs side by side. The join is the value: an org's
4
+ // operations (events, people) and its representation (how it looks when someone
5
+ // searches it afterward) are two ends of the same loop, and the useful insight lives
6
+ // in the relationship between them — attention generated vs. conversion captured.
7
+ //
8
+ // Deterministic and explainable on purpose: every sentence traces to a signal the
9
+ // operator can check. No LLM, no invented facts — if a signal is missing, it says so
10
+ // rather than guessing.
11
+
12
+ function band(score) {
13
+ if (score === null || score === undefined) return null;
14
+ if (score >= 75) return "strong";
15
+ if (score >= 55) return "okay";
16
+ return "weak";
17
+ }
18
+
19
+ function repLine(rep) {
20
+ if (!rep || rep.available === false) return null;
21
+ if (rep.overallScore === null) {
22
+ return rep.siteCheckAvailable === false
23
+ ? "We couldn't reach the website, so how you look online is unknown right now."
24
+ : "Your online representation was checked but didn't return a score.";
25
+ }
26
+ const b = band(rep.overallScore);
27
+ return `Your online representation scores ${rep.overallScore}/100 (${b}) — this is what a funder or partner sees when they search you.`;
28
+ }
29
+
30
+ function opsLine(ops) {
31
+ if (!ops || ops.available === false) return null;
32
+ const parts = [];
33
+ if (ops.events) parts.push(`${ops.events} event${ops.events === 1 ? "" : "s"}`);
34
+ if (ops.participants) parts.push(`${ops.participants} participant${ops.participants === 1 ? "" : "s"}`);
35
+ if (ops.programs) parts.push(`${ops.programs} program${ops.programs === 1 ? "" : "s"}`);
36
+ if (parts.length === 0) return "No program or event activity is recorded yet.";
37
+ return `On the operations side you're running ${parts.join(", ")}${ops.followUps ? ` with ${ops.followUps} follow-up${ops.followUps === 1 ? "" : "s"} in flight` : ", with no follow-ups tracked"}.`;
38
+ }
39
+
40
+ // The cross-domain insight: this is what neither agent can say alone.
41
+ function joinInsight(rep, ops) {
42
+ const repScored = rep && rep.available !== false && typeof rep.overallScore === "number";
43
+ const hasActivity = ops && ops.available !== false && (ops.participants > 0 || ops.events > 0);
44
+ if (!repScored || !hasActivity) return null;
45
+
46
+ const weakRep = rep.overallScore < 55;
47
+ const thinFollowUp = ops.participants > 0 && (ops.followUps ?? 0) === 0;
48
+
49
+ if (weakRep && hasActivity) {
50
+ return {
51
+ insight: "You're drawing people in through events, but your online presence is weak — so the funders and partners who look you up afterward land on a page that undersells you. You're generating attention faster than you're capturing it.",
52
+ recommendation: rep.topFixes?.[0]?.title
53
+ ? `Fix your representation before the next event — start with: "${rep.topFixes[0].title}".`
54
+ : "Fix your representation before the next event so the people your events attract find a page that converts them.",
55
+ };
56
+ }
57
+ if (thinFollowUp) {
58
+ return {
59
+ insight: `Your representation is holding up (${rep.overallScore}/100), but ${ops.participants} participants have zero follow-ups tracked — the leak is on the operations side, not the online side.`,
60
+ recommendation: "Turn attendees into next steps: log follow-ups on this event's participants while it's fresh.",
61
+ };
62
+ }
63
+ return {
64
+ insight: `Representation (${rep.overallScore}/100) and operations are both moving; the two ends of the loop are aligned.`,
65
+ recommendation: rep.topFixes?.[0]?.title ? `Keep the edge: ${rep.topFixes[0].title}.` : "Keep both sides moving together.",
66
+ };
67
+ }
68
+
69
+ // A leg that failed for a nameable reason (org not found, service unreachable)
70
+ // gets its reason into the read — a silently missing line hides exactly the fact
71
+ // the operator needs ("PIO has no org by that name") behind "unavailable".
72
+ function unavailableLine(label, leg) {
73
+ if (!leg || leg.available !== false || !leg.reason) return null;
74
+ return `${label} couldn't be checked: ${leg.reason}.`;
75
+ }
76
+
77
+ export function synthesize({ representation, operations }) {
78
+ const lines = [
79
+ repLine(representation),
80
+ opsLine(operations),
81
+ unavailableLine("Your online representation", representation),
82
+ unavailableLine("Your operations", operations),
83
+ ].filter(Boolean);
84
+ const joined = joinInsight(representation, operations);
85
+ // Fixture data must never read as a finding. Sample legs are flagged in the
86
+ // basis AND the headline — the numbers stay visible as an illustration, but
87
+ // nothing presents them as facts about the real organization.
88
+ const sample = Boolean(representation?.sample || operations?.sample);
89
+
90
+ let headline;
91
+ if (joined) headline = joined.insight;
92
+ else if (lines.length) headline = lines[0];
93
+ else headline = "Not enough signal from either side to say anything useful yet.";
94
+ if (sample) headline = `SAMPLE DATA (no live agents connected — an illustration, not a finding): ${headline}`;
95
+
96
+ const legBasis = (leg) => {
97
+ if (!leg || leg.available === false) return "unavailable";
98
+ return leg.sample ? "sample" : "included";
99
+ };
100
+
101
+ return {
102
+ headline,
103
+ read: lines,
104
+ recommendation: joined?.recommendation ?? representation?.topFixes?.[0]?.title ?? null,
105
+ sample,
106
+ basis: {
107
+ representation: legBasis(representation),
108
+ operations: legBasis(operations),
109
+ },
110
+ };
111
+ }
package/src/weftens.js ADDED
@@ -0,0 +1,61 @@
1
+ // Weftens — the orchestration layer. Given a request about an organization, it
2
+ // decides which specialist(s) to consult (Richy for representation, PIO for
3
+ // operations), gathers from them over their existing wire contracts, and — the part
4
+ // that makes it worth existing — SYNTHESIZES their outputs into one operator answer
5
+ // with a recommendation, rather than returning two blobs. Richy and PIO each still
6
+ // run standalone; Weftens is the join. Deterministic and explainable: no invented
7
+ // facts, and the raw per-agent detail is kept alongside the answer so nothing is
8
+ // hidden.
9
+
10
+ import { classifyIntent } from "./router.js";
11
+ import { synthesize } from "./synthesize.js";
12
+
13
+ export class WeftensError extends Error {
14
+ constructor(message) {
15
+ super(message);
16
+ this.name = "WeftensError";
17
+ }
18
+ }
19
+
20
+ async function callLeg(client, method, org, unconfiguredReason) {
21
+ if (!client) return { available: false, reason: unconfiguredReason };
22
+ try {
23
+ return await client[method](org);
24
+ } catch (error) {
25
+ return { available: false, reason: error?.message ?? "error" };
26
+ }
27
+ }
28
+
29
+ /**
30
+ * @param request { org: { name, website?, handles?, queries? }, intent?, question? }
31
+ * @param agents { richy, pio } — clients (real or fixture); either may be null
32
+ */
33
+ export async function orchestrate(request, agents = {}) {
34
+ const org = request?.org;
35
+ if (!org || typeof org.name !== "string" || org.name.trim() === "") {
36
+ throw new WeftensError("org.name is required");
37
+ }
38
+
39
+ const classified = classifyIntent(request.question ?? "");
40
+ const intent = request.intent && ["representation", "operations", "both"].includes(request.intent)
41
+ ? request.intent
42
+ : classified.intent;
43
+
44
+ const wantRep = intent === "representation" || intent === "both";
45
+ const wantOps = intent === "operations" || intent === "both";
46
+
47
+ const representation = wantRep
48
+ ? await callLeg(agents.richy, "representationCheck", org, "representation agent (Richy) not configured")
49
+ : undefined;
50
+ const operations = wantOps
51
+ ? await callLeg(agents.pio, "operationsSummary", org, "operations agent (PIO) not configured")
52
+ : undefined;
53
+
54
+ return {
55
+ org: org.name,
56
+ intent,
57
+ routing: request.intent ? "explicit" : classified.confident ? "classified" : "ambiguous — consulted both",
58
+ answer: synthesize({ representation, operations }),
59
+ detail: { representation, operations },
60
+ };
61
+ }