55 echo "some text" | shieldprompt mask
66 shieldprompt unmask "[EMAIL_ADDRESS_1]" --vault vault.json
77 shieldprompt mask --file input.txt --save-vault vault.json
8+ shieldprompt mask --file secrets.txt --in-place --no-ner
9+ shieldprompt unmask --file secrets.txt --in-place
10+ shieldprompt map --file secrets.txt
811"""
912
1013from __future__ import annotations
@@ -24,7 +27,6 @@ def _load_vault(path: str) -> Vault:
2427 vault = Vault ()
2528 data = json .loads (Path (path ).read_text ())
2629 for token , real in data .items ():
27- entity_type = token .split ("_" )[0 ].lstrip ("[" )
2830 vault ._token_to_real [token ] = real
2931 vault ._real_to_token [real ] = token
3032 return vault
@@ -35,6 +37,21 @@ def _save_vault(vault: Vault, path: str) -> None:
3537 Path (path ).write_text (json .dumps (vault .mappings , indent = 2 ))
3638
3739
40+ def _default_vault_path (file_path : str ) -> str :
41+ """Default sidecar vault path for a source file."""
42+ return f"{ file_path } .shieldprompt.vault.json"
43+
44+
45+ def _resolve_vault_path (file_path : str | None , explicit_path : str | None ) -> str :
46+ """Resolve vault path from an explicit argument or a file sidecar."""
47+ if explicit_path :
48+ return explicit_path
49+ if file_path :
50+ return _default_vault_path (file_path )
51+ print ("Error: provide --vault when not using --file" , file = sys .stderr )
52+ sys .exit (1 )
53+
54+
3855def main () -> None :
3956 parser = argparse .ArgumentParser (
4057 prog = "shieldprompt" ,
@@ -53,14 +70,24 @@ def main() -> None:
5370 mask_p .add_argument (
5471 "--save-vault" , "-s" , help = "Save vault mappings to JSON file"
5572 )
73+ mask_p .add_argument (
74+ "--in-place" ,
75+ "-i" ,
76+ action = "store_true" ,
77+ help = "When used with --file, overwrite the file with masked text" ,
78+ )
5679 mask_p .add_argument ("--no-ner" , action = "store_true" , help = "Disable NER model" )
5780
5881 # --- unmask ---
5982 unmask_p = sub .add_parser ("unmask" , help = "Unmask tokens back to real values" )
6083 unmask_p .add_argument ("text" , nargs = "?" , help = "Text to unmask (or pipe via stdin)" )
6184 unmask_p .add_argument ("--file" , "-f" , help = "Read text from file" )
85+ unmask_p .add_argument ("--vault" , "-v" , help = "Path to vault JSON file" )
6286 unmask_p .add_argument (
63- "--vault" , "-v" , required = True , help = "Path to vault JSON file"
87+ "--in-place" ,
88+ "-i" ,
89+ action = "store_true" ,
90+ help = "When used with --file, overwrite the file with unmasked text" ,
6491 )
6592
6693 # --- inspect ---
@@ -69,6 +96,15 @@ def main() -> None:
6996 inspect_p .add_argument ("--file" , "-f" , help = "Read text from file" )
7097 inspect_p .add_argument ("--no-ner" , action = "store_true" , help = "Disable NER model" )
7198
99+ # --- map ---
100+ map_p = sub .add_parser (
101+ "map" ,
102+ help = "Show token->original mappings from a vault file" ,
103+ )
104+ map_p .add_argument ("--file" , "-f" , help = "Original file path (uses default sidecar vault)" )
105+ map_p .add_argument ("--vault" , "-v" , help = "Path to vault JSON file" )
106+ map_p .add_argument ("--json" , action = "store_true" , help = "Print mappings as JSON" )
107+
72108 args = parser .parse_args ()
73109
74110 if args .command == "mask" :
@@ -77,6 +113,8 @@ def main() -> None:
77113 _cmd_unmask (args )
78114 elif args .command == "inspect" :
79115 _cmd_inspect (args )
116+ elif args .command == "map" :
117+ _cmd_map (args )
80118
81119
82120def _get_text (args ) -> str :
@@ -92,6 +130,10 @@ def _get_text(args) -> str:
92130
93131
94132def _cmd_mask (args ) -> None :
133+ if args .in_place and not args .file :
134+ print ("Error: --in-place requires --file" , file = sys .stderr )
135+ sys .exit (1 )
136+
95137 text = _get_text (args )
96138 entities = {EntityType (e ) for e in args .entities } if args .entities else None
97139 vault = Vault ()
@@ -101,17 +143,35 @@ def _cmd_mask(args) -> None:
101143 vault = vault ,
102144 )
103145 masked = shield .mask (text )
104- print (masked )
146+ if args .in_place :
147+ Path (args .file ).write_text (masked )
148+ else :
149+ print (masked )
105150
106- if args .save_vault :
107- _save_vault (vault , args .save_vault )
108- print (f"Vault saved to { args .save_vault } " , file = sys .stderr )
151+ should_save_vault = bool (args .save_vault or args .in_place )
152+ if should_save_vault :
153+ vault_path = args .save_vault or _default_vault_path (args .file )
154+ _save_vault (vault , vault_path )
155+ if args .in_place :
156+ print (f"Masked file in-place: { args .file } " , file = sys .stderr )
157+ print (f"Vault saved to { vault_path } " , file = sys .stderr )
109158
110159
111160def _cmd_unmask (args ) -> None :
161+ if args .in_place and not args .file :
162+ print ("Error: --in-place requires --file" , file = sys .stderr )
163+ sys .exit (1 )
164+
112165 text = _get_text (args )
113- vault = _load_vault (args .vault )
114- print (vault .restore_text (text ))
166+ vault_path = _resolve_vault_path (args .file , args .vault )
167+ vault = _load_vault (vault_path )
168+ unmasked = vault .restore_text (text )
169+
170+ if args .in_place :
171+ Path (args .file ).write_text (unmasked )
172+ print (f"Unmasked file in-place: { args .file } " , file = sys .stderr )
173+ else :
174+ print (unmasked )
115175
116176
117177def _cmd_inspect (args ) -> None :
@@ -127,5 +187,18 @@ def _cmd_inspect(args) -> None:
127187 print (f" { d .entity_type :20s} { d .value !r:30s} (pos { d .start } -{ d .end } , score={ d .score :.2f} )" )
128188
129189
190+ def _cmd_map (args ) -> None :
191+ vault_path = _resolve_vault_path (args .file , args .vault )
192+ mappings = _load_vault (vault_path ).mappings
193+ if args .json :
194+ print (json .dumps (mappings , indent = 2 ))
195+ return
196+ if not mappings :
197+ print ("No mappings found." )
198+ return
199+ for token , real in mappings .items ():
200+ print (f"{ token } -> { real } " )
201+
202+
130203if __name__ == "__main__" :
131204 main ()
0 commit comments