@veilio-inc/cli 0.1.0
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 +248 -0
- package/README.md +121 -0
- package/dist/args.d.ts +30 -0
- package/dist/args.d.ts.map +1 -0
- package/dist/args.js +108 -0
- package/dist/args.js.map +1 -0
- package/dist/commands.d.ts +19 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +216 -0
- package/dist/commands.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +101 -0
- package/dist/index.js.map +1 -0
- package/dist/store.d.ts +41 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +113 -0
- package/dist/store.js.map +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# Veilio Community License
|
|
2
|
+
|
|
3
|
+
_Version 1.0 · Effective 2026-08-08_
|
|
4
|
+
|
|
5
|
+
**Copyright © 2026 Veilio**
|
|
6
|
+
|
|
7
|
+
> **Plain-language summary (non-binding; the numbered terms below control).**
|
|
8
|
+
> Use Veilio for anything you like — personally, or inside your company, including
|
|
9
|
+
> to make money. Read it, fork it, change it, self-host it, ship products with it.
|
|
10
|
+
> The one thing you may not do without written permission is sell Veilio itself:
|
|
11
|
+
> reselling it, white-labeling it, or running it as a hosted service for other
|
|
12
|
+
> people. Contributions are welcome. For a commercial or redistribution license,
|
|
13
|
+
> email `hello@veilio.dev`.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. Project Model
|
|
18
|
+
|
|
19
|
+
This project follows a **source-available and community-driven development model** with a separate commercial Cloud Edition. The source is public and contributions are welcome, but this is deliberately **not an OSI-approved open-source license**: the commercial restrictions in Section 5 go beyond what that definition permits.
|
|
20
|
+
|
|
21
|
+
The **Community Core** is publicly available and intended to be freely used, inspected, modified, forked, and improved by the community. Contributions from the community are welcome and are an important part of the project's development.
|
|
22
|
+
|
|
23
|
+
The **Cloud Edition** is a separate commercial offering operated by Veilio. It provides hosted and managed functionality and may include additional proprietary features, infrastructure, services, and support.
|
|
24
|
+
|
|
25
|
+
The Software may be used for personal purposes as well as for internal commercial purposes. Organizations may deploy and use the Software within their own businesses without requiring a commercial license.
|
|
26
|
+
|
|
27
|
+
However, the Software may not be commercially redistributed, resold, white-labeled, or offered as a third-party hosted service without prior written permission from Veilio.
|
|
28
|
+
|
|
29
|
+
The purpose of this model is to keep the core technology open and community-driven while allowing the project owner to maintain and commercially operate the official Cloud Edition.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 2. Definitions
|
|
34
|
+
|
|
35
|
+
For the purposes of this License:
|
|
36
|
+
|
|
37
|
+
**"Software"** means the source code, binaries, documentation, configuration, and other materials distributed as part of this project.
|
|
38
|
+
|
|
39
|
+
**"Community Core"** means the publicly available portion of the Software developed and distributed under this License.
|
|
40
|
+
|
|
41
|
+
**"Modified Version"** means any work based on, derived from, or incorporating substantial portions of the Software.
|
|
42
|
+
|
|
43
|
+
**"Cloud Edition"** means the hosted, managed, or subscription-based version of the Software operated and commercially provided by Veilio, including any proprietary services, infrastructure, features, or components associated with it.
|
|
44
|
+
|
|
45
|
+
**"Commercial Distribution"** means selling, licensing, renting, leasing, sublicensing, or otherwise providing the Software or a Modified Version to third parties in exchange for payment or other commercial consideration.
|
|
46
|
+
|
|
47
|
+
**"Hosted Service"** means making the Software or a Modified Version available to third parties over a network, including as a SaaS, PaaS, managed service, hosted application, or similar commercial offering.
|
|
48
|
+
|
|
49
|
+
**"Competing Product"** means a commercially offered product or service whose primary purpose and core functionality substantially overlap with the Software or Cloud Edition.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 3. Permissions
|
|
54
|
+
|
|
55
|
+
Subject to the restrictions in this License, you are granted a worldwide, royalty-free, non-exclusive license to:
|
|
56
|
+
|
|
57
|
+
* use the Software for personal purposes;
|
|
58
|
+
* use the Software for internal commercial purposes;
|
|
59
|
+
* inspect and study the source code;
|
|
60
|
+
* modify the Software;
|
|
61
|
+
* create forks and derivative works;
|
|
62
|
+
* submit contributions, patches, bug fixes, and improvements to the project;
|
|
63
|
+
* use modified versions for personal purposes or internal commercial operations;
|
|
64
|
+
* share the Software and Modified Versions for non-commercial purposes.
|
|
65
|
+
|
|
66
|
+
These permissions are intended to enable community development, experimentation, self-hosting, internal business use, and collaboration.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 4. Internal Commercial Use
|
|
71
|
+
|
|
72
|
+
Commercial use of the Software is permitted when the Software is used as an internal tool or component of your own business activities.
|
|
73
|
+
|
|
74
|
+
For example, an organization may deploy the Software on its own infrastructure and use it to:
|
|
75
|
+
|
|
76
|
+
* operate internal business processes;
|
|
77
|
+
* support its employees;
|
|
78
|
+
* develop its own products;
|
|
79
|
+
* provide its own services;
|
|
80
|
+
* manage its own operations; or
|
|
81
|
+
* otherwise conduct its own commercial activities.
|
|
82
|
+
|
|
83
|
+
The fact that an organization generates revenue through its business does not, by itself, constitute a violation of this License.
|
|
84
|
+
|
|
85
|
+
The restriction applies to **commercializing the Software itself**, rather than using the Software as part of your own business.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 5. Commercial Restrictions
|
|
90
|
+
|
|
91
|
+
Without prior written permission from Veilio, you may not:
|
|
92
|
+
|
|
93
|
+
### 5.1 Sell or Resell the Software
|
|
94
|
+
|
|
95
|
+
Sell, license, rent, lease, sublicense, or otherwise commercially distribute the Software or a Modified Version as a standalone product.
|
|
96
|
+
|
|
97
|
+
### 5.2 Offer the Software as a Hosted Service
|
|
98
|
+
|
|
99
|
+
Operate the Software or a Modified Version as a commercial SaaS, PaaS, hosted application, managed service, or similar service for third parties.
|
|
100
|
+
|
|
101
|
+
### 5.3 Create a Competing Commercial Product
|
|
102
|
+
|
|
103
|
+
Use the Software or a Modified Version as the basis for a commercially offered product or service that substantially competes with the Cloud Edition.
|
|
104
|
+
|
|
105
|
+
### 5.4 White-Label or Rebrand the Software
|
|
106
|
+
|
|
107
|
+
Repackage, white-label, rebrand, or otherwise present the Software or a Modified Version as your own commercial software product.
|
|
108
|
+
|
|
109
|
+
### 5.5 Commercially Redistribute Modified Versions
|
|
110
|
+
|
|
111
|
+
Commercially distribute a Modified Version where the primary value or functionality being sold is provided by the Software.
|
|
112
|
+
|
|
113
|
+
Requests for written permission may be sent to `hello@veilio.dev`.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 6. Cloud Edition
|
|
118
|
+
|
|
119
|
+
The Cloud Edition is the official commercial hosted offering of the project.
|
|
120
|
+
|
|
121
|
+
The Cloud Edition may be provided through paid subscriptions and may include:
|
|
122
|
+
|
|
123
|
+
* managed hosting;
|
|
124
|
+
* automatic updates;
|
|
125
|
+
* backups;
|
|
126
|
+
* infrastructure and scalability;
|
|
127
|
+
* monitoring;
|
|
128
|
+
* enterprise functionality;
|
|
129
|
+
* technical support;
|
|
130
|
+
* proprietary features;
|
|
131
|
+
* other hosted or managed services.
|
|
132
|
+
|
|
133
|
+
The Community Core and Cloud Edition may share common code, concepts, or functionality. However, access to the Community Core does not grant any right to commercially operate a competing hosted version of the Cloud Edition.
|
|
134
|
+
|
|
135
|
+
The Cloud Edition may be governed by separate Terms of Service, subscription agreements, privacy policies, and commercial licenses.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## 7. Forks and Modified Versions
|
|
140
|
+
|
|
141
|
+
You are permitted to create forks and Modified Versions of the Software for:
|
|
142
|
+
|
|
143
|
+
* personal use;
|
|
144
|
+
* internal business use;
|
|
145
|
+
* research;
|
|
146
|
+
* education;
|
|
147
|
+
* development;
|
|
148
|
+
* experimentation; and
|
|
149
|
+
* other purposes permitted by this License.
|
|
150
|
+
|
|
151
|
+
Forks and Modified Versions must retain applicable copyright notices and a copy of this License.
|
|
152
|
+
|
|
153
|
+
Creating a fork does not grant permission to commercially distribute that fork, offer it as a commercial Hosted Service, or use it to create a Competing Product.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 8. Contributions
|
|
158
|
+
|
|
159
|
+
Contributions to the Community Core are welcome.
|
|
160
|
+
|
|
161
|
+
By submitting a contribution, you represent that you have the necessary rights to submit that contribution and agree that it may be incorporated into the project and distributed under this License.
|
|
162
|
+
|
|
163
|
+
Unless otherwise agreed in writing, contributions will be made available under the same licensing terms as the Software.
|
|
164
|
+
|
|
165
|
+
Contributors retain ownership of their original contributions to the extent permitted by applicable law.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 9. Redistribution
|
|
170
|
+
|
|
171
|
+
You may redistribute the Software and Modified Versions for non-commercial purposes, provided that:
|
|
172
|
+
|
|
173
|
+
* applicable copyright notices are retained;
|
|
174
|
+
* the original project is appropriately credited;
|
|
175
|
+
* a copy of this License is included;
|
|
176
|
+
* recipients are informed of the applicable licensing terms; and
|
|
177
|
+
* the redistribution does not imply endorsement by Veilio.
|
|
178
|
+
|
|
179
|
+
Commercial redistribution requires prior written permission from Veilio.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 10. Attribution
|
|
184
|
+
|
|
185
|
+
Redistributions of the Software or Modified Versions must retain:
|
|
186
|
+
|
|
187
|
+
* applicable copyright notices;
|
|
188
|
+
* notices identifying the original project; and
|
|
189
|
+
* a copy of this License.
|
|
190
|
+
|
|
191
|
+
You may add your own copyright notices for your modifications, provided that they do not imply ownership of the original Software.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## 11. Trademarks
|
|
196
|
+
|
|
197
|
+
This License does not grant permission to use the project's name, logo, trademarks, service marks, domain names, or branding except as reasonably necessary to identify the original Software.
|
|
198
|
+
|
|
199
|
+
You may not use the project's trademarks to imply endorsement, sponsorship, affiliation, or official status without prior written permission.
|
|
200
|
+
|
|
201
|
+
Creating a fork does not grant the right to use the project's trademarks for that fork.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 12. No Warranty
|
|
206
|
+
|
|
207
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
|
|
208
|
+
|
|
209
|
+
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE AUTHORS AND COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 13. Limitation of Liability
|
|
214
|
+
|
|
215
|
+
TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT SHALL THE AUTHORS, COPYRIGHT HOLDERS, CONTRIBUTORS, OR PROJECT OWNERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM OR RELATED TO THE SOFTWARE OR THE USE OR INABILITY TO USE THE SOFTWARE.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 14. Termination
|
|
220
|
+
|
|
221
|
+
Your rights under this License terminate automatically if you materially violate its terms.
|
|
222
|
+
|
|
223
|
+
Upon termination, you must cease the activities that are prohibited by this License.
|
|
224
|
+
|
|
225
|
+
Permissions granted to recipients who are not in violation of this License remain unaffected.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## 15. Reservation of Rights
|
|
230
|
+
|
|
231
|
+
All rights not expressly granted by this License are reserved by Veilio.
|
|
232
|
+
|
|
233
|
+
In particular, this License does not grant rights to commercially sell, redistribute, host, white-label, or operate a competing commercial service based on the Software beyond the permissions expressly stated above.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## 16. License Versions
|
|
238
|
+
|
|
239
|
+
The project owner may publish future versions of this License.
|
|
240
|
+
|
|
241
|
+
Each version will be identified by a version number. Unless explicitly stated otherwise, versions of the Software already distributed under a particular version of this License remain subject to that version.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
**Project:** Veilio
|
|
246
|
+
**Copyright:** © 2026 Veilio
|
|
247
|
+
**License:** Veilio Community License, Version 1.0
|
|
248
|
+
**Contact:** `hello@veilio.dev`
|
package/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# @veilio-inc/cli
|
|
2
|
+
|
|
3
|
+
Anonymize code before it reaches an LLM, restore it after — from a terminal, a pipe, or a pre-commit hook.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
veilio scrub src/billing.ts | pbcopy # mask, then paste anywhere
|
|
7
|
+
pbpaste | veilio restore # bring the answer back
|
|
8
|
+
git diff --cached | veilio scan # refuse to commit a live key
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @veilio-inc/cli # or run it without installing:
|
|
15
|
+
npx @veilio-inc/cli scrub src/billing.ts
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Requires Node 24 or newer. No account, no API key, and no network call on any of
|
|
19
|
+
the commands below — `packages/cli/tests/purity.test.ts` traps the network
|
|
20
|
+
globals and fails if one is ever introduced.
|
|
21
|
+
|
|
22
|
+
## Commands
|
|
23
|
+
|
|
24
|
+
| Command | Purpose |
|
|
25
|
+
| -------------------- | --------------------------------------------------------------------- |
|
|
26
|
+
| `scrub [files...]` | Mask identifiers, redact credentials. Reads stdin when given no file. |
|
|
27
|
+
| `restore [files...]` | Swap placeholders back, strip AI-generated noise. |
|
|
28
|
+
| `scan [files...]` | Detect credentials only. Never rewrites. Exits 1 on findings. |
|
|
29
|
+
| `map` | Show the symbol map (`--clear` to wipe it). |
|
|
30
|
+
|
|
31
|
+
## Options
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
-l, --language <lang> auto (default), typescript, python, go, java, csharp,
|
|
35
|
+
rust, ruby, php, c, sql
|
|
36
|
+
-s, --secrets <policy> redact (default) | warn | off
|
|
37
|
+
-p, --preamble Prepend the downstream-AI note and placeholder legend
|
|
38
|
+
-m, --map <path> Use a specific map file
|
|
39
|
+
--json Machine-readable output (scan, map)
|
|
40
|
+
--strict scan: also fail on advisory findings
|
|
41
|
+
-q, --quiet Suppress the stderr summary
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Piping
|
|
45
|
+
|
|
46
|
+
Transformed code goes to **stdout**; summaries, warnings and errors go to **stderr**. That split is what makes the CLI composable — the summary stays visible without corrupting the pipe.
|
|
47
|
+
|
|
48
|
+
## Exit codes
|
|
49
|
+
|
|
50
|
+
| Code | Meaning |
|
|
51
|
+
| ---- | -------------------------------------- |
|
|
52
|
+
| 0 | Clean |
|
|
53
|
+
| 1 | Findings that should stop the pipeline |
|
|
54
|
+
| 2 | Usage or IO error |
|
|
55
|
+
|
|
56
|
+
As a pre-commit hook:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
#!/bin/sh
|
|
60
|
+
git diff --cached | veilio scan || {
|
|
61
|
+
echo "Commit blocked: credentials detected." >&2
|
|
62
|
+
exit 1
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## The symbol map
|
|
67
|
+
|
|
68
|
+
`scrub` writes `.veilio/map.json` at the project root and `restore` reads it, so placeholders stay stable across runs and across a whole session. The store is created `0600` with its own `.gitignore` — it holds the real identifier names, so committing it would undo the anonymization for anyone reading the repo.
|
|
69
|
+
|
|
70
|
+
Redacted credentials are **not** in the map. `restore` cannot bring them back, by construction.
|
|
71
|
+
|
|
72
|
+
## In CI
|
|
73
|
+
|
|
74
|
+
### GitHub Action
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
- uses: veilio-inc/veilio/packages/cli@v0.1.0
|
|
78
|
+
with:
|
|
79
|
+
strict: false # also fail on emails / private IPs
|
|
80
|
+
sarif: veilio.sarif # optional: upload to code scanning
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**This example does not work yet, and needs two things that do not exist.** The
|
|
84
|
+
`@v0.1.0` ref is a git tag on this repository — the only tags here are
|
|
85
|
+
`engine-v*`, cut by semantic-release for the engine — and the Action's own script
|
|
86
|
+
runs `npx --yes @veilio-inc/cli@<version>`, which needs the package on npm.
|
|
87
|
+
Publishing supplies the second; the first is a tag someone has to cut, and note
|
|
88
|
+
that `v*.*.*` is also what triggers the Community Edition release workflow, so
|
|
89
|
+
the two are not independent.
|
|
90
|
+
|
|
91
|
+
Scans the **pull-request diff** by default, not the whole tree. A repo adopting
|
|
92
|
+
this mid-life almost always has a historical finding somewhere; blocking every PR
|
|
93
|
+
on that is how a security check gets switched off in week one. Pass `paths:` to
|
|
94
|
+
scan a fixed set instead.
|
|
95
|
+
|
|
96
|
+
Findings land in the job summary and, with `sarif:`, in GitHub code scanning.
|
|
97
|
+
Reports carry truncated previews only — a stored, shareable artifact must never
|
|
98
|
+
contain the credential it is reporting.
|
|
99
|
+
|
|
100
|
+
### Pre-commit
|
|
101
|
+
|
|
102
|
+
Via the [pre-commit framework](https://pre-commit.com):
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
repos:
|
|
106
|
+
- repo: https://github.com/veilio-inc/veilio
|
|
107
|
+
rev: v0.1.0
|
|
108
|
+
hooks:
|
|
109
|
+
- id: veilio-scan
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Or a plain git hook:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
cp packages/cli/hooks/pre-commit .git/hooks/pre-commit
|
|
116
|
+
chmod +x .git/hooks/pre-commit
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
It scans the **staged** diff — what you are about to commit is what matters, and
|
|
120
|
+
a finding in an unstaged scratch file shouldn't block you. `--no-verify`
|
|
121
|
+
overrides, as usual.
|
package/dist/args.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type LanguageOption } from '@veilio-inc/engine';
|
|
2
|
+
import type { SecretPolicy } from '@veilio-inc/engine';
|
|
3
|
+
export type Command = 'scrub' | 'restore' | 'scan' | 'map' | 'help' | 'version';
|
|
4
|
+
export interface ParsedArgs {
|
|
5
|
+
command: Command;
|
|
6
|
+
/** Positional file paths. Empty means read stdin (or, for `scan`, error). */
|
|
7
|
+
files: string[];
|
|
8
|
+
language: LanguageOption;
|
|
9
|
+
secrets: SecretPolicy;
|
|
10
|
+
/** Prepend the downstream-AI preamble and legend to scrub output. */
|
|
11
|
+
preamble: boolean;
|
|
12
|
+
/** Override the map store location. */
|
|
13
|
+
mapPath: string | null;
|
|
14
|
+
/** Machine-readable output (scan). */
|
|
15
|
+
json: boolean;
|
|
16
|
+
/** Suppress the human-readable summary on stderr. */
|
|
17
|
+
quiet: boolean;
|
|
18
|
+
/** `map --clear` wipes the store. */
|
|
19
|
+
clear: boolean;
|
|
20
|
+
/** Treat medium-severity findings as failures too (scan). */
|
|
21
|
+
strict: boolean;
|
|
22
|
+
/** Keep JSDoc blocks when restoring. */
|
|
23
|
+
keepDocs: boolean;
|
|
24
|
+
/** Allow a map write that would drop entries already on disk. */
|
|
25
|
+
force: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare class UsageError extends Error {
|
|
28
|
+
}
|
|
29
|
+
export declare function parseArgs(argv: readonly string[]): ParsedArgs;
|
|
30
|
+
//# sourceMappingURL=args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAIA,OAAO,EAA4B,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAClF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEtD,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,CAAA;AAE/E,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAA;IAChB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,QAAQ,EAAE,cAAc,CAAA;IACxB,OAAO,EAAE,YAAY,CAAA;IACrB,qEAAqE;IACrE,QAAQ,EAAE,OAAO,CAAA;IACjB,uCAAuC;IACvC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,sCAAsC;IACtC,IAAI,EAAE,OAAO,CAAA;IACb,qDAAqD;IACrD,KAAK,EAAE,OAAO,CAAA;IACd,qCAAqC;IACrC,KAAK,EAAE,OAAO,CAAA;IACd,6DAA6D;IAC7D,MAAM,EAAE,OAAO,CAAA;IACf,wCAAwC;IACxC,QAAQ,EAAE,OAAO,CAAA;IACjB,iEAAiE;IACjE,KAAK,EAAE,OAAO,CAAA;CACf;AAED,qBAAa,UAAW,SAAQ,KAAK;CAAG;AAaxC,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CAgG7D"}
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Hand-rolled argument parsing. The engine ships zero runtime dependencies as a
|
|
2
|
+
// privacy invariant (an audited supply chain is the point), and a CLI that
|
|
3
|
+
// wraps it should not undo that for the sake of flag parsing.
|
|
4
|
+
import { LANGUAGES } from '@veilio-inc/engine';
|
|
5
|
+
export class UsageError extends Error {
|
|
6
|
+
}
|
|
7
|
+
const COMMANDS = new Set(['scrub', 'restore', 'scan', 'map', 'help', 'version']);
|
|
8
|
+
const SECRET_POLICIES = new Set(['redact', 'warn', 'off']);
|
|
9
|
+
const LANGUAGE_VALUES = new Set([...LANGUAGES, 'auto']);
|
|
10
|
+
function requireValue(flag, value) {
|
|
11
|
+
if (value === undefined || value.startsWith('-')) {
|
|
12
|
+
throw new UsageError(`${flag} requires a value`);
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
export function parseArgs(argv) {
|
|
17
|
+
const parsed = {
|
|
18
|
+
command: 'help',
|
|
19
|
+
files: [],
|
|
20
|
+
language: 'auto',
|
|
21
|
+
secrets: 'redact',
|
|
22
|
+
preamble: false,
|
|
23
|
+
mapPath: null,
|
|
24
|
+
json: false,
|
|
25
|
+
quiet: false,
|
|
26
|
+
clear: false,
|
|
27
|
+
strict: false,
|
|
28
|
+
keepDocs: false,
|
|
29
|
+
force: false,
|
|
30
|
+
};
|
|
31
|
+
if (argv.length === 0)
|
|
32
|
+
return parsed;
|
|
33
|
+
let i = 0;
|
|
34
|
+
const first = argv[0];
|
|
35
|
+
if (!first.startsWith('-')) {
|
|
36
|
+
if (!COMMANDS.has(first)) {
|
|
37
|
+
throw new UsageError(`unknown command "${first}"`);
|
|
38
|
+
}
|
|
39
|
+
parsed.command = first;
|
|
40
|
+
i = 1;
|
|
41
|
+
}
|
|
42
|
+
for (; i < argv.length; i++) {
|
|
43
|
+
const arg = argv[i];
|
|
44
|
+
switch (arg) {
|
|
45
|
+
case '-h':
|
|
46
|
+
case '--help':
|
|
47
|
+
parsed.command = 'help';
|
|
48
|
+
break;
|
|
49
|
+
case '-v':
|
|
50
|
+
case '--version':
|
|
51
|
+
parsed.command = 'version';
|
|
52
|
+
break;
|
|
53
|
+
case '-l':
|
|
54
|
+
case '--language': {
|
|
55
|
+
const value = requireValue(arg, argv[++i]);
|
|
56
|
+
if (!LANGUAGE_VALUES.has(value)) {
|
|
57
|
+
throw new UsageError(`unknown language "${value}" — expected auto or one of: ${LANGUAGES.join(', ')}`);
|
|
58
|
+
}
|
|
59
|
+
parsed.language = value;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case '-s':
|
|
63
|
+
case '--secrets': {
|
|
64
|
+
const value = requireValue(arg, argv[++i]);
|
|
65
|
+
if (!SECRET_POLICIES.has(value)) {
|
|
66
|
+
throw new UsageError(`--secrets must be redact, warn, or off (got "${value}")`);
|
|
67
|
+
}
|
|
68
|
+
parsed.secrets = value;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case '-m':
|
|
72
|
+
case '--map':
|
|
73
|
+
parsed.mapPath = requireValue(arg, argv[++i]);
|
|
74
|
+
break;
|
|
75
|
+
case '-p':
|
|
76
|
+
case '--preamble':
|
|
77
|
+
parsed.preamble = true;
|
|
78
|
+
break;
|
|
79
|
+
case '--json':
|
|
80
|
+
parsed.json = true;
|
|
81
|
+
break;
|
|
82
|
+
case '-q':
|
|
83
|
+
case '--quiet':
|
|
84
|
+
parsed.quiet = true;
|
|
85
|
+
break;
|
|
86
|
+
case '--clear':
|
|
87
|
+
parsed.clear = true;
|
|
88
|
+
break;
|
|
89
|
+
case '--strict':
|
|
90
|
+
parsed.strict = true;
|
|
91
|
+
break;
|
|
92
|
+
case '--keep-docs':
|
|
93
|
+
parsed.keepDocs = true;
|
|
94
|
+
break;
|
|
95
|
+
case '-f':
|
|
96
|
+
case '--force':
|
|
97
|
+
parsed.force = true;
|
|
98
|
+
break;
|
|
99
|
+
default:
|
|
100
|
+
if (arg.startsWith('-') && arg !== '-') {
|
|
101
|
+
throw new UsageError(`unknown flag "${arg}"`);
|
|
102
|
+
}
|
|
103
|
+
parsed.files.push(arg);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return parsed;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=args.js.map
|
package/dist/args.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ParsedArgs } from './args.js';
|
|
2
|
+
export interface Io {
|
|
3
|
+
cwd: string;
|
|
4
|
+
stdin: () => Promise<string>;
|
|
5
|
+
stdout: (text: string) => void;
|
|
6
|
+
stderr: (text: string) => void;
|
|
7
|
+
}
|
|
8
|
+
/** Exit codes are the contract for CI and pre-commit hooks:
|
|
9
|
+
* 0 clean, 1 findings that should stop the pipeline, 2 usage/IO failure. */
|
|
10
|
+
export declare const EXIT_OK = 0;
|
|
11
|
+
export declare const EXIT_FINDINGS = 1;
|
|
12
|
+
export declare const EXIT_ERROR = 2;
|
|
13
|
+
export declare function runScrub(args: ParsedArgs, io: Io): Promise<number>;
|
|
14
|
+
export declare function runRestore(args: ParsedArgs, io: Io): Promise<number>;
|
|
15
|
+
/** Detect-only pass for pre-commit hooks and CI. Never rewrites anything. */
|
|
16
|
+
export declare function runScan(args: ParsedArgs, io: Io): Promise<number>;
|
|
17
|
+
export declare function runMap(args: ParsedArgs, io: Io): Promise<number>;
|
|
18
|
+
export declare const HELP = "veilio \u2014 anonymize code before it reaches an LLM, restore it after.\n\nUSAGE\n veilio <command> [files...] [options]\n\nCOMMANDS\n scrub [files...] Mask identifiers and redact credentials. Reads stdin when\n no file is given. Writes the symbol map to .veilio/map.json.\n restore [files...] Swap placeholders back and strip AI-generated noise.\n scan [files...] Detect credentials only; never rewrites. Exits 1 when it\n finds something \u2014 use it in a pre-commit hook or CI.\n map Show the current symbol map (--clear to wipe it).\n\nOPTIONS\n -l, --language <lang> auto (default), typescript, python, go, java, csharp,\n rust, ruby, php, c, sql\n -s, --secrets <policy> redact (default) | warn | off\n -p, --preamble Prepend the downstream-AI note and placeholder legend\n -m, --map <path> Use a specific map file\n --json Machine-readable output (scan, map)\n --strict scan: also fail on advisory findings\n --keep-docs restore: keep JSDoc blocks the model wrote\n -f, --force Allow a map write that would drop existing entries\n -q, --quiet Suppress the all-clear summary (findings always show)\n -h, --help Show this help\n -v, --version Show the version\n\nEXIT CODES\n 0 clean 1 findings that should stop the pipeline 2 usage or IO error\n\nEXAMPLES\n veilio scrub src/billing.ts | pbcopy\n pbpaste | veilio restore\n git diff --cached | veilio scan # pre-commit gate\n veilio scan 'src/**/*.ts' --json > findings.json\n\nTransformed code goes to stdout; everything else goes to stderr, so the CLI\ncomposes in a pipe.\n";
|
|
19
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAG3C,MAAM,WAAW,EAAE;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;IAC5B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/B;AAED;6EAC6E;AAC7E,eAAO,MAAM,OAAO,IAAI,CAAA;AACxB,eAAO,MAAM,aAAa,IAAI,CAAA;AAC9B,eAAO,MAAM,UAAU,IAAI,CAAA;AAsB3B,wBAAsB,QAAQ,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAyDxE;AAED,wBAAsB,UAAU,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAsD1E;AAED,6EAA6E;AAC7E,wBAAsB,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiCvE;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBtE;AAED,eAAO,MAAM,IAAI,svDAsChB,CAAA"}
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// Command implementations.
|
|
2
|
+
//
|
|
3
|
+
// Two rules hold everywhere here:
|
|
4
|
+
// 1. Transformed code goes to stdout and nothing else does, so the CLI can sit
|
|
5
|
+
// in a pipe (`veilio scrub src/billing.ts | pbcopy`).
|
|
6
|
+
// 2. Summaries, warnings and errors go to stderr, so they stay visible without
|
|
7
|
+
// corrupting that pipe.
|
|
8
|
+
import { readFileSync } from 'node:fs';
|
|
9
|
+
import { resolve } from 'node:path';
|
|
10
|
+
import { anonymize, detectSecrets, hasBlockingSecrets, restore, summarizeSecrets, withAiPreamble, LANGUAGE_LABELS, STRIPPABLE_TYPES, BIN_NAME, STORE_DIR, } from '@veilio-inc/engine';
|
|
11
|
+
import { clearMap, loadMap, resolveMapPath, saveMap } from './store.js';
|
|
12
|
+
/** Exit codes are the contract for CI and pre-commit hooks:
|
|
13
|
+
* 0 clean, 1 findings that should stop the pipeline, 2 usage/IO failure. */
|
|
14
|
+
export const EXIT_OK = 0;
|
|
15
|
+
export const EXIT_FINDINGS = 1;
|
|
16
|
+
export const EXIT_ERROR = 2;
|
|
17
|
+
async function readInput(args, io) {
|
|
18
|
+
if (args.files.length === 0)
|
|
19
|
+
return io.stdin();
|
|
20
|
+
return args.files.map((f) => readFileSync(resolve(io.cwd, f), 'utf8')).join('\n');
|
|
21
|
+
}
|
|
22
|
+
function formatFinding(f, file) {
|
|
23
|
+
const where = file ? `${file}:${f.line}:${f.column}` : `line ${f.line}:${f.column}`;
|
|
24
|
+
const state = f.redacted ? 'redacted' : 'left in place';
|
|
25
|
+
return ` ${f.severity.padEnd(8)} ${where} ${f.label} — ${f.preview} (${state})`;
|
|
26
|
+
}
|
|
27
|
+
function summaryLine(findings) {
|
|
28
|
+
const s = summarizeSecrets(findings);
|
|
29
|
+
const parts = [];
|
|
30
|
+
if (s.critical)
|
|
31
|
+
parts.push(`${s.critical} critical`);
|
|
32
|
+
if (s.high)
|
|
33
|
+
parts.push(`${s.high} high`);
|
|
34
|
+
if (s.medium)
|
|
35
|
+
parts.push(`${s.medium} advisory`);
|
|
36
|
+
return parts.join(', ');
|
|
37
|
+
}
|
|
38
|
+
export async function runScrub(args, io) {
|
|
39
|
+
const source = await readInput(args, io);
|
|
40
|
+
const mapPath = resolveMapPath(args.mapPath, io.cwd);
|
|
41
|
+
const existingMap = loadMap(mapPath);
|
|
42
|
+
const result = anonymize(source, {
|
|
43
|
+
existingMap,
|
|
44
|
+
language: args.language,
|
|
45
|
+
secrets: args.secrets,
|
|
46
|
+
});
|
|
47
|
+
saveMap(mapPath, result.map, { force: args.force });
|
|
48
|
+
io.stdout(args.preamble ? withAiPreamble(result.anonymized, result.map) : result.anonymized);
|
|
49
|
+
// Survives --quiet. Everything else here describes work that went right; this
|
|
50
|
+
// says the masking itself may be wrong. No marker matched, so the file was
|
|
51
|
+
// tokenised with TypeScript rules — in a Ruby file that means `#` comments
|
|
52
|
+
// were read as code and their prose masked, and in the other direction a
|
|
53
|
+
// language whose comment syntax we did not apply had its prose left in the
|
|
54
|
+
// clear. Output that looks anonymised and is not is the one thing a pipeline
|
|
55
|
+
// must not swallow.
|
|
56
|
+
if (result.languageFallback) {
|
|
57
|
+
io.stderr(`${BIN_NAME}: no language marker matched — masked as ${LANGUAGE_LABELS[result.language]}, which may be wrong. ` +
|
|
58
|
+
`Pass --language to be sure.\n`);
|
|
59
|
+
}
|
|
60
|
+
if (!args.quiet) {
|
|
61
|
+
const added = Object.keys(result.map).length - Object.keys(existingMap).length;
|
|
62
|
+
io.stderr(`${BIN_NAME}: ${LANGUAGE_LABELS[result.language]} — ${added} new placeholder${added === 1 ? '' : 's'}, ${Object.keys(result.map).length} in map\n`);
|
|
63
|
+
// Under --quiet, unlike the language warning above. Nearly every real file
|
|
64
|
+
// has a comment beside code, so this fires on almost every run; surviving
|
|
65
|
+
// --quiet would defeat the flag and teach people to stop passing it. The
|
|
66
|
+
// engine reports it on every result either way, so nothing is hidden from a
|
|
67
|
+
// caller that wants it.
|
|
68
|
+
if (result.comments.total > 0) {
|
|
69
|
+
const { total, inline, characters } = result.comments;
|
|
70
|
+
const where = inline === 0 ? 'above the code' : `${inline} inside the body`;
|
|
71
|
+
io.stderr(`${BIN_NAME}: ${total} comment${total === 1 ? '' : 's'} (${where}) left as written — ` +
|
|
72
|
+
`${characters} characters of prose are NOT masked. Names and ticket numbers in them go out as typed.\n`);
|
|
73
|
+
}
|
|
74
|
+
if (result.secrets.length > 0) {
|
|
75
|
+
io.stderr(`${BIN_NAME}: credentials detected — ${summaryLine(result.secrets)}\n`);
|
|
76
|
+
for (const f of result.secrets)
|
|
77
|
+
io.stderr(`${formatFinding(f)}\n`);
|
|
78
|
+
if (result.secrets.some((f) => f.redacted)) {
|
|
79
|
+
io.stderr(`${BIN_NAME}: redacted values are NOT recoverable on restore. Rotate anything real.\n`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return EXIT_OK;
|
|
84
|
+
}
|
|
85
|
+
export async function runRestore(args, io) {
|
|
86
|
+
const source = await readInput(args, io);
|
|
87
|
+
const mapPath = resolveMapPath(args.mapPath, io.cwd);
|
|
88
|
+
const map = loadMap(mapPath);
|
|
89
|
+
if (Object.keys(map).length === 0) {
|
|
90
|
+
// Restoring against an empty map returns the input verbatim, which looks
|
|
91
|
+
// like success. Say so instead.
|
|
92
|
+
io.stderr(`${BIN_NAME}: no symbol map at ${mapPath} — run "${BIN_NAME} scrub" first.\n`);
|
|
93
|
+
return EXIT_ERROR;
|
|
94
|
+
}
|
|
95
|
+
// Without --keep-docs the default strips JSDoc along with the narration and
|
|
96
|
+
// TODOs. That is right for noise, wrong when the model was asked to document
|
|
97
|
+
// its output — so the escape hatch is a flag, not a code change.
|
|
98
|
+
const strip = args.keepDocs ? STRIPPABLE_TYPES.filter((t) => t !== 'jsdoc') : 'all';
|
|
99
|
+
const result = restore(source, map, { strip });
|
|
100
|
+
io.stdout(result.restored);
|
|
101
|
+
const { resolved, missing, unresolved } = result.report;
|
|
102
|
+
// A token the map cannot explain is a finding, not a summary line: the text on
|
|
103
|
+
// stdout now contains something that means nothing, and the user is about to
|
|
104
|
+
// paste it into an editor. --quiet suppresses the all-clear, never findings —
|
|
105
|
+
// the same contract `scan` follows.
|
|
106
|
+
if (unresolved.length > 0) {
|
|
107
|
+
io.stderr(`${BIN_NAME}: ${unresolved.length} placeholder-shaped token${unresolved.length === 1 ? '' : 's'} not in the map — ` +
|
|
108
|
+
`${unresolved.join(', ')}\n`);
|
|
109
|
+
io.stderr(`${BIN_NAME}: the AI invented or altered these; they are still in the output above.\n`);
|
|
110
|
+
}
|
|
111
|
+
if (!args.quiet) {
|
|
112
|
+
io.stderr(`${BIN_NAME}: restored ${resolved.length} of ${Object.keys(map).length} placeholders, stripped ${result.strippedCount} AI artifact${result.strippedCount === 1 ? '' : 's'}\n`);
|
|
113
|
+
// Usually innocent — an answer about one function omits the rest of the
|
|
114
|
+
// file — so this sits under --quiet with the summary rather than above it.
|
|
115
|
+
if (missing.length > 0) {
|
|
116
|
+
io.stderr(`${BIN_NAME}: ${missing.length} placeholder${missing.length === 1 ? '' : 's'} did not appear in the input; ` +
|
|
117
|
+
`if the AI covered the whole file, it renamed them and those names are not recoverable from this text.\n`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Deliberately still EXIT_OK. `restore` writes usable text to stdout even when
|
|
121
|
+
// a token is unexplained, and exiting non-zero would break every
|
|
122
|
+
// `... | veilio restore > file` pipeline under `set -e` for a warning the user
|
|
123
|
+
// can act on. Gating that behind a flag is a separate decision from reporting.
|
|
124
|
+
return EXIT_OK;
|
|
125
|
+
}
|
|
126
|
+
/** Detect-only pass for pre-commit hooks and CI. Never rewrites anything. */
|
|
127
|
+
export async function runScan(args, io) {
|
|
128
|
+
const targets = args.files.length > 0
|
|
129
|
+
? args.files.map((f) => ({ name: f, text: readFileSync(resolve(io.cwd, f), 'utf8') }))
|
|
130
|
+
: [{ name: '<stdin>', text: await io.stdin() }];
|
|
131
|
+
const all = [];
|
|
132
|
+
for (const target of targets) {
|
|
133
|
+
for (const finding of detectSecrets(target.text)) {
|
|
134
|
+
all.push({ file: target.name, finding });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (args.json) {
|
|
138
|
+
io.stdout(`${JSON.stringify(all.map((r) => ({ file: r.file, ...r.finding })), null, 2)}\n`);
|
|
139
|
+
}
|
|
140
|
+
else if (all.length === 0) {
|
|
141
|
+
if (!args.quiet)
|
|
142
|
+
io.stderr(`${BIN_NAME}: no credentials detected\n`);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
io.stderr(`${BIN_NAME}: ${summaryLine(all.map((r) => r.finding))}\n`);
|
|
146
|
+
for (const { file, finding } of all)
|
|
147
|
+
io.stderr(`${formatFinding(finding, file)}\n`);
|
|
148
|
+
}
|
|
149
|
+
const findings = all.map((r) => r.finding);
|
|
150
|
+
// Default gate is critical/high. --strict also fails on advisory findings,
|
|
151
|
+
// for teams whose policy covers customer emails and internal hostnames.
|
|
152
|
+
const fail = args.strict ? findings.length > 0 : hasBlockingSecrets(findings);
|
|
153
|
+
return fail ? EXIT_FINDINGS : EXIT_OK;
|
|
154
|
+
}
|
|
155
|
+
export async function runMap(args, io) {
|
|
156
|
+
const mapPath = resolveMapPath(args.mapPath, io.cwd);
|
|
157
|
+
if (args.clear) {
|
|
158
|
+
const removed = clearMap(mapPath);
|
|
159
|
+
io.stderr(removed ? `${BIN_NAME}: cleared ${mapPath}\n` : `${BIN_NAME}: no map at ${mapPath}\n`);
|
|
160
|
+
return EXIT_OK;
|
|
161
|
+
}
|
|
162
|
+
const map = loadMap(mapPath);
|
|
163
|
+
const entries = Object.entries(map);
|
|
164
|
+
if (args.json) {
|
|
165
|
+
io.stdout(`${JSON.stringify(map, null, 2)}\n`);
|
|
166
|
+
return EXIT_OK;
|
|
167
|
+
}
|
|
168
|
+
if (entries.length === 0) {
|
|
169
|
+
io.stderr(`${BIN_NAME}: no map at ${mapPath}\n`);
|
|
170
|
+
return EXIT_OK;
|
|
171
|
+
}
|
|
172
|
+
io.stderr(`${BIN_NAME}: ${entries.length} placeholders at ${mapPath}\n`);
|
|
173
|
+
for (const [placeholder, real] of entries)
|
|
174
|
+
io.stdout(`${placeholder}\t${real}\n`);
|
|
175
|
+
return EXIT_OK;
|
|
176
|
+
}
|
|
177
|
+
export const HELP = `${BIN_NAME} — anonymize code before it reaches an LLM, restore it after.
|
|
178
|
+
|
|
179
|
+
USAGE
|
|
180
|
+
${BIN_NAME} <command> [files...] [options]
|
|
181
|
+
|
|
182
|
+
COMMANDS
|
|
183
|
+
scrub [files...] Mask identifiers and redact credentials. Reads stdin when
|
|
184
|
+
no file is given. Writes the symbol map to ${STORE_DIR}/map.json.
|
|
185
|
+
restore [files...] Swap placeholders back and strip AI-generated noise.
|
|
186
|
+
scan [files...] Detect credentials only; never rewrites. Exits 1 when it
|
|
187
|
+
finds something — use it in a pre-commit hook or CI.
|
|
188
|
+
map Show the current symbol map (--clear to wipe it).
|
|
189
|
+
|
|
190
|
+
OPTIONS
|
|
191
|
+
-l, --language <lang> auto (default), typescript, python, go, java, csharp,
|
|
192
|
+
rust, ruby, php, c, sql
|
|
193
|
+
-s, --secrets <policy> redact (default) | warn | off
|
|
194
|
+
-p, --preamble Prepend the downstream-AI note and placeholder legend
|
|
195
|
+
-m, --map <path> Use a specific map file
|
|
196
|
+
--json Machine-readable output (scan, map)
|
|
197
|
+
--strict scan: also fail on advisory findings
|
|
198
|
+
--keep-docs restore: keep JSDoc blocks the model wrote
|
|
199
|
+
-f, --force Allow a map write that would drop existing entries
|
|
200
|
+
-q, --quiet Suppress the all-clear summary (findings always show)
|
|
201
|
+
-h, --help Show this help
|
|
202
|
+
-v, --version Show the version
|
|
203
|
+
|
|
204
|
+
EXIT CODES
|
|
205
|
+
0 clean 1 findings that should stop the pipeline 2 usage or IO error
|
|
206
|
+
|
|
207
|
+
EXAMPLES
|
|
208
|
+
${BIN_NAME} scrub src/billing.ts | pbcopy
|
|
209
|
+
pbpaste | ${BIN_NAME} restore
|
|
210
|
+
git diff --cached | ${BIN_NAME} scan # pre-commit gate
|
|
211
|
+
${BIN_NAME} scan 'src/**/*.ts' --json > findings.json
|
|
212
|
+
|
|
213
|
+
Transformed code goes to stdout; everything else goes to stderr, so the CLI
|
|
214
|
+
composes in a pipe.
|
|
215
|
+
`;
|
|
216
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { type Io } from './commands.js';
|
|
3
|
+
export declare const VERSION = "0.1.0";
|
|
4
|
+
/** Testable entry point: everything that touches the process is injected. */
|
|
5
|
+
export declare function main(argv: readonly string[], io: Io): Promise<number>;
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAA2D,KAAK,EAAE,EAAE,MAAM,eAAe,CAAA;AAIhG,eAAO,MAAM,OAAO,UAAU,CAAA;AAE9B,6EAA6E;AAC7E,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiC3E"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs, UsageError } from './args.js';
|
|
3
|
+
import { EXIT_ERROR, HELP, runMap, runRestore, runScan, runScrub } from './commands.js';
|
|
4
|
+
import { realpathSync } from 'node:fs';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
export const VERSION = '0.1.0';
|
|
7
|
+
/** Testable entry point: everything that touches the process is injected. */
|
|
8
|
+
export async function main(argv, io) {
|
|
9
|
+
let args;
|
|
10
|
+
try {
|
|
11
|
+
args = parseArgs(argv);
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
if (err instanceof UsageError) {
|
|
15
|
+
io.stderr(`veilio: ${err.message}\n\nRun "veilio --help" for usage.\n`);
|
|
16
|
+
return EXIT_ERROR;
|
|
17
|
+
}
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
switch (args.command) {
|
|
22
|
+
case 'help':
|
|
23
|
+
io.stdout(HELP);
|
|
24
|
+
return 0;
|
|
25
|
+
case 'version':
|
|
26
|
+
io.stdout(`${VERSION}\n`);
|
|
27
|
+
return 0;
|
|
28
|
+
case 'scrub':
|
|
29
|
+
return await runScrub(args, io);
|
|
30
|
+
case 'restore':
|
|
31
|
+
return await runRestore(args, io);
|
|
32
|
+
case 'scan':
|
|
33
|
+
return await runScan(args, io);
|
|
34
|
+
case 'map':
|
|
35
|
+
return await runMap(args, io);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
io.stderr(`veilio: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
40
|
+
return EXIT_ERROR;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function readStdin() {
|
|
44
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
45
|
+
// A TTY with no piped input would hang forever waiting for EOF. Fail with
|
|
46
|
+
// usage guidance instead of appearing to freeze.
|
|
47
|
+
if (process.stdin.isTTY) {
|
|
48
|
+
rejectPromise(new Error('no input — pass a file or pipe to stdin'));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
let data = '';
|
|
52
|
+
process.stdin.setEncoding('utf8');
|
|
53
|
+
process.stdin.on('data', (chunk) => (data += chunk));
|
|
54
|
+
process.stdin.on('end', () => resolvePromise(data));
|
|
55
|
+
process.stdin.on('error', rejectPromise);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Was this file run as a program, or imported?
|
|
60
|
+
*
|
|
61
|
+
* The obvious form of this check — comparing `import.meta.url` to
|
|
62
|
+
* `file://${process.argv[1]}` — is wrong in the one way that matters, and it
|
|
63
|
+
* shipped: npm installs a binary as a SYMLINK in `node_modules/.bin`, so
|
|
64
|
+
* `argv[1]` is the link while `import.meta.url` is the file it resolves to. The
|
|
65
|
+
* two never match, the branch below never runs, and `veilio --version` prints
|
|
66
|
+
* nothing and exits 0. Installed fine, did nothing, said nothing.
|
|
67
|
+
*
|
|
68
|
+
* `realpathSync` resolves the link. `pathToFileURL` handles the rest of what
|
|
69
|
+
* string interpolation got wrong: a path containing a space or any non-ASCII
|
|
70
|
+
* character needs percent-encoding, and a Windows path needs `file:///C:/...`
|
|
71
|
+
* rather than `file://C:\...`.
|
|
72
|
+
*
|
|
73
|
+
* Wrapped because `argv[1]` may name something unstattable — `node --eval`, or a
|
|
74
|
+
* file deleted mid-run. Not a program then, which is the safe reading.
|
|
75
|
+
*/
|
|
76
|
+
function invokedAsProgram() {
|
|
77
|
+
const argv1 = process.argv[1];
|
|
78
|
+
if (argv1 === undefined)
|
|
79
|
+
return false;
|
|
80
|
+
try {
|
|
81
|
+
return import.meta.url === pathToFileURL(realpathSync(argv1)).href;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (invokedAsProgram()) {
|
|
88
|
+
const io = {
|
|
89
|
+
cwd: process.cwd(),
|
|
90
|
+
stdin: readStdin,
|
|
91
|
+
stdout: (text) => process.stdout.write(text),
|
|
92
|
+
stderr: (text) => process.stderr.write(text),
|
|
93
|
+
};
|
|
94
|
+
main(process.argv.slice(2), io).then((code) => {
|
|
95
|
+
process.exitCode = code;
|
|
96
|
+
}, (err) => {
|
|
97
|
+
process.stderr.write(`veilio: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
98
|
+
process.exitCode = EXIT_ERROR;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { STORE_DIR, type SymbolMap } from '@veilio-inc/engine';
|
|
2
|
+
export { STORE_DIR };
|
|
3
|
+
export declare const STORE_FILE = "map.json";
|
|
4
|
+
/** Walk up from `from` looking for an existing store or a repo root, so running
|
|
5
|
+
* the CLI in a subdirectory still finds the project's map. Falls back to
|
|
6
|
+
* `from` when neither is found. */
|
|
7
|
+
export declare function findProjectRoot(from: string): string;
|
|
8
|
+
/** Resolve the map file path: explicit override, else the project store. */
|
|
9
|
+
export declare function resolveMapPath(override: string | null, cwd: string): string;
|
|
10
|
+
export interface StoredMap {
|
|
11
|
+
map: SymbolMap;
|
|
12
|
+
/** Schema marker so a future format change can be detected rather than
|
|
13
|
+
* silently misread as a map. */
|
|
14
|
+
version: 1;
|
|
15
|
+
}
|
|
16
|
+
/** Read the stored map, or an empty map when there is none. Throws only when a
|
|
17
|
+
* file exists but cannot be understood — silently starting from empty there
|
|
18
|
+
* would orphan every placeholder the user already has in flight. */
|
|
19
|
+
export declare function loadMap(path: string): SymbolMap;
|
|
20
|
+
export declare class MapOverwriteError extends Error {
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Write the map, refusing a write that would lose entries already on disk.
|
|
24
|
+
*
|
|
25
|
+
* Not a guard on every write. `scrub` loads the existing map, hands it to
|
|
26
|
+
* `anonymize` as `existingMap` and saves the union, so the normal path only ever
|
|
27
|
+
* grows the file — demanding a flag for that would mean typing `--force` on
|
|
28
|
+
* every second command, which is how a safety prompt becomes muscle memory and
|
|
29
|
+
* stops being read.
|
|
30
|
+
*
|
|
31
|
+
* What it refuses is the write that actually destroys something: one whose map
|
|
32
|
+
* no longer explains a placeholder the file already had. That map is the only
|
|
33
|
+
* means of restoring output produced with it, and once the entry is gone the
|
|
34
|
+
* text that used it can never be read back. Reachable by pointing `--map` at
|
|
35
|
+
* another session's store, or by a future caller that forgets to load first.
|
|
36
|
+
*/
|
|
37
|
+
export declare function saveMap(path: string, map: SymbolMap, options?: {
|
|
38
|
+
force?: boolean;
|
|
39
|
+
}): void;
|
|
40
|
+
export declare function clearMap(path: string): boolean;
|
|
41
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9D,OAAO,EAAE,SAAS,EAAE,CAAA;AACpB,eAAO,MAAM,UAAU,aAAa,CAAA;AAEpC;;oCAEoC;AACpC,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQpD;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAG3E;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,SAAS,CAAA;IACd;qCACiC;IACjC,OAAO,EAAE,CAAC,CAAA;CACX;AAOD;;qEAEqE;AACrE,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAgB/C;AAED,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAE/C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,IAAI,CA8B7F;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAI9C"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Symbol-map persistence.
|
|
2
|
+
//
|
|
3
|
+
// The map is what makes the round trip work: scrub writes it, restore reads it.
|
|
4
|
+
// It lives in the project, not a global cache, because placeholder numbering is
|
|
5
|
+
// only meaningful within one codebase — sharing `__CLS__1` across unrelated
|
|
6
|
+
// projects would collide names and restore the wrong identifiers.
|
|
7
|
+
//
|
|
8
|
+
// The map holds real identifier names, so it is written 0600 and the store
|
|
9
|
+
// directory carries a .gitignore. Committing it would undo the anonymization
|
|
10
|
+
// for anyone reading the repo.
|
|
11
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { dirname, join, resolve } from 'node:path';
|
|
13
|
+
import { STORE_DIR } from '@veilio-inc/engine';
|
|
14
|
+
export { STORE_DIR };
|
|
15
|
+
export const STORE_FILE = 'map.json';
|
|
16
|
+
/** Walk up from `from` looking for an existing store or a repo root, so running
|
|
17
|
+
* the CLI in a subdirectory still finds the project's map. Falls back to
|
|
18
|
+
* `from` when neither is found. */
|
|
19
|
+
export function findProjectRoot(from) {
|
|
20
|
+
let dir = resolve(from);
|
|
21
|
+
for (;;) {
|
|
22
|
+
if (existsSync(join(dir, STORE_DIR)) || existsSync(join(dir, '.git')))
|
|
23
|
+
return dir;
|
|
24
|
+
const parent = dirname(dir);
|
|
25
|
+
if (parent === dir)
|
|
26
|
+
return resolve(from);
|
|
27
|
+
dir = parent;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** Resolve the map file path: explicit override, else the project store. */
|
|
31
|
+
export function resolveMapPath(override, cwd) {
|
|
32
|
+
if (override !== null)
|
|
33
|
+
return resolve(cwd, override);
|
|
34
|
+
return join(findProjectRoot(cwd), STORE_DIR, STORE_FILE);
|
|
35
|
+
}
|
|
36
|
+
function isSymbolMap(value) {
|
|
37
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
38
|
+
return false;
|
|
39
|
+
return Object.values(value).every((v) => typeof v === 'string');
|
|
40
|
+
}
|
|
41
|
+
/** Read the stored map, or an empty map when there is none. Throws only when a
|
|
42
|
+
* file exists but cannot be understood — silently starting from empty there
|
|
43
|
+
* would orphan every placeholder the user already has in flight. */
|
|
44
|
+
export function loadMap(path) {
|
|
45
|
+
if (!existsSync(path))
|
|
46
|
+
return {};
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new Error(`map file at ${path} is not valid JSON`);
|
|
53
|
+
}
|
|
54
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
55
|
+
throw new Error(`map file at ${path} is malformed`);
|
|
56
|
+
}
|
|
57
|
+
const map = parsed.map;
|
|
58
|
+
if (!isSymbolMap(map)) {
|
|
59
|
+
throw new Error(`map file at ${path} is malformed`);
|
|
60
|
+
}
|
|
61
|
+
return map;
|
|
62
|
+
}
|
|
63
|
+
export class MapOverwriteError extends Error {
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Write the map, refusing a write that would lose entries already on disk.
|
|
67
|
+
*
|
|
68
|
+
* Not a guard on every write. `scrub` loads the existing map, hands it to
|
|
69
|
+
* `anonymize` as `existingMap` and saves the union, so the normal path only ever
|
|
70
|
+
* grows the file — demanding a flag for that would mean typing `--force` on
|
|
71
|
+
* every second command, which is how a safety prompt becomes muscle memory and
|
|
72
|
+
* stops being read.
|
|
73
|
+
*
|
|
74
|
+
* What it refuses is the write that actually destroys something: one whose map
|
|
75
|
+
* no longer explains a placeholder the file already had. That map is the only
|
|
76
|
+
* means of restoring output produced with it, and once the entry is gone the
|
|
77
|
+
* text that used it can never be read back. Reachable by pointing `--map` at
|
|
78
|
+
* another session's store, or by a future caller that forgets to load first.
|
|
79
|
+
*/
|
|
80
|
+
export function saveMap(path, map, options = {}) {
|
|
81
|
+
if (!options.force && existsSync(path)) {
|
|
82
|
+
const dropped = Object.entries(loadMap(path)).filter(([placeholder, real]) => map[placeholder] !== real);
|
|
83
|
+
if (dropped.length > 0) {
|
|
84
|
+
const shown = dropped
|
|
85
|
+
.slice(0, 3)
|
|
86
|
+
.map(([p]) => p)
|
|
87
|
+
.join(', ');
|
|
88
|
+
throw new MapOverwriteError(`refusing to overwrite ${path}: ${dropped.length} existing placeholder${dropped.length === 1 ? '' : 's'} ` +
|
|
89
|
+
`(${shown}${dropped.length > 3 ? ', …' : ''}) would be lost, and text already anonymized with them ` +
|
|
90
|
+
`could never be restored. Pass --force to overwrite anyway.`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const dir = dirname(path);
|
|
94
|
+
mkdirSync(dir, { recursive: true });
|
|
95
|
+
// A store directory inside a repo must never be committed — it holds the real
|
|
96
|
+
// names the anonymization exists to hide.
|
|
97
|
+
const ignore = join(dir, '.gitignore');
|
|
98
|
+
if (dir.endsWith(STORE_DIR) && !existsSync(ignore)) {
|
|
99
|
+
writeFileSync(ignore, '*\n', { mode: 0o600 });
|
|
100
|
+
}
|
|
101
|
+
const payload = { version: 1, map };
|
|
102
|
+
writeFileSync(path, `${JSON.stringify(payload, null, 2)}\n`, { mode: 0o600 });
|
|
103
|
+
// writeFileSync only applies `mode` when creating the file; enforce it on
|
|
104
|
+
// rewrite too so a pre-existing world-readable map gets locked down.
|
|
105
|
+
chmodSync(path, 0o600);
|
|
106
|
+
}
|
|
107
|
+
export function clearMap(path) {
|
|
108
|
+
if (!existsSync(path))
|
|
109
|
+
return false;
|
|
110
|
+
rmSync(path);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@veilio-inc/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Anonymize identifiers and redact credentials in source code before it reaches an LLM — from the terminal, a pipe, or a pre-commit hook.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"veilio": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=24"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"anonymizer",
|
|
21
|
+
"llm",
|
|
22
|
+
"ai",
|
|
23
|
+
"privacy",
|
|
24
|
+
"secret-scanning",
|
|
25
|
+
"pre-commit",
|
|
26
|
+
"cli",
|
|
27
|
+
"veilio"
|
|
28
|
+
],
|
|
29
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@veilio-inc/engine": "^1.4.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc",
|
|
35
|
+
"clean": "rm -rf dist",
|
|
36
|
+
"prepublishOnly": "npm run clean && npm run typecheck && npm run build && npm run test",
|
|
37
|
+
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:coverage": "vitest run --coverage"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
43
|
+
"typescript": "^5.4.0",
|
|
44
|
+
"vitest": "^4.1.10"
|
|
45
|
+
},
|
|
46
|
+
"exports": {
|
|
47
|
+
".": "./dist/index.js",
|
|
48
|
+
"./store": "./dist/store.js"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "git+https://github.com/veilio-inc/veilio.git",
|
|
53
|
+
"directory": "packages/cli"
|
|
54
|
+
},
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://github.com/veilio-inc/veilio/issues"
|
|
57
|
+
},
|
|
58
|
+
"homepage": "https://github.com/veilio-inc/veilio/tree/main/packages/cli#readme",
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
}
|
|
62
|
+
}
|