@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.
Files changed (46) hide show
  1. package/README.md +4 -3
  2. package/code/pi-extension/src/outfitter-extension.js +157 -66
  3. package/code/pi-extension/src/outfitter-runtime-extension.js +41 -25
  4. package/dist/cli/commands/ListCommand.d.ts +2 -0
  5. package/dist/cli/commands/ListCommand.js +32 -8
  6. package/dist/cli/commands/ListCommand.js.map +1 -1
  7. package/dist/cli/commands/PiRuntimeLaunch.d.ts +6 -0
  8. package/dist/cli/commands/PiRuntimeLaunch.js +11 -1
  9. package/dist/cli/commands/PiRuntimeLaunch.js.map +1 -1
  10. package/dist/cli/commands/RunAgentCommand.d.ts +2 -0
  11. package/dist/cli/commands/RunAgentCommand.js +30 -13
  12. package/dist/cli/commands/RunAgentCommand.js.map +1 -1
  13. package/dist/cli/commands/SetupCommand.d.ts +22 -1
  14. package/dist/cli/commands/SetupCommand.js +97 -8
  15. package/dist/cli/commands/SetupCommand.js.map +1 -1
  16. package/dist/cli/commands/SyncCommand.js +7 -4
  17. package/dist/cli/commands/SyncCommand.js.map +1 -1
  18. package/dist/dump/WorkflowDump.js +3 -0
  19. package/dist/dump/WorkflowDump.js.map +1 -1
  20. package/dist/projection/ProjectHarness.js +3 -0
  21. package/dist/projection/ProjectHarness.js.map +1 -1
  22. package/dist/resolver/ResolverValidation.d.ts +6 -6
  23. package/dist/resolver/ResolverValidation.js +43 -2
  24. package/dist/resolver/ResolverValidation.js.map +1 -1
  25. package/dist/resolver/WorkflowDefinition.d.ts +10 -0
  26. package/dist/resolver/WorkflowDefinition.js.map +1 -1
  27. package/dist/resolver/WorkflowOutput.d.ts +9 -0
  28. package/dist/resolver/WorkflowOutput.js +40 -0
  29. package/dist/resolver/WorkflowOutput.js.map +1 -0
  30. package/dist/schemas/workflow.schema.json +26 -0
  31. package/dist/setup/DefaultCatalog.d.ts +1 -1
  32. package/dist/setup/DefaultCatalog.js +1 -1
  33. package/dist/setup/Setup.d.ts +11 -2
  34. package/dist/setup/Setup.js +19 -29
  35. package/dist/setup/Setup.js.map +1 -1
  36. package/dist/system/SystemExtensionHook.js +5 -1
  37. package/dist/system/SystemExtensionHook.js.map +1 -1
  38. package/docs/documentation/catalogs.md +36 -1
  39. package/docs/documentation/cli.md +7 -6
  40. package/docs/documentation/dump-and-bake.md +1 -1
  41. package/docs/documentation/hooks.md +1 -1
  42. package/docs/documentation/local-development.md +1 -1
  43. package/docs/documentation/settings.md +1 -1
  44. package/docs/documentation/support-matrix.md +1 -0
  45. package/package.json +1 -1
  46. 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` restores the original Pi-native profile-catalog walkthrough on top of `.agents`:
74
- choose the default Outfitter catalog, create your own profile, or provide another catalog, then pick
75
- the home/project target and default CLI agent. The default picker is fetched from the immutable
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
- async selectProfile(profiles, currentDefault) {
100
- const items = profiles.map((profile) => ({
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 setup',
132
+ 'Choose an Outfitter profile',
107
133
  '',
108
- "Choose the default profile from the selected catalog for future 'outfitter' launches.",
109
- 'The current Pi process keeps the profile it started with; this setting applies on the next launch.',
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 initialProfileId =
112
- currentDefault ?? (profiles.some((profile) => profile.id === 'engineer') ? 'engineer' : profiles[0]?.id);
113
- const selectedId = await selectFromItems(ctx, title, items, initialProfileId);
114
- if (selectedId === undefined) return undefined;
115
- return profiles.find((profile) => profile.id === selectedId);
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 setupMode = await questionUi.selectSetupMode();
307
- if (setupMode === undefined) return cancel(ctx);
308
- if (setupMode === 'catalog') return await runRemoteSettingsOnboarding(ctx, questionUi);
309
- if (setupMode === 'create') return await runCreateProfileOnboarding(ctx, questionUi);
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
- const selectDescribedOption = (ctx, titleLines, items, initialValue) =>
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', '↑↓ navigate enter select escape/ctrl+c cancel'));
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
- const label = profile.label ? ' — ' + profile.label : '';
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 original "no provider connected yet"
8
- // sign-in prompt: when pi starts with no models available, it offers to connect one and delegates
9
- // to pi's native /login command, which persists credentials in pi's own agent directory and selects
10
- // a default model in-session.
11
- //
12
- // The setup pi stays deliberately model-free and MUST NOT open /login (OFTR-010.1.4); that is why
13
- // the auto-login lives here instead. The CLI stamps profile metadata into a per-run copy before
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
- 'Pi does not have a model provider connected yet.',
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))) return;
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. Falls back to a plain label list when pi does not
111
- // support custom UI, then resolves the chosen label back to its item value.
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', '↑↓ navigate enter select escape/ctrl+c cancel'));
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
- for (const kind of resolveKindFilter(input.kind)) {
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;AA8BrF,MAAM,YAAY,GAAsC,IAAI,GAAG,CAAC;IAC9D,CAAC,QAAQ,EAAE,OAAO,CAAC;IACnB,CAAC,QAAQ,EAAE,OAAO,CAAC;IACnB,CAAC,WAAW,EAAE,WAAW,CAAC;IAC1B,CAAC,UAAU,EAAE,SAAS,CAAC;IACvB,CAAC,WAAW,EAAE,UAAU,CAAC;CAC1B,CAAC,CAAC;AAEH,MAAM,YAAY,GAAsC,IAAI,GAAG,CAAC;IAC9D,CAAC,OAAO,EAAE,QAAQ,CAAC;IACnB,CAAC,OAAO,EAAE,QAAQ,CAAC;IACnB,CAAC,WAAW,EAAE,WAAW,CAAC;IAC1B,CAAC,SAAS,EAAE,UAAU,CAAC;IACvB,CAAC,UAAU,EAAE,WAAW,CAAC;CAC1B,CAAC,CAAC;AAEH,MAAM,iBAAiB,GAAG,CAAC,IAAwB,EAA2B,EAAE;IAC9E,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAExC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,uBAAuB,CAAC,GAAG,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/G,CAAC;IAED,OAAO,CAAC,QAAQ,CAAC,CAAC;AACpB,CAAC,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAC,GAAyB,EAAE,KAAyB,EAAQ,EAAE;IACtF,IAAI,KAAK,KAAK,SAAS,IAAI,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,KAAK,SAAS,EAAE,CAAC;QAC3E,MAAM,IAAI,KAAK,CAAC,kBAAkB,KAAK,0DAA0D,CAAC,CAAC;IACrG,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,mBAAmB,GAAG,CAAC,GAAyB,EAAE,IAAkB,EAAE,gBAAmC,EAAE,EAAE,CACjH,IAAI,KAAK,UAAU;IACjB,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QAChC,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;QACrD,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;IAClD,CAAC,CAAC;IACJ,CAAC,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AAE/B,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,KAAgB,EAAc,EAAE;IACjE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,iBAAiB,EAAE,GAAG,mBAAmB,CAAC,KAAK,CAAC,CAAC;IAElG,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClE,MAAM,IAAI,KAAK,CAAC,gDAAgD,MAAM,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,QAAQ,GAAa,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,YAAY,OAAO,EAAE,CAAC,CAAC;IAE5E,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,IAAI,iBAAiB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1D,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,QAAQ,EAAE,UAAU,6BAA6B,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IAC5G,CAAC;IAED,gBAAgB,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,OAAO,GAAwB,EAAE,CAAC;IAExC,KAAK,MAAM,IAAI,IAAI,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,MAAM,eAAe,GAAG,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACpF,MAAM,eAAe,GAAG,mBAAmB,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,CAAC,SAAU,CAAC,CAAC;QAC5E,MAAM,cAAc,GAAG,eAAe,CAAC,CAAC,CAAC,kBAAkB,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACzF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;QACxF,KAAK,MAAM,QAAQ,IAAI,cAAc;YAAE,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC9E,OAAO,CAAC,IAAI,CACV,GAAG,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC;aACvB,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;aAC1D,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YAClB,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,IAAI,EAAE,QAAQ,CAAC,IAAI;YACnB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK;YAClC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI;YAC1B,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,IAAI,IAAI;SAC/C,CAAC,CAAC,CACN,CAAC;QAEF,QAAQ,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,IAAI,CAAE,GAAG,eAAe,CAAC,CAAC,CAAC,WAAW,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAChG,QAAQ,CAAC,IAAI,CACX,GAAG,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC;YACtB,CAAC,CAAC,CAAC,UAAU,CAAC;YACd,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC;iBACpB,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;iBAC1D,GAAG,CACF,CAAC,QAAQ,EAAE,EAAE,CACX,KAAK,QAAQ,CAAC,IAAI,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,GACjD,QAAQ,CAAC,MAAM,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,eAClD,GAAG,CACN,CAAC,CACT,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;AACvD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,eAAwC,EAAE,EAAiB,EAAE,CAAC,CAAC;IAC/F,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,6EAA6E;IAC1F,QAAQ,CAAC,OAAgB;QACvB,OAAO,CAAC,UAAU,CAChB,IAAI,OAAO,CAAC,MAAM,CAAC;aAChB,WAAW,CAAC,6EAA6E,CAAC;aAC1F,QAAQ,CAAC,QAAQ,EAAE,0EAA0E,CAAC;aAC9F,MAAM,CAAC,UAAU,EAAE,6CAA6C,CAAC;aACjE,MAAM,CAAC,QAAQ,EAAE,6DAA6D,CAAC;aAC/E,MAAM,CACL,cAAc,EACd,6FAA6F,CAC9F;aACA,MAAM,CAAC,CAAC,IAAwB,EAAE,OAA6D,EAAE,EAAE;YAClG,MAAM,MAAM,GAAG,kBAAkB,CAAC;gBAChC,+FAA+F;gBAC/F,aAAa,EAAE,oBAAoB,CAAC,YAAY,CAAC,aAAa,CAAC;gBAC/D,gBAAgB,EAAE,uBAAuB,CAAC,YAAY,CAAC,gBAAgB,CAAC;gBACxE,IAAI;gBACJ,KAAK,EAAE,OAAO,CAAC,KAAK;gBACpB,MAAM,EAAE,OAAO,CAAC,MAAM;aACvB,CAAC,CAAC;YAEH,uFAAuF;YACvF,MAAM,KAAK,GAAG,YAAY,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC;YACpD,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI;gBACvB,KAAK,CACH,IAAI,CAAC,SAAS,CACZ,EAAE,EAAE,EAAE,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,CAAC,QAAQ,EAAE,EACxF,IAAI,EACJ,CAAC,CACF,CACF,CAAC;;gBACC,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ;oBAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YAE3D,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC;gBAAE,OAAO,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAChE,CAAC,CAAC,CACL,CAAC;IACJ,CAAC;CACF,CAAC,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
- return readFileSync(extensionPath, 'utf8').replace(/["']__OUTFITTER_ACTIVE_PROFILE__["']/gu, JSON.stringify(input.profile) ?? 'undefined');
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;AAYhF,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,IAAuB,EAAW,EAAE,CAC3E,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;IACvB,IAAI,2BAA2B,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,IAAI,GAAG,KAAK,QAAQ;QAAE,OAAO,qBAAqB,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9E,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,qBAAqB,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7F,OAAO,KAAK,CAAC;AACf,CAAC,CAAC,CAAC;AAEL,0EAA0E;AAC1E,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,KAAqD,EAAU,EAAE;IAC/G,MAAM,aAAa,GAAG,uBAAuB,CAAC,qBAAqB,CAAC,CAAC;IACrE,4GAA4G;IAC5G,IAAI,aAAa,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAE/F,OAAO,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,OAAO,CAChD,wCAAwC,EACxC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,WAAW,CAC7C,CAAC;AACJ,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,IAAqB,EAAE,KAA8B,EAAmB,EAAE;IACjH,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,IAAI,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAE9E,MAAM,kBAAkB,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;IACnE,MAAM,aAAa,GAAG,IAAI,CAAC,kBAAkB,EAAE,gCAAgC,CAAC,CAAC;IACjF,SAAS,CAAC,kBAAkB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,aAAa,CAAC,aAAa,EAAE,+BAA+B,CAAC,KAAK,CAAC,CAAC,CAAC;IAErE,OAAO,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC,aAAa,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AACzE,CAAC,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. */