@lifeaitools/clauth 1.7.2 → 1.8.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 +16 -14
- package/cli/commands/npm.js +59 -0
- package/cli/commands/serve.js +16 -3
- package/cli/index.js +22 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -200,32 +200,34 @@ Tests actual MCP tool calls (not just OAuth + listing).
|
|
|
200
200
|
|
|
201
201
|
## Releasing a New Version (maintainers)
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
Publishing is **manual** — there is no auto-publish. The GitHub Actions
|
|
204
|
+
`publish.yml` workflow was removed on 2026-04-27 (commit `08b7751`); trusted
|
|
205
|
+
publishing via OIDC was tried first (`b2bf08b`→`41d2d92`) and dropped. clauth is
|
|
206
|
+
a **private** repo and GitHub Actions bill for minutes on private repos, so we
|
|
207
|
+
don't run them here (reserve Actions for *public* repos, where they're free).
|
|
206
208
|
|
|
207
209
|
```bash
|
|
208
210
|
# 1. Bump version in package.json
|
|
209
|
-
# 2. Commit + tag + push
|
|
211
|
+
# 2. Commit + tag + push
|
|
210
212
|
git add -A && git commit -m "feat(...): description (vX.Y.Z)"
|
|
211
213
|
git tag vX.Y.Z
|
|
212
214
|
git push && git push --tags
|
|
213
215
|
|
|
214
|
-
# 3.
|
|
216
|
+
# 3. Publish manually with the vault npm token
|
|
217
|
+
clauth npm set-local # writes ~/.npmrc auth from the vault 'npm' service
|
|
218
|
+
npm publish --access public
|
|
219
|
+
|
|
220
|
+
# 4. Verify on the registry (direct check — bypasses npm's local cache)
|
|
215
221
|
curl -s https://registry.npmjs.org/@lifeaitools/clauth \
|
|
216
222
|
| python -c "import sys,json;print(json.load(sys.stdin)['dist-tags'])"
|
|
217
223
|
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
clauth npm set-local # writes ~/.npmrc auth from the vault 'npm' service
|
|
221
|
-
npm publish --access public
|
|
224
|
+
# 5. Update the running daemon
|
|
225
|
+
curl -s -X POST http://127.0.0.1:52437/restart # picks up new code, stays unlocked
|
|
222
226
|
```
|
|
223
227
|
|
|
224
|
-
**
|
|
225
|
-
|
|
226
|
-
npm
|
|
227
|
-
value) — re-sync the receiver's token to the current vault `npm` service, then
|
|
228
|
-
use the manual publish above to unblock the release.
|
|
228
|
+
**The tag push does NOT publish anything** — you must run step 3. A version bump
|
|
229
|
+
that is committed+tagged but never `npm publish`ed leaves the registry stale
|
|
230
|
+
(symptom: `npm view` still shows the old version after a push).
|
|
229
231
|
|
|
230
232
|
---
|
|
231
233
|
|
package/cli/commands/npm.js
CHANGED
|
@@ -121,3 +121,62 @@ export async function runNpm(action = "help", opts = {}) {
|
|
|
121
121
|
usage();
|
|
122
122
|
throw new Error(`unknown clauth npm action: ${action}`);
|
|
123
123
|
}
|
|
124
|
+
|
|
125
|
+
// ── Guarded publish ──────────────────────────────────────────────────────────
|
|
126
|
+
// Generic, package-agnostic publish that REFUSES to publish unless the package
|
|
127
|
+
// is already committed and pushed to GitHub — so the npm tarball can never be
|
|
128
|
+
// built from uncommitted "dev" code that isn't on the repo. Works for standalone
|
|
129
|
+
// repos and monorepo subpackages (clean-check is scoped to the package's dir).
|
|
130
|
+
export async function runPublish(target, opts = {}) {
|
|
131
|
+
const pkgDir = path.resolve(target || process.cwd());
|
|
132
|
+
const pkgJsonPath = path.join(pkgDir, "package.json");
|
|
133
|
+
if (!fs.existsSync(pkgJsonPath)) throw new Error(`No package.json found at ${pkgDir}`);
|
|
134
|
+
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
|
|
135
|
+
if (!pkg.name || !pkg.version) throw new Error(`package.json at ${pkgDir} is missing name or version`);
|
|
136
|
+
if (pkg.private === true) throw new Error(`${pkg.name} is marked "private": true — refusing to publish`);
|
|
137
|
+
const access = opts.access || pkg.publishConfig?.access || (pkg.name.startsWith("@") ? "public" : undefined);
|
|
138
|
+
|
|
139
|
+
const gitRoot = run("git", ["rev-parse", "--show-toplevel"], { cwd: pkgDir }).stdout?.trim();
|
|
140
|
+
if (!gitRoot) throw new Error(`${pkgDir} is not inside a git repository`);
|
|
141
|
+
const rel = path.relative(gitRoot, pkgDir) || ".";
|
|
142
|
+
|
|
143
|
+
// Guard 1 — nothing uncommitted in the package (else we'd pack dev code).
|
|
144
|
+
const dirty = run("git", ["status", "--porcelain", "--", rel], { cwd: gitRoot }).stdout?.trim();
|
|
145
|
+
if (dirty && !opts.allowDirty) {
|
|
146
|
+
throw new Error(`Refusing to publish ${pkg.name}@${pkg.version}: uncommitted changes in ${rel} would be packed but are NOT on GitHub:\n${dirty}\n\nCommit + push first (or pass --allow-dirty to override).`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Guard 2 — HEAD is on a remote (actually pushed to GitHub).
|
|
150
|
+
const head = run("git", ["rev-parse", "HEAD"], { cwd: gitRoot }).stdout?.trim();
|
|
151
|
+
const onRemote = run("git", ["branch", "-r", "--contains", head], { cwd: gitRoot }).stdout?.trim();
|
|
152
|
+
if (!onRemote && !opts.allowUnpushed) {
|
|
153
|
+
throw new Error(`Refusing to publish ${pkg.name}@${pkg.version}: HEAD ${head.slice(0, 9)} is not on any remote branch — push to GitHub first (or pass --allow-unpushed to override).`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Traceability — warn (don't block) if there's no v<version> tag at HEAD.
|
|
157
|
+
const tags = (run("git", ["tag", "--points-at", "HEAD"], { cwd: gitRoot }).stdout || "").split("\n").map((s) => s.trim()).filter(Boolean);
|
|
158
|
+
if (!tags.includes(`v${pkg.version}`)) {
|
|
159
|
+
console.log(`⚠ No git tag v${pkg.version} at HEAD (traceability only). Tags here: ${tags.join(", ") || "none"}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.log(`${opts.dryRun ? "[dry-run] " : ""}Publishing ${pkg.name}@${pkg.version} from ${rel} @ ${head.slice(0, 9)} (pushed${access ? `, access=${access}` : ""})`);
|
|
163
|
+
|
|
164
|
+
const token = await fetchNpmToken();
|
|
165
|
+
const args = ["publish"];
|
|
166
|
+
if (access) args.push("--access", access);
|
|
167
|
+
if (opts.dryRun) args.push("--dry-run");
|
|
168
|
+
const result = withNpmAuth(token, (npmrc) => run("npm", ["--userconfig", npmrc, ...args], { cwd: pkgDir }));
|
|
169
|
+
printResult(result, { redact: true });
|
|
170
|
+
if (result.status !== 0) { process.exitCode = result.status; throw new Error(`npm publish failed for ${pkg.name}`); }
|
|
171
|
+
if (opts.dryRun) { console.log("Dry run complete — nothing published."); return; }
|
|
172
|
+
|
|
173
|
+
// Verify on the registry directly (bypasses npm's local cache lag).
|
|
174
|
+
try {
|
|
175
|
+
const reg = await fetch(`https://registry.npmjs.org/${pkg.name}`);
|
|
176
|
+
const meta = await reg.json();
|
|
177
|
+
if (meta.versions?.[pkg.version]) console.log(`✓ Verified ${pkg.name}@${pkg.version} on the registry (latest: ${meta["dist-tags"]?.latest}).`);
|
|
178
|
+
else console.log(`⚠ ${pkg.name}@${pkg.version} not visible on the registry yet (propagation lag) — re-check shortly.`);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
console.log(`(could not verify on registry: ${e.message})`);
|
|
181
|
+
}
|
|
182
|
+
}
|
package/cli/commands/serve.js
CHANGED
|
@@ -492,7 +492,7 @@ function openBrowser(url) {
|
|
|
492
492
|
}
|
|
493
493
|
|
|
494
494
|
// ── Dashboard HTML ───────────────────────────────────────────
|
|
495
|
-
function dashboardHtml(port, whitelist, isStaged = false) {
|
|
495
|
+
function dashboardHtml(port, whitelist, isStaged = false, initWriteToken = null) {
|
|
496
496
|
return `<!DOCTYPE html>
|
|
497
497
|
<html lang="en">
|
|
498
498
|
<head>
|
|
@@ -1001,7 +1001,9 @@ function renderSetPanel(name) {
|
|
|
1001
1001
|
}
|
|
1002
1002
|
|
|
1003
1003
|
// ── Boot: check lock state ──────────────────
|
|
1004
|
-
|
|
1004
|
+
// Injected by the daemon when it serves the page on an unlocked vault, so a
|
|
1005
|
+
// fresh load (incl. when auto-unlocked via --pw/boot.key) is write-ready.
|
|
1006
|
+
let writeToken = ${JSON.stringify(initWriteToken)};
|
|
1005
1007
|
|
|
1006
1008
|
function writeHeaders(extra) {
|
|
1007
1009
|
if (!writeToken) throw new Error("Write access requires password unlock in this browser session.");
|
|
@@ -3447,8 +3449,19 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
|
|
|
3447
3449
|
|
|
3448
3450
|
// GET / — built-in web dashboard
|
|
3449
3451
|
if (method === "GET" && reqPath === "/") {
|
|
3452
|
+
// When unlocked, ensure a non-expired write session exists and hand its
|
|
3453
|
+
// token to the page so the dashboard is write-ready on load — fixes the
|
|
3454
|
+
// --pw/boot.key auto-unlock case where the page never saw the unlock
|
|
3455
|
+
// screen and so held no write token. (Consistent with the existing trust
|
|
3456
|
+
// model: /v/<service> already serves raw credentials to localhost when
|
|
3457
|
+
// unlocked, so a same-origin write token is no broader.)
|
|
3458
|
+
let initWriteToken = null;
|
|
3459
|
+
if (password) {
|
|
3460
|
+
if (!writeSession || Date.now() > writeSession.expiresAt) writeSession = makeWriteToken();
|
|
3461
|
+
initWriteToken = writeSession.token;
|
|
3462
|
+
}
|
|
3450
3463
|
res.writeHead(200, { "Content-Type": "text/html", ...CORS });
|
|
3451
|
-
return res.end(dashboardHtml(port, whitelist, isStaged));
|
|
3464
|
+
return res.end(dashboardHtml(port, whitelist, isStaged, initWriteToken));
|
|
3452
3465
|
}
|
|
3453
3466
|
|
|
3454
3467
|
// GET /ping
|
package/cli/index.js
CHANGED
|
@@ -150,7 +150,7 @@ import { runUninstall } from './commands/uninstall.js';
|
|
|
150
150
|
import { runScrub } from './commands/scrub.js';
|
|
151
151
|
import { runServe } from './commands/serve.js';
|
|
152
152
|
import { runCodevelop } from './commands/codevelop.js';
|
|
153
|
-
import { runNpm } from './commands/npm.js';
|
|
153
|
+
import { runNpm, runPublish } from './commands/npm.js';
|
|
154
154
|
|
|
155
155
|
program
|
|
156
156
|
.command('install')
|
|
@@ -229,6 +229,27 @@ program
|
|
|
229
229
|
await runNpm(action, { ...opts, args });
|
|
230
230
|
});
|
|
231
231
|
|
|
232
|
+
// ──────────────────────────────────────────────
|
|
233
|
+
// clauth publish [target]
|
|
234
|
+
// Guarded npm publish for ANY package — refuses to ship code that isn't
|
|
235
|
+
// committed AND pushed to GitHub (prevents npm/repo divergence from dev builds).
|
|
236
|
+
// ──────────────────────────────────────────────
|
|
237
|
+
program
|
|
238
|
+
.command("publish [target]")
|
|
239
|
+
.description("Safely publish an npm package (default: cwd). Refuses unless committed + pushed to GitHub.")
|
|
240
|
+
.option("--dry-run", "Run all guards and pack, but do not publish")
|
|
241
|
+
.option("--access <access>", "npm access: public | restricted")
|
|
242
|
+
.option("--allow-dirty", "Override the uncommitted-changes guard (NOT recommended)")
|
|
243
|
+
.option("--allow-unpushed", "Override the not-pushed-to-remote guard (NOT recommended)")
|
|
244
|
+
.action(async (target, opts) => {
|
|
245
|
+
try {
|
|
246
|
+
await runPublish(target, opts);
|
|
247
|
+
} catch (err) {
|
|
248
|
+
console.error(chalk.red(err.message));
|
|
249
|
+
process.exitCode = 1;
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
232
253
|
// ──────────────────────────────────────────────
|
|
233
254
|
// clauth setup
|
|
234
255
|
// ──────────────────────────────────────────────
|