@ai-outfitter/outfitter 1.15.0 → 1.16.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/README.md +4 -3
- package/code/pi-extension/src/outfitter-extension.js +157 -66
- package/code/pi-extension/src/outfitter-runtime-extension.js +41 -25
- package/dist/cli/commands/ListCommand.d.ts +2 -0
- package/dist/cli/commands/ListCommand.js +32 -8
- package/dist/cli/commands/ListCommand.js.map +1 -1
- package/dist/cli/commands/PiRuntimeLaunch.d.ts +6 -0
- package/dist/cli/commands/PiRuntimeLaunch.js +11 -1
- package/dist/cli/commands/PiRuntimeLaunch.js.map +1 -1
- package/dist/cli/commands/RunAgentCommand.d.ts +2 -0
- package/dist/cli/commands/RunAgentCommand.js +30 -13
- package/dist/cli/commands/RunAgentCommand.js.map +1 -1
- package/dist/cli/commands/SetupCommand.d.ts +22 -1
- package/dist/cli/commands/SetupCommand.js +97 -8
- package/dist/cli/commands/SetupCommand.js.map +1 -1
- package/dist/cli/commands/SyncCommand.js +7 -4
- package/dist/cli/commands/SyncCommand.js.map +1 -1
- package/dist/dump/WorkflowDump.js +3 -0
- package/dist/dump/WorkflowDump.js.map +1 -1
- package/dist/projection/ProjectHarness.js +3 -0
- package/dist/projection/ProjectHarness.js.map +1 -1
- package/dist/resolver/ResolverValidation.d.ts +6 -6
- package/dist/resolver/ResolverValidation.js +43 -2
- package/dist/resolver/ResolverValidation.js.map +1 -1
- package/dist/resolver/WorkflowDefinition.d.ts +10 -0
- package/dist/resolver/WorkflowDefinition.js.map +1 -1
- package/dist/resolver/WorkflowOutput.d.ts +9 -0
- package/dist/resolver/WorkflowOutput.js +40 -0
- package/dist/resolver/WorkflowOutput.js.map +1 -0
- package/dist/schemas/workflow.schema.json +26 -0
- package/dist/setup/DefaultCatalog.d.ts +1 -1
- package/dist/setup/DefaultCatalog.js +1 -1
- package/dist/setup/Setup.d.ts +11 -2
- package/dist/setup/Setup.js +19 -29
- package/dist/setup/Setup.js.map +1 -1
- package/dist/system/SystemExtensionHook.js +5 -1
- package/dist/system/SystemExtensionHook.js.map +1 -1
- package/docs/documentation/catalogs.md +36 -1
- package/docs/documentation/cli.md +7 -6
- package/docs/documentation/dump-and-bake.md +1 -1
- package/docs/documentation/hooks.md +1 -1
- package/docs/documentation/local-development.md +1 -1
- package/docs/documentation/settings.md +1 -1
- package/docs/documentation/support-matrix.md +1 -0
- package/package.json +1 -1
- package/src/schemas/workflow.schema.json +26 -0
package/README.md
CHANGED
|
@@ -70,9 +70,10 @@ mcp: [github]
|
|
|
70
70
|
default_agent: engineer
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
`outfitter setup`
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
`outfitter setup` runs the Pi-native walkthrough on top of `.agents`: choose a profile from the
|
|
74
|
+
default Outfitter catalog or import a different `.agents` catalog, then pick the home/project target
|
|
75
|
+
and default CLI agent. For a custom profile, write `.agents/agents/<id>/agent.md` and set
|
|
76
|
+
`default_agent`. The default picker is fetched from the immutable
|
|
76
77
|
`ai-outfitter/community-profiles` Release Please tag pinned by Outfitter—never from a sibling checkout.
|
|
77
78
|
Managed porting and persistent harness symlinks are deferred to
|
|
78
79
|
[#187](https://github.com/ai-outfitter/outfitter/issues/187).
|
|
@@ -8,6 +8,9 @@ const OUTFITTER_AGENT_CHOICES = '__OUTFITTER_AGENT_CHOICES__';
|
|
|
8
8
|
const OUTFITTER_CURRENT_DEFAULT = '__OUTFITTER_CURRENT_DEFAULT__';
|
|
9
9
|
const OUTFITTER_SETUP_SOURCE_URI = '__OUTFITTER_SETUP_SOURCE_URI__';
|
|
10
10
|
const OUTFITTER_AUTO_OPEN = '__OUTFITTER_AUTO_OPEN__';
|
|
11
|
+
// The offline placeholder provider the CLI gives this setup shell so Pi starts cleanly; it is not a
|
|
12
|
+
// real model provider and never counts as one.
|
|
13
|
+
const OUTFITTER_SETUP_PROVIDER = '__OUTFITTER_SETUP_PROVIDER__';
|
|
11
14
|
const OUTFITTER_ASCII_ART = [
|
|
12
15
|
'▄▄▄▄▄▄▄ ▄▄▄ ▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄',
|
|
13
16
|
'███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ▀▀▀ ███ ███',
|
|
@@ -21,6 +24,28 @@ const OUTFITTER_ASCII_GRADIENT = ['success', 'accent', 'text', 'muted', 'dim'];
|
|
|
21
24
|
const OUTFITTER_PROFILE_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
|
|
22
25
|
const OUTFITTER_PLAN_TOOLS = ['read', 'grep', 'find', 'ls'];
|
|
23
26
|
const OUTFITTER_DEFAULT_TOOLS = ['read', 'bash', 'edit', 'write'];
|
|
27
|
+
const OUTFITTER_IMPORT_CHOICE = '__import__';
|
|
28
|
+
const OUTFITTER_MORE_CHOICE = '__more__';
|
|
29
|
+
// Keep this dialog in sync with outfitter-runtime-extension.js, which shows the same prompt for
|
|
30
|
+
// users who already have .agents but no provider.
|
|
31
|
+
const OUTFITTER_PROVIDER_PROMPT = {
|
|
32
|
+
title: [
|
|
33
|
+
'Pi does not have a model provider connected yet.',
|
|
34
|
+
'Connect one now so Outfitter can use Pi. Credentials stay inside Pi.',
|
|
35
|
+
'Press Enter to open /login, or Escape to skip and connect later.',
|
|
36
|
+
],
|
|
37
|
+
items: [
|
|
38
|
+
{
|
|
39
|
+
value: 'connect',
|
|
40
|
+
label: 'Connect a model provider',
|
|
41
|
+
description: 'Opens Pi /login to sign in via OAuth or paste an API key.',
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
footer: 'enter connect escape skip',
|
|
45
|
+
};
|
|
46
|
+
const OUTFITTER_LOGIN_POLL_MS = 150;
|
|
47
|
+
// Polls allowed for Pi to open its /login UI after the command is submitted (about three seconds).
|
|
48
|
+
const OUTFITTER_LOGIN_OPEN_POLLS = 20;
|
|
24
49
|
const loadPrivateCatalogOnboarding = () => import('./pi-extension/privateCatalogOnboarding.js');
|
|
25
50
|
|
|
26
51
|
export default function outfitter(pi) {
|
|
@@ -70,16 +95,6 @@ export default function outfitter(pi) {
|
|
|
70
95
|
};
|
|
71
96
|
|
|
72
97
|
const createQuestionUi = (ctx) => ({
|
|
73
|
-
async selectSetupMode() {
|
|
74
|
-
const options = [
|
|
75
|
-
'Use the default Outfitter profile catalog',
|
|
76
|
-
'Create your own profile',
|
|
77
|
-
'Provide a different catalog to import',
|
|
78
|
-
];
|
|
79
|
-
const selected = await ctx.ui.select('How would you like to set up Outfitter?', options);
|
|
80
|
-
if (selected === undefined) return undefined;
|
|
81
|
-
return options.indexOf(selected) === 1 ? 'create' : options.indexOf(selected) === 2 ? 'catalog' : 'default';
|
|
82
|
-
},
|
|
83
98
|
async selectInstallTarget() {
|
|
84
99
|
const items = [
|
|
85
100
|
{
|
|
@@ -96,23 +111,62 @@ export default function outfitter(pi) {
|
|
|
96
111
|
];
|
|
97
112
|
return selectFromItems(ctx, ['Where should Outfitter install these settings?'], items, 'home');
|
|
98
113
|
},
|
|
99
|
-
|
|
100
|
-
|
|
114
|
+
// One screen replaces the old setup-mode and profile questions (#381): the featured catalog
|
|
115
|
+
// profiles first, a "More profiles" row for the rest, then a row that imports a different
|
|
116
|
+
// .agents catalog. The CLI marks featured profiles and orders them; abstract ones never arrive.
|
|
117
|
+
async selectProfileOrImport(profiles, currentDefault) {
|
|
118
|
+
const featured = profiles.filter((profile) => profile.featured === true);
|
|
119
|
+
const others = profiles.filter((profile) => profile.featured !== true);
|
|
120
|
+
const collapsible = featured.length > 0 && others.length > 0;
|
|
121
|
+
const importItem = {
|
|
122
|
+
value: OUTFITTER_IMPORT_CHOICE,
|
|
123
|
+
label: 'Import a different .agents catalog',
|
|
124
|
+
description: 'Point Outfitter at a GitHub repository that holds your own .agents settings.',
|
|
125
|
+
};
|
|
126
|
+
const profileItem = (profile) => ({
|
|
101
127
|
value: profile.id,
|
|
102
128
|
label: formatProfileLabel(profile, currentDefault),
|
|
103
129
|
description: profile.description,
|
|
104
|
-
})
|
|
130
|
+
});
|
|
105
131
|
const title = [
|
|
106
|
-
'Outfitter profile
|
|
132
|
+
'Choose an Outfitter profile',
|
|
107
133
|
'',
|
|
108
|
-
|
|
109
|
-
|
|
134
|
+
profiles.length === 0
|
|
135
|
+
? 'No profiles were found in the default Outfitter catalog. Fix the catalog sync or import a different one.'
|
|
136
|
+
: "Profiles come from the community catalog and are added to your .agents as the default for future 'outfitter' launches.",
|
|
110
137
|
];
|
|
111
|
-
const
|
|
112
|
-
currentDefault
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
138
|
+
const preferred = (candidates) =>
|
|
139
|
+
currentDefault !== undefined && candidates.some((profile) => profile.id === currentDefault)
|
|
140
|
+
? currentDefault
|
|
141
|
+
: candidates.some((profile) => profile.id === 'engineer')
|
|
142
|
+
? 'engineer'
|
|
143
|
+
: (candidates[0]?.id ?? OUTFITTER_IMPORT_CHOICE);
|
|
144
|
+
const showAll = collapsible && currentDefault !== undefined && others.some((profile) => profile.id === currentDefault);
|
|
145
|
+
let items;
|
|
146
|
+
let initialValue;
|
|
147
|
+
if (collapsible && !showAll) {
|
|
148
|
+
items = [
|
|
149
|
+
...featured.map(profileItem),
|
|
150
|
+
{
|
|
151
|
+
value: OUTFITTER_MORE_CHOICE,
|
|
152
|
+
label: 'More profiles (' + others.length + ')',
|
|
153
|
+
description: 'Show the other catalog profiles.',
|
|
154
|
+
},
|
|
155
|
+
importItem,
|
|
156
|
+
];
|
|
157
|
+
initialValue = preferred(featured);
|
|
158
|
+
} else {
|
|
159
|
+
items = [...profiles.map(profileItem), importItem];
|
|
160
|
+
initialValue = preferred(profiles);
|
|
161
|
+
}
|
|
162
|
+
let selected = await selectFromItems(ctx, title, items, initialValue);
|
|
163
|
+
if (selected === OUTFITTER_MORE_CHOICE) {
|
|
164
|
+
// Same picker with every profile; Engineer (or the current default) stays preselected.
|
|
165
|
+
selected = await selectFromItems(ctx, title, [...profiles.map(profileItem), importItem], preferred(profiles));
|
|
166
|
+
}
|
|
167
|
+
if (selected === undefined) return undefined;
|
|
168
|
+
if (selected === OUTFITTER_IMPORT_CHOICE) return { import: true };
|
|
169
|
+
return { profile: profiles.find((profile) => profile.id === selected) };
|
|
116
170
|
},
|
|
117
171
|
async selectCliAgent() {
|
|
118
172
|
const items = [
|
|
@@ -149,6 +203,70 @@ export default function outfitter(pi) {
|
|
|
149
203
|
notify: (message, type = 'info') => ctx.ui.notify(message, type),
|
|
150
204
|
});
|
|
151
205
|
|
|
206
|
+
// Real providers only: the CLI stamps a placeholder provider into this setup shell so Pi starts
|
|
207
|
+
// without a login warning, and that placeholder must never satisfy the provider check.
|
|
208
|
+
const countConnectedModels = async (ctx) => {
|
|
209
|
+
if (ctx.modelRegistry === undefined || typeof ctx.modelRegistry.getAvailable !== 'function') return 0;
|
|
210
|
+
try {
|
|
211
|
+
const available = await ctx.modelRegistry.getAvailable();
|
|
212
|
+
return Array.isArray(available)
|
|
213
|
+
? available.filter((model) => model?.provider !== OUTFITTER_SETUP_PROVIDER).length
|
|
214
|
+
: 0;
|
|
215
|
+
} catch {
|
|
216
|
+
return 0;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// Submits /login and resolves 'connected' once a real provider is available, or 'cancelled' when
|
|
221
|
+
// the user leaves Pi's login UI without one. Pi emits no auth event and keeps the placeholder
|
|
222
|
+
// model selected after a login (so `model_select` never fires here); completion is observed
|
|
223
|
+
// through the model registry, which Pi refreshes right after saving credentials, and abandonment
|
|
224
|
+
// through focus returning to the editor after the login UI was shown.
|
|
225
|
+
const openLoginAndWait = (ctx) =>
|
|
226
|
+
ctx.ui.custom(
|
|
227
|
+
(tui, _theme, _keybindings, done) => {
|
|
228
|
+
let editor;
|
|
229
|
+
let loginUiSeen = false;
|
|
230
|
+
let polls = 0;
|
|
231
|
+
const poll = async () => {
|
|
232
|
+
if ((await countConnectedModels(ctx)) > 0) return done('connected');
|
|
233
|
+
polls += 1;
|
|
234
|
+
if (tui.focusedComponent !== editor) loginUiSeen = true;
|
|
235
|
+
else if (loginUiSeen || polls >= OUTFITTER_LOGIN_OPEN_POLLS) return done('cancelled');
|
|
236
|
+
setTimeout(poll, OUTFITTER_LOGIN_POLL_MS);
|
|
237
|
+
};
|
|
238
|
+
ctx.ui.setEditorText('/login');
|
|
239
|
+
setTimeout(() => {
|
|
240
|
+
editor = tui.focusedComponent;
|
|
241
|
+
// Without a focused editor the command cannot be submitted, so there is nothing to wait for.
|
|
242
|
+
if (editor?.handleInput === undefined) return done('cancelled');
|
|
243
|
+
editor.handleInput('\r');
|
|
244
|
+
setTimeout(poll, OUTFITTER_LOGIN_POLL_MS);
|
|
245
|
+
}, 25);
|
|
246
|
+
return { render: () => [], invalidate: () => undefined };
|
|
247
|
+
},
|
|
248
|
+
{ overlay: true, overlayOptions: { nonCapturing: true, visible: () => false } },
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Second half of the guided first run: once .agents is written, offer Pi's native /login inside
|
|
252
|
+
// this same session so the relaunched profile starts with credentials in place. Escape skips and
|
|
253
|
+
// the CLI prints the one-line /login hint instead of prompting again.
|
|
254
|
+
const connectModelProvider = async (ctx) => {
|
|
255
|
+
if (ctx.mode !== 'tui' || typeof ctx.ui.custom !== 'function') return 'unavailable';
|
|
256
|
+
if ((await countConnectedModels(ctx)) > 0) return 'available';
|
|
257
|
+
const selected = await selectDescribedOption(
|
|
258
|
+
ctx,
|
|
259
|
+
OUTFITTER_PROVIDER_PROMPT.title,
|
|
260
|
+
OUTFITTER_PROVIDER_PROMPT.items,
|
|
261
|
+
'connect',
|
|
262
|
+
{ footer: OUTFITTER_PROVIDER_PROMPT.footer },
|
|
263
|
+
);
|
|
264
|
+
if (selected === 'connect' && (await openLoginAndWait(ctx)) === 'connected') return 'connected';
|
|
265
|
+
return 'skipped';
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// `run` relaunches the selected profile as soon as this shell exits, so no restart notice here.
|
|
269
|
+
// Only the Pi harness needs a Pi model provider.
|
|
152
270
|
const finish = async (ctx, selection, message) => {
|
|
153
271
|
await writeSetupResult(selection);
|
|
154
272
|
ctx.ui.notify(
|
|
@@ -158,6 +276,9 @@ export default function outfitter(pi) {
|
|
|
158
276
|
].join('\n'),
|
|
159
277
|
'info',
|
|
160
278
|
);
|
|
279
|
+
if (selection.harness === 'pi' && (await connectModelProvider(ctx)) === 'skipped') {
|
|
280
|
+
await writeSetupResult({ ...selection, providerConnection: 'skipped' });
|
|
281
|
+
}
|
|
161
282
|
ctx.shutdown();
|
|
162
283
|
};
|
|
163
284
|
|
|
@@ -166,16 +287,7 @@ export default function outfitter(pi) {
|
|
|
166
287
|
ctx.shutdown();
|
|
167
288
|
};
|
|
168
289
|
|
|
169
|
-
const runDefaultCatalogOnboarding = async (ctx, questionUi) => {
|
|
170
|
-
if (OUTFITTER_AGENT_CHOICES.length === 0) {
|
|
171
|
-
questionUi.notify(
|
|
172
|
-
'No profiles were found in the default Outfitter profile catalog. Fix the catalog sync or provide a different catalog.',
|
|
173
|
-
'error',
|
|
174
|
-
);
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
const selectedProfile = await questionUi.selectProfile(OUTFITTER_AGENT_CHOICES, OUTFITTER_CURRENT_DEFAULT);
|
|
178
|
-
if (selectedProfile === undefined) return cancel(ctx);
|
|
290
|
+
const runDefaultCatalogOnboarding = async (ctx, questionUi, selectedProfile) => {
|
|
179
291
|
if (!OUTFITTER_PROFILE_ID_PATTERN.test(selectedProfile.id)) {
|
|
180
292
|
questionUi.notify('Selected profile id is not filesystem-safe; no settings were changed.', 'error');
|
|
181
293
|
return;
|
|
@@ -190,32 +302,6 @@ export default function outfitter(pi) {
|
|
|
190
302
|
[
|
|
191
303
|
"Outfitter saved default profile '" + selectedProfile.id + "' to " + settingsPath + '.',
|
|
192
304
|
'Profile choices were loaded from the default Outfitter profile catalog, not generated locally.',
|
|
193
|
-
"It applies on the next 'outfitter' launch; restart Outfitter to load the selected profile.",
|
|
194
|
-
].join('\n'),
|
|
195
|
-
);
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
const runCreateProfileOnboarding = async (ctx, questionUi) => {
|
|
199
|
-
const enteredProfileId = normalizeInputValue(await questionUi.input('Profile id', 'my_profile'));
|
|
200
|
-
const profileId = enteredProfileId?.replaceAll('_', '-');
|
|
201
|
-
if (!profileId || !OUTFITTER_PROFILE_ID_PATTERN.test(profileId)) {
|
|
202
|
-
questionUi.notify('Profile id is not filesystem-safe; no settings were changed.', 'error');
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
const label = normalizeInputValue(await questionUi.input('Profile label', profileId));
|
|
206
|
-
const choice = await askTargetAndHarness(questionUi);
|
|
207
|
-
if (choice === undefined) return cancel(ctx);
|
|
208
|
-
const { target, harness } = choice;
|
|
209
|
-
const root = installRoot(target);
|
|
210
|
-
const profilePath = root + '/.agents/agents/' + profileId + '/agent.md';
|
|
211
|
-
const settingsPath = root + '/.agents/settings.yml';
|
|
212
|
-
await finish(
|
|
213
|
-
ctx,
|
|
214
|
-
{ setupMode: 'create', agentId: profileId, agentLabel: label, harness, target },
|
|
215
|
-
[
|
|
216
|
-
"Outfitter created profile '" + profileId + "' at " + profilePath + '.',
|
|
217
|
-
'Outfitter saved settings to ' + settingsPath + '.',
|
|
218
|
-
"It applies on the next 'outfitter' launch; restart Outfitter to load the selected profile.",
|
|
219
305
|
].join('\n'),
|
|
220
306
|
);
|
|
221
307
|
};
|
|
@@ -303,11 +389,10 @@ export default function outfitter(pi) {
|
|
|
303
389
|
if (OUTFITTER_SETUP_SOURCE_URI !== undefined) {
|
|
304
390
|
return await runProvidedSourceOnboarding(ctx, questionUi, OUTFITTER_SETUP_SOURCE_URI);
|
|
305
391
|
}
|
|
306
|
-
const
|
|
307
|
-
if (
|
|
308
|
-
if (
|
|
309
|
-
|
|
310
|
-
return await runDefaultCatalogOnboarding(ctx, questionUi);
|
|
392
|
+
const choice = await questionUi.selectProfileOrImport(OUTFITTER_AGENT_CHOICES, OUTFITTER_CURRENT_DEFAULT);
|
|
393
|
+
if (choice === undefined) return cancel(ctx);
|
|
394
|
+
if (choice.import) return await runRemoteSettingsOnboarding(ctx, questionUi);
|
|
395
|
+
return await runDefaultCatalogOnboarding(ctx, questionUi, choice.profile);
|
|
311
396
|
} finally {
|
|
312
397
|
onboardingRunning = false;
|
|
313
398
|
}
|
|
@@ -475,7 +560,9 @@ const selectFromItems = async (ctx, titleLines, items, initialValue) => {
|
|
|
475
560
|
: items.find((item) => item.label === selected)?.value;
|
|
476
561
|
};
|
|
477
562
|
|
|
478
|
-
|
|
563
|
+
// `options.footer` overrides the key legend; by default a single-item list omits "↑↓ navigate"
|
|
564
|
+
// because the arrow keys do nothing there.
|
|
565
|
+
const selectDescribedOption = (ctx, titleLines, items, initialValue, options = {}) =>
|
|
479
566
|
ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
480
567
|
let selectedIndex = Math.max(
|
|
481
568
|
0,
|
|
@@ -535,7 +622,7 @@ const selectDescribedOption = (ctx, titleLines, items, initialValue) =>
|
|
|
535
622
|
renderSelectedItem(prefix, label, selected ? item.description : undefined);
|
|
536
623
|
});
|
|
537
624
|
lines.push('');
|
|
538
|
-
add(theme.fg('dim',
|
|
625
|
+
add(theme.fg('dim', options.footer ?? pickerFooter(items.length)));
|
|
539
626
|
add(theme.fg('accent', '─'.repeat(maxWidth)));
|
|
540
627
|
cachedWidth = maxWidth;
|
|
541
628
|
cachedLines = lines;
|
|
@@ -555,9 +642,13 @@ const selectDescribedOption = (ctx, titleLines, items, initialValue) =>
|
|
|
555
642
|
};
|
|
556
643
|
});
|
|
557
644
|
|
|
645
|
+
// Rows show the display label only (the CLI falls back to the id when a profile has none); the id
|
|
646
|
+
// still travels in the handoff.
|
|
558
647
|
const formatProfileLabel = (profile, currentDefault) => {
|
|
559
648
|
const current = profile.id === currentDefault ? ' (current)' : '';
|
|
560
649
|
const recommended = currentDefault === undefined && profile.id === 'engineer' ? ' (Recommended)' : '';
|
|
561
|
-
|
|
562
|
-
return profile.id + label + current + recommended;
|
|
650
|
+
return (profile.label || profile.id) + current + recommended;
|
|
563
651
|
};
|
|
652
|
+
|
|
653
|
+
const pickerFooter = (itemCount) =>
|
|
654
|
+
(itemCount > 1 ? '↑↓ navigate ' : '') + 'enter select escape/ctrl+c cancel';
|
|
@@ -1,17 +1,37 @@
|
|
|
1
1
|
import { Key, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from '@earendil-works/pi-tui';
|
|
2
2
|
|
|
3
3
|
const OUTFITTER_ACTIVE_PROFILE = '__OUTFITTER_ACTIVE_PROFILE__';
|
|
4
|
+
// 'dialog' offers /login when no provider is connected. 'hint' only prints the one-line reminder;
|
|
5
|
+
// the CLI stamps it when the user skipped the provider step of first-run setup moments earlier.
|
|
6
|
+
const OUTFITTER_PROVIDER_PROMPT_MODE = '__OUTFITTER_PROVIDER_PROMPT_MODE__';
|
|
7
|
+
// Keep this dialog in sync with outfitter-extension.js, which shows the same prompt right after
|
|
8
|
+
// the first-run walkthrough.
|
|
9
|
+
const OUTFITTER_PROVIDER_PROMPT = {
|
|
10
|
+
title: [
|
|
11
|
+
'Pi does not have a model provider connected yet.',
|
|
12
|
+
'Connect one now so Outfitter can use Pi. Credentials stay inside Pi.',
|
|
13
|
+
'Press Enter to open /login, or Escape to skip and connect later.',
|
|
14
|
+
],
|
|
15
|
+
items: [
|
|
16
|
+
{
|
|
17
|
+
value: 'connect',
|
|
18
|
+
label: 'Connect a model provider',
|
|
19
|
+
description: 'Opens Pi /login to sign in via OAuth or paste an API key.',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
footer: 'enter connect escape skip',
|
|
23
|
+
};
|
|
24
|
+
const OUTFITTER_PROVIDER_HINT = "No model provider connected yet. Run '/login' inside Outfitter to connect one.";
|
|
4
25
|
|
|
5
26
|
// Outfitter runtime extension. Unlike the setup walkthrough extension
|
|
6
27
|
// (outfitter-extension.js), this file is loaded into the real profile pi session. It restores the
|
|
7
|
-
// compact Outfitter + profile header, plus the
|
|
8
|
-
//
|
|
9
|
-
// to pi's native /login command, which persists credentials in
|
|
10
|
-
// a default model in-session.
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// loading it via --extension.
|
|
28
|
+
// compact Outfitter + profile header, plus the "no provider connected yet" sign-in prompt for
|
|
29
|
+
// users who already have .agents but no provider: when pi starts with no models available, it
|
|
30
|
+
// offers to connect one and delegates to pi's native /login command, which persists credentials in
|
|
31
|
+
// pi's own agent directory and selects a default model in-session. First-run users normally
|
|
32
|
+
// connect a provider inside the setup walkthrough instead (OFTR-010.7), so this prompt does not
|
|
33
|
+
// repeat right after setup. The CLI stamps profile metadata into a per-run copy before loading it
|
|
34
|
+
// via --extension.
|
|
15
35
|
|
|
16
36
|
export default function outfitterRuntime(pi) {
|
|
17
37
|
let loginSubmitted = false;
|
|
@@ -48,19 +68,10 @@ export default function outfitterRuntime(pi) {
|
|
|
48
68
|
if (typeof ctx.ui.custom !== 'function') return true;
|
|
49
69
|
const selected = await selectDescribedOption(
|
|
50
70
|
ctx,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
'Connect one now so Outfitter can use Pi.',
|
|
54
|
-
'Credentials stay inside Pi.',
|
|
55
|
-
],
|
|
56
|
-
[
|
|
57
|
-
{
|
|
58
|
-
value: 'connect',
|
|
59
|
-
label: 'Connect a model provider',
|
|
60
|
-
description: 'Open Pi /login to sign in via OAuth or an API key.',
|
|
61
|
-
},
|
|
62
|
-
],
|
|
71
|
+
OUTFITTER_PROVIDER_PROMPT.title,
|
|
72
|
+
OUTFITTER_PROVIDER_PROMPT.items,
|
|
63
73
|
'connect',
|
|
74
|
+
{ footer: OUTFITTER_PROVIDER_PROMPT.footer },
|
|
64
75
|
);
|
|
65
76
|
return selected === 'connect';
|
|
66
77
|
};
|
|
@@ -69,7 +80,10 @@ export default function outfitterRuntime(pi) {
|
|
|
69
80
|
if (loginSubmitted || ctx.mode !== 'tui') return;
|
|
70
81
|
const availableModelCount = await getAvailableModelCount(ctx);
|
|
71
82
|
if (availableModelCount > 0) return;
|
|
72
|
-
if (!(await confirmModelProviderConnection(ctx)))
|
|
83
|
+
if (OUTFITTER_PROVIDER_PROMPT_MODE === 'hint' || !(await confirmModelProviderConnection(ctx))) {
|
|
84
|
+
ctx.ui.notify(OUTFITTER_PROVIDER_HINT, 'warning');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
73
87
|
loginSubmitted = await submitSlashCommand(ctx, '/login');
|
|
74
88
|
};
|
|
75
89
|
|
|
@@ -107,9 +121,9 @@ const setRuntimeHeader = (ctx) => {
|
|
|
107
121
|
};
|
|
108
122
|
|
|
109
123
|
// Renders a described-option picker identical to the setup walkthrough's, so the runtime sign-in
|
|
110
|
-
// prompt matches the rest of the Outfitter UI.
|
|
111
|
-
//
|
|
112
|
-
const selectDescribedOption = (ctx, titleLines, items, initialValue) =>
|
|
124
|
+
// prompt matches the rest of the Outfitter UI. `options.footer` overrides the key legend; by
|
|
125
|
+
// default a single-item list omits "↑↓ navigate" because the arrow keys do nothing there.
|
|
126
|
+
const selectDescribedOption = (ctx, titleLines, items, initialValue, options = {}) =>
|
|
113
127
|
ctx.ui.custom((tui, theme, _keybindings, done) => {
|
|
114
128
|
let selectedIndex = Math.max(
|
|
115
129
|
0,
|
|
@@ -169,7 +183,7 @@ const selectDescribedOption = (ctx, titleLines, items, initialValue) =>
|
|
|
169
183
|
renderSelectedItem(prefix, label, selected ? item.description : undefined);
|
|
170
184
|
});
|
|
171
185
|
lines.push('');
|
|
172
|
-
add(theme.fg('dim',
|
|
186
|
+
add(theme.fg('dim', options.footer ?? pickerFooter(items.length)));
|
|
173
187
|
add(theme.fg('accent', '─'.repeat(maxWidth)));
|
|
174
188
|
cachedWidth = maxWidth;
|
|
175
189
|
cachedLines = lines;
|
|
@@ -188,3 +202,5 @@ const selectDescribedOption = (ctx, titleLines, items, initialValue) =>
|
|
|
188
202
|
},
|
|
189
203
|
};
|
|
190
204
|
});
|
|
205
|
+
|
|
206
|
+
const pickerFooter = (itemCount) => (itemCount > 1 ? '↑↓ navigate ' : '') + 'enter select escape/ctrl+c cancel';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ResourceKind } from '../../resolver/Resource.js';
|
|
2
|
+
import type { ResolvedWorkflowOutputs } from '../../resolver/WorkflowOutput.js';
|
|
2
3
|
import type { CommandObject } from './CommandObject.js';
|
|
3
4
|
export interface ListInput {
|
|
4
5
|
readonly homeDirectory: string;
|
|
@@ -18,6 +19,7 @@ export interface ListResourceEntry {
|
|
|
18
19
|
readonly layer: string;
|
|
19
20
|
readonly path: string;
|
|
20
21
|
readonly ownerAgent: string | null;
|
|
22
|
+
readonly outputs?: ResolvedWorkflowOutputs;
|
|
21
23
|
}
|
|
22
24
|
export interface ListCommandDependencies {
|
|
23
25
|
readonly homeDirectory?: string;
|
|
@@ -3,6 +3,8 @@ import { Command } from 'commander';
|
|
|
3
3
|
import { strictAmbiguityFailureMessage } from '../../resolver/AmbiguityWarnings.js';
|
|
4
4
|
import { agentLocalKinds, compareSlugs, findResource, listAgentResources, listResources, resourceKinds, } from '../../resolver/Resource.js';
|
|
5
5
|
import { resolveEffectiveSet } from '../../resolver/ResolverContext.js';
|
|
6
|
+
import { isWorkflowDefinitionIssue, readWorkflowDefinition } from '../../resolver/WorkflowDefinition.js';
|
|
7
|
+
import { resolveWorkflowOutputs } from '../../resolver/WorkflowOutput.js';
|
|
6
8
|
import { formatSettingsIssue } from '../../settings/SettingsLoader.js';
|
|
7
9
|
import { resolveHomeDirectory, resolveProjectDirectory } from './ProcessDefaults.js';
|
|
8
10
|
const kindByPlural = new Map([
|
|
@@ -40,6 +42,32 @@ const listGlobalResources = (set, kind, enabledWorkflows) => kind === 'workflow'
|
|
|
40
42
|
return resource === undefined ? [] : [resource];
|
|
41
43
|
})
|
|
42
44
|
: listResources(set, kind);
|
|
45
|
+
const readWorkflowDefinitions = (set) => {
|
|
46
|
+
const definitions = new Map();
|
|
47
|
+
for (const resource of listResources(set, 'workflow')) {
|
|
48
|
+
const definition = readWorkflowDefinition(resource.winner.path);
|
|
49
|
+
if (!isWorkflowDefinitionIssue(definition))
|
|
50
|
+
definitions.set(resource.slug, definition);
|
|
51
|
+
}
|
|
52
|
+
return definitions;
|
|
53
|
+
};
|
|
54
|
+
const workflowDefinitionsForKinds = (set, kinds) => kinds.includes('workflow') ? readWorkflowDefinitions(set) : new Map();
|
|
55
|
+
const listEntry = (resource, definitions) => {
|
|
56
|
+
const provenance = {
|
|
57
|
+
kind: resource.kind,
|
|
58
|
+
slug: resource.slug,
|
|
59
|
+
layer: resource.winner.layer.label,
|
|
60
|
+
path: resource.winner.path,
|
|
61
|
+
ownerAgent: resource.winner.ownerAgent ?? null,
|
|
62
|
+
};
|
|
63
|
+
if (resource.kind !== 'workflow')
|
|
64
|
+
return provenance;
|
|
65
|
+
const definition = definitions.get(resource.slug);
|
|
66
|
+
return {
|
|
67
|
+
...provenance,
|
|
68
|
+
outputs: definition === undefined ? {} : resolveWorkflowOutputs(definition, definitions),
|
|
69
|
+
};
|
|
70
|
+
};
|
|
43
71
|
export const executeListCommand = (input) => {
|
|
44
72
|
const { set, settings, settingsIssues, warnings, ambiguityWarnings } = resolveEffectiveSet(input);
|
|
45
73
|
if (settingsIssues.length > 0) {
|
|
@@ -52,7 +80,9 @@ export const executeListCommand = (input) => {
|
|
|
52
80
|
}
|
|
53
81
|
assertKnownAgent(set, input.agent);
|
|
54
82
|
const entries = [];
|
|
55
|
-
|
|
83
|
+
const kinds = resolveKindFilter(input.kind);
|
|
84
|
+
const definitions = workflowDefinitionsForKinds(set, kinds);
|
|
85
|
+
for (const kind of kinds) {
|
|
56
86
|
const hasAgentContext = input.agent !== undefined && agentLocalKinds.includes(kind);
|
|
57
87
|
const globalResources = listGlobalResources(set, kind, settings.workflows);
|
|
58
88
|
const localResources = hasAgentContext ? listAgentResources(set, input.agent, kind) : [];
|
|
@@ -61,13 +91,7 @@ export const executeListCommand = (input) => {
|
|
|
61
91
|
resources.set(resource.slug, resource);
|
|
62
92
|
entries.push(...[...resources.values()]
|
|
63
93
|
.sort((left, right) => compareSlugs(left.slug, right.slug))
|
|
64
|
-
.map((resource) => (
|
|
65
|
-
kind: resource.kind,
|
|
66
|
-
slug: resource.slug,
|
|
67
|
-
layer: resource.winner.layer.label,
|
|
68
|
-
path: resource.winner.path,
|
|
69
|
-
ownerAgent: resource.winner.ownerAgent ?? null,
|
|
70
|
-
})));
|
|
94
|
+
.map((resource) => listEntry(resource, definitions)));
|
|
71
95
|
messages.push(`${pluralByKind.get(kind)}${hasAgentContext ? ` (agent ${input.agent})` : ''}:`);
|
|
72
96
|
messages.push(...(resources.size === 0
|
|
73
97
|
? [' (none)']
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ListCommand.js","sourceRoot":"","sources":["../../../src/cli/commands/ListCommand.ts"],"names":[],"mappings":"AAAA,oEAAoE;AAEpE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,6BAA6B,EAAE,MAAM,qCAAqC,CAAC;AAEpF,OAAO,EACL,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACb,aAAa,GACd,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAEvE,OAAO,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"ListCommand.js","sourceRoot":"","sources":["../../../src/cli/commands/ListCommand.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,9 +3,15 @@ export interface PiRuntimeProfileIdentity {
|
|
|
3
3
|
readonly id: string;
|
|
4
4
|
readonly label?: string;
|
|
5
5
|
}
|
|
6
|
+
export type PiProviderPromptMode = 'dialog' | 'hint';
|
|
6
7
|
export interface PiRuntimeExtensionInput {
|
|
7
8
|
readonly profile?: PiRuntimeProfileIdentity;
|
|
8
9
|
readonly rootDirectory: string;
|
|
10
|
+
/**
|
|
11
|
+
* How the extension reacts when pi has no model provider: offer /login in a dialog (default), or
|
|
12
|
+
* print only a one-line /login hint because the user just skipped that step in first-run setup.
|
|
13
|
+
*/
|
|
14
|
+
readonly providerPrompt?: PiProviderPromptMode;
|
|
9
15
|
}
|
|
10
16
|
export declare const isNonInteractivePiLaunch: (args: readonly string[]) => boolean;
|
|
11
17
|
/** Stamps per-run identity metadata into the runtime extension source. */
|
|
@@ -23,7 +23,17 @@ export const createPiRuntimeExtensionContent = (input) => {
|
|
|
23
23
|
/* v8 ignore next -- defensive: the runtime extension ships with the package, so it resolves in practice. */
|
|
24
24
|
if (extensionPath === undefined)
|
|
25
25
|
throw new Error('Outfitter runtime extension was not found.');
|
|
26
|
-
|
|
26
|
+
const values = {
|
|
27
|
+
OUTFITTER_ACTIVE_PROFILE: input.profile,
|
|
28
|
+
OUTFITTER_PROVIDER_PROMPT_MODE: input.providerPrompt ?? 'dialog',
|
|
29
|
+
};
|
|
30
|
+
// Each placeholder becomes its JSON value (an absent profile stamps `undefined`). Stamped values
|
|
31
|
+
// are never rescanned, so placeholder-shaped profile metadata is left alone.
|
|
32
|
+
let content = readFileSync(extensionPath, 'utf8');
|
|
33
|
+
for (const [name, value] of Object.entries(values)) {
|
|
34
|
+
content = content.replace(new RegExp(`["']__${name}__["']`, 'gu'), () => JSON.stringify(value) ?? 'undefined');
|
|
35
|
+
}
|
|
36
|
+
return content;
|
|
27
37
|
};
|
|
28
38
|
/**
|
|
29
39
|
* Materializes and prepends `--extension <runtime>` to an interactive pi launch. Non-pi and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PiRuntimeLaunch.js","sourceRoot":"","sources":["../../../src/cli/commands/PiRuntimeLaunch.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,mGAAmG;AACnG,kGAAkG;AAClG,2EAA2E;AAC3E,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAG1E,MAAM,2BAA2B,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC;AAC5F,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;AAEhE,MAAM,qBAAqB,GAAG,iDAAiD,CAAC;
|
|
1
|
+
{"version":3,"file":"PiRuntimeLaunch.js","sourceRoot":"","sources":["../../../src/cli/commands/PiRuntimeLaunch.ts"],"names":[],"mappings":"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"}
|
|
@@ -33,6 +33,8 @@ export interface RunAgentInput {
|
|
|
33
33
|
readonly launcher: AgentProcessLauncher;
|
|
34
34
|
/** Onboarding to run once when no agent is selected and settings define no `default_agent`. */
|
|
35
35
|
readonly setup?: SetupRunner;
|
|
36
|
+
/** The user skipped connecting a Pi provider in setup; the pi session prints a /login hint instead of prompting. */
|
|
37
|
+
readonly providerPromptSkipped?: boolean;
|
|
36
38
|
/** Keep the runtime projection directory after the run (debugging). */
|
|
37
39
|
readonly retainProjection?: boolean;
|
|
38
40
|
/** Sink for setup notices and warnings; emitted before launch so they precede the pi session. */
|