palmier 0.9.20 → 0.9.21
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 +4 -1
- package/dist/agents/wizard.d.ts +23 -0
- package/dist/agents/wizard.js +160 -0
- package/dist/commands/agents.d.ts +1 -0
- package/dist/commands/agents.js +59 -0
- package/dist/commands/init.js +22 -122
- package/dist/index.js +7 -0
- package/dist/pwa/assets/{index-BcEa2BTo.css → index-B9oqDWJv.css} +1 -1
- package/dist/pwa/assets/{index-xF0O5I2z.js → index-kjOWpVKd.js} +3 -3
- package/dist/pwa/assets/{web-DVnCcdDQ.js → web-BQQlSLPT.js} +1 -1
- package/dist/pwa/assets/{web-Dnq4poOz.js → web-CpqzX7Jk.js} +1 -1
- package/dist/pwa/assets/{web-okZXRyxh.js → web-ZSYmpLle.js} +1 -1
- package/dist/pwa/index.html +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -191,10 +191,12 @@ Agents are re-detected on every daemon start; managed-agent versions are re-prob
|
|
|
191
191
|
|
|
192
192
|
### Palmier-managed Agents
|
|
193
193
|
|
|
194
|
-
An agent is considered **Palmier-managed** if it was installed via `palmier init`
|
|
194
|
+
An agent is considered **Palmier-managed** if it was installed via `palmier init`, `palmier agents`, or its update is initiated via the PWA. Palmier-managed agents have a known installed version stamped at install/update time; that version is what the PWA uses to drive the agent soft-update dialog and what's recorded into each session's run metadata so a session always shows the agent version it actually ran with — even after the live agent is upgraded.
|
|
195
195
|
|
|
196
196
|
Agents installed by the user outside the wizard (e.g., `npm install -g <pkg>` directly) are detected and usable but are **not** considered Palmier-managed. The PWA shows them under a separate "Version not managed by Palmier" section and does not offer auto-update for them.
|
|
197
197
|
|
|
198
|
+
Run `palmier agents` to manage agent CLIs after setup: it lists installed agents and offers an interactive picker to install or uninstall one. `palmier init` only prompts for an agent install when none are detected; once any agent is installed, init just lists them and continues with host registration.
|
|
199
|
+
|
|
198
200
|
### Updates
|
|
199
201
|
|
|
200
202
|
- **Palmier itself** — when a newer version of `palmier` is published to npm, the PWA shows a dismissible "Update Available" dialog. Clicking "Update Now" runs `npm update -g palmier` on the host and restarts the daemon. Clicking "Dismiss" suppresses the dialog for that exact version (per host, per device); a future release re-arms it.
|
|
@@ -209,6 +211,7 @@ The default network interface is detected once during `palmier init` and saved t
|
|
|
209
211
|
| Command | Description |
|
|
210
212
|
|---|---|
|
|
211
213
|
| `palmier init` | Interactive setup wizard |
|
|
214
|
+
| `palmier agents` | List, install, and uninstall agent CLIs |
|
|
212
215
|
| `palmier pair` | Generate a pairing code to pair a new device |
|
|
213
216
|
| `palmier clients list` | List active client tokens |
|
|
214
217
|
| `palmier clients revoke <token>` | Revoke a specific client token |
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type DetectedAgent } from "./agent.js";
|
|
2
|
+
export declare const colors: {
|
|
3
|
+
bold: (s: string) => string;
|
|
4
|
+
dim: (s: string) => string;
|
|
5
|
+
green: (s: string) => string;
|
|
6
|
+
cyan: (s: string) => string;
|
|
7
|
+
red: (s: string) => string;
|
|
8
|
+
yellow: (s: string) => string;
|
|
9
|
+
};
|
|
10
|
+
export declare function printInstalledAgents(agents: DetectedAgent[]): void;
|
|
11
|
+
export interface InstallPickerOptions {
|
|
12
|
+
/** Show a "Cancel" entry as the first choice. */
|
|
13
|
+
allowCancel?: boolean;
|
|
14
|
+
/** Override the picker prompt message. */
|
|
15
|
+
message?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Show the install picker and run the npm install + auth flow for the chosen
|
|
18
|
+
* agent. Returns the new DetectedAgent record on success, or null if the user
|
|
19
|
+
* cancelled, no installables remain, or the install failed. */
|
|
20
|
+
export declare function pickAndInstallAgent(current: DetectedAgent[], options?: InstallPickerOptions): Promise<DetectedAgent | null>;
|
|
21
|
+
/** Show the uninstall picker. Runs `npm uninstall -g` for the chosen agent
|
|
22
|
+
* and returns the agent key on success, or null on cancel/failure/no candidates. */
|
|
23
|
+
export declare function pickAndUninstallAgent(current: DetectedAgent[]): Promise<string | null>;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { spawnSync } from "child_process";
|
|
2
|
+
import * as readline from "readline";
|
|
3
|
+
import { selectFromList } from "../prompts.js";
|
|
4
|
+
import { getAgent, getNpmInstalledVersion, listInstallableAgents, } from "./agent.js";
|
|
5
|
+
export const colors = {
|
|
6
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
7
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
8
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
9
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
10
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
11
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
12
|
+
};
|
|
13
|
+
const { bold, dim, green, cyan, red } = colors;
|
|
14
|
+
export function printInstalledAgents(agents) {
|
|
15
|
+
if (agents.length === 0) {
|
|
16
|
+
console.log(` ${dim("(none installed)")}`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
for (const a of agents) {
|
|
20
|
+
const version = a.version ? ` ${dim(`v${a.version}`)}` : "";
|
|
21
|
+
const note = a.version ? "" : ` ${dim("(not managed by Palmier)")}`;
|
|
22
|
+
console.log(` ${green("✓")} ${a.label}${version}${note}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Show the install picker and run the npm install + auth flow for the chosen
|
|
26
|
+
* agent. Returns the new DetectedAgent record on success, or null if the user
|
|
27
|
+
* cancelled, no installables remain, or the install failed. */
|
|
28
|
+
export async function pickAndInstallAgent(current, options = {}) {
|
|
29
|
+
const detectedKeys = new Set(current.map((a) => a.key));
|
|
30
|
+
const missing = listInstallableAgents()
|
|
31
|
+
.filter((a) => !detectedKeys.has(a.key))
|
|
32
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
33
|
+
if (missing.length === 0) {
|
|
34
|
+
console.log(`\n${dim("All supported agents are already installed.")}`);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const installChoices = missing.map((a) => ({
|
|
38
|
+
label: a.freeUsage ? `${a.label} ${green(`[${a.freeUsage}]`)}` : a.label,
|
|
39
|
+
hint: a.npmPackage,
|
|
40
|
+
}));
|
|
41
|
+
const choices = options.allowCancel
|
|
42
|
+
? [{ label: "Cancel", hint: "go back" }, ...installChoices]
|
|
43
|
+
: installChoices;
|
|
44
|
+
const message = options.message ?? `\n${bold("Select an agent to install:")}`;
|
|
45
|
+
const idx = await selectFromList(message, choices);
|
|
46
|
+
if (idx === null)
|
|
47
|
+
return null;
|
|
48
|
+
if (options.allowCancel && idx === 0)
|
|
49
|
+
return null;
|
|
50
|
+
const choice = missing[options.allowCancel ? idx - 1 : idx];
|
|
51
|
+
if (!installAgentPackage(choice))
|
|
52
|
+
return null;
|
|
53
|
+
console.log(green(` ${choice.label} installed.`));
|
|
54
|
+
const tool = getAgent(choice.key);
|
|
55
|
+
const version = getNpmInstalledVersion(choice.npmPackage) ?? undefined;
|
|
56
|
+
const record = {
|
|
57
|
+
key: choice.key,
|
|
58
|
+
label: choice.label,
|
|
59
|
+
...(tool.supportsPermissions ? { supportsPermissions: true } : {}),
|
|
60
|
+
...(tool.supportsYolo ? { supportsYolo: true } : {}),
|
|
61
|
+
npmPackage: choice.npmPackage,
|
|
62
|
+
...(version ? { version } : {}),
|
|
63
|
+
};
|
|
64
|
+
if (tool.authArgs && tool.authArgs.length > 0) {
|
|
65
|
+
runAgentAuthFlow(choice.label, tool.command, tool.authArgs);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.log(`\n${bold("Next: authenticate the CLI.")}`);
|
|
69
|
+
console.log(` Run ${cyan(choice.command)} in another terminal and follow the sign-in prompts.`);
|
|
70
|
+
console.log(` Palmier will use the CLI on your behalf once it's signed in.`);
|
|
71
|
+
}
|
|
72
|
+
await waitForEnter("Press Enter once authentication is complete...");
|
|
73
|
+
return record;
|
|
74
|
+
}
|
|
75
|
+
/** Show the uninstall picker. Runs `npm uninstall -g` for the chosen agent
|
|
76
|
+
* and returns the agent key on success, or null on cancel/failure/no candidates. */
|
|
77
|
+
export async function pickAndUninstallAgent(current) {
|
|
78
|
+
const uninstallable = current.filter((a) => a.npmPackage);
|
|
79
|
+
if (uninstallable.length === 0) {
|
|
80
|
+
console.log(`\n${dim("No agents available to uninstall.")}`);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const choices = [
|
|
84
|
+
{ label: "Cancel", hint: "go back" },
|
|
85
|
+
...uninstallable.map((a) => ({
|
|
86
|
+
label: a.label,
|
|
87
|
+
hint: a.npmPackage,
|
|
88
|
+
})),
|
|
89
|
+
];
|
|
90
|
+
const idx = await selectFromList(`\n${bold("Select an agent to uninstall:")}`, choices);
|
|
91
|
+
if (idx === null || idx === 0)
|
|
92
|
+
return null;
|
|
93
|
+
const target = uninstallable[idx - 1];
|
|
94
|
+
if (!target.npmPackage)
|
|
95
|
+
return null;
|
|
96
|
+
if (!uninstallAgentPackage(target.npmPackage))
|
|
97
|
+
return null;
|
|
98
|
+
console.log(green(` ${target.label} uninstalled.`));
|
|
99
|
+
return target.key;
|
|
100
|
+
}
|
|
101
|
+
function installAgentPackage(agent) {
|
|
102
|
+
console.log(`\nInstalling ${cyan(agent.npmPackage)}...\n`);
|
|
103
|
+
const cmd = `npm install -g ${agent.npmPackage}`;
|
|
104
|
+
const result = spawnSync(cmd, { shell: true, stdio: "inherit" });
|
|
105
|
+
if (result.error) {
|
|
106
|
+
console.log(`\n${red(`Failed to run npm: ${result.error.message}`)}`);
|
|
107
|
+
console.log(`Make sure ${cyan("npm")} is on your PATH, then retry.`);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
if (result.status !== 0) {
|
|
111
|
+
const exitInfo = result.signal ? `signal ${result.signal}` : `exit ${result.status}`;
|
|
112
|
+
console.log(`\n${red(`${cmd} failed (${exitInfo}).`)}`);
|
|
113
|
+
if (process.platform === "win32") {
|
|
114
|
+
console.log(`If this is a permissions error, try opening a terminal as Administrator and re-running.`);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.log(`If this is a permissions error, try running with ${cyan("sudo")} or fix your global npm prefix.`);
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
function uninstallAgentPackage(npmPackage) {
|
|
124
|
+
console.log(`\nUninstalling ${cyan(npmPackage)}...\n`);
|
|
125
|
+
const cmd = `npm uninstall -g ${npmPackage}`;
|
|
126
|
+
const result = spawnSync(cmd, { shell: true, stdio: "inherit" });
|
|
127
|
+
if (result.error) {
|
|
128
|
+
console.log(`\n${red(`Failed to run npm: ${result.error.message}`)}`);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
if (result.status !== 0) {
|
|
132
|
+
const exitInfo = result.signal ? `signal ${result.signal}` : `exit ${result.status}`;
|
|
133
|
+
console.log(`\n${red(`${cmd} failed (${exitInfo}).`)}`);
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
function runAgentAuthFlow(label, command, args) {
|
|
139
|
+
const cmd = `${command} ${args.join(" ")}`;
|
|
140
|
+
console.log(`\n${bold(`Authenticating ${label}...`)} ${dim(`(${cmd})`)}\n`);
|
|
141
|
+
const result = spawnSync(cmd, { shell: true, stdio: "inherit" });
|
|
142
|
+
console.log("");
|
|
143
|
+
if (result.error) {
|
|
144
|
+
console.log(red(`Auth failed: could not run ${cmd} — ${result.error.message}`));
|
|
145
|
+
console.log(`Re-run ${cyan(cmd)} manually after this.\n`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (result.status !== 0) {
|
|
149
|
+
const exitInfo = result.signal ? `signal ${result.signal}` : `exit ${result.status}`;
|
|
150
|
+
console.log(red(`Auth failed (${exitInfo}).`));
|
|
151
|
+
console.log(`Re-run ${cyan(cmd)} manually after this.\n`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
console.log(green(`Successfully authenticated ${label}.\n`));
|
|
155
|
+
}
|
|
156
|
+
async function waitForEnter(message) {
|
|
157
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
158
|
+
await new Promise((resolve) => rl.question(`\n${dim(message)} `, resolve));
|
|
159
|
+
rl.close();
|
|
160
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function agentsCommand(): Promise<void>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { loadConfig, saveConfig } from "../config.js";
|
|
2
|
+
import { detectAgents } from "../agents/agent.js";
|
|
3
|
+
import { colors, pickAndInstallAgent, pickAndUninstallAgent, printInstalledAgents, } from "../agents/wizard.js";
|
|
4
|
+
import { selectFromList } from "../prompts.js";
|
|
5
|
+
import { getPlatform } from "../platform/index.js";
|
|
6
|
+
const { bold, dim, cyan } = colors;
|
|
7
|
+
export async function agentsCommand() {
|
|
8
|
+
let config = null;
|
|
9
|
+
try {
|
|
10
|
+
config = loadConfig();
|
|
11
|
+
}
|
|
12
|
+
catch { /* host not yet initialized */ }
|
|
13
|
+
let agents = await detectAgents(config?.agents);
|
|
14
|
+
let dirty = false;
|
|
15
|
+
while (true) {
|
|
16
|
+
console.log(`\n${bold("=== Installed agents ===")}\n`);
|
|
17
|
+
printInstalledAgents(agents);
|
|
18
|
+
const idx = await selectFromList(`\n${bold("What would you like to do?")}`, [
|
|
19
|
+
{ label: "Install an agent", hint: "add a supported CLI" },
|
|
20
|
+
{ label: "Uninstall an agent", hint: "remove a supported CLI" },
|
|
21
|
+
{ label: "Done", hint: "exit" },
|
|
22
|
+
]);
|
|
23
|
+
if (idx === null || idx === 2)
|
|
24
|
+
break;
|
|
25
|
+
if (idx === 0) {
|
|
26
|
+
const installed = await pickAndInstallAgent(agents, { allowCancel: true });
|
|
27
|
+
if (installed) {
|
|
28
|
+
agents = [...agents.filter((a) => a.key !== installed.key), installed];
|
|
29
|
+
dirty = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (idx === 1) {
|
|
33
|
+
const removedKey = await pickAndUninstallAgent(agents);
|
|
34
|
+
if (removedKey) {
|
|
35
|
+
agents = agents.filter((a) => a.key !== removedKey);
|
|
36
|
+
dirty = true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
agents = await detectAgents(agents);
|
|
40
|
+
}
|
|
41
|
+
if (!dirty)
|
|
42
|
+
return;
|
|
43
|
+
if (config) {
|
|
44
|
+
config.agents = agents;
|
|
45
|
+
saveConfig(config);
|
|
46
|
+
console.log(`\nConfig saved to ${dim("~/.config/palmier/host.json")}`);
|
|
47
|
+
try {
|
|
48
|
+
await getPlatform().restartDaemon();
|
|
49
|
+
console.log(dim("Daemon restarted."));
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
53
|
+
console.log(dim(`(Daemon restart skipped: ${message})`));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.log(`\n${dim(`Run ${cyan("palmier init")} to register this host with the new agent set.`)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import * as readline from "readline";
|
|
2
|
-
import { spawnSync } from "child_process";
|
|
3
2
|
import { loadConfig, saveConfig } from "../config.js";
|
|
4
|
-
import { detectAgents
|
|
3
|
+
import { detectAgents } from "../agents/agent.js";
|
|
4
|
+
import { colors, pickAndInstallAgent, printInstalledAgents } from "../agents/wizard.js";
|
|
5
5
|
import { getPlatform } from "../platform/index.js";
|
|
6
6
|
import { pairCommand } from "./pair.js";
|
|
7
7
|
import { detectDefaultInterface, getInterfaceIpv4 } from "../network.js";
|
|
8
8
|
import { listTasks } from "../task.js";
|
|
9
|
-
|
|
10
|
-
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
11
|
-
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
12
|
-
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
13
|
-
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
14
|
-
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
9
|
+
const { bold, dim, green, cyan, red } = colors;
|
|
15
10
|
export async function initCommand() {
|
|
16
11
|
console.log(`\n${bold("=== Palmier Host Setup ===")}\n`);
|
|
17
12
|
console.log(`By continuing, you agree to the ${cyan("Terms of Service")} (https://www.palmier.me/terms)`);
|
|
@@ -23,18 +18,26 @@ export async function initCommand() {
|
|
|
23
18
|
}
|
|
24
19
|
catch { /* first init */ }
|
|
25
20
|
let agents = await detectAgents(previousConfig?.agents);
|
|
26
|
-
logDetectedAgents(agents);
|
|
27
|
-
await offerAgentInstall(agents, () => {
|
|
28
|
-
if (previousConfig) {
|
|
29
|
-
previousConfig.agents = agents;
|
|
30
|
-
saveConfig(previousConfig);
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
21
|
if (agents.length === 0) {
|
|
34
|
-
console.log(`\n${red("No agent CLIs detected.")} Palmier
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
22
|
+
console.log(`\n${red("No agent CLIs detected.")} Palmier needs at least one to run.`);
|
|
23
|
+
const installed = await pickAndInstallAgent(agents);
|
|
24
|
+
if (installed) {
|
|
25
|
+
agents = [...agents, installed];
|
|
26
|
+
if (previousConfig) {
|
|
27
|
+
previousConfig.agents = agents;
|
|
28
|
+
saveConfig(previousConfig);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (agents.length === 0) {
|
|
32
|
+
console.log(`\nSee supported agents: https://www.palmier.me/agents\n`);
|
|
33
|
+
console.log(`Install at least one agent CLI, then run ${cyan("palmier init")} again.`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
console.log(`\n${bold("Installed agents:")}`);
|
|
39
|
+
printInstalledAgents(agents);
|
|
40
|
+
console.log(`\n${dim(`Tip: run ${cyan("palmier agents")} to install more or uninstall.\n`)}`);
|
|
38
41
|
}
|
|
39
42
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
40
43
|
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
@@ -129,109 +132,6 @@ export async function initCommand() {
|
|
|
129
132
|
throw err;
|
|
130
133
|
}
|
|
131
134
|
}
|
|
132
|
-
async function offerAgentInstall(agents, onAgentInstalled) {
|
|
133
|
-
while (true) {
|
|
134
|
-
const detectedKeys = new Set(agents.map((a) => a.key));
|
|
135
|
-
const missing = listInstallableAgents()
|
|
136
|
-
.filter((a) => !detectedKeys.has(a.key))
|
|
137
|
-
.sort((a, b) => a.label.localeCompare(b.label));
|
|
138
|
-
if (missing.length === 0)
|
|
139
|
-
return;
|
|
140
|
-
const hasAgents = agents.length > 0;
|
|
141
|
-
const message = hasAgents
|
|
142
|
-
? `\n${bold("Install additional agents?")} The following supported agents can be installed:`
|
|
143
|
-
: `\n${red("No agent CLIs detected.")} Palmier can install one for you via npm:`;
|
|
144
|
-
const installChoices = missing.map((a) => ({
|
|
145
|
-
label: a.freeUsage ? `${a.label} ${green(`[${a.freeUsage}]`)}` : a.label,
|
|
146
|
-
hint: `${a.npmPackage}`,
|
|
147
|
-
}));
|
|
148
|
-
const choices = hasAgents
|
|
149
|
-
? [{ label: "No — continue to the next step ", hint: "skip installation" }, ...installChoices]
|
|
150
|
-
: installChoices;
|
|
151
|
-
const idx = await selectFromList(message, choices);
|
|
152
|
-
if (idx === null)
|
|
153
|
-
return;
|
|
154
|
-
if (hasAgents && idx === 0)
|
|
155
|
-
return;
|
|
156
|
-
const choice = missing[hasAgents ? idx - 1 : idx];
|
|
157
|
-
if (!installAgentPackage(choice))
|
|
158
|
-
return;
|
|
159
|
-
console.log(green(` ${choice.label} installed.`));
|
|
160
|
-
// Stamp the agent record with version *before* auth so that an interrupted
|
|
161
|
-
// wizard (Ctrl+C during sign-in, etc.) still leaves the agent recorded as
|
|
162
|
-
// Palmier-managed in the persisted config on next run.
|
|
163
|
-
const tool = getAgent(choice.key);
|
|
164
|
-
const version = getNpmInstalledVersion(choice.npmPackage) ?? undefined;
|
|
165
|
-
agents.push({
|
|
166
|
-
key: choice.key,
|
|
167
|
-
label: choice.label,
|
|
168
|
-
...(tool.supportsPermissions ? { supportsPermissions: true } : {}),
|
|
169
|
-
...(tool.supportsYolo ? { supportsYolo: true } : {}),
|
|
170
|
-
npmPackage: choice.npmPackage,
|
|
171
|
-
...(version ? { version } : {}),
|
|
172
|
-
});
|
|
173
|
-
onAgentInstalled?.();
|
|
174
|
-
if (tool.authArgs && tool.authArgs.length > 0) {
|
|
175
|
-
runAgentAuthFlow(choice.label, tool.command, tool.authArgs);
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
console.log(`\n${bold("Next: authenticate the CLI.")}`);
|
|
179
|
-
console.log(` Run ${cyan(choice.command)} in another terminal and follow the sign-in prompts.`);
|
|
180
|
-
console.log(` Palmier will use the CLI on your behalf once it's signed in.`);
|
|
181
|
-
}
|
|
182
|
-
await waitForEnter("Press Enter once authentication is complete...");
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
async function waitForEnter(message) {
|
|
186
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
187
|
-
await new Promise((resolve) => rl.question(`\n${dim(message)} `, resolve));
|
|
188
|
-
rl.close();
|
|
189
|
-
}
|
|
190
|
-
function logDetectedAgents(agents) {
|
|
191
|
-
if (agents.length === 0)
|
|
192
|
-
return;
|
|
193
|
-
console.log(` Found: ${green(agents.map((a) => a.version ? `${a.label} v${a.version}` : a.label).join(", "))}`);
|
|
194
|
-
}
|
|
195
|
-
function runAgentAuthFlow(label, command, args) {
|
|
196
|
-
const cmd = `${command} ${args.join(" ")}`;
|
|
197
|
-
console.log(`\n${bold(`Authenticating ${label}...`)} ${dim(`(${cmd})`)}\n`);
|
|
198
|
-
const result = spawnSync(cmd, { shell: true, stdio: "inherit" });
|
|
199
|
-
console.log("");
|
|
200
|
-
if (result.error) {
|
|
201
|
-
console.log(red(`Auth failed: could not run ${cmd} — ${result.error.message}`));
|
|
202
|
-
console.log(`Re-run ${cyan(cmd)} manually after init finishes.\n`);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (result.status !== 0) {
|
|
206
|
-
const exitInfo = result.signal ? `signal ${result.signal}` : `exit ${result.status}`;
|
|
207
|
-
console.log(red(`Auth failed (${exitInfo}).`));
|
|
208
|
-
console.log(`Re-run ${cyan(cmd)} manually after init finishes.\n`);
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
console.log(green(`Successfully authenticated ${label}.\n`));
|
|
212
|
-
}
|
|
213
|
-
function installAgentPackage(agent) {
|
|
214
|
-
console.log(`\nInstalling ${cyan(agent.npmPackage)}...\n`);
|
|
215
|
-
const cmd = `npm install -g ${agent.npmPackage}`;
|
|
216
|
-
const result = spawnSync(cmd, { shell: true, stdio: "inherit" });
|
|
217
|
-
if (result.error) {
|
|
218
|
-
console.log(`\n${red(`Failed to run npm: ${result.error.message}`)}`);
|
|
219
|
-
console.log(`Make sure ${cyan("npm")} is on your PATH, then retry.`);
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
if (result.status !== 0) {
|
|
223
|
-
const exitInfo = result.signal ? `signal ${result.signal}` : `exit ${result.status}`;
|
|
224
|
-
console.log(`\n${red(`${cmd} failed (${exitInfo}).`)}`);
|
|
225
|
-
if (process.platform === "win32") {
|
|
226
|
-
console.log(`If this is a permissions error, try opening a terminal as Administrator and re-running ${cyan("palmier init")}.`);
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
console.log(`If this is a permissions error, try running with ${cyan("sudo")} or fix your global npm prefix.`);
|
|
230
|
-
}
|
|
231
|
-
return false;
|
|
232
|
-
}
|
|
233
|
-
return true;
|
|
234
|
-
}
|
|
235
135
|
async function registerHost(serverUrl, existingHostId) {
|
|
236
136
|
try {
|
|
237
137
|
const res = await fetch(`${serverUrl}/api/hosts/register`, {
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { serveCommand } from "./commands/serve.js";
|
|
|
11
11
|
import { pairCommand } from "./commands/pair.js";
|
|
12
12
|
import { restartCommand } from "./commands/restart.js";
|
|
13
13
|
import { clientsListCommand, clientsRevokeCommand, clientsRevokeAllCommand } from "./commands/clients.js";
|
|
14
|
+
import { agentsCommand } from "./commands/agents.js";
|
|
14
15
|
import { uninstallCommand } from "./commands/uninstall.js";
|
|
15
16
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
17
|
const pkg = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
|
@@ -76,6 +77,12 @@ clientsCmd
|
|
|
76
77
|
.action(async () => {
|
|
77
78
|
await clientsRevokeAllCommand();
|
|
78
79
|
});
|
|
80
|
+
program
|
|
81
|
+
.command("agents")
|
|
82
|
+
.description("List, install, and uninstall agent CLIs")
|
|
83
|
+
.action(async () => {
|
|
84
|
+
await agentsCommand();
|
|
85
|
+
});
|
|
79
86
|
program
|
|
80
87
|
.command("uninstall")
|
|
81
88
|
.description("Stop the daemon and remove all scheduled tasks")
|