palmier 0.9.19 → 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 +19 -4
- package/dist/agents/agent.d.ts +17 -3
- package/dist/agents/agent.js +17 -27
- package/dist/agents/aider.js +3 -2
- package/dist/agents/claude.js +4 -2
- package/dist/agents/cline.js +3 -2
- package/dist/agents/codex.js +4 -2
- package/dist/agents/copilot.js +4 -2
- package/dist/agents/cursor.js +3 -2
- package/dist/agents/deepagents.js +3 -2
- package/dist/agents/droid.js +3 -2
- package/dist/agents/gemini.js +3 -2
- package/dist/agents/goose.js +3 -2
- package/dist/agents/hermes.js +3 -2
- package/dist/agents/kimi.js +3 -2
- package/dist/agents/kiro.js +3 -2
- package/dist/agents/openclaw.js +3 -2
- package/dist/agents/opencode.js +3 -2
- package/dist/agents/qoder.js +3 -2
- package/dist/agents/qwen.js +4 -2
- 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 +36 -90
- package/dist/index.js +7 -0
- package/dist/pwa/assets/{index-C0RDMMZW.css → index-B9oqDWJv.css} +1 -1
- package/dist/pwa/assets/{index-CcZCM6E8.js → index-kjOWpVKd.js} +26 -26
- package/dist/pwa/assets/{web-5pnMoH5p.js → web-BQQlSLPT.js} +1 -1
- package/dist/pwa/assets/{web-DOqABMBn.js → web-CpqzX7Jk.js} +1 -1
- package/dist/pwa/assets/{web-DnbZXe0M.js → web-ZSYmpLle.js} +1 -1
- package/dist/pwa/index.html +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ Palmier runs as a background daemon (systemd on Linux, launchd on macOS, Task Sc
|
|
|
67
67
|
|
|
68
68
|
### MCP Server
|
|
69
69
|
|
|
70
|
-
Palmier exposes an [MCP](https://modelcontextprotocol.io) server at `http://localhost:7256/mcp` (streamable HTTP transport). MCP-capable agents can register it to get tool and resource definitions automatically. The same tools and resources are also available
|
|
70
|
+
Palmier exposes an [MCP](https://modelcontextprotocol.io) server at `http://localhost:7256/mcp` (streamable HTTP transport). MCP-capable agents can register it to get tool and resource definitions automatically. The same tools and resources are also readily available if running agent tasks directly from Palmier app, without installing the MCP server.
|
|
71
71
|
|
|
72
72
|
**MCP server URL:** `http://localhost:7256/mcp`
|
|
73
73
|
|
|
@@ -174,19 +174,33 @@ Revoking the linked device also clears the host's linked-device record; device c
|
|
|
174
174
|
### The `init` Command
|
|
175
175
|
|
|
176
176
|
The wizard:
|
|
177
|
-
- Detects installed agent CLIs and caches the result
|
|
177
|
+
- Detects installed agent CLIs and caches the result; agents previously installed by Palmier have their installed version re-probed so the recorded version stays in sync with manual upgrades
|
|
178
|
+
- Offers to install missing supported agents from npm (one at a time, arrow-key menu). After each install the agent's version is stamped (becoming "Palmier-managed"), the wizard kicks off authentication, and waits for you to press Enter before offering the next install. On re-init, the agents list is saved after every install so an interrupted wizard is resumable.
|
|
178
179
|
- Asks for the HTTP port
|
|
179
180
|
- Detects the default network interface (used for auto-LAN)
|
|
180
181
|
- Shows a summary (including any existing scheduled tasks to recover) and asks for confirmation
|
|
181
182
|
- Registers with the Palmier server, saves configuration to `~/.config/palmier/host.json`
|
|
182
|
-
- Installs a background daemon (systemd user service on Linux, LaunchAgent on macOS, Task Scheduler on Windows)
|
|
183
|
+
- Installs a background daemon (systemd user service on Linux, LaunchAgent on macOS, Task Scheduler on Windows). On re-init, also restarts the daemon so the running process picks up the new agents/versions for `host.info`.
|
|
183
184
|
- Auto-enters pair mode to connect your first device
|
|
184
185
|
|
|
185
186
|
The daemon automatically recovers existing tasks by reinstalling their system timers on startup.
|
|
186
187
|
|
|
187
188
|
> **macOS note:** Palmier installs as a user-level LaunchAgent, so it runs without `sudo`. LaunchAgents only run while the user is logged into the GUI session — after a reboot, scheduled tasks stay dormant until you log in at least once. Enable auto-login in System Settings → Users & Groups if you need unattended operation across reboots.
|
|
188
189
|
|
|
189
|
-
Agents are re-detected on every daemon start
|
|
190
|
+
Agents are re-detected on every daemon start; managed-agent versions are re-probed live so upgrades performed outside the wizard show up automatically.
|
|
191
|
+
|
|
192
|
+
### Palmier-managed Agents
|
|
193
|
+
|
|
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
|
+
|
|
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
|
+
|
|
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
|
+
|
|
200
|
+
### Updates
|
|
201
|
+
|
|
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.
|
|
203
|
+
- **Palmier-managed agents** — same flow per agent: when npm publishes a newer version, the PWA shows an "Agent Update Available" dialog. Clicking "Update Now" runs `npm update -g <pkg>` on the host (no daemon restart needed). Dismissals are per host, per agent, per version.
|
|
190
204
|
|
|
191
205
|
### Re-detecting the LAN Network
|
|
192
206
|
|
|
@@ -197,6 +211,7 @@ The default network interface is detected once during `palmier init` and saved t
|
|
|
197
211
|
| Command | Description |
|
|
198
212
|
|---|---|
|
|
199
213
|
| `palmier init` | Interactive setup wizard |
|
|
214
|
+
| `palmier agents` | List, install, and uninstall agent CLIs |
|
|
200
215
|
| `palmier pair` | Generate a pairing code to pair a new device |
|
|
201
216
|
| `palmier clients list` | List active client tokens |
|
|
202
217
|
| `palmier clients revoke <token>` | Revoke a specific client token |
|
package/dist/agents/agent.d.ts
CHANGED
|
@@ -12,12 +12,19 @@ export interface CommandLine {
|
|
|
12
12
|
}>;
|
|
13
13
|
}
|
|
14
14
|
export interface AgentTool {
|
|
15
|
+
/** Human-readable name shown in the PWA and CLI (e.g. "Claude Code"). */
|
|
16
|
+
label: string;
|
|
15
17
|
/** The agent's CLI binary name (e.g. "claude", "kiro-cli"). */
|
|
16
18
|
command: string;
|
|
17
19
|
/** Static args for a short, non-interactive prompt. The prompt is appended to the end of this list. */
|
|
18
|
-
|
|
20
|
+
promptArgs: string[];
|
|
19
21
|
/** Single arg passed to `command` to probe whether the CLI is installed. Usually `"--version"`. */
|
|
20
|
-
|
|
22
|
+
probeArg: string;
|
|
23
|
+
/** Optional args to launch the agent's auth flow after a successful install. When set,
|
|
24
|
+
* `palmier init` runs `<command> <args...>` interactively (stdio: "inherit") so the user
|
|
25
|
+
* can sign in before configuration continues. Leave undefined for agents that auth on
|
|
26
|
+
* first run with no separate command. */
|
|
27
|
+
authArgs?: string[];
|
|
21
28
|
/** Whether this agent supports permission overrides (e.g. --allowedTools).
|
|
22
29
|
* When falsy, the permissions section is omitted from agent instructions. */
|
|
23
30
|
supportsPermissions?: boolean;
|
|
@@ -59,5 +66,12 @@ export interface InstallableAgent {
|
|
|
59
66
|
freeUsage?: string;
|
|
60
67
|
}
|
|
61
68
|
export declare function listInstallableAgents(): InstallableAgent[];
|
|
62
|
-
|
|
69
|
+
/** Detect agents present on PATH and resolve their version when they are
|
|
70
|
+
* Palmier-managed. An agent is treated as managed if either:
|
|
71
|
+
* - it had a `version` in the `previous` list (preserved across daemon restarts), or
|
|
72
|
+
* - its key is in `newlyInstalled` (e.g. just installed by the wizard this session).
|
|
73
|
+
*
|
|
74
|
+
* Every managed agent has its version probed live via `npm ls -g`, so manual
|
|
75
|
+
* upgrades outside Palmier are picked up on the next detection. */
|
|
76
|
+
export declare function detectAgents(previous?: DetectedAgent[], newlyInstalled?: Set<string>): Promise<DetectedAgent[]>;
|
|
63
77
|
export declare function getAgent(name: string): AgentTool;
|
package/dist/agents/agent.js
CHANGED
|
@@ -18,10 +18,10 @@ import { clineAgent } from "./cline.js";
|
|
|
18
18
|
import { qoderAgent } from "./qoder.js";
|
|
19
19
|
import { hermesAgent } from "./hermes.js";
|
|
20
20
|
export function getPromptCommandLine(agent, prompt) {
|
|
21
|
-
return { args: [...agent.
|
|
21
|
+
return { args: [...agent.promptArgs, prompt] };
|
|
22
22
|
}
|
|
23
23
|
export async function probeAgent(agent) {
|
|
24
|
-
const probe = `${agent.command} ${agent.
|
|
24
|
+
const probe = `${agent.command} ${agent.probeArg}`;
|
|
25
25
|
try {
|
|
26
26
|
execSync(probe, { stdio: "ignore", shell: SHELL });
|
|
27
27
|
}
|
|
@@ -73,25 +73,6 @@ const agentRegistry = {
|
|
|
73
73
|
qoder: qoderAgent,
|
|
74
74
|
hermes: hermesAgent,
|
|
75
75
|
};
|
|
76
|
-
const agentLabels = {
|
|
77
|
-
claude: "Claude Code",
|
|
78
|
-
gemini: "Gemini CLI",
|
|
79
|
-
codex: "Codex CLI",
|
|
80
|
-
droid: "Droid CLI",
|
|
81
|
-
openclaw: "OpenClaw",
|
|
82
|
-
copilot: "Copilot CLI",
|
|
83
|
-
qwen: "Qwen Code",
|
|
84
|
-
kimi: "Kimi Code",
|
|
85
|
-
goose: "Goose CLI",
|
|
86
|
-
opencode: "OpenCode",
|
|
87
|
-
deepagents: "Deep Agents CLI",
|
|
88
|
-
aider: "Aider",
|
|
89
|
-
cursor: "Cursor CLI",
|
|
90
|
-
kiro: "Kiro CLI",
|
|
91
|
-
cline: "Cline CLI",
|
|
92
|
-
qoder: "Qoder CLI",
|
|
93
|
-
hermes: "Hermes Agent",
|
|
94
|
-
};
|
|
95
76
|
export function listInstallableAgents() {
|
|
96
77
|
const out = [];
|
|
97
78
|
for (const [key, agent] of Object.entries(agentRegistry)) {
|
|
@@ -99,7 +80,7 @@ export function listInstallableAgents() {
|
|
|
99
80
|
continue;
|
|
100
81
|
out.push({
|
|
101
82
|
key,
|
|
102
|
-
label:
|
|
83
|
+
label: agent.label,
|
|
103
84
|
npmPackage: agent.npmPackage,
|
|
104
85
|
command: agent.command,
|
|
105
86
|
...(agent.freeUsage ? { freeUsage: agent.freeUsage } : {}),
|
|
@@ -107,22 +88,31 @@ export function listInstallableAgents() {
|
|
|
107
88
|
}
|
|
108
89
|
return out;
|
|
109
90
|
}
|
|
110
|
-
|
|
91
|
+
/** Detect agents present on PATH and resolve their version when they are
|
|
92
|
+
* Palmier-managed. An agent is treated as managed if either:
|
|
93
|
+
* - it had a `version` in the `previous` list (preserved across daemon restarts), or
|
|
94
|
+
* - its key is in `newlyInstalled` (e.g. just installed by the wizard this session).
|
|
95
|
+
*
|
|
96
|
+
* Every managed agent has its version probed live via `npm ls -g`, so manual
|
|
97
|
+
* upgrades outside Palmier are picked up on the next detection. */
|
|
98
|
+
export async function detectAgents(previous, newlyInstalled) {
|
|
111
99
|
const previousByKey = new Map((previous ?? []).map((a) => [a.key, a]));
|
|
112
100
|
const detected = [];
|
|
113
101
|
for (const [key, agent] of Object.entries(agentRegistry)) {
|
|
114
|
-
const label = agentLabels[key] ?? key;
|
|
115
102
|
const ok = await probeAgent(agent);
|
|
116
103
|
if (!ok)
|
|
117
104
|
continue;
|
|
118
|
-
const
|
|
105
|
+
const wasManaged = !!previousByKey.get(key)?.version || (newlyInstalled?.has(key) ?? false);
|
|
106
|
+
const version = wasManaged && agent.npmPackage
|
|
107
|
+
? getNpmInstalledVersion(agent.npmPackage) ?? undefined
|
|
108
|
+
: undefined;
|
|
119
109
|
detected.push({
|
|
120
110
|
key,
|
|
121
|
-
label,
|
|
111
|
+
label: agent.label,
|
|
122
112
|
supportsPermissions: agent.supportsPermissions,
|
|
123
113
|
supportsYolo: agent.supportsYolo,
|
|
124
114
|
...(agent.npmPackage ? { npmPackage: agent.npmPackage } : {}),
|
|
125
|
-
...(
|
|
115
|
+
...(version ? { version } : {}),
|
|
126
116
|
});
|
|
127
117
|
}
|
|
128
118
|
return detected;
|
package/dist/agents/aider.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const aiderAgent = {
|
|
3
|
+
label: "Aider",
|
|
3
4
|
command: "aider",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["--message"],
|
|
6
|
+
probeArg: "--version",
|
|
6
7
|
supportsYolo: true,
|
|
7
8
|
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
8
9
|
const yolo = extraPermissions === "yolo";
|
package/dist/agents/claude.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const claudeAgent = {
|
|
3
|
+
label: "Claude Code",
|
|
3
4
|
command: "claude",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["-p"],
|
|
6
|
+
probeArg: "--version",
|
|
7
|
+
authArgs: ["auth", "login"],
|
|
6
8
|
supportsPermissions: true,
|
|
7
9
|
supportsYolo: true,
|
|
8
10
|
npmPackage: "@anthropic-ai/claude-code",
|
package/dist/agents/cline.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const clineAgent = {
|
|
3
|
+
label: "Cline CLI",
|
|
3
4
|
command: "cline",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["--yolo", "-p"],
|
|
6
|
+
probeArg: "--version",
|
|
6
7
|
supportsYolo: true,
|
|
7
8
|
npmPackage: "cline",
|
|
8
9
|
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
package/dist/agents/codex.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const codexAgent = {
|
|
3
|
+
label: "Codex CLI",
|
|
3
4
|
command: "codex",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["exec", "--skip-git-repo-check"],
|
|
6
|
+
probeArg: "--version",
|
|
7
|
+
authArgs: ["login"],
|
|
6
8
|
supportsYolo: true,
|
|
7
9
|
suppressStdErr: true,
|
|
8
10
|
npmPackage: "@openai/codex",
|
package/dist/agents/copilot.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const copilotAgent = {
|
|
3
|
+
label: "Copilot CLI",
|
|
3
4
|
command: "copilot",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["-p"],
|
|
6
|
+
probeArg: "-v",
|
|
7
|
+
authArgs: ["login"],
|
|
6
8
|
supportsYolo: true,
|
|
7
9
|
suppressStdErr: true,
|
|
8
10
|
npmPackage: "@github/copilot",
|
package/dist/agents/cursor.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const cursorAgent = {
|
|
3
|
+
label: "Cursor CLI",
|
|
3
4
|
command: "cursor",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["-p"],
|
|
6
|
+
probeArg: "--version",
|
|
6
7
|
supportsYolo: true,
|
|
7
8
|
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
8
9
|
const yolo = extraPermissions === "yolo";
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const deepAgentsAgent = {
|
|
3
|
+
label: "Deep Agents CLI",
|
|
3
4
|
command: "deepagents",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["--non-interactive"],
|
|
6
|
+
probeArg: "--version",
|
|
6
7
|
supportsYolo: true,
|
|
7
8
|
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
8
9
|
const yolo = extraPermissions === "yolo";
|
package/dist/agents/droid.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const droidAgent = {
|
|
3
|
+
label: "Droid CLI",
|
|
3
4
|
command: "droid",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["exec"],
|
|
6
|
+
probeArg: "--version",
|
|
6
7
|
supportsYolo: true,
|
|
7
8
|
npmPackage: "@factory/cli",
|
|
8
9
|
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
package/dist/agents/gemini.js
CHANGED
|
@@ -16,9 +16,10 @@ export function renderPolicyToml(allowedTools) {
|
|
|
16
16
|
].join("\n");
|
|
17
17
|
}
|
|
18
18
|
export const geminiAgent = {
|
|
19
|
+
label: "Gemini CLI",
|
|
19
20
|
command: "gemini",
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
promptArgs: ["--prompt"],
|
|
22
|
+
probeArg: "--version",
|
|
22
23
|
supportsPermissions: true,
|
|
23
24
|
supportsYolo: true,
|
|
24
25
|
npmPackage: "@google/gemini-cli",
|
package/dist/agents/goose.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const gooseAgent = {
|
|
3
|
+
label: "Goose CLI",
|
|
3
4
|
command: "goose",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["run", "--text"],
|
|
6
|
+
probeArg: "--version",
|
|
6
7
|
supportsYolo: true,
|
|
7
8
|
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
8
9
|
const yolo = extraPermissions === "yolo";
|
package/dist/agents/hermes.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const hermesAgent = {
|
|
3
|
+
label: "Hermes Agent",
|
|
3
4
|
command: "hermes",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["chat", "-q"],
|
|
6
|
+
probeArg: "--version",
|
|
6
7
|
supportsYolo: true,
|
|
7
8
|
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
8
9
|
const yolo = extraPermissions === "yolo";
|
package/dist/agents/kimi.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const kimiAgent = {
|
|
3
|
+
label: "Kimi Code",
|
|
3
4
|
command: "kimi",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["-p"],
|
|
6
|
+
probeArg: "--version",
|
|
6
7
|
supportsYolo: true,
|
|
7
8
|
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
8
9
|
const yolo = extraPermissions === "yolo";
|
package/dist/agents/kiro.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const kiroAgent = {
|
|
3
|
+
label: "Kiro CLI",
|
|
3
4
|
command: "kiro-cli",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["--no-interactive"],
|
|
6
|
+
probeArg: "--version",
|
|
6
7
|
supportsYolo: true,
|
|
7
8
|
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
8
9
|
const yolo = extraPermissions === "yolo";
|
package/dist/agents/openclaw.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const openClawAgent = {
|
|
3
|
+
label: "OpenClaw",
|
|
3
4
|
command: "openclaw",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["agent", "--local", "--agent", "main", "--message"],
|
|
6
|
+
probeArg: "--version",
|
|
6
7
|
npmPackage: "openclaw",
|
|
7
8
|
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
8
9
|
const prompt = followupPrompt ?? getAgentInstructions(task);
|
package/dist/agents/opencode.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const openCodeAgent = {
|
|
3
|
+
label: "OpenCode",
|
|
3
4
|
command: "opencode",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["run"],
|
|
6
|
+
probeArg: "--version",
|
|
6
7
|
supportsYolo: true,
|
|
7
8
|
npmPackage: "opencode-ai",
|
|
8
9
|
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
package/dist/agents/qoder.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const qoderAgent = {
|
|
3
|
+
label: "Qoder CLI",
|
|
3
4
|
command: "qodercli",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["-p"],
|
|
6
|
+
probeArg: "--version",
|
|
6
7
|
supportsYolo: true,
|
|
7
8
|
npmPackage: "@qoder-ai/qodercli",
|
|
8
9
|
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
package/dist/agents/qwen.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { getAgentInstructions } from "./shared-prompt.js";
|
|
2
2
|
export const qwenAgent = {
|
|
3
|
+
label: "Qwen Code",
|
|
3
4
|
command: "qwen",
|
|
4
|
-
|
|
5
|
-
|
|
5
|
+
promptArgs: ["-p"],
|
|
6
|
+
probeArg: "--version",
|
|
7
|
+
authArgs: ["auth", "qwen-oauth"],
|
|
6
8
|
supportsYolo: true,
|
|
7
9
|
npmPackage: "@qwen-code/qwen-code",
|
|
8
10
|
getTaskRunCommandLine(task, followupPrompt, extraPermissions) {
|
|
@@ -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
|
+
}
|