@ai-outfitter/outfitter 0.6.1 → 0.7.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/LICENSE.md +20 -50
- package/README.md +41 -280
- package/code/enterprise/LICENSE +35 -0
- package/code/enterprise/README.md +7 -0
- package/code/enterprise/cli/privateCatalogGate.cjs +134 -0
- package/code/enterprise/cli/privateCatalogSettings.cjs +59 -0
- package/code/enterprise/pi-extension/privateCatalogOnboarding.js +89 -0
- package/code/enterprise/private-catalog-boundary.json +9 -0
- package/code/enterprise/privateCatalog.js +24 -0
- package/code/enterprise/shared/privateCatalogPolicy.cjs +66 -0
- package/dist/agents/AdapterProfileControls.d.ts +2 -2
- package/dist/agents/AdapterProfileControls.js +8 -1
- package/dist/agents/AdapterProfileControls.js.map +1 -1
- package/dist/agents/AgentAdapter.d.ts +11 -0
- package/dist/agents/AgentLaunch.d.ts +6 -0
- package/dist/agents/AgentLaunch.js +89 -0
- package/dist/agents/AgentLaunch.js.map +1 -0
- package/dist/agents/claude/ClaudeAdapter.js +18 -3
- package/dist/agents/claude/ClaudeAdapter.js.map +1 -1
- package/dist/agents/pi/PiAdapter.js +162 -33
- package/dist/agents/pi/PiAdapter.js.map +1 -1
- package/dist/agents/pi/PiArgs.d.ts +2 -0
- package/dist/agents/pi/PiArgs.js +15 -0
- package/dist/agents/pi/PiArgs.js.map +1 -0
- package/dist/agents/pi/PiExtensionCache.d.ts +4 -0
- package/dist/agents/pi/PiExtensionCache.js +105 -0
- package/dist/agents/pi/PiExtensionCache.js.map +1 -0
- package/dist/cli/commands/PiLoginLaunch.d.ts +9 -2
- package/dist/cli/commands/PiLoginLaunch.js +817 -75
- package/dist/cli/commands/PiLoginLaunch.js.map +1 -1
- package/dist/cli/commands/RunCommand.d.ts +22 -3
- package/dist/cli/commands/RunCommand.js +104 -20
- package/dist/cli/commands/RunCommand.js.map +1 -1
- package/dist/cli/commands/SetupCommand.d.ts +19 -2
- package/dist/cli/commands/SetupCommand.js +281 -57
- package/dist/cli/commands/SetupCommand.js.map +1 -1
- package/dist/cli/commands/SyncCommand.d.ts +19 -1
- package/dist/cli/commands/SyncCommand.js +47 -6
- package/dist/cli/commands/SyncCommand.js.map +1 -1
- package/dist/cli/commands/WelcomeCommand.js +1 -1
- package/dist/cli/commands/WelcomeCommand.js.map +1 -1
- package/dist/cli/commands/assets/outfitter-ascii.txt +5 -0
- package/dist/cli/commands/profile/Command.d.ts +1 -0
- package/dist/cli/commands/profile/Command.js +3 -0
- package/dist/cli/commands/profile/Command.js.map +1 -1
- package/dist/cli/commands/profile/LintCommand.d.ts +19 -0
- package/dist/cli/commands/profile/LintCommand.js +123 -0
- package/dist/cli/commands/profile/LintCommand.js.map +1 -0
- package/dist/cli.js +8 -2
- package/dist/cli.js.map +1 -1
- package/dist/merge/ArrayMergePolicy.js.map +1 -1
- package/dist/merge/SettingsValueMerger.js.map +1 -1
- package/dist/profiles/Profile.d.ts +13 -1
- package/dist/profiles/Profile.js.map +1 -1
- package/dist/profiles/ProfileLoader.d.ts +4 -0
- package/dist/profiles/ProfileLoader.js +117 -17
- package/dist/profiles/ProfileLoader.js.map +1 -1
- package/dist/profiles/ProfileMerger.js +3 -0
- package/dist/profiles/ProfileMerger.js.map +1 -1
- package/dist/profiles/PromptIncludes.d.ts +32 -0
- package/dist/profiles/PromptIncludes.js +147 -0
- package/dist/profiles/PromptIncludes.js.map +1 -0
- package/dist/prompts/SystemPromptExport.d.ts +16 -0
- package/dist/prompts/SystemPromptExport.js +81 -0
- package/dist/prompts/SystemPromptExport.js.map +1 -0
- package/dist/schemas/profile.schema.json +37 -2
- package/dist/schemas/settings.schema.json +23 -0
- package/dist/settings/Settings.d.ts +9 -0
- package/dist/settings/Settings.js.map +1 -1
- package/dist/settings/SettingsLoader.js +5 -0
- package/dist/settings/SettingsLoader.js.map +1 -1
- package/dist/settings/SettingsMerger.js +11 -0
- package/dist/settings/SettingsMerger.js.map +1 -1
- package/package.json +7 -11
- package/src/schemas/profile.schema.json +37 -2
- package/src/schemas/settings.schema.json +23 -0
- package/doc/.deepreview +0 -30
- package/doc/architecture.md +0 -856
- package/doc/controllable-elements.md +0 -162
- package/doc/file_structure.md +0 -141
- package/doc/integration_test_system.md +0 -214
- package/doc/specs/validating_requirements_with_rules.md +0 -55
- package/doc/state_writeback_strategy.md +0 -342
- package/requirements/OFTR-001-project-foundation.md +0 -53
- package/requirements/OFTR-002-settings.md +0 -65
- package/requirements/OFTR-003-profiles.md +0 -60
- package/requirements/OFTR-004-sync-and-setup.md +0 -67
- package/requirements/OFTR-005-run-and-composite-profile.md +0 -60
- package/requirements/OFTR-006-agent-adapters.md +0 -66
- package/requirements/OFTR-007-controllable-elements.md +0 -32
- package/requirements/OFTR-008-requirements-governance.md +0 -42
- package/requirements/OFTR-009-release-publishing.md +0 -35
- package/requirements/OFTR-010-onboarding-welcome.md +0 -48
package/LICENSE.md
CHANGED
|
@@ -1,58 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
Portions of this software are licensed as follows:
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
3
|
+
- All content that resides under the `code/enterprise/` directory of this repository, if that directory exists, is licensed under the license defined in `code/enterprise/LICENSE`.
|
|
4
|
+
- Content outside of the above mentioned directory or restrictions above is available under the MIT license as defined below.
|
|
45
5
|
|
|
46
6
|
---
|
|
47
7
|
|
|
48
|
-
|
|
8
|
+
MIT License
|
|
49
9
|
|
|
50
|
-
|
|
10
|
+
Copyright (c) 2026 Unsupervised
|
|
51
11
|
|
|
52
|
-
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
53
18
|
|
|
54
|
-
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
55
21
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,295 +1,56 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Outfitter
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Outfitter builds effective agent profiles and launches them through wrapped agent CLIs like [`pi`](https://github.com/earendil-works/pi-coding-agent), Claude Code, and future adapters. Individuals, teams, and organizations can share and compose repeatable agent profiles.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
If you have not tried [Pi](https://pi.dev) yet, Outfitter is the quickest path to a recommended Pi loadout for engineering work.
|
|
6
6
|
|
|
7
|
-
|
|
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:
|
|
7
|
+
## Quick start
|
|
17
8
|
|
|
18
9
|
```bash
|
|
19
10
|
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
11
|
outfitter setup
|
|
73
|
-
outfitter
|
|
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
|
-
- interactive setup-source onboarding shows the Outfitter welcome first, explains which source is being imported, asks whether to install profiles into user home or the current project, then asks one source-profile/default prompt;
|
|
142
|
-
- if the user chooses home and `~/.outfitter/settings.yml` does not exist, Outfitter copies the starter `settings.yml`;
|
|
143
|
-
- if the user chooses project and `<project>/.outfitter/settings.yml` does not exist, Outfitter copies the starter `settings.yml` and ensures `./profiles` is exposed;
|
|
144
|
-
- if starter profiles exist, Outfitter copies missing profile files into the selected `profiles/` folder;
|
|
145
|
-
- existing settings and profile files are otherwise left unchanged;
|
|
146
|
-
- after setup, Outfitter runs the same sync behavior used by `outfitter sync`, then offers to start with the selected default profile or shows both `outfitter` and `outfitter --profile <profile>` start commands;
|
|
147
|
-
- on initial interactive first-run setup, Outfitter skips the older default-profile prompt and lets welcome onboarding choose the generated local default profile;
|
|
148
|
-
- outside that initial welcome handoff and outside setup-source import onboarding, Outfitter shows a short setup wizard that lists synced profiles and writes the selected default profile to user settings;
|
|
149
|
-
- no-source interactive setup continues into welcome onboarding to record role and loadout choices.
|
|
150
|
-
|
|
151
|
-
A setup repository can use either root-level Outfitter files:
|
|
152
|
-
|
|
153
|
-
```text
|
|
154
|
-
outfitter_config/
|
|
155
|
-
settings.yml
|
|
156
|
-
profiles/
|
|
157
|
-
engineering-default/
|
|
158
|
-
profile.yml
|
|
159
|
-
support/
|
|
160
|
-
profile.yml
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
or a `.outfitter/` layout:
|
|
164
|
-
|
|
165
|
-
```text
|
|
166
|
-
outfitter_config/
|
|
167
|
-
.outfitter/
|
|
168
|
-
settings.yml
|
|
169
|
-
profiles/
|
|
170
|
-
engineering-default/
|
|
171
|
-
profile.yml
|
|
172
|
-
support/
|
|
173
|
-
profile.yml
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
Example `settings.yml` for a setup repository:
|
|
177
|
-
|
|
178
|
-
```yaml
|
|
179
|
-
default_profile: engineering-default
|
|
180
|
-
|
|
181
|
-
profile_sources:
|
|
182
|
-
- path: ./profiles
|
|
183
|
-
|
|
184
|
-
# Optional: keep loading future updates from this same repo.
|
|
185
|
-
- github: my_account/outfitter_config
|
|
186
|
-
ref: main
|
|
187
|
-
path: profiles
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
If you want ongoing centralized settings, use a small local `~/.outfitter/settings.yml` that points at remote settings:
|
|
191
|
-
|
|
192
|
-
```yaml
|
|
193
|
-
remote_settings:
|
|
194
|
-
- github: my_account/outfitter_config
|
|
195
|
-
ref: main
|
|
196
|
-
path: settings.yml
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
Then run:
|
|
200
|
-
|
|
201
|
-
```bash
|
|
202
|
-
outfitter sync
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
## Profile model sketch
|
|
206
|
-
|
|
207
|
-
A profile will use YAML.
|
|
208
|
-
An initial profile shape is:
|
|
209
|
-
|
|
210
|
-
```yaml
|
|
211
|
-
id: engineering-default
|
|
212
|
-
label: Engineering Default
|
|
213
|
-
description: General software engineering profile for coding, tests, reviews, and repo navigation.
|
|
214
|
-
inherits:
|
|
215
|
-
- base-typescript
|
|
216
|
-
- shared-prose
|
|
217
|
-
|
|
218
|
-
controls:
|
|
219
|
-
model: anthropic/claude-sonnet-4
|
|
220
|
-
environment:
|
|
221
|
-
TEAM_MODE: engineering
|
|
12
|
+
outfitter
|
|
222
13
|
```
|
|
223
14
|
|
|
224
|
-
|
|
15
|
+
Outfitter launches agent CLIs; install the agents you plan to use separately.
|
|
225
16
|
|
|
226
|
-
|
|
17
|
+
For the full walkthrough, see [Getting started](./docs/documentation/getting-started.md).
|
|
227
18
|
|
|
228
|
-
Profiles
|
|
229
|
-
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.
|
|
230
|
-
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.
|
|
231
|
-
Pi-specific job overrides remain supported under `cli_specific/pi/deepwork/jobs/`.
|
|
19
|
+
## Profiles
|
|
232
20
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
Profiles can request Pi extensions with `controls.pi.extensions`, for example:
|
|
21
|
+
[Profiles](./docs/documentation/profiles.md) compose the context, tools, prompts, skills, extensions, subagents, and DeepWork workflows that shape an agent. Profiles can be shared using [Profile Catalog Repos](./docs/documentation/profile-repository.md).
|
|
236
22
|
|
|
237
23
|
```yaml
|
|
24
|
+
# ~/.outfitter/profiles/home-default.yml
|
|
25
|
+
id: home-default
|
|
26
|
+
label: Home Default
|
|
27
|
+
description: Reusable personal defaults for Outfitter-managed Pi runs.
|
|
238
28
|
controls:
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
## Design direction
|
|
268
|
-
|
|
269
|
-
The current recommendation is to build `outfitter` around pi's existing native configuration mechanisms:
|
|
270
|
-
|
|
271
|
-
1. Use a temporary composite profile directory as `PI_CODING_AGENT_DIR` for each run.
|
|
272
|
-
2. Persist intentional pi state through adapter-declared symlinks to profile, native pi, or Outfitter cache files.
|
|
273
|
-
Native pi fallback is only a durable target for declared state symlinks; it is not an inherited base profile layer.
|
|
274
|
-
3. Layer profile-controlled environment variables and pi CLI flags on top.
|
|
275
|
-
4. Use explicit `--extension` / `-e` injection for bootstrap behavior that needs to run inside pi.
|
|
276
|
-
5. Decide per profile whether project-local `.pi` overrides are allowed.
|
|
277
|
-
6. Keep the wrapper responsible for anything that must happen before pi starts, such as selecting config directories, setting credentials, or choosing session locations.
|
|
278
|
-
|
|
279
|
-
## Status
|
|
280
|
-
|
|
281
|
-
This repository is under phased implementation.
|
|
282
|
-
|
|
283
|
-
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.
|
|
284
|
-
Stable end-to-end pi launch behavior and user-facing examples will be hardened in a later phase.
|
|
285
|
-
The initial dependency and architecture decisions are documented in `package.json`, `doc/architecture.md`, and `requirements/`.
|
|
286
|
-
|
|
287
|
-
## Future work
|
|
288
|
-
|
|
289
|
-
- Define a stable profile schema.
|
|
290
|
-
- Decide where organization-managed profiles are discovered from.
|
|
291
|
-
- Harden stable `outfitter run --profile <profile>` behavior.
|
|
292
|
-
- Add validation and inspection commands.
|
|
293
|
-
- Expand user-facing documentation for resolved profile inheritance and composition.
|
|
294
|
-
- Add locking / policy controls for business-managed environments.
|
|
295
|
-
- Add examples for common organizational deployments.
|
|
29
|
+
provider: openai-codex
|
|
30
|
+
model: gpt-5.5
|
|
31
|
+
thinking: high
|
|
32
|
+
append_system_prompt:
|
|
33
|
+
- |
|
|
34
|
+
Use concise, evidence-backed engineering prose.
|
|
35
|
+
Prefer small, reviewable changes.
|
|
36
|
+
Keep durable decisions in repo files.
|
|
37
|
+
- repo_file: docs/architecture.md
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Documentation
|
|
41
|
+
|
|
42
|
+
- [Getting started](./docs/documentation/getting-started.md)
|
|
43
|
+
- [Profiles](./docs/documentation/profiles.md)
|
|
44
|
+
- [Profile repositories](./docs/documentation/profile-repository.md)
|
|
45
|
+
- [State persistence](./docs/documentation/state.md)
|
|
46
|
+
- [First-time CLI agent users](./docs/documentation/first-time-cli-agent-users.md)
|
|
47
|
+
- [Switching to Outfitter](./docs/documentation/switching-to-outfitter.md)
|
|
48
|
+
- [Documentation index](./docs/documentation/README.md)
|
|
49
|
+
|
|
50
|
+
Use cases:
|
|
51
|
+
|
|
52
|
+
- [Organization profile catalog](./docs/documentation/usecases/orginization-profile-catalog.md) — Publish shared team roles so new users can start with organization-approved defaults.
|
|
53
|
+
- [Engineering profile catalog](./docs/documentation/usecases/engineering.md) — Package coding, platform, and review profiles for repeatable engineering workflows.
|
|
54
|
+
- [Persona reviews](./docs/documentation/usecases/persona-reviews.md) — Create customer personas to get feedback on ideas, documentation, and designs.
|
|
55
|
+
|
|
56
|
+
For local development, repository structure, and release workflow details, see [Contributing](./CONTRIBUTING.md).
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
The Unsupervised Enterprise license (the "Enterprise License")
|
|
2
|
+
Copyright (c) 2026 - present Unsupervised.com, Inc.
|
|
3
|
+
|
|
4
|
+
With regard to the Unsupervised Software:
|
|
5
|
+
|
|
6
|
+
This software and associated documentation files (the "Software") may only be
|
|
7
|
+
used in production, if you (and any entity that you represent) have agreed to,
|
|
8
|
+
and are in compliance with, the Unsupervised Subscription Terms of Service,
|
|
9
|
+
available via email (info@unsupervised.com) (the "Enterprise Terms"), or other
|
|
10
|
+
agreement governing the use of the Software, as agreed by you and Unsupervised,
|
|
11
|
+
and otherwise have a valid Unsupervised Enterprise license for the correct
|
|
12
|
+
number of user seats. Subject to the foregoing sentence, you are free to modify
|
|
13
|
+
this Software and publish patches to the Software. You agree that Unsupervised
|
|
14
|
+
and/or its licensors (as applicable) retain all right, title and interest in and
|
|
15
|
+
to all such modifications and/or patches, and all such modifications and/or
|
|
16
|
+
patches may only be used, copied, modified, displayed, distributed, or otherwise
|
|
17
|
+
exploited with a valid Unsupervised Enterprise license for the correct number of
|
|
18
|
+
user seats. Notwithstanding the foregoing, you may copy and modify the Software
|
|
19
|
+
for development and testing purposes, without requiring a subscription. You agree
|
|
20
|
+
that Unsupervised and/or its licensors (as applicable) retain all right, title
|
|
21
|
+
and interest in and to all such modifications. You are not granted any other
|
|
22
|
+
rights beyond what is expressly stated herein. Subject to the foregoing, it is
|
|
23
|
+
forbidden to copy, merge, publish, distribute, sublicense, and/or sell the
|
|
24
|
+
Software.
|
|
25
|
+
|
|
26
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
27
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
28
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
29
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
30
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
31
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
32
|
+
|
|
33
|
+
For all third party components incorporated into the Unsupervised Software,
|
|
34
|
+
those components are licensed under the original license provided by the owner of
|
|
35
|
+
the applicable component.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Outfitter Enterprise Code
|
|
2
|
+
|
|
3
|
+
Files in this directory and its descendants are licensed under the Outfitter Enterprise/Business Source License in [`LICENSE`](./LICENSE).
|
|
4
|
+
|
|
5
|
+
Everything outside `code/enterprise/**` is licensed under the root [`LICENSE.md`](../../LICENSE.md) terms.
|
|
6
|
+
|
|
7
|
+
`privateCatalog.js` defines the package-visible commercial boundary for private profile catalog support. Shared policy lives in `shared/`, CLI sync/settings helpers live in `cli/`, and Pi-native onboarding helpers live in `pi-extension/`. Package staging imports the policy marker and emits `private-catalog-boundary.json` so the published package carries executed enterprise policy artifacts, while public sync/setup behavior remains non-enforcing and continues to rely on the user's ambient Git configuration.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const { spawnSync } = require('node:child_process');
|
|
2
|
+
const { dirname, join } = require('node:path');
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
formatPrivateCatalogCliPrompt,
|
|
6
|
+
formatPrivateCatalogSkippedMessage,
|
|
7
|
+
formatPrivateCatalogSkipResultMessage,
|
|
8
|
+
privateCatalogEnabledMessage,
|
|
9
|
+
} = require('../shared/privateCatalogPolicy.cjs');
|
|
10
|
+
const { enablePrivateProfileCatalogs, isPrivateProfileCatalogsEnabled } = require('./privateCatalogSettings.cjs');
|
|
11
|
+
|
|
12
|
+
const createPrivateCatalogGate = ({ homeDirectory, classifier, prompt }) => {
|
|
13
|
+
const settingsPath = join(homeDirectory, '.outfitter', 'settings.yml');
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
classifier,
|
|
17
|
+
enabled: isPrivateProfileCatalogsEnabled(settingsPath),
|
|
18
|
+
homeDirectory,
|
|
19
|
+
prompt: prompt ?? createTtyPrivateCatalogPrompt(),
|
|
20
|
+
promptedRepositories: new Set(),
|
|
21
|
+
settingsPath,
|
|
22
|
+
skippedRepositories: new Set(),
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const gatePrivateCatalogSources = (sources, gate, helpers) => {
|
|
27
|
+
const allowedSources = [];
|
|
28
|
+
const skippedResults = [];
|
|
29
|
+
const messages = [];
|
|
30
|
+
|
|
31
|
+
for (const source of sources) {
|
|
32
|
+
const repository = source.github;
|
|
33
|
+
if (repository === undefined || gate.enabled || gate.classifier.classify(repository) !== 'private') {
|
|
34
|
+
allowedSources.push(source);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!gate.promptedRepositories.has(repository) && gate.prompt.interactive && gate.prompt.confirm(repository)) {
|
|
39
|
+
enablePrivateProfileCatalogs(gate.settingsPath);
|
|
40
|
+
gate.enabled = true;
|
|
41
|
+
messages.push(privateCatalogEnabledMessage);
|
|
42
|
+
allowedSources.push(source);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
gate.promptedRepositories.add(repository);
|
|
47
|
+
if (!gate.skippedRepositories.has(repository)) {
|
|
48
|
+
messages.push(formatPrivateCatalogSkippedMessage(repository, gate.prompt.interactive));
|
|
49
|
+
gate.skippedRepositories.add(repository);
|
|
50
|
+
}
|
|
51
|
+
skippedResults.push({
|
|
52
|
+
uri: helpers.formatDisplayUri(source),
|
|
53
|
+
cachePath: helpers.createRemoteRepositoryCachePath(dirname(dirname(gate.settingsPath)), source),
|
|
54
|
+
status: 'skipped',
|
|
55
|
+
message: formatPrivateCatalogSkipResultMessage(repository),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { allowedSources, skippedResults, messages };
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const createTtyPrivateCatalogPrompt = () => ({
|
|
63
|
+
interactive: process.stdin.isTTY === true && process.stdout.isTTY === true,
|
|
64
|
+
confirm(repository) {
|
|
65
|
+
const prompt = formatPrivateCatalogCliPrompt(repository);
|
|
66
|
+
const result = spawnSync(
|
|
67
|
+
'sh',
|
|
68
|
+
[
|
|
69
|
+
'-c',
|
|
70
|
+
'printf "%s" "$OUTFITTER_PRIVATE_CATALOG_PROMPT" > /dev/tty && IFS= read -r answer < /dev/tty && case "$answer" in y|Y|yes|YES) exit 0 ;; *) exit 1 ;; esac',
|
|
71
|
+
],
|
|
72
|
+
{ env: { ...process.env, OUTFITTER_PRIVATE_CATALOG_PROMPT: prompt }, stdio: 'inherit' },
|
|
73
|
+
);
|
|
74
|
+
return result.status === 0;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const createGitHubRepositoryVisibilityClassifier = () => ({
|
|
79
|
+
classify(repository) {
|
|
80
|
+
if (process.env.VITEST !== undefined) {
|
|
81
|
+
return 'unknown';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const [owner, repo] = repository.split('/');
|
|
85
|
+
|
|
86
|
+
if (!owner || !repo) {
|
|
87
|
+
return 'unknown';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const script = `
|
|
91
|
+
import https from 'node:https';
|
|
92
|
+
const [owner, repo] = process.argv.slice(1);
|
|
93
|
+
const request = https.get({
|
|
94
|
+
hostname: 'api.github.com',
|
|
95
|
+
path: '/repos/' + encodeURIComponent(owner) + '/' + encodeURIComponent(repo),
|
|
96
|
+
headers: { 'Accept': 'application/vnd.github+json', 'User-Agent': 'ai-outfitter-cli' },
|
|
97
|
+
timeout: 2000,
|
|
98
|
+
}, (response) => {
|
|
99
|
+
let body = '';
|
|
100
|
+
response.setEncoding('utf8');
|
|
101
|
+
response.on('data', (chunk) => { body += chunk; });
|
|
102
|
+
response.on('end', () => {
|
|
103
|
+
if (response.statusCode !== 200) {
|
|
104
|
+
console.log('unknown');
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const document = JSON.parse(body);
|
|
109
|
+
console.log(document.private === true ? 'private' : document.private === false ? 'public' : 'unknown');
|
|
110
|
+
} catch {
|
|
111
|
+
console.log('unknown');
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
request.on('timeout', () => request.destroy());
|
|
116
|
+
request.on('error', () => console.log('unknown'));
|
|
117
|
+
`;
|
|
118
|
+
const result = spawnSync(process.execPath, ['--input-type=module', '--eval', script, owner, repo], {
|
|
119
|
+
encoding: 'utf8',
|
|
120
|
+
stdio: 'pipe',
|
|
121
|
+
timeout: 3000,
|
|
122
|
+
});
|
|
123
|
+
const visibility = result.stdout.trim();
|
|
124
|
+
|
|
125
|
+
return visibility === 'private' || visibility === 'public' ? visibility : 'unknown';
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
module.exports = {
|
|
130
|
+
createGitHubRepositoryVisibilityClassifier,
|
|
131
|
+
createPrivateCatalogGate,
|
|
132
|
+
createTtyPrivateCatalogPrompt,
|
|
133
|
+
gatePrivateCatalogSources,
|
|
134
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const { existsSync, mkdirSync, readFileSync, writeFileSync } = require('node:fs');
|
|
2
|
+
const { dirname } = require('node:path');
|
|
3
|
+
|
|
4
|
+
const { parse, stringify } = require('yaml');
|
|
5
|
+
|
|
6
|
+
const isPrivateProfileCatalogsEnabled = (settingsPath) => {
|
|
7
|
+
if (!existsSync(settingsPath)) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const parsed = parse(readFileSync(settingsPath, 'utf8'));
|
|
13
|
+
return readPrivateProfileCatalogsSetting(parsed) === true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const enablePrivateProfileCatalogs = (settingsPath) => {
|
|
20
|
+
const document = readSettingsRecord(settingsPath);
|
|
21
|
+
const enterprise = readMutableRecord(document.enterprise);
|
|
22
|
+
|
|
23
|
+
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
24
|
+
writeFileSync(
|
|
25
|
+
settingsPath,
|
|
26
|
+
stringify({
|
|
27
|
+
...document,
|
|
28
|
+
enterprise: {
|
|
29
|
+
...enterprise,
|
|
30
|
+
private_profile_catalogs: true,
|
|
31
|
+
},
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const readPrivateProfileCatalogsSetting = (value) => {
|
|
37
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return value.enterprise?.private_profile_catalogs;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const readSettingsRecord = (settingsPath) => {
|
|
45
|
+
if (!existsSync(settingsPath)) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const parsed = parse(readFileSync(settingsPath, 'utf8'));
|
|
50
|
+
return readMutableRecord(parsed);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const readMutableRecord = (value) =>
|
|
54
|
+
value !== null && typeof value === 'object' && !Array.isArray(value) ? { ...value } : {};
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
enablePrivateProfileCatalogs,
|
|
58
|
+
isPrivateProfileCatalogsEnabled,
|
|
59
|
+
};
|