legionio 1.4.92 → 1.4.93
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +9 -0
- data/completions/_legion +123 -0
- data/completions/legion.bash +28 -1
- data/lib/legion/cli/dataset_command.rb +148 -0
- data/lib/legion/cli/prompt_command.rb +197 -0
- data/lib/legion/cli.rb +8 -0
- data/lib/legion/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 318839858d16ecbaaf6c7db4a0d9562e1641ca24d174fda914b9d359f3f91e69
|
|
4
|
+
data.tar.gz: 91e1415070e2f547bc7c71646e79312f048f8973b2cb004045a8d546a0000387
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e5cd6e2ff71cc11db2a1e8928aae0c53104f931c18829bf470658bbcac3e2f90a18aeb7c564dcc7eb3f113fa9c65b54bb165a0f322fc3b1021a0ebf98148bf66
|
|
7
|
+
data.tar.gz: ff3c9e796fef50d7ca2e7b1970c4516a2d839031f8bee6036fc43b758f0035d85b5bc80fd631c5e55d8a83ce28fe5a7ccfa55fa69b67237b868331d1606d3d34
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Legion Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.93] - 2026-03-20
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `legion prompt` CLI subcommand for versioned LLM prompt template management (list, show, create, tag, diff)
|
|
7
|
+
- `legion dataset` CLI subcommand for versioned dataset management (list, show, import, export)
|
|
8
|
+
- Both commands wrap `lex-prompt` and `lex-dataset` extension clients via `begin/rescue LoadError` guards
|
|
9
|
+
- Both commands guard with `Connection.ensure_data` and follow existing `with_*_client` pattern
|
|
10
|
+
- Tab completion entries for `prompt` and `dataset` in `completions/legion.bash` and `completions/_legion`
|
|
11
|
+
|
|
3
12
|
## [1.4.92] - 2026-03-20
|
|
4
13
|
|
|
5
14
|
### Added
|
data/completions/_legion
CHANGED
|
@@ -45,6 +45,8 @@ _legion() {
|
|
|
45
45
|
commit) _legion_commit ;;
|
|
46
46
|
pr) _legion_pr ;;
|
|
47
47
|
review) _legion_review ;;
|
|
48
|
+
prompt) _legion_prompt ;;
|
|
49
|
+
dataset) _legion_dataset ;;
|
|
48
50
|
esac
|
|
49
51
|
;;
|
|
50
52
|
esac
|
|
@@ -62,6 +64,8 @@ _legion_commands() {
|
|
|
62
64
|
'init:Interactive project setup wizard'
|
|
63
65
|
'tty:Launch interactive TTY shell'
|
|
64
66
|
'ask:Quick AI prompt (shortcut for chat prompt)'
|
|
67
|
+
'prompt:Manage versioned LLM prompt templates'
|
|
68
|
+
'dataset:Manage versioned datasets'
|
|
65
69
|
'version:Show version information'
|
|
66
70
|
'help:Show help'
|
|
67
71
|
)
|
|
@@ -837,4 +841,123 @@ _legion_check() {
|
|
|
837
841
|
'--no-color[Disable color output]'
|
|
838
842
|
}
|
|
839
843
|
|
|
844
|
+
_legion_prompt() {
|
|
845
|
+
local -a subcmds
|
|
846
|
+
subcmds=(
|
|
847
|
+
'list:List all prompts'
|
|
848
|
+
'show:Show a prompt template and parameters'
|
|
849
|
+
'create:Create a new prompt'
|
|
850
|
+
'tag:Tag a prompt version'
|
|
851
|
+
'diff:Show text diff between two versions of a prompt'
|
|
852
|
+
)
|
|
853
|
+
|
|
854
|
+
_arguments -C \
|
|
855
|
+
'--json[Output as JSON]' \
|
|
856
|
+
'--no-color[Disable color output]' \
|
|
857
|
+
'--help[Show help]' \
|
|
858
|
+
'1: :->cmd' \
|
|
859
|
+
'*:: :->args'
|
|
860
|
+
|
|
861
|
+
case $state in
|
|
862
|
+
cmd) _describe 'prompt command' subcmds ;;
|
|
863
|
+
args)
|
|
864
|
+
case $words[1] in
|
|
865
|
+
list)
|
|
866
|
+
_arguments \
|
|
867
|
+
'--json[Output as JSON]' \
|
|
868
|
+
'--no-color[Disable color output]'
|
|
869
|
+
;;
|
|
870
|
+
show)
|
|
871
|
+
_arguments \
|
|
872
|
+
':prompt name:' \
|
|
873
|
+
'--version[Specific version number]:version:' \
|
|
874
|
+
'--tag[Tag name to resolve]:tag:' \
|
|
875
|
+
'--json[Output as JSON]' \
|
|
876
|
+
'--no-color[Disable color output]'
|
|
877
|
+
;;
|
|
878
|
+
create)
|
|
879
|
+
_arguments \
|
|
880
|
+
':prompt name:' \
|
|
881
|
+
'--template[Prompt template text]:template:' \
|
|
882
|
+
'--description[Short description]:desc:' \
|
|
883
|
+
'--model-params[Model parameters as JSON]:json:' \
|
|
884
|
+
'--json[Output as JSON]' \
|
|
885
|
+
'--no-color[Disable color output]'
|
|
886
|
+
;;
|
|
887
|
+
tag)
|
|
888
|
+
_arguments \
|
|
889
|
+
':prompt name:' \
|
|
890
|
+
':tag name:' \
|
|
891
|
+
'--version[Version to tag]:version:' \
|
|
892
|
+
'--json[Output as JSON]' \
|
|
893
|
+
'--no-color[Disable color output]'
|
|
894
|
+
;;
|
|
895
|
+
diff)
|
|
896
|
+
_arguments \
|
|
897
|
+
':prompt name:' \
|
|
898
|
+
':version 1:' \
|
|
899
|
+
':version 2:' \
|
|
900
|
+
'--json[Output as JSON]' \
|
|
901
|
+
'--no-color[Disable color output]'
|
|
902
|
+
;;
|
|
903
|
+
esac
|
|
904
|
+
;;
|
|
905
|
+
esac
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
_legion_dataset() {
|
|
909
|
+
local -a subcmds
|
|
910
|
+
subcmds=(
|
|
911
|
+
'list:List all datasets'
|
|
912
|
+
'show:Show dataset info and first 10 rows'
|
|
913
|
+
'import:Import a dataset from a file'
|
|
914
|
+
'export:Export a dataset to a file'
|
|
915
|
+
)
|
|
916
|
+
|
|
917
|
+
_arguments -C \
|
|
918
|
+
'--json[Output as JSON]' \
|
|
919
|
+
'--no-color[Disable color output]' \
|
|
920
|
+
'--help[Show help]' \
|
|
921
|
+
'1: :->cmd' \
|
|
922
|
+
'*:: :->args'
|
|
923
|
+
|
|
924
|
+
case $state in
|
|
925
|
+
cmd) _describe 'dataset command' subcmds ;;
|
|
926
|
+
args)
|
|
927
|
+
case $words[1] in
|
|
928
|
+
list)
|
|
929
|
+
_arguments \
|
|
930
|
+
'--json[Output as JSON]' \
|
|
931
|
+
'--no-color[Disable color output]'
|
|
932
|
+
;;
|
|
933
|
+
show)
|
|
934
|
+
_arguments \
|
|
935
|
+
':dataset name:' \
|
|
936
|
+
'--version[Specific version number]:version:' \
|
|
937
|
+
'--json[Output as JSON]' \
|
|
938
|
+
'--no-color[Disable color output]'
|
|
939
|
+
;;
|
|
940
|
+
import)
|
|
941
|
+
_arguments \
|
|
942
|
+
':dataset name:' \
|
|
943
|
+
':file path:_files' \
|
|
944
|
+
'--format[File format]:format:(json csv jsonl)' \
|
|
945
|
+
'--description[Dataset description]:desc:' \
|
|
946
|
+
'--json[Output as JSON]' \
|
|
947
|
+
'--no-color[Disable color output]'
|
|
948
|
+
;;
|
|
949
|
+
export)
|
|
950
|
+
_arguments \
|
|
951
|
+
':dataset name:' \
|
|
952
|
+
':output path:_files' \
|
|
953
|
+
'--format[File format]:format:(json csv jsonl)' \
|
|
954
|
+
'--version[Version to export]:version:' \
|
|
955
|
+
'--json[Output as JSON]' \
|
|
956
|
+
'--no-color[Disable color output]'
|
|
957
|
+
;;
|
|
958
|
+
esac
|
|
959
|
+
;;
|
|
960
|
+
esac
|
|
961
|
+
}
|
|
962
|
+
|
|
840
963
|
_legion "$@"
|
data/completions/legion.bash
CHANGED
|
@@ -26,7 +26,7 @@ _legion_complete() {
|
|
|
26
26
|
cword=$COMP_CWORD
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
local top_commands="chat commit pr review memory plan init tty ask version help"
|
|
29
|
+
local top_commands="chat commit pr review memory plan init tty ask prompt dataset version help"
|
|
30
30
|
local global_flags="--json --no-color --verbose --config-dir --help"
|
|
31
31
|
|
|
32
32
|
# Top-level command
|
|
@@ -264,6 +264,33 @@ _legion_complete() {
|
|
|
264
264
|
COMPREPLY=($(compgen -W "--json --no-color --help" -- "${cur}"))
|
|
265
265
|
;;
|
|
266
266
|
|
|
267
|
+
prompt)
|
|
268
|
+
if [[ $cword -eq 2 ]]; then
|
|
269
|
+
COMPREPLY=($(compgen -W "list show create tag diff" -- "${cur}"))
|
|
270
|
+
else
|
|
271
|
+
case "${words[2]}" in
|
|
272
|
+
list) COMPREPLY=($(compgen -W "--json --no-color --help" -- "${cur}")) ;;
|
|
273
|
+
show) COMPREPLY=($(compgen -W "--version --tag --json --no-color --help" -- "${cur}")) ;;
|
|
274
|
+
create) COMPREPLY=($(compgen -W "--template --description --model-params --json --no-color --help" -- "${cur}")) ;;
|
|
275
|
+
tag) COMPREPLY=($(compgen -W "--version --json --no-color --help" -- "${cur}")) ;;
|
|
276
|
+
diff) COMPREPLY=($(compgen -W "--json --no-color --help" -- "${cur}")) ;;
|
|
277
|
+
esac
|
|
278
|
+
fi
|
|
279
|
+
;;
|
|
280
|
+
|
|
281
|
+
dataset)
|
|
282
|
+
if [[ $cword -eq 2 ]]; then
|
|
283
|
+
COMPREPLY=($(compgen -W "list show import export" -- "${cur}"))
|
|
284
|
+
else
|
|
285
|
+
case "${words[2]}" in
|
|
286
|
+
list) COMPREPLY=($(compgen -W "--json --no-color --help" -- "${cur}")) ;;
|
|
287
|
+
show) COMPREPLY=($(compgen -W "--version --json --no-color --help" -- "${cur}")) ;;
|
|
288
|
+
import) COMPREPLY=($(compgen -W "--format --description --json --no-color --help" -- "${cur}")) ;;
|
|
289
|
+
export) COMPREPLY=($(compgen -W "--format --version --json --no-color --help" -- "${cur}")) ;;
|
|
290
|
+
esac
|
|
291
|
+
fi
|
|
292
|
+
;;
|
|
293
|
+
|
|
267
294
|
dream)
|
|
268
295
|
COMPREPLY=($(compgen -W "--wait --json --no-color --help" -- "${cur}"))
|
|
269
296
|
;;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module CLI
|
|
7
|
+
class Dataset < Thor
|
|
8
|
+
def self.exit_on_failure?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class_option :json, type: :boolean, default: false, desc: 'Output as JSON'
|
|
13
|
+
class_option :no_color, type: :boolean, default: false, desc: 'Disable color output'
|
|
14
|
+
class_option :verbose, type: :boolean, default: false, aliases: ['-V'], desc: 'Verbose logging'
|
|
15
|
+
class_option :config_dir, type: :string, desc: 'Config directory path'
|
|
16
|
+
|
|
17
|
+
desc 'list', 'List all datasets'
|
|
18
|
+
def list
|
|
19
|
+
out = formatter
|
|
20
|
+
with_dataset_client do |client|
|
|
21
|
+
datasets = client.list_datasets
|
|
22
|
+
if options[:json]
|
|
23
|
+
out.json(datasets)
|
|
24
|
+
elsif datasets.empty?
|
|
25
|
+
out.warn('No datasets found')
|
|
26
|
+
else
|
|
27
|
+
rows = datasets.map do |d|
|
|
28
|
+
[d[:name].to_s, (d[:description] || '').to_s,
|
|
29
|
+
(d[:latest_version] || '-').to_s, (d[:row_count] || 0).to_s]
|
|
30
|
+
end
|
|
31
|
+
out.table(%w[name description version row_count], rows)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
default_task :list
|
|
36
|
+
|
|
37
|
+
desc 'show NAME', 'Show dataset info and first 10 rows'
|
|
38
|
+
option :version, type: :numeric, desc: 'Specific version number'
|
|
39
|
+
def show(name)
|
|
40
|
+
out = formatter
|
|
41
|
+
with_dataset_client do |client|
|
|
42
|
+
kwargs = { name: name }
|
|
43
|
+
kwargs[:version] = options[:version] if options[:version]
|
|
44
|
+
result = client.get_dataset(**kwargs)
|
|
45
|
+
if result[:error]
|
|
46
|
+
out.error("Dataset '#{name}': #{result[:error]}")
|
|
47
|
+
raise SystemExit, 1
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if options[:json]
|
|
51
|
+
out.json(result)
|
|
52
|
+
else
|
|
53
|
+
out.header("Dataset: #{result[:name]}")
|
|
54
|
+
out.spacer
|
|
55
|
+
out.detail({ version: result[:version], row_count: result[:row_count] })
|
|
56
|
+
out.spacer
|
|
57
|
+
preview = (result[:rows] || []).first(10)
|
|
58
|
+
if preview.empty?
|
|
59
|
+
out.warn('No rows in this dataset version')
|
|
60
|
+
else
|
|
61
|
+
rows = preview.map do |r|
|
|
62
|
+
[r[:row_index].to_s, r[:input].to_s.slice(0, 60), (r[:expected_output] || '').to_s.slice(0, 60)]
|
|
63
|
+
end
|
|
64
|
+
out.table(%w[index input expected_output], rows)
|
|
65
|
+
remaining = result[:row_count].to_i - preview.size
|
|
66
|
+
out.warn("... #{remaining} more rows not shown") if remaining.positive?
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
desc 'import NAME PATH', 'Import a dataset from a file'
|
|
73
|
+
option :format, type: :string, default: 'json', enum: %w[json csv jsonl], desc: 'File format'
|
|
74
|
+
option :description, type: :string, desc: 'Dataset description'
|
|
75
|
+
def import(name, path)
|
|
76
|
+
out = formatter
|
|
77
|
+
with_dataset_client do |client|
|
|
78
|
+
unless File.exist?(path)
|
|
79
|
+
out.error("File not found: #{path}")
|
|
80
|
+
raise SystemExit, 1
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
result = client.import_dataset(
|
|
84
|
+
name: name,
|
|
85
|
+
path: path,
|
|
86
|
+
format: options[:format],
|
|
87
|
+
description: options[:description]
|
|
88
|
+
)
|
|
89
|
+
if options[:json]
|
|
90
|
+
out.json(result)
|
|
91
|
+
else
|
|
92
|
+
out.success("Imported '#{result[:name]}' v#{result[:version]} (#{result[:row_count]} rows)")
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
desc 'export NAME PATH', 'Export a dataset to a file'
|
|
98
|
+
option :format, type: :string, default: 'json', enum: %w[json csv jsonl], desc: 'File format'
|
|
99
|
+
option :version, type: :numeric, desc: 'Version to export'
|
|
100
|
+
def export(name, path)
|
|
101
|
+
out = formatter
|
|
102
|
+
with_dataset_client do |client|
|
|
103
|
+
kwargs = { name: name, path: path, format: options[:format] }
|
|
104
|
+
kwargs[:version] = options[:version] if options[:version]
|
|
105
|
+
result = client.export_dataset(**kwargs)
|
|
106
|
+
if options[:json]
|
|
107
|
+
out.json(result)
|
|
108
|
+
else
|
|
109
|
+
out.success("Exported #{result[:row_count]} rows to #{result[:path]}")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
no_commands do
|
|
115
|
+
def formatter
|
|
116
|
+
@formatter ||= Output::Formatter.new(
|
|
117
|
+
json: options[:json],
|
|
118
|
+
color: !options[:no_color]
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def with_dataset_client
|
|
123
|
+
Connection.config_dir = options[:config_dir] if options[:config_dir]
|
|
124
|
+
Connection.log_level = options[:verbose] ? 'debug' : 'error'
|
|
125
|
+
Connection.ensure_data
|
|
126
|
+
|
|
127
|
+
begin
|
|
128
|
+
require 'legion/extensions/dataset'
|
|
129
|
+
require 'legion/extensions/dataset/runners/dataset'
|
|
130
|
+
require 'legion/extensions/dataset/client'
|
|
131
|
+
rescue LoadError
|
|
132
|
+
formatter.error('lex-dataset gem is not installed (gem install lex-dataset)')
|
|
133
|
+
raise SystemExit, 1
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
db = Legion::Data.db
|
|
137
|
+
client = Legion::Extensions::Dataset::Client.new(db: db)
|
|
138
|
+
yield client
|
|
139
|
+
rescue CLI::Error => e
|
|
140
|
+
formatter.error(e.message)
|
|
141
|
+
raise SystemExit, 1
|
|
142
|
+
ensure
|
|
143
|
+
Connection.shutdown
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module CLI
|
|
7
|
+
class Prompt < Thor
|
|
8
|
+
def self.exit_on_failure?
|
|
9
|
+
true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class_option :json, type: :boolean, default: false, desc: 'Output as JSON'
|
|
13
|
+
class_option :no_color, type: :boolean, default: false, desc: 'Disable color output'
|
|
14
|
+
class_option :verbose, type: :boolean, default: false, aliases: ['-V'], desc: 'Verbose logging'
|
|
15
|
+
class_option :config_dir, type: :string, desc: 'Config directory path'
|
|
16
|
+
|
|
17
|
+
desc 'list', 'List all prompts'
|
|
18
|
+
def list
|
|
19
|
+
out = formatter
|
|
20
|
+
with_prompt_client do |client|
|
|
21
|
+
prompts = client.list_prompts
|
|
22
|
+
if options[:json]
|
|
23
|
+
out.json(prompts)
|
|
24
|
+
elsif prompts.empty?
|
|
25
|
+
out.warn('No prompts found')
|
|
26
|
+
else
|
|
27
|
+
rows = prompts.map do |p|
|
|
28
|
+
[p[:name].to_s, (p[:description] || '').to_s,
|
|
29
|
+
(p[:latest_version] || '-').to_s, (p[:updated_at] || '-').to_s]
|
|
30
|
+
end
|
|
31
|
+
out.table(%w[name description version updated_at], rows)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
default_task :list
|
|
36
|
+
|
|
37
|
+
desc 'show NAME', 'Show a prompt template and parameters'
|
|
38
|
+
option :version, type: :numeric, desc: 'Specific version number'
|
|
39
|
+
option :tag, type: :string, desc: 'Tag name to resolve'
|
|
40
|
+
def show(name)
|
|
41
|
+
out = formatter
|
|
42
|
+
with_prompt_client do |client|
|
|
43
|
+
kwargs = { name: name }
|
|
44
|
+
kwargs[:version] = options[:version] if options[:version]
|
|
45
|
+
kwargs[:tag] = options[:tag] if options[:tag]
|
|
46
|
+
result = client.get_prompt(**kwargs)
|
|
47
|
+
if result[:error]
|
|
48
|
+
out.error("Prompt '#{name}': #{result[:error]}")
|
|
49
|
+
raise SystemExit, 1
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
if options[:json]
|
|
53
|
+
out.json(result)
|
|
54
|
+
else
|
|
55
|
+
out.header("Prompt: #{result[:name]}")
|
|
56
|
+
out.spacer
|
|
57
|
+
out.detail({ version: result[:version], content_hash: result[:content_hash],
|
|
58
|
+
created_at: result[:created_at] })
|
|
59
|
+
unless result[:model_params].nil? || result[:model_params].empty?
|
|
60
|
+
out.spacer
|
|
61
|
+
out.header('Model Params')
|
|
62
|
+
out.detail(result[:model_params])
|
|
63
|
+
end
|
|
64
|
+
out.spacer
|
|
65
|
+
puts result[:template]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
desc 'create NAME', 'Create a new prompt'
|
|
71
|
+
option :template, type: :string, required: true, desc: 'Prompt template text'
|
|
72
|
+
option :description, type: :string, desc: 'Short description'
|
|
73
|
+
option :model_params, type: :string, desc: 'Model parameters as JSON'
|
|
74
|
+
def create(name)
|
|
75
|
+
out = formatter
|
|
76
|
+
with_prompt_client do |client|
|
|
77
|
+
params = parse_model_params(options[:model_params], out)
|
|
78
|
+
return if params.nil?
|
|
79
|
+
|
|
80
|
+
result = client.create_prompt(
|
|
81
|
+
name: name,
|
|
82
|
+
template: options[:template],
|
|
83
|
+
description: options[:description],
|
|
84
|
+
model_params: params
|
|
85
|
+
)
|
|
86
|
+
if options[:json]
|
|
87
|
+
out.json(result)
|
|
88
|
+
else
|
|
89
|
+
out.success("Created prompt '#{result[:name]}' (version #{result[:version]})")
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
desc 'tag NAME TAG', 'Tag a prompt version'
|
|
95
|
+
option :version, type: :numeric, desc: 'Version to tag (defaults to latest)'
|
|
96
|
+
def tag(name, tag_name)
|
|
97
|
+
out = formatter
|
|
98
|
+
with_prompt_client do |client|
|
|
99
|
+
kwargs = { name: name, tag: tag_name }
|
|
100
|
+
kwargs[:version] = options[:version] if options[:version]
|
|
101
|
+
result = client.tag_prompt(**kwargs)
|
|
102
|
+
if result[:error]
|
|
103
|
+
out.error("Prompt '#{name}': #{result[:error]}")
|
|
104
|
+
raise SystemExit, 1
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
if options[:json]
|
|
108
|
+
out.json(result)
|
|
109
|
+
else
|
|
110
|
+
out.success("Tagged '#{result[:name]}' v#{result[:version]} as '#{result[:tag]}'")
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
desc 'diff NAME V1 V2', 'Show text diff between two versions of a prompt'
|
|
116
|
+
def diff(name, ver1, ver2)
|
|
117
|
+
out = formatter
|
|
118
|
+
with_prompt_client do |client|
|
|
119
|
+
r1 = client.get_prompt(name: name, version: ver1.to_i)
|
|
120
|
+
r2 = client.get_prompt(name: name, version: ver2.to_i)
|
|
121
|
+
|
|
122
|
+
if r1[:error]
|
|
123
|
+
out.error("Version #{ver1}: #{r1[:error]}")
|
|
124
|
+
raise SystemExit, 1
|
|
125
|
+
end
|
|
126
|
+
if r2[:error]
|
|
127
|
+
out.error("Version #{ver2}: #{r2[:error]}")
|
|
128
|
+
raise SystemExit, 1
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
if options[:json]
|
|
132
|
+
out.json({ name: name, v1: ver1.to_i, v2: ver2.to_i,
|
|
133
|
+
template_v1: r1[:template], template_v2: r2[:template] })
|
|
134
|
+
else
|
|
135
|
+
require 'diff/lcs' if defined?(Diff::LCS)
|
|
136
|
+
puts "--- v#{ver1}"
|
|
137
|
+
puts "+++ v#{ver2}"
|
|
138
|
+
puts diff_lines(r1[:template].to_s, r2[:template].to_s)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
no_commands do
|
|
144
|
+
def formatter
|
|
145
|
+
@formatter ||= Output::Formatter.new(
|
|
146
|
+
json: options[:json],
|
|
147
|
+
color: !options[:no_color]
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def with_prompt_client
|
|
152
|
+
Connection.config_dir = options[:config_dir] if options[:config_dir]
|
|
153
|
+
Connection.log_level = options[:verbose] ? 'debug' : 'error'
|
|
154
|
+
Connection.ensure_data
|
|
155
|
+
|
|
156
|
+
begin
|
|
157
|
+
require 'legion/extensions/prompt'
|
|
158
|
+
require 'legion/extensions/prompt/runners/prompt'
|
|
159
|
+
require 'legion/extensions/prompt/client'
|
|
160
|
+
rescue LoadError
|
|
161
|
+
formatter.error('lex-prompt gem is not installed (gem install lex-prompt)')
|
|
162
|
+
raise SystemExit, 1
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
db = Legion::Data.db
|
|
166
|
+
client = Legion::Extensions::Prompt::Client.new(db: db)
|
|
167
|
+
yield client
|
|
168
|
+
rescue CLI::Error => e
|
|
169
|
+
formatter.error(e.message)
|
|
170
|
+
raise SystemExit, 1
|
|
171
|
+
ensure
|
|
172
|
+
Connection.shutdown
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def parse_model_params(raw, out)
|
|
176
|
+
return {} if raw.nil? || raw.empty?
|
|
177
|
+
|
|
178
|
+
::JSON.parse(raw)
|
|
179
|
+
rescue ::JSON::ParserError => e
|
|
180
|
+
out.error("Invalid JSON for --model-params: #{e.message}")
|
|
181
|
+
nil
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def diff_lines(old_text, new_text)
|
|
185
|
+
old_lines = old_text.split("\n")
|
|
186
|
+
new_lines = new_text.split("\n")
|
|
187
|
+
result = []
|
|
188
|
+
old_set = old_lines.to_set
|
|
189
|
+
new_set = new_lines.to_set
|
|
190
|
+
old_lines.each { |l| result << "- #{l}" unless new_set.include?(l) }
|
|
191
|
+
new_lines.each { |l| result << "+ #{l}" unless old_set.include?(l) }
|
|
192
|
+
result.join("\n")
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
data/lib/legion/cli.rb
CHANGED
|
@@ -41,6 +41,8 @@ module Legion
|
|
|
41
41
|
autoload :Update, 'legion/cli/update_command'
|
|
42
42
|
autoload :Init, 'legion/cli/init_command'
|
|
43
43
|
autoload :Skill, 'legion/cli/skill_command'
|
|
44
|
+
autoload :Prompt, 'legion/cli/prompt_command'
|
|
45
|
+
autoload :Dataset, 'legion/cli/dataset_command'
|
|
44
46
|
autoload :Cost, 'legion/cli/cost_command'
|
|
45
47
|
autoload :Marketplace, 'legion/cli/marketplace_command'
|
|
46
48
|
autoload :Notebook, 'legion/cli/notebook_command'
|
|
@@ -238,6 +240,12 @@ module Legion
|
|
|
238
240
|
desc 'skill', 'Manage skills (.legion/skills/ markdown files)'
|
|
239
241
|
subcommand 'skill', Legion::CLI::Skill
|
|
240
242
|
|
|
243
|
+
desc 'prompt SUBCOMMAND', 'Manage versioned LLM prompt templates'
|
|
244
|
+
subcommand 'prompt', Legion::CLI::Prompt
|
|
245
|
+
|
|
246
|
+
desc 'dataset SUBCOMMAND', 'Manage versioned datasets'
|
|
247
|
+
subcommand 'dataset', Legion::CLI::Dataset
|
|
248
|
+
|
|
241
249
|
desc 'cost', 'Cost visibility and reporting'
|
|
242
250
|
subcommand 'cost', Legion::CLI::Cost
|
|
243
251
|
|
data/lib/legion/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: legionio
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.4.
|
|
4
|
+
version: 1.4.93
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -450,6 +450,7 @@ files:
|
|
|
450
450
|
- lib/legion/cli/dashboard/data_fetcher.rb
|
|
451
451
|
- lib/legion/cli/dashboard/renderer.rb
|
|
452
452
|
- lib/legion/cli/dashboard_command.rb
|
|
453
|
+
- lib/legion/cli/dataset_command.rb
|
|
453
454
|
- lib/legion/cli/detect_command.rb
|
|
454
455
|
- lib/legion/cli/doctor/bundle_check.rb
|
|
455
456
|
- lib/legion/cli/doctor/cache_check.rb
|
|
@@ -533,6 +534,7 @@ files:
|
|
|
533
534
|
- lib/legion/cli/payroll_command.rb
|
|
534
535
|
- lib/legion/cli/plan_command.rb
|
|
535
536
|
- lib/legion/cli/pr_command.rb
|
|
537
|
+
- lib/legion/cli/prompt_command.rb
|
|
536
538
|
- lib/legion/cli/rbac_command.rb
|
|
537
539
|
- lib/legion/cli/relationship.rb
|
|
538
540
|
- lib/legion/cli/review_command.rb
|