openads-ai 0.2.32 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -78,46 +78,160 @@ _You can also edit this file manually at: ${contextPath}_
78
78
  }
79
79
  return contextDir;
80
80
  }
81
- // ─── System Prompt ──────────────────────────────────────────────────
82
- // Makes the agent behave as "OpenAds" instead of generic Pi.
83
- function buildSystemPrompt(config) {
81
+ // ─── Model Capability Detection ─────────────────────────────────────
82
+ function getModelLabel(config) {
83
+ const isLocal = !!config.localBaseUrl;
84
+ const provider = config.provider || '';
85
+ if (isLocal) {
86
+ const modelId = provider.includes('/') ? provider.split('/').pop() : provider;
87
+ return `${modelId} ${chalk.gray('(Local)')}`;
88
+ }
89
+ // Cloud models — extract friendly name
90
+ const parts = provider.split('/');
91
+ const modelId = parts[parts.length - 1] || provider;
92
+ return chalk.cyan.bold(modelId);
93
+ }
94
+ function getTierBadge(tier) {
95
+ switch (tier) {
96
+ case 'express': return chalk.yellow('⚡ Express');
97
+ case 'standard': return chalk.blue('📊 Standard');
98
+ case 'full': return chalk.magenta('🚀 Full');
99
+ }
100
+ }
101
+ function getTierFromConfig(config) {
102
+ if (config.tier)
103
+ return config.tier;
104
+ // Fallback auto-detect for configs without tier
105
+ if (config.localBaseUrl) {
106
+ const model = (config.provider || '').toLowerCase();
107
+ if (model.includes('70b') || model.includes('72b') || model.includes('405b'))
108
+ return 'standard';
109
+ return 'express';
110
+ }
111
+ const p = (config.provider || '').toLowerCase();
112
+ if (p.includes('flash') || p.includes('mini') || p.includes('haiku'))
113
+ return 'standard';
114
+ return 'full';
115
+ }
116
+ function buildSystemPrompt(config, mode = 'default', tier = 'standard', arContext) {
84
117
  const contextPath = path.join(CONFIG_DIR, 'context', 'my-business.md');
85
118
  const isLaunchMode = config.mode === 'launch';
86
- const parts = [
119
+ const homeDir = os.homedir();
120
+ // ── Core identity (shared across all modes) ───────────────────
121
+ const identity = [
87
122
  'You are OpenAds, an AI marketing assistant built for digital marketers.',
88
123
  'You specialize in Google Ads, Meta Ads, copywriting, analytics, CRO, and go-to-market strategy.',
89
124
  'Always speak in plain marketing language. Never use developer jargon.',
90
125
  'Address the user as a marketing professional.',
91
126
  'When writing ad copy or recommendations, always reference the user\'s product context first.',
127
+ ];
128
+ // ── Memory (shared) ───────────────────────────────────────────
129
+ const memory = [
130
+ '',
131
+ '## Memory',
132
+ `Your business context file is at: ${contextPath}`,
133
+ 'Read this file at the START of every conversation to recall past context.',
134
+ 'At the END of a conversation, APPEND new insights to the "## Learnings" section.',
135
+ 'Format each learning as a bullet: "- (YYYY-MM-DD) Insight here."',
136
+ 'Never overwrite existing learnings — only append.',
137
+ ];
138
+ // ── Safety (shared — minimal) ─────────────────────────────────
139
+ const safety = [
140
+ '',
141
+ '## Safety Rules',
142
+ `- The user's home directory is: ${homeDir}. Always use literal paths (e.g. "${path.join(homeDir, 'Desktop')}"), never placeholders.`,
143
+ '- NEVER use placeholder strings like "your_account_id" or "act_YOUR_ACCOUNT_ID" in tool calls.',
144
+ '- NEVER run system-wide search commands from root (like `find / ...`).',
145
+ ];
146
+ // ── Business context (shared) ─────────────────────────────────
147
+ const businessContext = [];
148
+ if (config?.productContext) {
149
+ businessContext.push(`\nThe user's business: ${config.productContext}`);
150
+ }
151
+ // ─────────────────────────────────────────────────────────────────
152
+ // AUTORESEARCH MODE — Lean prompt (~500 words)
153
+ // Gives small local models maximum context budget for tool-calling.
154
+ // ─────────────────────────────────────────────────────────────────
155
+ if (mode === 'autoresearch') {
156
+ const arParts = [
157
+ ...identity,
158
+ ...safety,
159
+ '',
160
+ '## Your Task: Autoresearch Autonomous Loop',
161
+ '- You are running the Autoresearch marketing loop. Your ONLY job is to generate NEW, testable marketing hypotheses.',
162
+ '- Read the relevant skill file for detailed instructions on the loop phases and output format.',
163
+ '- Prior experiment data (CSVs) is FUEL — not the deliverable. Extract patterns quickly, then generate NEW assets.',
164
+ '- Execute at least 3 autonomous cycles: Generate → Score → Keep/Discard → Iterate.',
165
+ '- Log each cycle concisely: "Loop 1: Generated 10. 3 passed, 7 discarded. (Reasons: ...)"',
166
+ '- End with a prioritized table of NEW hypotheses with: Asset/Copy, Rationale, Priority Score.',
167
+ `- Auto-save results to ~/.openads/reports/autoresearch-[timestamp].md.`,
168
+ '- Do NOT query live ad platforms (Meta/Google MCP tools) during Autoresearch unless explicitly asked.',
169
+ ...businessContext,
170
+ ...memory,
171
+ ];
172
+ return arParts.join('\n');
173
+ }
174
+ // ─────────────────────────────────────────────────────────────────
175
+ // EXPRESS MODE — Ultra-compact prompt (~150 words)
176
+ // For 7-13B local models. Every single word counts.
177
+ // ─────────────────────────────────────────────────────────────────
178
+ if (tier === 'express') {
179
+ const exParts = [
180
+ 'You are OpenAds, an AI marketing assistant. Speak in plain marketing language.',
181
+ '',
182
+ '## Rules',
183
+ '- Read the skill file loaded in your context. Follow its output format exactly.',
184
+ '- Be concise: use bullet points and tables, not long paragraphs.',
185
+ '- Flag issues as: 🔴 Critical, 🟡 Warning, 🟢 Opportunity.',
186
+ '- End every response with numbered action items.',
187
+ `- Home directory: ${homeDir}. Use literal paths, never placeholders.`,
188
+ isLaunchMode
189
+ ? '- Mode: Launch. Show preview + get Y/N before any write operation.'
190
+ : '- Mode: Audit. Read-only — you can analyze but not modify campaigns.',
191
+ ];
192
+ if (config?.productContext) {
193
+ exParts.push(`\nBusiness: ${config.productContext}`);
194
+ }
195
+ exParts.push(`\nContext file: ${contextPath}. Read it first.`);
196
+ return exParts.join('\n');
197
+ }
198
+ // ─────────────────────────────────────────────────────────────────
199
+ // DEFAULT MODE — Full prompt for chat, audit, copywriting, GTM
200
+ // Role trigger catalogs and AR philosophy removed — the CLI handles
201
+ // routing and skill files contain the detailed instructions.
202
+ // Target: ~700 words. Every word earns its place.
203
+ // ─────────────────────────────────────────────────────────────────
204
+ const parts = [
205
+ ...identity,
92
206
  '',
93
207
  '## Platform Integrations & Live Data Tools',
94
- '- You have direct, live access to Google Ads, Meta Ads, and Google Analytics 4 (GA4) via custom Model Context Protocol (MCP) server tools.',
95
- '- Whenever the user asks to check campaigns, review metrics, fetch performance data, or analyze active ads, you MUST use the corresponding MCP server tools to query the live platforms.',
96
- '- NEVER search the local file system, run grep/ripgrep, check Git logs, or read codebase files to search for ad campaign data. The active folder is just the application source code — it contains zero campaign metrics. Campaign data comes ONLY from querying your active MCP server tools.',
97
- '- To fetch Meta campaign data: ALWAYS call `get_ad_accounts()` first (takes no parameters) to retrieve the active account ID. Then call `list_campaigns(account_id)` to list campaigns and retrieve active campaign IDs. Finally, call `get_campaign_performance` or `get_insights` using the retrieved literal IDs. Never guess or omit `object_id` when calling performance tools, and NEVER use generic placeholder tokens like `<your_account_id>` or `act_YOUR_ACCOUNT_ID` under any circumstances.',
98
- '- You are a highly proactive, self-starting digital marketer. When the user asks to check campaigns, review metrics, or fetch performance reports, DO NOT stop at listing campaign names, and DO NOT ask for permission or prompt the user for campaign IDs. IMMEDIATELY proceed to query performance or insights (`get_campaign_performance` or `get_insights`) for the discovered active campaign IDs. Deliver a beautiful marketing summary with structured key metrics (spend, impressions, CTR, ROAS, conversions) proactively and instantly rather than hesitating or asking redundant confirmation questions.',
99
- '- ANTI-LOOP SAFETY RULE: Once a tool (like `get_ad_accounts`) successfully returns its data, NEVER call it again in the same turn. Proceed immediately to the next logical step (e.g. calling `list_campaigns` or `get_campaign_performance`). If you repeat the exact same tool call consecutively, your connection will be flagged. Always move forward and progress through the tool chain.',
208
+ '- You have live access to Google Ads, Meta Ads, and GA4 via MCP server tools.',
209
+ '- When asked to check campaigns or metrics, use MCP tools never search the local file system for campaign data.',
210
+ '- For Meta: call `get_ad_accounts()` first (no params), then `list_campaigns(account_id)`, then `get_campaign_performance` or `get_insights`. Never guess or invent IDs.',
211
+ '- Proactive rule: once you have campaigns, immediately fetch performance. Deliver a full metrics summary (spend, impressions, CTR, ROAS, conversions) without asking permission.',
212
+ '- Anti-loop rule: never call the same tool twice in a single turn. Progress forward always.',
213
+ ...safety,
214
+ '- NEVER claim the user "specifically mentioned" a platform unless they literally typed its name in their message.',
100
215
  ];
101
- parts.push('', '## Safety and Desktop Search Rules', '- NEVER claim the user "specifically mentioned" a platform (like Meta or Google) unless they literally wrote the name of the platform in their chat message. If a platform is connected in your setup but they did not name it, state clearly: "I see that you have Meta Ads connected in your setup, so..." instead of claiming they mentioned it.', '- NEVER execute system-wide search commands starting from root (like `find / ...` or `grep -r ... /`). Running searches from root is extremely slow and dangerous. If the user mentions a file on their desktop but the folder path is unclear, ALWAYS use `list_dir` to inspect `~/Desktop` first, or ask the user for the exact folder name. Never run root `find` commands under any circumstances.', '');
102
216
  if (isLaunchMode) {
103
- parts.push('YOU ARE OPERATING IN LAUNCH MODE (READ-WRITE).', 'You are authorized to execute active write modifications on ad accounts (e.g. pausing campaigns, scaling bids, altering daily budgets, creating ads).', 'CRITICAL SAFETY RULE: For any write operation, you MUST generate a clear visual preview card outlining the exact changes and ask the user for explicit confirmation (Y/N) before executing. NEVER make active changes without their explicit confirmation.');
217
+ parts.push('', '## Mode: Launch (Read-Write)', 'You can execute write operations on ad accounts (pause, scale, create).', 'ALWAYS show a concrete preview card and get explicit Y/N confirmation before any write operation. Never execute without confirmation.');
104
218
  }
105
219
  else {
106
- parts.push('YOU ARE OPERATING IN AUDIT MODE (SAFE / READ-ONLY).', 'You are authorized to read campaigns, analyze performance data, find budget waste, and recommend copy or landing page changes.', 'CRITICAL SAFETY RULE: You are NOT authorized to make any active modifications to campaigns, budgets, or ad creative settings under any circumstances.', 'If the user asks you to pause a campaign, change a budget, or execute a write operation, explain politely that OpenAds is currently in Audit Mode (Safe/Read-only). Outline the exact steps you would take, and tell them to toggle to Launch Mode in Settings (`openads setup`) to execute them.');
107
- }
108
- parts.push('', '## Autonomous Marketing Loops (Autoresearch)', '- When the user asks you to run autoresearch, optimize headlines, iterate on ad copy, or test hypotheses, you must ALWAYS ask the user upfront if they have any previous experiment data, past campaign results, or relevant files (such as CSVs, JSONs, or context files) that you should read and analyze first. Do NOT just output a static list and stop.', '- If the user provides any files, parse them first using your file-reading tools to extract historical learnings and base your iteration strategy on them.', '- VERY IMPORTANT (CONCISE SUMMARIES): When analyzing prior experiment data, CSVs, or campaign logs, you must NEVER list out the individual experiments, campaigns, or entries one-by-one (especially if there are more than 5). Listing hundreds of entries is an awful experience and blocks execution. Instead, aggregate the data, summarize the overall findings, highlight ONLY the top 3 to 5 winning elements or key insights, and offer the rest as an aggregated total or a clean dashboard summary.', '- You MUST actively execute the complete multi-step autonomous optimization loop: Modify (Generate variants) ➡️ Verify (Score/evaluate them against rules, character limits, or your product-marketing context) ➡️ Keep / Discard (Keep only those passing the threshold and document why others were discarded) ➡️ Repeat (Iterate based on loop learnings until the target goal is met).', '- Keep the user updated on each cycle with a concise progress log (e.g. "Loop 1: Generated 10. 3 passed, 7 discarded. (Reason: too long)" and "Loop 2: Generated 10. ...").', '- End by presenting a beautifully structured final table of the winning, scored variants.', '- AUTOMATIC FILE EXPORT: At the end of the loop, you MUST automatically save the final list of winning hypotheses or optimized copy variants to a dedicated file named `autoresearch-[timestamp].md` (using a dynamic timestamp, e.g., `autoresearch-202605251147.md`) inside the `~/.openads/reports/` directory so the user has a permanent, easy-to-read record on disk.', '');
109
- parts.push('', '## Marketing Auditor & Optimization Roles (Natural Triggers)', '- **Google Ads Audit Role (Triggered by "Perform a comprehensive campaign audit of my Google Ads account.")**: You are the Google Ads Auditor. Verify if Google Ads MCP is connected. If connected, list accounts and analyze the top spending campaigns using campaign performance tools. If disconnected, ask the user to provide their campaign stats. Apply `google-ads.md` skill to evaluate keywords, match types, and bidding. Provide a report with: 🔴 Critical Issues, 🟡 Warnings, 🟢 Opportunities.', '- **Meta Ads Audit Role (Triggered by "Perform a comprehensive campaign audit of my Meta Ads account.")**: You are the Meta Ads Auditor. Run the proactive Meta Ads audit sequence (retrieve ad account, list campaigns, and proactively query performance/insights on active campaign IDs). Apply `meta-ads.md` skill to evaluate ABO vs CBO, creative hooks, and ad copy. Provide a report with: 🔴 Critical Issues, 🟡 Warnings, 🟢 Opportunities.', '- **Multi-Platform Audit Role (Triggered by "Perform a comprehensive multi-platform campaign audit of my connected ad accounts.")**: You are the Lead Growth Marketing Auditor. Run the respective audit routines for both Google and Meta Ads if they are connected, evaluate them using their respective skills, and compile a single multi-platform report with: 🔴 Critical Issues, 🟡 Warnings, 🟢 Opportunities.', '- **Ad Copywriter Role (Triggered by "Help me generate high-performing ad copy for my campaigns.")**: You are the Ad Copywriter. Read `product-marketing.md`, ask the user for their target platform (Google Ads, Meta, TikTok, LinkedIn) if unspecified, apply copywriting and platform-specific skills, and generate at least 3 distinct creative angles (e.g. Pain, Curiosity, Social Proof).', '- **Go-To-Market Strategist Role (Triggered by "Help me build a comprehensive Go-To-Market strategy for my product.")**: You are the Go-To-Market Strategist. Read `product-marketing.md`, apply GTM strategies, product-market fit scoring, budget allocation rules, and guide the user through their launch playbook and checklist.', '- **Autoresearch Planner Role (Triggered by "Launch the Autoresearch configuration wizard to optimize my marketing assets and test hypotheses.")**: You are the Autoresearch Planner. Your primary objective is to welcome the user, draft a stellar marketing optimization plan, and get their approval to launch the autonomous optimization loop. To deliver a world-class, premium Customer Experience (CX) and avoid a tedious, slow back-and-forth: 1. You must NEVER ask for budget details, spending limits, daily spend caps, target behaviors, lookalike audiences, or campaign status at startup. The Autoresearch loop is generic and applies to non-campaign assets like landing pages, creative assets, and ad copy. Asking for budget or media-buying parameters is a severe design error and breaks the general UX. 2. In your very first message, welcome the user with a stunning marketing greeting. Explain that this wizard is a general-purpose marketing optimization engine that optimizes ANY asset (such as ad creatives, landing pages, ad copy, headlines, or email hooks). 3. Present a beautifully structured, clean welcome form displaying ONLY the 4 general parameters: - **Goal**: What are you optimizing? (e.g. landing page headers, CTR creative hooks, ad copy). - **Main Metric / Scoring**: How should we score the variants? (e.g. CTR history, PAS copywriting framework, CRO rules, character limits). - **Scope / Constraints**: What are the brand rules or words to avoid? - **Prior Data**: Do you have past performance logs or experiment CSVs I should read first? You must NEVER include budget, spend caps, target platforms, or target ad settings in this form under any circumstances. 4. If the user provides prior experiment data (like a CSV), you must IMMEDIATELY read and parse it using your file tools. Analyze the historical performance, extract key learnings, and use them to automatically deduce the Goal, Metric, and Scope. Pre-fill the boundaries using these extracted insights! 5. Draft a comprehensive Autoresearch Plan (summarizing the Goal, Metric, Scope, and Prior Data learnings) and conclude with the direct call-to-action: "Would you like me to start the autonomous loop now? (Y/N)". Once approved, actively execute the complete multi-cycle optimization loop (Modify ➡️ Verify ➡️ Keep/Discard ➡️ Repeat) in your thought process, print the loop progress log for each cycle, and save the final winning variants to a report file inside `~/.openads/reports/autoresearch-[timestamp].md`.');
110
- parts.push('', '## Memory', '', `Your business context file is at: ${contextPath}`, 'This file contains everything you have learned about the user\'s business across sessions.', 'At the START of every conversation, read this file to recall past context.', 'At the END of a conversation (or when you learn something significant), APPEND new insights to the "## Learnings" section of that file.', 'Things worth remembering: product details, audience segments, campaign performance benchmarks, winning ad angles, competitor insights, budget constraints, seasonal patterns, and any preferences the user expresses.', 'Format each learning as a bullet point with a date, e.g.: "- (2026-05-24) Best-performing Meta creative uses customer testimonial videos."', 'Never overwrite existing learnings — only append new ones.', 'If the learnings section grows beyond 50 items, summarize the oldest 25 into a "## Summary" section at the top and remove the individual bullets.');
111
- if (config?.productContext) {
112
- parts.push(`\nThe user's business: ${config.productContext}`);
220
+ parts.push('', '## Mode: Audit (Read-Only)', 'You can analyze performance, find waste, and recommend changes. You CANNOT make active modifications.', 'If asked to execute a write operation, explain Audit Mode and tell the user to run `openads setup` to enable Launch Mode.');
113
221
  }
222
+ parts.push('', '## Your Skills', '- Skill files (.md) are loaded into your context for the active task. Read them first.', '- Key skills available: Google Ads, Meta Ads, Copywriting, CRO, Email, Video Ads, Analytics, Competitor Research, Customer Research, Go-To-Market, Autoresearch loops.', '- For copywriting: use PAS/AIDA frameworks, platform character limits, and the user\'s product context.', '- For audits: flag 🔴 Critical Issues, 🟡 Warnings, 🟢 Opportunities.', '- For Autoresearch: the deliverable is ALWAYS new testable hypotheses — never a data summary. Follow the skill file phases.');
114
223
  if (config?.connectGoogle) {
115
- parts.push('Google Ads is connected — you can read live campaign data.');
224
+ parts.push('- Google Ads is connected — you can read live campaign data.');
116
225
  }
117
226
  if (config?.metaToken) {
118
- parts.push('Meta Ads is connected — you can read live campaign and creative data.');
227
+ parts.push('- Meta Ads is connected — you can read live campaign and creative data.');
228
+ }
229
+ parts.push(...businessContext);
230
+ parts.push(...memory);
231
+ // Full tier gets additional frontier-only instructions
232
+ if (tier === 'full') {
233
+ parts.push('', '## Advanced Analysis (Full Mode)', '- When auditing, cross-reference metrics across platforms if both are connected.', '- For copywriting, generate 5+ variants with distinct emotional registers and A/B testing rationale.', '- For Autoresearch, run 5 autonomous cycles and score against prior data patterns.', '- Proactively suggest follow-up actions the user hasn\'t asked for.', '- Use rich markdown formatting: tables, headers, bold/italic emphasis, and structured sections.');
119
234
  }
120
- parts.push('', '## HIGH-PRIORITY INTEGRITY RULE', '- Even if Meta Ads or Google Ads are connected above, you must NEVER assume or state that the Autoresearch wizard is specifically for Meta Ads, Google Ads, or any single channel at startup. You must remain 100% general-purpose and platform-agnostic. Always welcome the user to a general campaign optimization wizard and provide examples across multiple channels (Meta, Google, copywriting, creatives, landing pages) rather than defaulting or restricting your language to Meta Ads.', '- You must NEVER ask for budget, spending limits, daily caps, campaign status, behavioral target demographics, or ad-account settings during Autoresearch setup. Autoresearch is a generic content-and-hypothesis loop (for copywriting, creative variants, landing page layouts, headlines, hooks, etc.) and ONLY requires: Goal (what to test), Metric/Scoring (how to evaluate), Scope/Constraints (what to avoid), and Prior Data (CSVs/files). Keep your startup questions strictly confined to these 4 general parameters.');
121
235
  return parts.join('\n');
122
236
  }
123
237
  // ─── API Key Environment Variable Mapping ───────────────────────────
@@ -207,6 +321,73 @@ function showSkills() {
207
321
  console.log(chalk.gray(' The agent picks them up automatically — no code needed.'));
208
322
  console.log('');
209
323
  }
324
+ // ─── Selective Skill Loading ────────────────────────────────────────
325
+ // Instead of loading all 24 skill files (~11K tokens), load only the ones
326
+ // relevant to the selected action. Critical for small local models.
327
+ function getRelevantSkills(action, arAction, skillsDir, tier = 'standard') {
328
+ const pmSkill = path.join(skillsDir, 'product-marketing.md');
329
+ const expressDir = path.join(skillsDir, 'express');
330
+ // ── Express tier → always load from express/ directory ──────────
331
+ if (tier === 'express') {
332
+ const epSkill = path.join(expressDir, 'product.md');
333
+ if (arAction) {
334
+ return [path.join(expressDir, 'autoresearch.md'), epSkill];
335
+ }
336
+ const expressMap = {
337
+ 'audit': [path.join(expressDir, 'audit.md'), epSkill],
338
+ 'copy': [path.join(expressDir, 'copywriting.md'), epSkill],
339
+ 'gtm': [path.join(expressDir, 'strategy.md'), epSkill],
340
+ 'autoresearch': [path.join(expressDir, 'autoresearch.md'), epSkill],
341
+ 'chat': [epSkill],
342
+ };
343
+ return expressMap[action] || [epSkill];
344
+ }
345
+ // ── Standard & Full → existing selective loading ────────────────
346
+ // Map ar actions to their specific skill files
347
+ const arSkillMap = {
348
+ 'ar-core': [path.join(skillsDir, 'automation', 'autoresearch.md'), pmSkill],
349
+ 'ar-plan': [path.join(skillsDir, 'automation', 'autoresearch-plan.md'), pmSkill],
350
+ 'ar-improve': [path.join(skillsDir, 'automation', 'autoresearch-improve.md'), pmSkill],
351
+ 'ar-learn': [path.join(skillsDir, 'automation', 'autoresearch-learn.md'), pmSkill],
352
+ 'ar-predict': [path.join(skillsDir, 'automation', 'autoresearch-predict.md'), pmSkill],
353
+ 'ar-probe': [path.join(skillsDir, 'automation', 'autoresearch-probe.md'), pmSkill],
354
+ 'ar-reason': [path.join(skillsDir, 'automation', 'autoresearch-reason.md'), pmSkill],
355
+ 'ar-scenario': [path.join(skillsDir, 'automation', 'autoresearch-scenario.md'), pmSkill],
356
+ 'ar-evals': [path.join(skillsDir, 'automation', 'autoresearch-evals.md'), pmSkill],
357
+ 'ar-debug': [path.join(skillsDir, 'automation', 'autoresearch-debug.md'), pmSkill],
358
+ 'ar-fix': [path.join(skillsDir, 'automation', 'autoresearch-fix.md'), pmSkill],
359
+ 'ar-security': [path.join(skillsDir, 'automation', 'autoresearch-security.md'), pmSkill],
360
+ 'ar-ship': [path.join(skillsDir, 'automation', 'autoresearch-ship.md'), pmSkill],
361
+ };
362
+ // Autoresearch actions → load only the relevant skill + product context
363
+ if (arAction && arSkillMap[arAction]) {
364
+ return arSkillMap[arAction];
365
+ }
366
+ // Audit → load platform-specific skills
367
+ if (action === 'audit') {
368
+ return [
369
+ path.join(skillsDir, 'ads', 'google-ads.md'),
370
+ path.join(skillsDir, 'ads', 'meta-ads.md'),
371
+ pmSkill,
372
+ ];
373
+ }
374
+ // Copy → load copywriting + product context
375
+ if (action === 'copy') {
376
+ return [
377
+ path.join(skillsDir, 'content', 'copywriting.md'),
378
+ pmSkill,
379
+ ];
380
+ }
381
+ // GTM → load go-to-market + product context
382
+ if (action === 'gtm') {
383
+ return [
384
+ path.join(skillsDir, 'strategy', 'go-to-market.md'),
385
+ pmSkill,
386
+ ];
387
+ }
388
+ // Chat (default) → load everything (full discovery)
389
+ return [skillsDir];
390
+ }
210
391
  // ─── Main ───────────────────────────────────────────────────────────
211
392
  async function main() {
212
393
  const args = process.argv.slice(2);
@@ -252,13 +433,17 @@ async function main() {
252
433
  // ─── Splash Screen ──────────────────────────────────────────────
253
434
  const pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8'));
254
435
  const cleanProvider = resolveModel(config.provider);
255
- const modelName = chalk.cyan.bold(cleanProvider);
436
+ const isLocalModel = !!config.localBaseUrl;
437
+ const modelName = getModelLabel(config);
256
438
  const googleStatus = config.connectGoogle ? chalk.green('● Connected') : chalk.gray('○ Not connected');
257
439
  const metaStatus = config.metaToken ? chalk.green('● Connected') : chalk.gray('○ Not connected');
258
440
  const modeName = config.mode === 'launch' ? chalk.red.bold('Launch Mode (Read-Write)') : chalk.green.bold('Audit Mode (Safe / Read-only)');
441
+ const tier = getTierFromConfig(config);
442
+ const tierBadge = getTierBadge(tier);
443
+ const modelTypeTag = isLocalModel ? chalk.gray('local') : chalk.gray('cloud');
259
444
  // Build compact status panel
260
445
  const statusLines = [
261
- ` ${chalk.bold.white('Model')} ${modelName}`,
446
+ ` ${chalk.bold.white('Model')} ${modelName} ${tierBadge} ${chalk.gray('·')} ${modelTypeTag}`,
262
447
  ` ${chalk.bold.white('Mode')} ${modeName}`,
263
448
  ` ${chalk.bold.white('Google Ads')} ${googleStatus}`,
264
449
  ` ${chalk.bold.white('Meta Ads')} ${metaStatus}`,
@@ -267,6 +452,8 @@ async function main() {
267
452
  ].join('\n');
268
453
  // ─── Interactive Menu (when no args) ────────────────────────────
269
454
  let finalArgs = [...args];
455
+ let selectedAction = ''; // Track which action was selected for system prompt mode
456
+ let selectedArAction = ''; // Track which AR sub-action for selective skill loading
270
457
  if (args.length === 0) {
271
458
  while (true) {
272
459
  console.clear();
@@ -280,30 +467,104 @@ async function main() {
280
467
  borderColor: 'cyan',
281
468
  dimBorder: true,
282
469
  }));
283
- const { action } = await enquirer.prompt({
284
- type: 'select',
285
- name: 'action',
286
- message: chalk.bold('What would you like to do?'),
287
- choices: [
470
+ // ─── Tier-Aware Main Menu ─────────────────────────────────────
471
+ const menuChoices = tier === 'express'
472
+ ? [
288
473
  { name: 'chat', message: `${chalk.cyan('💬')} Ask anything` },
289
- { name: 'audit', message: `${chalk.cyan('🔍')} Audit my ad campaigns ${chalk.gray('(audit)')}` },
290
- { name: 'copy', message: `${chalk.cyan('✍️')} Write ad copy for any platform ${chalk.gray('(copywriting)')}` },
291
- { name: 'autoresearch', message: `${chalk.cyan('🔄')} Test and improve ideas automatically ${chalk.gray('(autoresearch)')}` },
292
- { name: 'gtm', message: `${chalk.cyan('📈')} Build a go-to-market plan ${chalk.gray('(strategy)')}` },
293
- { name: 'skills', message: `${chalk.cyan('📚')} Browse available skills` },
294
- { name: 'schedule', message: `${chalk.cyan('⏰')} Schedule automations` },
474
+ { name: 'audit', message: `${chalk.cyan('🔍')} Quick audit — scan my campaigns` },
475
+ { name: 'copy', message: `${chalk.cyan('✍️')} Write ad copy headlines & descriptions` },
476
+ { name: 'autoresearch', message: `${chalk.cyan('🎯')} Generate ideas marketing hypotheses` },
477
+ { name: 'gtm', message: `${chalk.cyan('📈')} GTM brief — 1-page go-to-market plan` },
295
478
  { name: 'setup', message: `${chalk.gray('⚙️')} Settings` },
296
- { name: 'doctor', message: `${chalk.gray('🩺')} Diagnostics` },
297
479
  { name: 'exit', message: `${chalk.gray('❌')} Exit` }
298
480
  ]
481
+ : tier === 'standard'
482
+ ? [
483
+ { name: 'chat', message: `${chalk.cyan('💬')} Ask anything` },
484
+ { name: 'audit', message: `${chalk.cyan('🔍')} Audit my ad campaigns` },
485
+ { name: 'copy', message: `${chalk.cyan('✍️')} Write ad copy for any platform` },
486
+ { name: 'autoresearch', message: `${chalk.cyan('🔄')} Test and improve ideas ${chalk.gray('(autoresearch)')}` },
487
+ { name: 'gtm', message: `${chalk.cyan('📈')} Build a go-to-market plan` },
488
+ { name: 'schedule', message: `${chalk.cyan('⏰')} Schedule automations` },
489
+ { name: 'setup', message: `${chalk.gray('⚙️')} Settings` },
490
+ { name: 'doctor', message: `${chalk.gray('🩺')} Diagnostics` },
491
+ { name: 'exit', message: `${chalk.gray('❌')} Exit` }
492
+ ]
493
+ : [
494
+ { name: 'chat', message: `${chalk.cyan('💬')} Ask anything` },
495
+ { name: 'audit', message: `${chalk.cyan('🔍')} Audit my ad campaigns ${chalk.gray('(deep analysis)')}` },
496
+ { name: 'copy', message: `${chalk.cyan('✍️')} Write ad copy for any platform ${chalk.gray('(5+ variants)')}` },
497
+ { name: 'autoresearch', message: `${chalk.cyan('🔄')} Test and improve ideas automatically ${chalk.gray('(autoresearch)')}` },
498
+ { name: 'gtm', message: `${chalk.cyan('📈')} Build a go-to-market plan ${chalk.gray('(comprehensive)')}` },
499
+ { name: 'skills', message: `${chalk.cyan('📚')} Browse available skills` },
500
+ { name: 'schedule', message: `${chalk.cyan('⏰')} Schedule automations` },
501
+ { name: 'setup', message: `${chalk.gray('⚙️')} Settings` },
502
+ { name: 'doctor', message: `${chalk.gray('🩺')} Diagnostics` },
503
+ { name: 'exit', message: `${chalk.gray('❌')} Exit` }
504
+ ];
505
+ const { action } = await enquirer.prompt({
506
+ type: 'select',
507
+ name: 'action',
508
+ message: chalk.bold('What would you like to do?'),
509
+ choices: menuChoices,
299
510
  });
300
511
  if (action === 'exit') {
301
512
  console.log(chalk.cyan('\n Goodbye! Keep marketing 🎯\n'));
302
513
  return;
303
514
  }
304
515
  if (action === 'setup') {
305
- await runSetup();
306
- return;
516
+ // ─── Settings Sub-Menu ────────────────────────────────
517
+ const currentTierLabel = getTierBadge(tier);
518
+ const currentModeLabel = config.mode === 'launch' ? 'Launch' : 'Audit';
519
+ const { settingsAction } = await enquirer.prompt({
520
+ type: 'select',
521
+ name: 'settingsAction',
522
+ message: chalk.bold('Settings'),
523
+ choices: [
524
+ { name: 'tier', message: `${chalk.yellow('⚡')} Change experience tier ${chalk.gray(`(currently: ${tier})`)}` },
525
+ { name: 'mode', message: `${chalk.cyan('🔄')} Change operational mode ${chalk.gray(`(currently: ${currentModeLabel})`)}` },
526
+ { name: 'full', message: `${chalk.gray('⚙️')} Run full setup wizard` },
527
+ { name: 'back', message: chalk.gray('← Back') },
528
+ ]
529
+ });
530
+ if (settingsAction === 'full') {
531
+ await runSetup();
532
+ return;
533
+ }
534
+ if (settingsAction === 'tier') {
535
+ const { newTier } = await enquirer.prompt({
536
+ type: 'select',
537
+ name: 'newTier',
538
+ message: 'Select experience tier:',
539
+ choices: [
540
+ { name: 'express', message: `${chalk.yellow('⚡')} Express — Fast & reliable (Llama 8B, Mistral 7B)` },
541
+ { name: 'standard', message: `${chalk.blue('📊')} Standard — Balanced depth (Gemini Flash, GPT-4o Mini)` },
542
+ { name: 'full', message: `${chalk.magenta('🚀')} Full — Maximum depth (GPT-4o, Claude, Gemini Pro)` },
543
+ ],
544
+ initial: ['express', 'standard', 'full'].indexOf(tier)
545
+ });
546
+ config.tier = newTier;
547
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
548
+ console.log(chalk.green(`\n✓ Tier changed to ${getTierBadge(newTier)}.\n`));
549
+ continue;
550
+ }
551
+ if (settingsAction === 'mode') {
552
+ const { newMode } = await enquirer.prompt({
553
+ type: 'select',
554
+ name: 'newMode',
555
+ message: 'Select operational mode:',
556
+ choices: [
557
+ { name: 'audit', message: `${chalk.green('🔒')} Audit Mode (Safe / Read-only)` },
558
+ { name: 'launch', message: `${chalk.red('🔓')} Launch Mode (Read-Write)` },
559
+ ],
560
+ initial: config.mode === 'launch' ? 1 : 0
561
+ });
562
+ config.mode = newMode;
563
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
564
+ console.log(chalk.green(`\n✓ Mode changed to ${newMode === 'launch' ? 'Launch' : 'Audit'}.\n`));
565
+ continue;
566
+ }
567
+ continue;
307
568
  }
308
569
  if (action === 'doctor') {
309
570
  await runDoctor();
@@ -334,30 +595,470 @@ async function main() {
334
595
  continue;
335
596
  }
336
597
  if (action === 'audit') {
337
- let auditPrompt = 'Perform a comprehensive campaign audit of my Google Ads account.';
338
- if (config.metaToken && !config.connectGoogle) {
339
- auditPrompt = 'Perform a comprehensive campaign audit of my Meta Ads account.';
598
+ let auditPrompt = '';
599
+ // Express tier: skip platform sub-menu entirely — just go
600
+ if (tier === 'express') {
601
+ if (!config.metaToken && !config.connectGoogle) {
602
+ console.log('');
603
+ console.log(chalk.yellow(' ⚠️ No ad platforms connected.'));
604
+ console.log(chalk.gray(' Paste your campaign data in the chat, or run `openads setup` to connect.\n'));
605
+ auditPrompt = 'Quick audit. No platforms connected — ask me to paste campaign data, then flag issues as 🔴🟡🟢 and give 3 action items.';
606
+ }
607
+ else if (config.connectGoogle && config.metaToken) {
608
+ auditPrompt = 'Quick audit of all my connected ad campaigns. Flag issues as 🔴 Critical, 🟡 Warning, 🟢 Opportunity. Give me the top 3 actions.';
609
+ }
610
+ else if (config.connectGoogle) {
611
+ auditPrompt = 'Quick audit of my Google Ads campaigns. Flag issues as 🔴🟡🟢 and give 3 actions.';
612
+ }
613
+ else {
614
+ auditPrompt = 'Quick audit of my Meta Ads campaigns. Flag issues as 🔴🟡🟢 and give 3 actions.';
615
+ }
616
+ // Standard/Full: platform selection sub-menu
340
617
  }
341
- else if (config.metaToken && config.connectGoogle) {
342
- auditPrompt = 'Perform a comprehensive multi-platform campaign audit of my connected ad accounts.';
618
+ else {
619
+ if (!config.metaToken && !config.connectGoogle) {
620
+ console.log('');
621
+ console.log(chalk.yellow(' ⚠️ No ad platforms connected.'));
622
+ console.log(chalk.gray(' Connect Google Ads or Meta Ads via `openads setup` for live data.'));
623
+ console.log(chalk.gray(' You can still ask questions about your campaigns by pasting data into the chat.\n'));
624
+ auditPrompt = 'Audit my ad campaigns. No platforms are connected, so ask me to paste my campaign data.';
625
+ }
626
+ else if (config.metaToken && config.connectGoogle) {
627
+ const { auditPlatform } = await enquirer.prompt({
628
+ type: 'select',
629
+ name: 'auditPlatform',
630
+ message: chalk.bold('Which platform would you like to audit?'),
631
+ choices: [
632
+ { name: 'both', message: `${chalk.cyan('🔍')} Both platforms (Google Ads + Meta Ads)` },
633
+ { name: 'google', message: `${chalk.cyan('📊')} Google Ads only` },
634
+ { name: 'meta', message: `${chalk.cyan('📱')} Meta Ads only` },
635
+ { name: 'back', message: chalk.gray('← Back') },
636
+ ]
637
+ });
638
+ if (auditPlatform === 'back')
639
+ continue;
640
+ if (auditPlatform === 'google')
641
+ auditPrompt = 'Audit my Google Ads campaigns. Focus on keyword quality, budget waste, bid strategy, and RSA performance.';
642
+ else if (auditPlatform === 'meta')
643
+ auditPrompt = 'Audit my Meta Ads campaigns. Focus on creative fatigue, ABO vs CBO, ROAS trends, and audience overlap.';
644
+ else
645
+ auditPrompt = 'Audit all my connected ad accounts — Google Ads and Meta Ads.';
646
+ }
647
+ else if (config.connectGoogle) {
648
+ auditPrompt = 'Audit my Google Ads campaigns. Focus on keyword quality, budget waste, bid strategy, and RSA performance.';
649
+ }
650
+ else {
651
+ auditPrompt = 'Audit my Meta Ads campaigns. Focus on creative fatigue, ABO vs CBO, ROAS trends, and audience overlap.';
652
+ }
343
653
  }
344
654
  finalArgs = [auditPrompt];
655
+ selectedAction = 'audit';
656
+ }
657
+ else if (action === 'autoresearch') {
658
+ let inArMenu = true;
659
+ let arAction = '';
660
+ while (inArMenu) {
661
+ console.clear();
662
+ console.log('');
663
+ console.log(openadsGradient(LOGO));
664
+ console.log('');
665
+ console.log(boxen(statusLines, {
666
+ padding: { top: 1, bottom: 1, left: 2, right: 2 },
667
+ margin: { top: 0, bottom: 1, left: 2, right: 2 },
668
+ borderStyle: 'round',
669
+ borderColor: 'cyan',
670
+ dimBorder: true,
671
+ }));
672
+ // ── Express tier: flat, simple menu ─────────────────────
673
+ if (tier === 'express') {
674
+ const { opt } = await enquirer.prompt({
675
+ type: 'select',
676
+ name: 'opt',
677
+ message: chalk.bold('🔬 Autoresearch — What do you want to do?'),
678
+ choices: [
679
+ { name: 'ar-core', message: `${chalk.green('🎯')} Generate new ideas — Run the core hypothesis loop` },
680
+ { name: 'ar-plan', message: `${chalk.cyan('📋')} Plan my experiment — Figure out what to test & how` },
681
+ { name: 'ar-debug', message: `${chalk.yellow('🐛')} Debug underperformance — Diagnose drops & issues` },
682
+ { name: 'ar-fix', message: `${chalk.red('🔨')} Fix issues — Character limits, pixel breaks` },
683
+ { name: 'back', message: `${chalk.gray('← Back to main menu')}` },
684
+ ]
685
+ });
686
+ if (opt === 'back')
687
+ break;
688
+ arAction = opt;
689
+ inArMenu = false;
690
+ // ── Standard tier: 5 phases, 8 commands ─────────────────
691
+ }
692
+ else if (tier === 'standard') {
693
+ const { arPhase } = await enquirer.prompt({
694
+ type: 'select',
695
+ name: 'arPhase',
696
+ message: chalk.bold('🔬 Autoresearch — Select a Phase:'),
697
+ choices: [
698
+ { name: 'discover', message: `${chalk.yellow('📋 Discover')} — Plan experiments & research` },
699
+ { name: 'generate', message: `${chalk.green('🎯 Generate')} — Core autonomous hypothesis loop` },
700
+ { name: 'analyze', message: `${chalk.magenta('📊 Analyze')} — Inspect past performance` },
701
+ { name: 'fix', message: `${chalk.red('🔨 Fix')} — Fix issues & compliance` },
702
+ { name: 'ship', message: `${chalk.cyan('🚀 Ship')} — Format assets for deployment` },
703
+ { name: 'back', message: `${chalk.gray('← Back to main menu')}` },
704
+ ]
705
+ });
706
+ if (arPhase === 'back')
707
+ break;
708
+ if (arPhase === 'discover') {
709
+ const res = await enquirer.prompt({
710
+ type: 'select', name: 'opt',
711
+ message: chalk.bold('📋 Discover:'),
712
+ choices: [
713
+ { name: 'ar-plan', message: `${chalk.cyan('📋')} Plan my experiment — What to test & how to measure` },
714
+ { name: 'ar-improve', message: `${chalk.cyan('🧠')} Research ICP & growth — Growth opportunities from ICP & market` },
715
+ { name: 'ar-learn', message: `${chalk.cyan('💡')} Learn from the market — Competitive intel on pages & copy` },
716
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
717
+ ]
718
+ });
719
+ if (res.opt !== 'back') {
720
+ arAction = res.opt;
721
+ inArMenu = false;
722
+ }
723
+ }
724
+ else if (arPhase === 'generate') {
725
+ arAction = 'ar-core';
726
+ inArMenu = false;
727
+ }
728
+ else if (arPhase === 'analyze') {
729
+ const res = await enquirer.prompt({
730
+ type: 'select', name: 'opt',
731
+ message: chalk.bold('📊 Analyze:'),
732
+ choices: [
733
+ { name: 'ar-evals', message: `${chalk.cyan('📊')} Analyze past results — Find trends & plateaus` },
734
+ { name: 'ar-debug', message: `${chalk.cyan('🐛')} Debug underperformance — Root cause diagnostic` },
735
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
736
+ ]
737
+ });
738
+ if (res.opt !== 'back') {
739
+ arAction = res.opt;
740
+ inArMenu = false;
741
+ }
742
+ }
743
+ else if (arPhase === 'fix') {
744
+ const res = await enquirer.prompt({
745
+ type: 'select', name: 'opt',
746
+ message: chalk.bold('🔨 Fix:'),
747
+ choices: [
748
+ { name: 'ar-fix', message: `${chalk.cyan('🔨')} Fix issues one-by-one — Character limits, pixel breaks` },
749
+ { name: 'ar-security', message: `${chalk.cyan('🛡️')} Brand safety audit — Reg (FTC/GDPR), trademark` },
750
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
751
+ ]
752
+ });
753
+ if (res.opt !== 'back') {
754
+ arAction = res.opt;
755
+ inArMenu = false;
756
+ }
757
+ }
758
+ else if (arPhase === 'ship') {
759
+ arAction = 'ar-ship';
760
+ inArMenu = false;
761
+ }
762
+ // ── Full tier: all 6 phases, all 12 commands ────────────
763
+ }
764
+ else {
765
+ const { arPhase } = await enquirer.prompt({
766
+ type: 'select',
767
+ name: 'arPhase',
768
+ message: chalk.bold('🔬 Autoresearch — Select a Phase:'),
769
+ choices: [
770
+ { name: 'discover', message: `${chalk.yellow('📋 Discover')} — Plan experiments, research ICP, & competitive intel` },
771
+ { name: 'generate', message: `${chalk.green('🎯 Generate')} — Launch the core autonomous loop to test ideas` },
772
+ { name: 'validate', message: `${chalk.blue('🔮 Validate')} — Expert predictions, stress-tests, & debates` },
773
+ { name: 'analyze', message: `${chalk.magenta('📊 Analyze')} — Inspect past performance & debug drops` },
774
+ { name: 'fix', message: `${chalk.red('🔨 Fix')} — Fix issues & run brand safety audits` },
775
+ { name: 'ship', message: `${chalk.cyan('🚀 Ship')} — Format final assets & build deployment briefs` },
776
+ { name: 'back', message: `${chalk.gray('← Back to main menu')}` },
777
+ ]
778
+ });
779
+ if (arPhase === 'back')
780
+ break;
781
+ if (arPhase === 'discover') {
782
+ const res = await enquirer.prompt({
783
+ type: 'select', name: 'opt',
784
+ message: chalk.bold('📋 Discover Options:'),
785
+ choices: [
786
+ { name: 'ar-plan', message: `${chalk.cyan('📋')} Plan my experiment — Figure out what to test & how to measure` },
787
+ { name: 'ar-improve', message: `${chalk.cyan('🧠')} Research ICP & growth — Find growth opportunities from ICP & market` },
788
+ { name: 'ar-learn', message: `${chalk.cyan('💡')} Learn from the market — Competitive intel on pages & copy` },
789
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
790
+ ]
791
+ });
792
+ if (res.opt !== 'back') {
793
+ arAction = res.opt;
794
+ inArMenu = false;
795
+ }
796
+ }
797
+ else if (arPhase === 'generate') {
798
+ const res = await enquirer.prompt({
799
+ type: 'select', name: 'opt',
800
+ message: chalk.bold('🎯 Generate Options:'),
801
+ choices: [
802
+ { name: 'ar-core', message: `${chalk.cyan('🎯')} Generate new hypotheses — Run core loop to generate & score ideas` },
803
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
804
+ ]
805
+ });
806
+ if (res.opt !== 'back') {
807
+ arAction = res.opt;
808
+ inArMenu = false;
809
+ }
810
+ }
811
+ else if (arPhase === 'validate') {
812
+ const res = await enquirer.prompt({
813
+ type: 'select', name: 'opt',
814
+ message: chalk.bold('🔮 Validate Options:'),
815
+ choices: [
816
+ { name: 'ar-predict', message: `${chalk.cyan('🔮')} Get expert predictions — 5 expert personas debate your idea` },
817
+ { name: 'ar-probe', message: `${chalk.cyan('🔍')} Stress-test my brief — 8 personas stress-test your strategy` },
818
+ { name: 'ar-reason', message: `${chalk.cyan('⚖️')} Debate a strategy call — Adversarial debate on key binary calls` },
819
+ { name: 'ar-scenario', message: `${chalk.cyan('🎭')} Run what-if scenarios — Stress-test plans against disruptions` },
820
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
821
+ ]
822
+ });
823
+ if (res.opt !== 'back') {
824
+ arAction = res.opt;
825
+ inArMenu = false;
826
+ }
827
+ }
828
+ else if (arPhase === 'analyze') {
829
+ const res = await enquirer.prompt({
830
+ type: 'select', name: 'opt',
831
+ message: chalk.bold('📊 Analyze Options:'),
832
+ choices: [
833
+ { name: 'ar-evals', message: `${chalk.cyan('📊')} Analyze past results — Find trends & plateaus in experiment data` },
834
+ { name: 'ar-debug', message: `${chalk.cyan('🐛')} Debug underperformance — Root cause diagnostic for drops` },
835
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
836
+ ]
837
+ });
838
+ if (res.opt !== 'back') {
839
+ arAction = res.opt;
840
+ inArMenu = false;
841
+ }
842
+ }
843
+ else if (arPhase === 'fix') {
844
+ const res = await enquirer.prompt({
845
+ type: 'select', name: 'opt',
846
+ message: chalk.bold('🔨 Fix Options:'),
847
+ choices: [
848
+ { name: 'ar-fix', message: `${chalk.cyan('🔨')} Fix issues one-by-one — Crush character limits, pixel breaks` },
849
+ { name: 'ar-security', message: `${chalk.cyan('🛡️')} Brand safety audit — Reg (FTC/GDPR), trademark compliance` },
850
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
851
+ ]
852
+ });
853
+ if (res.opt !== 'back') {
854
+ arAction = res.opt;
855
+ inArMenu = false;
856
+ }
857
+ }
858
+ else if (arPhase === 'ship') {
859
+ const res = await enquirer.prompt({
860
+ type: 'select', name: 'opt',
861
+ message: chalk.bold('🚀 Ship Options:'),
862
+ choices: [
863
+ { name: 'ar-ship', message: `${chalk.cyan('🚀')} Prepare assets to ship — Format for platforms & build deployment brief` },
864
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
865
+ ]
866
+ });
867
+ if (res.opt !== 'back') {
868
+ arAction = res.opt;
869
+ inArMenu = false;
870
+ }
871
+ }
872
+ }
873
+ }
874
+ if (!arAction) {
875
+ continue;
876
+ }
877
+ let triggerMsg = '';
878
+ selectedArAction = arAction;
879
+ if (arAction === 'ar-core') {
880
+ let goal = '';
881
+ let metric = '';
882
+ let scope = '';
883
+ let csvPath = '';
884
+ const activeConfigPath = path.join(CONFIG_DIR, 'active-experiment.json');
885
+ let useActive = false;
886
+ if (fs.existsSync(activeConfigPath)) {
887
+ try {
888
+ const activeConfig = JSON.parse(fs.readFileSync(activeConfigPath, 'utf8'));
889
+ console.log(chalk.cyan('\n Found an active experiment plan:'));
890
+ console.log(` ${chalk.bold('Goal')}: ${activeConfig.goal}`);
891
+ console.log(` ${chalk.bold('Metric')}: ${activeConfig.metric}`);
892
+ console.log(` ${chalk.bold('Scope')}: ${activeConfig.scope || 'None'}`);
893
+ if (activeConfig.csvPath) {
894
+ console.log(` ${chalk.bold('Data')}: ${activeConfig.csvPath}`);
895
+ }
896
+ console.log('');
897
+ const { confirmActive } = await enquirer.prompt({
898
+ type: 'confirm',
899
+ name: 'confirmActive',
900
+ message: 'Would you like to run the autonomous loop on this active plan?',
901
+ initial: true
902
+ });
903
+ if (confirmActive) {
904
+ useActive = true;
905
+ goal = activeConfig.goal;
906
+ metric = activeConfig.metric;
907
+ scope = activeConfig.scope || '';
908
+ csvPath = activeConfig.csvPath || '';
909
+ }
910
+ }
911
+ catch (e) { }
912
+ }
913
+ if (!useActive) {
914
+ console.log(chalk.cyan('\n Let\'s configure your Autoresearch loop:'));
915
+ const answers = await enquirer.prompt([
916
+ {
917
+ type: 'input',
918
+ name: 'goal',
919
+ message: 'What is your experiment Goal? (e.g., Optimize landing page conversion rate)',
920
+ validate: (val) => val.trim() ? true : 'Goal cannot be empty.'
921
+ },
922
+ {
923
+ type: 'input',
924
+ name: 'metric',
925
+ message: 'What is your primary Metric? (e.g., CVR, CTR, ROAS)',
926
+ validate: (val) => val.trim() ? true : 'Metric cannot be empty.'
927
+ },
928
+ {
929
+ type: 'input',
930
+ name: 'scope',
931
+ message: 'Any Scope constraints? (e.g., No discounts, premium tone - optional)',
932
+ initial: ''
933
+ },
934
+ {
935
+ type: 'input',
936
+ name: 'csvPath',
937
+ message: 'Path to prior experiment CSV/data file (optional - press Enter to skip)',
938
+ initial: '',
939
+ validate: (val) => {
940
+ if (!val.trim())
941
+ return true;
942
+ const resolved = val.startsWith('~') ? val.replace('~', os.homedir()) : path.resolve(val);
943
+ if (fs.existsSync(resolved))
944
+ return true;
945
+ // Smart check standard folders
946
+ const baseName = path.basename(resolved);
947
+ const standardDirs = [
948
+ path.join(os.homedir(), 'Desktop'),
949
+ path.join(os.homedir(), 'Downloads'),
950
+ path.join(os.homedir(), 'Documents')
951
+ ];
952
+ for (const dir of standardDirs) {
953
+ if (fs.existsSync(path.join(dir, baseName)))
954
+ return true;
955
+ }
956
+ return `File not found at: ${resolved}. Please check the path.`;
957
+ }
958
+ }
959
+ ]);
960
+ goal = answers.goal;
961
+ metric = answers.metric;
962
+ scope = answers.scope || '';
963
+ let rawPath = answers.csvPath ? answers.csvPath.trim() : '';
964
+ if (rawPath) {
965
+ let resolved = rawPath.startsWith('~') ? rawPath.replace('~', os.homedir()) : path.resolve(rawPath);
966
+ if (fs.existsSync(resolved)) {
967
+ csvPath = resolved;
968
+ }
969
+ else {
970
+ // Resolve from standard directories
971
+ const baseName = path.basename(resolved);
972
+ const standardDirs = [
973
+ path.join(os.homedir(), 'Desktop'),
974
+ path.join(os.homedir(), 'Downloads'),
975
+ path.join(os.homedir(), 'Documents')
976
+ ];
977
+ let found = false;
978
+ for (const dir of standardDirs) {
979
+ const checkPath = path.join(dir, baseName);
980
+ if (fs.existsSync(checkPath)) {
981
+ csvPath = checkPath;
982
+ console.log(chalk.cyan(`\n 💡 Smart-resolved file name to standard directory:`));
983
+ console.log(` ${chalk.green(csvPath)}\n`);
984
+ found = true;
985
+ break;
986
+ }
987
+ }
988
+ if (!found)
989
+ csvPath = resolved;
990
+ }
991
+ }
992
+ else {
993
+ csvPath = '';
994
+ }
995
+ // Save the config to disk
996
+ fs.writeFileSync(activeConfigPath, JSON.stringify({ goal, metric, scope, csvPath }, null, 2));
997
+ }
998
+ // Concise trigger — system prompt + skill file have the full instructions.
999
+ // Short user message = more output tokens for the model to use.
1000
+ triggerMsg = `Run 3 Autoresearch cycles. Goal: ${goal}. Metric: ${metric}.`;
1001
+ if (scope)
1002
+ triggerMsg += ` Scope: ${scope}.`;
1003
+ if (csvPath)
1004
+ triggerMsg += ` Read the prior data CSV at ${csvPath} first, extract patterns, then generate new hypotheses.`;
1005
+ else
1006
+ triggerMsg += ` No prior data — generate hypotheses from scratch using product context.`;
1007
+ }
1008
+ else {
1009
+ // Concise triggers — the skill file has all the detailed instructions.
1010
+ // Short message = more output tokens for the model's response.
1011
+ const arTriggerMap = {
1012
+ 'ar-plan': 'Plan a marketing experiment. Walk me through Goal → Metric → Scope → Prior Data.',
1013
+ 'ar-improve': 'Research my ICP and identify growth opportunities.',
1014
+ 'ar-learn': 'Analyze my competitors and extract messaging gaps.',
1015
+ 'ar-predict': 'Have 5 expert marketing personas evaluate my idea and give a go/no-go verdict.',
1016
+ 'ar-probe': 'Stress-test my campaign brief with 8 personas.',
1017
+ 'ar-reason': 'Run an adversarial debate on my key strategy decision.',
1018
+ 'ar-scenario': 'Generate what-if scenarios for my marketing plan.',
1019
+ 'ar-evals': 'Analyze my past experiment results and recommend the next test.',
1020
+ 'ar-debug': 'Debug why my campaign is underperforming. Find the root cause.',
1021
+ 'ar-fix': 'Fix my marketing asset issues one by one.',
1022
+ 'ar-security': 'Run a brand safety and compliance audit on my marketing assets.',
1023
+ 'ar-ship': 'Prepare my winning assets for deployment.',
1024
+ };
1025
+ triggerMsg = arTriggerMap[arAction] || '';
1026
+ }
1027
+ finalArgs = [triggerMsg];
1028
+ selectedAction = 'autoresearch';
345
1029
  }
346
1030
  else {
347
- const actionMap = {
1031
+ // ── Tier-aware action prompts ────────────────────────────
1032
+ const expressActionMap = {
1033
+ chat: [],
1034
+ copy: ['Write 5 ad headlines and 3 primary text options for my product. Follow the skill file format.'],
1035
+ gtm: ['Create a 1-page GTM brief for my product. Follow the skill file format.'],
1036
+ };
1037
+ const standardActionMap = {
348
1038
  chat: [],
349
1039
  copy: ['Help me generate high-performing ad copy for my campaigns.'],
350
- autoresearch: ['Launch the Autoresearch configuration wizard to optimize my marketing assets and test hypotheses.'],
351
- gtm: ['Help me build a comprehensive Go-To-Market strategy for my product.'],
1040
+ gtm: ['Help me build a go-to-market strategy for my product.'],
352
1041
  };
1042
+ const fullActionMap = {
1043
+ chat: [],
1044
+ copy: ['Help me generate high-performing ad copy for my campaigns. Generate 5+ variants with distinct emotional registers, A/B testing rationale, and platform-specific formatting.'],
1045
+ gtm: ['Help me build a comprehensive Go-To-Market strategy for my product. Include competitive positioning, channel plan with budget allocation, timeline, and success metrics.'],
1046
+ };
1047
+ const actionMap = tier === 'express' ? expressActionMap : tier === 'full' ? fullActionMap : standardActionMap;
353
1048
  finalArgs = actionMap[action] || [];
1049
+ selectedAction = action;
354
1050
  }
355
1051
  break;
356
1052
  }
357
1053
  }
358
- // ─── Loading Spinner ────────────────────────────────────────────
1054
+ // ─── Loading Spinner ─────────────────────────────────────────
1055
+ const loadingMessages = {
1056
+ express: 'Launching express agent...',
1057
+ standard: 'Starting marketing agent...',
1058
+ full: 'Starting full marketing intelligence agent...',
1059
+ };
359
1060
  const spinner = ora({
360
- text: chalk.cyan('Starting marketing agent...'),
1061
+ text: chalk.cyan(loadingMessages[tier] || 'Starting marketing agent...'),
361
1062
  spinner: 'dots12',
362
1063
  color: 'cyan',
363
1064
  }).start();
@@ -371,14 +1072,20 @@ async function main() {
371
1072
  // System prompt flag
372
1073
  const systemPromptPath = path.join(CONFIG_DIR, 'agent', 'SYSTEM.md');
373
1074
  piArgsRaw.push('--system-prompt', systemPromptPath);
374
- // Skills directories
1075
+ // Skills directories — selective loading based on action
375
1076
  const skillsDir = path.join(pkgDir, 'skills');
376
1077
  const templatesDir = path.join(pkgDir, 'templates');
377
1078
  // Inject product context as a skill directory
378
1079
  const contextDir = injectProductContext(config);
1080
+ // Load only the skills relevant to the selected action and tier
1081
+ const relevantSkills = getRelevantSkills(selectedAction, selectedArAction, skillsDir, tier);
1082
+ const skillArgs = [];
1083
+ for (const skill of relevantSkills) {
1084
+ skillArgs.push('--skill', skill);
1085
+ }
379
1086
  const piArgs = [
380
1087
  ...piArgsRaw,
381
- '--skill', skillsDir,
1088
+ ...skillArgs,
382
1089
  '--prompt-template', templatesDir,
383
1090
  ...(contextDir ? ['--skill', contextDir] : []),
384
1091
  ...finalArgs
@@ -389,6 +1096,15 @@ async function main() {
389
1096
  ...process.env,
390
1097
  NODE_NO_WARNINGS: '1',
391
1098
  };
1099
+ // Signal to the MCP extension whether to skip tool registration.
1100
+ // Express tier and autoresearch skip MCP entirely.
1101
+ if (selectedAction === 'autoresearch' || tier === 'express') {
1102
+ env.OPENADS_SKIP_MCP = '1';
1103
+ }
1104
+ else {
1105
+ delete env.OPENADS_SKIP_MCP;
1106
+ }
1107
+ env.OPENADS_TIER = tier;
392
1108
  if (config.apiKey && config.apiKey !== 'dummy-key') {
393
1109
  const envVarName = getApiKeyEnvVar(cleanProvider);
394
1110
  env[envVarName] = config.apiKey;
@@ -411,8 +1127,9 @@ async function main() {
411
1127
  catch (e) { }
412
1128
  }
413
1129
  settings.quietStartup = true;
414
- // System prompt — makes the agent behave as OpenAds
415
- const systemPrompt = buildSystemPrompt(config);
1130
+ // System prompt — mode-aware and tier-aware
1131
+ const promptMode = (selectedAction === 'autoresearch') ? 'autoresearch' : 'default';
1132
+ const systemPrompt = buildSystemPrompt(config, promptMode, tier, { arAction: selectedArAction });
416
1133
  settings.systemPrompt = systemPrompt;
417
1134
  fs.writeFileSync(systemPromptPath, systemPrompt);
418
1135
  // Resilient retry settings for free-tier rate limits
@@ -443,12 +1160,18 @@ async function main() {
443
1160
  apiKey: "local-key-placeholder",
444
1161
  compat: {
445
1162
  supportsDeveloperRole: false,
446
- supportsReasoningEffort: false
1163
+ supportsReasoningEffort: false,
1164
+ supportsUsageInStreaming: false,
1165
+ requiresToolResultName: true,
1166
+ maxTokensField: "max_tokens"
447
1167
  },
448
1168
  models: [
449
1169
  {
450
1170
  id: modelIdForPi,
451
- name: `${modelIdForPi} (Local)`
1171
+ name: `${modelIdForPi} (Local)`,
1172
+ reasoning: false,
1173
+ contextWindow: 128000,
1174
+ maxTokens: 8192
452
1175
  }
453
1176
  ]
454
1177
  }