@notis_ai/cli 0.2.0-beta.113.1 → 0.2.0-beta.116.1

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 (178) hide show
  1. package/README.md +15 -0
  2. package/dist/scaffolds/notis-database/app/page.tsx +38 -41
  3. package/dist/scaffolds/notis-database/lib/types.ts +5 -7
  4. package/dist/scaffolds/notis-database/metadata/screenshot-1.png +0 -0
  5. package/dist/scaffolds/notis-database/metadata/screenshot-2.png +0 -0
  6. package/dist/scaffolds/notis-database/metadata/screenshot-3.png +0 -0
  7. package/dist/scaffolds/notis-database/metadata/screenshot-4.png +0 -0
  8. package/dist/scaffolds/notis-database/metadata/screenshot-5.png +0 -0
  9. package/dist/scaffolds/notis-database/metadata/screenshot-fixtures.json +1839 -0
  10. package/dist/scaffolds/notis-database/notis.config.ts +48 -1
  11. package/dist/scaffolds/notis-database/package-lock.json +3935 -0
  12. package/dist/scaffolds/notis-database/package.json +2 -1
  13. package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/package.json +4 -0
  14. package/dist/scaffolds/notis-database/packages/sdk/src/components/DocumentEditor.tsx +93 -0
  15. package/dist/scaffolds/notis-database/packages/sdk/src/components/Markdown.tsx +60 -0
  16. package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectActionBar.tsx +7 -2
  17. package/dist/scaffolds/notis-database/packages/sdk/src/config.ts +196 -0
  18. package/dist/scaffolds/notis-database/packages/sdk/src/documents.ts +229 -0
  19. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
  20. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocument.ts +78 -0
  21. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useDocuments.ts +121 -0
  22. package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useMultiSelect.ts +38 -2
  23. package/dist/scaffolds/notis-database/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
  24. package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/index.ts +30 -1
  25. package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/runtime.ts +76 -17
  26. package/dist/scaffolds/notis-database/packages/sdk/src/styles.css +186 -0
  27. package/dist/scaffolds/notis-database/src/mock-runtime.ts +4 -1
  28. package/dist/scaffolds/notis-journal/CHANGELOG.md +4 -0
  29. package/dist/scaffolds/notis-journal/app/page.tsx +1 -0
  30. package/dist/scaffolds/notis-journal/package-lock.json +4615 -0
  31. package/dist/scaffolds/notis-journal/package.json +2 -2
  32. package/dist/scaffolds/notis-journal/packages/sdk/src/config.ts +52 -1
  33. package/dist/scaffolds/notis-notes/app/page.tsx +39 -46
  34. package/dist/scaffolds/notis-notes/app/phosphor-icons.ts +596 -0
  35. package/dist/scaffolds/notis-notes/lib/visible-properties.ts +144 -0
  36. package/dist/scaffolds/notis-notes/metadata/screenshot-1.png +0 -0
  37. package/dist/scaffolds/notis-notes/metadata/screenshot-2.png +0 -0
  38. package/dist/scaffolds/notis-notes/metadata/screenshot-3.png +0 -0
  39. package/dist/scaffolds/notis-notes/metadata/screenshot-4.png +0 -0
  40. package/dist/scaffolds/notis-notes/metadata/screenshot-5.png +0 -0
  41. package/dist/scaffolds/notis-notes/metadata/screenshot-fixtures.json +752 -0
  42. package/dist/scaffolds/notis-notes/notis.config.ts +48 -3
  43. package/dist/scaffolds/notis-notes/package-lock.json +4636 -0
  44. package/dist/scaffolds/notis-notes/package.json +7 -3
  45. package/dist/scaffolds/notis-notes/packages/sdk/package.json +36 -0
  46. package/dist/scaffolds/notis-notes/packages/sdk/src/components/DocumentEditor.tsx +93 -0
  47. package/dist/scaffolds/notis-notes/packages/sdk/src/components/Markdown.tsx +60 -0
  48. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
  49. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
  50. package/dist/scaffolds/notis-notes/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
  51. package/dist/scaffolds/notis-notes/packages/sdk/src/config.ts +196 -0
  52. package/dist/scaffolds/notis-notes/packages/sdk/src/documents.ts +229 -0
  53. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useBackend.ts +41 -0
  54. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
  55. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocument.ts +78 -0
  56. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useDocuments.ts +121 -0
  57. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
  58. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotis.ts +34 -0
  59. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
  60. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTool.ts +64 -0
  61. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTools.ts +56 -0
  62. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
  63. package/dist/scaffolds/notis-notes/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
  64. package/dist/scaffolds/notis-notes/packages/sdk/src/index.ts +83 -0
  65. package/dist/scaffolds/notis-notes/packages/sdk/src/provider.tsx +43 -0
  66. package/dist/scaffolds/notis-notes/packages/sdk/src/runtime.ts +220 -0
  67. package/dist/scaffolds/notis-notes/packages/sdk/src/styles.css +186 -0
  68. package/dist/scaffolds/notis-notes/packages/sdk/src/ui.ts +15 -0
  69. package/dist/scaffolds/notis-notes/packages/sdk/src/vite.ts +56 -0
  70. package/dist/scaffolds/notis-notes/packages/sdk/tsconfig.json +15 -0
  71. package/dist/scaffolds/notis-random/CHANGELOG.md +12 -2
  72. package/dist/scaffolds/notis-random/app/history/page.tsx +5 -4
  73. package/dist/scaffolds/notis-random/app/page.tsx +141 -74
  74. package/dist/scaffolds/notis-random/components/ui/button.tsx +50 -0
  75. package/dist/scaffolds/notis-random/components/ui/card.tsx +16 -0
  76. package/dist/scaffolds/notis-random/components/ui/input.tsx +23 -0
  77. package/dist/scaffolds/notis-random/lib/notis-tools.ts +26 -7
  78. package/dist/scaffolds/notis-random/lib/rng.ts +179 -19
  79. package/dist/scaffolds/notis-random/lib/roll-record.ts +103 -16
  80. package/dist/scaffolds/notis-random/metadata/screenshot-1.png +0 -0
  81. package/dist/scaffolds/notis-random/metadata/screenshot-2.png +0 -0
  82. package/dist/scaffolds/notis-random/metadata/screenshot-3.png +0 -0
  83. package/dist/scaffolds/notis-random/metadata/screenshot-4.png +0 -0
  84. package/dist/scaffolds/notis-random/metadata/screenshot-5.png +0 -0
  85. package/dist/scaffolds/notis-random/metadata/screenshot-fixtures.json +753 -0
  86. package/dist/scaffolds/notis-random/notis.config.ts +47 -3
  87. package/dist/scaffolds/notis-random/package-lock.json +4513 -0
  88. package/dist/scaffolds/notis-random/package.json +6 -2
  89. package/dist/scaffolds/notis-random/packages/sdk/package.json +36 -0
  90. package/dist/scaffolds/notis-random/packages/sdk/src/components/DocumentEditor.tsx +93 -0
  91. package/dist/scaffolds/notis-random/packages/sdk/src/components/Markdown.tsx +60 -0
  92. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectActionBar.tsx +278 -0
  93. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectCheckbox.tsx +91 -0
  94. package/dist/scaffolds/notis-random/packages/sdk/src/components/MultiSelectDragOverlay.tsx +39 -0
  95. package/dist/scaffolds/notis-random/packages/sdk/src/config.ts +196 -0
  96. package/dist/scaffolds/notis-random/packages/sdk/src/documents.ts +229 -0
  97. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useBackend.ts +41 -0
  98. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDatabaseSchema.ts +85 -0
  99. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocument.ts +78 -0
  100. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useDocuments.ts +121 -0
  101. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useMultiSelect.ts +539 -0
  102. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotis.ts +34 -0
  103. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useNotisNavigation.ts +49 -0
  104. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTool.ts +64 -0
  105. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTools.ts +56 -0
  106. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useTopBarSearch.ts +73 -0
  107. package/dist/scaffolds/notis-random/packages/sdk/src/hooks/useUpsertDocument.ts +95 -0
  108. package/dist/scaffolds/notis-random/packages/sdk/src/index.ts +83 -0
  109. package/dist/scaffolds/notis-random/packages/sdk/src/provider.tsx +43 -0
  110. package/dist/scaffolds/notis-random/packages/sdk/src/runtime.ts +220 -0
  111. package/dist/scaffolds/notis-random/packages/sdk/src/styles.css +186 -0
  112. package/dist/scaffolds/notis-random/packages/sdk/src/ui.ts +15 -0
  113. package/dist/scaffolds/notis-random/packages/sdk/src/vite.ts +56 -0
  114. package/dist/scaffolds/notis-random/packages/sdk/tsconfig.json +15 -0
  115. package/dist/scaffolds/notis-random/tailwind.config.ts +8 -1
  116. package/dist/scaffolds.json +8 -17
  117. package/package.json +2 -1
  118. package/skills/notis-apps/SKILL.md +438 -144
  119. package/skills/notis-apps/cli.md +15 -0
  120. package/skills/notis-cli/SKILL.md +8 -4
  121. package/skills/notis-onboarding/BRIEF.md +93 -0
  122. package/src/cli.js +16 -0
  123. package/src/command-specs/apps.js +290 -41
  124. package/src/command-specs/index.js +2 -0
  125. package/src/command-specs/onboarding.js +216 -0
  126. package/src/command-specs/tools.js +8 -4
  127. package/src/runtime/app-dev-server.js +3 -2
  128. package/src/runtime/app-dev-sessions.js +14 -40
  129. package/src/runtime/app-platform.js +65 -4
  130. package/src/runtime/desktop-auth.js +66 -17
  131. package/src/runtime/errors.js +1 -0
  132. package/src/runtime/profiles.js +27 -6
  133. package/src/runtime/telemetry.js +92 -0
  134. package/src/runtime/transport.js +12 -0
  135. package/template/.harness/index.html.tmpl +30 -2
  136. package/template/packages/sdk/src/config.ts +38 -1
  137. package/dist/scaffolds/notis-affiliate-prospects/CHANGELOG.md +0 -7
  138. package/dist/scaffolds/notis-affiliate-prospects/README.md +0 -20
  139. package/dist/scaffolds/notis-affiliate-prospects/app/globals.css +0 -42
  140. package/dist/scaffolds/notis-affiliate-prospects/app/layout.tsx +0 -7
  141. package/dist/scaffolds/notis-affiliate-prospects/app/page.tsx +0 -248
  142. package/dist/scaffolds/notis-affiliate-prospects/app/prospects/page.tsx +0 -134
  143. package/dist/scaffolds/notis-affiliate-prospects/app/segments/page.tsx +0 -108
  144. package/dist/scaffolds/notis-affiliate-prospects/components/ui/card.tsx +0 -35
  145. package/dist/scaffolds/notis-affiliate-prospects/index.html +0 -12
  146. package/dist/scaffolds/notis-affiliate-prospects/lib/affiliate-data.ts +0 -218
  147. package/dist/scaffolds/notis-affiliate-prospects/lib/demo-prospects.ts +0 -52
  148. package/dist/scaffolds/notis-affiliate-prospects/lib/notis-tools.ts +0 -100
  149. package/dist/scaffolds/notis-affiliate-prospects/lib/utils.ts +0 -25
  150. package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-1.png +0 -0
  151. package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-2.png +0 -0
  152. package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-3.png +0 -0
  153. package/dist/scaffolds/notis-affiliate-prospects/metadata/screenshot-fixtures.json +0 -187
  154. package/dist/scaffolds/notis-affiliate-prospects/notis.config.ts +0 -65
  155. package/dist/scaffolds/notis-affiliate-prospects/package.json +0 -32
  156. package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/config.ts +0 -90
  157. package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useDatabase.ts +0 -76
  158. package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/hooks/useUpsertDocument.ts +0 -50
  159. package/dist/scaffolds/notis-affiliate-prospects/packages/sdk/src/styles.css +0 -38
  160. package/dist/scaffolds/notis-affiliate-prospects/postcss.config.mjs +0 -6
  161. package/dist/scaffolds/notis-affiliate-prospects/src/dev-main.tsx +0 -72
  162. package/dist/scaffolds/notis-affiliate-prospects/src/mock-runtime.ts +0 -242
  163. package/dist/scaffolds/notis-affiliate-prospects/tailwind.config.ts +0 -51
  164. package/dist/scaffolds/notis-affiliate-prospects/tsconfig.json +0 -23
  165. package/dist/scaffolds/notis-affiliate-prospects/vite.config.ts +0 -11
  166. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectCheckbox.tsx +0 -0
  167. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/components/MultiSelectDragOverlay.tsx +0 -0
  168. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useBackend.ts +0 -0
  169. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useNotis.ts +0 -0
  170. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useNotisNavigation.ts +0 -0
  171. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTool.ts +0 -0
  172. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTools.ts +0 -0
  173. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/hooks/useTopBarSearch.ts +0 -0
  174. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/provider.tsx +0 -0
  175. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/ui.ts +0 -0
  176. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/src/vite.ts +0 -0
  177. /package/dist/scaffolds/{notis-affiliate-prospects → notis-database}/packages/sdk/tsconfig.json +0 -0
  178. /package/dist/scaffolds/{notis-affiliate-prospects → notis-random}/components.json +0 -0
@@ -1,108 +0,0 @@
1
- 'use client';
2
-
3
- import { useMemo } from 'react';
4
- import {
5
- ArrowSquareOutIcon,
6
- ChartLineUpIcon,
7
- CheckCircleIcon,
8
- MagnifyingGlassIcon,
9
- UsersIcon,
10
- } from '@phosphor-icons/react';
11
-
12
- import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
13
- import { SEGMENTS, compactNumber } from '@/lib/affiliate-data';
14
- import { useAffiliateProspects } from '@/lib/notis-tools';
15
-
16
- export default function SegmentsPage() {
17
- const { prospects, loading, error } = useAffiliateProspects();
18
- const segments = useMemo(
19
- () =>
20
- SEGMENTS.map((segment) => {
21
- const rows = prospects.filter((prospect) => prospect.segment === segment.name);
22
- const qualified = rows.filter((prospect) => prospect.fitScore >= 75 && prospect.status !== 'Disqualified').length;
23
- const activated = rows.filter((prospect) => prospect.status === 'Activated').length;
24
- const reach = rows.reduce((sum, prospect) => sum + prospect.audienceSize + prospect.trafficEstimate, 0);
25
- return { ...segment, total: rows.length, qualified, activated, reach };
26
- }),
27
- [prospects],
28
- );
29
-
30
- return (
31
- <main data-store-screenshot="segments" className="affiliate-app-shell space-y-6">
32
- <header>
33
- <p className="text-xs font-medium text-muted-foreground">Seven focused acquisition motions</p>
34
- <h1 className="mt-1 text-2xl font-semibold tracking-tight">Recruiting segments</h1>
35
- <p className="mt-1 max-w-2xl text-sm text-muted-foreground">
36
- Each segment has its own sourcing signal, qualification model, and outreach playbook in
37
- <span className="font-medium text-foreground"> campaign/affiliate-network/segments</span>.
38
- </p>
39
- </header>
40
-
41
- {error ? (
42
- <p className="rounded-lg bg-destructive/10 px-4 py-3 text-sm text-destructive">{error.message}</p>
43
- ) : null}
44
- {loading && !prospects.length ? (
45
- <p className="rounded-lg border border-dashed border-border py-8 text-center text-sm text-muted-foreground">
46
- Loading segment coverage…
47
- </p>
48
- ) : null}
49
-
50
- <section className="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3">
51
- {segments.map((segment, index) => (
52
- <Card key={segment.name}>
53
- <CardHeader>
54
- <div className="flex items-start justify-between gap-3">
55
- <div>
56
- <p className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
57
- Segment {index + 1}
58
- </p>
59
- <CardTitle className="mt-1 text-base">{segment.name}</CardTitle>
60
- </div>
61
- <div className="rounded-lg bg-muted p-2 text-muted-foreground">
62
- <UsersIcon size={18} />
63
- </div>
64
- </div>
65
- <p className="text-xs leading-relaxed text-muted-foreground">{segment.description}</p>
66
- </CardHeader>
67
- <CardContent>
68
- <div className="grid grid-cols-3 gap-2">
69
- <Kpi icon={MagnifyingGlassIcon} value={segment.total} label="Found" />
70
- <Kpi icon={ChartLineUpIcon} value={segment.qualified} label="Qualified" />
71
- <Kpi icon={CheckCircleIcon} value={segment.activated} label="Active" />
72
- </div>
73
- <div className="mt-4 rounded-lg bg-muted/50 p-3">
74
- <p className="text-[11px] uppercase tracking-wide text-muted-foreground">Primary source</p>
75
- <p className="mt-1 text-sm font-medium">{segment.source}</p>
76
- <p className="mt-2 text-xs text-muted-foreground">
77
- Combined discoverable reach: {compactNumber(segment.reach)}
78
- </p>
79
- </div>
80
- <div className="mt-4 flex items-center justify-between text-xs text-muted-foreground">
81
- <span>Playbook is source-controlled</span>
82
- <ArrowSquareOutIcon size={14} />
83
- </div>
84
- </CardContent>
85
- </Card>
86
- ))}
87
- </section>
88
- </main>
89
- );
90
- }
91
-
92
- function Kpi({
93
- icon: Icon,
94
- value,
95
- label,
96
- }: {
97
- icon: typeof UsersIcon;
98
- value: number;
99
- label: string;
100
- }) {
101
- return (
102
- <div className="rounded-lg border border-border p-2.5">
103
- <Icon size={14} className="text-muted-foreground" />
104
- <p className="mt-2 text-lg font-semibold tabular-nums">{value}</p>
105
- <p className="text-[10px] text-muted-foreground">{label}</p>
106
- </div>
107
- );
108
- }
@@ -1,35 +0,0 @@
1
- import * as React from 'react';
2
-
3
- import { cn } from '@/lib/utils';
4
-
5
- export const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
6
- ({ className, ...props }, ref) => (
7
- <div
8
- ref={ref}
9
- className={cn('rounded-xl border border-border bg-card text-card-foreground shadow-sm', className)}
10
- {...props}
11
- />
12
- ),
13
- );
14
- Card.displayName = 'Card';
15
-
16
- export const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
17
- ({ className, ...props }, ref) => (
18
- <div ref={ref} className={cn('flex flex-col gap-1.5 p-5', className)} {...props} />
19
- ),
20
- );
21
- CardHeader.displayName = 'CardHeader';
22
-
23
- export const CardTitle = React.forwardRef<HTMLHeadingElement, React.HTMLAttributes<HTMLHeadingElement>>(
24
- ({ className, ...props }, ref) => (
25
- <h3 ref={ref} className={cn('text-sm font-semibold tracking-tight', className)} {...props} />
26
- ),
27
- );
28
- CardTitle.displayName = 'CardTitle';
29
-
30
- export const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
31
- ({ className, ...props }, ref) => (
32
- <div ref={ref} className={cn('p-5 pt-0', className)} {...props} />
33
- ),
34
- );
35
- CardContent.displayName = 'CardContent';
@@ -1,12 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Affiliate Prospects (dev preview)</title>
7
- </head>
8
- <body>
9
- <div id="root"></div>
10
- <script type="module" src="/src/dev-main.tsx"></script>
11
- </body>
12
- </html>
@@ -1,218 +0,0 @@
1
- export const SEGMENTS = [
2
- {
3
- name: 'Customer Advocate',
4
- short: 'Customers',
5
- description: 'Power users and existing referrers with firsthand product credibility.',
6
- source: 'Rewardful + product usage',
7
- },
8
- {
9
- name: 'Automation Agency',
10
- short: 'Agencies',
11
- description: 'Zapier, Make, n8n, and AI automation consultants who recommend tools to clients.',
12
- source: 'Partner directories + LinkedIn',
13
- },
14
- {
15
- name: 'AI Creator',
16
- short: 'Creators',
17
- description: 'AI, productivity, and automation creators, newsletters, and educators.',
18
- source: 'YouTube + LinkedIn + newsletters',
19
- },
20
- {
21
- name: 'SEO Publisher',
22
- short: 'Publishers',
23
- description: 'Comparison sites and writers ranking for high-intent automation searches.',
24
- source: 'DataForSEO SERPs',
25
- },
26
- {
27
- name: 'Integration Partner',
28
- short: 'Partners',
29
- description: 'Complementary SaaS teams, implementation partners, and integration ecosystems.',
30
- source: 'Integration catalog + partner pages',
31
- },
32
- {
33
- name: 'Community Educator',
34
- short: 'Communities',
35
- description: 'Operators of trusted no-code, RevOps, AI, and founder communities.',
36
- source: 'Community directories + events',
37
- },
38
- {
39
- name: 'Affiliate Marketplace',
40
- short: 'Marketplaces',
41
- description: 'Experienced B2B SaaS affiliates active in curated partner marketplaces.',
42
- source: 'PartnerStack + impact.com',
43
- },
44
- ] as const;
45
-
46
- export const PIPELINE = [
47
- 'New',
48
- 'Researching',
49
- 'Qualified',
50
- 'Ready for Outreach',
51
- 'Contacted',
52
- 'Replied',
53
- 'Interested',
54
- 'Applied',
55
- 'Activated',
56
- 'Nurture',
57
- 'Disqualified',
58
- ] as const;
59
-
60
- export type SegmentName = (typeof SEGMENTS)[number]['name'];
61
- export type ProspectStatus = (typeof PIPELINE)[number];
62
-
63
- export interface AffiliateProspect {
64
- id: string;
65
- name: string;
66
- company: string;
67
- segment: SegmentName;
68
- status: ProspectStatus;
69
- priority: 'P0' | 'P1' | 'P2' | 'P3';
70
- fitScore: number;
71
- email: string;
72
- linkedInUrl: string;
73
- website: string;
74
- source: string;
75
- audienceSize: number;
76
- trafficEstimate: number;
77
- enrichmentStatus: string;
78
- outreachChannel: string;
79
- nextAction: string;
80
- nextActionDate: string;
81
- lastTouchDate: string;
82
- referredSignups: number;
83
- activatedReferrals: number;
84
- attributedMrr: number;
85
- notes: string;
86
- optedOut: boolean;
87
- }
88
-
89
- interface DocumentLike {
90
- id?: string;
91
- document_id?: string;
92
- title?: string;
93
- properties?: Record<string, unknown>;
94
- }
95
-
96
- export function normalizeProspect(document: DocumentLike): AffiliateProspect {
97
- const properties = document.properties ?? {};
98
- return {
99
- id: String(document.id ?? document.document_id ?? document.title ?? crypto.randomUUID()),
100
- name: textValue(properties.Name) || document.title || 'Untitled prospect',
101
- company: textValue(properties.Company),
102
- segment: segmentValue(textValue(properties.Segment)),
103
- status: statusValue(textValue(properties.Status)),
104
- priority: priorityValue(textValue(properties.Priority)),
105
- fitScore: numberValue(properties['Fit Score']),
106
- email: textValue(properties.Email),
107
- linkedInUrl: textValue(properties.LinkedIn),
108
- website: textValue(properties.Website),
109
- source: textValue(properties.Source),
110
- audienceSize: numberValue(properties['Audience Size']),
111
- trafficEstimate: numberValue(properties['Monthly Traffic']),
112
- enrichmentStatus: textValue(properties['Enrichment Status']),
113
- outreachChannel: textValue(properties['Outreach Channel']),
114
- nextAction: textValue(properties['Next Action']),
115
- nextActionDate: dateValue(properties['Next Action Date']),
116
- lastTouchDate: dateValue(properties['Last Touch Date']),
117
- referredSignups: numberValue(properties['Referred Signups']),
118
- activatedReferrals: numberValue(properties['Activated Referrals']),
119
- attributedMrr: numberValue(properties['Attributed MRR']),
120
- notes: textValue(properties.Notes),
121
- optedOut: booleanValue(properties['Opted Out']),
122
- };
123
- }
124
-
125
- export function statusTone(status: ProspectStatus): string {
126
- if (status === 'Activated') return 'bg-emerald-500/10 text-emerald-700 dark:text-emerald-300';
127
- if (['Interested', 'Applied'].includes(status)) return 'bg-violet-500/10 text-violet-700 dark:text-violet-300';
128
- if (['Contacted', 'Replied'].includes(status)) return 'bg-sky-500/10 text-sky-700 dark:text-sky-300';
129
- if (['Nurture', 'Disqualified'].includes(status)) return 'bg-muted text-muted-foreground';
130
- return 'bg-amber-500/10 text-amber-700 dark:text-amber-300';
131
- }
132
-
133
- export function compactNumber(value: number): string {
134
- return new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 }).format(value);
135
- }
136
-
137
- export function money(value: number): string {
138
- return new Intl.NumberFormat('en', {
139
- style: 'currency',
140
- currency: 'USD',
141
- maximumFractionDigits: 0,
142
- }).format(value);
143
- }
144
-
145
- function segmentValue(value: string): SegmentName {
146
- const normalized = value.trim().toLowerCase();
147
- const aliases: Record<string, SegmentName> = {
148
- 'customer advocate': 'Customer Advocate',
149
- 'customer advocates': 'Customer Advocate',
150
- 'automation agency': 'Automation Agency',
151
- 'automation agencies': 'Automation Agency',
152
- 'ai creator': 'AI Creator',
153
- 'ai creators': 'AI Creator',
154
- 'seo publisher': 'SEO Publisher',
155
- 'seo publishers': 'SEO Publisher',
156
- 'integration partner': 'Integration Partner',
157
- 'integration partners': 'Integration Partner',
158
- 'community educator': 'Community Educator',
159
- 'community educators': 'Community Educator',
160
- 'affiliate marketplace': 'Affiliate Marketplace',
161
- 'affiliate marketplaces': 'Affiliate Marketplace',
162
- };
163
- return aliases[normalized] ?? 'Customer Advocate';
164
- }
165
-
166
- function statusValue(value: string): ProspectStatus {
167
- return (PIPELINE.find((status) => status === value) ?? 'New') as ProspectStatus;
168
- }
169
-
170
- function priorityValue(value: string): AffiliateProspect['priority'] {
171
- return value === 'P0' || value === 'P1' || value === 'P2' || value === 'P3' ? value : 'P2';
172
- }
173
-
174
- function textValue(value: unknown): string {
175
- if (typeof value === 'string') return value;
176
- if (!value || typeof value !== 'object') return '';
177
- const typed = value as Record<string, unknown>;
178
- if (typeof typed.url === 'string') return typed.url;
179
- if (typeof typed.email === 'string') return typed.email;
180
- if (typed.select && typeof typed.select === 'object') return String((typed.select as Record<string, unknown>).name ?? '');
181
- if (typed.status && typeof typed.status === 'object') return String((typed.status as Record<string, unknown>).name ?? '');
182
- for (const key of ['title', 'rich_text']) {
183
- const rows = typed[key];
184
- if (Array.isArray(rows)) {
185
- return rows
186
- .map((row) => {
187
- if (!row || typeof row !== 'object') return '';
188
- const record = row as Record<string, unknown>;
189
- if (typeof record.plain_text === 'string') return record.plain_text;
190
- const text = record.text as Record<string, unknown> | undefined;
191
- return typeof text?.content === 'string' ? text.content : '';
192
- })
193
- .join('');
194
- }
195
- }
196
- return '';
197
- }
198
-
199
- function numberValue(value: unknown): number {
200
- if (typeof value === 'number') return value;
201
- if (value && typeof value === 'object' && typeof (value as Record<string, unknown>).number === 'number') {
202
- return (value as Record<string, unknown>).number as number;
203
- }
204
- return 0;
205
- }
206
-
207
- function booleanValue(value: unknown): boolean {
208
- if (typeof value === 'boolean') return value;
209
- if (value && typeof value === 'object') return Boolean((value as Record<string, unknown>).checkbox);
210
- return false;
211
- }
212
-
213
- function dateValue(value: unknown): string {
214
- if (typeof value === 'string') return value;
215
- if (!value || typeof value !== 'object') return '';
216
- const date = (value as Record<string, unknown>).date;
217
- return date && typeof date === 'object' ? String((date as Record<string, unknown>).start ?? '') : '';
218
- }
@@ -1,52 +0,0 @@
1
- import type { AffiliateProspect, ProspectStatus, SegmentName } from '@/lib/affiliate-data';
2
-
3
- export const DEMO_PROSPECTS: AffiliateProspect[] = [
4
- demo('Alex Morgan', 'FlowNorth', 'Automation Agency', 'Interested', 94, 'Zapier Solution Partner Directory', 22200, 0, 'Send partner economics one-pager'),
5
- demo('Maya Chen', 'Agent Ops Weekly', 'AI Creator', 'Replied', 91, 'Newsletter discovery', 60000, 0, 'Share creator demo kit'),
6
- demo('Noah Williams', 'Automate Better', 'SEO Publisher', 'Ready for Outreach', 88, 'DataForSEO: Zapier alternatives', 76000, 0, 'Approve comparison-page pitch'),
7
- demo('Sofia Rossi', 'RevOps Circle', 'Community Educator', 'Qualified', 86, 'Community operator list', 14500, 0, 'Enrich sponsor history'),
8
- demo('Theo Martin', 'Notis customer', 'Customer Advocate', 'Activated', 97, 'Rewardful + product usage', 0, 1680, ''),
9
- demo('Lena Fischer', 'StackPilot', 'Integration Partner', 'Applied', 92, 'Integration overlap analysis', 62000, 0, 'Confirm co-marketing launch date'),
10
- demo('Sam Patel', 'SaaS Growth Lab', 'Affiliate Marketplace', 'Contacted', 82, 'PartnerStack marketplace', 51000, 0, 'Follow up with use-case proof'),
11
- demo('Jordan Lee', 'OpsCraft', 'Automation Agency', 'Qualified', 84, 'Make Partner Directory', 5400, 0, 'Review recent client verticals'),
12
- demo('Camille Laurent', 'Productive AI', 'SEO Publisher', 'Activated', 90, 'DataForSEO: AI automation tools', 92000, 2940, ''),
13
- demo('Owen King', 'Notis customer', 'Customer Advocate', 'Contacted', 80, 'Product usage', 0, 0, 'Invite to advocate cohort'),
14
- ];
15
-
16
- function demo(
17
- name: string,
18
- company: string,
19
- segment: SegmentName,
20
- status: ProspectStatus,
21
- fitScore: number,
22
- source: string,
23
- reach: number,
24
- attributedMrr: number,
25
- nextAction: string,
26
- ): AffiliateProspect {
27
- return {
28
- id: `demo-${name.toLowerCase().replace(/[^a-z0-9]+/g, '-')}`,
29
- name,
30
- company,
31
- segment,
32
- status,
33
- priority: fitScore >= 90 ? 'P0' : fitScore >= 75 ? 'P1' : 'P2',
34
- fitScore,
35
- email: `${name.split(' ')[0].toLowerCase()}@example.com`,
36
- linkedInUrl: 'https://linkedin.com',
37
- website: '',
38
- source,
39
- audienceSize: reach,
40
- trafficEstimate: 0,
41
- enrichmentStatus: 'Verified',
42
- outreachChannel: 'Email + LinkedIn',
43
- nextAction,
44
- nextActionDate: nextAction ? '2026-07-25' : '',
45
- lastTouchDate: '',
46
- referredSignups: attributedMrr ? 12 : 0,
47
- activatedReferrals: attributedMrr ? 5 : 0,
48
- attributedMrr,
49
- notes: '',
50
- optedOut: false,
51
- };
52
- }
@@ -1,100 +0,0 @@
1
- 'use client';
2
-
3
- import { useCallback, useEffect, useState } from 'react';
4
- import { useNotis, useTool } from '@notis/sdk';
5
-
6
- import { normalizeProspect, type AffiliateProspect } from '@/lib/affiliate-data';
7
- import { DEMO_PROSPECTS } from '@/lib/demo-prospects';
8
-
9
- type QueryArgs = {
10
- database_slug: 'affiliate_prospects_1';
11
- query: { page_size: number };
12
- offset?: number;
13
- };
14
-
15
- type QueryResult = {
16
- documents?: Array<{
17
- id?: string;
18
- document_id?: string;
19
- title?: string;
20
- properties?: Record<string, unknown>;
21
- }>;
22
- error?: string;
23
- message?: string;
24
- has_more?: boolean;
25
- next_offset?: number | null;
26
- };
27
-
28
- function isDevelopmentPreview(appId?: string) {
29
- if (appId === 'harness-app') return true;
30
- if (typeof window === 'undefined') return false;
31
- return window.location.pathname.includes('/apps/affiliate-prospects-dev-');
32
- }
33
-
34
- export function useAffiliateProspects() {
35
- const { app } = useNotis();
36
- const query = useTool<QueryArgs, QueryResult>('LOCAL_NOTIS_DATABASE_QUERY');
37
- const [prospects, setProspects] = useState<AffiliateProspect[]>([]);
38
- const [localError, setLocalError] = useState<Error | null>(null);
39
- const [usingPreviewFixture, setUsingPreviewFixture] = useState(false);
40
- const [fetchKey, setFetchKey] = useState(0);
41
-
42
- const refetch = useCallback(() => setFetchKey((key) => key + 1), []);
43
-
44
- useEffect(() => {
45
- let cancelled = false;
46
- setLocalError(null);
47
- setUsingPreviewFixture(false);
48
- const fetchAllProspects = async () => {
49
- const documents: NonNullable<QueryResult['documents']> = [];
50
- let offset = 0;
51
- while (true) {
52
- const result = await query.call({
53
- database_slug: 'affiliate_prospects_1',
54
- query: { page_size: 500 },
55
- ...(offset ? { offset } : {}),
56
- });
57
- if (!result.documents && (result.error || result.message)) {
58
- throw new Error(result.error ?? result.message);
59
- }
60
- documents.push(...(result.documents ?? []));
61
- if (!result.has_more) return documents;
62
- if (typeof result.next_offset !== 'number' || result.next_offset <= offset) {
63
- throw new Error('Prospect query returned an invalid pagination offset');
64
- }
65
- offset = result.next_offset;
66
- }
67
- };
68
-
69
- void fetchAllProspects()
70
- .then((documents) => {
71
- if (cancelled) return;
72
- const normalized = documents.map(normalizeProspect);
73
- const shouldUsePreviewFixture = !normalized.length && isDevelopmentPreview(app?.id);
74
- setUsingPreviewFixture(shouldUsePreviewFixture);
75
- setProspects(shouldUsePreviewFixture ? DEMO_PROSPECTS : normalized);
76
- })
77
- .catch((error) => {
78
- if (cancelled) return;
79
- if (isDevelopmentPreview(app?.id)) {
80
- setProspects(DEMO_PROSPECTS);
81
- setLocalError(null);
82
- setUsingPreviewFixture(true);
83
- return;
84
- }
85
- setProspects([]);
86
- setUsingPreviewFixture(false);
87
- setLocalError(error instanceof Error ? error : new Error(String(error)));
88
- });
89
- return () => {
90
- cancelled = true;
91
- };
92
- }, [app?.id, fetchKey, query.call]);
93
-
94
- return {
95
- prospects,
96
- loading: query.loading,
97
- error: usingPreviewFixture ? null : query.error ?? localError,
98
- refetch,
99
- };
100
- }
@@ -1,25 +0,0 @@
1
- import { clsx, type ClassValue } from 'clsx';
2
- import { twMerge } from 'tailwind-merge';
3
-
4
- export function cn(...inputs: ClassValue[]): string {
5
- return twMerge(clsx(inputs));
6
- }
7
-
8
- export function formatNumber(value: number): string {
9
- if (Number.isInteger(value)) return value.toString();
10
- return value.toFixed(4).replace(/0+$/, '').replace(/\.$/, '');
11
- }
12
-
13
- export function relativeTime(iso: string | null | undefined): string {
14
- if (!iso) return '';
15
- const then = new Date(iso).getTime();
16
- if (Number.isNaN(then)) return '';
17
- const diffSeconds = Math.round((Date.now() - then) / 1000);
18
- if (diffSeconds < 5) return 'just now';
19
- if (diffSeconds < 60) return `${diffSeconds}s ago`;
20
- const diffMinutes = Math.round(diffSeconds / 60);
21
- if (diffMinutes < 60) return `${diffMinutes}m ago`;
22
- const diffHours = Math.round(diffMinutes / 60);
23
- if (diffHours < 24) return `${diffHours}h ago`;
24
- return new Date(iso).toLocaleDateString();
25
- }