@lifeaitools/clauth 1.7.2 → 1.8.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 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
- clauth is a **private** repo, so we do **not** use GitHub Actions to publish —
204
- Actions bill for minutes on private repos. (Reserve Actions for *public* repos,
205
- where they're free.) Publishing is **webhook-driven**, with a manual fallback.
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 — the tag push triggers the publish webhook
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. Verify it published (direct registry check — bypasses npm's cache)
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
- # 4. Fallback — if the webhook did NOT publish (registry still shows the old
219
- # version), publish manually with the vault npm token:
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
- **NEVER** commit a version bump without taggingthe tag push is what triggers
225
- the publish webhook. If the webhook fails silently, the usual cause is a **stale
226
- npm token on the webhook receiver** (it holds an env copy, not the live vault
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
 
@@ -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/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
  // ──────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "1.7.2",
3
+ "version": "1.8.0",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {