kontexted 0.1.5
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 +75 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +48 -0
- package/dist/commands/logout.d.ts +5 -0
- package/dist/commands/logout.js +33 -0
- package/dist/commands/mcp.d.ts +15 -0
- package/dist/commands/mcp.js +65 -0
- package/dist/commands/server/doctor.d.ts +4 -0
- package/dist/commands/server/doctor.js +33 -0
- package/dist/commands/server/index.d.ts +6 -0
- package/dist/commands/server/index.js +125 -0
- package/dist/commands/server/init.d.ts +6 -0
- package/dist/commands/server/init.js +112 -0
- package/dist/commands/server/logs.d.ts +7 -0
- package/dist/commands/server/logs.js +39 -0
- package/dist/commands/server/migrate.d.ts +4 -0
- package/dist/commands/server/migrate.js +29 -0
- package/dist/commands/server/show-invite.d.ts +4 -0
- package/dist/commands/server/show-invite.js +29 -0
- package/dist/commands/server/start.d.ts +6 -0
- package/dist/commands/server/start.js +44 -0
- package/dist/commands/server/status.d.ts +4 -0
- package/dist/commands/server/status.js +23 -0
- package/dist/commands/server/stop.d.ts +6 -0
- package/dist/commands/server/stop.js +32 -0
- package/dist/commands/show-config.d.ts +5 -0
- package/dist/commands/show-config.js +19 -0
- package/dist/commands/skill.d.ts +5 -0
- package/dist/commands/skill.js +211 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +25 -0
- package/dist/lib/api-client.d.ts +36 -0
- package/dist/lib/api-client.js +130 -0
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.js +46 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/logger.d.ts +24 -0
- package/dist/lib/logger.js +76 -0
- package/dist/lib/mcp-client.d.ts +14 -0
- package/dist/lib/mcp-client.js +62 -0
- package/dist/lib/oauth.d.ts +42 -0
- package/dist/lib/oauth.js +383 -0
- package/dist/lib/profile.d.ts +37 -0
- package/dist/lib/profile.js +49 -0
- package/dist/lib/proxy-server.d.ts +12 -0
- package/dist/lib/proxy-server.js +131 -0
- package/dist/lib/server/binary.d.ts +32 -0
- package/dist/lib/server/binary.js +127 -0
- package/dist/lib/server/config.d.ts +57 -0
- package/dist/lib/server/config.js +136 -0
- package/dist/lib/server/constants.d.ts +29 -0
- package/dist/lib/server/constants.js +35 -0
- package/dist/lib/server/daemon.d.ts +34 -0
- package/dist/lib/server/daemon.js +199 -0
- package/dist/lib/server/index.d.ts +5 -0
- package/dist/lib/server/index.js +5 -0
- package/dist/lib/server/migrate.d.ts +9 -0
- package/dist/lib/server/migrate.js +51 -0
- package/dist/lib/server-url.d.ts +8 -0
- package/dist/lib/server-url.js +25 -0
- package/dist/skill-init/index.d.ts +3 -0
- package/dist/skill-init/index.js +3 -0
- package/dist/skill-init/providers/base.d.ts +26 -0
- package/dist/skill-init/providers/base.js +1 -0
- package/dist/skill-init/providers/index.d.ts +13 -0
- package/dist/skill-init/providers/index.js +17 -0
- package/dist/skill-init/providers/opencode.d.ts +5 -0
- package/dist/skill-init/providers/opencode.js +48 -0
- package/dist/skill-init/templates/index.d.ts +3 -0
- package/dist/skill-init/templates/index.js +3 -0
- package/dist/skill-init/templates/kontexted-cli.d.ts +2 -0
- package/dist/skill-init/templates/kontexted-cli.js +169 -0
- package/dist/skill-init/utils.d.ts +66 -0
- package/dist/skill-init/utils.js +122 -0
- package/dist/types/index.d.ts +67 -0
- package/dist/types/index.js +4 -0
- package/package.json +50 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
3
|
+
import { mkdirSync, closeSync, openSync } from 'fs';
|
|
4
|
+
import { getBinaryPath } from './binary.js';
|
|
5
|
+
import { LOG_FILE, PID_FILE, LOGS_DIR, KONTEXTED_DIR } from './constants.js';
|
|
6
|
+
/**
|
|
7
|
+
* Ensures required directories exist
|
|
8
|
+
*/
|
|
9
|
+
function ensureDirectories() {
|
|
10
|
+
if (!existsSync(KONTEXTED_DIR)) {
|
|
11
|
+
mkdirSync(KONTEXTED_DIR, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
if (!existsSync(LOGS_DIR)) {
|
|
14
|
+
mkdirSync(LOGS_DIR, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Reads the PID from the PID file
|
|
19
|
+
* Returns null if the file doesn't exist
|
|
20
|
+
*/
|
|
21
|
+
export function getPid() {
|
|
22
|
+
if (!existsSync(PID_FILE)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const content = readFileSync(PID_FILE, 'utf-8');
|
|
27
|
+
const pid = parseInt(content.trim(), 10);
|
|
28
|
+
return isNaN(pid) ? null : pid;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Checks if a process with the given PID is running
|
|
36
|
+
* Uses signal 0 to check process existence without actually sending a signal
|
|
37
|
+
*/
|
|
38
|
+
export function isRunning(pid) {
|
|
39
|
+
try {
|
|
40
|
+
// Signal 0 checks if the process exists without sending any signal
|
|
41
|
+
process.kill(pid, 0);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
// EPERM means the process exists but we don't have permission to signal it
|
|
46
|
+
// ESRCH means no such process
|
|
47
|
+
if (error instanceof Error && 'code' in error) {
|
|
48
|
+
const code = error.code;
|
|
49
|
+
return code === 'EPERM';
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Starts the server
|
|
56
|
+
* @param options.foreground - If true, runs in foreground (blocks). If false, runs as daemon
|
|
57
|
+
* @returns The PID of the started server
|
|
58
|
+
*/
|
|
59
|
+
export async function startServer(options = {}) {
|
|
60
|
+
const binaryPath = getBinaryPath();
|
|
61
|
+
if (!binaryPath) {
|
|
62
|
+
throw new Error('Server binary not found. Please ensure the platform package is installed.');
|
|
63
|
+
}
|
|
64
|
+
ensureDirectories();
|
|
65
|
+
// Check if already running
|
|
66
|
+
const existingPid = getPid();
|
|
67
|
+
if (existingPid && isRunning(existingPid)) {
|
|
68
|
+
throw new Error(`Server is already running with PID ${existingPid}`);
|
|
69
|
+
}
|
|
70
|
+
const foreground = options.foreground ?? false;
|
|
71
|
+
if (foreground) {
|
|
72
|
+
// Run in foreground - inherited stdio
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
const child = spawn(binaryPath, [], {
|
|
75
|
+
stdio: 'inherit',
|
|
76
|
+
detached: false,
|
|
77
|
+
});
|
|
78
|
+
child.on('error', (error) => {
|
|
79
|
+
reject(new Error(`Failed to start server: ${error.message}`));
|
|
80
|
+
});
|
|
81
|
+
child.on('close', (code) => {
|
|
82
|
+
if (code !== 0 && code !== null) {
|
|
83
|
+
reject(new Error(`Server exited with code ${code}`));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
// Return the pid (will be the current process since we're not detaching)
|
|
87
|
+
resolve(child.pid);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Run as daemon - detached process with redirected output
|
|
92
|
+
// Open log file for appending and get file descriptor
|
|
93
|
+
const logFd = openSync(LOG_FILE, 'a');
|
|
94
|
+
const child = spawn(binaryPath, [], {
|
|
95
|
+
stdio: ['ignore', logFd, logFd], // Use file descriptor, not stream
|
|
96
|
+
detached: true,
|
|
97
|
+
env: { ...process.env },
|
|
98
|
+
});
|
|
99
|
+
// Close the file descriptor in parent - child has its own copy
|
|
100
|
+
closeSync(logFd);
|
|
101
|
+
// Unref to allow the parent to exit independently
|
|
102
|
+
child.unref();
|
|
103
|
+
const pid = child.pid;
|
|
104
|
+
// Write PID to file
|
|
105
|
+
writeFileSync(PID_FILE, pid.toString(), 'utf-8');
|
|
106
|
+
// Wait a moment to ensure the process starts successfully
|
|
107
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
108
|
+
// Verify the process is running
|
|
109
|
+
if (!isRunning(pid)) {
|
|
110
|
+
throw new Error('Server failed to start');
|
|
111
|
+
}
|
|
112
|
+
return pid;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Stops the server
|
|
117
|
+
* @param options.force - If true, uses SIGKILL instead of SIGTERM
|
|
118
|
+
* @returns True if the server was stopped, false if it wasn't running
|
|
119
|
+
*/
|
|
120
|
+
export async function stopServer(options = {}) {
|
|
121
|
+
const force = options.force ?? false;
|
|
122
|
+
const pid = getPid();
|
|
123
|
+
if (!pid) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
if (!isRunning(pid)) {
|
|
127
|
+
// Clean up stale PID file
|
|
128
|
+
try {
|
|
129
|
+
unlinkSync(PID_FILE);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// Ignore errors
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
const signal = force ? 'SIGKILL' : 'SIGTERM';
|
|
137
|
+
try {
|
|
138
|
+
process.kill(pid, signal);
|
|
139
|
+
// Wait for process to exit
|
|
140
|
+
const maxWaitMs = force ? 5000 : 30000;
|
|
141
|
+
const checkInterval = 100;
|
|
142
|
+
let waited = 0;
|
|
143
|
+
while (isRunning(pid) && waited < maxWaitMs) {
|
|
144
|
+
await new Promise((resolve) => setTimeout(resolve, checkInterval));
|
|
145
|
+
waited += checkInterval;
|
|
146
|
+
}
|
|
147
|
+
if (isRunning(pid)) {
|
|
148
|
+
throw new Error('Server did not stop in time');
|
|
149
|
+
}
|
|
150
|
+
// Clean up PID file
|
|
151
|
+
try {
|
|
152
|
+
unlinkSync(PID_FILE);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// Ignore errors
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
if (error instanceof Error && error.message.includes('ESRCH')) {
|
|
161
|
+
// Process doesn't exist, clean up
|
|
162
|
+
try {
|
|
163
|
+
unlinkSync(PID_FILE);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Ignore errors
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Gets the current server status
|
|
175
|
+
*/
|
|
176
|
+
export function getServerStatus() {
|
|
177
|
+
const pid = getPid();
|
|
178
|
+
if (!pid) {
|
|
179
|
+
return { running: false };
|
|
180
|
+
}
|
|
181
|
+
const running = isRunning(pid);
|
|
182
|
+
if (!running) {
|
|
183
|
+
// Clean up stale PID file
|
|
184
|
+
try {
|
|
185
|
+
unlinkSync(PID_FILE);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Ignore errors
|
|
189
|
+
}
|
|
190
|
+
return { running: false };
|
|
191
|
+
}
|
|
192
|
+
// Note: Getting the actual start time would require platform-specific code
|
|
193
|
+
// For now, we just report that it's running with the PID
|
|
194
|
+
return {
|
|
195
|
+
running: true,
|
|
196
|
+
pid,
|
|
197
|
+
// startedAt would require reading /proc/<pid>/stat on Linux or similar
|
|
198
|
+
};
|
|
199
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface MigrationResult {
|
|
2
|
+
success: boolean;
|
|
3
|
+
error?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Runs database migrations using the kontexted-migrate binary
|
|
7
|
+
* Sets environment variables from config and spawns the migration process
|
|
8
|
+
*/
|
|
9
|
+
export declare function runMigration(): Promise<MigrationResult>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { getMigratePath } from './binary.js';
|
|
3
|
+
import { loadConfig } from './config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Runs database migrations using the kontexted-migrate binary
|
|
6
|
+
* Sets environment variables from config and spawns the migration process
|
|
7
|
+
*/
|
|
8
|
+
export async function runMigration() {
|
|
9
|
+
const migratePath = getMigratePath();
|
|
10
|
+
if (!migratePath) {
|
|
11
|
+
return {
|
|
12
|
+
success: false,
|
|
13
|
+
error: 'Migration binary not found. Ensure the platform package is installed.'
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
const config = loadConfig();
|
|
17
|
+
if (!config) {
|
|
18
|
+
return {
|
|
19
|
+
success: false,
|
|
20
|
+
error: 'Configuration not found. Run `kontexted server init` first.'
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return new Promise((resolve) => {
|
|
24
|
+
const env = {
|
|
25
|
+
...process.env,
|
|
26
|
+
DATABASE_URL: config.database.url,
|
|
27
|
+
DATABASE_DIALECT: config.database.dialect,
|
|
28
|
+
};
|
|
29
|
+
const child = spawn(migratePath, [], {
|
|
30
|
+
env,
|
|
31
|
+
stdio: 'inherit', // Show output directly
|
|
32
|
+
});
|
|
33
|
+
child.on('close', (code) => {
|
|
34
|
+
if (code === 0) {
|
|
35
|
+
resolve({ success: true });
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
resolve({
|
|
39
|
+
success: false,
|
|
40
|
+
error: `Migration exited with code ${code}`
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
child.on('error', (err) => {
|
|
45
|
+
resolve({
|
|
46
|
+
success: false,
|
|
47
|
+
error: `Failed to run migration: ${err.message}`
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve canonical server base URL.
|
|
3
|
+
*
|
|
4
|
+
* Accepted inputs:
|
|
5
|
+
* - https://host
|
|
6
|
+
* - host (will default to https)
|
|
7
|
+
*/
|
|
8
|
+
export function resolveServerUrl(input) {
|
|
9
|
+
let parsed;
|
|
10
|
+
// Add https:// if no protocol specified
|
|
11
|
+
if (!input.startsWith('http://') && !input.startsWith('https://')) {
|
|
12
|
+
input = 'https://' + input;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
parsed = new URL(input);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new Error(`Invalid URL: ${input}. Provide a server URL like https://app.example.com or app.example.com`);
|
|
19
|
+
}
|
|
20
|
+
// Always use root as base path, ignore any path in input
|
|
21
|
+
parsed.pathname = "/";
|
|
22
|
+
parsed.search = "";
|
|
23
|
+
parsed.hash = "";
|
|
24
|
+
return parsed.toString().replace(/\/$/, "");
|
|
25
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Definition of a skill to be generated
|
|
3
|
+
*/
|
|
4
|
+
export interface SkillDefinition {
|
|
5
|
+
/** Skill name (must match directory name, lowercase alphanumeric with hyphens) */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Short description (1-1024 characters) */
|
|
8
|
+
description: string;
|
|
9
|
+
/** The full skill content (markdown) */
|
|
10
|
+
content: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Provider interface for different AI agent platforms
|
|
14
|
+
*/
|
|
15
|
+
export interface SkillProvider {
|
|
16
|
+
/** Unique provider identifier (e.g., 'opencode') */
|
|
17
|
+
id: string;
|
|
18
|
+
/** Human-readable provider name */
|
|
19
|
+
name: string;
|
|
20
|
+
/** Get the file path where a skill should be written */
|
|
21
|
+
getSkillPath(skillName: string): string;
|
|
22
|
+
/** Validate skill name according to provider rules */
|
|
23
|
+
validateSkillName(name: string): boolean;
|
|
24
|
+
/** Generate the complete skill file content including frontmatter */
|
|
25
|
+
generateSkillContent(skill: SkillDefinition): string;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { SkillProvider } from "../../skill-init/providers/base.js";
|
|
2
|
+
export type { SkillProvider, SkillDefinition } from "../../skill-init/providers/base.js";
|
|
3
|
+
/** List of available skill provider IDs */
|
|
4
|
+
export declare const availableProviders: readonly ["opencode"];
|
|
5
|
+
/** Type representing valid provider IDs */
|
|
6
|
+
export type ProviderId = (typeof availableProviders)[number];
|
|
7
|
+
/**
|
|
8
|
+
* Get a skill provider by its ID
|
|
9
|
+
* @param id - The provider ID to look up
|
|
10
|
+
* @returns The corresponding SkillProvider
|
|
11
|
+
* @throws Error if the provider ID is not found
|
|
12
|
+
*/
|
|
13
|
+
export declare function getProvider(id: ProviderId): SkillProvider;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { opencodeProvider } from "../../skill-init/providers/opencode.js";
|
|
2
|
+
/** List of available skill provider IDs */
|
|
3
|
+
export const availableProviders = ["opencode"];
|
|
4
|
+
/**
|
|
5
|
+
* Get a skill provider by its ID
|
|
6
|
+
* @param id - The provider ID to look up
|
|
7
|
+
* @returns The corresponding SkillProvider
|
|
8
|
+
* @throws Error if the provider ID is not found
|
|
9
|
+
*/
|
|
10
|
+
export function getProvider(id) {
|
|
11
|
+
switch (id) {
|
|
12
|
+
case "opencode":
|
|
13
|
+
return opencodeProvider;
|
|
14
|
+
default:
|
|
15
|
+
throw new Error(`Unknown skill provider: ${id}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** OpenCode skill provider identifier */
|
|
2
|
+
const OPENCODE_PROVIDER_ID = "opencode";
|
|
3
|
+
/** OpenCode provider human-readable name */
|
|
4
|
+
const OPENCODE_PROVIDER_NAME = "OpenCode";
|
|
5
|
+
/** Regex for validating OpenCode skill names: lowercase alphanumeric with single hyphen separators */
|
|
6
|
+
const SKILL_NAME_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
7
|
+
/**
|
|
8
|
+
* OpenCode provider implementation for skill generation
|
|
9
|
+
*/
|
|
10
|
+
export const opencodeProvider = {
|
|
11
|
+
id: OPENCODE_PROVIDER_ID,
|
|
12
|
+
name: OPENCODE_PROVIDER_NAME,
|
|
13
|
+
/**
|
|
14
|
+
* Get the file path for an OpenCode skill
|
|
15
|
+
* @param skillName - The skill name (already validated)
|
|
16
|
+
* @returns The relative file path where the skill should be written
|
|
17
|
+
*/
|
|
18
|
+
getSkillPath(skillName) {
|
|
19
|
+
return `.opencode/skills/${skillName}/SKILL.md`;
|
|
20
|
+
},
|
|
21
|
+
/**
|
|
22
|
+
* Validate a skill name according to OpenCode rules
|
|
23
|
+
* @param name - The skill name to validate
|
|
24
|
+
* @returns true if the name is valid, false otherwise
|
|
25
|
+
*/
|
|
26
|
+
validateSkillName(name) {
|
|
27
|
+
// Check length constraints
|
|
28
|
+
if (name.length < 1 || name.length > 64) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
// Validate against regex pattern
|
|
32
|
+
return SKILL_NAME_REGEX.test(name);
|
|
33
|
+
},
|
|
34
|
+
/**
|
|
35
|
+
* Generate the complete skill file content including frontmatter
|
|
36
|
+
* @param skill - The skill definition to generate content for
|
|
37
|
+
* @returns The complete markdown content with frontmatter
|
|
38
|
+
*/
|
|
39
|
+
generateSkillContent(skill) {
|
|
40
|
+
const frontmatter = `---
|
|
41
|
+
name: ${skill.name}
|
|
42
|
+
description: ${skill.description}
|
|
43
|
+
---`;
|
|
44
|
+
return `${frontmatter}
|
|
45
|
+
|
|
46
|
+
${skill.content}`;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
export const kontextedCliSkill = {
|
|
2
|
+
name: 'kontexted-cli',
|
|
3
|
+
description: 'Access and manage Kontexted workspaces, search notes, and retrieve content through the kontexted CLI. Use when the user needs to explore workspace structure, find specific notes, or read note content using profile aliases.',
|
|
4
|
+
content: String.raw `# Kontexted CLI Skill Commands
|
|
5
|
+
|
|
6
|
+
\`\`\`
|
|
7
|
+
kontexted skill workspace-tree --alias <profile> # Get workspace folder/note structure
|
|
8
|
+
kontexted skill search-notes --alias <profile> --query "<text>" # Search notes
|
|
9
|
+
kontexted skill note-by-id --alias <profile> --note-id <id> # Get specific note
|
|
10
|
+
\`\`\`
|
|
11
|
+
|
|
12
|
+
## Prerequisites
|
|
13
|
+
|
|
14
|
+
Before using the kontexted CLI skill commands, ensure:
|
|
15
|
+
|
|
16
|
+
1. **kontexted CLI is installed** - Install via npm or your preferred package manager
|
|
17
|
+
2. **User has authenticated** - User must have run \`kontexted login\` with a profile alias
|
|
18
|
+
3. **Profile has a workspace configured** - The profile alias must be associated with an active workspace
|
|
19
|
+
|
|
20
|
+
All commands require the \`--alias\` parameter to specify which profile to use. The profile must already be set up and authenticated.
|
|
21
|
+
|
|
22
|
+
## Available Tools
|
|
23
|
+
|
|
24
|
+
### workspace-tree
|
|
25
|
+
|
|
26
|
+
Get the complete folder and note structure of a workspace.
|
|
27
|
+
|
|
28
|
+
\`\`\`bash
|
|
29
|
+
kontexted skill workspace-tree --alias <profile>
|
|
30
|
+
\`\`\`
|
|
31
|
+
|
|
32
|
+
**Options:**
|
|
33
|
+
- \`--alias\` (required): The profile alias to use for authentication
|
|
34
|
+
|
|
35
|
+
**Returns:** JSON object containing the workspace structure with folders and notes hierarchy
|
|
36
|
+
|
|
37
|
+
**When to use:**
|
|
38
|
+
- When you need to understand the organization of a workspace
|
|
39
|
+
- When exploring available notes before reading specific content
|
|
40
|
+
- When building navigation paths to locate notes
|
|
41
|
+
|
|
42
|
+
### search-notes
|
|
43
|
+
|
|
44
|
+
Search for notes containing specific text content.
|
|
45
|
+
|
|
46
|
+
\`\`\`bash
|
|
47
|
+
kontexted skill search-notes --alias <profile> --query "<text>" [--limit <n>]
|
|
48
|
+
\`\`\`
|
|
49
|
+
|
|
50
|
+
**Options:**
|
|
51
|
+
- \`--alias\` (required): The profile alias to use for authentication
|
|
52
|
+
- \`--query\` (required): The search text to find in notes
|
|
53
|
+
- \`--limit\` (optional): Maximum number of results to return (default: 10)
|
|
54
|
+
|
|
55
|
+
**Returns:** JSON array of matching notes with metadata including note ID, title, and snippets
|
|
56
|
+
|
|
57
|
+
**When to use:**
|
|
58
|
+
- When you need to find notes containing specific keywords
|
|
59
|
+
- When searching for content across multiple notes
|
|
60
|
+
- When the user asks to find notes about a particular topic
|
|
61
|
+
|
|
62
|
+
### note-by-id
|
|
63
|
+
|
|
64
|
+
Retrieve the complete content of a specific note by its ID.
|
|
65
|
+
|
|
66
|
+
\`\`\`bash
|
|
67
|
+
kontexted skill note-by-id --alias <profile> --note-id <id>
|
|
68
|
+
\`\`\`
|
|
69
|
+
|
|
70
|
+
**Options:**
|
|
71
|
+
- \`--alias\` (required): The profile alias to use for authentication
|
|
72
|
+
- \`--note-id\` (required): The unique identifier of the note to retrieve
|
|
73
|
+
|
|
74
|
+
**Returns:** JSON object containing the full note content including body, metadata, and structure
|
|
75
|
+
|
|
76
|
+
**When to use:**
|
|
77
|
+
- When you have a specific note ID and need its content
|
|
78
|
+
- After finding a note via search or workspace tree exploration
|
|
79
|
+
- When the user asks to read a specific note
|
|
80
|
+
|
|
81
|
+
## Typical Workflow
|
|
82
|
+
|
|
83
|
+
The skill commands work best when combined in a logical sequence:
|
|
84
|
+
|
|
85
|
+
1. **Explore** - Use \`workspace-tree\` to understand workspace structure
|
|
86
|
+
2. **Search** - Use \`search-notes\` to find relevant notes by content
|
|
87
|
+
3. **Read** - Use \`note-by-id\` to retrieve full content of specific notes
|
|
88
|
+
|
|
89
|
+
**Example workflow:**
|
|
90
|
+
\`\`\`bash
|
|
91
|
+
# First, explore the workspace structure
|
|
92
|
+
kontexted skill workspace-tree --alias work
|
|
93
|
+
|
|
94
|
+
# Then, search for notes containing specific content
|
|
95
|
+
kontexted skill search-notes --alias work --query "project planning" --limit 5
|
|
96
|
+
|
|
97
|
+
# Finally, read the content of notes of interest
|
|
98
|
+
kontexted skill note-by-id --alias work --note-id "abc123"
|
|
99
|
+
\`\`\`
|
|
100
|
+
|
|
101
|
+
## Example Usage
|
|
102
|
+
|
|
103
|
+
### Exploring a workspace
|
|
104
|
+
|
|
105
|
+
\`\`\`bash
|
|
106
|
+
# Get the complete structure of a personal workspace
|
|
107
|
+
kontexted skill workspace-tree --alias personal
|
|
108
|
+
\`\`\`
|
|
109
|
+
|
|
110
|
+
### Searching for content
|
|
111
|
+
|
|
112
|
+
\`\`\`bash
|
|
113
|
+
# Find notes about meeting notes
|
|
114
|
+
kontexted skill search-notes --alias work --query "meeting notes"
|
|
115
|
+
|
|
116
|
+
# Limit results to 3 notes
|
|
117
|
+
kontexted skill search-notes --alias work --query "todo" --limit 3
|
|
118
|
+
\`\`\`
|
|
119
|
+
|
|
120
|
+
### Reading specific notes
|
|
121
|
+
|
|
122
|
+
\`\`\`bash
|
|
123
|
+
# Get content of a note when you have its ID
|
|
124
|
+
kontexted skill note-by-id --alias work --note-id "note-uuid-123"
|
|
125
|
+
\`\`\`
|
|
126
|
+
|
|
127
|
+
### Combining commands in a single task
|
|
128
|
+
|
|
129
|
+
\`\`\`bash
|
|
130
|
+
# Task: Find and read notes about project requirements
|
|
131
|
+
# Step 1: Search for relevant notes
|
|
132
|
+
kontexted skill search-notes --alias work --query "requirements" --limit 5
|
|
133
|
+
|
|
134
|
+
# Step 2: Read each matching note
|
|
135
|
+
kontexted skill note-by-id --alias work --note-id "req-001"
|
|
136
|
+
kontexted skill note-by-id --alias work --note-id "req-002"
|
|
137
|
+
\`\`\`
|
|
138
|
+
|
|
139
|
+
## Error Handling
|
|
140
|
+
|
|
141
|
+
### Authentication errors
|
|
142
|
+
|
|
143
|
+
If you encounter authentication errors:
|
|
144
|
+
|
|
145
|
+
1. **"Profile not found"** - The specified alias doesn't exist. Ask the user to run \`kontexted login --alias <profile>\` first.
|
|
146
|
+
|
|
147
|
+
2. **"Not authenticated"** - The profile exists but isn't authenticated. Ask the user to re-authenticate with \`kontexted login --alias <profile>\`.
|
|
148
|
+
|
|
149
|
+
3. **"No workspace configured"** - The profile is authenticated but has no workspace. Ask the user to set up a workspace with \`kontexted workspace set --alias <profile>\`.
|
|
150
|
+
|
|
151
|
+
### Other errors
|
|
152
|
+
|
|
153
|
+
- **"Note not found"** - The specified note ID doesn't exist or belongs to a different workspace
|
|
154
|
+
- **"Workspace not accessible"** - The workspace exists but the user lacks access permissions
|
|
155
|
+
- **"Connection error"** - Network issues. Retry the command or check the user's connection
|
|
156
|
+
|
|
157
|
+
When errors occur, report them clearly to the user so they can take appropriate action. The kontexted CLI handles most errors with descriptive messages.
|
|
158
|
+
|
|
159
|
+
## Output Format
|
|
160
|
+
|
|
161
|
+
All commands return JSON output that is easy to parse:
|
|
162
|
+
|
|
163
|
+
- \`workspace-tree\`: Returns nested object with folders and notes
|
|
164
|
+
- \`search-notes\`: Returns array of matching notes with ID, title, and snippets
|
|
165
|
+
- \`note-by-id\`: Returns complete note object with body and metadata
|
|
166
|
+
|
|
167
|
+
Use this structured output to provide clear responses to users about workspace contents and note information.
|
|
168
|
+
`
|
|
169
|
+
};
|