Skip to content

Commit 79b46ff

Browse files
authored
feat: add the create flags of sync prompts add
sync prompts add takes --content, --model-config-key, and --name, and --yes, which skips the confirmation of a create. --content turns the add into a create. The help says how the content becomes the prompt of an agent or a completion config, and has an example of a create without prompting. The project-key/config-key/variation-key form still adds variations that exist, and a create with that form is an error. The error for a variation that LaunchDarkly does not have now names the add that creates it, with --model-config-key and --content.
1 parent 79a9fd7 commit 79b46ff

6 files changed

Lines changed: 77 additions & 13 deletions

File tree

‎cmd/sync/attachments.go‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ func createInput(cmd *cobra.Command) *synccreate.Input {
151151
return &text
152152
}
153153
input := synccreate.Input{
154-
Content: value(contentFlag), JSONSchema: value(jsonSchemaFlag), Name: value(nameFlag), Description: value(descriptionFlag),
154+
Content: value(contentFlag), JSONSchema: value(jsonSchemaFlag), Name: value(nameFlag),
155+
Description: value(descriptionFlag), ModelConfigKey: value(modelConfigKeyFlag),
155156
}
156157
if !set {
157158
return nil

‎cmd/sync/prompt.go‎

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,28 +153,40 @@ func newWatchCmd(client resources.Client, scope syncdomain.Scope, help targetFla
153153
func newAddCmd(client resources.Client) *cobra.Command {
154154
cmd := &cobra.Command{
155155
Use: "add [project-key/config-key/variation-key...]",
156-
Short: "Add prompt variations from LaunchDarkly",
156+
Short: "Add prompt variations from LaunchDarkly, or create a variation",
157+
Long: "Add prompt variations from LaunchDarkly, or create a variation. " +
158+
"Without --content, add starts syncing variations that exist in LaunchDarkly. " +
159+
"With --content, add creates the variation in an existing config. For an agent config, the content is the instructions. " +
160+
"For a completion config, the content is role blocks such as <system>...</system>, or plain text for one system message. " +
161+
"The project-key/config-key/variation-key form still works.",
157162
Example: ` # Choose variations interactively
158163
ldcli sync prompts add
159164
160-
# Choose variations in one config
161-
ldcli sync prompts add --project production --config-key support
162-
163165
# Add variations by key without prompting
164-
ldcli sync prompts add --project production --config-key support --key default --key concise --no-input`,
166+
ldcli sync prompts add --project production --config-key support --key default --key concise --no-input
167+
168+
# Create an agent variation without prompting
169+
ldcli sync prompts add --project production --config-key support --key escalation \
170+
--model-config-key claude \
171+
--content "Hand the conversation to a person when the customer asks for one." \
172+
--yes --no-input`,
165173
Args: validatedArgs(cobra.MinimumNArgs(0)),
166174
RunE: runCommand(client, syncdomain.ScopePrompts, func(cmd *cobra.Command, args []string) (syncprompt.CommandAction, error) {
167175
variations, err := parseVariationSelectors(args)
168176
if err != nil {
169177
return nil, err
170178
}
171179
dryRun, _ := cmd.Flags().GetBool(dryRunFlag)
172-
return syncprompt.AddAction{Resources: variations, Filter: targetFilter(cmd), DryRun: dryRun}, nil
180+
return syncprompt.AddAction{Resources: variations, Filter: targetFilter(cmd), DryRun: dryRun, Create: createInput(cmd)}, nil
173181
}),
174182
}
175183
addTargetFlags(cmd, questionFlagHelp)
184+
cmd.Flags().String(contentFlag, "", "Prompt of a variation that add creates")
185+
cmd.Flags().String(modelConfigKeyFlag, "", "Model config of a variation that add creates")
186+
cmd.Flags().String(nameFlag, "", "Name of a variation that add creates. The default comes from the key.")
176187
cmd.Flags().Bool(dryRunFlag, false, "Preview local files without creating them")
177188
cmd.Flags().Bool(diffFlag, false, "Show full diffs and file content in the plan")
189+
cmd.Flags().Bool(yesFlag, false, "Create without confirmation")
178190
cmd.Flags().Bool(noInputFlag, false, "Fail instead of prompting for missing input")
179191
return cmd
180192
}

‎cmd/sync/scope_test.go‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@ import (
1515
// syncCommandFlags is the exact flag set of every sync command that runs. A
1616
// new command, or a new question without a flag, must change this list.
1717
var syncCommandFlags = map[string][]string{
18-
"sync all": {"conflict", "diff", "dry-run", "no-input", "project", "yes"},
19-
"sync all watch": {"conflict", "diff", "no-input", "project", "yes"},
20-
"sync prompts": {"config-key", "conflict", "diff", "dry-run", "key", "no-input", "project", "resolve", "yes"},
21-
"sync prompts watch": {"config-key", "conflict", "diff", "key", "no-input", "project", "resolve", "yes"},
22-
"sync prompts add": {"config-key", "diff", "dry-run", "key", "no-input", "project"},
18+
"sync all": {"conflict", "diff", "dry-run", "no-input", "project", "yes"},
19+
"sync all watch": {"conflict", "diff", "no-input", "project", "yes"},
20+
"sync prompts": {"config-key", "conflict", "diff", "dry-run", "key", "no-input", "project", "resolve", "yes"},
21+
"sync prompts watch": {"config-key", "conflict", "diff", "key", "no-input", "project", "resolve", "yes"},
22+
"sync prompts add": {
23+
"config-key", "content", "diff", "dry-run", "key", "model-config-key", "name", "no-input", "project", "yes",
24+
},
2325
"sync prompts attach tool": {"conflict", "diff", "no-input", "project", "resolve", "to", "yes"},
2426
"sync prompts attach skill": {"conflict", "diff", "no-input", "project", "resolve", "to", "yes"},
2527
"sync prompts detach": {"archive", "config-key", "key", "no-input", "project", "yes"},
@@ -102,6 +104,14 @@ func TestAddReadsTheCreateInputFromTheFlagsThatAreSet(t *testing.T) {
102104
assert.Equal(t, "", *input.Description)
103105
assert.Nil(t, input.Name)
104106
assert.Nil(t, input.JSONSchema)
107+
108+
prompts, _, err := root.Find([]string{"prompts", "add"})
109+
require.NoError(t, err)
110+
require.NoError(t, prompts.ParseFlags([]string{"--content", "Escalate.", "--model-config-key", "claude"}))
111+
input = createInput(prompts)
112+
require.NotNil(t, input)
113+
assert.Equal(t, "claude", *input.ModelConfigKey)
114+
assert.Nil(t, input.Name)
105115
}
106116

107117
func TestSyncCommandScopeNamesTheKindOfTheCommand(t *testing.T) {

‎internal/sync/bootstrap/bootstrap.go‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ func selectedVariations(options Options) ([]synclocal.VariationFile, error) {
115115
})
116116
if index < 0 {
117117
return nil, fmt.Errorf(
118-
"variation %q does not exist in config %q in project %q", variationKey, configKey, selection.ProjectKey,
118+
"variation %q does not exist in config %q in project %q. To create it, run %s with --model-config-key and --content.",
119+
variationKey, configKey, selection.ProjectKey, AddCommand(selection),
119120
)
120121
}
121122

‎internal/sync/bootstrap/bootstrap_test.go‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,19 @@ func TestRunUsesExplicitVariationSelectorsWithoutTerminal(t *testing.T) {
177177
assert.Contains(t, output.String(), " .launchdarkly/project/configs/config/variation.prompt.md +9")
178178
}
179179

180+
func TestRunNamesTheCreateFlagsOfAMissingVariation(t *testing.T) {
181+
catalog := &fakeCatalog{config: syncapi.Config{Key: "support", Mode: syncdomain.VariationModeAgent}}
182+
183+
err := Run(Options{
184+
Catalog: catalog, Store: synclocal.NewStore(t.TempDir()), Output: &bytes.Buffer{},
185+
Selections: []syncdomain.ResourceID{syncdomain.VariationID("demo", "support", "escalation")},
186+
})
187+
188+
require.EqualError(t, err, `variation "escalation" does not exist in config "support" in project "demo". `+
189+
"To create it, run ldcli sync prompts add --project demo --config-key support --key escalation "+
190+
"with --model-config-key and --content.")
191+
}
192+
180193
func TestFinishSelectionDryRunDoesNotCreateFiles(t *testing.T) {
181194
root := t.TempDir()
182195
var output bytes.Buffer

‎internal/sync/prompt/create_test.go‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,33 @@ func TestCreateVariationRemovesTheFileWhenLaunchDarklyRejectsTheCreate(t *testin
786786
assert.NoDirExists(t, filepath.Join(root, syncdomain.RootDir))
787787
}
788788

789+
func TestPromptsAddCreatesAVariationFromItsFlags(t *testing.T) {
790+
root := initRepository(t)
791+
api := &directAPI{modelConfigs: []syncapi.ModelConfig{claude}}
792+
793+
_, stderr, err := runPrompt(t, root, api, "add", "--project", "production", "--config-key", "support", "--key", "escalation",
794+
"--model-config-key", "claude", "--name", "Escalate to a person", "--content", "Hand the conversation to a person.",
795+
"--yes", "--no-input")
796+
797+
require.NoError(t, err)
798+
assert.Contains(t, stderr, "Note: Created .launchdarkly/production/"+escalationFile+".\n")
799+
require.NotNil(t, api.variation)
800+
assert.Equal(t, "Escalate to a person", api.variation.Name)
801+
assert.Equal(t, "Hand the conversation to a person.", api.variation.Instructions)
802+
}
803+
804+
func TestPromptsAddRefusesASelectorWithCreateFlags(t *testing.T) {
805+
root := initRepository(t)
806+
api := &directAPI{}
807+
808+
_, _, err := runPrompt(t, root, api, "add", "production/support/escalation",
809+
"--model-config-key", "claude", "--content", "Escalate.", "--yes", "--no-input")
810+
811+
require.ErrorContains(t, err, "a variation that add creates comes from --project, --config-key, and --key, "+
812+
"not from project-key/config-key/variation-key arguments")
813+
assert.Empty(t, api.requests)
814+
}
815+
789816
// createVariationAction is the add that creates the variation key in the
790817
// config "support" of the project "production".
791818
func createVariationAction(key string, input synccreate.Input) syncprompt.AddAction {

0 commit comments

Comments
 (0)