@forwardimpact/outpost 3.0.3

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 (120) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +15 -0
  3. package/bin/fit-outpost.js +3 -0
  4. package/config/scheduler.json +34 -0
  5. package/package.json +63 -0
  6. package/src/agent-runner.js +189 -0
  7. package/src/index.js +5 -0
  8. package/src/kb-manager.js +228 -0
  9. package/src/outpost.js +417 -0
  10. package/src/scheduler.js +195 -0
  11. package/src/socket-server.js +336 -0
  12. package/src/state-manager.js +126 -0
  13. package/templates/.claude/agents/chief-of-staff.md +58 -0
  14. package/templates/.claude/agents/concierge.md +46 -0
  15. package/templates/.claude/agents/head-hunter.md +57 -0
  16. package/templates/.claude/agents/librarian.md +60 -0
  17. package/templates/.claude/agents/postman.md +47 -0
  18. package/templates/.claude/agents/recruiter.md +63 -0
  19. package/templates/.claude/settings.json +88 -0
  20. package/templates/.claude/skills/candidate-report/SKILL.md +139 -0
  21. package/templates/.claude/skills/candidate-report/references/report-template.html +203 -0
  22. package/templates/.claude/skills/candidate-report/references/report.css +438 -0
  23. package/templates/.claude/skills/candidate-report/references/rubric.md +76 -0
  24. package/templates/.claude/skills/candidate-report/scripts/render-pdf.mjs +48 -0
  25. package/templates/.claude/skills/deck-create/SKILL.md +75 -0
  26. package/templates/.claude/skills/deck-create/references/slide.css +35 -0
  27. package/templates/.claude/skills/deck-create/scripts/convert-to-pdf.mjs +47 -0
  28. package/templates/.claude/skills/deck-summarize/SKILL.md +139 -0
  29. package/templates/.claude/skills/deck-summarize/references/brief-template.md +85 -0
  30. package/templates/.claude/skills/deck-summarize/scripts/extract-pptx.mjs +210 -0
  31. package/templates/.claude/skills/doc-collab/SKILL.md +112 -0
  32. package/templates/.claude/skills/doc-create/SKILL.md +105 -0
  33. package/templates/.claude/skills/doc-create/scripts/convert-to-pdf.mjs +50 -0
  34. package/templates/.claude/skills/draft-emails/SKILL.md +141 -0
  35. package/templates/.claude/skills/draft-emails/references/template.md +26 -0
  36. package/templates/.claude/skills/draft-emails/scripts/scan-emails.mjs +66 -0
  37. package/templates/.claude/skills/draft-emails/scripts/send-email.mjs +139 -0
  38. package/templates/.claude/skills/extract-entities/SKILL.md +187 -0
  39. package/templates/.claude/skills/extract-entities/references/TEMPLATES.md +14 -0
  40. package/templates/.claude/skills/extract-entities/references/conditions.md +55 -0
  41. package/templates/.claude/skills/extract-entities/references/content.md +104 -0
  42. package/templates/.claude/skills/extract-entities/references/links.md +48 -0
  43. package/templates/.claude/skills/extract-entities/references/recruitment.md +57 -0
  44. package/templates/.claude/skills/extract-entities/references/resolution.md +71 -0
  45. package/templates/.claude/skills/extract-entities/references/sources.md +70 -0
  46. package/templates/.claude/skills/extract-entities/references/templates-conditions.md +36 -0
  47. package/templates/.claude/skills/extract-entities/references/templates-goals-priorities.md +63 -0
  48. package/templates/.claude/skills/extract-entities/references/templates-people-orgs.md +67 -0
  49. package/templates/.claude/skills/extract-entities/references/templates-projects-topics.md +68 -0
  50. package/templates/.claude/skills/extract-entities/scripts/state.mjs +131 -0
  51. package/templates/.claude/skills/hyprnote-follow/SKILL.md +174 -0
  52. package/templates/.claude/skills/hyprnote-follow/references/coaching.md +118 -0
  53. package/templates/.claude/skills/hyprnote-follow/references/debrief.md +59 -0
  54. package/templates/.claude/skills/hyprnote-follow/references/meeting-types.md +14 -0
  55. package/templates/.claude/skills/hyprnote-follow/scripts/follow.mjs +243 -0
  56. package/templates/.claude/skills/hyprnote-process/SKILL.md +147 -0
  57. package/templates/.claude/skills/hyprnote-process/references/extraction.md +84 -0
  58. package/templates/.claude/skills/hyprnote-process/references/sessions.md +81 -0
  59. package/templates/.claude/skills/hyprnote-process/scripts/scan.mjs +246 -0
  60. package/templates/.claude/skills/hyprnote-trim/SKILL.md +177 -0
  61. package/templates/.claude/skills/manage-tasks/SKILL.md +152 -0
  62. package/templates/.claude/skills/manage-tasks/references/format.md +73 -0
  63. package/templates/.claude/skills/meeting-prep/SKILL.md +180 -0
  64. package/templates/.claude/skills/organize-files/SKILL.md +146 -0
  65. package/templates/.claude/skills/organize-files/scripts/organize-by-type.mjs +105 -0
  66. package/templates/.claude/skills/organize-files/scripts/summarize.mjs +91 -0
  67. package/templates/.claude/skills/req-assess/SKILL.md +137 -0
  68. package/templates/.claude/skills/req-assess/references/interview-template.md +62 -0
  69. package/templates/.claude/skills/req-assess/references/panel-template.md +117 -0
  70. package/templates/.claude/skills/req-assess/references/rubric.md +65 -0
  71. package/templates/.claude/skills/req-decide/SKILL.md +163 -0
  72. package/templates/.claude/skills/req-decide/references/rubric.md +63 -0
  73. package/templates/.claude/skills/req-decide/references/template.md +118 -0
  74. package/templates/.claude/skills/req-forget/SKILL.md +154 -0
  75. package/templates/.claude/skills/req-forget/references/classify.md +23 -0
  76. package/templates/.claude/skills/req-forget/references/locations.md +59 -0
  77. package/templates/.claude/skills/req-forget/references/report-template.md +67 -0
  78. package/templates/.claude/skills/req-scan/SKILL.md +145 -0
  79. package/templates/.claude/skills/req-scan/references/fallbacks.md +76 -0
  80. package/templates/.claude/skills/req-scan/references/filters.md +83 -0
  81. package/templates/.claude/skills/req-scan/references/sources.md +76 -0
  82. package/templates/.claude/skills/req-scan/references/state.md +54 -0
  83. package/templates/.claude/skills/req-scan/references/template.md +36 -0
  84. package/templates/.claude/skills/req-scan/scripts/state.mjs +396 -0
  85. package/templates/.claude/skills/req-screen/SKILL.md +151 -0
  86. package/templates/.claude/skills/req-screen/references/rubric.md +94 -0
  87. package/templates/.claude/skills/req-screen/references/template.md +91 -0
  88. package/templates/.claude/skills/req-track/SKILL.md +189 -0
  89. package/templates/.claude/skills/req-track/references/fields.md +114 -0
  90. package/templates/.claude/skills/req-track/references/signals.md +42 -0
  91. package/templates/.claude/skills/req-track/references/statuses.md +32 -0
  92. package/templates/.claude/skills/req-track/references/templates.md +127 -0
  93. package/templates/.claude/skills/req-workday/SKILL.md +162 -0
  94. package/templates/.claude/skills/req-workday/references/field-mapping.md +40 -0
  95. package/templates/.claude/skills/req-workday/references/status-mapping.md +38 -0
  96. package/templates/.claude/skills/req-workday/references/templates.md +126 -0
  97. package/templates/.claude/skills/req-workday/references/xlsx-format.md +56 -0
  98. package/templates/.claude/skills/req-workday/scripts/parse-workday.mjs +313 -0
  99. package/templates/.claude/skills/send-chat/SKILL.md +170 -0
  100. package/templates/.claude/skills/sync-apple-calendar/SKILL.md +145 -0
  101. package/templates/.claude/skills/sync-apple-calendar/references/SCHEMA.md +80 -0
  102. package/templates/.claude/skills/sync-apple-calendar/scripts/query.mjs +307 -0
  103. package/templates/.claude/skills/sync-apple-calendar/scripts/sync.mjs +332 -0
  104. package/templates/.claude/skills/sync-apple-mail/SKILL.md +151 -0
  105. package/templates/.claude/skills/sync-apple-mail/references/SCHEMA.md +115 -0
  106. package/templates/.claude/skills/sync-apple-mail/scripts/parse-emlx.mjs +389 -0
  107. package/templates/.claude/skills/sync-apple-mail/scripts/sync-helpers.mjs +372 -0
  108. package/templates/.claude/skills/sync-apple-mail/scripts/sync.mjs +239 -0
  109. package/templates/.claude/skills/sync-teams/SKILL.md +177 -0
  110. package/templates/.claude/skills/sync-teams/scripts/idb-reader.mjs +147 -0
  111. package/templates/.claude/skills/sync-teams/scripts/leveldb-reader.mjs +288 -0
  112. package/templates/.claude/skills/sync-teams/scripts/sync.mjs +381 -0
  113. package/templates/.claude/skills/upstream-skill/SKILL.md +130 -0
  114. package/templates/.claude/skills/upstream-skill/references/examples.md +80 -0
  115. package/templates/.claude/skills/weekly-update/SKILL.md +141 -0
  116. package/templates/.claude/skills/weekly-update/references/template.md +55 -0
  117. package/templates/CLAUDE.md +134 -0
  118. package/templates/USER.md +3 -0
  119. package/templates/apm.yml +5 -0
  120. package/templates/knowledge/Briefings/.gitkeep +0 -0
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Render a candidate report HTML file to A4 PDF using Playwright.
4
+ *
5
+ * Usage: node scripts/render-pdf.mjs [input.html] [output.pdf]
6
+ *
7
+ * Defaults:
8
+ * input → /tmp/candidate-report.html
9
+ * output → ~/Desktop/candidate-report.pdf
10
+ *
11
+ * Requires: bun install playwright && bunx playwright install chromium
12
+ */
13
+
14
+ import { resolve, join } from "node:path";
15
+ import { homedir } from "node:os";
16
+
17
+ const HELP = `render-pdf — render candidate report HTML to A4 PDF via Playwright
18
+
19
+ Usage: node scripts/render-pdf.mjs [input.html] [output.pdf] [-h|--help]
20
+
21
+ Arguments:
22
+ input.html HTML report file (default: /tmp/candidate-report.html)
23
+ output.pdf Output PDF path (default: ~/Desktop/candidate-report.pdf)
24
+
25
+ Requires: bun install playwright && bunx playwright install chromium`;
26
+
27
+ if (process.argv.includes("-h") || process.argv.includes("--help")) {
28
+ console.log(HELP);
29
+ process.exit(0);
30
+ }
31
+
32
+ const positional = process.argv.slice(2).filter((a) => !a.startsWith("-"));
33
+ const input = positional[0] || "/tmp/candidate-report.html";
34
+ const output =
35
+ positional[1] || join(homedir(), "Desktop", "candidate-report.pdf");
36
+
37
+ const { chromium } = await import("playwright");
38
+ const browser = await chromium.launch();
39
+ const page = await browser.newPage();
40
+ await page.goto(`file://${resolve(input)}`, { waitUntil: "networkidle" });
41
+ await page.pdf({
42
+ path: output,
43
+ format: "A4",
44
+ printBackground: true,
45
+ margin: { top: 0, right: 0, bottom: 0, left: 0 },
46
+ });
47
+ await browser.close();
48
+ console.log(`Done: ${output}`);
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: deck-create
3
+ description: Generate PDF slide decks from user requests using Playwright to render HTML slides to PDF. Use when the user asks to create a presentation, slide deck, or pitch deck. Pulls context from the knowledge base for company info, project details, and people.
4
+ compatibility: Requires Node.js installed. Playwright is installed on first use.
5
+ ---
6
+
7
+ # Create Presentations
8
+
9
+ Generate PDF slide decks from user requests. Uses Playwright to render HTML
10
+ slides to PDF. Can pull context from the knowledge base for company info,
11
+ project details, and people.
12
+
13
+ ## Trigger
14
+
15
+ Run when the user asks to create a presentation, slide deck, or pitch deck.
16
+
17
+ ## Prerequisites
18
+
19
+ - Node.js installed
20
+ - Playwright will be installed on first use
21
+
22
+ ## Inputs
23
+
24
+ - User's description of the presentation
25
+ - `knowledge/` — optional context about company, product, team, projects
26
+
27
+ ## Outputs
28
+
29
+ - `~/Desktop/presentation.pdf` — the generated PDF presentation
30
+
31
+ ---
32
+
33
+ ## Workflow
34
+
35
+ 1. Check `knowledge/` for relevant context about the company, product, team,
36
+ etc.
37
+ 2. Ensure Playwright is installed:
38
+ `bun install playwright && bunx playwright install chromium`
39
+ 3. Create an HTML file at `/tmp/outpost-presentation.html` with slides
40
+ (1280x720px each)
41
+ 4. Include the required CSS from [references/slide.css](references/slide.css)
42
+ 5. Run the conversion script:
43
+
44
+ node scripts/convert-to-pdf.mjs
45
+
46
+ 6. Tell the user: "Your presentation is ready at ~/Desktop/presentation.pdf"
47
+
48
+ **Do NOT show HTML code to the user. Just create the PDF and deliver it.**
49
+
50
+ The conversion script accepts optional arguments:
51
+
52
+ node scripts/convert-to-pdf.mjs [input.html] [output.pdf]
53
+
54
+ Defaults: input = `/tmp/outpost-presentation.html`, output =
55
+ `~/Desktop/presentation.pdf`
56
+
57
+ ## PDF Rendering Rules
58
+
59
+ **These prevent rendering issues in PDF:**
60
+
61
+ 1. **No layered elements** — Style content elements directly, no separate
62
+ background elements
63
+ 2. **No box-shadow** — Use borders instead: `border: 1px solid #e5e7eb`
64
+ 3. **Bullets via CSS only** — Use `li::before` pseudo-elements
65
+ 4. **Content must fit** — Slides are 1280x720px with 60px padding. Safe area is
66
+ 1160x600px. Use `overflow: hidden`
67
+ 5. **No footers or headers** — No fixed/absolute positioned footer/header
68
+ elements
69
+
70
+ ## Constraints
71
+
72
+ - Always use the knowledge base for context when available
73
+ - Output to `~/Desktop/presentation.pdf` unless user specifies otherwise
74
+ - Keep slides clean and readable — max 5-6 bullet points per slide
75
+ - Use consistent styling throughout
@@ -0,0 +1,35 @@
1
+ /* Required CSS for HTML slide decks rendered to PDF via Playwright.
2
+ *
3
+ * Include this in the <style> block of /tmp/outpost-presentation.html.
4
+ * See scripts/convert-to-pdf.mjs for the rendering script.
5
+ *
6
+ * PDF rendering rules:
7
+ * - No layered elements — style content directly, no separate backgrounds
8
+ * - No box-shadow — use borders: border: 1px solid #e5e7eb
9
+ * - Bullets via CSS only — use li::before pseudo-elements
10
+ * - Content must fit — safe area is 1160x600px (1280x720 minus 60px padding)
11
+ * - No footers or headers — no fixed/absolute positioned elements
12
+ */
13
+
14
+ @page {
15
+ size: 1280px 720px;
16
+ margin: 0;
17
+ }
18
+
19
+ html {
20
+ -webkit-print-color-adjust: exact !important;
21
+ print-color-adjust: exact !important;
22
+ }
23
+
24
+ .slide {
25
+ width: 1280px;
26
+ height: 720px;
27
+ padding: 60px;
28
+ overflow: hidden;
29
+ page-break-after: always;
30
+ page-break-inside: avoid;
31
+ }
32
+
33
+ .slide:last-child {
34
+ page-break-after: auto;
35
+ }
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Convert HTML slides to PDF using Playwright.
4
+ *
5
+ * Renders an HTML file containing slide markup (1280x720px per slide) into a
6
+ * PDF document. Each slide is sized to exactly 1280x720 pixels with background
7
+ * colours and images preserved. Defaults to reading from /tmp and writing to
8
+ * ~/Desktop when no arguments are given.
9
+ *
10
+ * Requires: bun install playwright && bunx playwright install chromium
11
+ */
12
+
13
+ import { join } from "node:path";
14
+ import { resolve } from "node:path";
15
+ import { homedir } from "node:os";
16
+
17
+ const HELP = `convert-to-pdf — render HTML slides to PDF via Playwright
18
+
19
+ Usage: bun scripts/convert-to-pdf.mjs [input.html] [output.pdf] [-h|--help]
20
+
21
+ Arguments:
22
+ input.html HTML slides file (default: /tmp/outpost-presentation.html)
23
+ output.pdf Output PDF path (default: ~/Desktop/presentation.pdf)
24
+
25
+ Requires: bun install playwright && bunx playwright install chromium`;
26
+
27
+ if (process.argv.includes("-h") || process.argv.includes("--help")) {
28
+ console.log(HELP);
29
+ process.exit(0);
30
+ }
31
+
32
+ const positional = process.argv.slice(2).filter((a) => !a.startsWith("-"));
33
+ const input = positional[0] || "/tmp/outpost-presentation.html";
34
+ const output = positional[1] || join(homedir(), "Desktop", "presentation.pdf");
35
+
36
+ const { chromium } = await import("playwright");
37
+ const browser = await chromium.launch();
38
+ const page = await browser.newPage();
39
+ await page.goto(`file://${resolve(input)}`, { waitUntil: "networkidle" });
40
+ await page.pdf({
41
+ path: output,
42
+ width: "1280px",
43
+ height: "720px",
44
+ printBackground: true,
45
+ });
46
+ await browser.close();
47
+ console.log(`Done: ${output}`);
@@ -0,0 +1,139 @@
1
+ ---
2
+ name: deck-summarize
3
+ description: Synthesize PowerPoint decks into engineer-friendly markdown briefs covering Jobs-To-Be-Done, dependencies, and synthetic data needs. Use when the user asks to break down, summarize, or make sense of a slide deck (.pptx) for engineering work.
4
+ compatibility: Node.js only — no external dependencies.
5
+ ---
6
+
7
+ # Synthesize Deck
8
+
9
+ Turn messy PowerPoint specification decks into clear, actionable markdown briefs
10
+ that forward deployed engineers can build from. Strip business jargon and focus
11
+ on what matters: what needs to be built, what blocks progress, what data is
12
+ needed to start prototyping.
13
+
14
+ ## Trigger
15
+
16
+ The user asks to summarize, synthesize, or break down a `.pptx` deck; make sense
17
+ of a specification or proposal deck for engineering; create an engineering brief
18
+ from a slide deck; or understand what a project deck is actually asking for.
19
+
20
+ ## Prerequisites
21
+
22
+ - Node.js 18+.
23
+ - Input files must be `.pptx`.
24
+
25
+ ## Inputs
26
+
27
+ - One or more `.pptx` file paths.
28
+ - Optional: focus areas the engineer cares about.
29
+
30
+ ## Outputs
31
+
32
+ - One markdown file per deck (or one combined file for related decks) written to
33
+ `knowledge/Projects/{Project Name} - Engineering Brief.md`.
34
+
35
+ <do_confirm_checklist goal="Verify the brief is engineer-actionable before
36
+ delivering">
37
+
38
+ - [ ] No invented requirements — every claim traces to the deck.
39
+ - [ ] Plain language; no marketing jargon (no "synergize", "orchestrate",
40
+ "leverage", "intelligent \_\_\_ hub").
41
+ - [ ] JTBDs describe the user's goal, not the proposed solution; one job per
42
+ statement; each includes the "so that".
43
+ - [ ] Data dependencies table flags blockers (missing, locked, compliance).
44
+ - [ ] Synthetic data needs name fields, ranges, edge cases, and volume.
45
+ - [ ] Gaps and open questions list what an engineer would notice missing.
46
+ - [ ] Brief is under 2,000 lines — a summary, not a transcription.
47
+ - [ ] Knowledge base looked up for mentioned people, orgs, and projects.
48
+
49
+ </do_confirm_checklist>
50
+
51
+ Output template and the data dependencies table:
52
+ [references/brief-template.md](references/brief-template.md).
53
+
54
+ ## Procedure
55
+
56
+ ### 1. Extract text
57
+
58
+ ```bash
59
+ node .claude/skills/deck-summarize/scripts/extract-pptx.mjs "$FILE_PATH"
60
+ ```
61
+
62
+ For multiple decks, pass all files at once. To save the extracted text:
63
+
64
+ ```bash
65
+ node .claude/skills/deck-summarize/scripts/extract-pptx.mjs "$FILE_PATH" -o /tmp/deck_extract.txt
66
+ ```
67
+
68
+ Read all extracted text before continuing.
69
+
70
+ ### 2. Identify the core problem
71
+
72
+ Plain-language answers to: what process exists today; what's broken, slow, or
73
+ painful; who suffers. Don't restate the deck's framing.
74
+
75
+ ### 3. Extract Jobs-To-Be-Done
76
+
77
+ Format: `When [situation], I need to [action], so that [outcome].`
78
+
79
+ Group by user role/persona. One job per statement. Use the user's goal, not the
80
+ proposed solution. A job should still make sense if you discard the deck's
81
+ solution. Don't restate the deck's feature list as jobs and don't reuse its
82
+ jargon.
83
+
84
+ ### 4. Map dependencies
85
+
86
+ **4a. Data** — fill the table in
87
+ [references/brief-template.md](references/brief-template.md#data-dependencies-table).
88
+ Flag blockers (missing, locked, unstructured, compliance).
89
+
90
+ **4b. Systems & integrations** — every external system/API/platform: what the
91
+ integration does, read-only vs read-write, API vs manual/scraping, access
92
+ confirmed?
93
+
94
+ **4c. People & approvals** — approvals, reviews, or co-creation needed before
95
+ engineering can proceed. Flag long lead-time items (legal, compliance, vendor
96
+ contracts).
97
+
98
+ ### 5. Define synthetic-data needs
99
+
100
+ For each core feature/use case:
101
+
102
+ - **Generate:** entity, key fields and types, realistic value ranges and
103
+ distributions, edge cases that matter, volume for meaningful testing.
104
+ - **Simulate:** workflows and state transitions, time-series patterns,
105
+ multi-actor interactions, error/failure modes.
106
+ - **Format:** prefer CSV/JSON; PII-shaped fake data only — never real PII;
107
+ include happy-path _and_ adversarial examples; consider ML training/eval data.
108
+
109
+ ### 6. Translate the proposed solution
110
+
111
+ Describe the build in engineering terms: components, what each does in plain
112
+ terms, how they connect, end-to-end data flow, AI/ML capabilities and what
113
+ they're actually doing. Translate branded names — e.g. "Intelligent Intake Hub"
114
+ → "OCR + NLP pipeline that extracts structured fields from scanned enrollment
115
+ forms"; "Copay Guardian" → "Anomaly detection on weekly claims data".
116
+
117
+ ### 7. Identify what's missing
118
+
119
+ Call out: features without clear data sources; AI capabilities without a
120
+ training-data strategy; assumed integrations; user workflows that skip edge
121
+ cases; metrics promised without measurement infrastructure; timeline–scope
122
+ mismatches.
123
+
124
+ ### 8. Assemble the brief
125
+
126
+ Use the structure in
127
+ [references/brief-template.md](references/brief-template.md). Save to
128
+ `knowledge/Projects/{Project Name} - Engineering Brief.md`. For multiple related
129
+ decks, write one combined brief with shared dependencies.
130
+
131
+ ### 9. Save and report
132
+
133
+ Tell the user the file path and give a 3-sentence project summary.
134
+
135
+ ## Writing style
136
+
137
+ Plain language, concrete over abstract, honest about uncertainty, opinionated
138
+ when helpful (flag dependency or timeline risks), short sentences. Engineers
139
+ scan, they don't read essays.
@@ -0,0 +1,85 @@
1
+ # Engineering Brief Template
2
+
3
+ Reference template for `deck-summarize` Step 8. Save the assembled brief to
4
+ `knowledge/Projects/{Project Name} - Engineering Brief.md`.
5
+
6
+ ```markdown
7
+ ---
8
+ project: {Project Name}
9
+ source: {list of deck filenames}
10
+ date_synthesized: {today's date}
11
+ status: engineering-brief
12
+ ---
13
+
14
+ # {Project Name} — Engineering Brief
15
+
16
+ > One-paragraph plain-English summary of what this project is and why it
17
+ > exists.
18
+
19
+ ## The Problem
20
+
21
+ {Step 2 output — what's broken, who it affects, why it matters}
22
+
23
+ ## Jobs-To-Be-Done
24
+
25
+ ### {Persona 1}
26
+ - When [situation], I need to [action], so that [outcome].
27
+ - ...
28
+
29
+ ### {Persona 2}
30
+ - ...
31
+
32
+ ## What They Want to Build
33
+
34
+ {Step 6 output — solution in engineering terms, components, data flow}
35
+
36
+ ### System Architecture (Simplified)
37
+
38
+ {Text-based diagram or description of how components connect}
39
+
40
+ ### AI/ML Components
41
+
42
+ {What models/capabilities are needed, what they do, what data they need}
43
+
44
+ ## Dependencies
45
+
46
+ ### Data
47
+ {Step 4a table}
48
+
49
+ ### Systems & Integrations
50
+ {Step 4b list}
51
+
52
+ ### People & Approvals
53
+ {Step 4c list}
54
+
55
+ ## Synthetic Data for Prototyping
56
+
57
+ ### {Feature/Use Case 1}
58
+ {Step 5 output}
59
+
60
+ ### {Feature/Use Case 2}
61
+ {Step 5 output}
62
+
63
+ ## Gaps & Open Questions
64
+
65
+ {Step 7 output as a numbered list}
66
+
67
+ ## Phasing
68
+
69
+ {Timeline and wave structure from the deck, with engineering commentary on
70
+ what's realistic and what depends on what}
71
+
72
+ ## Key Metrics
73
+
74
+ {What success looks like, translated into measurable engineering terms}
75
+ ```
76
+
77
+ ## Data dependencies table
78
+
79
+ For Step 4a, fill in this table. Flag any data that doesn't seem to exist yet,
80
+ is locked behind inaccessible systems, is unstructured and would need heavy
81
+ preprocessing, or carries compliance/privacy constraints.
82
+
83
+ | Data | Where It Lives | Format | Access | Blocker? |
84
+ | ----------- | -------------------------- | ---------------------------------- | ------------------------------- | -------------- |
85
+ | _What data_ | _System/team that owns it_ | _Structured/unstructured/API/file_ | _Do we have it? Can we get it?_ | _Yes/No + why_ |
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Extract text from PowerPoint (.pptx) slides.
4
+ *
5
+ * PPTX files are ZIP archives containing XML. This script extracts all text
6
+ * from each slide and outputs it as structured markdown with slide headings.
7
+ * Handles multiple files and outputs to stdout or a file.
8
+ *
9
+ * Usage:
10
+ * node scripts/extract-pptx.mjs <path-to-pptx>
11
+ * node scripts/extract-pptx.mjs <path-to-pptx> -o /tmp/extract.txt
12
+ * node scripts/extract-pptx.mjs file1.pptx file2.pptx
13
+ * node scripts/extract-pptx.mjs -h|--help
14
+ *
15
+ * No external dependencies — uses Node.js built-in modules only.
16
+ */
17
+
18
+ import { readFileSync, writeFileSync } from "node:fs";
19
+ import { basename } from "node:path";
20
+
21
+ const HELP = `extract-pptx — extract slide text from .pptx files
22
+
23
+ Usage:
24
+ node scripts/extract-pptx.mjs <file.pptx> [file2.pptx ...]
25
+ node scripts/extract-pptx.mjs <file.pptx> -o <output.txt>
26
+ node scripts/extract-pptx.mjs -h|--help
27
+
28
+ Options:
29
+ -o <path> Write output to file instead of stdout
30
+ -h, --help Show this help
31
+
32
+ Output: Markdown-formatted text with ## Slide N headings per slide.
33
+ Multiple files get # Deck: filename.pptx headings.`;
34
+
35
+ if (
36
+ process.argv.includes("-h") ||
37
+ process.argv.includes("--help") ||
38
+ process.argv.length < 3
39
+ ) {
40
+ console.log(HELP);
41
+ process.exit(process.argv.length < 3 ? 1 : 0);
42
+ }
43
+
44
+ // --- Parse arguments ---
45
+
46
+ const args = process.argv.slice(2);
47
+ let outputPath = null;
48
+ const files = [];
49
+
50
+ for (let i = 0; i < args.length; i++) {
51
+ if (args[i] === "-o" && i + 1 < args.length) {
52
+ outputPath = args[++i];
53
+ } else if (!args[i].startsWith("-")) {
54
+ files.push(args[i]);
55
+ }
56
+ }
57
+
58
+ if (files.length === 0) {
59
+ console.error("Error: no .pptx files provided");
60
+ process.exit(1);
61
+ }
62
+
63
+ // --- ZIP parsing (no dependencies) ---
64
+
65
+ /**
66
+ * Parse a ZIP file's central directory to extract file entries.
67
+ * @param {Buffer} buf
68
+ * @returns {Array<{name: string, offset: number, compressedSize: number, compressionMethod: number}>}
69
+ */
70
+ function parseZipEntries(buf) {
71
+ // Find End of Central Directory record (signature 0x06054b50)
72
+ let eocdOffset = -1;
73
+ for (let i = buf.length - 22; i >= 0; i--) {
74
+ if (
75
+ buf[i] === 0x50 &&
76
+ buf[i + 1] === 0x4b &&
77
+ buf[i + 2] === 0x05 &&
78
+ buf[i + 3] === 0x06
79
+ ) {
80
+ eocdOffset = i;
81
+ break;
82
+ }
83
+ }
84
+ if (eocdOffset === -1) throw new Error("Not a valid ZIP file");
85
+
86
+ const cdOffset = buf.readUInt32LE(eocdOffset + 16);
87
+ const cdEntries = buf.readUInt16LE(eocdOffset + 10);
88
+
89
+ const entries = [];
90
+ let pos = cdOffset;
91
+
92
+ for (let e = 0; e < cdEntries; e++) {
93
+ // Central directory file header signature: 0x02014b50
94
+ if (buf.readUInt32LE(pos) !== 0x02014b50) break;
95
+
96
+ const compressionMethod = buf.readUInt16LE(pos + 10);
97
+ const compressedSize = buf.readUInt32LE(pos + 20);
98
+ const nameLen = buf.readUInt16LE(pos + 28);
99
+ const extraLen = buf.readUInt16LE(pos + 30);
100
+ const commentLen = buf.readUInt16LE(pos + 32);
101
+ const localHeaderOffset = buf.readUInt32LE(pos + 42);
102
+ const name = buf.toString("utf8", pos + 46, pos + 46 + nameLen);
103
+
104
+ entries.push({
105
+ name,
106
+ offset: localHeaderOffset,
107
+ compressedSize,
108
+ compressionMethod,
109
+ });
110
+ pos += 46 + nameLen + extraLen + commentLen;
111
+ }
112
+
113
+ return entries;
114
+ }
115
+
116
+ const { inflateRawSync } = await import("node:zlib");
117
+
118
+ /**
119
+ * Read the uncompressed content of a ZIP entry.
120
+ * @param {Buffer} buf
121
+ * @param {{offset: number, compressedSize: number, compressionMethod: number}} entry
122
+ * @returns {Buffer}
123
+ */
124
+ function readEntry(buf, entry) {
125
+ const pos = entry.offset;
126
+ const nameLen = buf.readUInt16LE(pos + 26);
127
+ const extraLen = buf.readUInt16LE(pos + 28);
128
+ const dataStart = pos + 30 + nameLen + extraLen;
129
+ const raw = buf.subarray(dataStart, dataStart + entry.compressedSize);
130
+
131
+ if (entry.compressionMethod === 0) return raw;
132
+ if (entry.compressionMethod === 8) return inflateRawSync(raw);
133
+ throw new Error(
134
+ `Unsupported compression method ${entry.compressionMethod} for ${entry.name}`,
135
+ );
136
+ }
137
+
138
+ /**
139
+ * Extract all text content from slide XML using the DrawingML namespace.
140
+ * Matches <a:t>text</a:t> elements used by PowerPoint.
141
+ * @param {string} xml
142
+ * @returns {string[]}
143
+ */
144
+ function extractTextFromXml(xml) {
145
+ const texts = [];
146
+ const re = /<a:t>([^<]*)<\/a:t>/g;
147
+ let match;
148
+ while ((match = re.exec(xml)) !== null) {
149
+ const text = match[1].trim();
150
+ if (text) texts.push(text);
151
+ }
152
+ return texts;
153
+ }
154
+
155
+ /**
156
+ * Extract slide text from a .pptx file.
157
+ * @param {string} filePath
158
+ * @returns {string} Markdown-formatted slide text
159
+ */
160
+ function extractPptx(filePath) {
161
+ const buf = readFileSync(filePath);
162
+ const entries = parseZipEntries(buf);
163
+
164
+ // Find slide XML files and sort by slide number
165
+ const slideEntries = entries
166
+ .filter(
167
+ (e) => e.name.startsWith("ppt/slides/slide") && e.name.endsWith(".xml"),
168
+ )
169
+ .sort((a, b) => {
170
+ const numA = parseInt(a.name.match(/(\d+)/)?.[1] || "0", 10);
171
+ const numB = parseInt(b.name.match(/(\d+)/)?.[1] || "0", 10);
172
+ return numA - numB;
173
+ });
174
+
175
+ const lines = [];
176
+
177
+ for (const entry of slideEntries) {
178
+ const xml = readEntry(buf, entry).toString("utf8");
179
+ const texts = extractTextFromXml(xml);
180
+
181
+ if (texts.length > 0) {
182
+ const num = entry.name.match(/(\d+)/)?.[1] || "?";
183
+ lines.push(`## Slide ${num}`);
184
+ lines.push(texts.join("\n"));
185
+ lines.push("");
186
+ }
187
+ }
188
+
189
+ return lines.join("\n");
190
+ }
191
+
192
+ // --- Main ---
193
+
194
+ const outputs = [];
195
+
196
+ for (const file of files) {
197
+ if (files.length > 1) {
198
+ outputs.push(`# Deck: ${basename(file)}\n`);
199
+ }
200
+ outputs.push(extractPptx(file));
201
+ }
202
+
203
+ const result = outputs.join("\n");
204
+
205
+ if (outputPath) {
206
+ writeFileSync(outputPath, result);
207
+ console.log(`Extracted ${files.length} deck(s) → ${outputPath}`);
208
+ } else {
209
+ process.stdout.write(result);
210
+ }