octwin-cli 0.8.0 → 0.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/CHANGELOG.md CHANGED
@@ -5,6 +5,27 @@ Format: [Keep a Changelog](https://keepachangelog.com/) — newest first, bucket
5
5
  **Added · Changed · Deprecated · Removed · Fixed · Security**. The platform-wide view lives in the
6
6
  repo root [`CHANGELOG.md`](../../CHANGELOG.md); this file is the CLI-only cut that ships with the package.
7
7
 
8
+ ## [0.8.1] - 2026-08-27
9
+
10
+ ### Fixed
11
+ - **Logging in made `platform-kb pull` WORSE.** The command preferred the tenant-scoped route
12
+ whenever a token was saved, and that route is guarded by `pack:deploy` — so a developer holding a
13
+ token without that scope got a **403 on a pull that succeeds with no token at all**. It is the
14
+ likeliest token a new developer holds: no console preset could even reach `pack:deploy` before
15
+ 2026-08-26. `platform-kb` now always uses the anonymous route and never sends a credential.
16
+ `--token` is accepted and ignored so existing scripts keep working.
17
+
18
+ The platform serves the *same* bundle from all three of its views — its route file states the
19
+ payload is identical and tenant-independent — so the authed route returned nothing extra and the
20
+ credential could only ever subtract. The two reasons the old code gave did not survive checking:
21
+ "keeps the author's own instance the source of truth" is vacuous (both routes are the same
22
+ instance), and "works against platforms that predate the public rung" is a backward-compatibility
23
+ shim this codebase does not carry.
24
+ - **The drift nudge now works without a token.** It polled through the same authed route, so an
25
+ author inspecting data with a narrow token was told, every time, that it *could not* check —
26
+ advice in place of the signal. Both the poll and the apology are gone: the poll is anonymous, so
27
+ it simply works.
28
+
8
29
  ## [0.8.0] - 2026-08-27
9
30
 
10
31
  ### Added
package/dist/index.js CHANGED
@@ -587,11 +587,12 @@ function diffKbIndex(prev, next) {
587
587
  }
588
588
  /**
589
589
  * Fetch the platform's KB identity (`?meta=1`) — the cheap poll behind both the
590
- * drift nudge and `--if-stale`. Returns null on anything that is not a clean
590
+ * drift nudge and `--if-stale`. Returns not-ok on anything that is not a clean
591
591
  * answer; the caller decides whether that is worth a word.
592
592
  *
593
- * `notAuthorized` is reported separately because it is the one failure with a
594
- * fix the author can act on.
593
+ * There is no `notAuthorized` case any more. `kbEndpoint` is anonymous, so this
594
+ * poll cannot be refused for lack of a scope — which used to be its most common
595
+ * failure, and the reason the drift nudge below carried a whole branch of advice.
595
596
  */
596
597
  async function fetchKbMeta(t, timeoutMs = 2_000) {
597
598
  const ep = kbEndpoint(t);
@@ -602,12 +603,12 @@ async function fetchKbMeta(t, timeoutMs = 2_000) {
602
603
  // socket checked out of the pool, and this poll runs on the way to a possible `exitNow` —
603
604
  // a held socket at exit is a pending libuv handle.
604
605
  await res.arrayBuffer().catch(() => undefined);
605
- return { ok: false, notAuthorized: res.status === 401 || res.status === 403 };
606
+ return { ok: false };
606
607
  }
607
608
  return { ok: true, meta: await res.json() };
608
609
  }
609
610
  catch {
610
- return { ok: false, notAuthorized: false };
611
+ return { ok: false };
611
612
  }
612
613
  }
613
614
  /** Nudge (to stderr) when the platform's capability KB has changed since the last
@@ -628,24 +629,20 @@ async function kbStaleNotice(flags) {
628
629
  const local = readLocalKb(packDir);
629
630
  if (!local?.content_hash)
630
631
  return []; // never pulled → the skill already says to pull
631
- const t = resolveTargetOrNull(flags);
632
- if (!t)
632
+ // URL only, deliberately: the KB poll is anonymous, so requiring a token here would
633
+ // silence the nudge for exactly the authors who most need it. This used to call
634
+ // `resolveTargetOrNull` (url AND token) against the tenant-scoped route, which needs
635
+ // `pack:deploy` — so an author inspecting data with a narrow (`records:read`-only)
636
+ // token got NO drift signal at all, silently, and a stale reference is precisely what
637
+ // makes an author invent a primitive from memory. The branch that apologised for that
638
+ // is gone with the cause; every remaining failure (offline, timeout, a platform
639
+ // serving no reference) stays silent, because observing must not break a command.
640
+ const t = readTarget(flags);
641
+ if (!t.url)
633
642
  return [];
634
643
  const polled = await fetchKbMeta(t);
635
- if (!polled.ok) {
636
- // The tenant-scoped meta poll needs `pack:deploy`, but this nudge rides on every
637
- // networked command — so an author inspecting data with a narrow (`records:read`-only)
638
- // token got NO drift signal at all, silently, and a stale reference is exactly what
639
- // makes an author invent a primitive from memory. Say so once; stay silent for every
640
- // other failure (offline, timeout, a platform without the route).
641
- if (polled.notAuthorized) {
642
- return [
643
- '\nⓘ can\'t check whether the platform capability reference drifted — that token lacks `pack:deploy`.',
644
- ' Check it without a token: octwin platform-kb --check (or refresh: octwin platform-kb --token oct_…)',
645
- ];
646
- }
644
+ if (!polled.ok)
647
645
  return [];
648
- }
649
646
  const meta = polled.meta;
650
647
  if (meta.content_hash && meta.content_hash !== local.content_hash) {
651
648
  // Per-entry summary (now that the index carries per-entry hashes) — the
@@ -1207,20 +1204,32 @@ function resolveTargetOrNull(flags) {
1207
1204
  /**
1208
1205
  * Where to read the platform capability reference from, and how.
1209
1206
  *
1210
- * The KB is tenant-independent platform stdlib, and the platform now serves it
1211
- * anonymously at `/api/public/octwin-platform-kb` precisely so a COLD START does
1212
- * not need a login: `.octwin/` is gitignored, so a fresh clone or a fresh
1213
- * Claude Code session has no reference at all, and an authoring agent's first
1214
- * useful question came after a credential round-trip it had no way to satisfy.
1207
+ * ALWAYS the anonymous route. The KB is tenant-independent platform stdlib, and
1208
+ * the platform serves the SAME bundle from all three of its views -- its own
1209
+ * route file says so twice ("THREE views of the SAME `getPlatformKbBundle()`";
1210
+ * "the payload is identical") and forbids tenant data ever entering the reader.
1211
+ * So the authed route returns nothing extra, and asking for a credential to read
1212
+ * it can only ever subtract.
1213
+ *
1214
+ * It subtracted, measurably. This used to prefer the tenant-scoped route
1215
+ * WHENEVER a token was saved, and that route is guarded by `pack:deploy` -- so a
1216
+ * developer holding a token WITHOUT that scope got a 403 on a pull that would
1217
+ * have succeeded with no token at all. Having logged in made the CLI strictly
1218
+ * worse than not having logged in, on the very first command an author runs. No
1219
+ * console preset could even reach `pack:deploy` before 2026-08-26, so that was
1220
+ * the likeliest token a new developer held.
1221
+ *
1222
+ * The two arguments the old comment gave for preferring a token did not survive
1223
+ * being checked: "keeps the author's own instance the source of truth" is
1224
+ * vacuous (both routes are `t.url` -- the same instance), and "works against
1225
+ * platforms that predate the public rung" is a backward-compatibility shim,
1226
+ * which this codebase does not carry.
1215
1227
  *
1216
- * A token still WINS when one is available: the tenant-scoped route is the one
1217
- * that has always existed, it works against platforms that predate the public
1218
- * rung, and using it keeps the author's own instance the source of truth.
1228
+ * `--token` is still ACCEPTED here, and ignored, so a scripted `platform-kb pull
1229
+ * --token …` keeps working instead of turning into an unknown-flag error.
1219
1230
  */
1220
1231
  function kbEndpoint(t) {
1221
- return t.token
1222
- ? { url: `${t.url}/api/self/t/octwin-platform-kb`, headers: authHeaders(t), anonymous: false }
1223
- : { url: `${t.url}/api/public/octwin-platform-kb`, headers: {}, anonymous: true };
1232
+ return { url: `${t.url}/api/public/octwin-platform-kb`, headers: {} };
1224
1233
  }
1225
1234
  /** The raw resolution both wrappers share — may return empty url/token. */
1226
1235
  function readTarget(flags) {
@@ -1756,9 +1765,7 @@ async function cmdPlatformKb(flags) {
1756
1765
  const local = readLocalKb(packDir);
1757
1766
  const polled = await fetchKbMeta(t, 10_000);
1758
1767
  if (!polled.ok) {
1759
- console.error(polled.notAuthorized
1760
- ? '✗ cannot check — the platform refused the token, and this instance serves no anonymous reference.'
1761
- : `✗ cannot check — ${url} did not answer.`);
1768
+ console.error(`✗ cannot check — ${url} did not answer, or serves no capability reference.`);
1762
1769
  exitNow(1);
1763
1770
  }
1764
1771
  const remote = polled.meta.content_hash;
@@ -1787,7 +1794,7 @@ async function cmdPlatformKb(flags) {
1787
1794
  return;
1788
1795
  }
1789
1796
  }
1790
- console.log(`→ Pulling the platform capability reference from ${url}${ep.anonymous ? ' (anonymous — no token needed for the reference)' : ''} …`);
1797
+ console.log(`→ Pulling the platform capability reference from ${url} (no token needed for the reference) …`);
1791
1798
  const res = await fetchOrDie(ep.url, { headers: ep.headers }, 'platform-kb pull');
1792
1799
  const text = await res.text();
1793
1800
  if (!res.ok) {
@@ -5162,14 +5169,15 @@ octwin projects rm <slug> [--yes]
5162
5169
  INDEX.md (the corpus) · SYMBOLS.md (every name -> its file; grep this) ·
5163
5170
  OUTLINE.md (every heading with its line number).
5164
5171
 
5165
- NO TOKEN NEEDED — the reference is platform stdlib and is served anonymously.
5166
- A token is used when you have one (it also works against older platforms).
5172
+ NO TOKEN NEEDED — the reference is platform stdlib and is served anonymously,
5173
+ and this command never sends one. --token is accepted and ignored, so an older
5174
+ script that passes it keeps working.
5167
5175
 
5168
5176
  --if-stale poll the platform's content_hash first and skip the download when
5169
5177
  nothing changed. Cheap enough to run at the start of every session.
5170
5178
  --check report only, write nothing. Exit 0 = current, 2 = stale or never
5171
- pulled, 1 = could not tell (offline / refused). For scripts and
5172
- agent loops that want to branch without parsing prose.`,
5179
+ pulled, 1 = could not tell (offline / no reference served). For
5180
+ scripts and agent loops that want to branch without parsing prose.`,
5173
5181
  test: `octwin test [--dir .]
5174
5182
  Alias for \`octwin validate --remote\` — the full platform check.`,
5175
5183
  memos: `octwin memos [--all] [--json]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octwin-cli",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Octwin external-pack developer CLI (by CEQUENS) — scaffold, validate, deploy, and check pure-YAML packs on your tenant.",
5
5
  "type": "module",
6
6
  "bin": {