@knolo/cli 6.2.0 → 6.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 +82 -3
- package/bin/flight.mjs +367 -0
- package/bin/knolo.mjs +52 -0
- package/bin/setup.mjs +260 -0
- package/bin/skill.mjs +1308 -0
- package/package.json +2 -2
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/kar/closure.rs +395 -0
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/kar/cover.rs +692 -0
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/kar/evaluate.rs +343 -0
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/kar/json.rs +486 -0
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/kar/mod.rs +14 -0
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/kar/model.rs +1068 -0
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/kar/session.rs +1156 -0
- package/templates/icp-knowledge-canister/canisters/knolo-core-rust/src/lib.rs +7 -1
package/bin/setup.mjs
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* knolo setup
|
|
3
|
+
*
|
|
4
|
+
* Writes a local box config and builds it when knolo-box is on PATH.
|
|
5
|
+
* It does not install a pack, call a hub write API, or open a network
|
|
6
|
+
* connection. A missing knolo-box is one line, and setup still exits 0.
|
|
7
|
+
*/
|
|
8
|
+
import { spawn } from 'node:child_process';
|
|
9
|
+
import { constants } from 'node:fs';
|
|
10
|
+
import { access, readFile, realpath, stat } from 'node:fs/promises';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { resolveRegistryUrl } from './registry/config.mjs';
|
|
14
|
+
|
|
15
|
+
export const SETUP_HELP = `Usage: knolo setup [--registry <url>] [--root <dir>] [--agent <id>] [--auto-rebuild] [--reset] [--skills <path>] [--managed-skills <path>] [--memory <path>] [--transcripts <path>]
|
|
16
|
+
|
|
17
|
+
Resolves the hub the same way as the other hub commands.
|
|
18
|
+
Scans ~/.grok when that directory exists, plus every --root.
|
|
19
|
+
Runs knolo-box init --tolerant, then knolo-box build --config.
|
|
20
|
+
Keeps an existing config and rebuilds it. Pass --reset to replace it.
|
|
21
|
+
Always includes ~/.knolo/skills as a skills root.
|
|
22
|
+
Pass --agent to set the caller name. Pass --auto-rebuild to rebuild on the next search.
|
|
23
|
+
Does not prompt. Does not install a pack or call the hub.
|
|
24
|
+
A missing knolo-box is printed, and setup still exits 0.
|
|
25
|
+
When more than one skill folder matches, pass --skills or --managed-skills.`;
|
|
26
|
+
|
|
27
|
+
export async function runKnoloSetup(
|
|
28
|
+
args,
|
|
29
|
+
{
|
|
30
|
+
env = process.env,
|
|
31
|
+
homeDir = homedir(),
|
|
32
|
+
spawnImpl = spawnBox,
|
|
33
|
+
print = console.log,
|
|
34
|
+
printError = console.error,
|
|
35
|
+
} = {}
|
|
36
|
+
) {
|
|
37
|
+
const parsed = parseSetupArgs(args);
|
|
38
|
+
if (parsed.help) {
|
|
39
|
+
print(SETUP_HELP);
|
|
40
|
+
return { ok: true, help: true };
|
|
41
|
+
}
|
|
42
|
+
const registry = resolveRegistryUrl({ value: parsed.registry, env });
|
|
43
|
+
const box = await findOnPath('knolo-box', env.PATH);
|
|
44
|
+
const configPath = path.resolve(
|
|
45
|
+
path.join(homeDir, '.knolo', 'box', 'box.config.json')
|
|
46
|
+
);
|
|
47
|
+
const hubSkills = path.resolve(path.join(homeDir, '.knolo', 'skills'));
|
|
48
|
+
const grokHome = resolveGrokHome(env, homeDir);
|
|
49
|
+
const roots = [];
|
|
50
|
+
if (await directoryExists(grokHome)) roots.push(grokHome);
|
|
51
|
+
for (const root of parsed.roots) roots.push(root);
|
|
52
|
+
|
|
53
|
+
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
|
+
}
|
|
59
|
+
const replace = !existing || parsed.reset === true;
|
|
60
|
+
|
|
61
|
+
let built = false;
|
|
62
|
+
if (!box) {
|
|
63
|
+
printError('knolo-box is not on PATH. Skipping the local index.');
|
|
64
|
+
if (existing && !parsed.reset) printError('Kept the existing config.');
|
|
65
|
+
} else if (!replace) {
|
|
66
|
+
const buildStatus = await spawnImpl(
|
|
67
|
+
box,
|
|
68
|
+
['build', '--config', configPath],
|
|
69
|
+
{ env }
|
|
70
|
+
);
|
|
71
|
+
if (buildStatus !== 0) {
|
|
72
|
+
throw new Error(`knolo-box build exited with status ${buildStatus}.`);
|
|
73
|
+
}
|
|
74
|
+
built = true;
|
|
75
|
+
printError('Kept the existing config.');
|
|
76
|
+
} else {
|
|
77
|
+
const initArgs = [
|
|
78
|
+
'init',
|
|
79
|
+
'--config',
|
|
80
|
+
configPath,
|
|
81
|
+
'--tolerant',
|
|
82
|
+
'--hub-skills',
|
|
83
|
+
hubSkills,
|
|
84
|
+
];
|
|
85
|
+
for (const root of roots) initArgs.push('--root', root);
|
|
86
|
+
if (parsed.agent) initArgs.push('--agent', parsed.agent);
|
|
87
|
+
if (parsed.autoRebuild) initArgs.push('--auto-rebuild');
|
|
88
|
+
if (parsed.skills) initArgs.push('--skills', parsed.skills);
|
|
89
|
+
if (parsed.managedSkills) initArgs.push('--managed-skills', parsed.managedSkills);
|
|
90
|
+
if (parsed.memory) initArgs.push('--memory', parsed.memory);
|
|
91
|
+
if (parsed.transcripts) initArgs.push('--transcripts', parsed.transcripts);
|
|
92
|
+
const initStatus = await spawnImpl(box, initArgs, { env });
|
|
93
|
+
if (initStatus !== 0) {
|
|
94
|
+
throw new Error(`knolo-box init exited with status ${initStatus}.`);
|
|
95
|
+
}
|
|
96
|
+
const buildStatus = await spawnImpl(
|
|
97
|
+
box,
|
|
98
|
+
['build', '--config', configPath],
|
|
99
|
+
{ env }
|
|
100
|
+
);
|
|
101
|
+
if (buildStatus !== 0) {
|
|
102
|
+
throw new Error(`knolo-box build exited with status ${buildStatus}.`);
|
|
103
|
+
}
|
|
104
|
+
built = true;
|
|
105
|
+
if ((await skillRootsBesideHub(configPath, hubSkills)).length === 0) {
|
|
106
|
+
printError('No Grok skill folder was found.');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
print(`Config: ${configPath}`);
|
|
111
|
+
print('First install: knolo skill install <publisher>/<name>');
|
|
112
|
+
print(`Connector: knolo-box mcp --config ${configPath}`);
|
|
113
|
+
print(`Publish needs a token from ${registry}/dashboard/tokens.`);
|
|
114
|
+
return { ok: true, registry, built, configPath };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function resolveGrokHome(env, homeDir) {
|
|
118
|
+
const configured = typeof env.GROK_HOME === 'string' ? env.GROK_HOME.trim() : '';
|
|
119
|
+
const raw = configured || path.join(homeDir, '.grok');
|
|
120
|
+
if (raw === '~') return homeDir;
|
|
121
|
+
if (raw.startsWith('~/')) return path.resolve(path.join(homeDir, raw.slice(2)));
|
|
122
|
+
return path.resolve(raw);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function findOnPath(name, pathEnv) {
|
|
126
|
+
const dirs = String(pathEnv ?? '')
|
|
127
|
+
.split(path.delimiter)
|
|
128
|
+
.filter(Boolean);
|
|
129
|
+
for (const dir of dirs) {
|
|
130
|
+
const candidate = path.join(dir, name);
|
|
131
|
+
try {
|
|
132
|
+
await access(candidate, constants.X_OK);
|
|
133
|
+
return candidate;
|
|
134
|
+
} catch {
|
|
135
|
+
// Try the next directory.
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function requestsNewConfig(parsed) {
|
|
142
|
+
return (
|
|
143
|
+
parsed.roots.length > 0 ||
|
|
144
|
+
parsed.autoRebuild === true ||
|
|
145
|
+
Boolean(parsed.agent) ||
|
|
146
|
+
Boolean(parsed.skills) ||
|
|
147
|
+
Boolean(parsed.managedSkills) ||
|
|
148
|
+
Boolean(parsed.memory) ||
|
|
149
|
+
Boolean(parsed.transcripts)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function parseSetupArgs(args) {
|
|
154
|
+
const parsed = { roots: [], autoRebuild: false, reset: false };
|
|
155
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
156
|
+
const arg = args[index];
|
|
157
|
+
if (arg === '--help' || arg === '-h') return { help: true };
|
|
158
|
+
if (arg === '--auto-rebuild') {
|
|
159
|
+
parsed.autoRebuild = true;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (arg === '--reset') {
|
|
163
|
+
parsed.reset = true;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (arg === '--root') {
|
|
167
|
+
parsed.roots.push(oneValue(args, index, arg));
|
|
168
|
+
index += 1;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const key = singleFlag(arg);
|
|
172
|
+
if (key) {
|
|
173
|
+
if (parsed[key] !== undefined) {
|
|
174
|
+
throw new Error(`${arg} accepts one value.`);
|
|
175
|
+
}
|
|
176
|
+
parsed[key] = oneValue(args, index, arg);
|
|
177
|
+
index += 1;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
throw new Error(`Unknown argument for setup: ${arg}`);
|
|
181
|
+
}
|
|
182
|
+
return parsed;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function singleFlag(arg) {
|
|
186
|
+
if (arg === '--registry') return 'registry';
|
|
187
|
+
if (arg === '--agent') return 'agent';
|
|
188
|
+
if (arg === '--skills') return 'skills';
|
|
189
|
+
if (arg === '--managed-skills') return 'managedSkills';
|
|
190
|
+
if (arg === '--memory') return 'memory';
|
|
191
|
+
if (arg === '--transcripts') return 'transcripts';
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function oneValue(args, index, arg) {
|
|
196
|
+
const value = args[index + 1];
|
|
197
|
+
if (value === undefined || value.startsWith('--')) {
|
|
198
|
+
throw new Error(`Missing value for ${arg}`);
|
|
199
|
+
}
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function fileExists(candidate) {
|
|
204
|
+
try {
|
|
205
|
+
await access(candidate, constants.F_OK);
|
|
206
|
+
return true;
|
|
207
|
+
} catch {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function directoryExists(candidate) {
|
|
213
|
+
try {
|
|
214
|
+
return (await stat(candidate)).isDirectory();
|
|
215
|
+
} catch {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function skillRootsBesideHub(configPath, hubSkills) {
|
|
221
|
+
let config;
|
|
222
|
+
try {
|
|
223
|
+
config = JSON.parse(await readFile(configPath, 'utf8'));
|
|
224
|
+
} catch {
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
227
|
+
const roots = config?.scopes?.skills?.roots;
|
|
228
|
+
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;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function canonicalDir(candidate) {
|
|
240
|
+
try {
|
|
241
|
+
return await realpath(candidate);
|
|
242
|
+
} catch {
|
|
243
|
+
return path.resolve(candidate);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function spawnBox(command, args, options = {}) {
|
|
248
|
+
return new Promise((resolve, reject) => {
|
|
249
|
+
const child = spawn(command, args, {
|
|
250
|
+
env: options.env,
|
|
251
|
+
cwd: options.cwd,
|
|
252
|
+
stdio: ['ignore', 'ignore', 'inherit'],
|
|
253
|
+
});
|
|
254
|
+
child.on('error', reject);
|
|
255
|
+
child.on('exit', (code, signal) => {
|
|
256
|
+
if (signal) reject(new Error(`knolo-box exited from ${signal}.`));
|
|
257
|
+
else resolve(code ?? 1);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
}
|