@knolo/cli 5.2.0 → 5.2.2

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
@@ -47,10 +47,56 @@ An explicit `--registry` value takes precedence over `KNOLO_HUB_URL`. In
47
47
  development mode (`NODE_ENV=development`), the default is
48
48
  `http://localhost:3000`; otherwise the default is `https://hub.knolo.dev`.
49
49
 
50
- The current Hub surface is read-only discovery and verified installation. Local
51
- `knolo add <name> <path>` continues to update a project’s local source
52
- configuration. A Hub pack is installed only after its manifest, Blob bytes,
53
- digest, size, and local Knowledge Image structure have all been verified:
50
+ Hub writes use a dashboard token as an HTTP Bearer credential. GitHub sign-in
51
+ is needed only to mint that token at
52
+ `https://hub.knolo.dev/dashboard/tokens`; `knolo login` is local-only and does
53
+ not call a Hub login or token-minting endpoint:
54
+
55
+ ```bash
56
+ knolo login --token kno_…
57
+ # write requests use: Authorization: Bearer kno_…
58
+ ```
59
+
60
+ `knolo publish` uploads bytes with `@vercel/blob` to the public store, waits
61
+ for verification, creates an attested draft, and releases it. Set
62
+ `PACKS_READ_WRITE_TOKEN` from the production `knolo-hub` Vercel environment
63
+ before publishing; do not substitute `BLOB_READ_WRITE_TOKEN`:
64
+
65
+ ```bash
66
+ cd /path/to/knolo-hub
67
+ npx vercel env pull /tmp/knolo-env-prod --environment=production --yes
68
+ export PACKS_READ_WRITE_TOKEN="$(awk -F= '$1 == "PACKS_READ_WRITE_TOKEN" {sub(/^[^=]*=/, ""); gsub(/^\"|\"$/, ""); print; exit}' /tmp/knolo-env-prod)"
69
+
70
+ knolo publish ./dist/knowledge.knolo \
71
+ --slug refund-policy --version 1.2.0 --license Apache-2.0
72
+ knolo yank acme/refund-policy@1.2.0
73
+ ```
74
+
75
+ The upload uses pathname `sha256/<64-lowercase-hex>.knolo`, public access,
76
+ `addRandomSuffix: false`, `application/octet-stream`, and a one-year cache.
77
+ Only public Blob URLs are accepted because Hub verification fetches the
78
+ artifact itself. The CLI never sends the `kno_…` token to Blob, in a query
79
+ string, cookie, or pack bytes. A 401 usually means the `Bearer` word is
80
+ missing or the token was revoked; it does not mean the Hub write API requires
81
+ a GitHub browser session.
82
+
83
+ The older `@knolo/cli@5.2.0` publish/yank command was an unimplemented stub;
84
+ current versions use the live Hub HTTP sequence. For a direct smoke test of a
85
+ stored credential, request the account endpoint with the same header:
86
+
87
+ ```bash
88
+ curl -sS \
89
+ -H "Authorization: Bearer kno_…" \
90
+ -H "Accept: application/json" \
91
+ https://hub.knolo.dev/api/v1/account
92
+ ```
93
+
94
+ Do not use `POST /api/v1/tokens` as a CLI login endpoint; it is for the
95
+ dashboard/GitHub session that mints tokens.
96
+
97
+ Hub pack installation remains available without credentials and only happens
98
+ after its manifest, Blob bytes, digest, size, and local Knowledge Image
99
+ structure have all been verified:
54
100
 
55
101
  ```bash
56
102
  knolo add acme/refund-policy@1.2.0
@@ -127,11 +173,11 @@ curl -sS "http://127.0.0.1:8787/search?q=billing&k=5"
127
173
 
128
174
  These commands stay local-first:
129
175
 
130
- * No hosted service
131
- * No vector database
132
- * Lexical retrieval by default
133
- * Controller-only pack mutation; 2 MiB pack limit
134
- * Pack state survives canister upgrades
176
+ - No hosted service
177
+ - No vector database
178
+ - Lexical retrieval by default
179
+ - Controller-only pack mutation; 2 MiB pack limit
180
+ - Pack state survives canister upgrades
135
181
 
136
182
  ---
137
183
 
@@ -152,8 +198,8 @@ knolo.config.ts
152
198
 
153
199
  ```ts
154
200
  export default {
155
- input: "./knowledge",
156
- output: "./dist/knowledge.knolo"
201
+ input: './knowledge',
202
+ output: './dist/knowledge.knolo',
157
203
  };
158
204
  ```
159
205
 
@@ -161,12 +207,12 @@ export default {
161
207
 
162
208
  ## 🧱 What the CLI Does
163
209
 
164
- * Parses structured documents
165
- * Normalizes metadata
166
- * Indexes namespaces
167
- * Extracts agent routing profiles
168
- * Validates agent registry
169
- * Generates compact `.knolo` bundle
210
+ - Parses structured documents
211
+ - Normalizes metadata
212
+ - Indexes namespaces
213
+ - Extracts agent routing profiles
214
+ - Validates agent registry
215
+ - Generates compact `.knolo` bundle
170
216
 
171
217
  All builds are deterministic.
172
218
 
@@ -176,11 +222,11 @@ All builds are deterministic.
176
222
 
177
223
  Current v4 features include:
178
224
 
179
- * Routing profile extraction
180
- * Tool policy validation
181
- * Mount-time registry validation
182
- * Deterministic selection logic
183
- * Pack manifests, analyzer identities, retrieval plans, receipts, and evidence spans
225
+ - Routing profile extraction
226
+ - Tool policy validation
227
+ - Mount-time registry validation
228
+ - Deterministic selection logic
229
+ - Pack manifests, analyzer identities, retrieval plans, receipts, and evidence spans
184
230
 
185
231
  ---
186
232
 
@@ -188,16 +234,16 @@ Current v4 features include:
188
234
 
189
235
  KnoLo intentionally avoids:
190
236
 
191
- * Vector databases
192
- * Similarity search
193
- * External inference APIs
237
+ - Vector databases
238
+ - Similarity search
239
+ - External inference APIs
194
240
 
195
241
  This ensures:
196
242
 
197
- * Reproducibility
198
- * Security
199
- * Low memory usage
200
- * Predictable results
243
+ - Reproducibility
244
+ - Security
245
+ - Low memory usage
246
+ - Predictable results
201
247
 
202
248
  ---
203
249
 
package/bin/knolo.mjs CHANGED
@@ -20,7 +20,7 @@ import path from 'node:path';
20
20
  import process from 'node:process';
21
21
  import { fileURLToPath, pathToFileURL } from 'node:url';
22
22
  import { createRequire } from 'node:module';
23
- import { isHubAddInvocation, runHubAdd, runHubInfo, runHubLogin, runHubLogout, runHubPublishStub, runHubSearch, runHubWhoami } from './registry/commands.mjs';
23
+ import { isHubAddInvocation, runHubAdd, runHubInfo, runHubLogin, runHubLogout, runHubPublish, runHubSearch, runHubWhoami, runHubYank } from './registry/commands.mjs';
24
24
 
25
25
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
26
26
  const require = createRequire(import.meta.url);
@@ -34,7 +34,7 @@ const DEFAULT_CONFIG = {
34
34
  };
35
35
  const SUPPORTED_EXTENSIONS = new Set(['.md', '.txt', '.json']);
36
36
  const SKIP_DIRS = new Set(['node_modules', 'dist', '.git', '.dfx', '.cargo-target']);
37
- const SUBCOMMANDS = new Set(['init', 'add', 'build', 'query', 'inspect', 'migrate', 'verify', 'explain', 'diff', 'dev', 'v5', 'search', 'info', 'login', 'whoami', 'logout', 'publish', 'semantic:index', 'semantic:inspect', 'semantic:validate']);
37
+ const SUBCOMMANDS = new Set(['init', 'add', 'build', 'query', 'inspect', 'migrate', 'verify', 'explain', 'diff', 'dev', 'v5', 'search', 'info', 'login', 'whoami', 'logout', 'publish', 'yank', 'semantic:index', 'semantic:inspect', 'semantic:validate']);
38
38
  const ICP_SUBCOMMANDS = new Set(['init', 'build-pack', 'upload', 'query', 'health', 'info', 'clear']);
39
39
  const ICP_TEMPLATE_CANDIDATES = [
40
40
  path.resolve(__dirname, '../templates/icp-knowledge-canister'),
@@ -86,7 +86,8 @@ Commands:
86
86
  login Store a Hub token locally
87
87
  whoami Show the stored Hub token prefix
88
88
  logout Remove the stored Hub token
89
- publish Publish support is waiting for Hub write APIs
89
+ publish Publish a pack with a Hub Bearer token
90
+ yank <pack>@<version> Yank a released pack with a Hub Bearer token
90
91
  build Build a .knolo pack from configured sources
91
92
  query <question> Query a built pack and print top hits
92
93
  inspect <pack> Inspect pack format, sections, and manifest
@@ -111,10 +112,11 @@ function printCommandHelp(command) {
111
112
  add: 'Usage: knolo add <name> <path> | <publisher>/<slug>[@<version>] [--out <path>] [--force] [--json] [--registry <url>]',
112
113
  search: 'Usage: knolo search <query> [--format V4|V5] [--license <id>] [--official] [--agents] [--json] [--registry <url>]',
113
114
  info: 'Usage: knolo info <publisher>/<slug> [--json] [--registry <url>]',
114
- login: 'Usage: knolo login [--token kno_…] [--stdin] [--registry <url>]',
115
+ login: 'Usage: knolo login [--token kno_…] [--stdin] [--registry <url>]\n\nStores the raw token locally. Hub write calls send Authorization: Bearer kno_….\nMint tokens at https://hub.knolo.dev/dashboard/tokens; login does not call POST /api/v1/tokens.',
115
116
  whoami: 'Usage: knolo whoami',
116
117
  logout: 'Usage: knolo logout',
117
- publish: 'Usage: knolo publish <pack.knolo> (not available until Hub write APIs support CLI tokens)',
118
+ publish: 'Usage: knolo publish <pack.knolo> --slug <slug> --version <version> --license <SPDX> [--publisher <handle>] [--readme <text>] [--sources <text>] [--intended-use <text>] [--json] [--registry <url>]\n\nUses the stored token as Authorization: Bearer kno_…. Uploads go directly to a public Blob URL; Hub verifies before release.\nOlder @knolo/cli 5.2.0 builds were unimplemented; Hub has always accepted Bearer CLI tokens.',
119
+ yank: 'Usage: knolo yank <publisher>/<slug>@<version> [--json] [--registry <url>]\n\nUses the stored token as Authorization: Bearer kno_…. Yank is owner-only and leaves the public Blob in place.',
118
120
  build: 'Usage: knolo build',
119
121
  query: 'Usage: knolo query <question> [--pack <path>] [--k <number>] [--receipt <file>] [--json]',
120
122
  inspect: 'Usage: knolo inspect <pack.knolo>',
@@ -1195,7 +1197,8 @@ async function main() {
1195
1197
  if (command === 'login') return await runHubLogin(commandArgs);
1196
1198
  if (command === 'whoami') return await runHubWhoami(commandArgs);
1197
1199
  if (command === 'logout') return await runHubLogout(commandArgs);
1198
- if (command === 'publish') return runHubPublishStub(commandArgs);
1200
+ if (command === 'publish') return runHubPublish(commandArgs);
1201
+ if (command === 'yank') return runHubYank(commandArgs);
1199
1202
 
1200
1203
  const core = await loadCore();
1201
1204
  if (command === 'build') return await cmdBuild(core);
@@ -1,6 +1,6 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import { createWriteStream } from 'node:fs';
3
- import { chmod, mkdir, readFile, rename, unlink } from 'node:fs/promises';
3
+ import { chmod, copyFile, mkdir, readFile, rename, unlink } from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import { Readable } from 'node:stream';
6
6
 
@@ -86,11 +86,27 @@ export async function readArtifact(tempPath) {
86
86
  }
87
87
 
88
88
  export async function installCachedArtifact(tempPath, cachePath) {
89
+ const stagedPath = await stageCachedArtifact(tempPath, cachePath);
90
+ try {
91
+ await rename(stagedPath, cachePath);
92
+ await chmod(cachePath, 0o644);
93
+ return cachePath;
94
+ } finally {
95
+ await unlink(stagedPath).catch(() => {});
96
+ }
97
+ }
98
+
99
+ export async function stageCachedArtifact(sourcePath, cachePath) {
89
100
  await mkdir(path.dirname(cachePath), { recursive: true });
90
- await chmod(tempPath, 0o644);
91
- await rename(tempPath, cachePath);
92
- await chmod(cachePath, 0o644);
93
- return cachePath;
101
+ const stagedPath = `${cachePath}.tmp-${process.pid}-${randomUUID()}`;
102
+ try {
103
+ await copyFile(sourcePath, stagedPath);
104
+ await chmod(stagedPath, 0o644);
105
+ return stagedPath;
106
+ } catch (error) {
107
+ await unlink(stagedPath).catch(() => {});
108
+ throw error;
109
+ }
94
110
  }
95
111
 
96
112
  async function writeChunk(stream, chunk) {