@houwert/conductor 0.2.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.
Files changed (60) hide show
  1. package/.claude-plugin/plugin.json +6 -0
  2. package/README.md +39 -0
  3. package/dist/commands/assert-not-visible.js +47 -0
  4. package/dist/commands/assert-visible.js +58 -0
  5. package/dist/commands/back.js +25 -0
  6. package/dist/commands/cheat-sheet.js +100 -0
  7. package/dist/commands/daemon.js +61 -0
  8. package/dist/commands/device-pool.js +202 -0
  9. package/dist/commands/erase-text.js +26 -0
  10. package/dist/commands/foreground-app.js +50 -0
  11. package/dist/commands/hide-keyboard.js +27 -0
  12. package/dist/commands/inspect.js +37 -0
  13. package/dist/commands/install.js +64 -0
  14. package/dist/commands/launch-app.js +42 -0
  15. package/dist/commands/list-apps.js +60 -0
  16. package/dist/commands/list-devices.js +61 -0
  17. package/dist/commands/open-link.js +22 -0
  18. package/dist/commands/press-key.js +91 -0
  19. package/dist/commands/run-flow-inline.js +25 -0
  20. package/dist/commands/run-flow.js +29 -0
  21. package/dist/commands/run-parallel.js +143 -0
  22. package/dist/commands/screenshot.js +29 -0
  23. package/dist/commands/scroll-until-visible.js +69 -0
  24. package/dist/commands/scroll.js +36 -0
  25. package/dist/commands/session.js +49 -0
  26. package/dist/commands/set-location.js +18 -0
  27. package/dist/commands/set-orientation.js +23 -0
  28. package/dist/commands/start-device.js +178 -0
  29. package/dist/commands/stop-app.js +32 -0
  30. package/dist/commands/swipe.js +72 -0
  31. package/dist/commands/tap.js +69 -0
  32. package/dist/commands/type.js +22 -0
  33. package/dist/daemon/client.js +112 -0
  34. package/dist/daemon/protocol.js +25 -0
  35. package/dist/daemon/server.js +208 -0
  36. package/dist/drivers/android.js +343 -0
  37. package/dist/drivers/bootstrap.js +371 -0
  38. package/dist/drivers/element-resolver.js +371 -0
  39. package/dist/drivers/flow-runner.js +1309 -0
  40. package/dist/drivers/ios.js +328 -0
  41. package/dist/drivers/js-engine.js +150 -0
  42. package/dist/drivers/wait.js +211 -0
  43. package/dist/index.js +426 -0
  44. package/dist/output.js +36 -0
  45. package/dist/pkg-root.js +28 -0
  46. package/dist/postinstall.js +12 -0
  47. package/dist/runner.js +190 -0
  48. package/dist/session.js +66 -0
  49. package/dist/update-check.js +109 -0
  50. package/dist/utils.js +19 -0
  51. package/dist/verbose.js +17 -0
  52. package/drivers/android/conductor-app.apk +0 -0
  53. package/drivers/android/conductor-server.apk +0 -0
  54. package/drivers/ios/conductor-driver-ios-config.xctestrun +126 -0
  55. package/drivers/ios/conductor-driver-ios.zip +0 -0
  56. package/drivers/ios/conductor-driver-iosUITests-Runner.zip +0 -0
  57. package/package.json +52 -0
  58. package/proto/conductor_android.proto +116 -0
  59. package/skills/conductor/SKILL.md +677 -0
  60. package/skills/conductor/references/flow-syntax.md +179 -0
package/dist/index.js ADDED
@@ -0,0 +1,426 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const minimist_1 = __importDefault(require("minimist"));
8
+ const verbose_js_1 = require("./verbose.js");
9
+ const list_devices_js_1 = require("./commands/list-devices.js");
10
+ const launch_app_js_1 = require("./commands/launch-app.js");
11
+ const stop_app_js_1 = require("./commands/stop-app.js");
12
+ const tap_js_1 = require("./commands/tap.js");
13
+ const type_js_1 = require("./commands/type.js");
14
+ const back_js_1 = require("./commands/back.js");
15
+ const scroll_js_1 = require("./commands/scroll.js");
16
+ const swipe_js_1 = require("./commands/swipe.js");
17
+ const assert_visible_js_1 = require("./commands/assert-visible.js");
18
+ const screenshot_js_1 = require("./commands/screenshot.js");
19
+ const inspect_js_1 = require("./commands/inspect.js");
20
+ const run_flow_js_1 = require("./commands/run-flow.js");
21
+ const run_flow_inline_js_1 = require("./commands/run-flow-inline.js");
22
+ const press_key_js_1 = require("./commands/press-key.js");
23
+ const session_js_1 = require("./commands/session.js");
24
+ const cheat_sheet_js_1 = require("./commands/cheat-sheet.js");
25
+ const daemon_js_1 = require("./commands/daemon.js");
26
+ const install_js_1 = require("./commands/install.js");
27
+ const device_pool_js_1 = require("./commands/device-pool.js");
28
+ const run_parallel_js_1 = require("./commands/run-parallel.js");
29
+ const foreground_app_js_1 = require("./commands/foreground-app.js");
30
+ const list_apps_js_1 = require("./commands/list-apps.js");
31
+ const erase_text_js_1 = require("./commands/erase-text.js");
32
+ const assert_not_visible_js_1 = require("./commands/assert-not-visible.js");
33
+ const open_link_js_1 = require("./commands/open-link.js");
34
+ const hide_keyboard_js_1 = require("./commands/hide-keyboard.js");
35
+ const scroll_until_visible_js_1 = require("./commands/scroll-until-visible.js");
36
+ const set_location_js_1 = require("./commands/set-location.js");
37
+ const set_orientation_js_1 = require("./commands/set-orientation.js");
38
+ const start_device_js_1 = require("./commands/start-device.js");
39
+ const runner_js_1 = require("./runner.js");
40
+ const update_check_js_1 = require("./update-check.js");
41
+ const HELP = `
42
+ Usage: conductor <command> [args] [options]
43
+
44
+ Commands:
45
+ start-device --platform <ios|android> Boot a simulator or emulator
46
+ --os-version <n> iOS version (e.g. 18) or Android API level (e.g. 33)
47
+ --avd <name> Android AVD name (default: first available)
48
+ list-devices List connected devices/simulators
49
+ foreground-app Print bundle ID / package of the foreground app
50
+ list-apps List installed app IDs / package names
51
+ launch-app <appId> Launch app (saves to session)
52
+ --clear-state Clear app data/state before launching
53
+ --clear-keychain Clear keychain before launching
54
+ --no-stop-app Do not stop the app before launching (resume instead of restart)
55
+ --argument key=value Set launch argument (repeatable)
56
+ stop-app [<appId>] Stop app
57
+ tap <element> Tap element by text or id
58
+ --id <id> Match by accessibility id instead of text
59
+ --text <text> Match by text only (not id)
60
+ --index <n> Pick the nth match (0-based)
61
+ --long-press Hold instead of tap
62
+ --double-tap Double-tap the element
63
+ --optional Do not fail if element is not found
64
+ --focused Match only focused elements
65
+ --enabled / --no-enabled Match by enabled state
66
+ --checked / --no-checked Match by checked state
67
+ --selected / --no-selected Match by selected state
68
+ --below <text> Match element below the given reference
69
+ --above <text> Match element above the given reference
70
+ --left-of <text> Match element left of the given reference
71
+ --right-of <text> Match element right of the given reference
72
+ --verbose Log all candidates and chosen element
73
+ type <text> Type text into focused field
74
+ erase-text [n] Erase n characters (default: 50)
75
+ back Press back button
76
+ press-key <key> Press a key (Enter, Backspace, Home, ...)
77
+ hide-keyboard Dismiss the on-screen keyboard
78
+ scroll [--direction down|up|left|right]
79
+ swipe --direction <UP|DOWN|LEFT|RIGHT>
80
+ --start <x,y> Start coordinate (0–1 normalised or absolute px)
81
+ --end <x,y> End coordinate (0–1 normalised or absolute px)
82
+ --duration <ms> Swipe duration in milliseconds (default: 500)
83
+ scroll-until-visible <element> Scroll until element is visible
84
+ --id <id> Match by accessibility id instead of text
85
+ --text <text> Match by text only (not id)
86
+ --direction <down|up|left|right> Scroll direction (default: down)
87
+ --timeout <ms> Max time in milliseconds (default: 30000)
88
+ assert-visible <element> Assert element is visible
89
+ --id <id> Match by accessibility id instead of text
90
+ --text <text> Match by text only (not id)
91
+ --index <n> Pick the nth match (0-based)
92
+ --timeout <ms> Max wait time in milliseconds
93
+ --optional Do not fail if element is not found
94
+ --focused Match only focused elements
95
+ --enabled / --no-enabled Match by enabled state
96
+ --checked / --no-checked Match by checked state
97
+ --selected / --no-selected Match by selected state
98
+ --below <text> Match element below the given reference
99
+ --above <text> Match element above the given reference
100
+ --left-of <text> Match element left of the given reference
101
+ --right-of <text> Match element right of the given reference
102
+ assert-not-visible <element> Assert element is absent from screen
103
+ --id <id> Match by accessibility id instead of text
104
+ --text <text> Match by text only (not id)
105
+ --timeout <ms> Max check time in milliseconds (default: 1000)
106
+ open-link <url> Open a URL / deep link
107
+ set-location --lat <n> --lng <n> Set GPS coordinates
108
+ set-orientation <portrait|landscape> Set device orientation
109
+ screenshot [--output <path>] Take screenshot
110
+ inspect Print UI hierarchy
111
+ run-flow <file> [--device <id>] Run a Maestro YAML flow file
112
+ --env KEY=VALUE Inject env var (repeatable; overrides flow env block)
113
+ --benchmark Print elapsed time for each command and total flow time
114
+ run-flow-inline <yaml> Run inline YAML commands
115
+ --benchmark Print elapsed time for each command and total flow time
116
+ session [--clear] [--list] Show, clear, or list device sessions
117
+ cheat-sheet Print command reference
118
+ install Install/reinstall Claude Code plugin
119
+ daemon-start Start background daemon (manages driver process)
120
+ daemon-stop [--all] Stop background daemon (--all stops every session's daemon)
121
+ daemon-status Show daemon status
122
+ device-pool --list List all devices and pool status
123
+ device-pool --acquire Claim a free device (prints device ID)
124
+ device-pool --release <id> Release a device back to the pool
125
+ run-parallel --flows-dir <path> Run flows in parallel across all devices
126
+
127
+ Options:
128
+ --device <id> Target device ID (also keys the session and daemon)
129
+ --json Output as machine-readable JSON
130
+ --verbose, -v Log daemon calls, fallbacks, and raw output
131
+ --help, -h Show this help
132
+ `.trim();
133
+ async function main() {
134
+ (0, update_check_js_1.checkForUpdates)();
135
+ const argv = (0, minimist_1.default)(process.argv.slice(2), {
136
+ boolean: [
137
+ 'json',
138
+ 'help',
139
+ 'clear',
140
+ 'list',
141
+ 'verbose',
142
+ 'skills',
143
+ 'all',
144
+ 'acquire',
145
+ 'release',
146
+ 'clear-state',
147
+ 'clear-keychain',
148
+ 'stop-app',
149
+ 'long-press',
150
+ 'double-tap',
151
+ 'optional',
152
+ 'benchmark',
153
+ ],
154
+ string: [
155
+ 'device',
156
+ 'output',
157
+ 'direction',
158
+ 'flows-dir',
159
+ 'yaml',
160
+ 'id',
161
+ 'text',
162
+ 'start',
163
+ 'end',
164
+ 'env',
165
+ 'below',
166
+ 'above',
167
+ 'left-of',
168
+ 'right-of',
169
+ 'lat',
170
+ 'lng',
171
+ 'platform',
172
+ 'os-version',
173
+ 'avd',
174
+ ],
175
+ alias: { h: 'help', v: 'verbose' },
176
+ });
177
+ if (argv['verbose'])
178
+ (0, verbose_js_1.setVerbose)(true);
179
+ const [command, ...rest] = argv._;
180
+ const opts = { json: argv['json'] };
181
+ // The device ID is the natural key for both the session file and the daemon.
182
+ // Use --device if given, otherwise detect the first booted device, otherwise 'default'.
183
+ const explicitDevice = argv['device'];
184
+ const sessionName = explicitDevice ?? (await (0, runner_js_1.detectFirstDevice)()) ?? 'default';
185
+ if (!command || argv['help']) {
186
+ console.log(HELP);
187
+ process.exit(0);
188
+ }
189
+ let exitCode = 0;
190
+ switch (command) {
191
+ case 'start-device':
192
+ exitCode = await (0, start_device_js_1.startDevice)(argv['platform'], opts, {
193
+ osVersion: argv['os-version'],
194
+ avd: argv['avd'],
195
+ });
196
+ break;
197
+ case 'list-devices':
198
+ exitCode = await (0, list_devices_js_1.listDevices)(opts);
199
+ break;
200
+ case 'foreground-app':
201
+ exitCode = await (0, foreground_app_js_1.foregroundApp)(opts, sessionName);
202
+ break;
203
+ case 'list-apps':
204
+ exitCode = await (0, list_apps_js_1.listApps)(opts, sessionName);
205
+ break;
206
+ case 'launch-app': {
207
+ const appId = rest[0] ?? '';
208
+ // --argument key=value (repeatable) → Record<string, string>
209
+ const rawArgs = argv['argument'] ?? argv['arg'];
210
+ const argPairs = Array.isArray(rawArgs) ? rawArgs : rawArgs ? [rawArgs] : [];
211
+ const launchArgs = argPairs.length
212
+ ? Object.fromEntries(argPairs.map((a) => a.split('=', 2)))
213
+ : undefined;
214
+ exitCode = await (0, launch_app_js_1.launchApp)(appId, argv['device'], opts, sessionName, {
215
+ clearState: argv['clear-state'],
216
+ clearKeychain: argv['clear-keychain'],
217
+ stopApp: argv['stop-app'] !== false,
218
+ launchArgs,
219
+ });
220
+ break;
221
+ }
222
+ case 'stop-app': {
223
+ const appId = rest[0];
224
+ exitCode = await (0, stop_app_js_1.stopApp)(appId, opts, sessionName);
225
+ break;
226
+ }
227
+ case 'tap': {
228
+ const element = rest.join(' ');
229
+ exitCode = await (0, tap_js_1.tap)(element, opts, sessionName, {
230
+ id: argv['id'],
231
+ text: argv['text'],
232
+ index: argv['index'] !== undefined ? Number(argv['index']) : undefined,
233
+ longPress: argv['long-press'],
234
+ doubleTap: argv['double-tap'],
235
+ optional: argv['optional'],
236
+ focused: argv['focused'] !== undefined ? argv['focused'] : undefined,
237
+ enabled: argv['enabled'] !== undefined ? argv['enabled'] : undefined,
238
+ checked: argv['checked'] !== undefined ? argv['checked'] : undefined,
239
+ selected: argv['selected'] !== undefined ? argv['selected'] : undefined,
240
+ below: argv['below'],
241
+ above: argv['above'],
242
+ leftOf: argv['left-of'],
243
+ rightOf: argv['right-of'],
244
+ });
245
+ break;
246
+ }
247
+ case 'type': {
248
+ const text = rest.join(' ');
249
+ exitCode = await (0, type_js_1.typeText)(text, opts, sessionName);
250
+ break;
251
+ }
252
+ case 'erase-text': {
253
+ const n = rest[0] !== undefined
254
+ ? Number(rest[0])
255
+ : argv['characters'] !== undefined
256
+ ? Number(argv['characters'])
257
+ : 50;
258
+ exitCode = await (0, erase_text_js_1.eraseText)(n, opts, sessionName);
259
+ break;
260
+ }
261
+ case 'back':
262
+ exitCode = await (0, back_js_1.back)(opts, sessionName);
263
+ break;
264
+ case 'hide-keyboard':
265
+ exitCode = await (0, hide_keyboard_js_1.hideKeyboard)(opts, sessionName);
266
+ break;
267
+ case 'press-key': {
268
+ const key = rest[0] ?? '';
269
+ exitCode = await (0, press_key_js_1.pressKey)(key, opts, sessionName);
270
+ break;
271
+ }
272
+ case 'scroll': {
273
+ const dir = (argv['direction'] || 'down').toLowerCase();
274
+ exitCode = await (0, scroll_js_1.scroll)(dir, opts, sessionName);
275
+ break;
276
+ }
277
+ case 'swipe': {
278
+ const dir = argv['direction'] || '';
279
+ exitCode = await (0, swipe_js_1.swipe)(dir, opts, sessionName, {
280
+ start: argv['start'],
281
+ end: argv['end'],
282
+ duration: argv['duration'] !== undefined ? Number(argv['duration']) : undefined,
283
+ });
284
+ break;
285
+ }
286
+ case 'scroll-until-visible': {
287
+ const element = rest.join(' ');
288
+ const rawDir = (argv['direction'] || 'down').toLowerCase();
289
+ exitCode = await (0, scroll_until_visible_js_1.scrollUntilVisible)(element, opts, sessionName, {
290
+ id: argv['id'],
291
+ text: argv['text'],
292
+ index: argv['index'] !== undefined ? Number(argv['index']) : undefined,
293
+ direction: rawDir,
294
+ timeout: argv['timeout'] !== undefined ? Number(argv['timeout']) : undefined,
295
+ focused: argv['focused'] !== undefined ? argv['focused'] : undefined,
296
+ enabled: argv['enabled'] !== undefined ? argv['enabled'] : undefined,
297
+ checked: argv['checked'] !== undefined ? argv['checked'] : undefined,
298
+ selected: argv['selected'] !== undefined ? argv['selected'] : undefined,
299
+ });
300
+ break;
301
+ }
302
+ case 'assert-visible': {
303
+ const element = rest.join(' ');
304
+ exitCode = await (0, assert_visible_js_1.assertVisible)(element, opts, sessionName, {
305
+ id: argv['id'],
306
+ text: argv['text'],
307
+ index: argv['index'] !== undefined ? Number(argv['index']) : undefined,
308
+ timeout: argv['timeout'] !== undefined ? Number(argv['timeout']) : undefined,
309
+ optional: argv['optional'],
310
+ focused: argv['focused'] !== undefined ? argv['focused'] : undefined,
311
+ enabled: argv['enabled'] !== undefined ? argv['enabled'] : undefined,
312
+ checked: argv['checked'] !== undefined ? argv['checked'] : undefined,
313
+ selected: argv['selected'] !== undefined ? argv['selected'] : undefined,
314
+ below: argv['below'],
315
+ above: argv['above'],
316
+ leftOf: argv['left-of'],
317
+ rightOf: argv['right-of'],
318
+ });
319
+ break;
320
+ }
321
+ case 'assert-not-visible': {
322
+ const element = rest.join(' ');
323
+ exitCode = await (0, assert_not_visible_js_1.assertNotVisible)(element, opts, sessionName, {
324
+ id: argv['id'],
325
+ text: argv['text'],
326
+ index: argv['index'] !== undefined ? Number(argv['index']) : undefined,
327
+ timeout: argv['timeout'] !== undefined ? Number(argv['timeout']) : undefined,
328
+ focused: argv['focused'] !== undefined ? argv['focused'] : undefined,
329
+ enabled: argv['enabled'] !== undefined ? argv['enabled'] : undefined,
330
+ checked: argv['checked'] !== undefined ? argv['checked'] : undefined,
331
+ selected: argv['selected'] !== undefined ? argv['selected'] : undefined,
332
+ below: argv['below'],
333
+ above: argv['above'],
334
+ leftOf: argv['left-of'],
335
+ rightOf: argv['right-of'],
336
+ });
337
+ break;
338
+ }
339
+ case 'open-link': {
340
+ const url = rest[0] ?? argv['url'] ?? '';
341
+ exitCode = await (0, open_link_js_1.openLink)(url, opts, sessionName);
342
+ break;
343
+ }
344
+ case 'set-location': {
345
+ const lat = Number(argv['lat'] ?? argv['latitude']);
346
+ const lng = Number(argv['lng'] ?? argv['longitude']);
347
+ if (isNaN(lat) || isNaN(lng)) {
348
+ console.error('set-location requires --lat <n> --lng <n>');
349
+ exitCode = 1;
350
+ }
351
+ else {
352
+ exitCode = await (0, set_location_js_1.setLocation)(lat, lng, opts, sessionName);
353
+ }
354
+ break;
355
+ }
356
+ case 'set-orientation': {
357
+ const orientation = (rest[0] ??
358
+ argv['orientation'] ??
359
+ '').toLowerCase();
360
+ exitCode = await (0, set_orientation_js_1.setOrientation)(orientation, opts, sessionName);
361
+ break;
362
+ }
363
+ case 'screenshot': {
364
+ const outPath = argv['output'];
365
+ exitCode = await (0, screenshot_js_1.screenshot)(outPath, opts, sessionName);
366
+ break;
367
+ }
368
+ case 'inspect':
369
+ exitCode = await (0, inspect_js_1.inspect)(opts, sessionName);
370
+ break;
371
+ case 'run-flow': {
372
+ const file = rest[0] ?? '';
373
+ const rawEnv = argv['env'];
374
+ const envPairs = Array.isArray(rawEnv) ? rawEnv : rawEnv ? [rawEnv] : [];
375
+ const flowEnv = Object.fromEntries(envPairs.map((e) => e.split('=', 2)));
376
+ exitCode = await (0, run_flow_js_1.runFlow)(file, opts, sessionName, flowEnv, argv['benchmark']);
377
+ break;
378
+ }
379
+ case 'run-flow-inline': {
380
+ const yaml = argv['yaml'] || rest.join(' ');
381
+ exitCode = await (0, run_flow_inline_js_1.runFlowInline)(yaml, opts, sessionName, argv['benchmark']);
382
+ break;
383
+ }
384
+ case 'session':
385
+ exitCode = await (0, session_js_1.sessionCmd)(argv['clear'], argv['list'], opts, sessionName);
386
+ break;
387
+ case 'cheat-sheet':
388
+ exitCode = await (0, cheat_sheet_js_1.cheatSheet)();
389
+ break;
390
+ case 'install':
391
+ exitCode = await (0, install_js_1.installSkills)(opts);
392
+ break;
393
+ case 'daemon-start':
394
+ exitCode = await (0, daemon_js_1.daemonStart)(opts, sessionName);
395
+ break;
396
+ case 'daemon-stop':
397
+ exitCode = await (0, daemon_js_1.daemonStop)(opts, sessionName, argv['all']);
398
+ break;
399
+ case 'daemon-status':
400
+ exitCode = await (0, daemon_js_1.daemonStatusCmd)(opts, sessionName);
401
+ break;
402
+ case 'device-pool': {
403
+ const acquire = argv['acquire'];
404
+ const release = argv['release'];
405
+ const releaseId = typeof argv['release'] === 'string' ? argv['release'] : rest[0];
406
+ const action = acquire ? 'acquire' : release || releaseId ? 'release' : 'list';
407
+ exitCode = await (0, device_pool_js_1.devicePool)(action, releaseId, opts);
408
+ break;
409
+ }
410
+ case 'run-parallel': {
411
+ const flowsDir = argv['flows-dir'] ?? rest[0] ?? '';
412
+ exitCode = await (0, run_parallel_js_1.runParallel)(flowsDir, opts);
413
+ break;
414
+ }
415
+ default:
416
+ console.error(`Unknown command: ${command}`);
417
+ console.error('Run `conductor --help` for usage.');
418
+ exitCode = 1;
419
+ }
420
+ process.exit(exitCode);
421
+ }
422
+ main().catch((err) => {
423
+ const message = err instanceof Error ? err.message : String(err);
424
+ console.error(`Error: ${message}`);
425
+ process.exit(1);
426
+ });
package/dist/output.js ADDED
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printSuccess = printSuccess;
4
+ exports.printError = printError;
5
+ exports.printData = printData;
6
+ exports.exit = exit;
7
+ function printSuccess(message, opts) {
8
+ if (opts?.json) {
9
+ console.log(JSON.stringify({ status: 'ok', message }));
10
+ }
11
+ else {
12
+ console.log(`✓ ${message}`);
13
+ }
14
+ }
15
+ function printError(message, opts) {
16
+ if (opts?.json) {
17
+ console.log(JSON.stringify({ status: 'error', message }));
18
+ }
19
+ else {
20
+ console.error(`✗ ${message}`);
21
+ }
22
+ }
23
+ function printData(data, opts) {
24
+ if (opts?.json) {
25
+ console.log(JSON.stringify(data));
26
+ }
27
+ else if (typeof data === 'string') {
28
+ console.log(data);
29
+ }
30
+ else {
31
+ console.log(JSON.stringify(data, null, 2));
32
+ }
33
+ }
34
+ function exit(code) {
35
+ process.exit(code);
36
+ }
@@ -0,0 +1,28 @@
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.findPkgRoot = findPkgRoot;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ /**
10
+ * Walk up from __dirname to find the package root (the directory containing
11
+ * package.json). This handles both the normal build (dist/...) and the test
12
+ * build (dist-tests/packages/cli/src/...) where __dirname has extra path
13
+ * levels, making a fixed relative path incorrect.
14
+ */
15
+ function findPkgRoot(fromDir) {
16
+ let dir = fromDir;
17
+ while (true) {
18
+ if (fs_1.default.existsSync(path_1.default.join(dir, 'package.json'))) {
19
+ return dir;
20
+ }
21
+ const parent = path_1.default.dirname(dir);
22
+ if (parent === dir)
23
+ break;
24
+ dir = parent;
25
+ }
26
+ // Fallback: two levels up from the file's directory (dist/commands/ → pkg root)
27
+ return path_1.default.join(fromDir, '..', '..');
28
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const install_js_1 = require("./commands/install.js");
4
+ // Runs as `postinstall` after `npm install -g @houwert/conductor`.
5
+ // Silent on failure so it never breaks the npm install.
6
+ try {
7
+ (0, install_js_1.installPlugin)();
8
+ process.stdout.write('✓ Conductor Claude Code plugin installed\n');
9
+ }
10
+ catch {
11
+ // Silent on failure — never break npm install
12
+ }