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.
Files changed (78) hide show
  1. package/README.md +75 -0
  2. package/dist/commands/login.d.ts +2 -0
  3. package/dist/commands/login.js +48 -0
  4. package/dist/commands/logout.d.ts +5 -0
  5. package/dist/commands/logout.js +33 -0
  6. package/dist/commands/mcp.d.ts +15 -0
  7. package/dist/commands/mcp.js +65 -0
  8. package/dist/commands/server/doctor.d.ts +4 -0
  9. package/dist/commands/server/doctor.js +33 -0
  10. package/dist/commands/server/index.d.ts +6 -0
  11. package/dist/commands/server/index.js +125 -0
  12. package/dist/commands/server/init.d.ts +6 -0
  13. package/dist/commands/server/init.js +112 -0
  14. package/dist/commands/server/logs.d.ts +7 -0
  15. package/dist/commands/server/logs.js +39 -0
  16. package/dist/commands/server/migrate.d.ts +4 -0
  17. package/dist/commands/server/migrate.js +29 -0
  18. package/dist/commands/server/show-invite.d.ts +4 -0
  19. package/dist/commands/server/show-invite.js +29 -0
  20. package/dist/commands/server/start.d.ts +6 -0
  21. package/dist/commands/server/start.js +44 -0
  22. package/dist/commands/server/status.d.ts +4 -0
  23. package/dist/commands/server/status.js +23 -0
  24. package/dist/commands/server/stop.d.ts +6 -0
  25. package/dist/commands/server/stop.js +32 -0
  26. package/dist/commands/show-config.d.ts +5 -0
  27. package/dist/commands/show-config.js +19 -0
  28. package/dist/commands/skill.d.ts +5 -0
  29. package/dist/commands/skill.js +211 -0
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +25 -0
  32. package/dist/lib/api-client.d.ts +36 -0
  33. package/dist/lib/api-client.js +130 -0
  34. package/dist/lib/config.d.ts +17 -0
  35. package/dist/lib/config.js +46 -0
  36. package/dist/lib/index.d.ts +6 -0
  37. package/dist/lib/index.js +6 -0
  38. package/dist/lib/logger.d.ts +24 -0
  39. package/dist/lib/logger.js +76 -0
  40. package/dist/lib/mcp-client.d.ts +14 -0
  41. package/dist/lib/mcp-client.js +62 -0
  42. package/dist/lib/oauth.d.ts +42 -0
  43. package/dist/lib/oauth.js +383 -0
  44. package/dist/lib/profile.d.ts +37 -0
  45. package/dist/lib/profile.js +49 -0
  46. package/dist/lib/proxy-server.d.ts +12 -0
  47. package/dist/lib/proxy-server.js +131 -0
  48. package/dist/lib/server/binary.d.ts +32 -0
  49. package/dist/lib/server/binary.js +127 -0
  50. package/dist/lib/server/config.d.ts +57 -0
  51. package/dist/lib/server/config.js +136 -0
  52. package/dist/lib/server/constants.d.ts +29 -0
  53. package/dist/lib/server/constants.js +35 -0
  54. package/dist/lib/server/daemon.d.ts +34 -0
  55. package/dist/lib/server/daemon.js +199 -0
  56. package/dist/lib/server/index.d.ts +5 -0
  57. package/dist/lib/server/index.js +5 -0
  58. package/dist/lib/server/migrate.d.ts +9 -0
  59. package/dist/lib/server/migrate.js +51 -0
  60. package/dist/lib/server-url.d.ts +8 -0
  61. package/dist/lib/server-url.js +25 -0
  62. package/dist/skill-init/index.d.ts +3 -0
  63. package/dist/skill-init/index.js +3 -0
  64. package/dist/skill-init/providers/base.d.ts +26 -0
  65. package/dist/skill-init/providers/base.js +1 -0
  66. package/dist/skill-init/providers/index.d.ts +13 -0
  67. package/dist/skill-init/providers/index.js +17 -0
  68. package/dist/skill-init/providers/opencode.d.ts +5 -0
  69. package/dist/skill-init/providers/opencode.js +48 -0
  70. package/dist/skill-init/templates/index.d.ts +3 -0
  71. package/dist/skill-init/templates/index.js +3 -0
  72. package/dist/skill-init/templates/kontexted-cli.d.ts +2 -0
  73. package/dist/skill-init/templates/kontexted-cli.js +169 -0
  74. package/dist/skill-init/utils.d.ts +66 -0
  75. package/dist/skill-init/utils.js +122 -0
  76. package/dist/types/index.d.ts +67 -0
  77. package/dist/types/index.js +4 -0
  78. 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,5 @@
1
+ export * from './constants.js';
2
+ export * from './binary.js';
3
+ export * from './config.js';
4
+ export * from './daemon.js';
5
+ export * from './migrate.js';
@@ -0,0 +1,5 @@
1
+ export * from './constants.js';
2
+ export * from './binary.js';
3
+ export * from './config.js';
4
+ export * from './daemon.js';
5
+ export * from './migrate.js';
@@ -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,8 @@
1
+ /**
2
+ * Resolve canonical server base URL.
3
+ *
4
+ * Accepted inputs:
5
+ * - https://host
6
+ * - host (will default to https)
7
+ */
8
+ export declare function resolveServerUrl(input: string): string;
@@ -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,3 @@
1
+ export * from '../skill-init/providers/index.js';
2
+ export * from '../skill-init/templates/index.js';
3
+ export * from '../skill-init/utils.js';
@@ -0,0 +1,3 @@
1
+ export * from '../skill-init/providers/index.js';
2
+ export * from '../skill-init/templates/index.js';
3
+ export * from '../skill-init/utils.js';
@@ -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,5 @@
1
+ import type { SkillProvider } from "../../skill-init/providers/base.js";
2
+ /**
3
+ * OpenCode provider implementation for skill generation
4
+ */
5
+ export declare const opencodeProvider: SkillProvider;
@@ -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,3 @@
1
+ import { kontextedCliSkill } from '../../skill-init/templates/kontexted-cli.js';
2
+ export { kontextedCliSkill };
3
+ export declare const allTemplates: import("../index.js").SkillDefinition[];
@@ -0,0 +1,3 @@
1
+ import { kontextedCliSkill } from '../../skill-init/templates/kontexted-cli.js';
2
+ export { kontextedCliSkill };
3
+ export const allTemplates = [kontextedCliSkill];
@@ -0,0 +1,2 @@
1
+ import type { SkillDefinition } from '../providers/base.js';
2
+ export declare const kontextedCliSkill: SkillDefinition;
@@ -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
+ };