Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,22 @@ Analyze SDK integration status in your project.
clix doctor
```

### `clix ios-setup`

Configure iOS capabilities required for the Clix SDK (Push Notifications and App Groups).

```bash
clix ios-setup
```

**What it does:**
1. Analyzes your iOS project structure
2. Checks current capabilities status
3. Creates/modifies entitlements files
4. Guides you through Xcode and Apple Developer Portal configuration

**Note:** Some steps require manual action in Xcode and Apple Developer Portal.

### Interactive Skills (Chat Mode Only)

The following skills require step-by-step guidance and are only available in chat mode. Run `clix` to start interactive chat, then use `/<skill>` commands.
Expand Down Expand Up @@ -235,6 +251,7 @@ Use these commands within the interactive chat (`clix`):
| `/install` | | Autonomous SDK installation |
| `/doctor` | | Check SDK integration status |
| `/debug` | | Interactive debugging assistant |
| `/ios-setup` | `/capabilities`, `/ios-capabilities` | Configure iOS capabilities |

### Interactive Skills

Expand Down
10 changes: 10 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 57 additions & 5 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ Clix CLI is an interactive command-line tool that provides a chat interface with
**Core Features:**
- Interactive chat interface as the primary interaction mode
- Support for 6 AI agents: Claude, Codex, Gemini, OpenCode, Cursor, and GitHub Copilot
- 17 slash commands for quick actions
- Skills system with 5 interactive skills + 3 autonomous commands
- 18 slash commands for quick actions
- Skills system with 5 interactive skills + 4 autonomous commands
- Interactive debug assistant for problem diagnosis
- Session transfer to native agent CLIs
- Agent switching with full history preservation
Expand Down Expand Up @@ -208,13 +208,14 @@ The interactive chat (`clix` command) is the primary way to interact with Clix C

Type `/` in the chat to see the autocomplete menu. All 17 slash commands are organized into three categories:

#### Autonomous Commands (3 commands)
#### Autonomous Commands (4 commands)

These can be run from command-line (`clix <command>`) or chat mode:

- `/install` - Autonomous SDK installation with automatic file modifications
- `/doctor` - Analyze SDK integration status (automatic scan, JSON output)
- `/debug` - Interactive debugging assistant (asks for problem description)
- `/ios-setup` (aliases: `/capabilities`, `/ios-capabilities`) - Configure iOS capabilities for push notifications and app groups

#### Interactive Skills (5 commands)

Expand Down Expand Up @@ -352,6 +353,56 @@ Describe the problem: Events not appearing in Clix dashboard
[Provides fix with exact file location]
```

#### `/ios-setup` - iOS Setup & Capabilities Configuration

**Category:** Autonomous Command

**Aliases:** `/capabilities`, `/ios-capabilities`

**What it does:** Configures iOS capabilities required for the Clix SDK, specifically Push Notifications and App Groups.

**Capabilities configured:**
- **Push Notifications** - Enables APNs communication (entitlement: `aps-environment`)
- **App Groups** - Enables data sharing between app and Notification Service Extension (entitlement: `com.apple.security.application-groups`, format: `group.clix.{BUNDLE_ID}`)

**Workflow:**
1. Analyzes iOS project structure (finds .xcodeproj/.xcworkspace)
2. Detects Bundle ID and checks current capabilities status
3. Creates/modifies entitlements files for main app and extension
4. Provides step-by-step instructions for Xcode configuration
5. Guides through Apple Developer Portal setup
6. Outputs verification report

**What can be automated:**
- Creating/modifying entitlements files
- Reading project configuration

**What requires manual action:**
- Adding capabilities in Xcode UI (Signing & Capabilities)
- Enabling capabilities in Apple Developer Portal
- Registering App Group IDs
- Regenerating provisioning profiles

**Example:**
```
> /ios-setup
Analyzing iOS project...
Bundle ID: com.example.myapp
Push Notifications: not configured
App Groups: not configured

Creating entitlements files...
✓ Created MyApp.entitlements
✓ Created NotificationServiceExtension.entitlements

Manual steps required in Xcode:
1. Add Push Notifications capability to main target
2. Add App Groups capability with ID: group.clix.com.example.myapp
3. Add same App Group to extension target

{verification report JSON}
```

### Interactive Skills

#### `/integration` - SDK Integration Guide
Expand Down Expand Up @@ -1123,8 +1174,8 @@ When helping users with Clix CLI, keep these points in mind:
1. **Primary command is `clix`** - This launches interactive chat, not just a welcome screen
2. **Interactive > Commands** - The tool is primarily interactive, not command-based
3. **6 supported agents** - Gemini, Copilot, OpenCode, Cursor, Claude, Codex (recommend starting with Gemini or Copilot for free tiers)
4. **17 slash commands** - 3 autonomous commands + 5 interactive skills + 9 system commands
5. **Autonomous vs Interactive** - Autonomous commands (`/install`, `/doctor`, `/debug`) can run from CLI, Interactive skills (`/integration`, `/event-tracking`, etc.) require chat mode
4. **18 slash commands** - 4 autonomous commands + 5 interactive skills + 9 system commands
5. **Autonomous vs Interactive** - Autonomous commands (`/install`, `/doctor`, `/debug`, `/ios-setup`) can run from CLI, Interactive skills (`/integration`, `/event-tracking`, etc.) require chat mode
6. **Skills from package** - Interactive skills from @clix-so/clix-agent-skills package, Autonomous commands are local
7. **/install vs /integration** - `/install` makes changes autonomously, `/integration` provides guided steps
8. **Session transfer** - Saves to `~/.local/state/clix/sessions/`, provides command for native CLI
Expand All @@ -1140,6 +1191,7 @@ When helping users with Clix CLI, keep these points in mind:
- "Installation" → `/install` for autonomous, `/integration` for guided steps
- "Debugging" → Recommend `/debug` for interactive diagnosis
- "SDK integration" → Use `/install` for autonomous installation or `/integration` skill for guided steps
- "iOS capabilities" → Use `/ios-setup` to configure Push Notifications and App Groups
- "Event tracking" → Use `/event-tracking` skill (interactive, creates event plans)
- "User management" → Use `/user-management` skill (setUserId, properties, logout handling)
- "Personalization" → Use `/personalization` skill (Liquid templates for messages)
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
"homepage": "https://docs.clix.so/clix-cli",
"dependencies": {
"@clix-so/clix-agent-skills": "^0.2.3",
"@expo/apple-utils": "^2.1.14",
"@expo/plist": "^0.4.8",
"ink": "^6.6.0",
"ink-select-input": "^6.2.0",
"ink-spinner": "^5.0.0",
Expand Down
42 changes: 42 additions & 0 deletions src/cli.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { agentCommand } from './commands/agent';
import { chatCommand } from './commands/chat';
import { debugCommand } from './commands/debug';
import { installMCPCommand } from './commands/install-mcp';
import { iosSetupCommand } from './commands/ios-setup/index';
import { resumeCommand } from './commands/resume';
import { skillCommand } from './commands/skill/index';
import { uninstallCommand } from './commands/uninstall';
Expand Down Expand Up @@ -89,6 +90,26 @@ const cli = meow(generateHelpText(), {
shortFlag: 'f',
default: false,
},
// iOS setup flags
apiKey: {
type: 'string',
},
keyId: {
type: 'string',
},
issuerId: {
type: 'string',
},
bundleId: {
type: 'string',
},
skipPortal: {
type: 'boolean',
default: false,
},
pushEnv: {
type: 'string',
},
},
});

Expand Down Expand Up @@ -148,6 +169,27 @@ async function main() {
});
break;

case 'ios-setup':
case 'capabilities':
case 'ios-capabilities': {
const pushEnvRaw = cli.flags.pushEnv;
if (pushEnvRaw && !['development', 'production'].includes(pushEnvRaw)) {
console.error(`Invalid --push-env value: ${pushEnvRaw}`);
console.error('Expected: development | production');
process.exit(1);
}
const pushEnv = pushEnvRaw as 'development' | 'production' | undefined;
await iosSetupCommand({
apiKeyPath: cli.flags.apiKey,
keyId: cli.flags.keyId,
issuerId: cli.flags.issuerId,
bundleId: cli.flags.bundleId,
skipPortal: cli.flags.skipPortal,
pushEnvironment: pushEnv,
});
break;
}

default:
// Check if command is a skill type (dynamically)
if (skillTypes.includes(command ?? '')) {
Expand Down
147 changes: 147 additions & 0 deletions src/commands/ios-setup/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { render } from 'ink';
import type { AgentInfo } from '../../lib/agents';
import type { AgentExecutor, AgentMessage } from '../../lib/executor';
import { generateAgentPrompt } from '../../lib/ios';
import { AgentExecutionUI } from '../../ui/AgentExecutionUI';
import { type IosSetupOptions, type IosSetupResult, IosSetupUI } from '../../ui/IosSetupUI';
import { type FinalOutputResult, printFinalOutput } from '../../ui/utils/finalOutput';

export interface IosSetupCommandOptions {
/** Path to .p8 API Key file */
apiKeyPath?: string;
/** API Key ID */
keyId?: string;
/** Issuer ID */
issuerId?: string;
/** Bundle ID (override auto-detection) */
bundleId?: string;
/** Skip Apple Developer Portal sync */
skipPortal?: boolean;
/** Push notification environment */
pushEnvironment?: 'development' | 'production';
}

function toDirectSetupOutput(result: IosSetupResult): FinalOutputResult {
if (result.success) {
const details: string[] = [];

if (result.projectInfo) {
details.push(`Project: ${result.projectInfo.appName}`);
details.push(`Bundle ID: ${result.projectInfo.bundleId}`);
}

if (result.portalSync) {
if (result.portalSync.enabled.length > 0) {
details.push(`Enabled: ${result.portalSync.enabled.join(', ')}`);
}
if (result.portalSync.appGroupCreated && result.portalSync.appGroupId) {
details.push(`Created App Group: ${result.portalSync.appGroupId}`);
}
}

if (result.entitlementsUpdated.length > 0) {
details.push(`Updated files: ${result.entitlementsUpdated.length}`);
}

return {
type: 'success',
title: 'Direct setup completed',
message: result.agentContext
? 'Portal sync and entitlements configured. Starting agent for remaining tasks...'
: 'Portal sync and entitlements configured.',
details: details.length > 0 ? details : undefined,
};
}

return {
type: 'error',
title: 'iOS setup failed',
message: result.error || 'Unknown error occurred',
};
}

/**
* Run the direct implementation phase (Portal sync + Entitlements)
*/
async function runDirectSetup(options: IosSetupCommandOptions): Promise<IosSetupResult> {
const uiOptions: IosSetupOptions = {
apiKeyPath: options.apiKeyPath,
keyId: options.keyId,
issuerId: options.issuerId,
bundleId: options.bundleId,
skipPortal: options.skipPortal ?? (!options.apiKeyPath && !options.keyId && !options.issuerId),
pushEnvironment: options.pushEnvironment,
};

return new Promise((resolve) => {
const { unmount } = render(
<IosSetupUI
options={uiOptions}
onComplete={(result) => {
unmount();
resolve(result);
}}
/>,
{ incrementalRendering: true },
);
});
}

/**
* Run the agent phase to complete remaining tasks (Xcode project modifications, Extension setup)
*/
async function runAgentCompletion(
directResult: IosSetupResult,
): Promise<FinalOutputResult | undefined> {
if (!directResult.agentContext) {
return undefined;
}

const agentPrompt = generateAgentPrompt(directResult.agentContext);

// Create execute function for agent
async function* executeAgent(
executor: AgentExecutor,
_agent: AgentInfo,
): AsyncGenerator<AgentMessage> {
yield* executor.execute(agentPrompt);
}

return new Promise((resolve) => {
const { unmount } = render(
<AgentExecutionUI
title="iOS Setup - Agent Completion"
description="Completing Xcode project modifications and Extension setup"
execute={executeAgent}
onComplete={(result) => {
unmount();
resolve(result);
}}
/>,
{ incrementalRendering: true },
);
});
}

export async function iosSetupCommand(options: IosSetupCommandOptions): Promise<void> {
// Phase 1: Direct implementation (Portal sync + Entitlements)
const directResult = await runDirectSetup(options);

if (!directResult.success) {
printFinalOutput(toDirectSetupOutput(directResult));
return;
}

// Show direct setup completion
printFinalOutput(toDirectSetupOutput(directResult));

// Phase 2: Agent completion (Xcode project modifications, Extension setup)
if (directResult.agentContext) {
console.log('\n'); // Add spacing before agent phase
const agentResult = await runAgentCompletion(directResult);

if (agentResult) {
printFinalOutput(agentResult);
}
}
}
Loading