pi-runline 0.5.0 → 0.5.2
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.
|
@@ -75,7 +75,9 @@ export async function promptForCredentials(
|
|
|
75
75
|
runlineDir: string,
|
|
76
76
|
plugins: PluginSummary[],
|
|
77
77
|
newlyEnabled: string[],
|
|
78
|
+
options: { force?: boolean } = {},
|
|
78
79
|
): Promise<string[]> {
|
|
80
|
+
const force = options.force === true;
|
|
79
81
|
const config = readConfig(runlineDir);
|
|
80
82
|
const connections = getConnections(config);
|
|
81
83
|
const saved: string[] = [];
|
|
@@ -87,16 +89,17 @@ export async function promptForCredentials(
|
|
|
87
89
|
const schema = plugin.connectionConfigSchema;
|
|
88
90
|
if (isSchemaEmpty(schema)) continue; // no creds needed
|
|
89
91
|
|
|
90
|
-
if (connectionFor(connections, name)) continue; // already configured
|
|
92
|
+
if (!force && connectionFor(connections, name)) continue; // already configured
|
|
91
93
|
|
|
92
94
|
// Check env — if every required field has an env var set, skip the prompt.
|
|
95
|
+
// Skipped on `force` (the user explicitly asked to re-enter values).
|
|
93
96
|
const requiredFields = Object.entries(schema!).filter(
|
|
94
97
|
([, f]) => f.required,
|
|
95
98
|
);
|
|
96
99
|
const allFromEnv = requiredFields.every(
|
|
97
100
|
([, f]) => f.env && process.env[f.env],
|
|
98
101
|
);
|
|
99
|
-
if (requiredFields.length > 0 && allFromEnv) continue;
|
|
102
|
+
if (!force && requiredFields.length > 0 && allFromEnv) continue;
|
|
100
103
|
|
|
101
104
|
const wantSetup = await ctx.ui.confirm(
|
|
102
105
|
`Set up ${name}?`,
|
|
@@ -137,7 +140,12 @@ export async function promptForCredentials(
|
|
|
137
140
|
plugin: name,
|
|
138
141
|
config: values,
|
|
139
142
|
};
|
|
140
|
-
connections.
|
|
143
|
+
const existingIdx = connections.findIndex((c) => c.plugin === name);
|
|
144
|
+
if (existingIdx >= 0) {
|
|
145
|
+
connections[existingIdx] = conn;
|
|
146
|
+
} else {
|
|
147
|
+
connections.push(conn);
|
|
148
|
+
}
|
|
141
149
|
saved.push(name);
|
|
142
150
|
}
|
|
143
151
|
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import type { Component, TUI } from "@mariozechner/pi-tui";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
fuzzyFilter,
|
|
5
|
+
Input,
|
|
6
|
+
Key,
|
|
7
|
+
matchesKey,
|
|
8
|
+
visibleWidth,
|
|
9
|
+
} from "@mariozechner/pi-tui";
|
|
4
10
|
|
|
5
11
|
export interface PluginPickerItem {
|
|
6
12
|
name: string;
|
|
7
13
|
actionCount: number;
|
|
14
|
+
/** Plugin already has a stored connection — eligible for Ctrl-R reconfigure. */
|
|
15
|
+
connected?: boolean;
|
|
8
16
|
}
|
|
9
17
|
|
|
10
18
|
export interface PluginPickerResult {
|
|
11
19
|
/** undefined = cancelled */
|
|
12
20
|
selected?: string[];
|
|
21
|
+
/** Set when the user pressed Ctrl-R on a connected plugin. */
|
|
22
|
+
reconfigure?: string;
|
|
13
23
|
}
|
|
14
24
|
|
|
15
25
|
/**
|
|
@@ -18,7 +28,7 @@ export interface PluginPickerResult {
|
|
|
18
28
|
* Keys:
|
|
19
29
|
* ↑ / ↓ — move highlight
|
|
20
30
|
* space — toggle current item
|
|
21
|
-
*
|
|
31
|
+
* alt-r — reconfigure highlighted plugin (when connected)
|
|
22
32
|
* enter — save and close
|
|
23
33
|
* esc / C-c — cancel
|
|
24
34
|
* type — fuzzy filter
|
|
@@ -70,7 +80,7 @@ export class PluginPicker implements Component {
|
|
|
70
80
|
body.push(
|
|
71
81
|
theme.fg(
|
|
72
82
|
"dim",
|
|
73
|
-
"type to filter · space toggle ·
|
|
83
|
+
"type to filter · space toggle · alt+r reconfigure · enter save · esc cancel",
|
|
74
84
|
),
|
|
75
85
|
);
|
|
76
86
|
body.push("");
|
|
@@ -106,9 +116,12 @@ export class PluginPicker implements Component {
|
|
|
106
116
|
? theme.fg("success", box)
|
|
107
117
|
: theme.fg("dim", box);
|
|
108
118
|
const name = isCur ? theme.bold(item.name) : item.name;
|
|
119
|
+
const connectedTag = item.connected
|
|
120
|
+
? theme.fg("success", " • connected")
|
|
121
|
+
: "";
|
|
109
122
|
const count = theme.fg("dim", ` ${item.actionCount} actions`);
|
|
110
123
|
const arrow = isCur ? theme.fg("accent", "❯ ") : " ";
|
|
111
|
-
body.push(`${arrow}${boxColored} ${name}${count}`);
|
|
124
|
+
body.push(`${arrow}${boxColored} ${name}${connectedTag}${count}`);
|
|
112
125
|
}
|
|
113
126
|
for (let i = end - start; i < this.maxRows; i++) body.push("");
|
|
114
127
|
|
|
@@ -168,12 +181,13 @@ export class PluginPicker implements Component {
|
|
|
168
181
|
}
|
|
169
182
|
return;
|
|
170
183
|
}
|
|
171
|
-
if (data
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
for
|
|
175
|
-
|
|
176
|
-
|
|
184
|
+
if (matchesKey(data, Key.alt("r"))) {
|
|
185
|
+
// Alt-R — reconfigure the highlighted plugin if it already has
|
|
186
|
+
// saved credentials. No-op otherwise. (Ctrl-R is reserved by pi
|
|
187
|
+
// for `app.session.rename` and never reaches handleInput.)
|
|
188
|
+
const item = this.filtered[this.cursor];
|
|
189
|
+
if (item?.connected) {
|
|
190
|
+
this.onDone({ reconfigure: item.name });
|
|
177
191
|
}
|
|
178
192
|
return;
|
|
179
193
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { Markdown, Text } from "@mariozechner/pi-tui";
|
|
3
3
|
import { Type } from "@sinclair/typebox";
|
|
4
|
-
import { Runline } from "runline";
|
|
4
|
+
import { discoverPlugins, Runline } from "runline";
|
|
5
5
|
import { promptForCredentials } from "../connection-setup.js";
|
|
6
6
|
import { createPluginPickerFactory } from "../plugin-picker.js";
|
|
7
7
|
import {
|
|
8
8
|
findRunlineDir,
|
|
9
|
+
getConnectedPluginNames,
|
|
9
10
|
loadExtConfig,
|
|
10
11
|
savePiPlugins,
|
|
11
12
|
} from "../runline-resolve.js";
|
|
@@ -228,20 +229,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
228
229
|
return;
|
|
229
230
|
}
|
|
230
231
|
|
|
231
|
-
|
|
232
|
+
// Load the full bundled catalog directly — `Runline.fromProject`
|
|
233
|
+
// gates builtins by `connections[].plugin`, which is the wrong
|
|
234
|
+
// surface for the picker (the picker is HOW you decide which
|
|
235
|
+
// plugins to enable in the first place).
|
|
236
|
+
let allPlugins: Awaited<ReturnType<typeof discoverPlugins>>;
|
|
232
237
|
try {
|
|
233
|
-
|
|
238
|
+
allPlugins = await discoverPlugins(runlineDir);
|
|
234
239
|
} catch (err) {
|
|
235
240
|
ctx.ui.notify(
|
|
236
|
-
`runline failed to load: ${(err as Error).message}`,
|
|
241
|
+
`runline failed to load plugins: ${(err as Error).message}`,
|
|
237
242
|
"error",
|
|
238
243
|
);
|
|
239
244
|
return;
|
|
240
245
|
}
|
|
241
246
|
|
|
242
|
-
const
|
|
247
|
+
const connectedNames = getConnectedPluginNames(runlineDir);
|
|
248
|
+
const items = allPlugins.map((p) => ({
|
|
243
249
|
name: p.name,
|
|
244
250
|
actionCount: p.actions.length,
|
|
251
|
+
connected: connectedNames.has(p.name),
|
|
245
252
|
}));
|
|
246
253
|
const { piPlugins } = loadExtConfig(runlineDir);
|
|
247
254
|
const initial = piPlugins ?? [];
|
|
@@ -251,6 +258,27 @@ export default function (pi: ExtensionAPI) {
|
|
|
251
258
|
{ overlay: true, overlayOptions: { width: "80%", maxHeight: "80%" } },
|
|
252
259
|
);
|
|
253
260
|
|
|
261
|
+
// Ctrl-R inside the picker — reconfigure a single plugin and stop.
|
|
262
|
+
// Selection state isn't saved (user didn't press enter); they can
|
|
263
|
+
// re-open `/runline-plugins` to make selection changes.
|
|
264
|
+
if (result.reconfigure) {
|
|
265
|
+
const target = result.reconfigure;
|
|
266
|
+
const updated = await promptForCredentials(
|
|
267
|
+
ctx,
|
|
268
|
+
runlineDir,
|
|
269
|
+
allPlugins,
|
|
270
|
+
[target],
|
|
271
|
+
{ force: true },
|
|
272
|
+
);
|
|
273
|
+
ctx.ui.notify(
|
|
274
|
+
updated.length > 0
|
|
275
|
+
? `credentials updated for ${target}`
|
|
276
|
+
: `reconfigure cancelled for ${target}`,
|
|
277
|
+
"info",
|
|
278
|
+
);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
254
282
|
if (!result.selected) {
|
|
255
283
|
ctx.ui.notify("plugin selection cancelled", "info");
|
|
256
284
|
return;
|
|
@@ -270,7 +298,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
270
298
|
const saved = await promptForCredentials(
|
|
271
299
|
ctx,
|
|
272
300
|
runlineDir,
|
|
273
|
-
|
|
301
|
+
allPlugins,
|
|
274
302
|
newlyEnabled,
|
|
275
303
|
);
|
|
276
304
|
if (saved.length > 0) {
|
|
@@ -60,6 +60,25 @@ export function loadExtConfig(runlineDir: string): RunlineExtConfig {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/** Plugin names that already have a saved connection in .runline/config.json */
|
|
64
|
+
export function getConnectedPluginNames(runlineDir: string): Set<string> {
|
|
65
|
+
const configPath = path.join(runlineDir, "config.json");
|
|
66
|
+
if (!fs.existsSync(configPath)) return new Set();
|
|
67
|
+
try {
|
|
68
|
+
const raw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
69
|
+
const conns = Array.isArray(raw.connections) ? raw.connections : [];
|
|
70
|
+
return new Set(
|
|
71
|
+
conns
|
|
72
|
+
.map((c: { plugin?: unknown }) =>
|
|
73
|
+
typeof c.plugin === "string" ? c.plugin : null,
|
|
74
|
+
)
|
|
75
|
+
.filter((n: string | null): n is string => n !== null),
|
|
76
|
+
);
|
|
77
|
+
} catch {
|
|
78
|
+
return new Set();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
63
82
|
export function savePiPlugins(runlineDir: string, piPlugins: string[]): void {
|
|
64
83
|
const configPath = path.join(runlineDir, "config.json");
|
|
65
84
|
let raw: Record<string, unknown> = {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-runline",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Code mode for pi",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"keywords": [
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"lint:fix": "biome check --write extensions/"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"runline": "^0.5.
|
|
25
|
+
"runline": "^0.5.2"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@mariozechner/pi-coding-agent": "*",
|