appostle-installer 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oh Lord Agency
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # appostle-installer
2
+
3
+ One-shot installer for the Appostle daemon and the agent CLIs it talks to (Claude Code, Codex, OpenCode, Copilot, Pi). Sets up the daemon on your machine and pairs it with [appostle.app](https://appostle.app), where you control your agents from the web app, mobile app, or any paired client.
4
+
5
+ ```bash
6
+ npx appostle-installer
7
+ ```
8
+
9
+ ## What it does
10
+
11
+ 1. Asks which agent CLIs you want (Claude Code, Codex, Copilot, OpenCode, Pi)
12
+ 2. Installs each one (idempotent — skips anything already present)
13
+ 3. Walks you through each CLI's native auth flow
14
+ 4. Installs the daemon runtime (`@appostle/cli` from public npm)
15
+ 5. Configures auto-start so the daemon connects to [pair.appostle.app](https://pair.appostle.app) on every boot
16
+ 6. Prints a pairing link + QR — paste into [appostle.app](https://appostle.app)
17
+
18
+ ## Updating an existing install
19
+
20
+ Already paired and just want a newer daemon? Run the installer with `--update`:
21
+
22
+ ```bash
23
+ npx appostle-installer@latest --update
24
+ ```
25
+
26
+ That skips provider selection, auth, and the pairing step. It only:
27
+ - Reinstalls `@appostle/cli@latest`
28
+ - Re-applies the OS auto-start config (which restarts the running service)
29
+
30
+ Safe to run from cron / a scheduled task for unattended upgrades.
31
+
32
+ ## Re-show the pairing link
33
+
34
+ Need to pair another device or browser and don't have the link handy? Run:
35
+
36
+ ```bash
37
+ npx appostle-installer@latest --pair
38
+ ```
39
+
40
+ Prints a fresh QR + link for the local daemon and exits. No installs, no service changes — just the pairing artifact.
41
+
42
+ ## Other flags
43
+
44
+ | Flag | Effect |
45
+ |---|---|
46
+ | `--yes`, `-y` | Skip every interactive prompt; install daemon only (no providers, no auth, no pairing print). Good for CI / Dockerfile bootstrap. |
47
+ | `--providers=a,b,c` | Pre-pick a comma-separated list of provider IDs (e.g. `claude-code,codex`) and skip the multiselect. |
48
+ | `--no-auth` | Install providers without running their auth flows. Pair with `--providers=…`. |
49
+ | `--update` | Update-only mode (above). Implies non-interactive. |
50
+ | `--pair` | Print a fresh pairing link/QR and exit. Daemon must already be installed. |
51
+
52
+ ## Architecture
53
+
54
+ The daemon is the Appostle runtime (`@appostle/cli`), wired to Appostle's own relay and branded web UI.
55
+
56
+ ```
57
+ Your machine Our infra
58
+ ──────────── ─────────
59
+ appostle daemon ──(E2EE WS)──▶ pair.appostle.app (Cloudflare Worker relay)
60
+
61
+
62
+ browser ─────────▶ appostle.app (web UI, served from devportal)
63
+ ```
64
+
65
+ Traffic is E2E-encrypted — the relay only forwards ciphertext.
66
+
67
+ ## Platform support
68
+
69
+ | OS | Auto-start | Notes |
70
+ |---|---|---|
71
+ | macOS | launchd agent at `~/Library/LaunchAgents/app.appostle.daemon.plist` | tested |
72
+ | Linux | systemd user unit at `~/.config/systemd/user/appostle.service` | run `loginctl enable-linger $USER` to keep it up when logged out |
73
+ | Windows | `.vbs` launcher in Startup folder | ⚠️ **beta — unverified**. Code path is written but hasn't been dry-run on a real Windows machine yet. Please [open an issue](https://gitlab.com/ohlord-suite/appostle-daemon-installer/-/issues) if you hit problems. No restart-on-crash; upgrade to a Windows Service later if needed. |
74
+
75
+ ## Requirements
76
+
77
+ - Node.js 20+ and npm on PATH. Install from [nodejs.org](https://nodejs.org) if missing.
78
+ - A shell (bash/zsh/PowerShell).
79
+
80
+ ## Development
81
+
82
+ ```bash
83
+ git clone https://gitlab.com/ohlord-suite/appostle-daemon-installer.git
84
+ cd appostle-daemon-installer
85
+ npm install
86
+ npm run dev # live-run without building
87
+ npm run typecheck
88
+ npm run build # compile to dist/
89
+ ```
90
+
91
+ ## Publishing
92
+
93
+ ```bash
94
+ npm publish --access public
95
+ ```
96
+
97
+ Or push a `v*` tag to trigger the GitLab CI publish job.
98
+
99
+ ## License
100
+
101
+ MIT — but the daemon it installs ([@appostle/cli](https://www.npmjs.com/package/@appostle/cli)) is AGPL-3.0.
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ // Entry point for `npx appostle-installer`.
3
+
4
+ // Defensive Node version check.
5
+ // We rely on `node:util#styleText` (added in Node 20.12) via @clack/core.
6
+ // `engines` in package.json only warns on most installs; this is the hard gate.
7
+ const [major, minor] = process.versions.node.split(".").map(Number);
8
+ if (major < 20 || (major === 20 && minor < 12)) {
9
+ console.error(
10
+ `\n appostle-installer requires Node.js >= 20.12.0` +
11
+ `\n Detected: ${process.versions.node}` +
12
+ `\n` +
13
+ `\n Upgrade via your OS package manager, or install Node 22 LTS:` +
14
+ `\n macOS: brew install node@22` +
15
+ `\n Linux: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt install -y nodejs` +
16
+ `\n Windows: https://nodejs.org/en/download` +
17
+ `\n`,
18
+ );
19
+ process.exit(1);
20
+ }
21
+
22
+ import("../dist/appostle-installer.js");
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ // Entry point for the `appostle` CLI binary that ships inside
3
+ // appostle-installer. Same node version gate as appostle-installer.
4
+ const [major, minor] = process.versions.node.split(".").map(Number);
5
+ if (major < 20 || (major === 20 && minor < 12)) {
6
+ console.error(
7
+ `\n appostle requires Node.js >= 20.12.0` +
8
+ `\n Detected: ${process.versions.node}` +
9
+ `\n` +
10
+ `\n Upgrade via your OS package manager, or install Node 22 LTS:` +
11
+ `\n macOS: brew install node@22` +
12
+ `\n Linux: curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt install -y nodejs` +
13
+ `\n Windows: https://nodejs.org/en/download` +
14
+ `\n`,
15
+ );
16
+ process.exit(1);
17
+ }
18
+
19
+ import("../dist/appostle.js");
@@ -0,0 +1,490 @@
1
+ import { createRequire as __ac_createRequire } from "node:module";
2
+ const require = __ac_createRequire(import.meta.url);
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __esm = (fn, res) => function __init() {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ };
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+
13
+ // src/config.ts
14
+ var config_exports = {};
15
+ __export(config_exports, {
16
+ APPOSTLE_RELAY_ENDPOINT: () => APPOSTLE_RELAY_ENDPOINT,
17
+ APPOSTLE_WEB_URL: () => APPOSTLE_WEB_URL,
18
+ INSTALLER_PACKAGE: () => INSTALLER_PACKAGE
19
+ });
20
+ var APPOSTLE_RELAY_ENDPOINT, APPOSTLE_WEB_URL, INSTALLER_PACKAGE;
21
+ var init_config = __esm({
22
+ "src/config.ts"() {
23
+ "use strict";
24
+ APPOSTLE_RELAY_ENDPOINT = "pair.appostle.app:443";
25
+ APPOSTLE_WEB_URL = "https://appostle.app";
26
+ INSTALLER_PACKAGE = "appostle-installer";
27
+ }
28
+ });
29
+
30
+ // src/index.ts
31
+ init_config();
32
+ import {
33
+ cancel,
34
+ confirm,
35
+ intro,
36
+ isCancel,
37
+ log,
38
+ multiselect,
39
+ note,
40
+ outro,
41
+ spinner
42
+ } from "@clack/prompts";
43
+ import chalk from "chalk";
44
+
45
+ // src/daemon-setup.ts
46
+ init_config();
47
+
48
+ // src/util.ts
49
+ import { execa } from "execa";
50
+ import { accessSync, constants } from "node:fs";
51
+ import { platform } from "node:os";
52
+ import which from "which";
53
+ function currentPlatform() {
54
+ const p = platform();
55
+ if (p === "darwin" || p === "linux" || p === "win32") return p;
56
+ throw new Error(`Unsupported platform: ${p}`);
57
+ }
58
+ async function has(binary) {
59
+ try {
60
+ await which(binary);
61
+ return true;
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
66
+ async function resolveBinary(binary) {
67
+ try {
68
+ return await which(binary);
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+ async function runInteractive(command, args = [], options = {}) {
74
+ await execa(command, args, { stdio: "inherit", ...options });
75
+ }
76
+ var cachedNeedsSudo = null;
77
+ async function npmGlobalNeedsSudo() {
78
+ if (cachedNeedsSudo !== null) return cachedNeedsSudo;
79
+ if (currentPlatform() === "win32") return cachedNeedsSudo = false;
80
+ if (process.getuid?.() === 0) return cachedNeedsSudo = false;
81
+ try {
82
+ const { stdout } = await execa("npm", ["prefix", "-g"]);
83
+ const prefix = stdout.trim();
84
+ if (!prefix) return cachedNeedsSudo = false;
85
+ accessSync(prefix, constants.W_OK);
86
+ return cachedNeedsSudo = false;
87
+ } catch {
88
+ return cachedNeedsSudo = true;
89
+ }
90
+ }
91
+ async function npmInstallGlobal(packages) {
92
+ const useSudo = await npmGlobalNeedsSudo();
93
+ const args = ["install", "-g", ...packages];
94
+ if (useSudo) {
95
+ await runInteractive("sudo", ["-E", "npm", ...args]);
96
+ } else {
97
+ await runInteractive("npm", args);
98
+ }
99
+ }
100
+
101
+ // src/daemon-setup.ts
102
+ async function installAppostleGlobal() {
103
+ await npmInstallGlobal([`${INSTALLER_PACKAGE}@latest`]);
104
+ }
105
+ async function installAutostart() {
106
+ const os = currentPlatform();
107
+ if (os === "darwin") return installLaunchd();
108
+ if (os === "linux") return installSystemdUser();
109
+ if (os === "win32") return installWindowsStartup();
110
+ }
111
+ async function installLaunchd() {
112
+ const fs = await import("node:fs/promises");
113
+ const path = await import("node:path");
114
+ const osHome = (await import("node:os")).homedir();
115
+ const plistPath = path.join(osHome, "Library", "LaunchAgents", "agency.ohlord.appostle.plist");
116
+ const logPath = path.join(osHome, "Library", "Logs", "appostle.log");
117
+ const appostleBin = await resolveBinary("appostle") ?? "/usr/local/bin/appostle";
118
+ const plist = `<?xml version="1.0" encoding="UTF-8"?>
119
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
120
+ <plist version="1.0">
121
+ <dict>
122
+ <key>Label</key><string>agency.ohlord.appostle</string>
123
+ <key>ProgramArguments</key>
124
+ <array>
125
+ <string>${appostleBin}</string>
126
+ <string>daemon</string>
127
+ <string>start</string>
128
+ <string>--foreground</string>
129
+ </array>
130
+ <key>RunAtLoad</key><true/>
131
+ <key>KeepAlive</key><true/>
132
+ <key>StandardOutPath</key><string>${logPath}</string>
133
+ <key>StandardErrorPath</key><string>${logPath}</string>
134
+ <key>EnvironmentVariables</key>
135
+ <dict>
136
+ <key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
137
+ <key>HOME</key><string>${osHome}</string>
138
+ <key>NODE_ENV</key><string>production</string>
139
+ <key>APPOSTLE_RELAY_ENDPOINT</key><string>${APPOSTLE_RELAY_ENDPOINT}</string>
140
+ </dict>
141
+ </dict>
142
+ </plist>
143
+ `;
144
+ await fs.mkdir(path.dirname(plistPath), { recursive: true });
145
+ await fs.writeFile(plistPath, plist);
146
+ await writeAppostleConfig(osHome);
147
+ try {
148
+ await runInteractive("launchctl", ["unload", plistPath], { stdio: "ignore" });
149
+ } catch {
150
+ }
151
+ await runInteractive("launchctl", ["load", plistPath]);
152
+ }
153
+ async function writeAppostleConfig(osHome) {
154
+ const fs = await import("node:fs/promises");
155
+ const path = await import("node:path");
156
+ const configDir = path.join(osHome, ".appostle");
157
+ const configPath = path.join(configDir, "config.json");
158
+ const config = {
159
+ version: 1,
160
+ daemon: {
161
+ listen: "127.0.0.1:6767",
162
+ cors: { allowedOrigins: [APPOSTLE_WEB_URL] },
163
+ relay: { enabled: true, endpoint: APPOSTLE_RELAY_ENDPOINT }
164
+ },
165
+ app: { baseUrl: APPOSTLE_WEB_URL }
166
+ };
167
+ await fs.mkdir(configDir, { recursive: true });
168
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2));
169
+ }
170
+ async function installSystemdUser() {
171
+ const fs = await import("node:fs/promises");
172
+ const path = await import("node:path");
173
+ const osHome = (await import("node:os")).homedir();
174
+ const unitDir = path.join(osHome, ".config", "systemd", "user");
175
+ const unitPath = path.join(unitDir, "appostle.service");
176
+ const appostleBin = await resolveBinary("appostle") ?? "/usr/bin/appostle";
177
+ const logPath = path.join(osHome, ".local", "state", "appostle", "daemon.log");
178
+ const unit = `[Unit]
179
+ Description=Appostle Daemon
180
+ After=network.target
181
+
182
+ [Service]
183
+ Type=simple
184
+ ExecStart=${appostleBin} daemon start --foreground
185
+ Restart=on-failure
186
+ RestartSec=10
187
+ Environment=NODE_ENV=production
188
+ Environment=HOME=${osHome}
189
+ Environment=APPOSTLE_RELAY_ENDPOINT=${APPOSTLE_RELAY_ENDPOINT}
190
+ StandardOutput=append:${logPath}
191
+ StandardError=append:${logPath}
192
+
193
+ [Install]
194
+ WantedBy=default.target
195
+ `;
196
+ await fs.mkdir(unitDir, { recursive: true });
197
+ await fs.mkdir(path.dirname(logPath), { recursive: true });
198
+ await fs.writeFile(unitPath, unit);
199
+ await writeAppostleConfig(osHome);
200
+ await runInteractive("systemctl", ["--user", "daemon-reload"]);
201
+ await runInteractive("systemctl", ["--user", "enable", "--now", "appostle.service"]);
202
+ }
203
+ async function installWindowsStartup() {
204
+ const fs = await import("node:fs/promises");
205
+ const path = await import("node:path");
206
+ const osHome = (await import("node:os")).homedir();
207
+ const appostleBin = await resolveBinary("appostle") ?? "appostle.cmd";
208
+ const logDir = path.join(osHome, "AppData", "Local", "Appostle");
209
+ const logPath = path.join(logDir, "daemon.log");
210
+ const batPath = path.join(logDir, "appostle-daemon.bat");
211
+ const vbsPath = path.join(
212
+ osHome,
213
+ "AppData",
214
+ "Roaming",
215
+ "Microsoft",
216
+ "Windows",
217
+ "Start Menu",
218
+ "Programs",
219
+ "Startup",
220
+ "appostle.vbs"
221
+ );
222
+ const bat = `@echo off
223
+ set NODE_ENV=production
224
+ set APPOSTLE_RELAY_ENDPOINT=${APPOSTLE_RELAY_ENDPOINT}
225
+ "${appostleBin}" daemon start --foreground >> "${logPath}" 2>&1
226
+ `;
227
+ const vbs = `Set WshShell = CreateObject("WScript.Shell")
228
+ WshShell.Run """${batPath}""", 0, False
229
+ `;
230
+ await fs.mkdir(logDir, { recursive: true });
231
+ await fs.mkdir(path.dirname(vbsPath), { recursive: true });
232
+ await fs.writeFile(batPath, bat);
233
+ await fs.writeFile(vbsPath, vbs);
234
+ await writeAppostleConfig(osHome);
235
+ await runInteractive("wscript", [vbsPath]);
236
+ }
237
+ async function printPairingLink() {
238
+ const { execa: execa2 } = await import("execa");
239
+ const { APPOSTLE_WEB_URL: APPOSTLE_WEB_URL2 } = await Promise.resolve().then(() => (init_config(), config_exports));
240
+ const result = await execa2("appostle", ["daemon", "pair"], { reject: false });
241
+ const raw = (typeof result.stdout === "string" ? result.stdout : "") + (typeof result.stderr === "string" ? result.stderr : "");
242
+ const rewritten = raw.replace(/https?:\/\/[^/]+\/#offer=/g, `${APPOSTLE_WEB_URL2}/#offer=`);
243
+ process.stdout.write(rewritten);
244
+ }
245
+
246
+ // src/providers.ts
247
+ var claude = {
248
+ id: "claude",
249
+ label: "Claude Code",
250
+ description: "Anthropic's agent CLI (MCP, streaming, deep reasoning)",
251
+ async detect() {
252
+ return has("claude");
253
+ },
254
+ async install() {
255
+ await npmInstallGlobal(["@anthropic-ai/claude-code@latest"]);
256
+ },
257
+ async auth() {
258
+ await runInteractive("claude", ["/login"]);
259
+ }
260
+ };
261
+ var codex = {
262
+ id: "codex",
263
+ label: "Codex",
264
+ description: "OpenAI's Codex workspace agent",
265
+ async detect() {
266
+ return has("codex");
267
+ },
268
+ async install() {
269
+ await npmInstallGlobal(["@openai/codex@latest"]);
270
+ },
271
+ async auth() {
272
+ await runInteractive("codex", ["login"]);
273
+ }
274
+ };
275
+ var copilot = {
276
+ id: "copilot",
277
+ label: "GitHub Copilot",
278
+ description: "Standalone Copilot CLI with ACP support",
279
+ async detect() {
280
+ return has("copilot");
281
+ },
282
+ async install() {
283
+ await npmInstallGlobal(["@github/copilot@latest"]);
284
+ },
285
+ async auth() {
286
+ await runInteractive("copilot", ["auth", "login"]);
287
+ }
288
+ };
289
+ var opencode = {
290
+ id: "opencode",
291
+ label: "OpenCode",
292
+ description: "Open-source multi-provider coding assistant",
293
+ async detect() {
294
+ return has("opencode");
295
+ },
296
+ async install() {
297
+ await npmInstallGlobal(["opencode-ai@latest"]);
298
+ },
299
+ async auth() {
300
+ await runInteractive("opencode", ["auth", "login"]);
301
+ }
302
+ };
303
+ var pi = {
304
+ id: "pi",
305
+ label: "Pi",
306
+ description: "Minimal terminal-based agent with multi-provider LLM support",
307
+ async detect() {
308
+ return has("pi");
309
+ },
310
+ async install() {
311
+ await npmInstallGlobal(["@mariozechner/pi-coding-agent@latest"]);
312
+ },
313
+ async auth() {
314
+ await runInteractive("pi", ["login"]);
315
+ }
316
+ };
317
+ var PROVIDERS = [claude, codex, copilot, opencode, pi];
318
+
319
+ // src/index.ts
320
+ function parseArgs() {
321
+ const args = process.argv.slice(2);
322
+ const yes = args.includes("--yes") || args.includes("-y");
323
+ const noAuth = args.includes("--no-auth");
324
+ const update = args.includes("--update");
325
+ const pair = args.includes("--pair");
326
+ const providersArg = args.find((a) => a.startsWith("--providers="));
327
+ const providerIds = providersArg ? providersArg.replace("--providers=", "").split(",").map((s) => s.trim()).filter(Boolean) : [];
328
+ return { yes, providerIds, noAuth, update, pair };
329
+ }
330
+ async function main() {
331
+ const { yes, providerIds, noAuth, update, pair } = parseArgs();
332
+ intro(
333
+ chalk.magentaBright("Appostle") + chalk.dim(pair ? " \u2014 pairing link" : update ? " \u2014 daemon updater" : " \u2014 daemon installer")
334
+ );
335
+ const os = currentPlatform();
336
+ log.info(`Detected OS: ${os}`);
337
+ if (!await has("npm")) {
338
+ log.error("`npm` not found on PATH. Install Node.js from https://nodejs.org and re-run.");
339
+ process.exit(1);
340
+ }
341
+ if (pair) {
342
+ note(`Paste the pairing link below into ${APPOSTLE_WEB_URL} (or scan the QR).`, "Pair");
343
+ try {
344
+ await printPairingLink();
345
+ } catch (err) {
346
+ log.error(`Could not generate pairing link: ${err.message}`);
347
+ log.info("Is the daemon installed? If not, run `npx appostle-installer` first.");
348
+ process.exit(1);
349
+ }
350
+ outro(chalk.green("Done."));
351
+ return;
352
+ }
353
+ if (update) {
354
+ const us = spinner();
355
+ us.start("Updating Appostle (npm install -g appostle-installer@latest)\u2026");
356
+ try {
357
+ await installAppostleGlobal();
358
+ us.stop("Daemon package up to date");
359
+ } catch (err) {
360
+ us.stop("Daemon update failed");
361
+ log.error(err.message);
362
+ process.exit(1);
363
+ }
364
+ us.start(`Re-applying auto-start (${os}) to restart service\u2026`);
365
+ try {
366
+ await installAutostart();
367
+ us.stop("Auto-start refreshed; service restarted");
368
+ } catch (err) {
369
+ us.stop("Auto-start refresh failed");
370
+ log.error(err.message);
371
+ process.exit(1);
372
+ }
373
+ outro(chalk.green("Update complete."));
374
+ return;
375
+ }
376
+ let chosen = [];
377
+ if (yes && providerIds.length === 0) {
378
+ log.info("--yes: skipping provider selection");
379
+ } else if (providerIds.length > 0) {
380
+ chosen = PROVIDERS.filter((p) => providerIds.includes(p.id));
381
+ const unknown = providerIds.filter((id) => !PROVIDERS.find((p) => p.id === id));
382
+ if (unknown.length > 0) log.warn(`Unknown provider ids ignored: ${unknown.join(", ")}`);
383
+ log.info(`Providers to install: ${chosen.map((p) => p.label).join(", ")}`);
384
+ } else {
385
+ const selection = await multiselect({
386
+ message: "Which agent CLIs do you want installed?",
387
+ required: false,
388
+ options: PROVIDERS.map((p) => ({
389
+ value: p.id,
390
+ label: p.label,
391
+ hint: p.description
392
+ }))
393
+ });
394
+ if (isCancel(selection)) {
395
+ cancel("Cancelled.");
396
+ process.exit(0);
397
+ }
398
+ chosen = PROVIDERS.filter((p) => selection.includes(p.id));
399
+ }
400
+ for (const provider of chosen) {
401
+ const s2 = spinner();
402
+ s2.start(`Checking ${provider.label}\u2026`);
403
+ const alreadyInstalled = await provider.detect();
404
+ s2.stop(
405
+ alreadyInstalled ? `${provider.label} already installed` : `${provider.label} not found \u2014 installing`
406
+ );
407
+ if (!alreadyInstalled) {
408
+ try {
409
+ await provider.install();
410
+ } catch (err) {
411
+ log.error(`${provider.label} install failed: ${err.message}`);
412
+ if (yes || noAuth) {
413
+ log.warn(`Skipping ${provider.label} due to --yes/--no-auth`);
414
+ continue;
415
+ }
416
+ const keepGoing = await confirm({
417
+ message: `Skip ${provider.label} and continue?`,
418
+ initialValue: true
419
+ });
420
+ if (isCancel(keepGoing) || !keepGoing) {
421
+ cancel("Aborting.");
422
+ process.exit(1);
423
+ }
424
+ continue;
425
+ }
426
+ }
427
+ if (yes || noAuth) {
428
+ log.info(`Skipping ${provider.label} auth (--no-auth / --yes)`);
429
+ continue;
430
+ }
431
+ const doAuth = await confirm({
432
+ message: `Authenticate ${provider.label} now?`,
433
+ initialValue: true
434
+ });
435
+ if (isCancel(doAuth)) {
436
+ cancel("Cancelled.");
437
+ process.exit(0);
438
+ }
439
+ if (doAuth) {
440
+ try {
441
+ await provider.auth();
442
+ } catch (err) {
443
+ log.warn(
444
+ `${provider.label} auth returned non-zero \u2014 retry later with its own CLI. (${err.message})`
445
+ );
446
+ }
447
+ } else {
448
+ log.info(`Skipping ${provider.label} auth \u2014 you can run it later.`);
449
+ }
450
+ }
451
+ if (!yes) {
452
+ note(
453
+ "Installing Appostle globally (daemon + appostle CLI ship in the same package).",
454
+ "Daemon"
455
+ );
456
+ const proceed = await confirm({ message: "Continue?", initialValue: true });
457
+ if (isCancel(proceed) || !proceed) {
458
+ cancel("Stopped before daemon install. Re-run any time.");
459
+ process.exit(0);
460
+ }
461
+ }
462
+ const s = spinner();
463
+ s.start("Installing daemon (npm install -g appostle-installer)\u2026");
464
+ try {
465
+ await installAppostleGlobal();
466
+ s.stop("Daemon + appostle CLI installed");
467
+ } catch (err) {
468
+ s.stop("Install failed");
469
+ log.error(err.message);
470
+ process.exit(1);
471
+ }
472
+ s.start(`Configuring auto-start (${os})\u2026`);
473
+ await installAutostart();
474
+ s.stop("Auto-start configured");
475
+ if (os === "linux") {
476
+ log.info(
477
+ "Tip: run `loginctl enable-linger $USER` if you want the daemon to stay up when you're logged out."
478
+ );
479
+ }
480
+ if (!yes) {
481
+ note(`Paste the pairing link below into ${APPOSTLE_WEB_URL} (or scan the QR).`, "Pair");
482
+ }
483
+ await printPairingLink();
484
+ outro(chalk.green(`Done. Open ${APPOSTLE_WEB_URL}`));
485
+ }
486
+ main().catch((err) => {
487
+ console.error(chalk.red("\nInstaller failed:"), err);
488
+ process.exit(1);
489
+ });
490
+ //# sourceMappingURL=appostle-installer.js.map