sf-compact-cli 0.1.1

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/README.md ADDED
@@ -0,0 +1,281 @@
1
+ # sf-compact
2
+
3
+ Convert Salesforce metadata XML to AI-friendly compact formats. Semantically lossless roundtrip.
4
+
5
+ Salesforce metadata XML is extremely verbose — profiles, permission sets, flows, and objects can be 20,000–50,000+ lines of XML with 70–85% structural overhead. This burns tokens and money when AI tools (Claude Code, Codex, Cursor, etc.) read or edit your metadata.
6
+
7
+ **sf-compact** converts it to compact YAML or JSON, saving 42–54% of tokens depending on format.
8
+
9
+ ## Output Formats
10
+
11
+ | Format | Preserves order | Human-readable | Token savings |
12
+ |--------|:-:|:-:|:-:|
13
+ | `yaml` | No | Yes | ~49% |
14
+ | `yaml-ordered` | Yes | Yes | ~42% |
15
+ | `json` | Yes | Less | ~54% |
16
+
17
+ - **yaml** — groups repeated elements into arrays. Most compact YAML, but sibling order may change. Best for order-insensitive types (Profile, PermissionSet).
18
+ - **yaml-ordered** — uses `_children` sequences to preserve exact element order. Best for order-sensitive types (Flow, FlexiPage, Layout).
19
+ - **json** — compact single-line JSON with arrays. Preserves order, fewest tokens, less human-readable.
20
+
21
+ ## Before / After
22
+
23
+ **XML (848 tokens):**
24
+ ```xml
25
+ <?xml version="1.0" encoding="UTF-8"?>
26
+ <Profile xmlns="http://soap.sforce.com/2006/04/metadata">
27
+ <custom>false</custom>
28
+ <userLicense>Salesforce</userLicense>
29
+ <fieldPermissions>
30
+ <editable>true</editable>
31
+ <field>Account.AnnualRevenue</field>
32
+ <readable>true</readable>
33
+ </fieldPermissions>
34
+ <fieldPermissions>
35
+ <editable>false</editable>
36
+ <field>Account.BillingCity</field>
37
+ <readable>true</readable>
38
+ </fieldPermissions>
39
+ ...
40
+ </Profile>
41
+ ```
42
+
43
+ **YAML (432 tokens — 49% reduction):**
44
+ ```yaml
45
+ _tag: Profile
46
+ _ns: http://soap.sforce.com/2006/04/metadata
47
+ custom: false
48
+ userLicense: Salesforce
49
+ fieldPermissions:
50
+ - editable: true
51
+ field: Account.AnnualRevenue
52
+ readable: true
53
+ - editable: false
54
+ field: Account.BillingCity
55
+ readable: true
56
+ ...
57
+ ```
58
+
59
+ **JSON (389 tokens — 54% reduction):**
60
+ ```json
61
+ {"_tag":"Profile","_ns":"http://soap.sforce.com/2006/04/metadata","custom":"false","userLicense":"Salesforce","fieldPermissions":[{"editable":"true","field":"Account.AnnualRevenue","readable":"true"},{"editable":"false","field":"Account.BillingCity","readable":"true"}]}
62
+ ```
63
+
64
+ ## Install
65
+
66
+ ### From source (Rust required)
67
+ ```bash
68
+ cargo install --path .
69
+ ```
70
+
71
+ ### From crates.io
72
+ ```bash
73
+ cargo install sf-compact
74
+ ```
75
+
76
+ ## Usage
77
+
78
+ ### Pack (XML → compact format)
79
+ ```bash
80
+ sf-compact pack [source...] [-o output] [--format yaml|yaml-ordered|json] [--include pattern]
81
+ ```
82
+
83
+ ```bash
84
+ # Pack entire project (default: YAML format)
85
+ sf-compact pack force-app -o .sf-compact
86
+
87
+ # Pack as JSON for maximum token savings
88
+ sf-compact pack force-app --format json
89
+
90
+ # Pack specific directories
91
+ sf-compact pack force-app/main/default/profiles force-app/main/default/classes
92
+
93
+ # Pack only profiles
94
+ sf-compact pack force-app --include "*.profile-meta.xml"
95
+ ```
96
+
97
+ ### Unpack (compact format → XML)
98
+ ```bash
99
+ sf-compact unpack [source...] [-o output] [--include pattern]
100
+ ```
101
+
102
+ Auto-detects format by file extension (`.yaml` or `.json`).
103
+
104
+ ```bash
105
+ sf-compact unpack .sf-compact -o force-app
106
+ ```
107
+
108
+ ### Stats (preview savings)
109
+ ```bash
110
+ sf-compact stats [source...] [--include pattern] [--files]
111
+ ```
112
+
113
+ Analyze metadata and preview token/byte savings without writing files.
114
+
115
+ ```bash
116
+ $ sf-compact stats force-app
117
+
118
+ Preview: what sf-compact pack would produce
119
+ Tokenizer: cl100k_base (GPT-4 / Claude)
120
+
121
+ XML (now) YAML (after) savings
122
+ --------------------------------------------------------------------------------
123
+ Bytes 7313 3418 53.3%
124
+ Tokens 1719 925 46.2%
125
+
126
+ Would save 794 tokens across 5 files
127
+
128
+ By metadata type:
129
+ type files now → after tokens saved
130
+ ----------------------------------------------------------------------
131
+ profile 1 848 → 432 tokens 49.1%
132
+ flow 1 464 → 268 tokens 42.2%
133
+ field 1 232 → 126 tokens 45.7%
134
+ js 1 116 → 66 tokens 43.1%
135
+ cls 1 59 → 33 tokens 44.1%
136
+ ```
137
+
138
+ Use `--files` for per-file breakdown, `--include` to filter by glob pattern.
139
+
140
+ ### Configuration
141
+
142
+ sf-compact uses a `.sfcompact.yaml` config file for per-type format control.
143
+
144
+ ```bash
145
+ # Create config with smart defaults (yaml-ordered for order-sensitive types)
146
+ sf-compact config init
147
+
148
+ # Set format for specific types (batch — multiple types in one call)
149
+ sf-compact config set flow json profile yaml flexipage yaml-ordered
150
+
151
+ # Change default format for all types
152
+ sf-compact config set default json
153
+
154
+ # Skip a metadata type from conversion
155
+ sf-compact config skip customMetadata
156
+
157
+ # View current configuration
158
+ sf-compact config show
159
+ ```
160
+
161
+ Default config after `config init`:
162
+
163
+ ```yaml
164
+ default_format: yaml
165
+ formats:
166
+ Flow: yaml-ordered
167
+ FlexiPage: yaml-ordered
168
+ Layout: yaml-ordered
169
+ skip: []
170
+ ```
171
+
172
+ When `pack` runs, it reads `.sfcompact.yaml` and applies the format per metadata type. The `--format` CLI flag overrides the config for a single run.
173
+
174
+ ### Watch (auto-pack on changes)
175
+ ```bash
176
+ sf-compact watch [source...] [-o output] [--format yaml|yaml-ordered|json] [--include pattern]
177
+ ```
178
+
179
+ Watches source directories for XML changes and automatically repacks. Runs an initial pack, then monitors for file changes.
180
+
181
+ ```bash
182
+ # Watch default force-app directory
183
+ sf-compact watch
184
+
185
+ # Watch with JSON format
186
+ sf-compact watch force-app --format json
187
+ ```
188
+
189
+ ### Diff (detect unpacked changes)
190
+ ```bash
191
+ sf-compact diff [source...] [-o packed-dir] [--include pattern]
192
+ ```
193
+
194
+ Compare current XML metadata against the last packed output. Shows new, modified, and deleted files.
195
+
196
+ ```bash
197
+ $ sf-compact diff
198
+
199
+ + force-app/main/default/profiles/NewProfile.profile-meta.xml (new — not yet packed)
200
+ ~ force-app/main/default/flows/Case_Assignment.flow-meta.xml (modified since last pack)
201
+
202
+ 1 new, 1 modified, 0 deleted, 3 unchanged
203
+ Run `sf-compact pack` to update.
204
+ ```
205
+
206
+ ### MCP Server
207
+
208
+ sf-compact includes a built-in [MCP](https://modelcontextprotocol.io/) server for direct AI tool integration.
209
+
210
+ ```bash
211
+ # Add to your project's .mcp.json
212
+ sf-compact init mcp
213
+
214
+ # Or start manually
215
+ sf-compact mcp-serve
216
+ ```
217
+
218
+ This exposes `sf_compact_pack`, `sf_compact_unpack`, and `sf_compact_stats` as MCP tools that Claude Code, Cursor, and other MCP-compatible tools can discover and use automatically.
219
+
220
+ ### AI Instructions
221
+
222
+ Generate a provider-agnostic markdown file with usage instructions for any AI tool:
223
+
224
+ ```bash
225
+ sf-compact init instructions
226
+ sf-compact init instructions --name SALESFORCE.md
227
+ ```
228
+
229
+ ### Manifest
230
+
231
+ Output supported metadata types in JSON (includes format support and order-sensitivity flags):
232
+
233
+ ```bash
234
+ sf-compact manifest
235
+ ```
236
+
237
+ ## Supported Metadata Types
238
+
239
+ 75 file extensions mapping to Salesforce metadata types across 9 categories:
240
+
241
+ | Category | Types |
242
+ |----------|-------|
243
+ | **Security** | Profile, PermissionSet, PermissionSetGroup, RemoteSiteSetting, CspTrustedSite, ConnectedApp, SharingRules, CustomPermission, Role, Group, AuthProvider, SamlSsoConfig, Certificate |
244
+ | **Schema** | CustomObject, CustomField, ValidationRule, CustomMetadata, GlobalValueSet, StandardValueSet, RecordType, MatchingRule, DuplicateRule, CustomIndex, TopicsForObjects, CustomObjectTranslation, CustomFieldTranslation, FieldSet |
245
+ | **Code** | ApexClass, ApexTrigger, ApexComponent, ApexPage, LightningComponentBundle (js/css/html/xml), AuraDefinitionBundle (cmp/evt), StaticResource |
246
+ | **Automation** | Flow*, Workflow, WorkflowRule, AssignmentRules, AutoResponseRules, EscalationRules |
247
+ | **UI** | Layout*, CustomLabels, CustomApplication, CustomTab, FlexiPage*, CustomSite, QuickAction, PathAssistant, ListView, CompactLayout, WebLink, HomePageLayout, AppMenu, Community, Letterhead |
248
+ | **Analytics** | ReportType, Report, Dashboard |
249
+ | **Integration** | ExternalServiceRegistration, NamedCredential, ExternalCredential, InstalledPackage |
250
+ | **Notifications** | CustomNotificationType, NotificationTypeConfig, LightningMessageChannel, PlatformEventChannelMember |
251
+ | **Content** | EmailTemplate, ManagedContentType, CleanDataService, IframeWhiteListUrlSettings, Settings |
252
+
253
+ \* Order-sensitive types — `config init` defaults these to `yaml-ordered` to preserve element order.
254
+
255
+ ## Workflow
256
+
257
+ 1. **Configure** (once): `sf-compact config init` — creates `.sfcompact.yaml` with smart defaults
258
+ 2. **Pull metadata** from Salesforce (`sf project retrieve`)
259
+ 3. **Pack**: `sf-compact pack` — creates `.sf-compact/` with compact files
260
+ 4. **Work with compact files** — let AI tools read/edit the YAML/JSON format
261
+ 5. **Unpack**: `sf-compact unpack` — restores XML for deployment
262
+ 6. **Deploy** to Salesforce (`sf project deploy`)
263
+
264
+ > Use `sf-compact watch` during development to auto-pack on changes, and `sf-compact diff` to check if a repack is needed.
265
+
266
+ > Tip: Add `.sf-compact/` to `.gitignore` if you treat it as a build artifact, or commit it for AI-friendly diffs.
267
+
268
+ ## How it works
269
+
270
+ - Parses Salesforce metadata XML into a tree structure
271
+ - Groups repeated elements (e.g., `<fieldPermissions>`) into arrays (YAML) or `_children` sequences (yaml-ordered, JSON)
272
+ - Coerces booleans: `"true"` → `true`, `"false"` → `false`. All other values (including numeric strings like `"59.0"`, `"0012"`) are preserved as-is
273
+ - Flattens simple key-value containers into inline mappings
274
+ - Preserves namespaces, attributes, and all structural information for semantically lossless roundtrip
275
+ - Order-sensitive types (Flow, FlexiPage, Layout) default to `yaml-ordered` format, which preserves exact element order via `_children` sequences
276
+
277
+ Token counting uses the `cl100k_base` tokenizer (same family used by GPT-4 and Claude).
278
+
279
+ ## License
280
+
281
+ MIT
package/bin/sf-compact ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execFileSync } = require("child_process");
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+
7
+ const binDir = path.join(__dirname);
8
+ const binary = path.join(binDir, process.platform === "win32" ? "sf-compact.exe" : "sf-compact-bin");
9
+
10
+ if (!fs.existsSync(binary)) {
11
+ console.error("sf-compact binary not found. Try reinstalling: npm install -g sf-compact");
12
+ process.exit(1);
13
+ }
14
+
15
+ try {
16
+ execFileSync(binary, process.argv.slice(2), { stdio: "inherit" });
17
+ } catch (err) {
18
+ process.exit(err.status || 1);
19
+ }
package/install.js ADDED
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync } = require("child_process");
4
+ const fs = require("fs");
5
+ const path = require("path");
6
+ const https = require("https");
7
+ const http = require("http");
8
+
9
+ const VERSION = "0.1.1";
10
+ const REPO = "vradko/sf-compact";
11
+
12
+ const PLATFORMS = {
13
+ "darwin-arm64": `sf-compact-v${VERSION}-aarch64-apple-darwin.tar.gz`,
14
+ "darwin-x64": `sf-compact-v${VERSION}-x86_64-apple-darwin.tar.gz`,
15
+ "linux-x64": `sf-compact-v${VERSION}-x86_64-unknown-linux-musl.tar.gz`,
16
+ };
17
+
18
+ function getPlatformKey() {
19
+ return `${process.platform}-${process.arch}`;
20
+ }
21
+
22
+ function download(url) {
23
+ return new Promise((resolve, reject) => {
24
+ const get = url.startsWith("https") ? https.get : http.get;
25
+ get(url, (res) => {
26
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
27
+ return download(res.headers.location).then(resolve, reject);
28
+ }
29
+ if (res.statusCode !== 200) {
30
+ return reject(new Error(`HTTP ${res.statusCode} for ${url}`));
31
+ }
32
+ const chunks = [];
33
+ res.on("data", (chunk) => chunks.push(chunk));
34
+ res.on("end", () => resolve(Buffer.concat(chunks)));
35
+ res.on("error", reject);
36
+ }).on("error", reject);
37
+ });
38
+ }
39
+
40
+ async function main() {
41
+ const key = getPlatformKey();
42
+ const filename = PLATFORMS[key];
43
+
44
+ if (!filename) {
45
+ console.error(`sf-compact: unsupported platform ${key}`);
46
+ console.error(`Supported: ${Object.keys(PLATFORMS).join(", ")}`);
47
+ console.error("Install from source: cargo install sf-compact");
48
+ process.exit(1);
49
+ }
50
+
51
+ const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${filename}`;
52
+ const binDir = path.join(__dirname, "bin");
53
+ const binPath = path.join(binDir, "sf-compact-bin");
54
+
55
+ // Skip if already installed
56
+ if (fs.existsSync(binPath)) {
57
+ try {
58
+ const ver = execSync(`"${binPath}" --version`, { encoding: "utf8" }).trim();
59
+ if (ver.includes(VERSION)) {
60
+ return;
61
+ }
62
+ } catch {}
63
+ }
64
+
65
+ console.log(`Downloading sf-compact v${VERSION} for ${key}...`);
66
+
67
+ const tarball = await download(url);
68
+ const tmpFile = path.join(__dirname, filename);
69
+ fs.writeFileSync(tmpFile, tarball);
70
+
71
+ fs.mkdirSync(binDir, { recursive: true });
72
+
73
+ const tmpDir = path.join(__dirname, "tmp-extract");
74
+ fs.mkdirSync(tmpDir, { recursive: true });
75
+
76
+ if (filename.endsWith(".tar.gz")) {
77
+ execSync(`tar xzf "${tmpFile}" -C "${tmpDir}"`, { stdio: "inherit" });
78
+ } else {
79
+ execSync(`unzip -o "${tmpFile}" -d "${tmpDir}"`, { stdio: "inherit" });
80
+ }
81
+
82
+ // Rename to sf-compact-bin to avoid collision with the JS wrapper
83
+ const extracted = path.join(tmpDir, "sf-compact");
84
+ fs.renameSync(extracted, binPath);
85
+ fs.unlinkSync(tmpFile);
86
+ fs.rmSync(tmpDir, { recursive: true, force: true });
87
+ fs.chmodSync(binPath, 0o755);
88
+
89
+ console.log(`sf-compact v${VERSION} installed successfully.`);
90
+ }
91
+
92
+ main().catch((err) => {
93
+ console.error("sf-compact install failed:", err.message);
94
+ console.error("Install from source: cargo install sf-compact");
95
+ process.exit(1);
96
+ });
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "sf-compact-cli",
3
+ "version": "0.1.1",
4
+ "description": "Convert Salesforce metadata XML to AI-friendly compact formats. Semantically lossless roundtrip.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/vradko/sf-compact"
9
+ },
10
+ "homepage": "https://github.com/vradko/sf-compact",
11
+ "keywords": [
12
+ "salesforce",
13
+ "metadata",
14
+ "yaml",
15
+ "ai",
16
+ "token-optimization",
17
+ "xml",
18
+ "cli"
19
+ ],
20
+ "bin": {
21
+ "sf-compact": "bin/sf-compact"
22
+ },
23
+ "scripts": {
24
+ "postinstall": "node install.js"
25
+ },
26
+ "files": [
27
+ "bin/",
28
+ "install.js",
29
+ "README.md"
30
+ ]
31
+ }