@knolo/cli 6.2.2 → 6.2.3

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
@@ -13,8 +13,8 @@ reader.
13
13
 
14
14
  `knolo compose`, `knolo attest`, and `knolo verify-attestation` cite local
15
15
  Knowledge Images and write an attestation sidecar. This tree's `@knolo/cli`
16
- package file is `6.2.2` and depends on `@knolo/core` `^6.2.0`. npm
17
- `latest` is `6.2.1` until `6.2.2` is published. They do not fetch Hub. Verification
16
+ package file is `6.2.3` and depends on `@knolo/core` `^6.2.0`. npm
17
+ `latest` is `6.2.2` until `6.2.3` is published. They do not fetch Hub. Verification
18
18
  recomputes `compositionRoot` and checks bundled excerpt bytes. A keyring key
19
19
  must fall inside `notBefore`, `notAfter`, and `revokedAt`. `used` means a
20
20
  query or evaluation ran. It does not mean `SATISFIED`, and it does not mean a
@@ -188,9 +188,11 @@ every `publisher/name`. `check` reads a source directory that contains
188
188
  when `knolo-box` is on `PATH`. It scans `~/.grok` when that directory
189
189
  exists, plus every `--root`. It does not require `~/.grok`. It always
190
190
  includes `~/.knolo/skills`. An existing `~/.knolo/box/box.config.json` is
191
- kept and rebuilt. `--reset` replaces it. `--agent` sets the caller name.
192
- `--auto-rebuild` turns on a rebuild at the next search. Those flags require
193
- `--reset` when a config file is already there. Setup does not prompt,
191
+ kept and rebuilt. `--reset` replaces it. `--agent`, `--auto-rebuild`,
192
+ `--root`, and the scope paths merge into that file. `--root` may be the
193
+ parent of `agent-data`, or the `agent-data` directory itself. `--agent` sets
194
+ the caller name. `--auto-rebuild` turns on a rebuild at the next search.
195
+ Setup does not prompt,
194
196
  install a pack, or call the hub. A missing `knolo-box` is one line, and
195
197
  setup still exits 0. It prints the config path, `knolo skill install <publisher>/<name>`,
196
198
  and `knolo-box mcp --config <path>`. The install path, auth, trust, and
@@ -359,7 +361,7 @@ This ensures:
359
361
  ## 🗺 Roadmap
360
362
 
361
363
  The active roadmap is maintained in [`../../docs/ROADMAP.md`](../../docs/ROADMAP.md).
362
- The published npm release is `6.2.1`. This package file is `6.2.2`. It preserves the V4 CLI, keeps V5 verification,
364
+ The published npm release is `6.2.2`. This package file is `6.2.3` until that version is published. It preserves the V4 CLI, keeps V5 verification,
363
365
  migration, diagnostics, and read-only Studio management, and adds offline
364
366
  composition, attestation, and `knolo cortex`. Cortex verification does not
365
367
  mean an answer is true.
package/bin/knolo.mjs CHANGED
@@ -225,9 +225,12 @@ function printCommandHelp(command) {
225
225
 
226
226
  Resolves the hub the same way as the other hub commands.
227
227
  Scans ~/.grok when that directory exists, plus every --root.
228
+ --root may be the parent of agent-data, or the agent-data directory itself.
228
229
  Runs knolo-box init --tolerant, then knolo-box build --config.
229
230
  Keeps an existing config and rebuilds it. Pass --reset to replace it.
231
+ --agent, --auto-rebuild, --root, and the scope paths merge into an existing config.
230
232
  Always includes ~/.knolo/skills as a skills root.
233
+ When no skill folder is found, setup names the roots it scanned.
231
234
  Pass --agent to set the caller name. Pass --auto-rebuild to rebuild on the next search.
232
235
  Does not prompt. Does not install a pack or call the hub.
233
236
  A missing knolo-box is printed, and setup still exits 0.
@@ -1902,27 +1905,27 @@ async function main() {
1902
1905
  return printCommandHelp(command);
1903
1906
  if (command === 'kar') {
1904
1907
  const { runKarCli } = await import('./kar.mjs');
1905
- return runKarCli(commandArgs);
1908
+ return await runKarCli(commandArgs);
1906
1909
  }
1907
1910
  if (command === 'compose' || command === 'attest' || command === 'verify-attestation') {
1908
1911
  const { runComposeCli } = await import('./compose.mjs');
1909
- return runComposeCli(command, commandArgs);
1912
+ return await runComposeCli(command, commandArgs);
1910
1913
  }
1911
1914
  if (command === 'cortex') {
1912
1915
  const { runCortexCli } = await import('./cortex.mjs');
1913
- return runCortexCli(commandArgs);
1916
+ return await runCortexCli(commandArgs);
1914
1917
  }
1915
1918
  if (command === 'flight') {
1916
1919
  const { runFlightCli } = await import('./flight.mjs');
1917
- return runFlightCli(commandArgs);
1920
+ return await runFlightCli(commandArgs);
1918
1921
  }
1919
1922
  if (command === 'skill') {
1920
1923
  const { runSkillCli } = await import('./skill.mjs');
1921
- return runSkillCli(commandArgs);
1924
+ return await runSkillCli(commandArgs);
1922
1925
  }
1923
1926
  if (command === 'setup') {
1924
1927
  const { runKnoloSetup } = await import('./setup.mjs');
1925
- return runKnoloSetup(commandArgs);
1928
+ return await runKnoloSetup(commandArgs);
1926
1929
  }
1927
1930
  if (command === 'init') return await cmdInit();
1928
1931
  if (command === 'add') return await cmdAdd(commandArgs);
@@ -1931,8 +1934,8 @@ async function main() {
1931
1934
  if (command === 'login') return await runHubLogin(commandArgs);
1932
1935
  if (command === 'whoami') return await runHubWhoami(commandArgs);
1933
1936
  if (command === 'logout') return await runHubLogout(commandArgs);
1934
- if (command === 'publish') return runHubPublish(commandArgs);
1935
- if (command === 'yank') return runHubYank(commandArgs);
1937
+ if (command === 'publish') return await runHubPublish(commandArgs);
1938
+ if (command === 'yank') return await runHubYank(commandArgs);
1936
1939
 
1937
1940
  const core = await loadCore();
1938
1941
  if (command === 'build') return await cmdBuild(core);
package/bin/setup.mjs CHANGED
@@ -16,9 +16,12 @@ export const SETUP_HELP = `Usage: knolo setup [--registry <url>] [--root <dir>]
16
16
 
17
17
  Resolves the hub the same way as the other hub commands.
18
18
  Scans ~/.grok when that directory exists, plus every --root.
19
+ --root may be the parent of agent-data, or the agent-data directory itself.
19
20
  Runs knolo-box init --tolerant, then knolo-box build --config.
20
21
  Keeps an existing config and rebuilds it. Pass --reset to replace it.
22
+ --agent, --auto-rebuild, --root, and the scope paths merge into an existing config.
21
23
  Always includes ~/.knolo/skills as a skills root.
24
+ When no skill folder is found, setup names the roots it scanned.
22
25
  Pass --agent to set the caller name. Pass --auto-rebuild to rebuild on the next search.
23
26
  Does not prompt. Does not install a pack or call the hub.
24
27
  A missing knolo-box is printed, and setup still exits 0.
@@ -51,18 +54,16 @@ export async function runKnoloSetup(
51
54
  for (const root of parsed.roots) roots.push(root);
52
55
 
53
56
  const existing = await fileExists(configPath);
54
- if (existing && !parsed.reset && requestsNewConfig(parsed)) {
55
- throw new Error(
56
- `Config already exists: ${configPath}. Pass --reset to replace it.`
57
- );
58
- }
57
+ const hubPresent =
58
+ existing && (await configHasHubRoot(configPath, hubSkills, homeDir));
59
59
  const replace = !existing || parsed.reset === true;
60
+ const merge = existing && !parsed.reset && (requestsNewConfig(parsed) || !hubPresent);
60
61
 
61
62
  let built = false;
62
63
  if (!box) {
63
64
  printError('knolo-box is not on PATH. Skipping the local index.');
64
65
  if (existing && !parsed.reset) printError('Kept the existing config.');
65
- } else if (!replace) {
66
+ } else if (existing && !replace && !merge) {
66
67
  const buildStatus = await spawnImpl(
67
68
  box,
68
69
  ['build', '--config', configPath],
@@ -82,6 +83,8 @@ export async function runKnoloSetup(
82
83
  '--hub-skills',
83
84
  hubSkills,
84
85
  ];
86
+ if (parsed.reset && existing) initArgs.push('--force');
87
+ if (merge) initArgs.push('--merge');
85
88
  for (const root of roots) initArgs.push('--root', root);
86
89
  if (parsed.agent) initArgs.push('--agent', parsed.agent);
87
90
  if (parsed.autoRebuild) initArgs.push('--auto-rebuild');
@@ -102,8 +105,13 @@ export async function runKnoloSetup(
102
105
  throw new Error(`knolo-box build exited with status ${buildStatus}.`);
103
106
  }
104
107
  built = true;
105
- if ((await skillRootsBesideHub(configPath, hubSkills)).length === 0) {
108
+ if (existing && !parsed.reset) printError('Kept the existing config.');
109
+ if ((await skillRootsBesideHub(configPath, hubSkills, homeDir)).length === 0) {
106
110
  printError('No Grok skill folder was found.');
111
+ const scanned = roots.length > 0 ? roots.join(', ') : grokHome;
112
+ printError(
113
+ `Scanned ${scanned}. A root needs to contain agent-data or sand-data, or be the skill folder itself.`
114
+ );
107
115
  }
108
116
  }
109
117
 
@@ -217,7 +225,27 @@ async function directoryExists(candidate) {
217
225
  }
218
226
  }
219
227
 
220
- async function skillRootsBesideHub(configPath, hubSkills) {
228
+ async function configHasHubRoot(configPath, hubSkills, homeDir) {
229
+ const roots = await skillRoots(configPath);
230
+ const hub = await canonicalDir(hubSkills);
231
+ for (const root of roots) {
232
+ if ((await canonicalDir(expandHome(root, homeDir))) === hub) return true;
233
+ }
234
+ return false;
235
+ }
236
+
237
+ async function skillRootsBesideHub(configPath, hubSkills, homeDir) {
238
+ const roots = await skillRoots(configPath);
239
+ const hub = await canonicalDir(hubSkills);
240
+ const others = [];
241
+ for (const root of roots) {
242
+ if ((await canonicalDir(expandHome(root, homeDir))) === hub) continue;
243
+ others.push(root);
244
+ }
245
+ return others;
246
+ }
247
+
248
+ async function skillRoots(configPath) {
221
249
  let config;
222
250
  try {
223
251
  config = JSON.parse(await readFile(configPath, 'utf8'));
@@ -226,14 +254,13 @@ async function skillRootsBesideHub(configPath, hubSkills) {
226
254
  }
227
255
  const roots = config?.scopes?.skills?.roots;
228
256
  if (!Array.isArray(roots)) return [];
229
- const hub = await canonicalDir(hubSkills);
230
- const others = [];
231
- for (const root of roots) {
232
- if (typeof root !== 'string' || root.length === 0) continue;
233
- if ((await canonicalDir(root)) === hub) continue;
234
- others.push(root);
235
- }
236
- return others;
257
+ return roots.filter((root) => typeof root === 'string' && root.length > 0);
258
+ }
259
+
260
+ function expandHome(input, homeDir) {
261
+ if (input === '~') return homeDir;
262
+ if (input.startsWith('~/')) return path.join(homeDir, input.slice(2));
263
+ return input;
237
264
  }
238
265
 
239
266
  async function canonicalDir(candidate) {
package/bin/skill.mjs CHANGED
@@ -8,7 +8,17 @@
8
8
  */
9
9
  import { spawn } from 'node:child_process';
10
10
  import { createHash } from 'node:crypto';
11
- import { lstat, mkdir, mkdtemp, readdir, readFile, realpath, rm, writeFile } from 'node:fs/promises';
11
+ import {
12
+ chmod,
13
+ lstat,
14
+ mkdir,
15
+ mkdtemp,
16
+ readdir,
17
+ readFile,
18
+ realpath,
19
+ rm,
20
+ writeFile,
21
+ } from 'node:fs/promises';
12
22
  import { homedir, tmpdir } from 'node:os';
13
23
  import path from 'node:path';
14
24
  import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -341,8 +351,19 @@ async function indexInstalledSkill(destRoot, flags) {
341
351
  } catch {
342
352
  config = null;
343
353
  }
344
- const roots = skillRootsFromConfig(config, env, home);
345
354
  const dest = await canonicalDir(destRoot);
355
+ const hubSkills = path.resolve(path.join(home, '.knolo', 'skills'));
356
+ if (config && (await canonicalDir(hubSkills)) === dest) {
357
+ const added = await ensureHubSkillsRoot(configPath, config, dest, env, home);
358
+ if (added) {
359
+ try {
360
+ config = JSON.parse(await readFile(configPath, 'utf8'));
361
+ } catch {
362
+ config = null;
363
+ }
364
+ }
365
+ }
366
+ const roots = skillRootsFromConfig(config, env, home);
346
367
  let indexed = false;
347
368
  for (const root of roots) {
348
369
  if (typeof root !== 'string' || root.length === 0) continue;
@@ -370,6 +391,33 @@ async function indexInstalledSkill(destRoot, flags) {
370
391
  }
371
392
  }
372
393
 
394
+ async function ensureHubSkillsRoot(configPath, config, dest, env, home) {
395
+ const roots = skillRootsFromConfig(config, env, home);
396
+ for (const root of roots) {
397
+ if ((await canonicalDir(root)) === dest) return false;
398
+ }
399
+ const next = { ...config };
400
+ const scopes =
401
+ next.scopes && typeof next.scopes === 'object' && !Array.isArray(next.scopes)
402
+ ? { ...next.scopes }
403
+ : {};
404
+ const skills =
405
+ scopes.skills && typeof scopes.skills === 'object' && !Array.isArray(scopes.skills)
406
+ ? { ...scopes.skills }
407
+ : {};
408
+ const hadRoots = Array.isArray(skills.roots);
409
+ const prior = hadRoots
410
+ ? skills.roots.filter((item) => typeof item === 'string' && item.length > 0)
411
+ : roots;
412
+ skills.roots = [...prior, dest];
413
+ scopes.skills = skills;
414
+ next.scopes = scopes;
415
+ await mkdir(path.dirname(configPath), { recursive: true, mode: 0o700 });
416
+ await writeFile(configPath, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
417
+ await chmod(configPath, 0o600);
418
+ return true;
419
+ }
420
+
373
421
  function skillRootsFromConfig(config, env, home) {
374
422
  if (!config || typeof config !== 'object' || Array.isArray(config)) return [];
375
423
  const fallbackGrok = path.resolve(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knolo/cli",
3
- "version": "6.2.2",
3
+ "version": "6.2.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "knolo": "bin/knolo.mjs"