@synap-core/workspace-templates 0.6.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +104 -1
- package/dist/index.js +3360 -1617
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/business-developer.template.test.ts +280 -0
- package/src/business-developer.yaml +644 -0
- package/src/crm.yaml +38 -58
- package/src/define.flow-automations.test.ts +65 -0
- package/src/define.ts +95 -7
- package/src/ecosystem.yaml +24 -17
- package/src/index.ts +2 -0
- package/src/meetings-knowledge.yaml +204 -0
- package/src/outreach-comms.yaml +282 -0
- package/src/templates.ts +2999 -1288
- package/src/types.ts +106 -3
package/src/crm.yaml
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# CRM workspace template
|
|
2
2
|
# The CRM owns pre-sale work only. A won deal is handed to the operating
|
|
3
3
|
# workspace; this template deliberately does not provision client delivery,
|
|
4
|
-
# campaigns, tasks, documents,
|
|
5
|
-
# (
|
|
4
|
+
# campaigns, tasks, documents, automations, or sales-specific playbooks. A
|
|
5
|
+
# use-case overlay (for example `business-developer`) may add those workflows.
|
|
6
6
|
|
|
7
7
|
meta:
|
|
8
8
|
slug: crm
|
|
@@ -17,6 +17,20 @@ meta:
|
|
|
17
17
|
- contacts
|
|
18
18
|
isPublic: true
|
|
19
19
|
|
|
20
|
+
# SSOT ordering guarantee — this template REFERENCES foundation's pod-wide
|
|
21
|
+
# shared `client`/`partner`/`lead`/`churned` roles below. The apply engine seeds
|
|
22
|
+
# a shared profile's pod-wide BASE property body from whichever template
|
|
23
|
+
# creates it FIRST, and adds a REUSING template's extra props as workspace
|
|
24
|
+
# OVERLAYS. Requiring foundation makes the resolver install it first
|
|
25
|
+
# (deps-first), so the base is always foundation's SSOT body and never this
|
|
26
|
+
# template's. `require`, not `compose`: foundation is a real workspace, not an
|
|
27
|
+
# overlay.
|
|
28
|
+
dependencies:
|
|
29
|
+
- slug: foundation
|
|
30
|
+
kind: workspace
|
|
31
|
+
relation: require
|
|
32
|
+
reason: "Provides the pod-wide client/partner/lead/churned role vocabulary this workspace reuses."
|
|
33
|
+
|
|
20
34
|
workspace:
|
|
21
35
|
name: CRM
|
|
22
36
|
description: A focused pre-sale workspace for contacts, companies, and deals.
|
|
@@ -407,67 +421,33 @@ entityLinks:
|
|
|
407
421
|
targetProfileSlug: deal
|
|
408
422
|
type: linked_to_deal
|
|
409
423
|
|
|
410
|
-
#
|
|
411
|
-
#
|
|
412
|
-
#
|
|
413
|
-
# can offer
|
|
414
|
-
# lead" / "Qualify this lead"; a deal surfaces "Work this deal". The subject entity
|
|
415
|
-
# is passed as the matching entity param and substituted into the goalTemplate.
|
|
416
|
-
#
|
|
417
|
-
# Two subjects are used: `lead` (a shared ROLE facet on a person/company — the same
|
|
418
|
-
# facet-role subject pattern ecosystem.yaml uses for `competitor`) and `deal` (a
|
|
419
|
-
# primary CRM kind). The matcher keys purely on the profileSlug string, so a role
|
|
420
|
-
# facet and a kind are matched identically.
|
|
421
|
-
#
|
|
422
|
-
# No `grants:` on "Enrich this lead": enrichment tools (Apify/Unipile-LinkedIn) are
|
|
423
|
-
# workspace-conditional and may not be granted, so the goalTemplate context-injects
|
|
424
|
-
# the intent and the session agent uses whatever enrichment capability the workspace
|
|
425
|
-
# actually has — mirroring how "Produce Content" (content-os) grants nothing and
|
|
426
|
-
# leans on the workspace's tools, rather than hardcoding a tool that might be absent.
|
|
424
|
+
# Playbook — enriches a lead/prospect from public sources (company site,
|
|
425
|
+
# LinkedIn, firmographic signal) so ICP scoring and qualification start from
|
|
426
|
+
# real data instead of a blank record. Bound to `lead` so a newly captured
|
|
427
|
+
# lead can offer this as a session.
|
|
427
428
|
playbooks:
|
|
428
|
-
- name: Enrich
|
|
429
|
-
description:
|
|
429
|
+
- name: Enrich Lead/Company
|
|
430
|
+
description: Enrich a lead or company from public sources — firmographics, company size, and recent signal — to sharpen ICP scoring and qualification.
|
|
430
431
|
goalTemplate: >-
|
|
431
|
-
Enrich
|
|
432
|
-
|
|
433
|
-
and
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
contacts, and note where information is still missing.
|
|
432
|
+
Enrich {target} using public sources. Scrape their website and LinkedIn
|
|
433
|
+
presence (via Apify) for firmographics: company size, industry, location,
|
|
434
|
+
and any recent signal (funding, hiring, product launches). Fill in the
|
|
435
|
+
missing profile fields (industry, companySize, location, icpScore) with
|
|
436
|
+
what you find, and note anything that changes qualification. Do not
|
|
437
|
+
fabricate — leave a field blank rather than guess.
|
|
438
438
|
subjectProfile:
|
|
439
439
|
profileSlug: lead
|
|
440
440
|
params:
|
|
441
|
-
- { name:
|
|
441
|
+
- { name: target, type: entity, label: Lead or company, required: true }
|
|
442
442
|
executor: is-agent
|
|
443
|
+
grants:
|
|
444
|
+
- apify_run_actor
|
|
445
|
+
- entity.create
|
|
443
446
|
status: active
|
|
444
447
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
keep nurturing, or disqualify. Write the reasoning and the decision back onto
|
|
452
|
-
the lead (its stage, score, and next step) so the call is auditable.
|
|
453
|
-
subjectProfile:
|
|
454
|
-
profileSlug: lead
|
|
455
|
-
params:
|
|
456
|
-
- { name: lead, type: entity, label: Lead, required: true }
|
|
457
|
-
executor: is-agent
|
|
458
|
-
status: active
|
|
459
|
-
|
|
460
|
-
- name: Work this deal
|
|
461
|
-
description: Move a deal toward Won — read the current stage, find the blocker, and propose the next step and owner.
|
|
462
|
-
goalTemplate: >-
|
|
463
|
-
Work this deal ({deal}) toward Won. Assess where it stands — its current
|
|
464
|
-
stage, value, and close date — and identify the single thing blocking it from
|
|
465
|
-
advancing. Propose the concrete next step, who owns it, and by when, and
|
|
466
|
-
update the deal's next step accordingly. If the stage should change, say so
|
|
467
|
-
and why.
|
|
468
|
-
subjectProfile:
|
|
469
|
-
profileSlug: deal
|
|
470
|
-
params:
|
|
471
|
-
- { name: deal, type: entity, label: Deal, required: true }
|
|
472
|
-
executor: is-agent
|
|
473
|
-
status: active
|
|
448
|
+
# Apify powers the enrichment playbook (scrape public sources for a lead's
|
|
449
|
+
# firmographics). Composed pod-wide at provisioning via the existing
|
|
450
|
+
# integrations door; the user supplies the Apify API key once on install — a
|
|
451
|
+
# missing key fails gracefully per-skill, not the whole workspace.
|
|
452
|
+
integrations:
|
|
453
|
+
- templateKey: apify
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { toWorkspaceDefinition } from "./define.js";
|
|
3
|
+
import type { WorkspaceYaml } from "./types.js";
|
|
4
|
+
|
|
5
|
+
const workflowTemplate: WorkspaceYaml = {
|
|
6
|
+
meta: {
|
|
7
|
+
slug: "workflow-contract-test",
|
|
8
|
+
name: "Workflow contract test",
|
|
9
|
+
description: "Test template",
|
|
10
|
+
icon: "workflow",
|
|
11
|
+
color: "blue",
|
|
12
|
+
},
|
|
13
|
+
workspace: { name: "Workflow contract test", description: "Test" },
|
|
14
|
+
profiles: [],
|
|
15
|
+
automations: [
|
|
16
|
+
{
|
|
17
|
+
name: "A manual workflow",
|
|
18
|
+
triggerType: "manual",
|
|
19
|
+
flow: {
|
|
20
|
+
nodes: [{ id: "trigger", type: "trigger" }],
|
|
21
|
+
edges: [],
|
|
22
|
+
precondition: "trigger.payload.ready === true",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
actionPlacements: [
|
|
27
|
+
{
|
|
28
|
+
profileSlug: "person",
|
|
29
|
+
surface: "entity-detail",
|
|
30
|
+
kind: "automation",
|
|
31
|
+
ref: "A manual workflow",
|
|
32
|
+
label: "Run workflow",
|
|
33
|
+
when: {
|
|
34
|
+
requiredFacetSlugs: ["lead"],
|
|
35
|
+
propertyEquals: { stage: "ready" },
|
|
36
|
+
},
|
|
37
|
+
confirmation: { title: "Continue?", confirmLabel: "Run" },
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
describe("toWorkspaceDefinition flow automations", () => {
|
|
43
|
+
it("keeps graph workflows distinct from legacy loop automations", () => {
|
|
44
|
+
const { definition } = toWorkspaceDefinition(workflowTemplate);
|
|
45
|
+
|
|
46
|
+
expect(definition.flowAutomations).toEqual([
|
|
47
|
+
expect.objectContaining({
|
|
48
|
+
name: "A manual workflow",
|
|
49
|
+
triggerType: "manual",
|
|
50
|
+
flowDefinition: expect.objectContaining({
|
|
51
|
+
precondition: "trigger.payload.ready === true",
|
|
52
|
+
}),
|
|
53
|
+
}),
|
|
54
|
+
]);
|
|
55
|
+
expect("automations" in definition).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("preserves action applicability and confirmation policy", () => {
|
|
59
|
+
const { definition } = toWorkspaceDefinition(workflowTemplate);
|
|
60
|
+
expect(definition.actionPlacements?.[0]).toMatchObject({
|
|
61
|
+
when: { requiredFacetSlugs: ["lead"], propertyEquals: { stage: "ready" } },
|
|
62
|
+
confirmation: { title: "Continue?", confirmLabel: "Run" },
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|
package/src/define.ts
CHANGED
|
@@ -10,9 +10,39 @@
|
|
|
10
10
|
* shape never drifts per call-site.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { TemplateBentoBlock, WorkspaceYaml } from "./types.js";
|
|
13
|
+
import type { TemplateAutomation, TemplateBentoBlock, WorkspaceYaml } from "./types.js";
|
|
14
14
|
import { getWorkspaceTemplate } from "./templates.js";
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* A `TemplateAutomation` as the apply door expects it: `flowDefinition`, not
|
|
18
|
+
* the authored `flow` (mirrors `PackagePostWorkspaceBody['automations'][number]`
|
|
19
|
+
* / the Hub `POST /packages/apply` `AutomationSchema`).
|
|
20
|
+
*/
|
|
21
|
+
export interface PackageAutomationWire {
|
|
22
|
+
/** Stable template identity; names may be edited without orphaning the flow. */
|
|
23
|
+
key?: string;
|
|
24
|
+
name: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
triggerType: TemplateAutomation['triggerType'];
|
|
27
|
+
triggerConfig?: Record<string, unknown>;
|
|
28
|
+
flowDefinition?: { nodes: unknown[]; edges: unknown[]; precondition?: string };
|
|
29
|
+
status?: TemplateAutomation['status'];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function toAutomationWire(
|
|
33
|
+
automations: TemplateAutomation[] | undefined,
|
|
34
|
+
): PackageAutomationWire[] | undefined {
|
|
35
|
+
return automations?.map((a) => ({
|
|
36
|
+
key: a.key,
|
|
37
|
+
name: a.name,
|
|
38
|
+
description: a.description,
|
|
39
|
+
triggerType: a.triggerType,
|
|
40
|
+
triggerConfig: a.triggerConfig,
|
|
41
|
+
flowDefinition: a.flow,
|
|
42
|
+
status: a.status,
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
|
|
16
46
|
/** A widget block as the wire expects it: `bentoLayout[]`, keyed by `widgetType`. */
|
|
17
47
|
export interface BentoWidgetBlockWire {
|
|
18
48
|
widgetType: string;
|
|
@@ -147,6 +177,21 @@ export interface WorkspaceDefinitionInput {
|
|
|
147
177
|
* tools/skills/automations silently dropped on that door.
|
|
148
178
|
*/
|
|
149
179
|
capabilities?: WorkspaceYaml['integrations'];
|
|
180
|
+
/** Playbooks normalized to the workspace-create input contract. */
|
|
181
|
+
playbooks?: Array<Record<string, unknown>>;
|
|
182
|
+
/**
|
|
183
|
+
* Entity-detail action placements — forwarded verbatim to
|
|
184
|
+
* `applyPackagePostWorkspace`, which merges them into
|
|
185
|
+
* `settings.actionPlacements`. The tRPC door (`createFromDefinition`)
|
|
186
|
+
* declares `actionPlacements` in its zod input and carries them through the
|
|
187
|
+
* shared apply door. No shape transform: the authored form IS the wire form.
|
|
188
|
+
*/
|
|
189
|
+
actionPlacements?: WorkspaceYaml['actionPlacements'];
|
|
190
|
+
/**
|
|
191
|
+
* Graph-based automations. Deliberately distinct from the legacy
|
|
192
|
+
* `automations` loop/playbook-trigger wire shape accepted by this door.
|
|
193
|
+
*/
|
|
194
|
+
flowAutomations?: PackageAutomationWire[];
|
|
150
195
|
// Workspace-level metadata (flattened from the `workspace` block)
|
|
151
196
|
workspaceSubtype?: string;
|
|
152
197
|
workspaceType?: NonNullable<WorkspaceYaml['workspace']>['workspaceType'];
|
|
@@ -177,6 +222,27 @@ export interface ToDefinitionResult {
|
|
|
177
222
|
templateId: string;
|
|
178
223
|
}
|
|
179
224
|
|
|
225
|
+
/**
|
|
226
|
+
* The workspace-create door persists a compact primitive input contract. A
|
|
227
|
+
* template's entity/choice parameter is still represented precisely by its
|
|
228
|
+
* `subjectProfile` or runtime prompt, while the persisted input value is an ID
|
|
229
|
+
* or selected value string. Keep this conversion at the shared template seam
|
|
230
|
+
* so browser creation and package application cannot silently disagree.
|
|
231
|
+
*/
|
|
232
|
+
function toWorkspacePlaybooks(playbooks: WorkspaceYaml["playbooks"]): Array<Record<string, unknown>> | undefined {
|
|
233
|
+
return playbooks?.map((playbook) => ({
|
|
234
|
+
...playbook,
|
|
235
|
+
params: playbook.params?.map((param) => ({
|
|
236
|
+
name: param.name,
|
|
237
|
+
type: param.type === "number" || param.type === "boolean" ? param.type : "string",
|
|
238
|
+
...(param.defaultValue !== undefined ? { default: param.defaultValue } : {}),
|
|
239
|
+
...(param.label
|
|
240
|
+
? { description: param.label }
|
|
241
|
+
: {}),
|
|
242
|
+
})),
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
|
|
180
246
|
/**
|
|
181
247
|
* Resolve a template (by slug or YAML object) into the createFromDefinition
|
|
182
248
|
* payload. Pass `nameOverride` to rename the workspace at creation time.
|
|
@@ -228,6 +294,13 @@ export function toWorkspaceDefinition(
|
|
|
228
294
|
workspaceCapabilities: ws.capabilities,
|
|
229
295
|
sourceRoles: ws.sourceRoles,
|
|
230
296
|
defaultSources: ws.defaultSources,
|
|
297
|
+
// Workspace creation persists its own compact playbook parameter shape.
|
|
298
|
+
// Map authored entity/choice inputs here instead of dropping playbooks.
|
|
299
|
+
playbooks: toWorkspacePlaybooks(tpl.playbooks),
|
|
300
|
+
// Entity-detail action placements — forwarded verbatim; the apply door
|
|
301
|
+
// resolves playbook/automation refs and merges into settings.actionPlacements.
|
|
302
|
+
actionPlacements: tpl.actionPlacements,
|
|
303
|
+
flowAutomations: toAutomationWire(tpl.automations),
|
|
231
304
|
onboarding: tpl.onboarding,
|
|
232
305
|
// Template-composition dependencies — forwarded so the tRPC/UI door gets the
|
|
233
306
|
// SAME deps-first ordering the CLI/Hub door gets via `toPackageDefinition`.
|
|
@@ -281,6 +354,9 @@ type WorkspaceDefinitionForwarded =
|
|
|
281
354
|
| 'bento' // → bentoViewName + bentoLayout (widgets) + bentoViewBlocks (views)
|
|
282
355
|
| 'profileEntityBentoTemplates'
|
|
283
356
|
| 'integrations' // → capabilities (installed via shared applyPackagePostWorkspace door)
|
|
357
|
+
| 'playbooks' // → normalized compact playbook contract
|
|
358
|
+
| 'actionPlacements' // → settings.actionPlacements via applyPackagePostWorkspace
|
|
359
|
+
| 'automations' // → flowAutomations → shared applyPackagePostWorkspace door
|
|
284
360
|
| 'onboarding'
|
|
285
361
|
| 'dependencies';
|
|
286
362
|
|
|
@@ -297,12 +373,7 @@ type WorkspaceDefinitionDropped =
|
|
|
297
373
|
// The door DOES accept `extends`, but no template declares it today (0/21).
|
|
298
374
|
// Forwarding is a no-op until one does — wire it then.
|
|
299
375
|
| 'extends'
|
|
300
|
-
|
|
301
|
-
// accepts `playbooks`, but the shapes disagree: template param `type` allows
|
|
302
|
-
// 'text'|'entity'|'choice', the tRPC enum accepts only string|number|boolean
|
|
303
|
-
// (and defaultValue↔default, grants: string[]↔{kind,ref}[]). Raw forwarding =
|
|
304
|
-
// zod BAD_REQUEST. Needs a param mapper — separate wave, not a silent drop.
|
|
305
|
-
| 'playbooks';
|
|
376
|
+
;
|
|
306
377
|
|
|
307
378
|
type UnclassifiedWorkspaceDefinitionKey = Exclude<
|
|
308
379
|
keyof WorkspaceYaml,
|
|
@@ -367,6 +438,19 @@ export interface PackageDefinitionOutput {
|
|
|
367
438
|
layoutConfig?: WorkspaceYaml['layout'];
|
|
368
439
|
capabilities?: WorkspaceYaml['integrations'];
|
|
369
440
|
playbooks?: WorkspaceYaml['playbooks'];
|
|
441
|
+
/** Flow-based automations — mapped to the apply door's `flowDefinition` field. */
|
|
442
|
+
automations?: PackageAutomationWire[];
|
|
443
|
+
/**
|
|
444
|
+
* Entity-detail action placements — carried verbatim (authored form IS the
|
|
445
|
+
* wire form). NOTE: the Hub `PackageApplySchema` (synap-backend
|
|
446
|
+
* `hub-protocol/rest/packages.ts`) is a plain `z.object` and does not yet
|
|
447
|
+
* declare `actionPlacements`, so on the CLI/Hub install path this field is
|
|
448
|
+
* STRIPPED before `applyPackagePostWorkspace` sees it. The tRPC
|
|
449
|
+
* `createFromDefinition` door DOES carry it. Named follow-up: add an
|
|
450
|
+
* `ActionPlacementSchema` field to `PackageApplySchema` to close the Hub half
|
|
451
|
+
* (out of the T2 fence — flagged in the handoff).
|
|
452
|
+
*/
|
|
453
|
+
actionPlacements?: WorkspaceYaml['actionPlacements'];
|
|
370
454
|
onboarding?: WorkspaceYaml['onboarding'];
|
|
371
455
|
/** Template-composition dependencies — resolved by the apply endpoint. */
|
|
372
456
|
dependencies?: WorkspaceYaml['dependencies'];
|
|
@@ -421,6 +505,8 @@ export function toPackageDefinition(
|
|
|
421
505
|
layoutConfig: tpl.layout,
|
|
422
506
|
capabilities: tpl.integrations,
|
|
423
507
|
playbooks: tpl.playbooks,
|
|
508
|
+
automations: toAutomationWire(tpl.automations),
|
|
509
|
+
actionPlacements: tpl.actionPlacements,
|
|
424
510
|
onboarding: tpl.onboarding,
|
|
425
511
|
dependencies: tpl.dependencies,
|
|
426
512
|
};
|
|
@@ -448,6 +534,8 @@ type PackageDefinitionForwarded =
|
|
|
448
534
|
| 'layout' // → layoutConfig
|
|
449
535
|
| 'integrations' // → capabilities
|
|
450
536
|
| 'playbooks'
|
|
537
|
+
| 'automations' // → mapped to the apply door's flowDefinition wire shape
|
|
538
|
+
| 'actionPlacements' // → carried verbatim (Hub inbound schema decl is a follow-up)
|
|
451
539
|
| 'onboarding'
|
|
452
540
|
| 'dependencies';
|
|
453
541
|
|
package/src/ecosystem.yaml
CHANGED
|
@@ -381,9 +381,11 @@ playbooks:
|
|
|
381
381
|
goalTemplate: >-
|
|
382
382
|
Research this competitor ({competitor}) and compare them to us. Investigate
|
|
383
383
|
their {focus}: positioning, strengths, weaknesses, pricing, and recent
|
|
384
|
-
moves. Use
|
|
385
|
-
|
|
386
|
-
|
|
384
|
+
moves. Use web search for news and public signal, and use the Apify scraper
|
|
385
|
+
for a deep scan of their site (pricing page, changelog, careers page) when
|
|
386
|
+
a URL is known. Use the provided company context where relevant:
|
|
387
|
+
{companyContext}. Produce: {outputTypes}. Update the competitor entity with
|
|
388
|
+
what you find and make clear where we win and where we are exposed.
|
|
387
389
|
subjectProfile:
|
|
388
390
|
profileSlug: competitor
|
|
389
391
|
params:
|
|
@@ -398,8 +400,9 @@ playbooks:
|
|
|
398
400
|
# starts paid AI runs — the user turns it on when they want the watch.
|
|
399
401
|
schedule: { cron: "0 9 * * 1", enabled: false }
|
|
400
402
|
grants:
|
|
401
|
-
-
|
|
402
|
-
-
|
|
403
|
+
- exa_search
|
|
404
|
+
- apify_run_actor
|
|
405
|
+
- entity.create
|
|
403
406
|
status: active
|
|
404
407
|
|
|
405
408
|
# A radar DISCOVERY run: the self-generating half. No subjectProfile → a single
|
|
@@ -409,28 +412,32 @@ playbooks:
|
|
|
409
412
|
- name: Discover Competitors
|
|
410
413
|
description: Scan the market for competitors we don't track yet and add them to the ecosystem — the self-generating half of the radar.
|
|
411
414
|
goalTemplate: >-
|
|
412
|
-
Find companies that compete with us in {ourSpace}.
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
a competitor in our ecosystem), propose it as a
|
|
417
|
-
ALWAYS include its `website` (its canonical URL)
|
|
418
|
-
|
|
419
|
-
anything already known. Report what you added
|
|
415
|
+
Find companies that compete with us in {ourSpace}. Fan out neural web
|
|
416
|
+
searches with the exa_search skill using category "company" — cover direct
|
|
417
|
+
competitors, adjacent tools that solve the same job a different way, and
|
|
418
|
+
emerging players, not just the obvious incumbents. For each genuinely NEW
|
|
419
|
+
one (not already a competitor in our ecosystem), propose it as a
|
|
420
|
+
`competitor` entity and ALWAYS include its `website` (its canonical URL) as
|
|
421
|
+
the dedup key — never create a company whose website already matches one we
|
|
422
|
+
track. Skip anything already known. Report what you added, the search
|
|
423
|
+
angles you covered, and why each new one matters.
|
|
420
424
|
schedule: { cron: "0 9 * * 1", enabled: false }
|
|
421
425
|
params:
|
|
422
426
|
- { name: ourSpace, type: text, label: Our market / category, required: true }
|
|
423
427
|
executor: is-agent
|
|
424
428
|
grants:
|
|
425
429
|
- exa_search
|
|
426
|
-
-
|
|
430
|
+
- entity.create
|
|
427
431
|
status: active
|
|
428
432
|
|
|
429
|
-
#
|
|
430
|
-
#
|
|
431
|
-
#
|
|
433
|
+
# Exa powers the discovery radar (find companies we don't track yet); Apify
|
|
434
|
+
# powers the deep-scan half of Research Competitor (scrape a known competitor's
|
|
435
|
+
# site for pricing/changelog/careers signal). Composed pod-wide at provisioning
|
|
436
|
+
# via the existing integrations door; the user supplies each API key once on
|
|
437
|
+
# install — a missing key fails gracefully per-skill, not the whole workspace.
|
|
432
438
|
integrations:
|
|
433
439
|
- templateKey: exa
|
|
440
|
+
- templateKey: apify
|
|
434
441
|
|
|
435
442
|
onboarding:
|
|
436
443
|
goal: >
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Meetings & Knowledge workspace template
|
|
2
|
+
# A standalone capture layer for meetings: pull the transcript, summarize it,
|
|
3
|
+
# and turn it into the things that actually matter afterward — notes, tasks,
|
|
4
|
+
# and the people involved. Reuses the pod-wide person/task/note kinds so a
|
|
5
|
+
# meeting's output lands where every other workspace already looks for it.
|
|
6
|
+
|
|
7
|
+
meta:
|
|
8
|
+
slug: meetings-knowledge
|
|
9
|
+
name: Meetings & Knowledge
|
|
10
|
+
description: Import and summarize meetings — transcripts become notes, action items become tasks, and attendees become tracked people.
|
|
11
|
+
icon: video
|
|
12
|
+
color: "#8B5CF6"
|
|
13
|
+
tags: [meetings, notes, tasks, transcripts, calendar]
|
|
14
|
+
isPublic: true
|
|
15
|
+
|
|
16
|
+
workspace:
|
|
17
|
+
name: Meetings & Knowledge
|
|
18
|
+
description: Import and summarize meetings — transcripts become notes, action items become tasks, and attendees become tracked people.
|
|
19
|
+
subtype: meetings-knowledge
|
|
20
|
+
visibility: pod_visible
|
|
21
|
+
capabilities:
|
|
22
|
+
- meetings.import
|
|
23
|
+
- meetings.summaries
|
|
24
|
+
sourceRoles:
|
|
25
|
+
meetings: provider
|
|
26
|
+
|
|
27
|
+
profiles:
|
|
28
|
+
# ── Meeting (owned here) ─────────────────────────────────────────────────
|
|
29
|
+
- slug: meeting
|
|
30
|
+
displayName: Meeting
|
|
31
|
+
icon: video
|
|
32
|
+
color: violet
|
|
33
|
+
description: A meeting — its date, attendees, and what came out of it.
|
|
34
|
+
properties:
|
|
35
|
+
- slug: meetingDate
|
|
36
|
+
label: Date
|
|
37
|
+
valueType: date
|
|
38
|
+
inputType: date
|
|
39
|
+
- slug: platform
|
|
40
|
+
label: Platform
|
|
41
|
+
valueType: string
|
|
42
|
+
inputType: select
|
|
43
|
+
enumValues: [zoom, google-meet, teams, in-person, other]
|
|
44
|
+
- slug: attendees
|
|
45
|
+
label: Attendees
|
|
46
|
+
valueType: array
|
|
47
|
+
inputType: text
|
|
48
|
+
- slug: status
|
|
49
|
+
label: Status
|
|
50
|
+
valueType: string
|
|
51
|
+
inputType: select
|
|
52
|
+
enumValues: [scheduled, completed, processed]
|
|
53
|
+
constraints: { defaultValue: scheduled }
|
|
54
|
+
- slug: summary
|
|
55
|
+
label: Summary
|
|
56
|
+
valueType: string
|
|
57
|
+
inputType: textarea
|
|
58
|
+
- slug: transcriptUrl
|
|
59
|
+
label: Transcript URL
|
|
60
|
+
valueType: string
|
|
61
|
+
inputType: url
|
|
62
|
+
|
|
63
|
+
views:
|
|
64
|
+
- name: Meetings
|
|
65
|
+
slug: meetings
|
|
66
|
+
type: table
|
|
67
|
+
scopeProfileSlug: meeting
|
|
68
|
+
description: Every meeting, its platform, and whether it's been processed.
|
|
69
|
+
config:
|
|
70
|
+
columns: [title, meetingDate, platform, status]
|
|
71
|
+
defaultView: true
|
|
72
|
+
- name: Meetings Calendar
|
|
73
|
+
slug: meetings-calendar
|
|
74
|
+
type: calendar
|
|
75
|
+
scopeProfileSlug: meeting
|
|
76
|
+
description: Meetings on a calendar, keyed on their date.
|
|
77
|
+
- name: Action Items
|
|
78
|
+
slug: meeting-action-items
|
|
79
|
+
type: kanban
|
|
80
|
+
scopeProfileSlug: task
|
|
81
|
+
groupBy: status
|
|
82
|
+
description: Tasks generated from meetings, by status.
|
|
83
|
+
config:
|
|
84
|
+
groupBy: status
|
|
85
|
+
|
|
86
|
+
entityLinks:
|
|
87
|
+
- sourceProfileSlug: meeting
|
|
88
|
+
targetProfileSlug: person
|
|
89
|
+
type: attended_by
|
|
90
|
+
label: Attended by
|
|
91
|
+
- sourceProfileSlug: meeting
|
|
92
|
+
targetProfileSlug: task
|
|
93
|
+
type: generated
|
|
94
|
+
label: Generated
|
|
95
|
+
- sourceProfileSlug: meeting
|
|
96
|
+
targetProfileSlug: note
|
|
97
|
+
type: summarized_in
|
|
98
|
+
label: Summarized in
|
|
99
|
+
|
|
100
|
+
# Playbook — pulls the transcript (Fireflies) and calendar context (Google
|
|
101
|
+
# Calendar), summarizes it, and writes the follow-through: a note, tasks per
|
|
102
|
+
# action item, and any new attendees. Bound to the meeting kind so a captured
|
|
103
|
+
# meeting can offer this as a session.
|
|
104
|
+
playbooks:
|
|
105
|
+
- name: Import & Summarize Meeting
|
|
106
|
+
description: Pull a meeting's transcript, summarize it, and extract action items and attendees into tasks, notes, and people.
|
|
107
|
+
goalTemplate: >-
|
|
108
|
+
Import the meeting {meeting} — pull its transcript from Fireflies and use
|
|
109
|
+
Google Calendar for scheduling/attendee context. Produce: {outputTypes}.
|
|
110
|
+
Create a note with the summary, create a task for each action item, and
|
|
111
|
+
make sure attendees not already tracked are captured as person entities.
|
|
112
|
+
Update the meeting record's summary, transcriptUrl, and status.
|
|
113
|
+
subjectProfile:
|
|
114
|
+
profileSlug: meeting
|
|
115
|
+
params:
|
|
116
|
+
- { name: meeting, type: entity, label: Meeting, required: true }
|
|
117
|
+
- { name: outputTypes, type: text, label: Outputs, defaultValue: "a summary note, action-item tasks, and updated attendee records" }
|
|
118
|
+
executor: is-agent
|
|
119
|
+
grants:
|
|
120
|
+
- fireflies_import_last_meeting
|
|
121
|
+
- fireflies_search
|
|
122
|
+
- calendar_list
|
|
123
|
+
- entity.create
|
|
124
|
+
status: active
|
|
125
|
+
|
|
126
|
+
# Transcript import via Fireflies, calendar/attendee context via Google
|
|
127
|
+
# (nango-google). The user connects each once on install; a missing key fails
|
|
128
|
+
# gracefully per-skill.
|
|
129
|
+
integrations:
|
|
130
|
+
- templateKey: fireflies
|
|
131
|
+
- templateKey: nango-google
|
|
132
|
+
|
|
133
|
+
onboarding:
|
|
134
|
+
goal: >
|
|
135
|
+
Get recent meetings into the pod so summaries, action items, and attendees
|
|
136
|
+
are captured instead of living only in someone's memory of the call.
|
|
137
|
+
framing: |
|
|
138
|
+
THE CHIEF OF STAFF: Act as a sharp chief of staff who turns meetings into
|
|
139
|
+
follow-through. A meeting with no summary and no action items didn't
|
|
140
|
+
happen as far as the rest of the org is concerned.
|
|
141
|
+
expertise:
|
|
142
|
+
starters:
|
|
143
|
+
- >
|
|
144
|
+
Every meeting worth logging needs a "so what" — at least one action item
|
|
145
|
+
or decision. If a founder describes a meeting with no outcome, ask what
|
|
146
|
+
changed because of it.
|
|
147
|
+
- >
|
|
148
|
+
Attendees are people, not names in a transcript — capture them as
|
|
149
|
+
tracked contacts so the next meeting with them has history.
|
|
150
|
+
blindSpots:
|
|
151
|
+
- >
|
|
152
|
+
Recurring meetings (standups, 1:1s) get skipped because they feel
|
|
153
|
+
routine — but their action items are exactly what falls through the
|
|
154
|
+
cracks. Don't let "it's just a standup" excuse skipping capture.
|
|
155
|
+
bar: >
|
|
156
|
+
A handful of recent meetings are logged with a real summary and at least
|
|
157
|
+
one action item each, so the Import & Summarize playbook has something
|
|
158
|
+
concrete to run against.
|
|
159
|
+
collect:
|
|
160
|
+
- profileSlug: meeting
|
|
161
|
+
what: Recent or upcoming meetings worth tracking.
|
|
162
|
+
cardinality: few
|
|
163
|
+
keyFields: [meetingDate, platform]
|
|
164
|
+
openingQuestions:
|
|
165
|
+
- "What meetings do you want tracked — recurring ones, or just the important ones?"
|
|
166
|
+
- "Where do your meetings live — Zoom, Google Meet, somewhere else?"
|
|
167
|
+
- "Do you already record/transcribe them, or should that start now?"
|
|
168
|
+
doneWhen: A few meetings captured with a date and platform, at least one with a summary.
|
|
169
|
+
|
|
170
|
+
app:
|
|
171
|
+
kind: stored
|
|
172
|
+
cellKey: entity-detail
|
|
173
|
+
|
|
174
|
+
layout:
|
|
175
|
+
defaultView: Meetings
|
|
176
|
+
defaultApp: null
|
|
177
|
+
pinnedApps: [dashboard, data]
|
|
178
|
+
sidebarItems:
|
|
179
|
+
- kind: view
|
|
180
|
+
viewName: Meetings
|
|
181
|
+
label: Meetings
|
|
182
|
+
icon: video
|
|
183
|
+
- kind: view
|
|
184
|
+
viewName: Meetings Calendar
|
|
185
|
+
label: Calendar
|
|
186
|
+
icon: calendar
|
|
187
|
+
- kind: view
|
|
188
|
+
viewName: Action Items
|
|
189
|
+
label: Action Items
|
|
190
|
+
icon: check-square
|
|
191
|
+
- kind: app
|
|
192
|
+
appId: data
|
|
193
|
+
label: Data
|
|
194
|
+
icon: database
|
|
195
|
+
|
|
196
|
+
bento:
|
|
197
|
+
viewName: Meetings
|
|
198
|
+
blocks:
|
|
199
|
+
- kind: view
|
|
200
|
+
viewName: Meetings
|
|
201
|
+
pos: { x: 0, y: 0, w: 8, h: 5 }
|
|
202
|
+
- kind: view
|
|
203
|
+
viewName: Action Items
|
|
204
|
+
pos: { x: 8, y: 0, w: 4, h: 5 }
|