feat: add multi-target export profiles for one-click multi-format export

Export Profiles bundle targets into named groups, enabling instant
multi-format export (e.g. XML for Claude + Markdown for ChatGPT +
Plain for Gemini) via a single "Export context (multi-target)" command.

- Add ExportProfile interface, generateProfileId, BUILTIN_PROFILES
- Add per-target outputPath override for file output location
- Add export-multi-target command with active profile resolution
- Add ExportProfileModal for creating/editing profiles
- Add profile management UI in Settings > Output
- Add profile dropdown in generator modal for target pre-selection
- Clean up stale profile references on target deletion

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Luca Oelfke 2026-02-11 14:03:45 +01:00
parent 40ce34c9b4
commit 45a0de063e
6 changed files with 467 additions and 9 deletions

213
src/export-profile-modal.ts Normal file
View file

@ -0,0 +1,213 @@
import { App, Modal, Notice, Setting } from 'obsidian';
import PromptfirePlugin from './main';
import {
ExportProfile,
generateProfileId,
getTargetIcon,
} from './targets';
export class ExportProfileModal extends Modal {
plugin: PromptfirePlugin;
profile: ExportProfile | null;
isNew: boolean;
onSave: () => void;
// Form state
name: string = '';
description: string = '';
selectedTargetIds: Set<string> = new Set();
primaryTargetId: string | null = null;
constructor(
app: App,
plugin: PromptfirePlugin,
profile: ExportProfile | null,
onSave: () => void
) {
super(app);
this.plugin = plugin;
this.profile = profile;
this.isNew = profile === null;
this.onSave = onSave;
if (profile) {
this.name = profile.name;
this.description = profile.description || '';
this.selectedTargetIds = new Set(profile.targetIds);
this.primaryTargetId = profile.primaryTargetId;
}
}
onOpen() {
const { contentEl } = this;
contentEl.empty();
contentEl.addClass('export-profile-modal');
contentEl.createEl('h2', { text: this.isNew ? 'Add Export Profile' : 'Edit Export Profile' });
// Name
new Setting(contentEl)
.setName('Name')
.setDesc('Display name for this profile')
.addText(text => text
.setPlaceholder('e.g. Full LLM Export')
.setValue(this.name)
.onChange(v => this.name = v));
// Description
new Setting(contentEl)
.setName('Description')
.setDesc('Optional description')
.addTextArea(text => {
text.setPlaceholder('Describe what this profile does...')
.setValue(this.description)
.onChange(v => this.description = v);
text.inputEl.rows = 2;
text.inputEl.style.width = '100%';
});
// Target checklist
contentEl.createEl('h3', { text: 'Targets' });
const targets = this.plugin.settings.targets;
if (targets.length === 0) {
contentEl.createEl('p', {
text: 'No targets configured. Create targets first in Settings > Output.',
cls: 'setting-item-description',
});
} else {
const checklistContainer = contentEl.createDiv({ cls: 'profile-targets-checklist' });
checklistContainer.style.display = 'flex';
checklistContainer.style.flexDirection = 'column';
checklistContainer.style.gap = '8px';
checklistContainer.style.padding = '10px';
checklistContainer.style.backgroundColor = 'var(--background-secondary)';
checklistContainer.style.borderRadius = '4px';
checklistContainer.style.marginBottom = '15px';
for (const target of targets) {
const row = checklistContainer.createDiv();
row.style.display = 'flex';
row.style.alignItems = 'center';
row.style.gap = '10px';
const checkbox = row.createEl('input', { type: 'checkbox' });
checkbox.checked = this.selectedTargetIds.has(target.id);
checkbox.addEventListener('change', () => {
if (checkbox.checked) {
this.selectedTargetIds.add(target.id);
} else {
this.selectedTargetIds.delete(target.id);
if (this.primaryTargetId === target.id) {
this.primaryTargetId = null;
}
}
this.refreshPrimaryDropdown();
});
row.createEl('span', { text: getTargetIcon(target.format) });
const label = row.createEl('span', { text: target.name });
label.style.flex = '1';
const tokenInfo = row.createEl('span', {
text: `${target.maxTokens.toLocaleString()} tokens · ${target.format}`
});
tokenInfo.style.fontSize = '11px';
tokenInfo.style.color = 'var(--text-muted)';
}
}
// Primary target dropdown
this.primaryDropdownContainer = contentEl.createDiv();
this.refreshPrimaryDropdown();
// Buttons
const buttonContainer = contentEl.createDiv();
buttonContainer.style.display = 'flex';
buttonContainer.style.justifyContent = 'flex-end';
buttonContainer.style.gap = '10px';
buttonContainer.style.marginTop = '20px';
const cancelBtn = buttonContainer.createEl('button', { text: 'Cancel' });
cancelBtn.addEventListener('click', () => this.close());
const saveBtn = buttonContainer.createEl('button', { text: 'Save', cls: 'mod-cta' });
saveBtn.addEventListener('click', () => this.save());
}
private primaryDropdownContainer: HTMLElement | null = null;
private refreshPrimaryDropdown() {
if (!this.primaryDropdownContainer) return;
this.primaryDropdownContainer.empty();
const selectedTargets = this.plugin.settings.targets.filter(
t => this.selectedTargetIds.has(t.id)
);
if (selectedTargets.length === 0) return;
// Validate current primary is still in selection
if (this.primaryTargetId && !this.selectedTargetIds.has(this.primaryTargetId)) {
this.primaryTargetId = null;
}
new Setting(this.primaryDropdownContainer)
.setName('Primary target')
.setDesc('This target\'s output goes to clipboard; others are saved as files')
.addDropdown(dropdown => {
dropdown.addOption('', 'First selected target');
for (const target of selectedTargets) {
dropdown.addOption(target.id, target.name);
}
dropdown.setValue(this.primaryTargetId || '');
dropdown.onChange(v => this.primaryTargetId = v || null);
});
}
async save() {
if (!this.name.trim()) {
new Notice('Name is required');
return;
}
if (this.selectedTargetIds.size === 0) {
new Notice('Select at least one target');
return;
}
// Validate primary is in selection
if (this.primaryTargetId && !this.selectedTargetIds.has(this.primaryTargetId)) {
this.primaryTargetId = null;
}
const profileData: ExportProfile = {
id: this.profile?.id || generateProfileId(),
name: this.name.trim(),
description: this.description.trim() || undefined,
targetIds: Array.from(this.selectedTargetIds),
primaryTargetId: this.primaryTargetId,
};
if (this.isNew) {
this.plugin.settings.exportProfiles.push(profileData);
} else {
const index = this.plugin.settings.exportProfiles.findIndex(p => p.id === this.profile!.id);
if (index >= 0) {
this.plugin.settings.exportProfiles[index] = profileData;
}
}
await this.plugin.saveSettings();
new Notice(this.isNew ? 'Profile added' : 'Profile updated');
this.onSave();
this.close();
}
onClose() {
const { contentEl } = this;
contentEl.empty();
this.primaryDropdownContainer = null;
}
}

View file

@ -2,7 +2,7 @@ import { App, Modal, Notice, Setting, TFile, TFolder } from 'obsidian';
import PromptfirePlugin from './main'; import PromptfirePlugin from './main';
import { createFreetextSource, SourcePosition } from './sources'; import { createFreetextSource, SourcePosition } from './sources';
import { estimateTokens } from './history'; import { estimateTokens } from './history';
import { OutputTarget, OutputFormat, getTargetIcon, formatTokenCount } from './targets'; import { OutputTarget, ExportProfile, OutputFormat, getTargetIcon, formatTokenCount } from './targets';
interface FolderConfig { interface FolderConfig {
name: string; name: string;
@ -425,6 +425,29 @@ export class ContextGeneratorModal extends Modal {
if (this.plugin.settings.targets.length > 0) { if (this.plugin.settings.targets.length > 0) {
this.targetsHeadingEl = configZone.createEl('h3', { text: 'Output Targets' }); this.targetsHeadingEl = configZone.createEl('h3', { text: 'Output Targets' });
// Profile dropdown (only if profiles exist)
if (this.plugin.settings.exportProfiles.length > 0) {
new Setting(configZone)
.setName('Profile')
.setDesc('Pre-select targets from an export profile')
.addDropdown(dropdown => {
dropdown.addOption('', 'Manual selection');
for (const profile of this.plugin.settings.exportProfiles) {
dropdown.addOption(profile.id, profile.name);
}
dropdown.setValue('');
dropdown.onChange(v => {
if (v) {
const profile = this.plugin.settings.exportProfiles.find(p => p.id === v);
if (profile) {
this.selectedTargetIds = new Set(profile.targetIds);
this.onOpen();
}
}
});
});
}
const targetsContainer = configZone.createDiv({ cls: 'targets-checkboxes' }); const targetsContainer = configZone.createDiv({ cls: 'targets-checkboxes' });
targetsContainer.style.display = 'flex'; targetsContainer.style.display = 'flex';
targetsContainer.style.flexDirection = 'column'; targetsContainer.style.flexDirection = 'column';

View file

@ -18,6 +18,7 @@ import {
} from './presets'; } from './presets';
import { import {
OutputTarget, OutputTarget,
ExportProfile,
TargetExecutor, TargetExecutor,
TargetResult, TargetResult,
saveTargetToFile, saveTargetToFile,
@ -92,6 +93,12 @@ export default class PromptfirePlugin extends Plugin {
callback: () => this.copySmartContext() callback: () => this.copySmartContext()
}); });
this.addCommand({
id: 'export-multi-target',
name: 'Export context (multi-target)',
callback: () => this.exportMultiTarget()
});
this.addSettingTab(new PromptfireSettingTab(this.app, this)); this.addSettingTab(new PromptfireSettingTab(this.app, this));
} }
@ -116,6 +123,12 @@ export default class PromptfirePlugin extends Plugin {
); );
} }
} }
if (!loaded?.exportProfiles) {
this.settings.exportProfiles = [];
}
if (loaded?.activeProfileId === undefined) {
this.settings.activeProfileId = null;
}
} }
async saveSettings() { async saveSettings() {
@ -277,7 +290,8 @@ export default class PromptfirePlugin extends Plugin {
forceIncludeNote = false, forceIncludeNote = false,
temporaryFreetext?: string, temporaryFreetext?: string,
templateId?: string | null, templateId?: string | null,
targets?: OutputTarget[] targets?: OutputTarget[],
profilePrimaryTargetId?: string | null
) { ) {
const folder = this.app.vault.getAbstractFileByPath(this.settings.contextFolder); const folder = this.app.vault.getAbstractFileByPath(this.settings.contextFolder);
@ -412,7 +426,7 @@ export default class PromptfirePlugin extends Plugin {
// Process targets if provided // Process targets if provided
if (targets && targets.length > 0) { if (targets && targets.length > 0) {
await this.processTargets(combined, targets, historyMetadata, fileCount, sourceCount, templateName); await this.processTargets(combined, targets, historyMetadata, fileCount, sourceCount, templateName, profilePrimaryTargetId);
} else { } else {
// Copy and save to history (legacy mode) // Copy and save to history (legacy mode)
const copyAndSave = async () => { const copyAndSave = async () => {
@ -437,7 +451,8 @@ export default class PromptfirePlugin extends Plugin {
historyMetadata: Omit<HistoryMetadata, 'charCount' | 'estimatedTokens'>, historyMetadata: Omit<HistoryMetadata, 'charCount' | 'estimatedTokens'>,
fileCount: number, fileCount: number,
sourceCount: number, sourceCount: number,
templateName: string | null templateName: string | null,
overridePrimaryId?: string | null
) { ) {
const executor = new TargetExecutor(); const executor = new TargetExecutor();
const results: TargetResult[] = []; const results: TargetResult[] = [];
@ -449,7 +464,8 @@ export default class PromptfirePlugin extends Plugin {
} }
// Determine primary target // Determine primary target
const primaryId = this.settings.primaryTargetId || const primaryId = overridePrimaryId ??
this.settings.primaryTargetId ??
(targets.find(t => t.enabled)?.id ?? targets[0]?.id); (targets.find(t => t.enabled)?.id ?? targets[0]?.id);
const primaryResult = results.find(r => r.target.id === primaryId) || results[0]; const primaryResult = results.find(r => r.target.id === primaryId) || results[0];
@ -626,6 +642,36 @@ export default class PromptfirePlugin extends Plugin {
return checkAll(selection.headings); return checkAll(selection.headings);
} }
async exportMultiTarget() {
const profileId = this.settings.activeProfileId;
if (!profileId) {
new Notice('No active export profile. Set one in Settings > Output > Export Profiles.');
return;
}
const profile = this.settings.exportProfiles.find(p => p.id === profileId);
if (!profile) {
new Notice('Active export profile not found. Check Settings > Output > Export Profiles.');
return;
}
const targets = profile.targetIds
.map(id => this.settings.targets.find(t => t.id === id))
.filter((t): t is OutputTarget => t != null);
if (targets.length === 0) {
new Notice(`Profile "${profile.name}" has no valid targets.`);
return;
}
if (targets.length < profile.targetIds.length) {
const missing = profile.targetIds.length - targets.length;
new Notice(`${missing} target(s) in profile no longer exist and were skipped.`, 5000);
}
await this.copyContextToClipboard(false, undefined, undefined, targets, profile.primaryTargetId);
}
async copySmartContext() { async copySmartContext() {
if (!this.settings.intelligence.enabled) { if (!this.settings.intelligence.enabled) {
new Notice('Context intelligence is disabled. Enable it in Settings > Intelligence.'); new Notice('Context intelligence is disabled. Enable it in Settings > Intelligence.');

View file

@ -5,8 +5,9 @@ import { SourceModal } from './source-modal';
import { PromptTemplate, STARTER_TEMPLATES } from './templates'; import { PromptTemplate, STARTER_TEMPLATES } from './templates';
import { TemplateModal, TemplateImportExportModal } from './template-modal'; import { TemplateModal, TemplateImportExportModal } from './template-modal';
import { HistorySettings, DEFAULT_HISTORY_SETTINGS } from './history'; import { HistorySettings, DEFAULT_HISTORY_SETTINGS } from './history';
import { OutputTarget, BUILTIN_TARGETS, getTargetIcon } from './targets'; import { OutputTarget, ExportProfile, BUILTIN_TARGETS, BUILTIN_PROFILES, getTargetIcon } from './targets';
import { TargetModal } from './target-modal'; import { TargetModal } from './target-modal';
import { ExportProfileModal } from './export-profile-modal';
import { IntelligenceSettings, DEFAULT_INTELLIGENCE_SETTINGS } from './context-intelligence'; import { IntelligenceSettings, DEFAULT_INTELLIGENCE_SETTINGS } from './context-intelligence';
export interface PromptfireSettings { export interface PromptfireSettings {
@ -24,6 +25,8 @@ export interface PromptfireSettings {
targets: OutputTarget[]; targets: OutputTarget[];
primaryTargetId: string | null; primaryTargetId: string | null;
targetOutputFolder: string; targetOutputFolder: string;
exportProfiles: ExportProfile[];
activeProfileId: string | null;
lastSettingsTab: string; lastSettingsTab: string;
collapsedSections: string[]; collapsedSections: string[];
generatorPreviewOpen: boolean; generatorPreviewOpen: boolean;
@ -45,6 +48,8 @@ export const DEFAULT_SETTINGS: PromptfireSettings = {
targets: [], targets: [],
primaryTargetId: null, primaryTargetId: null,
targetOutputFolder: '_context/outputs', targetOutputFolder: '_context/outputs',
exportProfiles: [],
activeProfileId: null,
lastSettingsTab: 'general', lastSettingsTab: 'general',
collapsedSections: [], collapsedSections: [],
generatorPreviewOpen: false, generatorPreviewOpen: false,
@ -530,6 +535,21 @@ export class PromptfireSettingTab extends PluginSettingTab {
this.setDynamicDesc(primaryTargetSetting, 'This target\'s output is copied to clipboard', this.setDynamicDesc(primaryTargetSetting, 'This target\'s output is copied to clipboard',
totalTargets > 0 ? `${enabledTargets} of ${totalTargets} enabled` : ''); totalTargets > 0 ? `${enabledTargets} of ${totalTargets} enabled` : '');
new Setting(oc)
.setName('Active export profile')
.setDesc('Profile used by the "Export context (multi-target)" command')
.addDropdown(dropdown => {
dropdown.addOption('', 'None');
for (const profile of this.plugin.settings.exportProfiles) {
dropdown.addOption(profile.id, profile.name);
}
dropdown.setValue(this.plugin.settings.activeProfileId || '');
dropdown.onChange(async (value) => {
this.plugin.settings.activeProfileId = value || null;
await this.plugin.saveSettings();
});
});
const outputFolderSetting = new Setting(oc) const outputFolderSetting = new Setting(oc)
.setName('Output folder') .setName('Output folder')
.addText(text => text .addText(text => text
@ -541,6 +561,37 @@ export class PromptfireSettingTab extends PluginSettingTab {
this.describeOutputFolder(outputFolderSetting, this.plugin.settings.targetOutputFolder); this.describeOutputFolder(outputFolderSetting, this.plugin.settings.targetOutputFolder);
})); }));
this.describeOutputFolder(outputFolderSetting, this.plugin.settings.targetOutputFolder); this.describeOutputFolder(outputFolderSetting, this.plugin.settings.targetOutputFolder);
// Section: Export Profiles
const profilesSection = new CollapsibleSection(el, 'output-profiles', 'Export profiles', this.plugin);
const pc = profilesSection.contentEl;
const profileButtonContainer = pc.createDiv({ cls: 'profiles-button-container' });
profileButtonContainer.style.display = 'flex';
profileButtonContainer.style.gap = '8px';
profileButtonContainer.style.marginBottom = '15px';
const addProfileBtn = profileButtonContainer.createEl('button', { text: '+ New Profile' });
addProfileBtn.addEventListener('click', () => {
new ExportProfileModal(this.app, this.plugin, null, () => this.display()).open();
});
const addBuiltinProfilesBtn = profileButtonContainer.createEl('button', { text: 'Add Built-in Profiles' });
addBuiltinProfilesBtn.addEventListener('click', async () => {
const existingIds = this.plugin.settings.exportProfiles.map(p => p.id);
const newProfiles = BUILTIN_PROFILES.filter(p => !existingIds.includes(p.id));
if (newProfiles.length === 0) {
new Notice('All built-in profiles already added');
return;
}
this.plugin.settings.exportProfiles.push(...newProfiles);
await this.plugin.saveSettings();
new Notice(`Added ${newProfiles.length} built-in profile(s)`);
this.display();
});
const profilesContainer = pc.createDiv({ cls: 'profiles-list-container' });
this.renderProfilesList(profilesContainer);
} }
// === TAB: History === // === TAB: History ===
@ -1100,6 +1151,13 @@ export class PromptfireSettingTab extends PluginSettingTab {
if (this.plugin.settings.primaryTargetId === target.id) { if (this.plugin.settings.primaryTargetId === target.id) {
this.plugin.settings.primaryTargetId = null; this.plugin.settings.primaryTargetId = null;
} }
// Clean up profile references
for (const profile of this.plugin.settings.exportProfiles) {
profile.targetIds = profile.targetIds.filter(id => id !== target.id);
if (profile.primaryTargetId === target.id) {
profile.primaryTargetId = null;
}
}
await this.plugin.saveSettings(); await this.plugin.saveSettings();
this.display(); this.display();
}); });
@ -1111,4 +1169,86 @@ export class PromptfireSettingTab extends PluginSettingTab {
lastTargetRow.style.borderBottom = 'none'; lastTargetRow.style.borderBottom = 'none';
} }
} }
private renderProfilesList(container: HTMLElement) {
container.empty();
if (this.plugin.settings.exportProfiles.length === 0) {
const emptyMsg = container.createEl('p', {
text: 'No export profiles configured yet.',
cls: 'setting-item-description'
});
emptyMsg.style.fontStyle = 'italic';
return;
}
const list = container.createDiv({ cls: 'profiles-list' });
list.style.border = '1px solid var(--background-modifier-border)';
list.style.borderRadius = '4px';
list.style.marginBottom = '15px';
for (const profile of this.plugin.settings.exportProfiles) {
const row = list.createDiv({ cls: 'profile-row' });
row.style.display = 'flex';
row.style.alignItems = 'center';
row.style.padding = '8px 12px';
row.style.borderBottom = '1px solid var(--background-modifier-border)';
row.style.gap = '10px';
// Name and info
const textContainer = row.createDiv();
textContainer.style.flex = '1';
const name = textContainer.createEl('span', { text: profile.name });
name.style.fontWeight = '500';
// Show target names
const targetNames = profile.targetIds
.map(id => this.plugin.settings.targets.find(t => t.id === id)?.name)
.filter(Boolean)
.join(', ');
if (targetNames) {
const info = textContainer.createEl('div', { text: targetNames });
info.style.fontSize = '11px';
info.style.color = 'var(--text-muted)';
}
// Active badge
if (this.plugin.settings.activeProfileId === profile.id) {
const badge = row.createEl('span', { text: 'active' });
badge.style.padding = '2px 6px';
badge.style.borderRadius = '3px';
badge.style.fontSize = '11px';
badge.style.backgroundColor = 'var(--interactive-accent)';
badge.style.color = 'var(--text-on-accent)';
}
// Edit button
const editBtn = row.createEl('button', { text: '✎' });
editBtn.style.padding = '2px 8px';
editBtn.addEventListener('click', () => {
new ExportProfileModal(this.app, this.plugin, profile, () => this.display()).open();
});
// Delete button
const deleteBtn = row.createEl('button', { text: '✕' });
deleteBtn.style.padding = '2px 8px';
deleteBtn.addEventListener('click', async () => {
this.plugin.settings.exportProfiles = this.plugin.settings.exportProfiles.filter(
p => p.id !== profile.id
);
if (this.plugin.settings.activeProfileId === profile.id) {
this.plugin.settings.activeProfileId = null;
}
await this.plugin.saveSettings();
this.display();
});
}
// Remove bottom border from last item
const lastProfileRow = list.lastElementChild as HTMLElement;
if (lastProfileRow) {
lastProfileRow.style.borderBottom = 'none';
}
}
} }

View file

@ -21,6 +21,7 @@ export class TargetModal extends Modal {
wrapperPrefix: string = ''; wrapperPrefix: string = '';
wrapperSuffix: string = ''; wrapperSuffix: string = '';
separator: string = '\n\n---\n\n'; separator: string = '\n\n---\n\n';
outputPath: string = '';
enabled: boolean = true; enabled: boolean = true;
constructor( constructor(
@ -44,6 +45,7 @@ export class TargetModal extends Modal {
this.wrapperPrefix = target.wrapper?.prefix || ''; this.wrapperPrefix = target.wrapper?.prefix || '';
this.wrapperSuffix = target.wrapper?.suffix || ''; this.wrapperSuffix = target.wrapper?.suffix || '';
this.separator = target.separator || '\n\n---\n\n'; this.separator = target.separator || '\n\n---\n\n';
this.outputPath = target.outputPath || '';
this.enabled = target.enabled; this.enabled = target.enabled;
} }
} }
@ -138,6 +140,15 @@ export class TargetModal extends Modal {
text.inputEl.style.width = '100%'; text.inputEl.style.width = '100%';
}); });
// Output path
new Setting(contentEl)
.setName('Output path')
.setDesc('Custom folder for this target\'s file output (leave empty for default)')
.addText(text => text
.setPlaceholder('_context/outputs')
.setValue(this.outputPath)
.onChange(v => this.outputPath = v));
// Enabled toggle // Enabled toggle
new Setting(contentEl) new Setting(contentEl)
.setName('Enabled') .setName('Enabled')
@ -180,6 +191,7 @@ export class TargetModal extends Modal {
format: this.format, format: this.format,
strategy: this.strategy, strategy: this.strategy,
separator: this.separator, separator: this.separator,
outputPath: this.outputPath.trim() || undefined,
enabled: this.enabled, enabled: this.enabled,
isBuiltin: false, isBuiltin: false,
}; };

View file

@ -17,6 +17,7 @@ export interface OutputTarget {
suffix?: string; suffix?: string;
}; };
separator?: string; separator?: string;
outputPath?: string;
enabled: boolean; enabled: boolean;
isBuiltin?: boolean; isBuiltin?: boolean;
} }
@ -29,12 +30,24 @@ export interface TargetResult {
sectionsDropped: number; sectionsDropped: number;
} }
export interface ExportProfile {
id: string;
name: string;
description?: string;
targetIds: string[];
primaryTargetId: string | null;
}
// === ID Generation === // === ID Generation ===
export function generateTargetId(): string { export function generateTargetId(): string {
return 'target_' + Date.now().toString(36) + Math.random().toString(36).substring(2, 9); return 'target_' + Date.now().toString(36) + Math.random().toString(36).substring(2, 9);
} }
export function generateProfileId(): string {
return 'profile_' + Date.now().toString(36) + Math.random().toString(36).substring(2, 9);
}
// === Built-in Targets === // === Built-in Targets ===
export const BUILTIN_TARGETS: OutputTarget[] = [ export const BUILTIN_TARGETS: OutputTarget[] = [
@ -74,6 +87,16 @@ export const BUILTIN_TARGETS: OutputTarget[] = [
}, },
]; ];
export const BUILTIN_PROFILES: ExportProfile[] = [
{
id: 'builtin_profile_full',
name: 'Full LLM Export',
description: 'All built-in targets: XML for Claude, Markdown for GPT-4o, Plain for compact',
targetIds: ['builtin_promptfire', 'builtin_gpt4', 'builtin_compact'],
primaryTargetId: 'builtin_promptfire',
},
];
// === Content Transformer === // === Content Transformer ===
export class ContentTransformer { export class ContentTransformer {
@ -480,14 +503,15 @@ export async function saveTargetToFile(
result: TargetResult, result: TargetResult,
outputFolder: string outputFolder: string
): Promise<TFile | null> { ): Promise<TFile | null> {
const effectiveFolder = result.target.outputPath || outputFolder;
const filename = `context-${result.target.name.toLowerCase().replace(/\s+/g, '-')}.md`; const filename = `context-${result.target.name.toLowerCase().replace(/\s+/g, '-')}.md`;
const path = `${outputFolder}/${filename}`; const path = `${effectiveFolder}/${filename}`;
try { try {
// Ensure folder exists // Ensure folder exists
const folder = app.vault.getAbstractFileByPath(outputFolder); const folder = app.vault.getAbstractFileByPath(effectiveFolder);
if (!folder) { if (!folder) {
await app.vault.createFolder(outputFolder); await app.vault.createFolder(effectiveFolder);
} }
// Create or update file // Create or update file