Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 72 additions & 68 deletions README.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/limps/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"build": "tsc",
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc",
"dev": "tsc --watch",
"type-check": "tsc --noEmit",
"lint": "eslint src/ tests/ --max-warnings 0",
Expand Down
18 changes: 16 additions & 2 deletions packages/limps/src/cli.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,24 @@ import Pastel from 'pastel';
import { getPackageVersion } from './utils/version.js';
import { startHttpServer, stopHttpServer } from './server-http.js';
import { logRedactedError } from './utils/safe-logging.js';
import { getCompletionSuggestions } from './core/completion.js';

// Check if running start command with --foreground flag — bypass Ink for clean stdio
const args = process.argv.slice(2);
const isStartForeground = args[0] === 'start' && args.includes('--foreground');
const isCompletionRequest = process.env.LIMPS_COMPLETE === '1';

if (isCompletionRequest) {
const separatorIndex = args.indexOf('--');
const tokens = separatorIndex === -1 ? args : args.slice(separatorIndex + 1);
const suggestions = getCompletionSuggestions(tokens, {
configPath: process.env.MCP_PLANNING_CONFIG,
});
process.stdout.write(suggestions.join('\n'));
process.exit(0);
}

// Check if running server start command with --foreground flag — bypass Ink for clean stdio
const isStartForeground =
args.includes('--foreground') && args[0] === 'server' && args[1] === 'start';
const wantsHelp =
args.includes('--help') || args.includes('-h') || (isStartForeground && args.includes('help'));

Expand Down
2 changes: 1 addition & 1 deletion packages/limps/src/cli/config-cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ export function generateChatGptInstructions(configPath: string): string {
lines.push(` Server Name: ${serverName}`);
lines.push(' Server URL: https://your-tunnel-domain.example/mcp');
lines.push(' Authentication: (required by your proxy/deployment)');
lines.push(` Local command: limps start --config ${configPath}`);
lines.push(` Local command: limps server start --config ${configPath}`);
lines.push('');
lines.push('Tip: create one connector per project config.');

Expand Down
8 changes: 4 additions & 4 deletions packages/limps/src/cli/init-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function initProject(targetPath = '.'): string {
// Start the HTTP daemon
lines.push('Next steps:\n');
lines.push('1. Start the HTTP daemon:');
lines.push(` ${localPlannerPath} start --config "${configPath}"`);
lines.push(` ${localPlannerPath} server start --config "${configPath}"`);
lines.push('');
lines.push('2. Generate MCP client config:');
lines.push(` ${localPlannerPath} config print --client claude-code --config "${configPath}"`);
Expand All @@ -109,13 +109,13 @@ export function initProject(targetPath = '.'): string {

// Daemon management
lines.push('Manage the daemon:');
lines.push(` ${localPlannerPath} server-status --config "${configPath}" # Check status`);
lines.push(` ${localPlannerPath} stop --config "${configPath}" # Stop daemon`);
lines.push(` ${localPlannerPath} server status --config "${configPath}" # Check status`);
lines.push(` ${localPlannerPath} server stop --config "${configPath}" # Stop daemon`);
lines.push('');

// CLI usage
lines.push('Use --config for CLI commands:');
lines.push(` ${localPlannerPath} list-plans --config "${configPath}"`);
lines.push(` ${localPlannerPath} plan list --config "${configPath}"`);

return lines.join('\n');
}
2 changes: 1 addition & 1 deletion packages/limps/src/cli/next-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ function getPlanScoringOverrides(
!hasAnyOverrides
) {
console.warn(
`Malformed frontmatter in plan file: ${planFilePath}. Run \`limps repair-plans --check --json\` for a structured report.`
`Malformed frontmatter in plan file: ${planFilePath}. Run \`limps plan repair --check --json\` for a structured report.`
);
}

Expand Down
34 changes: 34 additions & 0 deletions packages/limps/src/commands/completion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Text } from 'ink';
import { z } from 'zod';
import { buildHelpOutput } from '../utils/cli-help.js';
import { getCompletionScript } from '../core/completion.js';

export const description = 'Generate shell completion scripts (bash, zsh, fish)';

export const args = z.tuple([z.enum(['bash', 'zsh', 'fish']).optional()]);

interface Props {
args: z.infer<typeof args>;
}

export default function CompletionCommand({ args }: Props): React.ReactNode {
const [shell] = args;
const help = buildHelpOutput({
usage: 'limps completion <bash|zsh|fish>',
examples: [
'limps completion zsh >> ~/.zshrc',
'limps completion bash >> ~/.bashrc',
'limps completion fish > ~/.config/fish/completions/limps.fish',
],
tips: [
'After writing completion config, open a new shell session.',
'Use tab after `limps `, `limps plan `, and `limps plan score --plan ` for suggestions.',
],
});

if (!shell) {
return <Text>{help.text}</Text>;
}

return <Text>{getCompletionScript(shell)}</Text>;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Text } from 'ink';
import { useEffect, useState } from 'react';
import { z } from 'zod';
import { createDoc, getCreateDocData } from '../cli/docs-create.js';
import { loadConfig } from '../config.js';
import { resolveConfigPath } from '../utils/config-resolver.js';
import { buildHelpOutput } from '../utils/cli-help.js';
import { handleJsonOutput, isJsonMode, outputJson, wrapError } from '../cli/json-output.js';
import { createDoc, getCreateDocData } from '../../cli/docs-create.js';
import { loadCommandContext } from '../../core/command-context.js';
import { buildHelpOutput } from '../../utils/cli-help.js';
import { handleJsonOutput, isJsonMode, outputJson, wrapError } from '../../cli/json-output.js';

export const description = 'Create a new document';

Expand All @@ -29,7 +28,7 @@ interface Props {
export default function CreateDocCommand({ args, options }: Props): React.ReactNode {
const [path] = args;
const help = buildHelpOutput({
usage: 'limps create-doc <path> [options]',
usage: 'limps docs create <path> [options]',
arguments: ['path Path for new document (relative to repo root)'],
options: [
'--config Path to config file',
Expand All @@ -38,8 +37,8 @@ export default function CreateDocCommand({ args, options }: Props): React.ReactN
'--json Output as JSON',
],
examples: [
'limps create-doc "research/notes.md" --content "# Notes"',
'limps create-doc "addendum.md" --template addendum --content "Details..."',
'limps docs create "research/notes.md" --content "# Notes"',
'limps docs create "addendum.md" --template addendum --content "Details..."',
],
});

Expand All @@ -60,8 +59,7 @@ export default function CreateDocCommand({ args, options }: Props): React.ReactN
return;
}

const configPath = resolveConfigPath(options.config);
const config = loadConfig(configPath);
const { config } = loadCommandContext(options.config);

const result = await getCreateDocData(config, {
path,
Expand Down Expand Up @@ -102,8 +100,7 @@ export default function CreateDocCommand({ args, options }: Props): React.ReactN
useEffect(() => {
(async (): Promise<void> => {
try {
const configPath = resolveConfigPath(options.config);
const config = loadConfig(configPath);
const { config } = loadCommandContext(options.config);
const result = await createDoc(config, {
path,
content: options.content || '',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Text } from 'ink';
import { useEffect, useState } from 'react';
import { z } from 'zod';
import { deleteDoc, getDeleteDocData } from '../cli/docs-delete.js';
import { loadConfig } from '../config.js';
import { resolveConfigPath } from '../utils/config-resolver.js';
import { buildHelpOutput } from '../utils/cli-help.js';
import { handleJsonOutput, isJsonMode, outputJson, wrapError } from '../cli/json-output.js';
import { deleteDoc, getDeleteDocData } from '../../cli/docs-delete.js';
import { loadCommandContext } from '../../core/command-context.js';
import { buildHelpOutput } from '../../utils/cli-help.js';
import { handleJsonOutput, isJsonMode, outputJson, wrapError } from '../../cli/json-output.js';

export const description = 'Delete a document';

Expand All @@ -26,7 +25,7 @@ interface Props {
export default function DeleteDocCommand({ args, options }: Props): React.ReactNode {
const [path] = args;
const help = buildHelpOutput({
usage: 'limps delete-doc <path> [options]',
usage: 'limps docs delete <path> [options]',
arguments: ['path Path to document to delete'],
options: [
'--config Path to config file',
Expand All @@ -35,9 +34,9 @@ export default function DeleteDocCommand({ args, options }: Props): React.ReactN
'--json Output as JSON',
],
examples: [
'limps delete-doc "notes.md"',
'limps delete-doc "notes.md" --confirm',
'limps delete-doc "old-file.md" --confirm --permanent',
'limps docs delete "notes.md"',
'limps docs delete "notes.md" --confirm',
'limps docs delete "old-file.md" --confirm --permanent',
],
});

Expand All @@ -58,8 +57,7 @@ export default function DeleteDocCommand({ args, options }: Props): React.ReactN
return;
}

const configPath = resolveConfigPath(options.config);
const config = loadConfig(configPath);
const { config } = loadCommandContext(options.config);

const result = await getDeleteDocData(config, {
path,
Expand Down Expand Up @@ -100,8 +98,7 @@ export default function DeleteDocCommand({ args, options }: Props): React.ReactN
useEffect(() => {
(async (): Promise<void> => {
try {
const configPath = resolveConfigPath(options.config);
const config = loadConfig(configPath);
const { config } = loadCommandContext(options.config);
const result = await deleteDoc(config, {
path,
confirm: options.confirm,
Expand Down
40 changes: 40 additions & 0 deletions packages/limps/src/commands/docs/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Text } from 'ink';
import { buildHelpOutput } from '../../utils/cli-help.js';

export const description = 'Document workflows (create/search/update/process)';

export default function DocsCommand(): React.ReactNode {
const help = buildHelpOutput({
usage: 'limps docs <command>',
sections: [
{
title: 'Commands',
lines: [
'list [path] List files/directories',
'search <query> Full-text search documents',
'create <path> Create a document',
'update <path> Update a document',
'delete <path> Delete a document',
'process [path] Run JavaScript processing against docs',
'tags <command> Manage tags (list/add/remove)',
'reindex Rebuild the search index',
],
},
{
title: 'Examples',
lines: [
'limps docs list plans',
'limps docs search "retry policy"',
'limps docs process plans/0004/plan.md --code "doc.content"',
'limps docs tags add plans/0004/plan.md --tags reviewed',
],
},
{
title: 'Help',
lines: ['Run `limps docs <command> --help` for options and examples.'],
},
],
});

return <Text>{help.text}</Text>;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Text } from 'ink';
import React, { useEffect, useState } from 'react';
import { z } from 'zod';
import { listDocs, getDocsListData } from '../cli/docs-list.js';
import { loadConfig } from '../config.js';
import { resolveConfigPath } from '../utils/config-resolver.js';
import { handleJsonOutput, isJsonMode, outputJson, wrapError } from '../cli/json-output.js';
import { listDocs, getDocsListData } from '../../cli/docs-list.js';
import { loadCommandContext } from '../../core/command-context.js';
import { handleJsonOutput, isJsonMode, outputJson, wrapError } from '../../cli/json-output.js';

export const description = 'List files and directories';

Expand Down Expand Up @@ -38,8 +37,7 @@ export default function ListDocsCommand({ args, options }: Props): React.ReactNo
const timer = setTimeout((): void => {
(async (): Promise<void> => {
try {
const configPath = resolveConfigPath(options.config);
const config = loadConfig(configPath);
const { config } = loadCommandContext(options.config);

const result = await getDocsListData(config, {
path,
Expand Down Expand Up @@ -79,8 +77,7 @@ export default function ListDocsCommand({ args, options }: Props): React.ReactNo
useEffect(() => {
(async (): Promise<void> => {
try {
const configPath = resolveConfigPath(options.config);
const config = loadConfig(configPath);
const { config } = loadCommandContext(options.config);
const result = await listDocs(config, {
path,
pattern: options.pattern,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import React, { useEffect, useState } from 'react';
import { Text, Box } from 'ink';
import { z } from 'zod';
import { loadConfig } from '../config.js';
import { resolveConfigPath } from '../utils/config-resolver.js';
import { buildHelpOutput } from '../utils/cli-help.js';
import { handleJsonOutput, isJsonMode, outputJson, wrapError } from '../cli/json-output.js';
import { loadCommandContext } from '../../core/command-context.js';
import { buildHelpOutput } from '../../utils/cli-help.js';
import { handleJsonOutput, isJsonMode, outputJson, wrapError } from '../../cli/json-output.js';
import {
handleProcessDoc,
type ProcessDocOutput,
ProcessDocInputSchema,
} from '../tools/process-doc.js';
} from '../../tools/process-doc.js';
import {
handleProcessDocs,
type ProcessDocsOutput,
ProcessDocsInputSchema,
} from '../tools/process-docs.js';
import { initializeDatabase, createSchema } from '../indexer.js';
} from '../../tools/process-docs.js';
import { initializeDatabase, createSchema } from '../../indexer.js';
import { join } from 'path';
import { mkdirSync } from 'fs';
import type { ToolContext } from '../types.js';
import type { ToolContext } from '../../types.js';

export const description = 'Process document(s) with JavaScript code';

Expand Down Expand Up @@ -48,7 +47,8 @@ export default function ProcessCommand({ args, options }: Props): React.ReactNod
const [error, setError] = useState<string | null>(null);

const help = buildHelpOutput({
usage: 'limps process <path> | limps process --pattern <glob> --code <code> [options]',
usage:
'limps docs process <path> | limps docs process --pattern <glob> --code <code> [options]',
arguments: ['path Optional document path (mutually exclusive with --pattern)'],
options: [
'--config Path to config file',
Expand All @@ -63,15 +63,15 @@ export default function ProcessCommand({ args, options }: Props): React.ReactNod
{
title: 'Single Document Examples',
lines: [
'limps process plans/0001-feature/plan.md --code "doc.content.length"',
'limps process plan.md --code "extractFrontmatter(doc.content).meta.name"',
'limps docs process plans/0001-feature/plan.md --code "doc.content.length"',
'limps docs process plan.md --code "extractFrontmatter(doc.content).meta.name"',
],
},
{
title: 'Multiple Documents Examples',
lines: [
'limps process --pattern "plans/*/*-plan.md" --code "docs.length"',
'limps process --pattern "**/*.md" --code "docs.map(d => d.path)"',
'limps docs process --pattern "plans/*/*-plan.md" --code "docs.length"',
'limps docs process --pattern "**/*.md" --code "docs.map(d => d.path)"',
],
},
],
Expand Down Expand Up @@ -122,8 +122,7 @@ export default function ProcessCommand({ args, options }: Props): React.ReactNod
return;
}

const configPath = resolveConfigPath(options.config);
const config = loadConfig(configPath);
const { config } = loadCommandContext(options.config);

// Initialize database
mkdirSync(config.dataPath, { recursive: true });
Expand Down Expand Up @@ -223,8 +222,7 @@ export default function ProcessCommand({ args, options }: Props): React.ReactNod
return;
}

const configPath = resolveConfigPath(options.config);
const config = loadConfig(configPath);
const { config } = loadCommandContext(options.config);

// Initialize database
mkdirSync(config.dataPath, { recursive: true });
Expand Down
Loading