Skip to content

Commit 5c9d3c3

Browse files
authored
Merge pull request #5862 from unisonweb/github-project-name
2 parents b3da260 + 7ccff70 commit 5c9d3c3

File tree

9 files changed

+122
-12
lines changed

9 files changed

+122
-12
lines changed

unison-cli/src/Unison/Codebase/Editor/HandleInput.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ loop e = do
194194
Cli.Env {serverBaseUrl} <- ask
195195
whenJust serverBaseUrl \baseUrl ->
196196
Cli.respond $
197-
PrintMessage $
197+
Literal $
198198
P.lines
199199
[ "The API information is as follows:",
200200
P.newline,
@@ -203,7 +203,7 @@ loop e = do
203203
P.indentN 2 (P.hiBlue ("API: " <> Pretty.text (Server.urlFor Server.Api baseUrl)))
204204
]
205205
CreateMessage pretty ->
206-
Cli.respond $ PrintMessage pretty
206+
Cli.respond $ Literal pretty
207207
ShowRootReflogI -> do
208208
let numEntriesToShow = 500
209209
(schLength, entries) <-

unison-cli/src/Unison/Codebase/Editor/HandleInput/Load.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ evalUnisonFile mode ppe unisonFile args = do
452452
| not $ null errs ->
453453
False <$ RuntimeUtils.displayDecompileErrors errs
454454
Runtime.Profile prof ->
455-
True <$ Cli.respond (Output.PrintMessage prof)
455+
True <$ Cli.respond (Output.Literal prof)
456456
_ -> pure True
457457
for_ (Map.elems map) \(_loc, kind, hash, _src, value, isHit) -> do
458458
-- only update the watch cache when there are no errors

unison-cli/src/Unison/Codebase/Editor/HandleInput/Names.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ handleNames ::
3939
Cli ()
4040
handleNames _ (nameQuery, Left errMsg) = do
4141
Cli.respond $
42-
PrintMessage $
42+
Literal $
4343
P.lines [prettyNameQuery, errMsg]
4444
where
4545
prettyNameQuery =

unison-cli/src/Unison/Codebase/Editor/HandleInput/RuntimeUtils.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ modeProfSpec (Permissive prof) = prof
5050

5151
displayDecompileErrors :: [DecompError] -> Cli ()
5252
displayDecompileErrors =
53-
Cli.respond . PrintMessage . msg . fmap (P.indentN 2 . P.indentN 2 . renderDecompError)
53+
Cli.respond . Literal . msg . fmap (P.indentN 2 . P.indentN 2 . renderDecompError)
5454
where
5555
msg em = do
5656
P.lines $
@@ -97,7 +97,7 @@ evalUnisonTermE mode ppe useCache tm = do
9797
displayResponse :: Runtime.Response DecompError -> Cli ()
9898
displayResponse (Runtime.DecompErrs errs)
9999
| not $ null errs = displayDecompileErrors errs
100-
displayResponse (Runtime.Profile prof) = Cli.respond (PrintMessage msg)
100+
displayResponse (Runtime.Profile prof) = Cli.respond (Literal msg)
101101
where
102102
msg = P.lines ["Profile Results:", ""] <> prof
103103
displayResponse _ = pure ()

unison-cli/src/Unison/Codebase/Editor/Output.hs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,6 @@ data Output
178178
Success
179179
| -- User did `update` before typechecking a file?
180180
NoUnisonFile
181-
| -- Used in Welcome module to instruct user
182-
PrintMessage (P.Pretty P.ColorText)
183181
| InvalidSourceName String
184182
| SourceLoadFailed String
185183
| -- No main function, the [Type v Ann] are the allowed types
@@ -518,7 +516,6 @@ isFailure o = case o of
518516
SaveTermNameConflict {} -> True
519517
RunResult {} -> False
520518
Success {} -> False
521-
PrintMessage {} -> False
522519
NoUnisonFile {} -> True
523520
InvalidSourceName {} -> True
524521
SourceLoadFailed {} -> True

unison-cli/src/Unison/CommandLine/Main.hs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import System.Console.Haskeline.History qualified as Line
2121
import System.FSNotify qualified as FSNotify
2222
import System.IO (hGetEcho, hPutStrLn, hSetEcho, stderr, stdin)
2323
import System.IO.Error (isDoesNotExistError)
24+
import U.Codebase.Sqlite.Queries qualified as Queries
2425
import Unison.Auth.CredentialManager qualified as AuthN
2526
import Unison.Auth.HTTPClient (AuthenticatedHttpClient)
2627
import Unison.Auth.HTTPClient qualified as AuthN
@@ -45,6 +46,7 @@ import Unison.CommandLine.Welcome qualified as Welcome
4546
import Unison.Parser.Ann (Ann)
4647
import Unison.Prelude
4748
import Unison.PrettyTerminal
49+
import Unison.Project qualified as Project
4850
import Unison.Runtime (Runtime)
4951
import Unison.Runtime.IOSource qualified as IOSource
5052
import Unison.Server.CodebaseServer qualified as Server
@@ -172,8 +174,38 @@ main dir welcome ppIds initialInputs runtime sbRuntime codebase serverBaseUrl uc
172174
ShouldWatchFiles -> allow
173175
)
174176

177+
-- On startup, we tell the user about any existing project names that don't pass the new project name regex,
178+
-- which isn't enforced yet.
179+
180+
invalidProjectNamesInputs <- do
181+
projects <- Codebase.runTransaction codebase Queries.loadAllProjects
182+
let invalidProjectNames =
183+
mapMaybe
184+
( \project ->
185+
if Project.isValidNewProjectName project.name
186+
then Nothing
187+
else Just project.name
188+
)
189+
projects
190+
pure case invalidProjectNames of
191+
[] -> []
192+
_ ->
193+
[ Right . CreateMessage . P.warnCallout $
194+
P.wrap "We're updating UCM's project naming rules, and these names won’t be supported much longer:"
195+
<> P.newline
196+
<> P.newline
197+
<> P.group (P.commas (map P.prettyProjectName invalidProjectNames))
198+
<> P.newline
199+
<> P.newline
200+
<> P.wrap
201+
( "Please"
202+
<> IP.makeExample IP.projectRenameInputPattern []
203+
<> "them using only ASCII letters, numbers, and hyphens, of length 2-40 characters."
204+
)
205+
]
206+
175207
let initialState = Cli.loopState0 ppIds
176-
initialInputsRef <- newIORef $ Welcome.run welcome ++ initialInputs
208+
initialInputsRef <- newIORef $ Welcome.run welcome ++ initialInputs ++ invalidProjectNamesInputs
177209
pageOutput <- newIORef True
178210

179211
initialEcho <- hGetEcho stdin

unison-cli/src/Unison/CommandLine/OutputMessages.hs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,6 @@ notifyUser dir issueFn = \case
559559
<> "to evaluate something before attempting"
560560
<> "to save it."
561561
Success -> pure $ P.bold "Done."
562-
PrintMessage pretty -> pure pretty
563562
NamespaceEmpty p ->
564563
case p of
565564
(p0 NEList.:| []) ->

unison-core/src/Unison/Project.hs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module Unison.Project
99
projectNameUserSlug,
1010
projectNameToUserProjectSlugs,
1111
prependUserSlugToProjectName,
12+
isValidNewProjectName,
1213
ProjectBranchName,
1314
projectBranchNameUserSlug,
1415
projectBranchNameToValidProjectBranchNameText,
@@ -34,6 +35,7 @@ where
3435

3536
import Data.Char qualified as Char
3637
import Data.Kind (Type)
38+
import Data.Monoid qualified as Monoid
3739
import Data.Text qualified as Text
3840
import Data.Text.Read qualified as Text (decimal)
3941
import Data.These (These (..))
@@ -75,6 +77,56 @@ projectNameParser = do
7577
isStartChar c =
7678
Char.isAlpha c || c == '_'
7779

80+
-- Parse a project name, and whether it ended in a forward slash (which is, of course, not part of the name)
81+
newProjectNameParser :: Megaparsec.Parsec Void Text (ProjectName, Bool)
82+
newProjectNameParser = do
83+
userSlug <-
84+
asum
85+
[ do
86+
user <- userSlugParser
87+
pure (Text.Builder.char '@' <> user <> Text.Builder.char '/'),
88+
pure mempty
89+
]
90+
projectSlug <- projectSlugParser
91+
hasTrailingSlash <- isJust <$> optional (Megaparsec.char '/')
92+
pure (UnsafeProjectName (Text.Builder.run (userSlug <> projectSlug)), hasTrailingSlash)
93+
where
94+
-- Github project regular expression: ^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){1,39}$
95+
--
96+
-- In English: a-z or 0-9, followed by 1-39 repetitions of a-z or 0-9 or hyphen, with the restriction that any
97+
-- hyphen must be followed by a-z or 0-9
98+
--
99+
-- We implement that here, but with parser combinators: a-z or 0-9, followed by 1 or more chunks of [optional
100+
-- hyphen followed by 1 or more a-z or 0-9], checking length at the end
101+
projectSlugParser :: Megaparsec.Parsec Void Text Text.Builder
102+
projectSlugParser = do
103+
firstChar <- Megaparsec.satisfy isAsciiLowerOrDigit
104+
chunks <- some ((,) <$> optional (Megaparsec.char '-') <*> Megaparsec.takeWhile1P Nothing isAsciiLowerOrDigit)
105+
when (chunksLength chunks > 39) (fail "Project name must be 2-40 characters long.")
106+
pure $
107+
Text.Builder.char firstChar
108+
<> foldMap
109+
( \(maybeHyphen, chunk) ->
110+
maybe (mempty @Text.Builder) Text.Builder.char maybeHyphen <> Text.Builder.text chunk
111+
)
112+
chunks
113+
where
114+
isAsciiLowerOrDigit :: Char -> Bool
115+
isAsciiLowerOrDigit c =
116+
Char.isAsciiLower c || Char.isDigit c
117+
118+
chunksLength :: [(Maybe Char, Text)] -> Int
119+
chunksLength =
120+
Monoid.getSum . foldMap (Monoid.Sum . chunkLength)
121+
122+
chunkLength :: (Maybe Char, Text) -> Int
123+
chunkLength (maybeHyphen, chunk) =
124+
(if isJust maybeHyphen then 1 else 0) + Text.length chunk
125+
126+
isValidNewProjectName :: ProjectName -> Bool
127+
isValidNewProjectName (UnsafeProjectName projectName) =
128+
isRight (Megaparsec.parse newProjectNameParser "" projectName)
129+
78130
-- | Get the user slug at the beginning of a project name, if there is one.
79131
--
80132
-- >>> projectNameUserSlug "@arya/lens"

unison-src/transcripts/project-outputs/docs/mcp.output.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ This approach allows agents to connect to UCM's MCP server directly via stdin/st
1212

1313
Note that this causes an additional UCM to run as an entirely independent process for each agent you're using.
1414

15+
#### Claude Code
16+
1517
To configure the MCP for use with Claude (and any tools which read Claude's json config), edit your Claude Desktop config JSON file, which is found:
1618

1719
- On Mac: `~/Library/Application Support/Claude/claude_desktop_config.json`
1820
- On Windows: `%APPDATA%\Claude\claude_desktop_config.json`
21+
- On Linux: `$HOME/.claude.json`
1922

2023
Configure a `unison` key in your `mcpServers` object as below. Replace `<path-to-ucm>` with the path to your `ucm` executable.
2124
E.g. on Mac this is likely `/opt/homebrew/bin/ucm`, you can run `which ucm` to find your UCM executable path.
@@ -32,7 +35,7 @@ E.g. on Mac this is likely `/opt/homebrew/bin/ucm`, you can run `which ucm` to f
3235

3336
E.g. my complete file on Mac looks like this:
3437

35-
```
38+
``` json
3639
{
3740
"mcpServers": {
3841
"unison": {
@@ -45,6 +48,33 @@ E.g. my complete file on Mac looks like this:
4548

4649
After saving the file, restart the Claude Desktop app. You should then see a new "unison" option in the MCP server list.
4750

51+
#### Codex
52+
53+
Codex is primarily used for OpenAI models, but can be used with other model providers that support the OpenAI API.
54+
55+
Configuration is similar to Claude Code; locate your Codex config file:
56+
57+
- On Linux: `$HOME/.codex/config.toml`
58+
59+
``` toml
60+
[mcp_servers.unison]
61+
command = "/path/to/ucm"
62+
args = ["mcp"]
63+
```
64+
65+
Restart `codex`; you should now be able to see the Unison MCP server by entering `/mcp` in Codex:
66+
67+
```
68+
/mcp
69+
70+
🔌 MCP Tools
71+
72+
• Server: unison
73+
• Command: /home/bbarker/.nix-profile/bin/ucm mcp
74+
• Tools: docs, get-current-project-context, lib-install, list-definition-dependencies, list-definition-dependents, list-library-definitions, list-local-projects, list-project-branches,
75+
list-project-definitions, list-project-libraries, search-by-type, search-definitions-by-name, share-project-readme, share-project-search, typecheck-code, view-definitions
76+
```
77+
4878
### Connecting to a running UCM executable (not recommended)
4979

5080
If instead you wish to connect an agent to a running UCM executable you can use an HTTP MCP connection.

0 commit comments

Comments
 (0)