@ai-outfitter/outfitter 0.3.0

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 (156) hide show
  1. package/LICENSE.md +58 -0
  2. package/README.md +256 -0
  3. package/dist/agents/AdapterProfileControls.d.ts +20 -0
  4. package/dist/agents/AdapterProfileControls.js +63 -0
  5. package/dist/agents/AdapterProfileControls.js.map +1 -0
  6. package/dist/agents/AdapterStatePaths.d.ts +12 -0
  7. package/dist/agents/AdapterStatePaths.js +44 -0
  8. package/dist/agents/AdapterStatePaths.js.map +1 -0
  9. package/dist/agents/AgentAdapter.d.ts +32 -0
  10. package/dist/agents/AgentAdapter.js +2 -0
  11. package/dist/agents/AgentAdapter.js.map +1 -0
  12. package/dist/agents/AgentRegistry.d.ts +6 -0
  13. package/dist/agents/AgentRegistry.js +17 -0
  14. package/dist/agents/AgentRegistry.js.map +1 -0
  15. package/dist/agents/LaunchResources.d.ts +13 -0
  16. package/dist/agents/LaunchResources.js +35 -0
  17. package/dist/agents/LaunchResources.js.map +1 -0
  18. package/dist/agents/ResourceIdentity.d.ts +2 -0
  19. package/dist/agents/ResourceIdentity.js +51 -0
  20. package/dist/agents/ResourceIdentity.js.map +1 -0
  21. package/dist/agents/claude/ClaudeAdapter.d.ts +2 -0
  22. package/dist/agents/claude/ClaudeAdapter.js +117 -0
  23. package/dist/agents/claude/ClaudeAdapter.js.map +1 -0
  24. package/dist/agents/claude/ClaudeCompositeProfileWriter.d.ts +5 -0
  25. package/dist/agents/claude/ClaudeCompositeProfileWriter.js +7 -0
  26. package/dist/agents/claude/ClaudeCompositeProfileWriter.js.map +1 -0
  27. package/dist/agents/pi/PiAdapter.d.ts +2 -0
  28. package/dist/agents/pi/PiAdapter.js +229 -0
  29. package/dist/agents/pi/PiAdapter.js.map +1 -0
  30. package/dist/agents/pi/PiCompositeProfileWriter.d.ts +5 -0
  31. package/dist/agents/pi/PiCompositeProfileWriter.js +7 -0
  32. package/dist/agents/pi/PiCompositeProfileWriter.js.map +1 -0
  33. package/dist/agents/pi/PiMcpConfig.d.ts +2 -0
  34. package/dist/agents/pi/PiMcpConfig.js +114 -0
  35. package/dist/agents/pi/PiMcpConfig.js.map +1 -0
  36. package/dist/agents/pi/PiSettingsMergePolicy.d.ts +17 -0
  37. package/dist/agents/pi/PiSettingsMergePolicy.js +59 -0
  38. package/dist/agents/pi/PiSettingsMergePolicy.js.map +1 -0
  39. package/dist/cli/OutfitterCli.d.ts +4 -0
  40. package/dist/cli/OutfitterCli.js +32 -0
  41. package/dist/cli/OutfitterCli.js.map +1 -0
  42. package/dist/cli/commands/CommandObject.d.ts +11 -0
  43. package/dist/cli/commands/CommandObject.js +5 -0
  44. package/dist/cli/commands/CommandObject.js.map +1 -0
  45. package/dist/cli/commands/FirstRunWelcomeProfile.d.ts +11 -0
  46. package/dist/cli/commands/FirstRunWelcomeProfile.js +107 -0
  47. package/dist/cli/commands/FirstRunWelcomeProfile.js.map +1 -0
  48. package/dist/cli/commands/PiLoginLaunch.d.ts +10 -0
  49. package/dist/cli/commands/PiLoginLaunch.js +76 -0
  50. package/dist/cli/commands/PiLoginLaunch.js.map +1 -0
  51. package/dist/cli/commands/RunCommand.d.ts +30 -0
  52. package/dist/cli/commands/RunCommand.js +299 -0
  53. package/dist/cli/commands/RunCommand.js.map +1 -0
  54. package/dist/cli/commands/SetupCommand.d.ts +34 -0
  55. package/dist/cli/commands/SetupCommand.js +379 -0
  56. package/dist/cli/commands/SetupCommand.js.map +1 -0
  57. package/dist/cli/commands/SyncCommand.d.ts +28 -0
  58. package/dist/cli/commands/SyncCommand.js +178 -0
  59. package/dist/cli/commands/SyncCommand.js.map +1 -0
  60. package/dist/cli/commands/WelcomeCommand.d.ts +54 -0
  61. package/dist/cli/commands/WelcomeCommand.js +214 -0
  62. package/dist/cli/commands/WelcomeCommand.js.map +1 -0
  63. package/dist/cli/commands/profile/Command.d.ts +6 -0
  64. package/dist/cli/commands/profile/Command.js +21 -0
  65. package/dist/cli/commands/profile/Command.js.map +1 -0
  66. package/dist/cli/commands/profile/CreateCommand.d.ts +19 -0
  67. package/dist/cli/commands/profile/CreateCommand.js +115 -0
  68. package/dist/cli/commands/profile/CreateCommand.js.map +1 -0
  69. package/dist/cli/commands/profile/ListCommand.d.ts +17 -0
  70. package/dist/cli/commands/profile/ListCommand.js +85 -0
  71. package/dist/cli/commands/profile/ListCommand.js.map +1 -0
  72. package/dist/cli/commands/profile/Shared.d.ts +9 -0
  73. package/dist/cli/commands/profile/Shared.js +10 -0
  74. package/dist/cli/commands/profile/Shared.js.map +1 -0
  75. package/dist/cli.d.ts +3 -0
  76. package/dist/cli.js +22 -0
  77. package/dist/cli.js.map +1 -0
  78. package/dist/compositeProfile/CompositeProfile.d.ts +8 -0
  79. package/dist/compositeProfile/CompositeProfile.js +6 -0
  80. package/dist/compositeProfile/CompositeProfile.js.map +1 -0
  81. package/dist/compositeProfile/CompositeProfileAssembler.d.ts +12 -0
  82. package/dist/compositeProfile/CompositeProfileAssembler.js +32 -0
  83. package/dist/compositeProfile/CompositeProfileAssembler.js.map +1 -0
  84. package/dist/compositeProfile/CompositeProfileFile.d.ts +16 -0
  85. package/dist/compositeProfile/CompositeProfileFile.js +16 -0
  86. package/dist/compositeProfile/CompositeProfileFile.js.map +1 -0
  87. package/dist/compositeProfile/CompositeProfileTemplate.d.ts +15 -0
  88. package/dist/compositeProfile/CompositeProfileTemplate.js +65 -0
  89. package/dist/compositeProfile/CompositeProfileTemplate.js.map +1 -0
  90. package/dist/compositeProfile/CompositeProfileWatcher.d.ts +18 -0
  91. package/dist/compositeProfile/CompositeProfileWatcher.js +46 -0
  92. package/dist/compositeProfile/CompositeProfileWatcher.js.map +1 -0
  93. package/dist/compositeProfile/StatePersistence.d.ts +24 -0
  94. package/dist/compositeProfile/StatePersistence.js +224 -0
  95. package/dist/compositeProfile/StatePersistence.js.map +1 -0
  96. package/dist/merge/ArrayMergePolicy.d.ts +8 -0
  97. package/dist/merge/ArrayMergePolicy.js +45 -0
  98. package/dist/merge/ArrayMergePolicy.js.map +1 -0
  99. package/dist/merge/SettingsValueMerger.d.ts +11 -0
  100. package/dist/merge/SettingsValueMerger.js +34 -0
  101. package/dist/merge/SettingsValueMerger.js.map +1 -0
  102. package/dist/profiles/Profile.d.ts +32 -0
  103. package/dist/profiles/Profile.js +7 -0
  104. package/dist/profiles/Profile.js.map +1 -0
  105. package/dist/profiles/ProfileCache.d.ts +8 -0
  106. package/dist/profiles/ProfileCache.js +36 -0
  107. package/dist/profiles/ProfileCache.js.map +1 -0
  108. package/dist/profiles/ProfileLoader.d.ts +24 -0
  109. package/dist/profiles/ProfileLoader.js +169 -0
  110. package/dist/profiles/ProfileLoader.js.map +1 -0
  111. package/dist/profiles/ProfileMerger.d.ts +20 -0
  112. package/dist/profiles/ProfileMerger.js +97 -0
  113. package/dist/profiles/ProfileMerger.js.map +1 -0
  114. package/dist/profiles/ProfileSource.d.ts +35 -0
  115. package/dist/profiles/ProfileSource.js +13 -0
  116. package/dist/profiles/ProfileSource.js.map +1 -0
  117. package/dist/schemas/profile-source.schema.json +29 -0
  118. package/dist/schemas/profile.schema.json +115 -0
  119. package/dist/schemas/settings.schema.json +37 -0
  120. package/dist/settings/Settings.d.ts +17 -0
  121. package/dist/settings/Settings.js +5 -0
  122. package/dist/settings/Settings.js.map +1 -0
  123. package/dist/settings/SettingsLoader.d.ts +33 -0
  124. package/dist/settings/SettingsLoader.js +129 -0
  125. package/dist/settings/SettingsLoader.js.map +1 -0
  126. package/dist/settings/SettingsMerger.d.ts +2 -0
  127. package/dist/settings/SettingsMerger.js +31 -0
  128. package/dist/settings/SettingsMerger.js.map +1 -0
  129. package/dist/validation/SchemaValidator.d.ts +11 -0
  130. package/dist/validation/SchemaValidator.js +36 -0
  131. package/dist/validation/SchemaValidator.js.map +1 -0
  132. package/dist/validation/YamlDocument.d.ts +13 -0
  133. package/dist/validation/YamlDocument.js +11 -0
  134. package/dist/validation/YamlDocument.js.map +1 -0
  135. package/doc/.deepreview +30 -0
  136. package/doc/architecture.md +834 -0
  137. package/doc/controllable-elements.md +162 -0
  138. package/doc/file_structure.md +133 -0
  139. package/doc/integration_test_system.md +214 -0
  140. package/doc/specs/validating_requirements_with_rules.md +55 -0
  141. package/doc/state_writeback_strategy.md +334 -0
  142. package/package.json +73 -0
  143. package/requirements/OFTR-001-project-foundation.md +53 -0
  144. package/requirements/OFTR-002-settings.md +65 -0
  145. package/requirements/OFTR-003-profiles.md +51 -0
  146. package/requirements/OFTR-004-sync-and-setup.md +57 -0
  147. package/requirements/OFTR-005-run-and-composite-profile.md +60 -0
  148. package/requirements/OFTR-006-agent-adapters.md +64 -0
  149. package/requirements/OFTR-007-controllable-elements.md +32 -0
  150. package/requirements/OFTR-008-requirements-governance.md +42 -0
  151. package/requirements/OFTR-009-release-publishing.md +25 -0
  152. package/requirements/OFTR-010-onboarding-welcome.md +38 -0
  153. package/src/schemas/SchemaDocument.ts +20 -0
  154. package/src/schemas/profile-source.schema.json +29 -0
  155. package/src/schemas/profile.schema.json +115 -0
  156. package/src/schemas/settings.schema.json +37 -0
package/LICENSE.md ADDED
@@ -0,0 +1,58 @@
1
+ # Business Source License 1.1
2
+
3
+ ## Parameters
4
+
5
+ **Licensor:** Unsupervised.com, Inc.
6
+
7
+ **Licensed Work:** Outfitter
8
+
9
+ The Licensed Work is © 2026 Unsupervised.com, Inc.
10
+
11
+ **Additional Use Grant:** You may use the Licensed Work without a commercial license only when it is used with or for:
12
+
13
+ - repositories that are publicly available under an open source license approved by the Open Source Initiative; or
14
+ - repositories with fewer than 10 contributors.
15
+
16
+ Use of the Licensed Work with or for a private repository with 10 or more contributors requires a commercial license from the Licensor.
17
+
18
+ **Change Date:** 2030-01-14
19
+
20
+ **Change License:** Apache License, Version 2.0
21
+
22
+ ---
23
+
24
+ ## License Terms
25
+
26
+ The Licensor hereby grants you the right to copy, modify, create derivative works, redistribute, and make non-production use of the Licensed Work.
27
+ The Licensor may make an Additional Use Grant, above, permitting limited production use.
28
+
29
+ Effective on the Change Date, or the fourth anniversary of the first publicly available distribution of a specific version of the Licensed Work under this License, whichever comes first, the Licensor hereby grants you rights under the terms of the Change License, and the rights granted in the paragraph above terminate.
30
+
31
+ If your use of the Licensed Work does not comply with the requirements currently in effect as described in this License, you must purchase a commercial license from the Licensor, its affiliated entities, or authorized resellers, or you must refrain from using the Licensed Work.
32
+
33
+ All copies of the original and modified Licensed Work, and derivative works of the Licensed Work, are subject to this License.
34
+ This License applies separately for each version of the Licensed Work and the Change Date may vary for each version of the Licensed Work released by Licensor.
35
+
36
+ You must conspicuously display this License on each original or modified copy of the Licensed Work.
37
+ If you receive the Licensed Work in original or modified form from a third party, the terms and conditions set forth in this License apply to your use of that work.
38
+
39
+ Any use of the Licensed Work in violation of this License will automatically terminate your rights under this License for the current and all other versions of the Licensed Work.
40
+
41
+ This License does not grant you any right in any trademark or logo of Licensor or its affiliates (provided that you may use a trademark or logo of Licensor as expressly required by this License).
42
+
43
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN "AS IS" BASIS.
44
+ LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE.
45
+
46
+ ---
47
+
48
+ ## Prohibited Uses
49
+
50
+ You may not use the Licensed Work with or for a private repository with 10 or more contributors unless you have purchased a commercial license from the Licensor, its affiliated entities, or authorized resellers.
51
+
52
+ For purposes of this License, a repository's contributor count is the number of people who have made commits or other accepted contributions to that repository, or who otherwise have permission to contribute code to that repository, whichever is greater.
53
+
54
+ **Clarification**: This prohibition does not prevent you from using the Licensed Work without a commercial license with or for:
55
+
56
+ - repositories that are publicly available under an open source license approved by the Open Source Initiative, regardless of contributor count;
57
+ - private repositories with fewer than 10 contributors;
58
+ - educational or research contexts that satisfy one of the preceding repository-use allowances.
package/README.md ADDED
@@ -0,0 +1,256 @@
1
+ # outfitter
2
+
3
+ `outfitter` is intended to be a management wrapper for launching [`pi`](https://github.com/earendil-works/pi-coding-agent) and Claude Code with configurable, reusable profiles.
4
+
5
+ The goal is manageable agent CLI configuration:
6
+
7
+ - Organizations can define standard pi or claude loadouts, share them, and launch agent CLIs consistently across different environments.
8
+ - Individuals can swap between configurations of their coding agent, share those, and easily migrate to new machines.
9
+
10
+ If you haven't tried [Pi](https://pi.dev) yet — we think it's a great coding harness & Outfitter is an easy way to try it.
11
+
12
+ - Install and run `outfitter` to load pi with our standard configuration for engineers.
13
+
14
+ ## Install
15
+
16
+ Install Outfitter globally from npm so the `outfitter` command is available on your PATH:
17
+
18
+ ```bash
19
+ npm install -g @ai-outfitter/outfitter
20
+ outfitter --help
21
+ ```
22
+
23
+ Upgrade with:
24
+
25
+ ```bash
26
+ npm update -g @ai-outfitter/outfitter
27
+ ```
28
+
29
+ Use `npx` when you want to test Outfitter without adding a global command:
30
+
31
+ ```bash
32
+ npx --yes @ai-outfitter/outfitter@latest --help
33
+ ```
34
+
35
+ For source development, see [`CONTRIBUTING.md`](./CONTRIBUTING.md).
36
+
37
+ Outfitter launches agent CLIs but does not install them.
38
+ Install the agents you plan to use separately:
39
+
40
+ - [pi](https://github.com/earendil-works/pi-coding-agent) — follow its installation instructions; the `pi` command must be on your PATH.
41
+ - [Claude Code](https://docs.anthropic.com/en/docs/claude-code) — only needed if you launch with `--agent claude`.
42
+
43
+ ## Why this exists
44
+
45
+ Pi is highly configurable through settings directories, extensions, skills, prompts, themes, model settings, environment variables, and CLI flags.
46
+ That flexibility is powerful, but businesses often need a higher-level control plane for repeatable deployments.
47
+
48
+ `outfitter` should make it easy to answer questions like:
49
+
50
+ - Which pi configuration should this employee or team use?
51
+ - Which extensions, skills, prompts, models, and providers are approved?
52
+ - Which credentials or environment variables should be present?
53
+ - Should project-local `.pi` configuration be allowed, merged, or ignored?
54
+ - How do we ship multiple standardized pi loadouts without manual setup?
55
+
56
+ ## Intended use case
57
+
58
+ Example profile concepts:
59
+
60
+ - `engineering-default` — standard engineering loadout with approved tools, prompts, and models.
61
+ - `support` — customer-support-focused prompt and restricted tool set.
62
+ - `sandbox` — isolated experimentation profile with disposable config and sessions.
63
+ - `regulated` — locked-down profile with stricter extensions, context, and session behavior.
64
+
65
+ A launch flow is intended to look like:
66
+
67
+ ```bash
68
+ outfitter
69
+ outfitter run --profile engineering-default
70
+ outfitter run -p support -- --cwd ~/work/customer-issue
71
+ outfitter sync
72
+ outfitter setup
73
+ outfitter setup https://github.com/my_account/outfitter_config
74
+ outfitter welcome
75
+ outfitter profile list
76
+ outfitter profile create regulated --scope user
77
+ ```
78
+
79
+ Under the hood, `outfitter` translates a selected profile into the selected agent launch environment.
80
+ Pi runs use `PI_CODING_AGENT_DIR`; Claude Code runs use `CLAUDE_CONFIG_DIR`; both receive supported CLI flags, prompts, model settings, and environment variables.
81
+ Select the adapter with `outfitter run --agent <pi|claude>`, or set `default_agent` in `settings.yml`.
82
+ If neither is set, Outfitter defaults to pi for backward compatibility.
83
+ If `outfitter` is run before `outfitter setup`, it creates the initial settings and default profile automatically before launching.
84
+ When that first-run setup has an interactive terminal, Outfitter continues into the same welcome onboarding used by `outfitter welcome`.
85
+
86
+ `outfitter welcome` explains Outfitter and Pi, asks you to choose an initial built-in role, and recommends a Pi productivity loadout.
87
+ The current built-in role choices are `engineer` and `data_analyst`; Outfitter creates the selected local profile on the fly.
88
+ The recommended loadout includes `ulta-tasklist`, `deepwork`, `pi-subagents`, and `pi-mcp-adapter`; you can accept it, choose individual items, or skip loadout installation.
89
+ If you skip loadout installation, the generated profile has no extensions or skills.
90
+ If Pi does not appear to be logged in after welcome onboarding, Outfitter opens Pi with `/login` automatically; outside welcome onboarding it prints a `/login` reminder instead.
91
+ Outfitter never collects or persists provider API keys itself.
92
+
93
+ `settings.yml` can point at local profiles, full Git URIs, or GitHub shorthand sources with optional refs and repository subpaths:
94
+
95
+ ```yaml
96
+ remote_settings:
97
+ - github: my_account/outfitter_config
98
+ ref: main
99
+ path: settings.yml
100
+
101
+ profile_sources:
102
+ - github: my_account/outfitter_config
103
+ ref: main
104
+ path: profiles
105
+ ```
106
+
107
+ Run `outfitter sync` to fetch/update remote settings and profiles before using them.
108
+
109
+ By default, Outfitter keeps reusable runtime cache files under `~/.outfitter/cache`.
110
+ Set `cache_directory` in `settings.yml` to choose a different cache root; relative values resolve from the settings file that declares them.
111
+ The pi adapter symlinks composite profile `utilities/` and `bin/` paths into this cache so pi-managed utilities such as `fd` and `rg` survive across temporary composite profile directories.
112
+
113
+ Settings can also define arbitrary nested `custom_settings` values for Outfitter-time composite profile templating:
114
+
115
+ ```yaml
116
+ custom_settings:
117
+ build_commands:
118
+ lint: npm run lint
119
+ ```
120
+
121
+ Generated composite profile files can reference them with Outfitter's LiquidJS-based custom delimiters:
122
+
123
+ ```yaml
124
+ command: '[[= outfitter.custom_settings.build_commands.lint ]]'
125
+ ```
126
+
127
+ Control tags use `[[% ... %]]`, for example `[[% for item in outfitter.custom_settings.items %]]`.
128
+ Outfitter intentionally does not use common `{{ ... }}` delimiters, and plain shell expressions like `[[ -f package.json ]]` are left alone.
129
+
130
+ ## Setup from a settings repository
131
+
132
+ You can bootstrap a machine from a Git repository:
133
+
134
+ ```bash
135
+ outfitter setup https://github.com/my_account/outfitter_config
136
+ ```
137
+
138
+ `outfitter setup` requires an interactive terminal on both stdin and stdout.
139
+ When a repository is provided, it clones or updates the repository in Outfitter's shared repository cache, then uses it as a non-overwriting starting point:
140
+
141
+ - if `~/.outfitter/settings.yml` does not exist, Outfitter copies the starter `settings.yml`;
142
+ - if starter profiles exist, Outfitter copies missing profile files into `~/.outfitter/profiles/`;
143
+ - existing user settings and profile files are otherwise left unchanged;
144
+ - after setup, Outfitter runs the same sync behavior used by `outfitter sync`;
145
+ - on initial interactive first-run setup, Outfitter skips the older default-profile prompt and lets welcome onboarding choose the generated local default profile;
146
+ - outside that initial welcome handoff, Outfitter shows a short setup wizard that lists synced profiles and writes the selected default profile to user settings;
147
+ - interactive setup continues into welcome onboarding to record role and loadout choices.
148
+
149
+ A setup repository can use either root-level Outfitter files:
150
+
151
+ ```text
152
+ outfitter_config/
153
+ settings.yml
154
+ profiles/
155
+ engineering-default/
156
+ profile.yml
157
+ support/
158
+ profile.yml
159
+ ```
160
+
161
+ or a `.outfitter/` layout:
162
+
163
+ ```text
164
+ outfitter_config/
165
+ .outfitter/
166
+ settings.yml
167
+ profiles/
168
+ engineering-default/
169
+ profile.yml
170
+ support/
171
+ profile.yml
172
+ ```
173
+
174
+ Example `settings.yml` for a setup repository:
175
+
176
+ ```yaml
177
+ default_profile: engineering-default
178
+
179
+ profile_sources:
180
+ - path: ./profiles
181
+
182
+ # Optional: keep loading future updates from this same repo.
183
+ - github: my_account/outfitter_config
184
+ ref: main
185
+ path: profiles
186
+ ```
187
+
188
+ If you want ongoing centralized settings, use a small local `~/.outfitter/settings.yml` that points at remote settings:
189
+
190
+ ```yaml
191
+ remote_settings:
192
+ - github: my_account/outfitter_config
193
+ ref: main
194
+ path: settings.yml
195
+ ```
196
+
197
+ Then run:
198
+
199
+ ```bash
200
+ outfitter sync
201
+ ```
202
+
203
+ ## Profile model sketch
204
+
205
+ A profile will use YAML.
206
+ An initial profile shape is:
207
+
208
+ ```yaml
209
+ id: engineering-default
210
+ label: Engineering Default
211
+ inherits:
212
+ - base-typescript
213
+
214
+ controls:
215
+ model: anthropic/claude-sonnet-4
216
+ environment:
217
+ TEAM_MODE: engineering
218
+ ```
219
+
220
+ The exact stable schema is governed by the requirements in `requirements/` and the JSON Schema files in `src/schemas/`, which are still expected to evolve with implementation.
221
+
222
+ Pi profiles can also ship DeepWork jobs for that profile under `cli_specific/pi/deepwork/jobs/`.
223
+ When Outfitter launches Pi, it adds contributing profile job folders to `DEEPWORK_ADDITIONAL_JOBS_FOLDERS` so the DeepWork frontend can discover profile-owned workflows without copying them into a project `.deepwork/jobs/` directory.
224
+ By default, profiles with bundled jobs receive only their profile-bundled jobs; set `controls.pi.allow_external_deepwork_jobs: true` to also include inherited `DEEPWORK_ADDITIONAL_JOBS_FOLDERS` entries.
225
+
226
+ ## Design direction
227
+
228
+ The current recommendation is to build `outfitter` around pi's existing native configuration mechanisms:
229
+
230
+ 1. Use a temporary composite profile directory as `PI_CODING_AGENT_DIR` for each run.
231
+ 2. Persist intentional pi state through adapter-declared symlinks to profile, native pi, or Outfitter cache files.
232
+ Native pi fallback is only a durable target for declared state symlinks; it is not an inherited base profile layer.
233
+ 3. Layer profile-controlled environment variables and pi CLI flags on top.
234
+ 4. Use explicit `--extension` / `-e` injection for bootstrap behavior that needs to run inside pi.
235
+ 5. Decide per profile whether project-local `.pi` overrides are allowed.
236
+ 6. Keep the wrapper responsible for anything that must happen before pi starts, such as selecting config directories, setting credentials, or choosing session locations.
237
+
238
+ See [`recommendation.md`](./recommendation.md) for current notes on pi startup behavior and wrapper strategy.
239
+
240
+ ## Status
241
+
242
+ This repository is under phased implementation.
243
+
244
+ A minimal executable CLI exists, with initial settings/profile schemas, local and URI-backed profile loading, profile resolution internals, first-pass `setup`, `sync`, `profile list`, and `profile create` commands, and a first-pass `run` command for assembling a temporary composite profile and launching pi or Claude Code.
245
+ Stable end-to-end pi launch behavior and user-facing examples will be hardened in a later phase.
246
+ The initial dependency and architecture decisions are documented in `package.json`, `doc/architecture.md`, and `requirements/`.
247
+
248
+ ## Future work
249
+
250
+ - Define a stable profile schema.
251
+ - Decide where organization-managed profiles are discovered from.
252
+ - Harden stable `outfitter run --profile <profile>` behavior.
253
+ - Add validation and inspection commands.
254
+ - Expand user-facing documentation for resolved profile inheritance and composition.
255
+ - Add locking / policy controls for business-managed environments.
256
+ - Add examples for common organizational deployments.
@@ -0,0 +1,20 @@
1
+ import type { ProfileControls } from '../profiles/Profile.js';
2
+ export declare const genericControlNames: Set<string>;
3
+ export declare const supportedControlNames: (controls: ReadonlySet<string>) => readonly string[];
4
+ export declare const controlAliases: readonly [{
5
+ readonly camelCase: "sessionDirectory";
6
+ readonly snakeCase: "session_directory";
7
+ }, {
8
+ readonly camelCase: "promptTemplate";
9
+ readonly snakeCase: "prompt_template";
10
+ }, {
11
+ readonly camelCase: "systemPrompt";
12
+ readonly snakeCase: "system_prompt";
13
+ }, {
14
+ readonly camelCase: "appendSystemPrompt";
15
+ readonly snakeCase: "append_system_prompt";
16
+ }];
17
+ export declare const mergeAgentSpecificControls: <T extends ProfileControls>(controls: ProfileControls, agentKey: "pi" | "claude") => T;
18
+ export declare const flagValue: (flag: string, value: string | undefined) => readonly string[];
19
+ export declare const repeatFlag: (flag: string, values: readonly string[] | undefined) => readonly string[];
20
+ export declare const findUnsupportedControlNames: (controls: Readonly<Record<string, unknown>>, supportedControls: ReadonlySet<string>, knownControls?: ReadonlySet<string>) => string[];
@@ -0,0 +1,63 @@
1
+ import { mergeLaunchResourceSources } from './LaunchResources.js';
2
+ export const genericControlNames = new Set([
3
+ 'model',
4
+ 'provider',
5
+ 'thinking',
6
+ 'environment',
7
+ 'args',
8
+ 'sessionDirectory',
9
+ 'session_directory',
10
+ 'extensions',
11
+ 'skills',
12
+ 'promptTemplate',
13
+ 'prompt_template',
14
+ 'systemPrompt',
15
+ 'system_prompt',
16
+ 'appendSystemPrompt',
17
+ 'append_system_prompt',
18
+ 'pi',
19
+ 'claude',
20
+ ]);
21
+ export const supportedControlNames = (controls) => [...controls].filter((controlName) => !controlName.includes('_') && controlName !== 'pi' && controlName !== 'claude');
22
+ export const controlAliases = [
23
+ { camelCase: 'sessionDirectory', snakeCase: 'session_directory' },
24
+ { camelCase: 'promptTemplate', snakeCase: 'prompt_template' },
25
+ { camelCase: 'systemPrompt', snakeCase: 'system_prompt' },
26
+ { camelCase: 'appendSystemPrompt', snakeCase: 'append_system_prompt' },
27
+ ];
28
+ export const mergeAgentSpecificControls = (controls, agentKey) => {
29
+ const agentControls = controls[agentKey];
30
+ return {
31
+ ...controls,
32
+ ...definedControls(agentControls),
33
+ environment: { ...controls.environment, ...agentControls?.environment },
34
+ args: agentControls?.args ?? controls.args,
35
+ extensions: mergeLaunchResourceSources('extension', controls.extensions, agentControls?.extensions),
36
+ skills: mergeLaunchResourceSources('skill', controls.skills, agentControls?.skills),
37
+ };
38
+ };
39
+ export const flagValue = (flag, value) => value === undefined ? [] : [flag, value];
40
+ export const repeatFlag = (flag, values) => values === undefined ? [] : values.flatMap((value) => [flag, value]);
41
+ export const findUnsupportedControlNames = (controls, supportedControls, knownControls = genericControlNames) => {
42
+ const controlNames = new Set(Object.keys(controls));
43
+ const unsupported = [];
44
+ for (const { camelCase, snakeCase } of controlAliases) {
45
+ if (!controlNames.has(camelCase) && !controlNames.has(snakeCase)) {
46
+ continue;
47
+ }
48
+ if (!supportedControls.has(camelCase) && !supportedControls.has(snakeCase)) {
49
+ unsupported.push(controlNames.has(snakeCase) ? snakeCase : camelCase);
50
+ }
51
+ controlNames.delete(camelCase);
52
+ controlNames.delete(snakeCase);
53
+ }
54
+ unsupported.push(...[...controlNames].filter((controlName) => !knownControls.has(controlName) || !supportedControls.has(controlName)));
55
+ return unsupported;
56
+ };
57
+ const definedControls = (controls) => {
58
+ if (controls === undefined) {
59
+ return {};
60
+ }
61
+ return Object.fromEntries(Object.entries(controls).filter((entry) => entry[1] !== undefined));
62
+ };
63
+ //# sourceMappingURL=AdapterProfileControls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AdapterProfileControls.js","sourceRoot":"","sources":["../../src/agents/AdapterProfileControls.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAElE,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC;IACzC,OAAO;IACP,UAAU;IACV,UAAU;IACV,aAAa;IACb,MAAM;IACN,kBAAkB;IAClB,mBAAmB;IACnB,YAAY;IACZ,QAAQ;IACR,gBAAgB;IAChB,iBAAiB;IACjB,cAAc;IACd,eAAe;IACf,oBAAoB;IACpB,sBAAsB;IACtB,IAAI;IACJ,QAAQ;CACT,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,QAA6B,EAAqB,EAAE,CACxF,CAAC,GAAG,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,WAAW,KAAK,IAAI,IAAI,WAAW,KAAK,QAAQ,CAAC,CAAC;AAExH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,EAAE,SAAS,EAAE,kBAAkB,EAAE,SAAS,EAAE,mBAAmB,EAAE;IACjE,EAAE,SAAS,EAAE,gBAAgB,EAAE,SAAS,EAAE,iBAAiB,EAAE;IAC7D,EAAE,SAAS,EAAE,cAAc,EAAE,SAAS,EAAE,eAAe,EAAE;IACzD,EAAE,SAAS,EAAE,oBAAoB,EAAE,SAAS,EAAE,sBAAsB,EAAE;CAC9D,CAAC;AAEX,MAAM,CAAC,MAAM,0BAA0B,GAAG,CACxC,QAAyB,EACzB,QAAyB,EACtB,EAAE;IACL,MAAM,aAAa,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAEzC,OAAO;QACL,GAAG,QAAQ;QACX,GAAG,eAAe,CAAC,aAAa,CAAC;QACjC,WAAW,EAAE,EAAE,GAAG,QAAQ,CAAC,WAAW,EAAE,GAAG,aAAa,EAAE,WAAW,EAAE;QACvE,IAAI,EAAE,aAAa,EAAE,IAAI,IAAI,QAAQ,CAAC,IAAI;QAC1C,UAAU,EAAE,0BAA0B,CAAC,WAAW,EAAE,QAAQ,CAAC,UAAU,EAAE,aAAa,EAAE,UAAU,CAAC;QACnG,MAAM,EAAE,0BAA0B,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC;KAC/E,CAAC;AACT,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,KAAyB,EAAqB,EAAE,CACtF,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAE3C,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,MAAqC,EAAqB,EAAE,CACnG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AAEvE,MAAM,CAAC,MAAM,2BAA2B,GAAG,CACzC,QAA2C,EAC3C,iBAAsC,EACtC,gBAAqC,mBAAmB,EAC9C,EAAE;IACZ,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpD,MAAM,WAAW,GAAa,EAAE,CAAC;IAEjC,KAAK,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,cAAc,EAAE,CAAC;QACtD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YACjE,SAAS;QACX,CAAC;QAED,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3E,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACxE,CAAC;QAED,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC/B,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACjC,CAAC;IAED,WAAW,CAAC,IAAI,CACd,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,MAAM,CACzB,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,CACxF,CACF,CAAC;IAEF,OAAO,WAAW,CAAC;AACrB,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,QAA2C,EAA4B,EAAE;IAChG,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC;AAChG,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { Profile } from '../profiles/Profile.js';
2
+ import type { StatePathDeclaration, StatePersistenceStrategy, CompositeProfileStatePath } from '../compositeProfile/StatePersistence.js';
3
+ export interface DeclaredStatePathInput {
4
+ readonly adapterId: string;
5
+ readonly declarations: Readonly<Record<string, StatePathDeclaration>>;
6
+ readonly profile: Profile;
7
+ readonly resolveSourcePath: (relativePath: string, directory: boolean) => string;
8
+ }
9
+ export declare const createDeclaredStatePaths: (input: DeclaredStatePathInput) => readonly CompositeProfileStatePath[];
10
+ export declare const assertDeclaredStatePersistenceKeys: (adapterId: string, declarations: Readonly<Record<string, StatePathDeclaration>>, profile: Profile) => void;
11
+ export declare const resolveStateStrategy: (profile: Profile, relativePath: string, declaration: StatePathDeclaration) => StatePersistenceStrategy;
12
+ export declare const findProfileStateSource: (profileFolders: readonly string[], adapterId: string, relativePath: string, directory: boolean) => string | undefined;
@@ -0,0 +1,44 @@
1
+ // Shared helpers for adapter-declared composite profile state paths.
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ export const createDeclaredStatePaths = (input) => {
5
+ assertDeclaredStatePersistenceKeys(input.adapterId, input.declarations, input.profile);
6
+ return Object.entries(input.declarations).map(([relativePath, declaration]) => {
7
+ const strategy = resolveStateStrategy(input.profile, relativePath, declaration);
8
+ const directory = relativePath.endsWith('/');
9
+ return {
10
+ relativePath,
11
+ strategy,
12
+ directory,
13
+ sourcePath: strategy === 'symlink' && relativePath !== 'unknown'
14
+ ? input.resolveSourcePath(relativePath, directory)
15
+ : undefined,
16
+ };
17
+ });
18
+ };
19
+ export const assertDeclaredStatePersistenceKeys = (adapterId, declarations, profile) => {
20
+ for (const relativePath of Object.keys(profile.statePersistence ?? {})) {
21
+ if (!Object.hasOwn(declarations, relativePath)) {
22
+ throw new Error(`state_persistence path '${relativePath}' is not declared by the ${adapterId} adapter`);
23
+ }
24
+ }
25
+ };
26
+ export const resolveStateStrategy = (profile, relativePath, declaration) => {
27
+ const strategy = profile.statePersistence?.[relativePath] ?? declaration.defaultStrategy;
28
+ /* v8 ignore next -- Adapter declarations define defaults; this guards future declaration regressions. */
29
+ if (strategy === undefined) {
30
+ throw new Error(`missing state_persistence strategy for "${relativePath}"`);
31
+ }
32
+ if (!declaration.allowedStrategies.includes(strategy)) {
33
+ throw new Error(`state_persistence strategy '${strategy}' is not allowed for "${relativePath}"`);
34
+ }
35
+ return strategy;
36
+ };
37
+ export const findProfileStateSource = (profileFolders, adapterId, relativePath, directory) => {
38
+ const normalizedRelativePath = directory ? relativePath.slice(0, -1) : relativePath;
39
+ return [...profileFolders]
40
+ .reverse()
41
+ .map((profileFolder) => join(profileFolder, 'cli_specific', adapterId, normalizedRelativePath))
42
+ .find((candidate) => existsSync(candidate));
43
+ };
44
+ //# sourceMappingURL=AdapterStatePaths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AdapterStatePaths.js","sourceRoot":"","sources":["../../src/agents/AdapterStatePaths.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAgBjC,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,KAA6B,EAAwC,EAAE;IAC9G,kCAAkC,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IAEvF,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,EAAE,WAAW,CAAC,EAAE,EAAE;QAC5E,MAAM,QAAQ,GAAG,oBAAoB,CAAC,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;QAChF,MAAM,SAAS,GAAG,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAE7C,OAAO;YACL,YAAY;YACZ,QAAQ;YACR,SAAS;YACT,UAAU,EACR,QAAQ,KAAK,SAAS,IAAI,YAAY,KAAK,SAAS;gBAClD,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,YAAY,EAAE,SAAS,CAAC;gBAClD,CAAC,CAAC,SAAS;SAChB,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kCAAkC,GAAG,CAChD,SAAiB,EACjB,YAA4D,EAC5D,OAAgB,EACV,EAAE;IACR,KAAK,MAAM,YAAY,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,IAAI,EAAE,CAAC,EAAE,CAAC;QACvE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,YAAY,CAAC,EAAE,CAAC;YAC/C,MAAM,IAAI,KAAK,CAAC,2BAA2B,YAAY,4BAA4B,SAAS,UAAU,CAAC,CAAC;QAC1G,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,OAAgB,EAChB,YAAoB,EACpB,WAAiC,EACP,EAAE;IAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,YAAY,CAAC,IAAI,WAAW,CAAC,eAAe,CAAC;IAEzF,yGAAyG;IACzG,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,2CAA2C,YAAY,GAAG,CAAC,CAAC;IAC9E,CAAC;IAED,IAAI,CAAC,WAAW,CAAC,iBAAiB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,+BAA+B,QAAQ,yBAAyB,YAAY,GAAG,CAAC,CAAC;IACnG,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACpC,cAAiC,EACjC,SAAiB,EACjB,YAAoB,EACpB,SAAkB,EACE,EAAE;IACtB,MAAM,sBAAsB,GAAG,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;IAEpF,OAAO,CAAC,GAAG,cAAc,CAAC;SACvB,OAAO,EAAE;SACT,GAAG,CAAC,CAAC,aAAa,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,cAAc,EAAE,SAAS,EAAE,sBAAsB,CAAC,CAAC;SAC9F,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;AAChD,CAAC,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { Profile } from '../profiles/Profile.js';
2
+ import type { Settings } from '../settings/Settings.js';
3
+ import type { StatePathDeclaration } from '../compositeProfile/StatePersistence.js';
4
+ import type { CompositeProfile } from '../compositeProfile/CompositeProfile.js';
5
+ export interface AgentLaunchPlan {
6
+ readonly command: string;
7
+ readonly args: readonly string[];
8
+ readonly env: Readonly<Record<string, string>>;
9
+ }
10
+ export interface AgentLaunchContext {
11
+ readonly profileFolders?: readonly string[];
12
+ }
13
+ export interface AgentCompositeProfilePlan {
14
+ readonly compositeProfile: CompositeProfile;
15
+ readonly warnings: readonly string[];
16
+ }
17
+ export interface AgentAdapter {
18
+ readonly id: string;
19
+ readonly supportedControls: readonly string[];
20
+ readonly statePaths?: Readonly<Record<string, StatePathDeclaration>>;
21
+ createCompositeProfile(profile: Profile, input: {
22
+ readonly rootDirectory: string;
23
+ readonly profilePaths: readonly string[];
24
+ readonly profileFolders?: readonly string[];
25
+ readonly homeDirectory?: string;
26
+ readonly cacheDirectory?: string;
27
+ readonly settings?: Settings;
28
+ readonly projectDirectory?: string;
29
+ }): AgentCompositeProfilePlan;
30
+ createLaunchPlan(compositeProfile: CompositeProfile, profile?: Profile, passThroughArgs?: readonly string[], context?: AgentLaunchContext): AgentLaunchPlan;
31
+ getUnsupportedControls(profile: Profile): readonly string[];
32
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=AgentAdapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentAdapter.js","sourceRoot":"","sources":["../../src/agents/AgentAdapter.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ import type { AgentAdapter } from './AgentAdapter.js';
2
+ export type SupportedAgentId = 'pi' | 'claude';
3
+ export declare const defaultAgentId: SupportedAgentId;
4
+ export declare const supportedAgentIds: readonly ["pi", "claude"];
5
+ export declare const isSupportedAgentId: (agentId: string) => agentId is SupportedAgentId;
6
+ export declare const createAgentAdapter: (agentId: string | undefined) => AgentAdapter;
@@ -0,0 +1,17 @@
1
+ import { createClaudeAdapter } from './claude/ClaudeAdapter.js';
2
+ import { createPiAdapter } from './pi/PiAdapter.js';
3
+ export const defaultAgentId = 'pi';
4
+ export const supportedAgentIds = [defaultAgentId, 'claude'];
5
+ export const isSupportedAgentId = (agentId) => supportedAgentIds.includes(agentId);
6
+ export const createAgentAdapter = (agentId) => {
7
+ const selectedAgentId = agentId ?? defaultAgentId;
8
+ switch (selectedAgentId) {
9
+ case 'pi':
10
+ return createPiAdapter();
11
+ case 'claude':
12
+ return createClaudeAdapter();
13
+ default:
14
+ throw new Error(`Unknown agent '${selectedAgentId}'. Expected one of: ${supportedAgentIds.join(', ')}.`);
15
+ }
16
+ };
17
+ //# sourceMappingURL=AgentRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentRegistry.js","sourceRoot":"","sources":["../../src/agents/AgentRegistry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAIpD,MAAM,CAAC,MAAM,cAAc,GAAqB,IAAI,CAAC;AAErD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,cAAc,EAAE,QAAQ,CAAgD,CAAC;AAE3G,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,OAAe,EAA+B,EAAE,CACjF,iBAAiB,CAAC,QAAQ,CAAC,OAA2B,CAAC,CAAC;AAE1D,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,OAA2B,EAAgB,EAAE;IAC9E,MAAM,eAAe,GAAG,OAAO,IAAI,cAAc,CAAC;IAElD,QAAQ,eAAe,EAAE,CAAC;QACxB,KAAK,IAAI;YACP,OAAO,eAAe,EAAE,CAAC;QAC3B,KAAK,QAAQ;YACX,OAAO,mBAAmB,EAAE,CAAC;QAC/B;YACE,MAAM,IAAI,KAAK,CAAC,kBAAkB,eAAe,uBAAuB,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7G,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ export type LaunchResourceKind = 'extension' | 'skill';
2
+ export type LaunchResourceOrigin = 'generic-controls' | 'agent-controls' | 'profile-controls' | 'settings';
3
+ export interface LaunchResourceEntry {
4
+ readonly kind: LaunchResourceKind;
5
+ readonly source: string;
6
+ readonly identity: string;
7
+ readonly origin: LaunchResourceOrigin;
8
+ /** Higher values win ties for the same identity. */
9
+ readonly precedence: number;
10
+ }
11
+ export declare const createLaunchResourceEntries: (kind: LaunchResourceKind, sources: readonly string[] | undefined, origin: LaunchResourceOrigin, precedence: number) => readonly LaunchResourceEntry[];
12
+ export declare const mergeLaunchResourceEntries: (entries: readonly LaunchResourceEntry[]) => readonly LaunchResourceEntry[];
13
+ export declare const mergeLaunchResourceSources: (kind: LaunchResourceKind, lowerPrecedence: readonly string[] | undefined, higherPrecedence: readonly string[] | undefined) => readonly string[] | undefined;
@@ -0,0 +1,35 @@
1
+ // Models launch inputs as precedence-bearing resources that can be merged and deduplicated.
2
+ import { normalizeExtensionResourceIdentity, normalizeLaunchResourceIdentity } from './ResourceIdentity.js';
3
+ export const createLaunchResourceEntries = (kind, sources = [], origin, precedence) => sources.map((source) => ({
4
+ kind,
5
+ source,
6
+ identity: createLaunchResourceIdentity(kind, source),
7
+ origin,
8
+ precedence,
9
+ }));
10
+ export const mergeLaunchResourceEntries = (entries) => {
11
+ const entriesByIdentity = new Map();
12
+ for (const entry of entries) {
13
+ const existing = entriesByIdentity.get(entry.identity);
14
+ if (existing === undefined || entry.precedence > existing.precedence) {
15
+ entriesByIdentity.set(entry.identity, entry);
16
+ }
17
+ }
18
+ return entries.filter((entry) => entriesByIdentity.get(entry.identity) === entry);
19
+ };
20
+ export const mergeLaunchResourceSources = (kind, lowerPrecedence, higherPrecedence) => {
21
+ if (lowerPrecedence === undefined && higherPrecedence === undefined) {
22
+ return undefined;
23
+ }
24
+ return mergeLaunchResourceEntries([
25
+ ...createLaunchResourceEntries(kind, higherPrecedence, 'agent-controls', 1),
26
+ ...createLaunchResourceEntries(kind, lowerPrecedence, 'generic-controls', 0),
27
+ ]).map((entry) => entry.source);
28
+ };
29
+ const createLaunchResourceIdentity = (kind, source) => {
30
+ if (kind === 'extension') {
31
+ return normalizeExtensionResourceIdentity(source);
32
+ }
33
+ return normalizeLaunchResourceIdentity(source);
34
+ };
35
+ //# sourceMappingURL=LaunchResources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LaunchResources.js","sourceRoot":"","sources":["../../src/agents/LaunchResources.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,OAAO,EAAE,kCAAkC,EAAE,+BAA+B,EAAE,MAAM,uBAAuB,CAAC;AAc5G,MAAM,CAAC,MAAM,2BAA2B,GAAG,CACzC,IAAwB,EACxB,UAA6B,EAAE,EAC/B,MAA4B,EAC5B,UAAkB,EACc,EAAE,CAClC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACvB,IAAI;IACJ,MAAM;IACN,QAAQ,EAAE,4BAA4B,CAAC,IAAI,EAAE,MAAM,CAAC;IACpD,MAAM;IACN,UAAU;CACX,CAAC,CAAC,CAAC;AAEN,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,OAAuC,EAAkC,EAAE;IACpH,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAA+B,CAAC;IAEjE,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAEvD,IAAI,QAAQ,KAAK,SAAS,IAAI,KAAK,CAAC,UAAU,GAAG,QAAQ,CAAC,UAAU,EAAE,CAAC;YACrE,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,iBAAiB,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,KAAK,CAAC,CAAC;AACpF,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,0BAA0B,GAAG,CACxC,IAAwB,EACxB,eAA8C,EAC9C,gBAA+C,EAChB,EAAE;IACjC,IAAI,eAAe,KAAK,SAAS,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;QACpE,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,0BAA0B,CAAC;QAChC,GAAG,2BAA2B,CAAC,IAAI,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAC,CAAC;QAC3E,GAAG,2BAA2B,CAAC,IAAI,EAAE,eAAe,EAAE,kBAAkB,EAAE,CAAC,CAAC;KAC7E,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AAClC,CAAC,CAAC;AAEF,MAAM,4BAA4B,GAAG,CAAC,IAAwB,EAAE,MAAc,EAAU,EAAE;IACxF,IAAI,IAAI,KAAK,WAAW,EAAE,CAAC;QACzB,OAAO,kCAAkC,CAAC,MAAM,CAAC,CAAC;IACpD,CAAC;IAED,OAAO,+BAA+B,CAAC,MAAM,CAAC,CAAC;AACjD,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const normalizeExtensionResourceIdentity: (source: string) => string;
2
+ export declare const normalizeLaunchResourceIdentity: (source: string) => string;