viepilot 2.4.0 → 2.15.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/CHANGELOG.md +206 -0
- package/README.md +16 -13
- package/docs/brainstorm/session-2026-04-11.md +194 -0
- package/docs/skills-reference.md +58 -0
- package/docs/user/features/interactive-prompts.md +83 -0
- package/docs/user/features/proposal.md +196 -0
- package/lib/google-slides-exporter.cjs +80 -0
- package/lib/proposal-generator.cjs +249 -0
- package/lib/screenshot-artifact.cjs +142 -0
- package/lib/viepilot-config.cjs +32 -1
- package/package.json +8 -1
- package/skills/vp-audit/SKILL.md +11 -0
- package/skills/vp-brainstorm/SKILL.md +21 -0
- package/skills/vp-crystallize/SKILL.md +64 -0
- package/skills/vp-docs/SKILL.md +16 -6
- package/skills/vp-proposal/SKILL.md +175 -0
- package/skills/vp-request/SKILL.md +22 -0
- package/templates/proposal/docx/project-detail.docx +0 -0
- package/templates/proposal/pptx/general.pptx +0 -0
- package/templates/proposal/pptx/product-pitch.pptx +0 -0
- package/templates/proposal/pptx/project-proposal-creative.pptx +0 -0
- package/templates/proposal/pptx/project-proposal-enterprise.pptx +0 -0
- package/templates/proposal/pptx/project-proposal-modern-tech.pptx +0 -0
- package/templates/proposal/pptx/project-proposal.pptx +0 -0
- package/templates/proposal/pptx/tech-architecture.pptx +0 -0
- package/workflows/brainstorm.md +26 -0
- package/workflows/crystallize.md +700 -1
- package/workflows/documentation.md +26 -11
- package/workflows/evolve.md +36 -0
- package/workflows/proposal.md +807 -0
- package/workflows/request.md +35 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Interactive Prompts (AskUserQuestion)
|
|
2
|
+
|
|
3
|
+
> **ENH-048** — Introduced in v2.15.0
|
|
4
|
+
|
|
5
|
+
ViePilot skills that ask user questions now use **adapter-aware interactive prompts**.
|
|
6
|
+
In Claude Code (terminal), questions appear as structured click-to-select UI.
|
|
7
|
+
All other adapters fall back to plain-text numbered lists automatically.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## How It Works
|
|
12
|
+
|
|
13
|
+
When a vp-* skill needs input (e.g. "What type of request?"), it checks whether
|
|
14
|
+
the `AskUserQuestion` tool is available in the current adapter:
|
|
15
|
+
|
|
16
|
+
| Adapter | Experience |
|
|
17
|
+
|---------|------------|
|
|
18
|
+
| **Claude Code (terminal)** | Click-to-select options with descriptions, optional multi-select, preview panels |
|
|
19
|
+
| **Claude Code (VS Code ext)** | Terminal mode — same as above (VS Code interactive UI pending [#12609](https://github.com/anthropics/claude-code/issues/12609)) |
|
|
20
|
+
| **Cursor — Plan Mode** | `AskQuestion` available in Plan Mode only |
|
|
21
|
+
| **Cursor — Agent/Skills** | Text fallback (AskQuestion not in Agent Mode) |
|
|
22
|
+
| **Codex CLI** | Text fallback (native tool N/A) |
|
|
23
|
+
| **Antigravity native** | Text fallback (Artifact model, no raw tool calls) |
|
|
24
|
+
|
|
25
|
+
No configuration needed. The fallback is automatic.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Affected Skills and Prompts
|
|
30
|
+
|
|
31
|
+
### /vp-crystallize
|
|
32
|
+
| Prompt | AUQ Options |
|
|
33
|
+
|--------|-------------|
|
|
34
|
+
| License selection | MIT / Apache-2.0 / GPL-3.0 / Proprietary |
|
|
35
|
+
| Brownfield overwrite confirm | Yes, continue / No, abort |
|
|
36
|
+
| Polyrepo related-repos | Yes, I'll list them / Skip for now |
|
|
37
|
+
| UI direction gate | Return to brainstorm / Continue with assumptions |
|
|
38
|
+
| Architect mode suggestion | Yes, architect mode / No, continue now |
|
|
39
|
+
|
|
40
|
+
### /vp-brainstorm
|
|
41
|
+
| Prompt | AUQ Options |
|
|
42
|
+
|--------|-------------|
|
|
43
|
+
| Session intent | Continue recent / Review specific / New session |
|
|
44
|
+
| Landing page layout | Layout A (Hero) / Layout B (Problem-Solution) / Layout C (Storytelling) / Layout D (SaaS) |
|
|
45
|
+
|
|
46
|
+
### /vp-request
|
|
47
|
+
| Prompt | AUQ Options |
|
|
48
|
+
|--------|-------------|
|
|
49
|
+
| Request type | Bug / Feature / Enhancement / Tech Debt |
|
|
50
|
+
| Bug severity | Critical / High / Medium / Low |
|
|
51
|
+
| Feature priority | Must-have / Should-have / Nice-to-have |
|
|
52
|
+
|
|
53
|
+
### /vp-evolve
|
|
54
|
+
| Prompt | AUQ Options |
|
|
55
|
+
|--------|-------------|
|
|
56
|
+
| Evolve intent | Add Feature / New Milestone / Refactor |
|
|
57
|
+
| Complexity | S (Small) / M (Medium) / L (Large) / XL (Extra Large) |
|
|
58
|
+
| Brainstorm routing | Yes, brainstorm first / No, plan directly |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## AskUserQuestion Tool Constraints
|
|
63
|
+
|
|
64
|
+
When using Claude Code, the tool has these constraints:
|
|
65
|
+
- **1–4 questions** per call
|
|
66
|
+
- **2–4 options** per question
|
|
67
|
+
- Optional `multiSelect: true` for non-exclusive choices
|
|
68
|
+
- Optional `preview` field for visual comparisons (code/layout mockups)
|
|
69
|
+
- Automatic "Other" option always appended by the UI
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Research Notes (Adapter Support)
|
|
74
|
+
|
|
75
|
+
Research conducted 2026-04-13 confirmed:
|
|
76
|
+
|
|
77
|
+
- **Cursor Agent/Skills Mode** does not expose `AskQuestion` — it only works in Plan Mode
|
|
78
|
+
([community request](https://forum.cursor.com/t/allow-askquestion-tool-calls-in-agent-mode-or-any-mode/152517))
|
|
79
|
+
- **Codex CLI** `ask_user_question` is a community MCP skill, not a native tool
|
|
80
|
+
([openai/codex#9926](https://github.com/openai/codex/issues/9926))
|
|
81
|
+
- **Antigravity** uses an "Artifacts" model for agent output — no raw tool execution for interactive UI
|
|
82
|
+
|
|
83
|
+
All non-Claude-Code adapters receive identical plain-text prompts as before ENH-048.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# vp-proposal — Proposal Package Generator
|
|
2
|
+
|
|
3
|
+
Generate professional proposal packages directly from your ViePilot brainstorm sessions.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
`/vp-proposal` converts a brainstorm session (or a direct brief) into three synchronized files:
|
|
10
|
+
|
|
11
|
+
| File | Format | Purpose |
|
|
12
|
+
|------|--------|---------|
|
|
13
|
+
| `{slug}-{date}.md` | Markdown | Source of truth, version-controlled |
|
|
14
|
+
| `{slug}-{date}.pptx` | PowerPoint | Presentation (direct delivery) |
|
|
15
|
+
| `{slug}-{date}.docx` | Word | Detailed supporting document |
|
|
16
|
+
| `{slug}-{date}-slides.txt` | Text | Google Slides URL (with `--slides` flag) |
|
|
17
|
+
|
|
18
|
+
All files are written to `docs/proposals/` in your project.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Auto-detects latest brainstorm session
|
|
26
|
+
/vp-proposal
|
|
27
|
+
|
|
28
|
+
# Specify type directly
|
|
29
|
+
/vp-proposal --type product-pitch
|
|
30
|
+
|
|
31
|
+
# With Google Slides upload
|
|
32
|
+
/vp-proposal --type project-proposal --slides
|
|
33
|
+
|
|
34
|
+
# Preview slide manifest without writing files
|
|
35
|
+
/vp-proposal --dry-run
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Proposal Types
|
|
41
|
+
|
|
42
|
+
| Type ID | Name | Slides | Best for |
|
|
43
|
+
|---------|------|--------|----------|
|
|
44
|
+
| `project-proposal` | Project Proposal | 10 | Client delivery — scope, timeline, budget |
|
|
45
|
+
| `tech-architecture` | Technical Architecture | 12 | Technical partners — system design |
|
|
46
|
+
| `product-pitch` | Product Pitch Deck | 12 | Investors / partners — pitch |
|
|
47
|
+
| `general` | General Proposal | 8 | Any audience — flexible structure |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Flags
|
|
52
|
+
|
|
53
|
+
| Flag | Description |
|
|
54
|
+
|------|-------------|
|
|
55
|
+
| `--type <id>` | Proposal type (see table above). If omitted: guided menu. |
|
|
56
|
+
| `--from <file>` | Explicit brainstorm session: `--from docs/brainstorm/session-2026-04-11.md` |
|
|
57
|
+
| `--slides` | Upload generated .pptx to Google Slides after generation |
|
|
58
|
+
| `--dry-run` | Show slide manifest (JSON) without writing any files |
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Context Detection
|
|
63
|
+
|
|
64
|
+
`/vp-proposal` automatically loads the most recent brainstorm session:
|
|
65
|
+
|
|
66
|
+
1. Scans `docs/brainstorm/session-*.md`
|
|
67
|
+
2. Sorts descending by filename (ISO date format)
|
|
68
|
+
3. Loads the latest session as proposal content
|
|
69
|
+
|
|
70
|
+
**Override:** use `--from <session-file>` to specify a different session.
|
|
71
|
+
|
|
72
|
+
**Standalone mode:** if no session exists, you'll be prompted for a brief:
|
|
73
|
+
- Project name
|
|
74
|
+
- One-line description
|
|
75
|
+
- Target audience
|
|
76
|
+
- 3–5 key points
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Template Override
|
|
81
|
+
|
|
82
|
+
ViePilot ships stock templates (dark navy/charcoal, ViePilot branded).
|
|
83
|
+
To use your own branded templates for a specific project:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
your-project/
|
|
87
|
+
└── .viepilot/
|
|
88
|
+
└── proposal-templates/
|
|
89
|
+
├── project-proposal.pptx ← overrides stock
|
|
90
|
+
├── tech-architecture.pptx
|
|
91
|
+
├── product-pitch.pptx
|
|
92
|
+
├── general.pptx
|
|
93
|
+
└── project-detail.docx
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Resolution order:**
|
|
97
|
+
1. `.viepilot/proposal-templates/{type}.pptx` (project-level override)
|
|
98
|
+
2. `{viepilot-install}/templates/proposal/pptx/{type}.pptx` (ViePilot stock)
|
|
99
|
+
|
|
100
|
+
The override only applies to the project where the file is placed.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Output Structure
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
docs/proposals/
|
|
108
|
+
├── my-project-2026-04-11.md # Markdown source of truth
|
|
109
|
+
├── my-project-2026-04-11.pptx # Presentation
|
|
110
|
+
├── my-project-2026-04-11.docx # Detailed document
|
|
111
|
+
└── my-project-2026-04-11-slides.txt # Google Slides URL (if --slides)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
The `.md` file is the diff-friendly source. The `.pptx` and `.docx` are generated from it and can be regenerated at any time.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Google Slides Export
|
|
119
|
+
|
|
120
|
+
Use the `--slides` flag to automatically upload the generated `.pptx` to Google Slides.
|
|
121
|
+
|
|
122
|
+
### Setup (one-time)
|
|
123
|
+
|
|
124
|
+
**Step 1: Create a Google Cloud project**
|
|
125
|
+
1. Go to [console.cloud.google.com](https://console.cloud.google.com)
|
|
126
|
+
2. Create a new project (or select an existing one)
|
|
127
|
+
|
|
128
|
+
**Step 2: Enable APIs**
|
|
129
|
+
1. Navigate to **APIs & Services → Library**
|
|
130
|
+
2. Enable **Google Drive API**
|
|
131
|
+
3. Enable **Google Slides API**
|
|
132
|
+
|
|
133
|
+
**Step 3: Create a Service Account**
|
|
134
|
+
1. Go to **APIs & Services → Credentials**
|
|
135
|
+
2. Click **Create Credentials → Service Account**
|
|
136
|
+
3. Fill in name (e.g., `viepilot-proposal`)
|
|
137
|
+
4. Click **Done**
|
|
138
|
+
5. Click the new service account → **Keys** tab → **Add Key → Create new key (JSON)**
|
|
139
|
+
6. Save the downloaded JSON file securely (e.g., `~/.config/viepilot-gcp-key.json`)
|
|
140
|
+
|
|
141
|
+
**Step 4: Set environment variable**
|
|
142
|
+
```bash
|
|
143
|
+
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/your-service-account-key.json
|
|
144
|
+
|
|
145
|
+
# Add to ~/.zshrc or ~/.bashrc to persist:
|
|
146
|
+
echo 'export GOOGLE_APPLICATION_CREDENTIALS=~/.config/viepilot-gcp-key.json' >> ~/.zshrc
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Step 5: Install the optional dependency**
|
|
150
|
+
```bash
|
|
151
|
+
npm install @googleapis/slides
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Usage
|
|
155
|
+
```bash
|
|
156
|
+
/vp-proposal --type product-pitch --slides
|
|
157
|
+
# Output: docs/proposals/my-project-2026-04-11-slides.txt
|
|
158
|
+
# Content: https://docs.google.com/presentation/d/{id}/edit
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Notes
|
|
162
|
+
- The uploaded presentation retains the `.pptx` layout (converted by Google Drive)
|
|
163
|
+
- Upload failures are non-fatal — the `.pptx`, `.docx`, and `.md` files are always written first
|
|
164
|
+
- The service account needs no special permissions beyond the API scopes
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Examples
|
|
169
|
+
|
|
170
|
+
### Client proposal from brainstorm session
|
|
171
|
+
```
|
|
172
|
+
/vp-proposal --type project-proposal
|
|
173
|
+
→ Auto-loads docs/brainstorm/session-2026-04-11.md
|
|
174
|
+
→ Generates 10-slide project proposal
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Investor pitch standalone
|
|
178
|
+
```
|
|
179
|
+
/vp-proposal --type product-pitch
|
|
180
|
+
→ No session found — prompts for brief
|
|
181
|
+
→ Generates 12-slide pitch deck
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Architecture review with Google Slides
|
|
185
|
+
```
|
|
186
|
+
/vp-proposal --type tech-architecture --slides
|
|
187
|
+
→ Generates 12-slide architecture proposal + uploads to Google Slides
|
|
188
|
+
→ Writes URL to docs/proposals/*-slides.txt
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Preview without writing files
|
|
192
|
+
```
|
|
193
|
+
/vp-proposal --dry-run
|
|
194
|
+
→ Prints JSON slide manifest to console
|
|
195
|
+
→ No files written
|
|
196
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* google-slides-exporter.cjs
|
|
4
|
+
* Uploads a .pptx file to Google Drive and converts it to a Google Slides presentation.
|
|
5
|
+
*
|
|
6
|
+
* Requirements (optional — only needed when --slides flag is used):
|
|
7
|
+
* npm install @googleapis/slides
|
|
8
|
+
* export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json
|
|
9
|
+
*
|
|
10
|
+
* See docs/user/features/proposal.md → "Google Slides Export" for setup guide.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Upload a local .pptx file to Google Drive and convert to Google Slides.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} pptxPath Absolute path to the .pptx file
|
|
20
|
+
* @param {string} title Title for the Google Slides presentation
|
|
21
|
+
* @returns {Promise<string>} Public edit URL of the created presentation
|
|
22
|
+
*/
|
|
23
|
+
async function uploadToSlides(pptxPath, title) {
|
|
24
|
+
// Lazy-load @googleapis/slides — it is an optionalDependency.
|
|
25
|
+
// Provide a clear, actionable error when the package is not installed.
|
|
26
|
+
let googleApis;
|
|
27
|
+
try {
|
|
28
|
+
googleApis = require('googleapis');
|
|
29
|
+
} catch {
|
|
30
|
+
throw new Error(
|
|
31
|
+
'Google Slides export requires the @googleapis/slides package.\n\n' +
|
|
32
|
+
'Install it:\n' +
|
|
33
|
+
' npm install @googleapis/slides\n\n' +
|
|
34
|
+
'Then set your service account credentials:\n' +
|
|
35
|
+
' export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json\n\n' +
|
|
36
|
+
'See: docs/user/features/proposal.md → "Google Slides Export"'
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const { google } = googleApis;
|
|
41
|
+
|
|
42
|
+
// Verify credentials env var is set before attempting auth
|
|
43
|
+
if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
'GOOGLE_APPLICATION_CREDENTIALS environment variable is not set.\n\n' +
|
|
46
|
+
'Set it to the path of your Google service account JSON key:\n' +
|
|
47
|
+
' export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json\n\n' +
|
|
48
|
+
'See: docs/user/features/proposal.md → "Google Slides Export"'
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Authenticate via service account (no browser interaction required)
|
|
53
|
+
const auth = new google.auth.GoogleAuth({
|
|
54
|
+
scopes: [
|
|
55
|
+
'https://www.googleapis.com/auth/drive',
|
|
56
|
+
'https://www.googleapis.com/auth/presentations',
|
|
57
|
+
],
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const authClient = await auth.getClient();
|
|
61
|
+
const drive = google.drive({ version: 'v3', auth: authClient });
|
|
62
|
+
|
|
63
|
+
// Upload .pptx and request conversion to Google Slides format
|
|
64
|
+
const { data: file } = await drive.files.create({
|
|
65
|
+
requestBody: {
|
|
66
|
+
name: title,
|
|
67
|
+
mimeType: 'application/vnd.google-apps.presentation',
|
|
68
|
+
},
|
|
69
|
+
media: {
|
|
70
|
+
mimeType:
|
|
71
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
72
|
+
body: fs.createReadStream(pptxPath),
|
|
73
|
+
},
|
|
74
|
+
fields: 'id',
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return `https://docs.google.com/presentation/d/${file.id}/edit`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { uploadToSlides };
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
// Proposal type definitions
|
|
8
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
+
const PROPOSAL_TYPES = {
|
|
10
|
+
'project-proposal': { slides: 10, label: 'Project Proposal' },
|
|
11
|
+
'tech-architecture': { slides: 12, label: 'Technical Architecture' },
|
|
12
|
+
'product-pitch': { slides: 12, label: 'Product Pitch Deck' },
|
|
13
|
+
'general': { slides: 8, label: 'General Proposal' },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
// Template resolution — 2-tier
|
|
18
|
+
// Tier 1: {projectRoot}/.viepilot/proposal-templates/{type}.{ext} (project override)
|
|
19
|
+
// Tier 2: {packageRoot}/templates/proposal/{ext}/{type}.{ext} (stock)
|
|
20
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
function resolveTemplate(type, ext, projectRoot) {
|
|
22
|
+
const override = path.join(projectRoot, '.viepilot', 'proposal-templates', `${type}.${ext}`);
|
|
23
|
+
if (fs.existsSync(override)) return override;
|
|
24
|
+
// Stock fallback — lives next to this file at ../templates/proposal/{ext}/
|
|
25
|
+
return path.join(__dirname, '..', 'templates', 'proposal', ext, `${type}.${ext}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
29
|
+
// Context detection — auto-load latest brainstorm session
|
|
30
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
31
|
+
function detectBrainstormSession(projectRoot) {
|
|
32
|
+
const dir = path.join(projectRoot, 'docs', 'brainstorm');
|
|
33
|
+
if (!fs.existsSync(dir)) return null;
|
|
34
|
+
const files = fs.readdirSync(dir)
|
|
35
|
+
.filter(f => f.startsWith('session-') && f.endsWith('.md'))
|
|
36
|
+
.sort()
|
|
37
|
+
.reverse();
|
|
38
|
+
return files.length ? path.join(dir, files[0]) : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
42
|
+
// Proposal type validation
|
|
43
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
44
|
+
function validateType(type) {
|
|
45
|
+
if (!PROPOSAL_TYPES[type]) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Unknown proposal type "${type}". Valid types: ${Object.keys(PROPOSAL_TYPES).join(', ')}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return PROPOSAL_TYPES[type];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
54
|
+
// Manifest meta schema (ENH-040)
|
|
55
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
56
|
+
/**
|
|
57
|
+
* Manifest meta object — collected via Step 2C quality brief.
|
|
58
|
+
*
|
|
59
|
+
* @typedef {Object} ProposalMeta
|
|
60
|
+
* @property {string} cta - Decision/action the proposal should drive
|
|
61
|
+
* @property {string} [budget] - Approximate budget range (optional)
|
|
62
|
+
* @property {string} [timeline] - Key deadline or constraint (optional)
|
|
63
|
+
* @property {string} decisionMaker - Who is the primary audience/decision-maker
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
67
|
+
// Output path builder
|
|
68
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
69
|
+
function buildOutputPaths(slug, projectRoot) {
|
|
70
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
71
|
+
const base = path.join(projectRoot, 'docs', 'proposals', `${slug}-${date}`);
|
|
72
|
+
return {
|
|
73
|
+
md: `${base}.md`,
|
|
74
|
+
pptx: `${base}.pptx`,
|
|
75
|
+
docx: `${base}.docx`,
|
|
76
|
+
slides: `${base}-slides.txt`,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
81
|
+
// Language instruction builder (ENH-039)
|
|
82
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
const LANG_NAMES = {
|
|
85
|
+
vi: 'Vietnamese', ja: 'Japanese', fr: 'French', zh: 'Chinese',
|
|
86
|
+
ko: 'Korean', de: 'German', es: 'Spanish', pt: 'Portuguese',
|
|
87
|
+
it: 'Italian', th: 'Thai', ar: 'Arabic', hi: 'Hindi',
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Build a language instruction string to prepend to the AI content generation prompt.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} lang - ISO 639-1 code (e.g. 'vi', 'en', 'ja')
|
|
94
|
+
* @param {boolean} [contentOnly=false] - if true, translate content only; keep structural labels English
|
|
95
|
+
* @returns {string} Instruction to inject into AI prompt (empty string for English)
|
|
96
|
+
*/
|
|
97
|
+
function buildLangInstruction(lang, contentOnly = false) {
|
|
98
|
+
if (!lang || lang === 'en') {
|
|
99
|
+
return ''; // English is default — no explicit instruction needed
|
|
100
|
+
}
|
|
101
|
+
const langName = LANG_NAMES[lang] || lang.toUpperCase();
|
|
102
|
+
|
|
103
|
+
if (contentOnly) {
|
|
104
|
+
return (
|
|
105
|
+
`LANGUAGE INSTRUCTION: Generate all slide content (bullet points, body text, ` +
|
|
106
|
+
`speaker notes, and paragraph content) in ${langName}. ` +
|
|
107
|
+
`Keep structural labels, section names, and template placeholders in English.`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return (
|
|
111
|
+
`LANGUAGE INSTRUCTION: Generate ALL content — slide headings, bullet points, ` +
|
|
112
|
+
`body text, speaker notes, document section titles, and paragraph content — in ${langName}. ` +
|
|
113
|
+
`Do not mix languages.`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** @type {Record<string, string[]>} */
|
|
118
|
+
const DIAGRAM_TYPES_BY_PROPOSAL = {
|
|
119
|
+
'project-proposal': ['flowchart', 'gantt'],
|
|
120
|
+
'tech-architecture': ['flowchart', 'sequenceDiagram', 'classDiagram'],
|
|
121
|
+
'product-pitch': ['flowchart', 'sequenceDiagram'],
|
|
122
|
+
'general': ['flowchart'],
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Returns the list of Mermaid diagram types to generate for a given proposal type.
|
|
127
|
+
* @param {string} typeId - Proposal type ID (e.g. 'project-proposal')
|
|
128
|
+
* @returns {string[]} Array of Mermaid diagram type identifiers
|
|
129
|
+
*/
|
|
130
|
+
function getDiagramTypes(typeId) {
|
|
131
|
+
return DIAGRAM_TYPES_BY_PROPOSAL[typeId] || ['flowchart'];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Known architect workspace page filenames (from vp-brainstorm --architect) */
|
|
135
|
+
const ARCHITECT_PAGES = [
|
|
136
|
+
'architecture.html',
|
|
137
|
+
'erd.html',
|
|
138
|
+
'sequence-diagram.html',
|
|
139
|
+
'feature-map.html',
|
|
140
|
+
'user-use-cases.html',
|
|
141
|
+
'deployment.html',
|
|
142
|
+
'apis.html',
|
|
143
|
+
'tech-notes.html',
|
|
144
|
+
'data-flow.html',
|
|
145
|
+
'decisions.html',
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Detect available HTML visual artifacts for screenshot embedding in PPTX slides.
|
|
150
|
+
* Scans the ui-direction session directory for index.html, pages/*.html, and
|
|
151
|
+
* architect workspace pages (architecture.html, erd.html, etc.).
|
|
152
|
+
*
|
|
153
|
+
* @param {string} [sessionDir] - Absolute path to a .viepilot/ui-direction/{session}/ dir.
|
|
154
|
+
* If omitted, auto-detects the latest session by scanning CWD/.viepilot/ui-direction/.
|
|
155
|
+
* @returns {{ uiPages: string[], architectPages: string[], sessionDir: string|null }}
|
|
156
|
+
*/
|
|
157
|
+
function detectVisualArtifacts(sessionDir) {
|
|
158
|
+
const fs = require('fs');
|
|
159
|
+
const path = require('path');
|
|
160
|
+
|
|
161
|
+
// Auto-detect latest session when no dir provided
|
|
162
|
+
let resolvedDir = sessionDir || null;
|
|
163
|
+
if (!resolvedDir) {
|
|
164
|
+
const uiBase = path.join(process.cwd(), '.viepilot', 'ui-direction');
|
|
165
|
+
if (fs.existsSync(uiBase)) {
|
|
166
|
+
const entries = fs.readdirSync(uiBase)
|
|
167
|
+
.filter(e => {
|
|
168
|
+
try { return fs.statSync(path.join(uiBase, e)).isDirectory(); } catch { return false; }
|
|
169
|
+
})
|
|
170
|
+
.sort()
|
|
171
|
+
.reverse(); // ISO date dirs: latest first
|
|
172
|
+
if (entries.length > 0) resolvedDir = path.join(uiBase, entries[0]);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const result = { uiPages: [], architectPages: [], sessionDir: resolvedDir };
|
|
177
|
+
if (!resolvedDir || !fs.existsSync(resolvedDir)) return result;
|
|
178
|
+
|
|
179
|
+
// ui-direction index page
|
|
180
|
+
const indexHtml = path.join(resolvedDir, 'index.html');
|
|
181
|
+
if (fs.existsSync(indexHtml)) result.uiPages.push(indexHtml);
|
|
182
|
+
|
|
183
|
+
// ui-direction sub-pages
|
|
184
|
+
const pagesDir = path.join(resolvedDir, 'pages');
|
|
185
|
+
if (fs.existsSync(pagesDir)) {
|
|
186
|
+
const pageFiles = fs.readdirSync(pagesDir)
|
|
187
|
+
.filter(f => f.endsWith('.html'))
|
|
188
|
+
.sort()
|
|
189
|
+
.map(f => path.join(pagesDir, f));
|
|
190
|
+
result.uiPages.push(...pageFiles);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Architect workspace pages (in same session dir)
|
|
194
|
+
for (const page of ARCHITECT_PAGES) {
|
|
195
|
+
const p = path.join(resolvedDir, page);
|
|
196
|
+
if (fs.existsSync(p)) result.architectPages.push(p);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
203
|
+
// Design configs — ENH-045
|
|
204
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
205
|
+
const DESIGN_CONFIGS = {
|
|
206
|
+
'modern-tech': {
|
|
207
|
+
colorPalette: 'navy-electric',
|
|
208
|
+
layoutStyle: 'modern-tech',
|
|
209
|
+
fontPair: 'Calibri / Calibri Light',
|
|
210
|
+
},
|
|
211
|
+
'enterprise': {
|
|
212
|
+
colorPalette: 'navy-gold',
|
|
213
|
+
layoutStyle: 'enterprise',
|
|
214
|
+
fontPair: 'Georgia / Calibri',
|
|
215
|
+
},
|
|
216
|
+
'creative': {
|
|
217
|
+
colorPalette: 'dark-vibrant',
|
|
218
|
+
layoutStyle: 'creative',
|
|
219
|
+
fontPair: 'Calibri / Calibri',
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Choose design config based on project context signals (ENH-045).
|
|
225
|
+
* @param {Object} ctx - { sector?, audience?, tone?, proposalType? }
|
|
226
|
+
* @returns {{ colorPalette: string, layoutStyle: string, fontPair: string }}
|
|
227
|
+
*/
|
|
228
|
+
function getDesignConfig(ctx = {}) {
|
|
229
|
+
const { sector = '', audience = '', tone = '' } = ctx;
|
|
230
|
+
const lower = `${sector} ${audience} ${tone}`.toLowerCase();
|
|
231
|
+
if (/bank|financ|enterprise|corp|legal|compliance|government/.test(lower))
|
|
232
|
+
return DESIGN_CONFIGS['enterprise'];
|
|
233
|
+
if (/startup|creative|design|game|media|agency|art/.test(lower))
|
|
234
|
+
return DESIGN_CONFIGS['creative'];
|
|
235
|
+
return DESIGN_CONFIGS['modern-tech'];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
module.exports = {
|
|
239
|
+
PROPOSAL_TYPES,
|
|
240
|
+
resolveTemplate,
|
|
241
|
+
detectBrainstormSession,
|
|
242
|
+
validateType,
|
|
243
|
+
buildOutputPaths,
|
|
244
|
+
buildLangInstruction,
|
|
245
|
+
getDiagramTypes,
|
|
246
|
+
detectVisualArtifacts,
|
|
247
|
+
DESIGN_CONFIGS,
|
|
248
|
+
getDesignConfig,
|
|
249
|
+
};
|