@limrun/cli 0.6.3 → 0.7.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/README.md CHANGED
@@ -33,7 +33,7 @@ The CLI stores configuration in `~/.lim/config.yaml`. This file is compatible wi
33
33
 
34
34
  ## Global Flags
35
35
 
36
- Every command supports these flags:
36
+ Most commands support these flags (exceptions: `lim skills install` does not take `--api-key` because it does not talk to the API):
37
37
 
38
38
  | Flag | Description |
39
39
  | ------------------- | ---------------------------------------------------------- |
@@ -86,6 +86,7 @@ This avoids relying on locally cached "last created" state and keeps the target
86
86
  - [Assets](#assets) — Upload and download files (APKs, IPAs, etc.)
87
87
  - [Sessions](#sessions) — Persistent connections for fast, interactive device control
88
88
  - [Xcode Build Pipeline](#xcode-build-pipeline) — Sync code and run xcodebuild remotely
89
+ - [Skills](#skills) — Install Limrun skills for AI coding agents (Claude Code, Cursor, Codex)
89
90
 
90
91
  ---
91
92
 
@@ -613,6 +614,55 @@ Provide `--certificate-p12`, `--certificate-password`, and `--provisioning-profi
613
614
 
614
615
  ---
615
616
 
617
+ ### Skills
618
+
619
+ Install Limrun skills into the native skills directory of AI coding agents (Claude Code, Cursor, Codex). After installation, the agent auto-discovers the skill and triggers it when you ask things like "build the iOS app" or "show me a screenshot."
620
+
621
+ ```bash
622
+ # Interactive: prompts for agents (with detected ones pre-checked) and scope
623
+ lim skills install
624
+
625
+ # Non-interactive
626
+ lim skills install --agents claude --scope project
627
+ lim skills install --agents claude --agents cursor --scope project
628
+ lim skills install --agents codex --scope global
629
+
630
+ # Overwrite existing skill files (otherwise the command refuses on non-interactive runs)
631
+ lim skills install --agents claude --scope project --force
632
+
633
+ # Machine-readable output for scripts
634
+ lim skills install --agents claude --scope project --json
635
+ ```
636
+
637
+ **Flags:**
638
+
639
+ | Flag | Description |
640
+ | --------------------------- | -------------------------------------------------------------------------------------------- |
641
+ | `--agents <id>` | Target agent. Repeat to select multiple. One of: `claude`, `cursor`, `codex`. |
642
+ | `--scope <project\|global>` | `project` writes into the current directory; `global` writes into the user's home directory. |
643
+ | `--force` | Overwrite existing skill files without confirmation. |
644
+ | `--json` | Emit structured JSON instead of the human summary. |
645
+ | `--quiet` | Suppress non-result output. |
646
+
647
+ **Install paths:**
648
+
649
+ | Agent | Project | Global |
650
+ | ----------- | ------------------------- | -------------------------------------------------------------------------- |
651
+ | Claude Code | `.claude/skills/<skill>/` | `$CLAUDE_CONFIG_DIR/skills/<skill>/` (default `~/.claude/skills/<skill>/`) |
652
+ | Cursor | `.agents/skills/<skill>/` | `~/.agents/skills/<skill>/` |
653
+ | Codex | `.codex/skills/<skill>/` | `$CODEX_HOME/skills/<skill>/` (default `~/.codex/skills/<skill>/`) |
654
+
655
+ **Behavior:**
656
+
657
+ - The command compares bundled vs existing content byte-for-byte. Identical content is reported as `Unchanged` (no writes).
658
+ - Different content: in interactive mode you are asked to confirm each overwrite; in non-interactive mode the command refuses unless `--force` is passed.
659
+ - Non-interactive runs are all-or-nothing: if any selected target conflicts and `--force` is not set, no files are written for any target, and the command exits with status 1.
660
+ - Ctrl-C cancellation at any prompt exits cleanly without writing.
661
+
662
+ Cursor reads `.agents/skills/` natively, so we install there rather than `.cursor/skills/`. As a bonus, the same install reaches OpenCode and any other tool that follows the AGENTS.md convention - no extra menu options needed.
663
+
664
+ ---
665
+
616
666
  ## Configuration
617
667
 
618
668
  The CLI reads configuration from multiple sources (in order of precedence):
@@ -1 +1 @@
1
- {"version":3,"file":"get.d.ts","sourceRoot":"","sources":["../../../src/commands/android/get.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW;IACjD,MAAM,CAAC,OAAO,SAAiD;IAC/D,MAAM,CAAC,WAAW,SACmI;IACrJ,MAAM,CAAC,QAAQ,WAAkF;IAEjG,MAAM,CAAC,IAAI;;MAKT;IAEF,MAAM,CAAC,KAAK;;;;;MAAgC;IAEtC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAsB3B"}
1
+ {"version":3,"file":"get.d.ts","sourceRoot":"","sources":["../../../src/commands/android/get.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,WAAW;IACjD,MAAM,CAAC,OAAO,SAAiD;IAC/D,MAAM,CAAC,WAAW,SACmI;IACrJ,MAAM,CAAC,QAAQ,WAAkF;IAEjG,MAAM,CAAC,IAAI;;MAKT;IAEF,MAAM,CAAC,KAAK;;;;;MAAgC;IAEtC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAuB3B"}
@@ -1 +1 @@
1
- {"version":3,"file":"get.js","sourceRoot":"","sources":["../../../src/commands/android/get.ts"],"names":[],"mappings":";;AAAA,sCAAmC;AACnC,qDAAiD;AAEjD,MAAqB,UAAW,SAAQ,0BAAW;IAejD,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACrD,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAE3B,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE;YAC7B,MAAM,gBAAgB,GAAG,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC9D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;YAC7E,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;gBACf,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YAC5B,CAAC;iBAAM,CAAC;gBACN,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAC9D,IAAI,CAAC,MAAM,CAAC,OAAO,QAAQ,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC3C,IAAI,CAAC,MAAM,CAAC,SAAS,QAAQ,CAAC,QAAQ,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC5D,IAAI,CAAC,MAAM,CAAC,WAAW,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC/C,IAAI,CAAC,MAAM,CAAC,UAAU,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC/C,IAAI,CAAC,MAAM,CAAC,gBAAgB,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;gBAC3E,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM;oBAAE,IAAI,CAAC,MAAM,CAAC,YAAY,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC9E,IAAI,QAAQ,CAAC,MAAM,CAAC,eAAe;oBAAE,IAAI,CAAC,MAAM,CAAC,sBAAsB,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC;gBAC1G,IAAI,eAAe;oBAAE,IAAI,CAAC,MAAM,CAAC,sBAAsB,eAAe,EAAE,CAAC,CAAC;YAC5E,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;;AAnCM,kBAAO,GAAG,6CAA6C,CAAC;AACxD,sBAAW,GAChB,kJAAkJ,CAAC;AAC9I,mBAAQ,GAAG,CAAC,+BAA+B,EAAE,2CAA2C,CAAC,CAAC;AAE1F,eAAI,GAAG;IACZ,EAAE,EAAE,WAAI,CAAC,MAAM,CAAC;QACd,WAAW,EAAE,8EAA8E;QAC3F,QAAQ,EAAE,KAAK;KAChB,CAAC;CACH,CAAC;AAEK,gBAAK,GAAG,EAAE,GAAG,0BAAW,CAAC,SAAS,EAAE,CAAC;kBAbzB,UAAU"}
1
+ {"version":3,"file":"get.js","sourceRoot":"","sources":["../../../src/commands/android/get.ts"],"names":[],"mappings":";;AAAA,sCAAmC;AACnC,qDAAiD;AAEjD,MAAqB,UAAW,SAAQ,0BAAW;IAejD,KAAK,CAAC,GAAG;QACP,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACrD,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAE3B,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE;YAC7B,MAAM,gBAAgB,GAAG,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC9D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,GAAG,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;YAC7E,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;gBACf,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;YAC5B,CAAC;iBAAM,CAAC;gBACN,MAAM,eAAe,GAAG,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAC9D,IAAI,CAAC,MAAM,CAAC,OAAO,QAAQ,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC3C,IAAI,CAAC,MAAM,CAAC,SAAS,QAAQ,CAAC,QAAQ,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC5D,IAAI,CAAC,MAAM,CAAC,WAAW,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC/C,IAAI,CAAC,MAAM,CAAC,UAAU,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC/C,IAAI,CAAC,MAAM,CAAC,gBAAgB,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;gBAC3E,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM;oBAAE,IAAI,CAAC,MAAM,CAAC,YAAY,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;gBAC9E,IAAI,QAAQ,CAAC,MAAM,CAAC,eAAe;oBACjC,IAAI,CAAC,MAAM,CAAC,sBAAsB,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC;gBACvE,IAAI,eAAe;oBAAE,IAAI,CAAC,MAAM,CAAC,sBAAsB,eAAe,EAAE,CAAC,CAAC;YAC5E,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;;AApCM,kBAAO,GAAG,6CAA6C,CAAC;AACxD,sBAAW,GAChB,kJAAkJ,CAAC;AAC9I,mBAAQ,GAAG,CAAC,+BAA+B,EAAE,2CAA2C,CAAC,CAAC;AAE1F,eAAI,GAAG;IACZ,EAAE,EAAE,WAAI,CAAC,MAAM,CAAC;QACd,WAAW,EAAE,8EAA8E;QAC3F,QAAQ,EAAE,KAAK;KAChB,CAAC;CACH,CAAC;AAEK,gBAAK,GAAG,EAAE,GAAG,0BAAW,CAAC,SAAS,EAAE,CAAC;kBAbzB,UAAU"}
@@ -0,0 +1,17 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class SkillsInstall extends Command {
3
+ static summary: string;
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ agents: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
8
+ scope: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
+ force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
+ quiet: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
12
+ };
13
+ run(): Promise<void>;
14
+ private runInner;
15
+ private emitOutput;
16
+ }
17
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../../src/commands/skills/install.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAS,MAAM,aAAa,CAAC;AA4L7C,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,OAAO;IAChD,MAAM,CAAC,OAAO,SAAgD;IAC9D,MAAM,CAAC,WAAW,SAC6K;IAC/L,MAAM,CAAC,QAAQ,WAIb;IACF,MAAM,CAAC,KAAK;;;;;;MAsBV;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;YAWZ,QAAQ;IA2GtB,OAAO,CAAC,UAAU;CAiCnB"}
@@ -0,0 +1,325 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const prompts_1 = __importDefault(require("prompts"));
9
+ const core_1 = require("@oclif/core");
10
+ const skills_1 = require("../../lib/skills");
11
+ const SKILL_NAME = 'limrun-ios';
12
+ const SKIPPED_REASON_CONFLICT = 'existing content differs; re-run with --force to overwrite';
13
+ const SKIPPED_REASON_BLOCKED = 'blocked: another target requires --force to proceed';
14
+ const SKIPPED_REASON_DECLINED = 'user declined overwrite confirmation';
15
+ class PromptCancelled extends Error {
16
+ constructor() {
17
+ super('cancelled');
18
+ this.name = 'PromptCancelled';
19
+ }
20
+ }
21
+ function detectAgentsForScope(scope) {
22
+ const detected = new Set();
23
+ for (const id of skills_1.AGENT_IDS) {
24
+ const agent = skills_1.AGENTS[id];
25
+ for (const p of agent.detectionPaths(scope)) {
26
+ if (fs_1.default.existsSync(p)) {
27
+ detected.add(id);
28
+ break;
29
+ }
30
+ }
31
+ }
32
+ return detected;
33
+ }
34
+ async function promptAgents(preselected) {
35
+ // Loop to enforce "at least one agent" without crashing.
36
+ while (true) {
37
+ // `prompts` does not expose the multiselect cursor in onState - the state
38
+ // event only carries {value, aborted, exited}. Track cursor ourselves by
39
+ // listening to keypress events on stdin and mirror prompts' own dispatch
40
+ // (see prompts/lib/util/action.js and multiselect.js):
41
+ // - up / k : wrap (cursor === 0 ? last : cursor - 1)
42
+ // - down / j / tab : wrap (cursor === last ? 0 : cursor + 1)
43
+ // - ctrl+a : first
44
+ // - ctrl+e : last
45
+ // Anything else (page nav, home/end) is rare for a 3-row list and not
46
+ // tracked here; the worst case is the same as the previous clamp bug.
47
+ let cursor = 0;
48
+ const onKeypress = (_str, key) => {
49
+ if (!key || !key.name)
50
+ return;
51
+ if (key.meta && key.name !== 'escape')
52
+ return;
53
+ const last = skills_1.AGENT_IDS.length - 1;
54
+ if (key.ctrl) {
55
+ if (key.name === 'a')
56
+ cursor = 0;
57
+ else if (key.name === 'e')
58
+ cursor = last;
59
+ return;
60
+ }
61
+ if (key.name === 'up' || key.name === 'k') {
62
+ cursor = cursor === 0 ? last : cursor - 1;
63
+ }
64
+ else if (key.name === 'down' || key.name === 'j' || key.name === 'tab') {
65
+ cursor = cursor === last ? 0 : cursor + 1;
66
+ }
67
+ };
68
+ process.stdin.on('keypress', onKeypress);
69
+ let response;
70
+ try {
71
+ response = await (0, prompts_1.default)({
72
+ type: 'multiselect',
73
+ name: 'agents',
74
+ message: 'Which agents do you want to set up?',
75
+ instructions: false,
76
+ choices: skills_1.AGENT_IDS.map((id) => ({
77
+ title: skills_1.AGENTS[id].displayName,
78
+ value: id,
79
+ selected: preselected.has(id),
80
+ })),
81
+ hint: 'Space to toggle, Enter to confirm (Enter alone picks the highlighted agent)',
82
+ }, {
83
+ onCancel: () => {
84
+ throw new PromptCancelled();
85
+ },
86
+ });
87
+ }
88
+ finally {
89
+ process.stdin.off('keypress', onKeypress);
90
+ }
91
+ let picked = (response.agents ?? []);
92
+ // If the user hit Enter without toggling anything, treat the highlighted
93
+ // row as their pick. Saves a Space keystroke for the common single-agent case.
94
+ if (picked.length === 0 && cursor >= 0 && cursor < skills_1.AGENT_IDS.length) {
95
+ picked = [skills_1.AGENT_IDS[cursor]];
96
+ }
97
+ if (picked.length > 0) {
98
+ process.stderr.write(` Selected: ${picked.map((id) => skills_1.AGENTS[id].displayName).join(', ')}\n`);
99
+ return picked;
100
+ }
101
+ // Re-prompt with a visible inline warning (should not be reachable now).
102
+ process.stderr.write('Select at least one agent.\n');
103
+ }
104
+ }
105
+ async function promptScope() {
106
+ const response = await (0, prompts_1.default)({
107
+ type: 'select',
108
+ name: 'scope',
109
+ message: 'Install location?',
110
+ choices: [
111
+ { title: 'Project', value: 'project', description: 'Install into the current directory' },
112
+ { title: 'Global', value: 'global', description: 'Install into your home directory' },
113
+ ],
114
+ initial: 0,
115
+ }, {
116
+ onCancel: () => {
117
+ throw new PromptCancelled();
118
+ },
119
+ });
120
+ return response.scope;
121
+ }
122
+ async function promptOverwrite(targetPath) {
123
+ const response = await (0, prompts_1.default)({
124
+ type: 'confirm',
125
+ name: 'ok',
126
+ message: `Overwrite existing ${targetPath}?`,
127
+ initial: false,
128
+ }, {
129
+ onCancel: () => {
130
+ throw new PromptCancelled();
131
+ },
132
+ });
133
+ return Boolean(response.ok);
134
+ }
135
+ function humanPath(absolutePath, scope) {
136
+ if (scope !== 'project') {
137
+ return absolutePath;
138
+ }
139
+ const relative = path_1.default.relative(process.cwd(), absolutePath);
140
+ if (relative.startsWith('..') || path_1.default.isAbsolute(relative)) {
141
+ return absolutePath;
142
+ }
143
+ return relative;
144
+ }
145
+ function statusLabel(status) {
146
+ switch (status) {
147
+ case 'installed':
148
+ return 'Installed';
149
+ case 'updated':
150
+ return 'Updated';
151
+ case 'unchanged':
152
+ return 'Unchanged';
153
+ case 'skipped':
154
+ return 'Skipped';
155
+ }
156
+ }
157
+ class SkillsInstall extends core_1.Command {
158
+ async run() {
159
+ try {
160
+ await this.runInner();
161
+ }
162
+ catch (err) {
163
+ if (err instanceof PromptCancelled) {
164
+ this.exit(130);
165
+ }
166
+ throw err;
167
+ }
168
+ }
169
+ async runInner() {
170
+ const { flags } = await this.parse(SkillsInstall);
171
+ const interactive = process.stdin.isTTY === true && !flags.json && !flags.quiet;
172
+ let agents;
173
+ let scope;
174
+ if (flags.agents && flags.agents.length > 0) {
175
+ agents = Array.from(new Set(flags.agents));
176
+ }
177
+ else if (interactive) {
178
+ // Pre-check based on project-local presence only. Global installs of
179
+ // agents (e.g. ~/.claude on a dev machine) are too weak a signal to
180
+ // auto-select them for this specific project's install.
181
+ agents = await promptAgents(detectAgentsForScope('project'));
182
+ }
183
+ else {
184
+ this.error('--agents requires at least one of: claude, cursor, codex.', { exit: 2 });
185
+ }
186
+ if (flags.scope) {
187
+ scope = flags.scope;
188
+ }
189
+ else if (interactive) {
190
+ scope = await promptScope();
191
+ }
192
+ else {
193
+ this.error('Specify --agents and --scope in non-interactive mode.', { exit: 2 });
194
+ }
195
+ const source = (0, skills_1.sourceSkillMd)(this.config, SKILL_NAME);
196
+ if (!fs_1.default.existsSync(source)) {
197
+ this.error(`Bundled skill source missing at ${source}.`, { exit: 1 });
198
+ }
199
+ // Phase 1: Plan.
200
+ const planned = agents.map((id) => {
201
+ const agent = skills_1.AGENTS[id];
202
+ const target = (0, skills_1.targetSkillMd)(agent, scope, SKILL_NAME);
203
+ const { kind } = (0, skills_1.planSkillFileCopy)(source, target);
204
+ return { agent, scope, source, target, kind };
205
+ });
206
+ // Phase 2: Confirm.
207
+ const results = [];
208
+ const anyConflict = planned.some((t) => t.kind === 'conflict');
209
+ if (anyConflict && !flags.force && !interactive) {
210
+ // Non-interactive + conflict + no force: all-or-nothing. Skip all targets.
211
+ for (const t of planned) {
212
+ results.push({
213
+ agent: t.agent.id,
214
+ scope: t.scope,
215
+ path: t.target,
216
+ status: 'skipped',
217
+ reason: t.kind === 'conflict' ? SKIPPED_REASON_CONFLICT : SKIPPED_REASON_BLOCKED,
218
+ });
219
+ }
220
+ if (!flags.json) {
221
+ // --quiet still suppresses the human summary, but a hard refusal needs to be visible.
222
+ process.stderr.write('Existing skill files would be overwritten. Re-run with --force, or run interactively to confirm per target.\n');
223
+ }
224
+ this.emitOutput(results, flags);
225
+ this.exit(1);
226
+ }
227
+ // Decide final status per target (no writes yet).
228
+ const finalDecisions = [];
229
+ for (const t of planned) {
230
+ if (t.kind === 'install') {
231
+ finalDecisions.push({ target: t, status: 'installed' });
232
+ }
233
+ else if (t.kind === 'unchanged') {
234
+ finalDecisions.push({ target: t, status: 'unchanged' });
235
+ }
236
+ else if (flags.force) {
237
+ finalDecisions.push({ target: t, status: 'updated' });
238
+ }
239
+ else {
240
+ // Interactive conflict without --force: prompt per target.
241
+ const displayPath = humanPath(t.target, t.scope);
242
+ const ok = await promptOverwrite(displayPath);
243
+ if (ok) {
244
+ finalDecisions.push({ target: t, status: 'updated' });
245
+ }
246
+ else {
247
+ finalDecisions.push({
248
+ target: t,
249
+ status: 'skipped',
250
+ reason: SKIPPED_REASON_DECLINED,
251
+ });
252
+ }
253
+ }
254
+ }
255
+ // Phase 3: Apply.
256
+ for (const decision of finalDecisions) {
257
+ if (decision.status === 'installed' || decision.status === 'updated') {
258
+ (0, skills_1.applySkillFileCopy)(decision.target.source, decision.target.target);
259
+ }
260
+ results.push({
261
+ agent: decision.target.agent.id,
262
+ scope: decision.target.scope,
263
+ path: decision.target.target,
264
+ status: decision.status,
265
+ ...(decision.reason ? { reason: decision.reason } : {}),
266
+ });
267
+ }
268
+ this.emitOutput(results, flags);
269
+ }
270
+ emitOutput(results, flags) {
271
+ if (flags.json) {
272
+ this.log(JSON.stringify({
273
+ skill: SKILL_NAME,
274
+ results,
275
+ }, null, 2));
276
+ return;
277
+ }
278
+ if (flags.quiet) {
279
+ return;
280
+ }
281
+ // Human summary.
282
+ this.log('');
283
+ this.log(` ${SKILL_NAME}`);
284
+ const colWidth = Math.max(...results.map((r) => skills_1.AGENTS[r.agent].displayName.length), 'Claude Code'.length);
285
+ for (const r of results) {
286
+ const agentLabel = skills_1.AGENTS[r.agent].displayName.padEnd(colWidth);
287
+ const displayPath = humanPath(r.path, r.scope);
288
+ const reason = r.reason ? ` (${r.reason})` : '';
289
+ this.log(` ${agentLabel} -> ${displayPath} ${statusLabel(r.status)}${reason}`);
290
+ }
291
+ this.log('');
292
+ }
293
+ }
294
+ SkillsInstall.summary = 'Install Limrun skills for AI coding agents';
295
+ SkillsInstall.description = 'Copy the bundled Limrun skill into the native skills directory for each selected agent (Claude Code, Cursor, Codex). Pre-checks detected agents and lets you pick project or global scope.';
296
+ SkillsInstall.examples = [
297
+ '<%= config.bin %> skills install',
298
+ '<%= config.bin %> skills install --agents claude --agents cursor --scope project',
299
+ '<%= config.bin %> skills install --agents codex --scope global --force',
300
+ ];
301
+ SkillsInstall.flags = {
302
+ agents: core_1.Flags.string({
303
+ description: 'Target agent. Repeat to pick multiple.',
304
+ multiple: true,
305
+ options: ['claude', 'cursor', 'codex'],
306
+ }),
307
+ scope: core_1.Flags.string({
308
+ description: 'Install scope.',
309
+ options: ['project', 'global'],
310
+ }),
311
+ force: core_1.Flags.boolean({
312
+ description: 'Overwrite existing skill files without confirmation.',
313
+ default: false,
314
+ }),
315
+ json: core_1.Flags.boolean({
316
+ description: 'Emit structured JSON output.',
317
+ default: false,
318
+ }),
319
+ quiet: core_1.Flags.boolean({
320
+ description: 'Suppress non-result output.',
321
+ default: false,
322
+ }),
323
+ };
324
+ exports.default = SkillsInstall;
325
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/commands/skills/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { type Config } from '@oclif/core';
2
+ export type AgentId = 'claude' | 'cursor' | 'codex';
3
+ export type Scope = 'project' | 'global';
4
+ export declare const AGENT_IDS: AgentId[];
5
+ export interface AgentSpec {
6
+ id: AgentId;
7
+ displayName: string;
8
+ skillsDir(scope: Scope): string;
9
+ detectionPaths(scope: Scope): string[];
10
+ }
11
+ export declare const AGENTS: Record<AgentId, AgentSpec>;
12
+ export declare function skillsRoot(config: Config): string;
13
+ export declare function sourceSkillMd(config: Config, skillName: string): string;
14
+ export declare function targetSkillMd(agent: AgentSpec, scope: Scope, skillName: string): string;
15
+ export type PlanKind = 'install' | 'unchanged' | 'conflict';
16
+ export declare function planSkillFileCopy(sourceFile: string, targetFile: string): {
17
+ kind: PlanKind;
18
+ };
19
+ export declare function applySkillFileCopy(sourceFile: string, targetFile: string): void;
20
+ //# sourceMappingURL=skills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/lib/skills.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AACpD,MAAM,MAAM,KAAK,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEzC,eAAO,MAAM,SAAS,EAAE,OAAO,EAAkC,CAAC;AAElE,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,OAAO,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC;IAChC,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,CAAC;CACxC;AAcD,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,SAAS,CAsC7C,CAAC;AAEF,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEvF;AAED,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;AAE5D,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAO5F;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAG/E"}
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AGENTS = exports.AGENT_IDS = void 0;
7
+ exports.skillsRoot = skillsRoot;
8
+ exports.sourceSkillMd = sourceSkillMd;
9
+ exports.targetSkillMd = targetSkillMd;
10
+ exports.planSkillFileCopy = planSkillFileCopy;
11
+ exports.applySkillFileCopy = applySkillFileCopy;
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const os_1 = __importDefault(require("os"));
15
+ exports.AGENT_IDS = ['claude', 'cursor', 'codex'];
16
+ function claudeGlobalRoot() {
17
+ const override = process.env.CLAUDE_CONFIG_DIR;
18
+ if (override)
19
+ return path_1.default.resolve(override);
20
+ return path_1.default.join(os_1.default.homedir(), '.claude');
21
+ }
22
+ function codexGlobalRoot() {
23
+ const override = process.env.CODEX_HOME;
24
+ if (override)
25
+ return path_1.default.resolve(override);
26
+ return path_1.default.join(os_1.default.homedir(), '.codex');
27
+ }
28
+ exports.AGENTS = {
29
+ claude: {
30
+ id: 'claude',
31
+ displayName: 'Claude Code',
32
+ skillsDir: (scope) => scope === 'project' ?
33
+ path_1.default.join(process.cwd(), '.claude', 'skills')
34
+ : path_1.default.join(claudeGlobalRoot(), 'skills'),
35
+ detectionPaths: (scope) => scope === 'project' ? [path_1.default.join(process.cwd(), '.claude')] : [claudeGlobalRoot()],
36
+ },
37
+ cursor: {
38
+ id: 'cursor',
39
+ displayName: 'Cursor',
40
+ // Cursor auto-discovers .agents/skills/ natively, same as .cursor/skills/.
41
+ // Installing into .agents/skills/ also reaches OpenCode and any other
42
+ // AGENTS.md-aware tool with a single copy, so prefer the broader path.
43
+ skillsDir: (scope) => scope === 'project' ?
44
+ path_1.default.join(process.cwd(), '.agents', 'skills')
45
+ : path_1.default.join(os_1.default.homedir(), '.agents', 'skills'),
46
+ // Detect either .cursor/ or .agents/: both are reliable signs the user
47
+ // is on a tool that auto-loads .agents/skills/.
48
+ detectionPaths: (scope) => scope === 'project' ?
49
+ [path_1.default.join(process.cwd(), '.cursor'), path_1.default.join(process.cwd(), '.agents')]
50
+ : [path_1.default.join(os_1.default.homedir(), '.cursor'), path_1.default.join(os_1.default.homedir(), '.agents')],
51
+ },
52
+ codex: {
53
+ id: 'codex',
54
+ displayName: 'Codex',
55
+ skillsDir: (scope) => scope === 'project' ?
56
+ path_1.default.join(process.cwd(), '.codex', 'skills')
57
+ : path_1.default.join(codexGlobalRoot(), 'skills'),
58
+ detectionPaths: (scope) => scope === 'project' ? [path_1.default.join(process.cwd(), '.codex')] : [codexGlobalRoot()],
59
+ },
60
+ };
61
+ function skillsRoot(config) {
62
+ return path_1.default.join(config.root, 'skills');
63
+ }
64
+ function sourceSkillMd(config, skillName) {
65
+ return path_1.default.join(skillsRoot(config), skillName, 'SKILL.md');
66
+ }
67
+ function targetSkillMd(agent, scope, skillName) {
68
+ return path_1.default.join(agent.skillsDir(scope), skillName, 'SKILL.md');
69
+ }
70
+ function planSkillFileCopy(sourceFile, targetFile) {
71
+ if (!fs_1.default.existsSync(targetFile)) {
72
+ return { kind: 'install' };
73
+ }
74
+ const sourceBuf = fs_1.default.readFileSync(sourceFile);
75
+ const targetBuf = fs_1.default.readFileSync(targetFile);
76
+ return { kind: sourceBuf.equals(targetBuf) ? 'unchanged' : 'conflict' };
77
+ }
78
+ function applySkillFileCopy(sourceFile, targetFile) {
79
+ fs_1.default.mkdirSync(path_1.default.dirname(targetFile), { recursive: true });
80
+ fs_1.default.copyFileSync(sourceFile, targetFile);
81
+ }
82
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../../src/lib/skills.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@limrun/cli",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "Use remote XCode, iOS Simulator, Android Emulator and more to build and test apps from Linux, Windows or macOS.",
5
5
  "bin": {
6
6
  "lim": "./bin/run.js"
@@ -30,13 +30,15 @@
30
30
  "cli-table3": "^0.6.5",
31
31
  "dotenv": "^17.4.2",
32
32
  "js-yaml": "^4.1.0",
33
- "open": "^10.1.0"
33
+ "open": "^10.1.0",
34
+ "prompts": "^2.4.2"
34
35
  },
35
36
  "devDependencies": {
36
37
  "@oclif/test": "^4",
37
38
  "@types/cli-progress": "^3.11.6",
38
39
  "@types/js-yaml": "^4.0.9",
39
40
  "@types/node": "^22",
41
+ "@types/prompts": "^2.4.9",
40
42
  "typescript": "^5.8.3"
41
43
  },
42
44
  "oclif": {
@@ -59,6 +61,9 @@
59
61
  },
60
62
  "session": {
61
63
  "description": "Manage persistent background sessions for device interaction: start, status, stop. Sessions are used to execute commands on remote devices without the need to reconnect to the device after each command."
64
+ },
65
+ "skills": {
66
+ "description": "Install Limrun skills for AI coding agents (Claude Code, Cursor, Codex). Run `lim skills install` to set up."
62
67
  }
63
68
  }
64
69
  },
@@ -68,6 +73,7 @@
68
73
  "files": [
69
74
  "dist",
70
75
  "bin",
76
+ "skills",
71
77
  "README.md"
72
78
  ]
73
79
  }
@@ -0,0 +1,181 @@
1
+ ---
2
+ name: limrun-ios
3
+ description: "Replaces xcodebuild with remote XCode and Simulators, plus full iOS UI control. Use when the user wants to build, run, or launch an iOS app, test iOS UI, interact with the simulator (tap, click, type into, scroll), inspect the screen, capture a screenshot, or record a video. Triggers on phrases like 'run it', 'build it', 'test it', 'show me a screenshot', 'launch on simulator', 'tap the button', or 'type into the field'."
4
+ ---
5
+
6
+ # Remote XCode & iOS Simulator
7
+
8
+ You are an iOS build-and-test operator. Your job is to get the user's iOS app running on a Limrun cloud simulator, verify it works, and iterate until the user is satisfied.
9
+
10
+ All builds and simulator operations run on Limrun and that's why you can build iOS
11
+ apps from any environments; linux, windows, macos, VM, container etc. Never try to
12
+ use local Xcode, local simulators, or local macOS build tools.
13
+
14
+ If `lim` CLI is not installed, you can install it with the following:
15
+
16
+ ```bash
17
+ npm install --global @limrun/cli
18
+ ```
19
+
20
+ ## Check the CLI for current commands and flags
21
+
22
+ The CLI is the source of truth for command names, flags, and behavior. Before invoking any `lim` command you have not already used in this session, MUST run its `--help` first. The examples below are happy-path only; this skill intentionally does not embed the full reference because it would rot. Use:
23
+
24
+ ```bash
25
+ lim ios --help # list all iOS subcommands
26
+ lim ios <subcommand> --help # flags and examples for one iOS subcommand
27
+ lim xcode --help # list all xcode subcommands
28
+ lim xcode <subcommand> --help # flags and examples for one xcode subcommand
29
+ lim session --help # list all session subcommands
30
+ lim session <subcommand> --help # flags and examples for one session subcommand
31
+ ```
32
+
33
+ ## Build and Reload
34
+
35
+ First, create an XCode & iOS Simulator pair:
36
+
37
+ ```bash
38
+ # Add label selector depending on your identifiers. For example, Linear issue, repo name etc.
39
+ lim ios create --xcode \
40
+ --reuse-if-exists \
41
+ --label issue=<ISSUE ID> \
42
+ --label repo=<Repo Name> \
43
+ --label agent=<Your Agent Name>
44
+ ```
45
+
46
+ The command output includes the instance ID (e.g. `ios_abc123`) and a `Signed Stream URL:`. Share that URL with the user so they can watch the simulator while you work. If you have a browser you can drive, open the URL in that browser and notify the user.
47
+
48
+ ### Start a session for fast interaction
49
+
50
+ Right after creating the instance, start a session so subsequent interaction commands are fast:
51
+
52
+ ```bash
53
+ # ID comes from the create output above. Always pass --id explicitly in agent workflows.
54
+ lim session start --id <instance-id>
55
+ ```
56
+
57
+ Every `tap`, `type`, `element-tree`, `screenshot`, `record`, and `perform` command auto-routes through this session at ~50ms instead of ~2s. `lim ios delete` will auto-stop the session at cleanup; no separate `session stop` step is required.
58
+
59
+ ### Build
60
+
61
+ Instead of `xcodebuild` command, you MUST use the following to build the iOS app.
62
+
63
+ ```bash
64
+ lim xcode build .
65
+ ```
66
+
67
+ Use `--scheme` and `--workspace` flags if the project has multiple schemes or uses a workspace file. This makes sure the files are synced with the remote xcode and triggers
68
+ a build where the build logs are streamed through stdout and stderr.
69
+
70
+ Every successful build will automatically re-install the app in iOS Simulator and re-launch it.
71
+
72
+ ## Interacting with the App
73
+
74
+ Prefer tapping by accessibility identifier, then by label, then by coordinates as a last resort:
75
+
76
+ ```bash
77
+ lim ios tap-element --ax-unique-id startButton
78
+ lim ios tap-element --ax-label "Save"
79
+ lim ios tap 201 450
80
+ ```
81
+
82
+ After every interaction, re-run `element-tree` to confirm the UI transitioned correctly. No sleep is needed between a tap and element-tree.
83
+
84
+ For text input:
85
+
86
+ ```bash
87
+ lim ios type "hello world"
88
+ ```
89
+
90
+ ## Testing Changes
91
+
92
+ After every build, test new or changed functionality by using interaction commands. Focus on what changed plus a quick smoke test of core flows.
93
+
94
+ Use element tree for functional assertions (element existence, labels, state changes). Use screenshots only for visual-only properties.
95
+ Use video recording for most accurate interaction tests such as animations, gameplay,
96
+ real experience etc.
97
+
98
+ Generally, start with getting an element tree:
99
+
100
+ ```bash
101
+ lim ios element-tree
102
+ ```
103
+
104
+ Then if a single action will be taken, just call it. For example:
105
+
106
+ ```bash
107
+ lim ios tap-element --ax-label Continue
108
+ ```
109
+
110
+ If you will take multiple actions, you can create a chain of actions to be executed
111
+ with precise timing.
112
+
113
+ Some examples:
114
+
115
+ ```bash
116
+ lim ios perform --action type=tap,x=100,y=200 --action "type=typeText,text=Hello World"
117
+ ```
118
+
119
+ ```bash
120
+ lim ios perform --action type=wait,durationMs=1000 --action type=pressKey,key=enter
121
+ ```
122
+
123
+ You can write to a file and execute that too:
124
+
125
+ ```bash
126
+ lim ios perform --file ./actions.yaml
127
+ ```
128
+
129
+ Use `lim ios perform --help` for more details on how to use it.
130
+
131
+ Video recording is available so you can review what the user sees while you are taking actions. For
132
+ any testing involving motion prefer video over screenshots for review.
133
+
134
+ Always include a demo video in the pull request so that user can see how it works.
135
+
136
+ Start recording (non-blocking):
137
+
138
+ ```bash
139
+ lim ios record start
140
+ ```
141
+
142
+ Stop and save recording:
143
+
144
+ ```bash
145
+ lim ios record stop -o /tmp/recording.mp4
146
+ ```
147
+
148
+ ## Finalize
149
+
150
+ When you are wrapping up changes, produce a shareable build artifact so the user can test it themselves:
151
+
152
+ ```bash
153
+ ASSET_NAME="<bundle-id-or-pr-number-or-session-identifier>.zip"
154
+ lim xcode build . --upload ${ASSET_NAME}
155
+ ```
156
+
157
+ Then share a preview link with the user (substitute the asset name above):
158
+
159
+ ```
160
+ https://console.limrun.com/preview?asset=${ASSET_NAME}&platform=ios
161
+ ```
162
+
163
+ Always provide this in your last message.
164
+
165
+ ## Cleanup
166
+
167
+ When the user is satisfied or the conversation is ending, always clean up:
168
+
169
+ ```bash
170
+ lim ios delete
171
+ ```
172
+
173
+ ## Gotchas
174
+
175
+ These are common failure points. Check here first when something goes wrong.
176
+
177
+ - **Instance ID is optional.** The CLI remembers the last created instance. You only need to pass an ID explicitly when controlling multiple instances.
178
+ - **No sleep needed between `tap-element` and `element-tree`.** The tap blocks until complete.
179
+ - **`element-tree` can be large.** Pipe through `grep` or `jq` to extract what you need rather than dumping the full tree into context.
180
+ - **Build errors are your job to fix.** If a build fails, read the error output, fix the code, and rebuild. Do not ask the user to fix build errors.
181
+ - **Bundle ID discovery.** If you don't know the bundle ID, check the Xcode project files or run `lim ios list-apps` after a successful build.