lsh-framework 3.5.1 → 3.6.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/README.md +16 -48
- package/dist/constants/config.js +5 -0
- package/dist/lib/discovery-backend.js +150 -0
- package/dist/lib/ipfs-secrets-storage.js +12 -14
- package/dist/lib/w3name-pointer.js +72 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -10,15 +10,13 @@
|
|
|
10
10
|
[](https://github.com/gwicho38/lsh/actions/workflows/node.js.yml)
|
|
11
11
|
[](https://opensource.org/licenses/MIT)
|
|
12
12
|
|
|
13
|
-
## What's New in v3.
|
|
13
|
+
## What's New in v3.5.x
|
|
14
14
|
|
|
15
|
-
- **
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **Test Coverage** - 59 new tests for constants modules (142 total constants tests)
|
|
19
|
-
- **Code Quality** - Lint warnings reduced from 744 to 550 (26.1% reduction)
|
|
15
|
+
- **Focused on secrets** - Removed the dormant pre-pivot platform code (SaaS multi-tenant, job/cron daemon, Supabase/Postgres persistence). LSH is now purely an encrypted `.env` sync tool over IPFS.
|
|
16
|
+
- **Dependency modernization** - Express 5, TypeScript 6, ESLint 10, Jest 30.
|
|
17
|
+
- **Hardening** - Bounded network calls (no hangs), command-injection fix in the Kubo installer, reliable npm publishing.
|
|
20
18
|
|
|
21
|
-
See [Release Notes](docs/releases/3.
|
|
19
|
+
See [Release Notes](docs/releases/3.5.0.md) for full details.
|
|
22
20
|
|
|
23
21
|
## Quick Start
|
|
24
22
|
|
|
@@ -211,22 +209,17 @@ lsh pull --env prod
|
|
|
211
209
|
|
|
212
210
|
## Automatic Secret Rotation
|
|
213
211
|
|
|
214
|
-
|
|
212
|
+
Schedule rotation with any external scheduler (system `cron`, a CI job, etc.) that
|
|
213
|
+
runs your rotation script and then pushes the updated secrets:
|
|
215
214
|
|
|
216
215
|
```bash
|
|
217
|
-
#
|
|
218
|
-
lsh
|
|
219
|
-
|
|
220
|
-
# Schedule monthly key rotation
|
|
221
|
-
lsh cron add \
|
|
222
|
-
--name "rotate-keys" \
|
|
223
|
-
--schedule "0 0 1 * *" \
|
|
224
|
-
--command "./scripts/rotate.sh && lsh push"
|
|
225
|
-
|
|
226
|
-
# List scheduled jobs
|
|
227
|
-
lsh cron list
|
|
216
|
+
# Example: monthly rotation via crontab — `crontab -e`
|
|
217
|
+
0 0 1 * * cd /path/to/project && ./scripts/rotate.sh && lsh push
|
|
228
218
|
```
|
|
229
219
|
|
|
220
|
+
Or as a scheduled CI job (GitHub Actions, etc.) that runs the script and `lsh push`.
|
|
221
|
+
LSH focuses on encrypting and syncing the `.env`; the rotation policy/schedule is yours.
|
|
222
|
+
|
|
230
223
|
## Export Formats
|
|
231
224
|
|
|
232
225
|
Export secrets in multiple formats:
|
|
@@ -322,27 +315,6 @@ lsh push
|
|
|
322
315
|
- **[Installation](docs/deployment/INSTALL.md)** - Detailed installation
|
|
323
316
|
- **[Developer Guide](CLAUDE.md)** - Contributing to LSH
|
|
324
317
|
|
|
325
|
-
## Advanced Features
|
|
326
|
-
|
|
327
|
-
LSH includes a full automation platform:
|
|
328
|
-
|
|
329
|
-
- **Persistent Daemon** - Background job execution
|
|
330
|
-
- **Cron Scheduling** - Time-based job scheduling
|
|
331
|
-
- **REST API** - HTTP API for integration
|
|
332
|
-
- **CI/CD Webhooks** - GitHub/GitLab webhook support
|
|
333
|
-
- **POSIX Shell** - Interactive shell with ZSH features
|
|
334
|
-
|
|
335
|
-
```bash
|
|
336
|
-
# Start daemon
|
|
337
|
-
lsh daemon start
|
|
338
|
-
|
|
339
|
-
# API server
|
|
340
|
-
LSH_API_KEY=xxx lsh api start --port 3030
|
|
341
|
-
|
|
342
|
-
# Interactive shell
|
|
343
|
-
lsh -i
|
|
344
|
-
```
|
|
345
|
-
|
|
346
318
|
## Configuration
|
|
347
319
|
|
|
348
320
|
### Environment Variables
|
|
@@ -355,14 +327,10 @@ LSH_SECRETS_KEY=<your-encryption-key>
|
|
|
355
327
|
# (configure once with: ipfs pin remote service add <name> <endpoint> <key>)
|
|
356
328
|
LSH_PIN_SERVICE=<service-name>
|
|
357
329
|
|
|
358
|
-
# Optional -
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
# Optional - API server
|
|
363
|
-
LSH_API_ENABLED=true
|
|
364
|
-
LSH_API_PORT=3030
|
|
365
|
-
LSH_API_KEY=<key>
|
|
330
|
+
# Optional - pointer discovery backends, comma-separated in priority order.
|
|
331
|
+
# Default 'w3name,ipns': durable w3name (signed IPNS via name.web3.storage, no
|
|
332
|
+
# account, no DHT TTL) with IPNS-over-DHT fallback. Set 'ipns' for DHT-only.
|
|
333
|
+
LSH_DISCOVERY=w3name,ipns
|
|
366
334
|
```
|
|
367
335
|
|
|
368
336
|
### Configuration Files
|
package/dist/constants/config.js
CHANGED
|
@@ -33,6 +33,9 @@ export const ENV_VARS = {
|
|
|
33
33
|
// Name of the kubo remote pinning service to use for durable sync
|
|
34
34
|
// (configured via `ipfs pin remote service add <name> <endpoint> <key>`).
|
|
35
35
|
LSH_PIN_SERVICE: 'LSH_PIN_SERVICE',
|
|
36
|
+
// Discovery backend(s) for the key→CID pointer, comma-separated in priority
|
|
37
|
+
// order. Supported: 'w3name' (durable, hosted), 'ipns' (DHT). Default: 'w3name,ipns'.
|
|
38
|
+
LSH_DISCOVERY: 'LSH_DISCOVERY',
|
|
36
39
|
// Feature flags
|
|
37
40
|
LSH_LOCAL_STORAGE_QUIET: 'LSH_LOCAL_STORAGE_QUIET',
|
|
38
41
|
LSH_V1_COMPAT: 'LSH_V1_COMPAT',
|
|
@@ -143,4 +146,6 @@ export const DEFAULTS = {
|
|
|
143
146
|
IPNS_RESOLVE_TIMEOUT_MS: 30000, // 30s for DHT lookup
|
|
144
147
|
IPNS_KEY_PREFIX: 'lsh-',
|
|
145
148
|
IPNS_KEY_DERIVATION_CONTEXT: 'lsh-ipns-v1',
|
|
149
|
+
// Default discovery backends (priority order): durable w3name, then DHT-IPNS fallback.
|
|
150
|
+
DISCOVERY_BACKENDS: 'w3name,ipns',
|
|
146
151
|
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discovery backend seam.
|
|
3
|
+
*
|
|
4
|
+
* "Discovery" = mapping a key-derived pointer to the latest content CID
|
|
5
|
+
* (publish on push, resolve on pull). Today the only backend is IPNS over the
|
|
6
|
+
* DHT (`IpnsDiscoveryBackend`), which is what LSH has always used — this module
|
|
7
|
+
* just extracts that behind an interface so additional backends (e.g. a durable
|
|
8
|
+
* w3name pointer; see issue #194) can be added without touching the storage layer.
|
|
9
|
+
*
|
|
10
|
+
* This is a behaviour-preserving refactor: `getDiscoveryBackend()` returns the
|
|
11
|
+
* IPNS backend, identical to the previous inline logic.
|
|
12
|
+
*/
|
|
13
|
+
import { deriveKeyInfo as defaultDeriveKeyInfo, ensureKeyImported as defaultEnsureKeyImported, } from './ipns-key-manager.js';
|
|
14
|
+
import { w3namePublish as defaultW3namePublish, w3nameResolve as defaultW3nameResolve, } from './w3name-pointer.js';
|
|
15
|
+
import { ENV_VARS, DEFAULTS } from '../constants/config.js';
|
|
16
|
+
import { createLogger } from './logger.js';
|
|
17
|
+
const logger = createLogger('Discovery');
|
|
18
|
+
/**
|
|
19
|
+
* IPNS-over-DHT discovery: derive a deterministic ed25519 key from the secrets
|
|
20
|
+
* key, import it into the local Kubo keystore, and publish/resolve via IPNS.
|
|
21
|
+
*/
|
|
22
|
+
export class IpnsDiscoveryBackend {
|
|
23
|
+
ipfsSync;
|
|
24
|
+
deps;
|
|
25
|
+
id = 'ipns';
|
|
26
|
+
constructor(ipfsSync, deps = {
|
|
27
|
+
deriveKeyInfo: defaultDeriveKeyInfo,
|
|
28
|
+
ensureKeyImported: defaultEnsureKeyImported,
|
|
29
|
+
}) {
|
|
30
|
+
this.ipfsSync = ipfsSync;
|
|
31
|
+
this.deps = deps;
|
|
32
|
+
}
|
|
33
|
+
async publish({ secretsKey, repoName, env, cid }) {
|
|
34
|
+
const keyInfo = this.deps.deriveKeyInfo(secretsKey, repoName, env);
|
|
35
|
+
const ipnsName = await this.deps.ensureKeyImported(this.ipfsSync.getApiUrl(), keyInfo);
|
|
36
|
+
if (!ipnsName) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return this.ipfsSync.publishToIPNS(cid, keyInfo.keyName);
|
|
40
|
+
}
|
|
41
|
+
async resolve({ secretsKey, repoName, env }) {
|
|
42
|
+
const keyInfo = this.deps.deriveKeyInfo(secretsKey, repoName, env);
|
|
43
|
+
const ipnsName = await this.deps.ensureKeyImported(this.ipfsSync.getApiUrl(), keyInfo);
|
|
44
|
+
if (!ipnsName) {
|
|
45
|
+
return { cid: null, name: null };
|
|
46
|
+
}
|
|
47
|
+
const cid = await this.ipfsSync.resolveIPNS(ipnsName);
|
|
48
|
+
return { cid, name: ipnsName };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Durable discovery via w3name (Storacha). Publishes/resolves a signed IPNS
|
|
53
|
+
* record hosted at name.web3.storage — survives offline nodes, no DHT TTL, no
|
|
54
|
+
* account. Uses the SAME seed-derived IPNS name as the `ipns` backend.
|
|
55
|
+
*/
|
|
56
|
+
export class W3nameDiscoveryBackend {
|
|
57
|
+
deps;
|
|
58
|
+
id = 'w3name';
|
|
59
|
+
constructor(deps = {
|
|
60
|
+
deriveKeyInfo: defaultDeriveKeyInfo,
|
|
61
|
+
publish: defaultW3namePublish,
|
|
62
|
+
resolve: defaultW3nameResolve,
|
|
63
|
+
}) {
|
|
64
|
+
this.deps = deps;
|
|
65
|
+
}
|
|
66
|
+
async publish({ secretsKey, repoName, env, cid }) {
|
|
67
|
+
const { seed } = this.deps.deriveKeyInfo(secretsKey, repoName, env);
|
|
68
|
+
return this.deps.publish(seed, cid);
|
|
69
|
+
}
|
|
70
|
+
async resolve({ secretsKey, repoName, env }) {
|
|
71
|
+
const { seed } = this.deps.deriveKeyInfo(secretsKey, repoName, env);
|
|
72
|
+
return this.deps.resolve(seed);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Composes backends in priority order. Publish dual-writes to all (best-effort —
|
|
77
|
+
* a failure in one doesn't block the others). Resolve tries each in order and
|
|
78
|
+
* returns the first hit, so a durable backend wins but a fallback still works.
|
|
79
|
+
*/
|
|
80
|
+
export class CompositeDiscoveryBackend {
|
|
81
|
+
backends;
|
|
82
|
+
id;
|
|
83
|
+
constructor(backends) {
|
|
84
|
+
this.backends = backends;
|
|
85
|
+
this.id = backends.map((b) => b.id).join('+');
|
|
86
|
+
}
|
|
87
|
+
async publish(params) {
|
|
88
|
+
let firstName = null;
|
|
89
|
+
for (const backend of this.backends) {
|
|
90
|
+
try {
|
|
91
|
+
const name = await backend.publish(params);
|
|
92
|
+
if (name && !firstName) {
|
|
93
|
+
firstName = name;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
logger.warn(`Discovery publish via "${backend.id}" failed: ${error.message}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return firstName;
|
|
101
|
+
}
|
|
102
|
+
async resolve(params) {
|
|
103
|
+
let firstName = null;
|
|
104
|
+
for (const backend of this.backends) {
|
|
105
|
+
try {
|
|
106
|
+
const result = await backend.resolve(params);
|
|
107
|
+
if (result.name && !firstName) {
|
|
108
|
+
firstName = result.name;
|
|
109
|
+
}
|
|
110
|
+
if (result.cid) {
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
logger.warn(`Discovery resolve via "${backend.id}" failed: ${error.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { cid: null, name: firstName };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Construct a single backend by id, or null if unknown. */
|
|
122
|
+
function buildBackend(id, ipfsSync) {
|
|
123
|
+
switch (id) {
|
|
124
|
+
case 'ipns':
|
|
125
|
+
return new IpnsDiscoveryBackend(ipfsSync);
|
|
126
|
+
case 'w3name':
|
|
127
|
+
return new W3nameDiscoveryBackend();
|
|
128
|
+
default:
|
|
129
|
+
logger.warn(`Unknown discovery backend "${id}" — ignoring.`);
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Select discovery backend(s) from `LSH_DISCOVERY` (comma-separated, priority
|
|
135
|
+
* order; default 'w3name,ipns'). Returns a single backend or a composite.
|
|
136
|
+
* Falls back to IPNS if the setting resolves to nothing valid.
|
|
137
|
+
*/
|
|
138
|
+
export function getDiscoveryBackend(ipfsSync) {
|
|
139
|
+
const setting = process.env[ENV_VARS.LSH_DISCOVERY] || DEFAULTS.DISCOVERY_BACKENDS;
|
|
140
|
+
const backends = setting
|
|
141
|
+
.split(',')
|
|
142
|
+
.map((s) => s.trim().toLowerCase())
|
|
143
|
+
.filter(Boolean)
|
|
144
|
+
.map((id) => buildBackend(id, ipfsSync))
|
|
145
|
+
.filter((b) => b !== null);
|
|
146
|
+
if (backends.length === 0) {
|
|
147
|
+
return new IpnsDiscoveryBackend(ipfsSync);
|
|
148
|
+
}
|
|
149
|
+
return backends.length === 1 ? backends[0] : new CompositeDiscoveryBackend(backends);
|
|
150
|
+
}
|
|
@@ -13,7 +13,7 @@ import * as os from 'os';
|
|
|
13
13
|
import * as crypto from 'crypto';
|
|
14
14
|
import { createLogger } from './logger.js';
|
|
15
15
|
import { getIPFSSync } from './ipfs-sync.js';
|
|
16
|
-
import {
|
|
16
|
+
import { getDiscoveryBackend } from './discovery-backend.js';
|
|
17
17
|
import { ENV_VARS, DEFAULTS } from '../constants/index.js';
|
|
18
18
|
import { extractErrorMessage } from './lsh-error.js';
|
|
19
19
|
const logger = createLogger('IPFSSecretsStorage');
|
|
@@ -95,16 +95,13 @@ export class IPFSSecretsStorage {
|
|
|
95
95
|
try {
|
|
96
96
|
const repoName = gitRepo || DEFAULTS.DEFAULT_ENVIRONMENT;
|
|
97
97
|
const env = environment || DEFAULTS.DEFAULT_ENVIRONMENT;
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
await this.saveMetadata();
|
|
106
|
-
logger.info(` 🔗 Published to IPNS: ${publishedName}`);
|
|
107
|
-
}
|
|
98
|
+
const discovery = getDiscoveryBackend(ipfsSync);
|
|
99
|
+
const publishedName = await discovery.publish({ secretsKey: encryptionKey, repoName, env, cid });
|
|
100
|
+
if (publishedName) {
|
|
101
|
+
metadata.ipns_name = publishedName;
|
|
102
|
+
this.metadata[this.getMetadataKey(gitRepo, environment)] = metadata;
|
|
103
|
+
await this.saveMetadata();
|
|
104
|
+
logger.info(` 🔗 Published to ${discovery.id}: ${publishedName}`);
|
|
108
105
|
}
|
|
109
106
|
}
|
|
110
107
|
catch (error) {
|
|
@@ -140,11 +137,12 @@ export class IPFSSecretsStorage {
|
|
|
140
137
|
try {
|
|
141
138
|
const repoName = gitRepo || DEFAULTS.DEFAULT_ENVIRONMENT;
|
|
142
139
|
const env = environment || DEFAULTS.DEFAULT_ENVIRONMENT;
|
|
143
|
-
const
|
|
144
|
-
const
|
|
140
|
+
const discovery = getDiscoveryBackend(ipfsSync);
|
|
141
|
+
const resolved = await discovery.resolve({ secretsKey: encryptionKey, repoName, env });
|
|
142
|
+
const ipnsName = resolved.name;
|
|
145
143
|
if (ipnsName) {
|
|
146
144
|
logger.info(` 🔍 Resolving via IPNS: ${ipnsName.substring(0, 20)}...`);
|
|
147
|
-
resolvedCid =
|
|
145
|
+
resolvedCid = resolved.cid;
|
|
148
146
|
if (resolvedCid) {
|
|
149
147
|
logger.info(` ✅ IPNS resolved to CID: ${resolvedCid}`);
|
|
150
148
|
// Update local metadata
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* w3name pointer ops (issue #194, Phase 2).
|
|
3
|
+
*
|
|
4
|
+
* Durable IPNS via Storacha's w3name: publish a signed record mapping the
|
|
5
|
+
* key-derived IPNS name → `/ipfs/<cid>`, hosted at name.web3.storage (no account,
|
|
6
|
+
* no auth). The name is IDENTICAL to the one Kubo derives from the same seed
|
|
7
|
+
* (verified in the Phase-2 spike), so this shares one logical pointer with the
|
|
8
|
+
* IPNS-over-DHT backend.
|
|
9
|
+
*
|
|
10
|
+
* w3name + @libp2p/crypto are heavy ESM deps; they are **lazily imported** inside
|
|
11
|
+
* the functions so the CLI / unit tests don't load them unless w3name is actually
|
|
12
|
+
* used.
|
|
13
|
+
*/
|
|
14
|
+
import { createLogger } from './logger.js';
|
|
15
|
+
const logger = createLogger('W3namePointer');
|
|
16
|
+
const PUBLISH_TIMEOUT_MS = 15000;
|
|
17
|
+
const RESOLVE_TIMEOUT_MS = 10000;
|
|
18
|
+
function withTimeout(p, ms, label) {
|
|
19
|
+
return Promise.race([
|
|
20
|
+
p,
|
|
21
|
+
new Promise((_, reject) => {
|
|
22
|
+
const timer = setTimeout(() => reject(new Error(`w3name ${label} timed out after ${ms}ms`)), ms);
|
|
23
|
+
timer.unref?.();
|
|
24
|
+
}),
|
|
25
|
+
]);
|
|
26
|
+
}
|
|
27
|
+
/** Build a w3name WritableName from a 32-byte ed25519 seed (deterministic). */
|
|
28
|
+
async function writableFromSeed(seed) {
|
|
29
|
+
const [{ generateKeyPairFromSeed, privateKeyToProtobuf }, Name] = await Promise.all([
|
|
30
|
+
import('@libp2p/crypto/keys'),
|
|
31
|
+
import('w3name'),
|
|
32
|
+
]);
|
|
33
|
+
const priv = await generateKeyPairFromSeed('Ed25519', new Uint8Array(seed));
|
|
34
|
+
const name = await Name.from(privateKeyToProtobuf(priv));
|
|
35
|
+
return { Name, name };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Publish `cid` under the seed-derived w3name pointer. Returns the IPNS name.
|
|
39
|
+
* Handles IPNS sequencing: increments from the current revision if one exists,
|
|
40
|
+
* else publishes v0.
|
|
41
|
+
*/
|
|
42
|
+
export async function w3namePublish(seed, cid) {
|
|
43
|
+
const { Name, name } = await writableFromSeed(seed);
|
|
44
|
+
const value = `/ipfs/${cid}`;
|
|
45
|
+
let revision;
|
|
46
|
+
try {
|
|
47
|
+
const current = await withTimeout(Name.resolve(name), RESOLVE_TIMEOUT_MS, 'resolve(pre-publish)');
|
|
48
|
+
revision = await Name.increment(current, value);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// No existing record (or unresolvable) → start a fresh revision.
|
|
52
|
+
revision = await Name.v0(name, value);
|
|
53
|
+
}
|
|
54
|
+
await withTimeout(Name.publish(revision, name.key), PUBLISH_TIMEOUT_MS, 'publish');
|
|
55
|
+
logger.debug(`Published to w3name: ${name.toString()} → ${value}`);
|
|
56
|
+
return name.toString();
|
|
57
|
+
}
|
|
58
|
+
/** Resolve the latest CID for the seed-derived w3name pointer. */
|
|
59
|
+
export async function w3nameResolve(seed) {
|
|
60
|
+
const { Name, name } = await writableFromSeed(seed);
|
|
61
|
+
const nameStr = name.toString();
|
|
62
|
+
try {
|
|
63
|
+
const revision = await withTimeout(Name.resolve(Name.parse(nameStr)), RESOLVE_TIMEOUT_MS, 'resolve');
|
|
64
|
+
const value = revision.value; // "/ipfs/<cid>"
|
|
65
|
+
const cid = value.startsWith('/ipfs/') ? value.slice('/ipfs/'.length) : null;
|
|
66
|
+
return { cid, name: nameStr };
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
logger.debug(`w3name resolve failed for ${nameStr}: ${error.message}`);
|
|
70
|
+
return { cid: null, name: nameStr };
|
|
71
|
+
}
|
|
72
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lsh-framework",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "Simple, cross-platform encrypted secrets manager with automatic sync, IPFS audit logs, and multi-environment support. Just run lsh sync and start managing your secrets.",
|
|
5
5
|
"main": "dist/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"package.json"
|
|
64
64
|
],
|
|
65
65
|
"dependencies": {
|
|
66
|
+
"@libp2p/crypto": "^5.1.19",
|
|
66
67
|
"@supabase/supabase-js": "^2.57.4",
|
|
67
68
|
"bcrypt": "^6.0.0",
|
|
68
69
|
"chalk": "^5.3.0",
|
|
@@ -80,7 +81,8 @@
|
|
|
80
81
|
"ora": "^9.0.0",
|
|
81
82
|
"pg": "^8.16.3",
|
|
82
83
|
"proper-lockfile": "^4.1.2",
|
|
83
|
-
"smol-toml": "^1.3.1"
|
|
84
|
+
"smol-toml": "^1.3.1",
|
|
85
|
+
"w3name": "^1.1.3"
|
|
84
86
|
},
|
|
85
87
|
"devDependencies": {
|
|
86
88
|
"@eslint/js": "^10.0.1",
|