app navigation

This commit is contained in:
Arjun 2026-03-13 10:26:08 +05:30 committed by arkml
parent 8f1adfb6a5
commit d150294af1
9 changed files with 433 additions and 1 deletions

View file

@ -0,0 +1,80 @@
export const skill = String.raw`
# App Navigation Skill
You have access to the **app-navigation** tool which lets you control the Rowboat UI directly opening notes, switching views, filtering the knowledge base, and creating saved views.
## Actions
### open-note
Open a specific knowledge file in the editor pane.
**When to use:** When the user asks to see, open, or view a specific note (e.g., "open John's note", "show me the Acme project page").
**Parameters:**
- ` + "`path`" + `: Full workspace-relative path (e.g., ` + "`knowledge/People/John Smith.md`" + `)
**Tips:**
- Use ` + "`workspace-grep`" + ` first to find the exact path if you're unsure of the filename.
- Always pass the full ` + "`knowledge/...`" + ` path, not just the filename.
### open-view
Switch the UI to the graph or bases view.
**When to use:** When the user asks to see the knowledge graph, view all notes, or open the bases/table view.
**Parameters:**
- ` + "`view`" + `: ` + "`\"graph\"`" + ` or ` + "`\"bases\"`" + `
### update-base-view
Change filters, columns, sort order, or search in the bases (table) view.
**When to use:** When the user asks to find, filter, sort, or search notes. Examples: "show me all active customers", "filter by topic=hiring", "sort by name", "search for pricing".
**Parameters:**
- ` + "`filters`" + `: Object with ` + "`set`" + `, ` + "`add`" + `, ` + "`remove`" + `, or ` + "`clear`" + ` each takes an array of ` + "`{ category, value }`" + ` pairs.
- ` + "`set`" + `: Replace ALL current filters with these.
- ` + "`add`" + `: Append filters without removing existing ones.
- ` + "`remove`" + `: Remove specific filters.
- ` + "`clear: true`" + `: Remove all filters.
- ` + "`columns`" + `: Object with ` + "`set`" + `, ` + "`add`" + `, or ` + "`remove`" + ` each takes an array of column names (frontmatter keys).
- ` + "`sort`" + `: ` + "`{ field, dir }`" + ` where dir is ` + "`\"asc\"`" + ` or ` + "`\"desc\"`" + `.
- ` + "`search`" + `: Free-text search string.
**Tips:**
- If unsure what categories/values are available, call ` + "`get-base-state`" + ` first.
- For "show me X", prefer ` + "`filters.set`" + ` to start fresh rather than ` + "`filters.add`" + `.
- Categories come from frontmatter keys (e.g., relationship, status, topic, type).
### get-base-state
Retrieve information about what's in the knowledge base available filter categories, values, and note count.
**When to use:** When you need to know what properties exist before filtering, or when the user asks "what can I filter by?", "how many notes are there?", etc.
**Parameters:**
- ` + "`base_name`" + ` (optional): Name of a saved base to inspect.
### create-base
Save the current view configuration as a named base.
**When to use:** When the user asks to save a filtered view, create a saved search, or says "save this as [name]".
**Parameters:**
- ` + "`name`" + `: Human-readable name for the base.
## Workflow Example
1. User: "Show me all people who are customers"
2. First, check what properties are available:
` + "`app-navigation({ action: \"get-base-state\" })`" + `
3. Apply filters based on the available properties:
` + "`app-navigation({ action: \"update-base-view\", filters: { set: [{ category: \"relationship\", value: \"customer\" }] } })`" + `
4. If the user wants to save it:
` + "`app-navigation({ action: \"create-base\", name: \"Customers\" })`" + `
## Important Notes
- The ` + "`update-base-view`" + ` action will automatically navigate to the bases view if the user isn't already there.
- ` + "`open-note`" + ` validates that the file exists before navigating.
- Filter categories and values come from frontmatter in knowledge files.
`;
export default skill;

View file

@ -11,6 +11,7 @@ import slackSkill from "./slack/skill.js";
import backgroundAgentsSkill from "./background-agents/skill.js";
import createPresentationsSkill from "./create-presentations/skill.js";
import webSearchSkill from "./web-search/skill.js";
import appNavigationSkill from "./app-navigation/skill.js";
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url));
const CATALOG_PREFIX = "src/application/assistant/skills";
@ -95,6 +96,12 @@ const definitions: SkillDefinition[] = [
summary: "Following the confirmation process before removing workflows or agents and their dependencies.",
content: deletionGuardrailsSkill,
},
{
id: "app-navigation",
title: "App Navigation",
summary: "Navigate the app UI - open notes, switch views, filter/search the knowledge base, and manage saved views.",
content: appNavigationSkill,
},
];
const skillEntries = definitions.map((definition) => ({

View file

@ -0,0 +1,22 @@
/**
* Shared types for the Bases view (saved filtered views over the knowledge graph).
*/
export type SortDir = 'asc' | 'desc';
export interface ActiveFilter {
category: string;
value: string;
}
export interface BaseConfig {
filters: ActiveFilter[];
columns: string[];
sort?: { field: string; dir: SortDir };
search?: string;
}
export const DEFAULT_BASE_CONFIG: BaseConfig = {
filters: [],
columns: [],
};

View file

@ -0,0 +1,60 @@
/**
* Frontmatter parsing utilities for knowledge base markdown files.
* Used by core (get-base-state) to scan knowledge files for available properties.
*/
/**
* Parse a markdown file's YAML frontmatter into key-value pairs.
* Handles both scalar values (`key: value`) and list values (`key:\n - item`).
* Returns `{ fields, body }` where fields maps keys to string or string[].
*/
export function parseFrontmatter(content: string): { fields: Record<string, string | string[]>; body: string } {
if (!content.startsWith('---')) {
return { fields: {}, body: content };
}
const endIndex = content.indexOf('\n---', 3);
if (endIndex === -1) {
return { fields: {}, body: content };
}
const rawBlock = content.slice(4, endIndex); // skip opening '---\n'
const body = content.slice(endIndex + 4).replace(/^\n/, '');
const fields: Record<string, string | string[]> = {};
let currentKey: string | null = null;
for (const line of rawBlock.split('\n')) {
if (line.trim() === '' || line === '---') {
currentKey = null;
continue;
}
// Top-level key: value
const topMatch = line.match(/^(\w[\w\s]*\w|\w+):\s*(.*)$/);
if (topMatch) {
const key = topMatch[1];
const value = topMatch[2].trim();
if (value) {
fields[key] = value;
currentKey = null;
} else {
// List will follow
currentKey = key;
fields[key] = [];
}
continue;
}
// List item under current key
if (currentKey) {
const itemMatch = line.match(/^\s+-\s+(.+)$/);
if (itemMatch) {
const arr = fields[currentKey];
if (Array.isArray(arr)) {
arr.push(itemMatch[1].trim());
}
}
}
}
return { fields, body };
}

View file

@ -8,4 +8,6 @@ export * as agentSchedule from './agent-schedule.js';
export * as agentScheduleState from './agent-schedule-state.js';
export * as serviceEvents from './service-events.js'
export * as inlineTask from './inline-task.js';
export * as frontmatter from './frontmatter.js';
export * as bases from './bases.js';
export { PrefixLogger };