@hyperdrive.bot/paseo-cli 0.3.33 → 0.3.35

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.
@@ -0,0 +1,23 @@
1
+ import type { Command } from "commander";
2
+ import type { CommandOptions, SingleResult, OutputSchema } from "../../output/index.js";
3
+ export interface ProviderAddRow {
4
+ provider: string;
5
+ label: string;
6
+ extends: string;
7
+ models: string;
8
+ reloaded: string;
9
+ }
10
+ export declare const providerAddSchema: OutputSchema<ProviderAddRow>;
11
+ export type ProviderAddResult = SingleResult<ProviderAddRow>;
12
+ export interface ProviderAddOptions extends CommandOptions {
13
+ host?: string;
14
+ extends?: string;
15
+ label?: string;
16
+ description?: string;
17
+ env?: string[];
18
+ model?: string[];
19
+ disallowedTool?: string[];
20
+ force?: boolean;
21
+ }
22
+ export declare function runAddCommand(providerId: string, options: ProviderAddOptions, _command: Command): Promise<ProviderAddResult>;
23
+ //# sourceMappingURL=add.d.ts.map
@@ -0,0 +1,110 @@
1
+ import { loadPersistedConfig, resolvePaseoHome, savePersistedConfig, } from "@hyperdrive.bot/paseo-server";
2
+ import { ProviderOverridesSchema } from "@hyperdrive.bot/paseo-protocol/provider-config";
3
+ import { tryConnectToDaemon } from "../../utils/client.js";
4
+ export const providerAddSchema = {
5
+ idField: "provider",
6
+ columns: [
7
+ { header: "PROVIDER", field: "provider", width: 20 },
8
+ { header: "LABEL", field: "label", width: 24 },
9
+ { header: "EXTENDS", field: "extends", width: 12 },
10
+ { header: "MODELS", field: "models", width: 30 },
11
+ {
12
+ header: "RELOADED",
13
+ field: "reloaded",
14
+ width: 24,
15
+ color: (value) => (value === "yes" ? "green" : "yellow"),
16
+ },
17
+ ],
18
+ };
19
+ /** `--env KEY=VALUE` (repeatable). Values may contain `=`; only the first splits. */
20
+ function parseEnvPairs(pairs) {
21
+ if (!pairs || pairs.length === 0)
22
+ return undefined;
23
+ const env = {};
24
+ for (const pair of pairs) {
25
+ const separator = pair.indexOf("=");
26
+ if (separator <= 0) {
27
+ throw new Error(`Invalid --env "${pair}". Expected KEY=VALUE.`);
28
+ }
29
+ env[pair.slice(0, separator)] = pair.slice(separator + 1);
30
+ }
31
+ return env;
32
+ }
33
+ /** `--model id` or `--model id:Label` or `--model id:Label:default` (repeatable). */
34
+ function parseModels(models) {
35
+ if (!models || models.length === 0)
36
+ return undefined;
37
+ return models.map((raw) => {
38
+ const [id, label, flag] = raw.split(":");
39
+ if (!id) {
40
+ throw new Error(`Invalid --model "${raw}". Expected id[:Label[:default]].`);
41
+ }
42
+ return {
43
+ id,
44
+ label: label && label.length > 0 ? label : id,
45
+ ...(flag === "default" ? { isDefault: true } : {}),
46
+ };
47
+ });
48
+ }
49
+ export async function runAddCommand(providerId, options, _command) {
50
+ if (!options.extends) {
51
+ throw new Error(`--extends is required. Choose a built-in provider (claude, codex, copilot, opencode, pi) or "acp".`);
52
+ }
53
+ const label = options.label ?? providerId;
54
+ const paseoHome = resolvePaseoHome();
55
+ const persisted = loadPersistedConfig(paseoHome);
56
+ const existingProviders = (persisted.agents?.providers ?? {});
57
+ if (existingProviders[providerId] && !options.force) {
58
+ throw new Error(`Provider "${providerId}" already exists in config.json. Pass --force to overwrite.`);
59
+ }
60
+ const entry = {
61
+ extends: options.extends,
62
+ label,
63
+ ...(options.description ? { description: options.description } : {}),
64
+ ...(parseEnvPairs(options.env) ? { env: parseEnvPairs(options.env) } : {}),
65
+ ...(parseModels(options.model) ? { models: parseModels(options.model) } : {}),
66
+ ...(options.disallowedTool?.length ? { disallowedTools: options.disallowedTool } : {}),
67
+ };
68
+ const nextProviders = { ...existingProviders, [providerId]: entry };
69
+ // Validate the WHOLE map, not just the new entry: ProviderOverridesSchema's
70
+ // superRefine enforces the id pattern and the extends/label requirement for
71
+ // custom providers, and we would rather fail here than write a config.json
72
+ // the daemon will reject on reload.
73
+ const parsed = ProviderOverridesSchema.safeParse(nextProviders);
74
+ if (!parsed.success) {
75
+ throw new Error(`Invalid provider config: ${parsed.error.issues.map((i) => i.message).join("; ")}`);
76
+ }
77
+ savePersistedConfig(paseoHome, {
78
+ ...persisted,
79
+ agents: { ...persisted.agents, providers: parsed.data },
80
+ });
81
+ // Hot-reload if a daemon is up; if not, the file write still stands and the
82
+ // provider is picked up on next start. Say which happened — never imply the
83
+ // running daemon saw it when there was no daemon to see it.
84
+ let reloaded = "no daemon running";
85
+ const client = await tryConnectToDaemon({ host: options.host });
86
+ if (client) {
87
+ try {
88
+ const result = await client.reloadProviderConfig();
89
+ reloaded =
90
+ result.added.includes(providerId) || result.updated.includes(providerId)
91
+ ? "yes"
92
+ : "daemon did not pick it up";
93
+ }
94
+ finally {
95
+ await client.close().catch(() => { });
96
+ }
97
+ }
98
+ return {
99
+ type: "single",
100
+ data: {
101
+ provider: providerId,
102
+ label,
103
+ extends: options.extends,
104
+ models: (parseModels(options.model) ?? []).map((m) => m.id).join(", ") || "—",
105
+ reloaded,
106
+ },
107
+ schema: providerAddSchema,
108
+ };
109
+ }
110
+ //# sourceMappingURL=add.js.map
@@ -1,6 +1,8 @@
1
1
  import { Command } from "commander";
2
+ import { runAddCommand } from "./add.js";
2
3
  import { runLsCommand } from "./ls.js";
3
4
  import { runModelsCommand } from "./models.js";
5
+ import { runReloadCommand } from "./reload.js";
4
6
  import { withOutput } from "../../output/index.js";
5
7
  import { addJsonAndDaemonHostOptions } from "../../utils/command-options.js";
6
8
  export function createProviderCommand() {
@@ -11,6 +13,25 @@ export function createProviderCommand() {
11
13
  .description("List models for a provider")
12
14
  .argument("<provider>", "Provider name (claude, codex, opencode)")
13
15
  .option("--thinking", "Include thinking option IDs for each model")).action(withOutput(runModelsCommand));
16
+ addJsonAndDaemonHostOptions(provider
17
+ .command("reload")
18
+ .description("Re-read providers from config.json into the running daemon (no restart)")
19
+ .option("--all", "Also list providers whose config did not change")).action(withOutput(runReloadCommand));
20
+ addJsonAndDaemonHostOptions(provider
21
+ .command("add")
22
+ .description("Add a custom provider to config.json and load it without a restart")
23
+ .argument("<id>", "Provider id (lowercase, e.g. zai)")
24
+ .requiredOption("--extends <base>", "Built-in to inherit from: claude, codex, copilot, opencode, pi, or acp")
25
+ .option("--label <label>", "Display name (defaults to the id)")
26
+ .option("--description <text>", "Description shown in the provider list")
27
+ .option("--env <KEY=VALUE>", "Environment variable (repeatable)", collect, [])
28
+ .option("--model <id[:Label[:default]]>", "Model definition (repeatable)", collect, [])
29
+ .option("--disallowed-tool <tool>", "Tool to disallow (repeatable)", collect, [])
30
+ .option("--force", "Overwrite an existing provider entry")).action(withOutput(runAddCommand));
14
31
  return provider;
15
32
  }
33
+ /** commander repeatable-option accumulator. */
34
+ function collect(value, previous) {
35
+ return [...previous, value];
36
+ }
16
37
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,21 @@
1
+ import type { Command } from "commander";
2
+ import type { CommandOptions, ListResult, OutputSchema } from "../../output/index.js";
3
+ /**
4
+ * One row per provider whose config.json entry changed. `unchanged` providers
5
+ * are omitted from the table by default — a reload that read back an identical
6
+ * file should look like a no-op, not a wall of rows.
7
+ */
8
+ export interface ProviderReloadRow {
9
+ provider: string;
10
+ change: "added" | "updated" | "removed" | "unchanged";
11
+ label: string;
12
+ enabled: string;
13
+ }
14
+ export declare const providerReloadSchema: OutputSchema<ProviderReloadRow>;
15
+ export type ProviderReloadResult = ListResult<ProviderReloadRow>;
16
+ export interface ProviderReloadOptions extends CommandOptions {
17
+ host?: string;
18
+ all?: boolean;
19
+ }
20
+ export declare function runReloadCommand(options: ProviderReloadOptions, _command: Command): Promise<ProviderReloadResult>;
21
+ //# sourceMappingURL=reload.d.ts.map
@@ -0,0 +1,64 @@
1
+ import { connectToDaemon } from "../../utils/client.js";
2
+ export const providerReloadSchema = {
3
+ idField: "provider",
4
+ columns: [
5
+ { header: "PROVIDER", field: "provider", width: 20 },
6
+ {
7
+ header: "CHANGE",
8
+ field: "change",
9
+ width: 10,
10
+ color: (value) => {
11
+ if (value === "added")
12
+ return "green";
13
+ if (value === "removed")
14
+ return "red";
15
+ if (value === "updated")
16
+ return "yellow";
17
+ return undefined;
18
+ },
19
+ },
20
+ { header: "LABEL", field: "label", width: 24 },
21
+ { header: "ENABLED", field: "enabled", width: 10 },
22
+ ],
23
+ };
24
+ export async function runReloadCommand(options, _command) {
25
+ // Deliberately connectToDaemon, not tryConnectToDaemon: a reload with no
26
+ // daemon running is a no-op the user must hear about, not a silent success.
27
+ const client = await connectToDaemon({ host: options.host });
28
+ try {
29
+ const result = await client.reloadProviderConfig();
30
+ const labels = new Map(result.providers.map((entry) => [entry.provider, entry]));
31
+ const rows = [];
32
+ const push = (provider, change) => {
33
+ const entry = labels.get(provider);
34
+ // A removed provider is gone from the registry, so it has no label or
35
+ // enabled state left to report.
36
+ const removed = change === "removed";
37
+ let enabled = "—";
38
+ if (!removed) {
39
+ enabled = entry?.enabled === false ? "Disabled" : "Enabled";
40
+ }
41
+ rows.push({
42
+ provider,
43
+ change,
44
+ label: entry?.label ?? (removed ? "—" : provider),
45
+ enabled,
46
+ });
47
+ };
48
+ for (const provider of result.added)
49
+ push(provider, "added");
50
+ for (const provider of result.updated)
51
+ push(provider, "updated");
52
+ for (const provider of result.removed)
53
+ push(provider, "removed");
54
+ if (options.all) {
55
+ for (const provider of result.unchanged)
56
+ push(provider, "unchanged");
57
+ }
58
+ return { type: "list", data: rows, schema: providerReloadSchema };
59
+ }
60
+ finally {
61
+ await client.close().catch(() => { });
62
+ }
63
+ }
64
+ //# sourceMappingURL=reload.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperdrive.bot/paseo-cli",
3
- "version": "0.3.33",
3
+ "version": "0.3.35",
4
4
  "description": "Paseo CLI - control your AI coding agents from the command line",
5
5
  "bin": {
6
6
  "paseo": "bin/paseo"
@@ -27,9 +27,9 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@clack/prompts": "^1.0.0",
30
- "@hyperdrive.bot/paseo-client": "0.3.33",
31
- "@hyperdrive.bot/paseo-protocol": "0.3.33",
32
- "@hyperdrive.bot/paseo-server": "0.3.33",
30
+ "@hyperdrive.bot/paseo-client": "0.3.35",
31
+ "@hyperdrive.bot/paseo-protocol": "0.3.35",
32
+ "@hyperdrive.bot/paseo-server": "0.3.35",
33
33
  "chalk": "^5.3.0",
34
34
  "commander": "^12.0.0",
35
35
  "mime-types": "^2.1.35",