openads-ai 0.3.0 → 0.5.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)', '- The PURPOSE of Autoresearch is to generate NEW, actionable, testable hypotheses. The deliverable is ALWAYS a prioritized list of what to test NEXT — concrete headlines, landing page variants, creative hooks, ad copy alternatives, layout changes, CTA experiments, or email subject lines. It is NOT a summary or review of past results.', '- Prior experiment data (CSVs, campaign logs, past A/B test results) is FUEL for the loop — not the output. When the user provides prior data, ingest it quickly, extract the top 3-5 winning patterns and losing patterns, and immediately use those learnings to generate smarter new hypotheses. Spend at most 2-3 sentences summarizing old results before pivoting to new recommendations.', '- CONCISE DATA INGESTION: When analyzing prior data, NEVER list individual experiments one-by-one. Aggregate behind the scenes, extract key patterns (e.g. "Video hero sections drove 3.4% CVR vs 1.9% for static images", "Mega-menu navigation correlated with 3.9% CVR"), and pivot immediately to generating new testable hypotheses informed by those patterns.', '- The autonomous loop follows Karpathy\'s autoresearch methodology — Goal + Metric + Iterate: Generate (Create new testable assets — concrete headlines, page layouts, creative hooks, copy variants) ➡️ Score (Evaluate each against prior learnings, marketing frameworks, CRO best practices, and the user\'s product context) ➡️ Keep / Discard (Keep only those scoring above threshold; document why others failed) ➡️ Iterate (Refine survivors, generate new variations inspired by winners, repeat until you have a polished final set of recommendations).', '- Keep the user updated on each cycle with a concise progress log (e.g. "Loop 1: Generated 10 headline hypotheses. 3 passed, 7 discarded. (Reason: too generic, no pain point)" and "Loop 2: Generated 8. 5 passed. ...").', '- End by presenting a beautifully structured final table of the NEW hypotheses to test, each with: the concrete asset/copy, the rationale (why it should win based on prior data patterns), and a priority score.', '- AUTOMATIC FILE EXPORT: At the end of the loop, you MUST automatically save the final prioritized list of new testable hypotheses to `autoresearch-[timestamp].md` inside `~/.openads/reports/`.', '');
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 Command Roles', '- **Core Loop (Triggered by "Launch the Autoresearch core loop to generate new testable marketing hypotheses.")**: Read `autoresearch.md` skill. Generate NEW testable hypotheses (headlines, copy, pages, hooks) using Goal + Metric + Loop. Prior data is fuel. Run at least 3 autonomous cycles: Generate ➡️ Score ➡️ Keep/Discard ➡️ Iterate. Output cycle logs, final table, and auto-save to `~/.openads/reports/`.', '- **Plan (Triggered by "Help me plan a marketing experiment with Autoresearch.")**: Read `autoresearch-plan.md` skill. Walk the user through Goal → Metric → Scope → Prior Data interactively. Output a validated experiment config ready to run. If the user provides a CSV, ingest it as fuel, extract patterns, and pre-fill the config. End with "Would you like me to start the autonomous loop now? (Y/N)".', '- **Improve (Triggered by "Research my ICP and find growth opportunities with Autoresearch.")**: Read `autoresearch-improve.md` skill. Research ICP pain points, competitor gaps, market trends, channel opportunities, and revenue growth angles. Generate prioritized campaign briefs / PRDs. 15 iterations.', '- **Learn (Triggered by "Analyze my competitors and market positioning with Autoresearch.")**: Read `autoresearch-learn.md` skill. Competitive intelligence: scout competitor pages/ads/positioning, generate knowledge docs, identify gaps and opportunities. 10 iterations.', '- **Predict (Triggered by "Assemble 5 marketing expert personas to evaluate my hypothesis with Autoresearch.")**: Read `autoresearch-predict.md` skill. 5 expert personas (CMO, Performance Marketer, Brand Strategist, CRO Specialist, Data Analyst) debate the user\'s hypothesis. Output consensus score and go/no-go recommendation. One-shot.', '- **Probe (Triggered by "Have 8 marketing personas stress-test my campaign brief with Autoresearch.")**: Read `autoresearch-probe.md` skill. 8 personas (Media Buyer, Creative Director, Data Analyst, Brand Manager, UX Designer, Compliance Officer, CFO, Target Customer) interrogate the brief for blind spots. 15 iterations.', '- **Reason (Triggered by "Run an adversarial debate on this marketing strategy decision with Autoresearch.")**: Read `autoresearch-reason.md` skill. Two sides argue, blind judges score. For hard calls like broad vs niche, video vs static, discount vs value. 8 iterations.', '- **Scenario (Triggered by "Generate what-if scenarios for my marketing plan with Autoresearch.")**: Read `autoresearch-scenario.md` skill. Edge cases across 12 marketing dimensions: competitor moves, algorithm changes, budget cuts, creative fatigue, seasonality, etc. 20 iterations.', '- **Evals (Triggered by "Analyze my past marketing experiment results with Autoresearch.")**: Read `autoresearch-evals.md` skill. Find trends, plateaus, winning/losing patterns in prior experiment data. Recommend next experiment. One-shot.', '- **Debug (Triggered by "Debug why my marketing campaign is underperforming with Autoresearch.")**: Read `autoresearch-debug.md` skill. Hypothesis-driven root cause analysis: creative fatigue, audience saturation, competitor entry, message-market misfit, tracking issues, etc. 15 iterations.', '- **Fix (Triggered by "Fix these known marketing asset issues one-by-one with Autoresearch.")**: Read `autoresearch-fix.md` skill. Systematically crush issues: character limits, compliance, broken tracking, accessibility, branding inconsistencies. 20 iterations.', '- **Security (Triggered by "Run a brand safety and compliance audit on my marketing assets with Autoresearch.")**: Read `autoresearch-security.md` skill. Trademark violations, regulatory compliance (FTC, GDPR), misleading claims, accessibility, brand consistency. 15 iterations. Report: 🔴 Critical, 🟡 Warning, 🟢 Opportunity.', '- **Ship (Triggered by "Prepare my winning marketing assets for deployment with Autoresearch.")**: Read `autoresearch-ship.md` skill. Format for platform specs, generate deployment brief, QA, recommend UTMs and measurement plan, suggest test duration and sample size. Linear phases. Save to `~/.openads/reports/`.');
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.', '- You must NEVER ask for budget, spending limits, daily caps, campaign status, or ad-account settings during Autoresearch setup. Autoresearch generates NEW testable hypotheses and ONLY requires: Goal (what new assets to generate), Metric (how to score them), Scope (constraints), and Prior Data (optional fuel).', '- THE DELIVERABLE IS ALWAYS NEW HYPOTHESES TO TEST — never a summary of old data. Prior data is fuel that informs what to generate next. When given a CSV, spend at most 2-3 sentences extracting patterns, then immediately pivot to drafting the plan for generating NEW testable assets. Draft the **Autoresearch Plan** and end with: "Would you like me to start the autonomous loop now? (Y/N)". NEVER stop at a data summary!', '- AUTONOMOUS LOOP EXECUTION: Once approved, execute at least 3 cycles autonomously in a SINGLE response. Each cycle: generate NEW concrete testable assets (headlines, page variants, hooks, copy) ➡️ score against the metric ➡️ keep winners, discard losers with reasons ➡️ iterate. Output cycle logs, final table of NEW hypotheses with rationale + priority score, and auto-save to `~/.openads/reports/autoresearch-[timestamp].md`.');
121
235
  return parts.join('\n');
122
236
  }
123
237
  // ─── API Key Environment Variable Mapping ───────────────────────────
@@ -207,6 +321,84 @@ 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'), path.join(expressDir, 'organic-social.md'), epSkill],
339
+ 'organic': [path.join(expressDir, 'organic-social.md'), epSkill],
340
+ 'gtm': [path.join(expressDir, 'strategy.md'), epSkill],
341
+ 'autoresearch': [path.join(expressDir, 'autoresearch.md'), epSkill],
342
+ 'chat': [path.join(expressDir, 'organic-social.md'), epSkill],
343
+ };
344
+ return expressMap[action] || [epSkill];
345
+ }
346
+ // ── Standard & Full → existing selective loading ────────────────
347
+ // Map ar actions to their specific skill files
348
+ const arSkillMap = {
349
+ 'ar-core': [path.join(skillsDir, 'automation', 'autoresearch.md'), pmSkill],
350
+ 'ar-plan': [path.join(skillsDir, 'automation', 'autoresearch-plan.md'), pmSkill],
351
+ 'ar-improve': [path.join(skillsDir, 'automation', 'autoresearch-improve.md'), pmSkill],
352
+ 'ar-learn': [path.join(skillsDir, 'automation', 'autoresearch-learn.md'), pmSkill],
353
+ 'ar-predict': [path.join(skillsDir, 'automation', 'autoresearch-predict.md'), pmSkill],
354
+ 'ar-probe': [path.join(skillsDir, 'automation', 'autoresearch-probe.md'), pmSkill],
355
+ 'ar-reason': [path.join(skillsDir, 'automation', 'autoresearch-reason.md'), pmSkill],
356
+ 'ar-scenario': [path.join(skillsDir, 'automation', 'autoresearch-scenario.md'), pmSkill],
357
+ 'ar-evals': [path.join(skillsDir, 'automation', 'autoresearch-evals.md'), pmSkill],
358
+ 'ar-debug': [path.join(skillsDir, 'automation', 'autoresearch-debug.md'), pmSkill],
359
+ 'ar-fix': [path.join(skillsDir, 'automation', 'autoresearch-fix.md'), pmSkill],
360
+ 'ar-security': [path.join(skillsDir, 'automation', 'autoresearch-security.md'), pmSkill],
361
+ 'ar-ship': [path.join(skillsDir, 'automation', 'autoresearch-ship.md'), pmSkill],
362
+ };
363
+ // Autoresearch actions → load only the relevant skill + product context
364
+ if (arAction && arSkillMap[arAction]) {
365
+ return arSkillMap[arAction];
366
+ }
367
+ // Audit → load platform-specific skills
368
+ if (action === 'audit') {
369
+ return [
370
+ path.join(skillsDir, 'ads', 'google-ads.md'),
371
+ path.join(skillsDir, 'ads', 'meta-ads.md'),
372
+ pmSkill,
373
+ ];
374
+ }
375
+ // Copy → load copywriting + organic social + facebook organic + product context
376
+ if (action === 'copy') {
377
+ return [
378
+ path.join(skillsDir, 'content', 'copywriting.md'),
379
+ path.join(skillsDir, 'organic', 'organic-social.md'),
380
+ path.join(skillsDir, 'organic', 'facebook-page.md'),
381
+ pmSkill,
382
+ ];
383
+ }
384
+ // Organic → load organic social + facebook organic + product context
385
+ if (action === 'organic') {
386
+ return [
387
+ path.join(skillsDir, 'organic', 'organic-social.md'),
388
+ path.join(skillsDir, 'organic', 'facebook-page.md'),
389
+ pmSkill,
390
+ ];
391
+ }
392
+ // GTM → load go-to-market + product context
393
+ if (action === 'gtm') {
394
+ return [
395
+ path.join(skillsDir, 'strategy', 'go-to-market.md'),
396
+ pmSkill,
397
+ ];
398
+ }
399
+ // Chat (default) → load everything (full discovery)
400
+ return [skillsDir];
401
+ }
210
402
  // ─── Main ───────────────────────────────────────────────────────────
211
403
  async function main() {
212
404
  const args = process.argv.slice(2);
@@ -252,13 +444,17 @@ async function main() {
252
444
  // ─── Splash Screen ──────────────────────────────────────────────
253
445
  const pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8'));
254
446
  const cleanProvider = resolveModel(config.provider);
255
- const modelName = chalk.cyan.bold(cleanProvider);
447
+ const isLocalModel = !!config.localBaseUrl;
448
+ const modelName = getModelLabel(config);
256
449
  const googleStatus = config.connectGoogle ? chalk.green('● Connected') : chalk.gray('○ Not connected');
257
450
  const metaStatus = config.metaToken ? chalk.green('● Connected') : chalk.gray('○ Not connected');
258
451
  const modeName = config.mode === 'launch' ? chalk.red.bold('Launch Mode (Read-Write)') : chalk.green.bold('Audit Mode (Safe / Read-only)');
452
+ const tier = getTierFromConfig(config);
453
+ const tierBadge = getTierBadge(tier);
454
+ const modelTypeTag = isLocalModel ? chalk.gray('local') : chalk.gray('cloud');
259
455
  // Build compact status panel
260
456
  const statusLines = [
261
- ` ${chalk.bold.white('Model')} ${modelName}`,
457
+ ` ${chalk.bold.white('Model')} ${modelName} ${tierBadge} ${chalk.gray('·')} ${modelTypeTag}`,
262
458
  ` ${chalk.bold.white('Mode')} ${modeName}`,
263
459
  ` ${chalk.bold.white('Google Ads')} ${googleStatus}`,
264
460
  ` ${chalk.bold.white('Meta Ads')} ${metaStatus}`,
@@ -267,6 +463,8 @@ async function main() {
267
463
  ].join('\n');
268
464
  // ─── Interactive Menu (when no args) ────────────────────────────
269
465
  let finalArgs = [...args];
466
+ let selectedAction = ''; // Track which action was selected for system prompt mode
467
+ let selectedArAction = ''; // Track which AR sub-action for selective skill loading
270
468
  if (args.length === 0) {
271
469
  while (true) {
272
470
  console.clear();
@@ -280,30 +478,107 @@ async function main() {
280
478
  borderColor: 'cyan',
281
479
  dimBorder: true,
282
480
  }));
283
- const { action } = await enquirer.prompt({
284
- type: 'select',
285
- name: 'action',
286
- message: chalk.bold('What would you like to do?'),
287
- choices: [
481
+ // ─── Tier-Aware Main Menu ─────────────────────────────────────
482
+ const menuChoices = tier === 'express'
483
+ ? [
288
484
  { 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` },
485
+ { name: 'audit', message: `${chalk.cyan('🔍')} Quick audit — scan my campaigns` },
486
+ { name: 'copy', message: `${chalk.cyan('✍️')} Write ad copy headlines & descriptions` },
487
+ { name: 'organic', message: `${chalk.cyan('📱')} Organic posts Instagram, LinkedIn, FB` },
488
+ { name: 'autoresearch', message: `${chalk.cyan('🎯')} Generate ideas marketing hypotheses` },
489
+ { name: 'gtm', message: `${chalk.cyan('📈')} GTM brief — 1-page go-to-market plan` },
295
490
  { name: 'setup', message: `${chalk.gray('⚙️')} Settings` },
296
- { name: 'doctor', message: `${chalk.gray('🩺')} Diagnostics` },
297
491
  { name: 'exit', message: `${chalk.gray('❌')} Exit` }
298
492
  ]
493
+ : tier === 'standard'
494
+ ? [
495
+ { name: 'chat', message: `${chalk.cyan('💬')} Ask anything` },
496
+ { name: 'audit', message: `${chalk.cyan('🔍')} Audit my ad campaigns` },
497
+ { name: 'copy', message: `${chalk.cyan('✍️')} Write ad copy for any platform` },
498
+ { name: 'organic', message: `${chalk.cyan('📱')} Organic social content` },
499
+ { name: 'autoresearch', message: `${chalk.cyan('🔄')} Test and improve ideas ${chalk.gray('(autoresearch)')}` },
500
+ { name: 'gtm', message: `${chalk.cyan('📈')} Build a go-to-market plan` },
501
+ { name: 'schedule', message: `${chalk.cyan('⏰')} Schedule automations` },
502
+ { name: 'setup', message: `${chalk.gray('⚙️')} Settings` },
503
+ { name: 'doctor', message: `${chalk.gray('🩺')} Diagnostics` },
504
+ { name: 'exit', message: `${chalk.gray('❌')} Exit` }
505
+ ]
506
+ : [
507
+ { name: 'chat', message: `${chalk.cyan('💬')} Ask anything` },
508
+ { name: 'audit', message: `${chalk.cyan('🔍')} Audit my ad campaigns ${chalk.gray('(deep analysis)')}` },
509
+ { name: 'copy', message: `${chalk.cyan('✍️')} Write ad copy for any platform ${chalk.gray('(5+ variants)')}` },
510
+ { name: 'organic', message: `${chalk.cyan('📱')} Organic social posting ${chalk.gray('(Facebook integration)')}` },
511
+ { name: 'autoresearch', message: `${chalk.cyan('🔄')} Test and improve ideas automatically ${chalk.gray('(autoresearch)')}` },
512
+ { name: 'gtm', message: `${chalk.cyan('📈')} Build a go-to-market plan ${chalk.gray('(comprehensive)')}` },
513
+ { name: 'skills', message: `${chalk.cyan('📚')} Browse available skills` },
514
+ { name: 'schedule', message: `${chalk.cyan('⏰')} Schedule automations` },
515
+ { name: 'setup', message: `${chalk.gray('⚙️')} Settings` },
516
+ { name: 'doctor', message: `${chalk.gray('🩺')} Diagnostics` },
517
+ { name: 'exit', message: `${chalk.gray('❌')} Exit` }
518
+ ];
519
+ const { action } = await enquirer.prompt({
520
+ type: 'select',
521
+ name: 'action',
522
+ message: chalk.bold('What would you like to do?'),
523
+ choices: menuChoices,
299
524
  });
300
525
  if (action === 'exit') {
301
526
  console.log(chalk.cyan('\n Goodbye! Keep marketing 🎯\n'));
302
527
  return;
303
528
  }
304
529
  if (action === 'setup') {
305
- await runSetup();
306
- return;
530
+ // ─── Settings Sub-Menu ────────────────────────────────
531
+ const currentTierLabel = getTierBadge(tier);
532
+ const currentModeLabel = config.mode === 'launch' ? 'Launch' : 'Audit';
533
+ const { settingsAction } = await enquirer.prompt({
534
+ type: 'select',
535
+ name: 'settingsAction',
536
+ message: chalk.bold('Settings'),
537
+ choices: [
538
+ { name: 'tier', message: `${chalk.yellow('⚡')} Change experience tier ${chalk.gray(`(currently: ${tier})`)}` },
539
+ { name: 'mode', message: `${chalk.cyan('🔄')} Change operational mode ${chalk.gray(`(currently: ${currentModeLabel})`)}` },
540
+ { name: 'full', message: `${chalk.gray('⚙️')} Run full setup wizard` },
541
+ { name: 'back', message: chalk.gray('← Back') },
542
+ ]
543
+ });
544
+ if (settingsAction === 'full') {
545
+ await runSetup();
546
+ return;
547
+ }
548
+ if (settingsAction === 'tier') {
549
+ const { newTier } = await enquirer.prompt({
550
+ type: 'select',
551
+ name: 'newTier',
552
+ message: 'Select experience tier:',
553
+ choices: [
554
+ { name: 'express', message: `${chalk.yellow('⚡')} Express — Fast & reliable (Llama 8B, Mistral 7B)` },
555
+ { name: 'standard', message: `${chalk.blue('📊')} Standard — Balanced depth (Gemini Flash, GPT-4o Mini)` },
556
+ { name: 'full', message: `${chalk.magenta('🚀')} Full — Maximum depth (GPT-4o, Claude, Gemini Pro)` },
557
+ ],
558
+ initial: ['express', 'standard', 'full'].indexOf(tier)
559
+ });
560
+ config.tier = newTier;
561
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
562
+ console.log(chalk.green(`\n✓ Tier changed to ${getTierBadge(newTier)}.\n`));
563
+ continue;
564
+ }
565
+ if (settingsAction === 'mode') {
566
+ const { newMode } = await enquirer.prompt({
567
+ type: 'select',
568
+ name: 'newMode',
569
+ message: 'Select operational mode:',
570
+ choices: [
571
+ { name: 'audit', message: `${chalk.green('🔒')} Audit Mode (Safe / Read-only)` },
572
+ { name: 'launch', message: `${chalk.red('🔓')} Launch Mode (Read-Write)` },
573
+ ],
574
+ initial: config.mode === 'launch' ? 1 : 0
575
+ });
576
+ config.mode = newMode;
577
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
578
+ console.log(chalk.green(`\n✓ Mode changed to ${newMode === 'launch' ? 'Launch' : 'Audit'}.\n`));
579
+ continue;
580
+ }
581
+ continue;
307
582
  }
308
583
  if (action === 'doctor') {
309
584
  await runDoctor();
@@ -334,86 +609,473 @@ async function main() {
334
609
  continue;
335
610
  }
336
611
  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.';
612
+ let auditPrompt = '';
613
+ // Express tier: skip platform sub-menu entirely — just go
614
+ if (tier === 'express') {
615
+ if (!config.metaToken && !config.connectGoogle) {
616
+ console.log('');
617
+ console.log(chalk.yellow(' ⚠️ No ad platforms connected.'));
618
+ console.log(chalk.gray(' Paste your campaign data in the chat, or run `openads setup` to connect.\n'));
619
+ auditPrompt = 'Quick audit. No platforms connected — ask me to paste campaign data, then flag issues as 🔴🟡🟢 and give 3 action items.';
620
+ }
621
+ else if (config.connectGoogle && config.metaToken) {
622
+ auditPrompt = 'Quick audit of all my connected ad campaigns. Flag issues as 🔴 Critical, 🟡 Warning, 🟢 Opportunity. Give me the top 3 actions.';
623
+ }
624
+ else if (config.connectGoogle) {
625
+ auditPrompt = 'Quick audit of my Google Ads campaigns. Flag issues as 🔴🟡🟢 and give 3 actions.';
626
+ }
627
+ else {
628
+ auditPrompt = 'Quick audit of my Meta Ads campaigns. Flag issues as 🔴🟡🟢 and give 3 actions.';
629
+ }
630
+ // Standard/Full: platform selection sub-menu
340
631
  }
341
- else if (config.metaToken && config.connectGoogle) {
342
- auditPrompt = 'Perform a comprehensive multi-platform campaign audit of my connected ad accounts.';
632
+ else {
633
+ if (!config.metaToken && !config.connectGoogle) {
634
+ console.log('');
635
+ console.log(chalk.yellow(' ⚠️ No ad platforms connected.'));
636
+ console.log(chalk.gray(' Connect Google Ads or Meta Ads via `openads setup` for live data.'));
637
+ console.log(chalk.gray(' You can still ask questions about your campaigns by pasting data into the chat.\n'));
638
+ auditPrompt = 'Audit my ad campaigns. No platforms are connected, so ask me to paste my campaign data.';
639
+ }
640
+ else if (config.metaToken && config.connectGoogle) {
641
+ const { auditPlatform } = await enquirer.prompt({
642
+ type: 'select',
643
+ name: 'auditPlatform',
644
+ message: chalk.bold('Which platform would you like to audit?'),
645
+ choices: [
646
+ { name: 'both', message: `${chalk.cyan('🔍')} Both platforms (Google Ads + Meta Ads)` },
647
+ { name: 'google', message: `${chalk.cyan('📊')} Google Ads only` },
648
+ { name: 'meta', message: `${chalk.cyan('📱')} Meta Ads only` },
649
+ { name: 'back', message: chalk.gray('← Back') },
650
+ ]
651
+ });
652
+ if (auditPlatform === 'back')
653
+ continue;
654
+ if (auditPlatform === 'google')
655
+ auditPrompt = 'Audit my Google Ads campaigns. Focus on keyword quality, budget waste, bid strategy, and RSA performance.';
656
+ else if (auditPlatform === 'meta')
657
+ auditPrompt = 'Audit my Meta Ads campaigns. Focus on creative fatigue, ABO vs CBO, ROAS trends, and audience overlap.';
658
+ else
659
+ auditPrompt = 'Audit all my connected ad accounts — Google Ads and Meta Ads.';
660
+ }
661
+ else if (config.connectGoogle) {
662
+ auditPrompt = 'Audit my Google Ads campaigns. Focus on keyword quality, budget waste, bid strategy, and RSA performance.';
663
+ }
664
+ else {
665
+ auditPrompt = 'Audit my Meta Ads campaigns. Focus on creative fatigue, ABO vs CBO, ROAS trends, and audience overlap.';
666
+ }
343
667
  }
344
668
  finalArgs = [auditPrompt];
669
+ selectedAction = 'audit';
345
670
  }
346
671
  else if (action === 'autoresearch') {
347
- // ── Autoresearch Submenu ─────────────────────────────────
348
- console.log();
349
- const { arAction } = await enquirer.prompt({
350
- type: 'select',
351
- name: 'arAction',
352
- message: chalk.bold('🔬 Autoresearch — What do you want to do?'),
353
- choices: [
354
- // DISCOVER
355
- { name: 'ar-plan', message: `${chalk.yellow('── DISCOVER ──────────────────────────')}` },
356
- { name: 'ar-plan', message: `${chalk.cyan('📋')} Plan my experiment ${chalk.gray('Figure out what to test & how to measure')}` },
357
- { name: 'ar-improve', message: `${chalk.cyan('🧠')} Research ICP & growth ${chalk.gray('Find opportunities from ICP, competitors, market')}` },
358
- { name: 'ar-learn', message: `${chalk.cyan('💡')} Learn from the market ${chalk.gray('Competitive intel — analyze what others do')}` },
359
- // GENERATE
360
- { name: 'ar-core', message: `${chalk.green('── GENERATE ──────────────────────────')}` },
361
- { name: 'ar-core', message: `${chalk.cyan('🎯')} Generate new hypotheses ${chalk.gray('Create & iterate on headlines, copy, pages, hooks')}` },
362
- // VALIDATE
363
- { name: 'ar-predict', message: `${chalk.blue('── VALIDATE ──────────────────────────')}` },
364
- { name: 'ar-predict', message: `${chalk.cyan('🔮')} Get expert predictions ${chalk.gray('5 marketing experts debate your idea')}` },
365
- { name: 'ar-probe', message: `${chalk.cyan('🔍')} Stress-test my brief ${chalk.gray('8 personas interrogate your campaign plan')}` },
366
- { name: 'ar-reason', message: `${chalk.cyan('⚖️')} Debate a strategy call ${chalk.gray('Adversarial debate: broad vs niche, video vs static')}` },
367
- { name: 'ar-scenario', message: `${chalk.cyan('🎭')} Run what-if scenarios ${chalk.gray('Competitor copies us? Algorithm changes? Budget cuts?')}` },
368
- // ANALYZE
369
- { name: 'ar-evals', message: `${chalk.magenta('── ANALYZE ───────────────────────────')}` },
370
- { name: 'ar-evals', message: `${chalk.cyan('📊')} Analyze past results ${chalk.gray('Trends, plateaus, patterns in experiment data')}` },
371
- { name: 'ar-debug', message: `${chalk.cyan('🐛')} Debug underperformance ${chalk.gray('Why is this campaign/page/funnel failing?')}` },
372
- // FIX
373
- { name: 'ar-fix', message: `${chalk.red('── FIX ───────────────────────────────')}` },
374
- { name: 'ar-fix', message: `${chalk.cyan('🔨')} Fix issues one-by-one ${chalk.gray('Tracking, compliance, character limits, branding')}` },
375
- { name: 'ar-security', message: `${chalk.cyan('🛡️')} Brand safety audit ${chalk.gray('Trademark, regulatory, misleading claims')}` },
376
- // SHIP
377
- { name: 'ar-ship', message: `${chalk.cyan('── SHIP ──────────────────────────────')}` },
378
- { name: 'ar-ship', message: `${chalk.cyan('🚀')} Prepare assets to ship ${chalk.gray('Format for platform specs, deployment brief, QA')}` },
379
- // BACK
380
- { name: 'ar-back', message: `${chalk.gray('←')} Back to main menu` },
381
- ]
382
- });
383
- if (arAction === 'ar-back') {
672
+ let inArMenu = true;
673
+ let arAction = '';
674
+ while (inArMenu) {
675
+ console.clear();
676
+ console.log('');
677
+ console.log(openadsGradient(LOGO));
678
+ console.log('');
679
+ console.log(boxen(statusLines, {
680
+ padding: { top: 1, bottom: 1, left: 2, right: 2 },
681
+ margin: { top: 0, bottom: 1, left: 2, right: 2 },
682
+ borderStyle: 'round',
683
+ borderColor: 'cyan',
684
+ dimBorder: true,
685
+ }));
686
+ // ── Express tier: flat, simple menu ─────────────────────
687
+ if (tier === 'express') {
688
+ const { opt } = await enquirer.prompt({
689
+ type: 'select',
690
+ name: 'opt',
691
+ message: chalk.bold('🔬 Autoresearch What do you want to do?'),
692
+ choices: [
693
+ { name: 'ar-core', message: `${chalk.green('🎯')} Generate new ideas — Run the core hypothesis loop` },
694
+ { name: 'ar-plan', message: `${chalk.cyan('📋')} Plan my experiment — Figure out what to test & how` },
695
+ { name: 'ar-debug', message: `${chalk.yellow('🐛')} Debug underperformance — Diagnose drops & issues` },
696
+ { name: 'ar-fix', message: `${chalk.red('🔨')} Fix issues — Character limits, pixel breaks` },
697
+ { name: 'back', message: `${chalk.gray('← Back to main menu')}` },
698
+ ]
699
+ });
700
+ if (opt === 'back')
701
+ break;
702
+ arAction = opt;
703
+ inArMenu = false;
704
+ // ── Standard tier: 5 phases, 8 commands ─────────────────
705
+ }
706
+ else if (tier === 'standard') {
707
+ const { arPhase } = await enquirer.prompt({
708
+ type: 'select',
709
+ name: 'arPhase',
710
+ message: chalk.bold('🔬 Autoresearch — Select a Phase:'),
711
+ choices: [
712
+ { name: 'discover', message: `${chalk.yellow('📋 Discover')} — Plan experiments & research` },
713
+ { name: 'generate', message: `${chalk.green('🎯 Generate')} — Core autonomous hypothesis loop` },
714
+ { name: 'analyze', message: `${chalk.magenta('📊 Analyze')} — Inspect past performance` },
715
+ { name: 'fix', message: `${chalk.red('🔨 Fix')} — Fix issues & compliance` },
716
+ { name: 'ship', message: `${chalk.cyan('🚀 Ship')} — Format assets for deployment` },
717
+ { name: 'back', message: `${chalk.gray('← Back to main menu')}` },
718
+ ]
719
+ });
720
+ if (arPhase === 'back')
721
+ break;
722
+ if (arPhase === 'discover') {
723
+ const res = await enquirer.prompt({
724
+ type: 'select', name: 'opt',
725
+ message: chalk.bold('📋 Discover:'),
726
+ choices: [
727
+ { name: 'ar-plan', message: `${chalk.cyan('📋')} Plan my experiment — What to test & how to measure` },
728
+ { name: 'ar-improve', message: `${chalk.cyan('🧠')} Research ICP & growth — Growth opportunities from ICP & market` },
729
+ { name: 'ar-learn', message: `${chalk.cyan('💡')} Learn from the market — Competitive intel on pages & copy` },
730
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
731
+ ]
732
+ });
733
+ if (res.opt !== 'back') {
734
+ arAction = res.opt;
735
+ inArMenu = false;
736
+ }
737
+ }
738
+ else if (arPhase === 'generate') {
739
+ arAction = 'ar-core';
740
+ inArMenu = false;
741
+ }
742
+ else if (arPhase === 'analyze') {
743
+ const res = await enquirer.prompt({
744
+ type: 'select', name: 'opt',
745
+ message: chalk.bold('📊 Analyze:'),
746
+ choices: [
747
+ { name: 'ar-evals', message: `${chalk.cyan('📊')} Analyze past results — Find trends & plateaus` },
748
+ { name: 'ar-debug', message: `${chalk.cyan('🐛')} Debug underperformance — Root cause diagnostic` },
749
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
750
+ ]
751
+ });
752
+ if (res.opt !== 'back') {
753
+ arAction = res.opt;
754
+ inArMenu = false;
755
+ }
756
+ }
757
+ else if (arPhase === 'fix') {
758
+ const res = await enquirer.prompt({
759
+ type: 'select', name: 'opt',
760
+ message: chalk.bold('🔨 Fix:'),
761
+ choices: [
762
+ { name: 'ar-fix', message: `${chalk.cyan('🔨')} Fix issues one-by-one — Character limits, pixel breaks` },
763
+ { name: 'ar-security', message: `${chalk.cyan('🛡️')} Brand safety audit — Reg (FTC/GDPR), trademark` },
764
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
765
+ ]
766
+ });
767
+ if (res.opt !== 'back') {
768
+ arAction = res.opt;
769
+ inArMenu = false;
770
+ }
771
+ }
772
+ else if (arPhase === 'ship') {
773
+ arAction = 'ar-ship';
774
+ inArMenu = false;
775
+ }
776
+ // ── Full tier: all 6 phases, all 12 commands ────────────
777
+ }
778
+ else {
779
+ const { arPhase } = await enquirer.prompt({
780
+ type: 'select',
781
+ name: 'arPhase',
782
+ message: chalk.bold('🔬 Autoresearch — Select a Phase:'),
783
+ choices: [
784
+ { name: 'discover', message: `${chalk.yellow('📋 Discover')} — Plan experiments, research ICP, & competitive intel` },
785
+ { name: 'generate', message: `${chalk.green('🎯 Generate')} — Launch the core autonomous loop to test ideas` },
786
+ { name: 'validate', message: `${chalk.blue('🔮 Validate')} — Expert predictions, stress-tests, & debates` },
787
+ { name: 'analyze', message: `${chalk.magenta('📊 Analyze')} — Inspect past performance & debug drops` },
788
+ { name: 'fix', message: `${chalk.red('🔨 Fix')} — Fix issues & run brand safety audits` },
789
+ { name: 'ship', message: `${chalk.cyan('🚀 Ship')} — Format final assets & build deployment briefs` },
790
+ { name: 'back', message: `${chalk.gray('← Back to main menu')}` },
791
+ ]
792
+ });
793
+ if (arPhase === 'back')
794
+ break;
795
+ if (arPhase === 'discover') {
796
+ const res = await enquirer.prompt({
797
+ type: 'select', name: 'opt',
798
+ message: chalk.bold('📋 Discover Options:'),
799
+ choices: [
800
+ { name: 'ar-plan', message: `${chalk.cyan('📋')} Plan my experiment — Figure out what to test & how to measure` },
801
+ { name: 'ar-improve', message: `${chalk.cyan('🧠')} Research ICP & growth — Find growth opportunities from ICP & market` },
802
+ { name: 'ar-learn', message: `${chalk.cyan('💡')} Learn from the market — Competitive intel on pages & copy` },
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 === 'generate') {
812
+ const res = await enquirer.prompt({
813
+ type: 'select', name: 'opt',
814
+ message: chalk.bold('🎯 Generate Options:'),
815
+ choices: [
816
+ { name: 'ar-core', message: `${chalk.cyan('🎯')} Generate new hypotheses — Run core loop to generate & score ideas` },
817
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
818
+ ]
819
+ });
820
+ if (res.opt !== 'back') {
821
+ arAction = res.opt;
822
+ inArMenu = false;
823
+ }
824
+ }
825
+ else if (arPhase === 'validate') {
826
+ const res = await enquirer.prompt({
827
+ type: 'select', name: 'opt',
828
+ message: chalk.bold('🔮 Validate Options:'),
829
+ choices: [
830
+ { name: 'ar-predict', message: `${chalk.cyan('🔮')} Get expert predictions — 5 expert personas debate your idea` },
831
+ { name: 'ar-probe', message: `${chalk.cyan('🔍')} Stress-test my brief — 8 personas stress-test your strategy` },
832
+ { name: 'ar-reason', message: `${chalk.cyan('⚖️')} Debate a strategy call — Adversarial debate on key binary calls` },
833
+ { name: 'ar-scenario', message: `${chalk.cyan('🎭')} Run what-if scenarios — Stress-test plans against disruptions` },
834
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
835
+ ]
836
+ });
837
+ if (res.opt !== 'back') {
838
+ arAction = res.opt;
839
+ inArMenu = false;
840
+ }
841
+ }
842
+ else if (arPhase === 'analyze') {
843
+ const res = await enquirer.prompt({
844
+ type: 'select', name: 'opt',
845
+ message: chalk.bold('📊 Analyze Options:'),
846
+ choices: [
847
+ { name: 'ar-evals', message: `${chalk.cyan('📊')} Analyze past results — Find trends & plateaus in experiment data` },
848
+ { name: 'ar-debug', message: `${chalk.cyan('🐛')} Debug underperformance — Root cause diagnostic for drops` },
849
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
850
+ ]
851
+ });
852
+ if (res.opt !== 'back') {
853
+ arAction = res.opt;
854
+ inArMenu = false;
855
+ }
856
+ }
857
+ else if (arPhase === 'fix') {
858
+ const res = await enquirer.prompt({
859
+ type: 'select', name: 'opt',
860
+ message: chalk.bold('🔨 Fix Options:'),
861
+ choices: [
862
+ { name: 'ar-fix', message: `${chalk.cyan('🔨')} Fix issues one-by-one — Crush character limits, pixel breaks` },
863
+ { name: 'ar-security', message: `${chalk.cyan('🛡️')} Brand safety audit — Reg (FTC/GDPR), trademark compliance` },
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
+ else if (arPhase === 'ship') {
873
+ const res = await enquirer.prompt({
874
+ type: 'select', name: 'opt',
875
+ message: chalk.bold('🚀 Ship Options:'),
876
+ choices: [
877
+ { name: 'ar-ship', message: `${chalk.cyan('🚀')} Prepare assets to ship — Format for platforms & build deployment brief` },
878
+ { name: 'back', message: `${chalk.gray('← Back to phases')}` },
879
+ ]
880
+ });
881
+ if (res.opt !== 'back') {
882
+ arAction = res.opt;
883
+ inArMenu = false;
884
+ }
885
+ }
886
+ }
887
+ }
888
+ if (!arAction) {
384
889
  continue;
385
890
  }
386
- const arTriggerMap = {
387
- 'ar-core': 'Launch the Autoresearch core loop to generate new testable marketing hypotheses.',
388
- 'ar-plan': 'Help me plan a marketing experiment with Autoresearch.',
389
- 'ar-improve': 'Research my ICP and find growth opportunities with Autoresearch.',
390
- 'ar-learn': 'Analyze my competitors and market positioning with Autoresearch.',
391
- 'ar-predict': 'Assemble 5 marketing expert personas to evaluate my hypothesis with Autoresearch.',
392
- 'ar-probe': 'Have 8 marketing personas stress-test my campaign brief with Autoresearch.',
393
- 'ar-reason': 'Run an adversarial debate on this marketing strategy decision with Autoresearch.',
394
- 'ar-scenario': 'Generate what-if scenarios for my marketing plan with Autoresearch.',
395
- 'ar-evals': 'Analyze my past marketing experiment results with Autoresearch.',
396
- 'ar-debug': 'Debug why my marketing campaign is underperforming with Autoresearch.',
397
- 'ar-fix': 'Fix these known marketing asset issues one-by-one with Autoresearch.',
398
- 'ar-security': 'Run a brand safety and compliance audit on my marketing assets with Autoresearch.',
399
- 'ar-ship': 'Prepare my winning marketing assets for deployment with Autoresearch.',
400
- };
401
- finalArgs = [arTriggerMap[arAction] || ''];
891
+ let triggerMsg = '';
892
+ selectedArAction = arAction;
893
+ if (arAction === 'ar-core') {
894
+ let goal = '';
895
+ let metric = '';
896
+ let scope = '';
897
+ let csvPath = '';
898
+ const activeConfigPath = path.join(CONFIG_DIR, 'active-experiment.json');
899
+ let useActive = false;
900
+ if (fs.existsSync(activeConfigPath)) {
901
+ try {
902
+ const activeConfig = JSON.parse(fs.readFileSync(activeConfigPath, 'utf8'));
903
+ console.log(chalk.cyan('\n Found an active experiment plan:'));
904
+ console.log(` ${chalk.bold('Goal')}: ${activeConfig.goal}`);
905
+ console.log(` ${chalk.bold('Metric')}: ${activeConfig.metric}`);
906
+ console.log(` ${chalk.bold('Scope')}: ${activeConfig.scope || 'None'}`);
907
+ if (activeConfig.csvPath) {
908
+ console.log(` ${chalk.bold('Data')}: ${activeConfig.csvPath}`);
909
+ }
910
+ console.log('');
911
+ const { confirmActive } = await enquirer.prompt({
912
+ type: 'confirm',
913
+ name: 'confirmActive',
914
+ message: 'Would you like to run the autonomous loop on this active plan?',
915
+ initial: true
916
+ });
917
+ if (confirmActive) {
918
+ useActive = true;
919
+ goal = activeConfig.goal;
920
+ metric = activeConfig.metric;
921
+ scope = activeConfig.scope || '';
922
+ csvPath = activeConfig.csvPath || '';
923
+ }
924
+ }
925
+ catch (e) { }
926
+ }
927
+ if (!useActive) {
928
+ console.log(chalk.cyan('\n Let\'s configure your Autoresearch loop:'));
929
+ const answers = await enquirer.prompt([
930
+ {
931
+ type: 'input',
932
+ name: 'goal',
933
+ message: 'What is your experiment Goal? (e.g., Optimize landing page conversion rate)',
934
+ validate: (val) => val.trim() ? true : 'Goal cannot be empty.'
935
+ },
936
+ {
937
+ type: 'input',
938
+ name: 'metric',
939
+ message: 'What is your primary Metric? (e.g., CVR, CTR, ROAS)',
940
+ validate: (val) => val.trim() ? true : 'Metric cannot be empty.'
941
+ },
942
+ {
943
+ type: 'input',
944
+ name: 'scope',
945
+ message: 'Any Scope constraints? (e.g., No discounts, premium tone - optional)',
946
+ initial: ''
947
+ },
948
+ {
949
+ type: 'input',
950
+ name: 'csvPath',
951
+ message: 'Path to prior experiment CSV/data file (optional - press Enter to skip)',
952
+ initial: '',
953
+ validate: (val) => {
954
+ if (!val.trim())
955
+ return true;
956
+ const resolved = val.startsWith('~') ? val.replace('~', os.homedir()) : path.resolve(val);
957
+ if (fs.existsSync(resolved))
958
+ return true;
959
+ // Smart check standard folders
960
+ const baseName = path.basename(resolved);
961
+ const standardDirs = [
962
+ path.join(os.homedir(), 'Desktop'),
963
+ path.join(os.homedir(), 'Downloads'),
964
+ path.join(os.homedir(), 'Documents')
965
+ ];
966
+ for (const dir of standardDirs) {
967
+ if (fs.existsSync(path.join(dir, baseName)))
968
+ return true;
969
+ }
970
+ return `File not found at: ${resolved}. Please check the path.`;
971
+ }
972
+ }
973
+ ]);
974
+ goal = answers.goal;
975
+ metric = answers.metric;
976
+ scope = answers.scope || '';
977
+ let rawPath = answers.csvPath ? answers.csvPath.trim() : '';
978
+ if (rawPath) {
979
+ let resolved = rawPath.startsWith('~') ? rawPath.replace('~', os.homedir()) : path.resolve(rawPath);
980
+ if (fs.existsSync(resolved)) {
981
+ csvPath = resolved;
982
+ }
983
+ else {
984
+ // Resolve from standard directories
985
+ const baseName = path.basename(resolved);
986
+ const standardDirs = [
987
+ path.join(os.homedir(), 'Desktop'),
988
+ path.join(os.homedir(), 'Downloads'),
989
+ path.join(os.homedir(), 'Documents')
990
+ ];
991
+ let found = false;
992
+ for (const dir of standardDirs) {
993
+ const checkPath = path.join(dir, baseName);
994
+ if (fs.existsSync(checkPath)) {
995
+ csvPath = checkPath;
996
+ console.log(chalk.cyan(`\n 💡 Smart-resolved file name to standard directory:`));
997
+ console.log(` ${chalk.green(csvPath)}\n`);
998
+ found = true;
999
+ break;
1000
+ }
1001
+ }
1002
+ if (!found)
1003
+ csvPath = resolved;
1004
+ }
1005
+ }
1006
+ else {
1007
+ csvPath = '';
1008
+ }
1009
+ // Save the config to disk
1010
+ fs.writeFileSync(activeConfigPath, JSON.stringify({ goal, metric, scope, csvPath }, null, 2));
1011
+ }
1012
+ // Concise trigger — system prompt + skill file have the full instructions.
1013
+ // Short user message = more output tokens for the model to use.
1014
+ triggerMsg = `Run 3 Autoresearch cycles. Goal: ${goal}. Metric: ${metric}.`;
1015
+ if (scope)
1016
+ triggerMsg += ` Scope: ${scope}.`;
1017
+ if (csvPath)
1018
+ triggerMsg += ` Read the prior data CSV at ${csvPath} first, extract patterns, then generate new hypotheses.`;
1019
+ else
1020
+ triggerMsg += ` No prior data — generate hypotheses from scratch using product context.`;
1021
+ }
1022
+ else {
1023
+ // Concise triggers — the skill file has all the detailed instructions.
1024
+ // Short message = more output tokens for the model's response.
1025
+ const arTriggerMap = {
1026
+ 'ar-plan': 'Plan a marketing experiment. Walk me through Goal → Metric → Scope → Prior Data.',
1027
+ 'ar-improve': 'Research my ICP and identify growth opportunities.',
1028
+ 'ar-learn': 'Analyze my competitors and extract messaging gaps.',
1029
+ 'ar-predict': 'Have 5 expert marketing personas evaluate my idea and give a go/no-go verdict.',
1030
+ 'ar-probe': 'Stress-test my campaign brief with 8 personas.',
1031
+ 'ar-reason': 'Run an adversarial debate on my key strategy decision.',
1032
+ 'ar-scenario': 'Generate what-if scenarios for my marketing plan.',
1033
+ 'ar-evals': 'Analyze my past experiment results and recommend the next test.',
1034
+ 'ar-debug': 'Debug why my campaign is underperforming. Find the root cause.',
1035
+ 'ar-fix': 'Fix my marketing asset issues one by one.',
1036
+ 'ar-security': 'Run a brand safety and compliance audit on my marketing assets.',
1037
+ 'ar-ship': 'Prepare my winning assets for deployment.',
1038
+ };
1039
+ triggerMsg = arTriggerMap[arAction] || '';
1040
+ }
1041
+ finalArgs = [triggerMsg];
1042
+ selectedAction = 'autoresearch';
402
1043
  }
403
1044
  else {
404
- const actionMap = {
1045
+ // ── Tier-aware action prompts ────────────────────────────
1046
+ const expressActionMap = {
1047
+ chat: [],
1048
+ copy: ['Write 5 ad headlines and 3 primary text options for my product. Follow the skill file format.'],
1049
+ organic: ['Write an organic social post for my product. Follow the organic-social-express skill format.'],
1050
+ gtm: ['Create a 1-page GTM brief for my product. Follow the skill file format.'],
1051
+ };
1052
+ const standardActionMap = {
405
1053
  chat: [],
406
1054
  copy: ['Help me generate high-performing ad copy for my campaigns.'],
407
- gtm: ['Help me build a comprehensive Go-To-Market strategy for my product.'],
1055
+ organic: ['Help me draft high-performing organic social content for Instagram, LinkedIn, TikTok, or Facebook.'],
1056
+ gtm: ['Help me build a go-to-market strategy for my product.'],
1057
+ };
1058
+ const fullActionMap = {
1059
+ chat: [],
1060
+ 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.'],
1061
+ organic: ['Help me plan, draft, or publish organic social content. If my Facebook Page is connected, check my recent posts first to avoid repeating topics, and format it natively for publication.'],
1062
+ 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.'],
408
1063
  };
1064
+ const actionMap = tier === 'express' ? expressActionMap : tier === 'full' ? fullActionMap : standardActionMap;
409
1065
  finalArgs = actionMap[action] || [];
1066
+ selectedAction = action;
410
1067
  }
411
1068
  break;
412
1069
  }
413
1070
  }
414
- // ─── Loading Spinner ────────────────────────────────────────────
1071
+ // ─── Loading Spinner ─────────────────────────────────────────
1072
+ const loadingMessages = {
1073
+ express: 'Launching express agent...',
1074
+ standard: 'Starting marketing agent...',
1075
+ full: 'Starting full marketing intelligence agent...',
1076
+ };
415
1077
  const spinner = ora({
416
- text: chalk.cyan('Starting marketing agent...'),
1078
+ text: chalk.cyan(loadingMessages[tier] || 'Starting marketing agent...'),
417
1079
  spinner: 'dots12',
418
1080
  color: 'cyan',
419
1081
  }).start();
@@ -427,14 +1089,20 @@ async function main() {
427
1089
  // System prompt flag
428
1090
  const systemPromptPath = path.join(CONFIG_DIR, 'agent', 'SYSTEM.md');
429
1091
  piArgsRaw.push('--system-prompt', systemPromptPath);
430
- // Skills directories
1092
+ // Skills directories — selective loading based on action
431
1093
  const skillsDir = path.join(pkgDir, 'skills');
432
1094
  const templatesDir = path.join(pkgDir, 'templates');
433
1095
  // Inject product context as a skill directory
434
1096
  const contextDir = injectProductContext(config);
1097
+ // Load only the skills relevant to the selected action and tier
1098
+ const relevantSkills = getRelevantSkills(selectedAction, selectedArAction, skillsDir, tier);
1099
+ const skillArgs = [];
1100
+ for (const skill of relevantSkills) {
1101
+ skillArgs.push('--skill', skill);
1102
+ }
435
1103
  const piArgs = [
436
1104
  ...piArgsRaw,
437
- '--skill', skillsDir,
1105
+ ...skillArgs,
438
1106
  '--prompt-template', templatesDir,
439
1107
  ...(contextDir ? ['--skill', contextDir] : []),
440
1108
  ...finalArgs
@@ -445,6 +1113,15 @@ async function main() {
445
1113
  ...process.env,
446
1114
  NODE_NO_WARNINGS: '1',
447
1115
  };
1116
+ // Signal to the MCP extension whether to skip tool registration.
1117
+ // Express tier and autoresearch skip MCP entirely.
1118
+ if (selectedAction === 'autoresearch' || tier === 'express') {
1119
+ env.OPENADS_SKIP_MCP = '1';
1120
+ }
1121
+ else {
1122
+ delete env.OPENADS_SKIP_MCP;
1123
+ }
1124
+ env.OPENADS_TIER = tier;
448
1125
  if (config.apiKey && config.apiKey !== 'dummy-key') {
449
1126
  const envVarName = getApiKeyEnvVar(cleanProvider);
450
1127
  env[envVarName] = config.apiKey;
@@ -467,8 +1144,9 @@ async function main() {
467
1144
  catch (e) { }
468
1145
  }
469
1146
  settings.quietStartup = true;
470
- // System prompt — makes the agent behave as OpenAds
471
- const systemPrompt = buildSystemPrompt(config);
1147
+ // System prompt — mode-aware and tier-aware
1148
+ const promptMode = (selectedAction === 'autoresearch') ? 'autoresearch' : 'default';
1149
+ const systemPrompt = buildSystemPrompt(config, promptMode, tier, { arAction: selectedArAction });
472
1150
  settings.systemPrompt = systemPrompt;
473
1151
  fs.writeFileSync(systemPromptPath, systemPrompt);
474
1152
  // Resilient retry settings for free-tier rate limits
@@ -499,12 +1177,18 @@ async function main() {
499
1177
  apiKey: "local-key-placeholder",
500
1178
  compat: {
501
1179
  supportsDeveloperRole: false,
502
- supportsReasoningEffort: false
1180
+ supportsReasoningEffort: false,
1181
+ supportsUsageInStreaming: false,
1182
+ requiresToolResultName: true,
1183
+ maxTokensField: "max_tokens"
503
1184
  },
504
1185
  models: [
505
1186
  {
506
1187
  id: modelIdForPi,
507
- name: `${modelIdForPi} (Local)`
1188
+ name: `${modelIdForPi} (Local)`,
1189
+ reasoning: false,
1190
+ contextWindow: 128000,
1191
+ maxTokens: 8192
508
1192
  }
509
1193
  ]
510
1194
  }