diff --git a/.gitignore b/.gitignore index cab7726..ad201ee 100644 --- a/.gitignore +++ b/.gitignore @@ -116,4 +116,5 @@ Temporary Items CLAUDE.md ANTHROPIC_DEVELOPER_PROMPT.txt GIT_SYNC_PHASE1_COMPLETE.md -build-dmg.sh \ No newline at end of file +build-dmg.sh +.claude/ diff --git a/oAI.xcodeproj/project.pbxproj b/oAI.xcodeproj/project.pbxproj index d02b9da..1b40277 100644 --- a/oAI.xcodeproj/project.pbxproj +++ b/oAI.xcodeproj/project.pbxproj @@ -279,7 +279,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 26.2; - MARKETING_VERSION = 2.2; + MARKETING_VERSION = 2.3; PRODUCT_BUNDLE_IDENTIFIER = com.oai.oAI; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -323,7 +323,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 26.2; - MARKETING_VERSION = 2.2; + MARKETING_VERSION = 2.3; PRODUCT_BUNDLE_IDENTIFIER = com.oai.oAI; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; diff --git a/oAI/Models/AgentSkill.swift b/oAI/Models/AgentSkill.swift new file mode 100644 index 0000000..403ac83 --- /dev/null +++ b/oAI/Models/AgentSkill.swift @@ -0,0 +1,42 @@ +// +// AgentSkill.swift +// oAI +// +// SKILL.md-style behavioral skills — markdown instruction files injected into the system prompt +// + +import Foundation + +struct AgentSkill: Codable, Identifiable { + var id: UUID + var name: String // display name, e.g. "Code Review" + var skillDescription: String // short summary shown in the list + var content: String // full markdown content (the actual instructions) + var isActive: Bool // when true, injected into the system prompt + var createdAt: Date + var updatedAt: Date + + init(id: UUID = UUID(), name: String, skillDescription: String = "", content: String, + isActive: Bool = true, createdAt: Date = Date(), updatedAt: Date = Date()) { + self.id = id + self.name = name + self.skillDescription = skillDescription + self.content = content + self.isActive = isActive + self.createdAt = createdAt + self.updatedAt = updatedAt + } + + /// Extract a brief description from the content if skillDescription is empty + var resolvedDescription: String { + guard skillDescription.isEmpty else { return skillDescription } + // Return first non-heading, non-empty line + for line in content.components(separatedBy: .newlines) { + let trimmed = line.trimmingCharacters(in: .whitespaces) + if !trimmed.isEmpty && !trimmed.hasPrefix("#") { + return String(trimmed.prefix(100)) + } + } + return name + } +} diff --git a/oAI/Models/Skill.swift b/oAI/Models/Skill.swift new file mode 100644 index 0000000..09b2cdc --- /dev/null +++ b/oAI/Models/Skill.swift @@ -0,0 +1,32 @@ +// +// Shortcut.swift +// oAI +// +// User-defined slash command templates (prompt shortcuts/macros) +// + +import Foundation + +struct Shortcut: Codable, Identifiable { + var id: UUID + var command: String // e.g. "/summarize" (always starts with /) + var description: String // shown in dropdown + var template: String // prompt text, may contain {{input}} + var createdAt: Date + var updatedAt: Date + + init(id: UUID = UUID(), command: String, description: String, template: String, + createdAt: Date = Date(), updatedAt: Date = Date()) { + self.id = id + self.command = command + self.description = description + self.template = template + self.createdAt = createdAt + self.updatedAt = updatedAt + } + + /// True when the template uses {{input}} and needs the user to provide text + var needsInput: Bool { + template.contains("{{input}}") + } +} diff --git a/oAI/Providers/ProviderRegistry.swift b/oAI/Providers/ProviderRegistry.swift index 711f3a9..9f67956 100644 --- a/oAI/Providers/ProviderRegistry.swift +++ b/oAI/Providers/ProviderRegistry.swift @@ -36,15 +36,11 @@ class ProviderRegistry { provider = OpenRouterProvider(apiKey: apiKey) case .anthropic: - if AnthropicOAuthService.shared.isAuthenticated { - // OAuth (Pro/Max subscription) takes precedence - provider = AnthropicProvider(oauth: true) - } else if let apiKey = settings.anthropicAPIKey, !apiKey.isEmpty { - provider = AnthropicProvider(apiKey: apiKey) - } else { - Log.api.warning("No API key or OAuth configured for Anthropic") + guard let apiKey = settings.anthropicAPIKey, !apiKey.isEmpty else { + Log.api.warning("No API key configured for Anthropic") return nil } + provider = AnthropicProvider(apiKey: apiKey) case .openai: guard let apiKey = settings.openaiAPIKey, !apiKey.isEmpty else { diff --git a/oAI/Resources/oAI.help/Contents/Resources/en.lproj/index.html b/oAI/Resources/oAI.help/Contents/Resources/en.lproj/index.html index 262a345..751acec 100644 --- a/oAI/Resources/oAI.help/Contents/Resources/en.lproj/index.html +++ b/oAI/Resources/oAI.help/Contents/Resources/en.lproj/index.html @@ -33,6 +33,8 @@
/summarize to run your "Summarize" shortcutShortcuts are personal slash commands you define yourself. Each shortcut expands to a prompt template and can optionally ask for input. Think of them as saved prompts with custom command names.
+ +/summarize shortcut that sends "Please summarize the following text concisely:\n\n{{input}}" — then just type /summarize and paste any text after it.
+ /shortcuts or go to Settings → Shortcuts tab/summarize)If your template contains {{input}}, the AI waits for you to type something after the command. Your text replaces {{input}} before sending.
Command: /translate-no
+Template: Translate the following text to Norwegian:\n\n{{input}}
+
+Usage: /translate-no Hello, how are you today?
+→ Sends: "Translate the following text to Norwegian:\n\nHello, how are you today?"
+ If your template has no {{input}}, the shortcut executes immediately when selected — no extra input needed.
Command: /hello
+Template: Say hello in 5 different languages with a brief cultural note for each.
+
+Usage: Type /hello → executes immediately
+ Your shortcuts appear in the / dropdown with a ⚡ prefix. Type the first letters of your command to filter them.
/roast "Give me a light-hearted roast of this text: {{input}}"
+/eli5 "Explain {{input}} as if I were 5 years old"
+/debug "Find the bug in this code and explain the fix:\n\n{{input}}"
+/tweet "Rewrite this as a punchy tweet under 280 chars: {{input}}"
+/standup "Based on these notes, write a daily standup update: {{input}}"
+ shortcuts.json in DownloadsAgent Skills are markdown instruction files that teach the AI how to behave. Active skills are automatically injected into the system prompt for every conversation — the AI reads them and applies the instructions when relevant.
+ +Skills follow the SKILL.md open standard, making them compatible with a growing ecosystem of skill marketplaces and community collections.
+ +/skills or go to Settings → Skills tab.md file or a .zip skill bundle, orA skill is a plain Markdown file. The first # Heading becomes the skill name. Write instructions in natural language — the AI decides when to apply them.
# Security Auditor
+
+When reviewing code, always:
+- Check for injection vulnerabilities (SQL, command, XSS)
+- Look for insecure storage of credentials or secrets
+- Verify authentication and authorization logic
+- Suggest specific fixes, not just observations
+- Rate severity: Critical / High / Medium / Low
+ # Norwegian Translator
+
+Whenever the user asks you to translate something, translate it to Norwegian Bokmål.
+- Maintain the original tone and register
+- Keep technical terms in English unless there is a well-established Norwegian equivalent
+- If the text is already in Norwegian, translate it to English instead
+ # Title — this becomes the skill name in the listActive skills are appended to the system prompt under an "Installed Skills" section. The AI sees them with every message and applies relevant guidance automatically. You can toggle skills on/off without deleting them.
+ +Each skill can have data files attached — JSON, YAML, CSV, TXT, or any plain-text format. When a skill is active, its files are injected into the system prompt right after the skill's markdown content, so the AI can read and reason over them.
+ +news_sources.json — a list of URLs for a "Daily AI News" skillsearch_queries.md — template search strings for a research skilloutput_templates.md — report formats for a writing skillconfig.yaml — parameters a skill should followIn the system prompt, each file is included as a labelled fenced code block immediately after the skill's instructions:
+### Daily AI News
+
+[skill instructions here]
+
+**Skill Data Files:**
+
+**news_sources.json:**
+```json
+{ "sources": [...] }
+```
+
+**search_queries.md:**
+```md
+...
+```
+ Files are stored locally at:
+~/Library/Application Support/oAI/skills/<skill-uuid>/
+├── news_sources.json
+└── search_queries.md
+ Deleting a skill also deletes its file directory automatically.
+ +Click Import in the Skills manager. You can select:
+.md file — plain SKILL.md-format file. The # heading becomes the skill name.zip bundle — a skill with attached data files. The archive should contain a skill.md and any number of data files (in any directory structure — they are flattened into the skill's file directory)If a skill with the same name already exists, it is replaced. Data files from a zip are merged into the existing skill's directory.
+ +skill-name.md to Downloadsskill-name.zip containing skill.md + all data files.md or .zip).zip directly on another machine to restore both the instructions and all attached files.
+ The SKILL.md community has produced hundreds of ready-made skills you can import directly:
+Configure Git synchronization for backing up and syncing conversations across devices.
+Configure AI-powered email auto-responder to automatically reply to incoming emails.
+[JARVIS], case-sensitive)Create and manage personal prompt template commands. See Shortcuts section for full details.
+ +Manage SKILL.md-style behavioral instruction files injected into the system prompt. See Agent Skills section for full details.
+This combined prompt is sent with every message to ensure consistent behavior.
@@ -1094,7 +1399,7 @@ AI Assistant