@kinqs/brainrouter-cli 0.3.4

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 (87) hide show
  1. package/.env.example +109 -0
  2. package/README.md +185 -0
  3. package/dist/agent/agent.d.ts +765 -0
  4. package/dist/agent/agent.js +1977 -0
  5. package/dist/cli/cliPrompt.d.ts +15 -0
  6. package/dist/cli/cliPrompt.js +62 -0
  7. package/dist/cli/commands/_context.d.ts +53 -0
  8. package/dist/cli/commands/_context.js +14 -0
  9. package/dist/cli/commands/_helpers.d.ts +45 -0
  10. package/dist/cli/commands/_helpers.js +140 -0
  11. package/dist/cli/commands/guard.d.ts +6 -0
  12. package/dist/cli/commands/guard.js +292 -0
  13. package/dist/cli/commands/memory.d.ts +12 -0
  14. package/dist/cli/commands/memory.js +263 -0
  15. package/dist/cli/commands/obs.d.ts +6 -0
  16. package/dist/cli/commands/obs.js +208 -0
  17. package/dist/cli/commands/orchestration.d.ts +6 -0
  18. package/dist/cli/commands/orchestration.js +218 -0
  19. package/dist/cli/commands/session.d.ts +6 -0
  20. package/dist/cli/commands/session.js +191 -0
  21. package/dist/cli/commands/ui.d.ts +6 -0
  22. package/dist/cli/commands/ui.js +477 -0
  23. package/dist/cli/commands/workflow.d.ts +6 -0
  24. package/dist/cli/commands/workflow.js +691 -0
  25. package/dist/cli/repl.d.ts +12 -0
  26. package/dist/cli/repl.js +894 -0
  27. package/dist/config/config.d.ts +22 -0
  28. package/dist/config/config.js +105 -0
  29. package/dist/config/workspace.d.ts +7 -0
  30. package/dist/config/workspace.js +62 -0
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +610 -0
  33. package/dist/memory/briefing.d.ts +46 -0
  34. package/dist/memory/briefing.js +152 -0
  35. package/dist/memory/consolidation.d.ts +60 -0
  36. package/dist/memory/consolidation.js +208 -0
  37. package/dist/memory/formatters.d.ts +38 -0
  38. package/dist/memory/formatters.js +102 -0
  39. package/dist/memory/mentions.d.ts +10 -0
  40. package/dist/memory/mentions.js +72 -0
  41. package/dist/orchestration/orchestrator.d.ts +36 -0
  42. package/dist/orchestration/orchestrator.js +71 -0
  43. package/dist/orchestration/roles.d.ts +11 -0
  44. package/dist/orchestration/roles.js +117 -0
  45. package/dist/orchestration/tools.d.ts +244 -0
  46. package/dist/orchestration/tools.js +528 -0
  47. package/dist/prompt/breadthHint.d.ts +48 -0
  48. package/dist/prompt/breadthHint.js +93 -0
  49. package/dist/prompt/compactor.d.ts +31 -0
  50. package/dist/prompt/compactor.js +112 -0
  51. package/dist/prompt/initAgentMd.d.ts +13 -0
  52. package/dist/prompt/initAgentMd.js +194 -0
  53. package/dist/prompt/skillRunner.d.ts +34 -0
  54. package/dist/prompt/skillRunner.js +146 -0
  55. package/dist/prompt/systemPrompt.d.ts +10 -0
  56. package/dist/prompt/systemPrompt.js +171 -0
  57. package/dist/runtime/clipboard.d.ts +17 -0
  58. package/dist/runtime/clipboard.js +52 -0
  59. package/dist/runtime/llmSemaphore.d.ts +30 -0
  60. package/dist/runtime/llmSemaphore.js +67 -0
  61. package/dist/runtime/loopRunner.d.ts +25 -0
  62. package/dist/runtime/loopRunner.js +79 -0
  63. package/dist/runtime/mcpClient.d.ts +156 -0
  64. package/dist/runtime/mcpClient.js +234 -0
  65. package/dist/runtime/mcpUtils.d.ts +36 -0
  66. package/dist/runtime/mcpUtils.js +64 -0
  67. package/dist/runtime/sandbox.d.ts +48 -0
  68. package/dist/runtime/sandbox.js +156 -0
  69. package/dist/runtime/tracing.d.ts +25 -0
  70. package/dist/runtime/tracing.js +91 -0
  71. package/dist/state/cliState.d.ts +59 -0
  72. package/dist/state/cliState.js +311 -0
  73. package/dist/state/goalStore.d.ts +174 -0
  74. package/dist/state/goalStore.js +410 -0
  75. package/dist/state/hookifyStore.d.ts +80 -0
  76. package/dist/state/hookifyStore.js +237 -0
  77. package/dist/state/hooksStore.d.ts +42 -0
  78. package/dist/state/hooksStore.js +71 -0
  79. package/dist/state/preferencesStore.d.ts +41 -0
  80. package/dist/state/preferencesStore.js +25 -0
  81. package/dist/state/sessionStore.d.ts +42 -0
  82. package/dist/state/sessionStore.js +193 -0
  83. package/dist/state/taskStore.d.ts +23 -0
  84. package/dist/state/taskStore.js +80 -0
  85. package/dist/state/workflowArtifacts.d.ts +33 -0
  86. package/dist/state/workflowArtifacts.js +139 -0
  87. package/package.json +71 -0
@@ -0,0 +1,80 @@
1
+ import fs from 'node:fs';
2
+ import { getCliStateFile, getSessionStateFile, readJsonFile, writeJsonFile } from './cliState.js';
3
+ const EMPTY_PLAN = {
4
+ updatedAt: '',
5
+ items: [],
6
+ };
7
+ /**
8
+ * Durable per-session plan. Lives at
9
+ * <workspace>/.brainrouter/cli/sessions/<encodedKey>/tasks.json
10
+ *
11
+ * Legacy callers that don't pass a sessionKey read/write the older workspace-
12
+ * level `tasks.json` so existing workspaces keep their plan after the upgrade.
13
+ */
14
+ export function readPlan(workspaceRoot, sessionKey) {
15
+ if (sessionKey) {
16
+ const sessionPath = getSessionStateFile(workspaceRoot, sessionKey, 'tasks.json');
17
+ if (fs.existsSync(sessionPath)) {
18
+ return readJsonFile(sessionPath, EMPTY_PLAN);
19
+ }
20
+ }
21
+ return readJsonFile(getCliStateFile(workspaceRoot, 'tasks.json'), EMPTY_PLAN);
22
+ }
23
+ export function updatePlan(workspaceRoot, input, sessionKey) {
24
+ if (!Array.isArray(input.plan)) {
25
+ throw new Error('plan must be an array.');
26
+ }
27
+ const items = input.plan.map((item, index) => normalizePlanItem(item, index));
28
+ if (items.filter(item => item.status === 'in_progress').length > 1) {
29
+ throw new Error('At most one plan item can be in_progress.');
30
+ }
31
+ const state = {
32
+ explanation: typeof input.explanation === 'string' && input.explanation.trim()
33
+ ? input.explanation.trim()
34
+ : undefined,
35
+ updatedAt: new Date().toISOString(),
36
+ items,
37
+ };
38
+ const filePath = sessionKey
39
+ ? getSessionStateFile(workspaceRoot, sessionKey, 'tasks.json')
40
+ : getCliStateFile(workspaceRoot, 'tasks.json');
41
+ writeJsonFile(filePath, state);
42
+ return state;
43
+ }
44
+ export function formatPlan(state) {
45
+ if (state.items.length === 0) {
46
+ return 'No active plan.';
47
+ }
48
+ const lines = ['Current plan:'];
49
+ if (state.explanation) {
50
+ lines.push(state.explanation);
51
+ }
52
+ for (const item of state.items) {
53
+ lines.push(`- [${statusMarker(item.status)}] ${item.step}`);
54
+ }
55
+ return lines.join('\n');
56
+ }
57
+ function normalizePlanItem(item, index) {
58
+ if (!item || typeof item !== 'object') {
59
+ throw new Error(`Plan item ${index + 1} must be an object.`);
60
+ }
61
+ const step = typeof item.step === 'string' ? item.step.trim() : '';
62
+ if (!step) {
63
+ throw new Error(`Plan item ${index + 1} is missing a non-empty step.`);
64
+ }
65
+ const status = item.status;
66
+ if (!isPlanItemStatus(status)) {
67
+ throw new Error(`Plan item ${index + 1} has invalid status "${String(status)}".`);
68
+ }
69
+ return { step, status };
70
+ }
71
+ function isPlanItemStatus(value) {
72
+ return value === 'pending' || value === 'in_progress' || value === 'completed';
73
+ }
74
+ function statusMarker(status) {
75
+ if (status === 'completed')
76
+ return 'x';
77
+ if (status === 'in_progress')
78
+ return '/';
79
+ return ' ';
80
+ }
@@ -0,0 +1,33 @@
1
+ export interface WorkflowMeta {
2
+ slug: string;
3
+ title: string;
4
+ kind: 'feature-dev' | 'spec' | 'review' | 'implement-plan' | string;
5
+ createdAt: string;
6
+ updatedAt: string;
7
+ status: 'draft' | 'awaiting-approval' | 'in-progress' | 'completed' | 'closed';
8
+ }
9
+ /** Canonical artifact names. Use the constants rather than hard-coded strings so a future rename is one edit. */
10
+ export declare const ARTIFACT: {
11
+ readonly spec: "spec.md";
12
+ readonly tasks: "tasks.md";
13
+ readonly walkthrough: "walkthrough.md";
14
+ };
15
+ export declare function slugify(input: string, fallback?: string): string;
16
+ export declare function getWorkflowsRoot(workspaceRoot: string): string;
17
+ export declare function getWorkflowDir(workspaceRoot: string, slug: string): string;
18
+ export declare function createWorkflow(workspaceRoot: string, input: {
19
+ title: string;
20
+ kind: WorkflowMeta['kind'];
21
+ slug?: string;
22
+ }): WorkflowMeta;
23
+ export declare function updateWorkflowStatus(workspaceRoot: string, slug: string, status: WorkflowMeta['status']): WorkflowMeta | undefined;
24
+ export declare function listWorkflows(workspaceRoot: string): WorkflowMeta[];
25
+ export declare function setCurrentWorkflow(workspaceRoot: string, slug: string): void;
26
+ export declare function getCurrentWorkflow(workspaceRoot: string): string | undefined;
27
+ /**
28
+ * Path (relative to workspace root) the LLM should `write_file` to for a
29
+ * given artifact. We return a workspace-relative path because that's the
30
+ * unit `write_file` expects.
31
+ */
32
+ export declare function artifactRelativePath(workspaceRoot: string, slug: string, artifact: string): string;
33
+ export declare function readArtifact(workspaceRoot: string, slug: string, artifact: string): string | undefined;
@@ -0,0 +1,139 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { getCliStateFile, getWorkspaceLocalDir, isPathInside, readJsonFile, writeJsonFile } from './cliState.js';
4
+ /**
5
+ * Canonical home for durable workflow artifacts produced by the multi-agent
6
+ * commands (/feature-dev, /spec, /review, /implement-plan).
7
+ *
8
+ * One workflow == one slug == one directory:
9
+ * <workspace>/.brainrouter/workflows/<slug>/
10
+ * spec.md — the agreed specification (what + why + boundaries)
11
+ * tasks.md — human-readable task breakdown for execution
12
+ * walkthrough.md — post-implementation summary (what was built, where)
13
+ * meta.json — { slug, title, kind, createdAt, updatedAt, status }
14
+ * notes/ — optional supplementary artifacts (explorer reports etc.)
15
+ *
16
+ * Workflows are the ONLY thing brainrouter writes inside the workspace. They
17
+ * stay here because (a) they're meant to be committed alongside code so the
18
+ * team shares them, and (b) the agent's `write_file` tool only accepts paths
19
+ * relative to the workspace root. Personal CLI state (sessions, hooks,
20
+ * memories, preferences) lives in `~/.brainrouter/workspaces/<encoded>/` and
21
+ * never touches the project tree. The current-workflow pointer is still per-
22
+ * user (it tracks which workflow YOU are focused on right now) so it lives
23
+ * with the CLI state, not the workspace.
24
+ */
25
+ const WORKFLOWS_SUBDIR = 'workflows';
26
+ const CURRENT_POINTER_FILE = 'current-workflow.json';
27
+ /** Canonical artifact names. Use the constants rather than hard-coded strings so a future rename is one edit. */
28
+ export const ARTIFACT = {
29
+ spec: 'spec.md',
30
+ tasks: 'tasks.md',
31
+ walkthrough: 'walkthrough.md',
32
+ };
33
+ export function slugify(input, fallback = 'workflow') {
34
+ const base = (input ?? '').toString().toLowerCase()
35
+ .replace(/[^a-z0-9\s-]/g, '')
36
+ .trim()
37
+ .replace(/\s+/g, '-')
38
+ .replace(/-+/g, '-')
39
+ .slice(0, 60);
40
+ return base || fallback;
41
+ }
42
+ export function getWorkflowsRoot(workspaceRoot) {
43
+ const wsLocal = getWorkspaceLocalDir(workspaceRoot);
44
+ const root = path.join(wsLocal, WORKFLOWS_SUBDIR);
45
+ if (!isPathInside(wsLocal, root)) {
46
+ throw new Error('Workflows root escapes workspace-local directory.');
47
+ }
48
+ fs.mkdirSync(root, { recursive: true });
49
+ return root;
50
+ }
51
+ export function getWorkflowDir(workspaceRoot, slug) {
52
+ const safeSlug = slugify(slug);
53
+ const root = getWorkflowsRoot(workspaceRoot);
54
+ const dir = path.join(root, safeSlug);
55
+ if (!isPathInside(root, dir)) {
56
+ throw new Error(`Workflow slug "${slug}" escapes workflows root.`);
57
+ }
58
+ fs.mkdirSync(dir, { recursive: true });
59
+ return dir;
60
+ }
61
+ export function createWorkflow(workspaceRoot, input) {
62
+ const slug = slugify(input.slug ?? input.title);
63
+ const dir = getWorkflowDir(workspaceRoot, slug);
64
+ const metaPath = path.join(dir, 'meta.json');
65
+ const now = new Date().toISOString();
66
+ const existing = readJsonFile(metaPath, null);
67
+ const meta = existing ?? {
68
+ slug,
69
+ title: input.title,
70
+ kind: input.kind,
71
+ createdAt: now,
72
+ updatedAt: now,
73
+ status: 'draft',
74
+ };
75
+ meta.updatedAt = now;
76
+ // If meta exists, keep its createdAt but allow title/kind drift only when the caller explicitly differs.
77
+ if (existing) {
78
+ if (input.title)
79
+ meta.title = input.title;
80
+ if (input.kind)
81
+ meta.kind = input.kind;
82
+ }
83
+ writeJsonFile(metaPath, meta);
84
+ setCurrentWorkflow(workspaceRoot, slug);
85
+ return meta;
86
+ }
87
+ export function updateWorkflowStatus(workspaceRoot, slug, status) {
88
+ const dir = getWorkflowDir(workspaceRoot, slug);
89
+ const metaPath = path.join(dir, 'meta.json');
90
+ const existing = readJsonFile(metaPath, null);
91
+ if (!existing)
92
+ return undefined;
93
+ existing.status = status;
94
+ existing.updatedAt = new Date().toISOString();
95
+ writeJsonFile(metaPath, existing);
96
+ return existing;
97
+ }
98
+ export function listWorkflows(workspaceRoot) {
99
+ const root = getWorkflowsRoot(workspaceRoot);
100
+ if (!fs.existsSync(root))
101
+ return [];
102
+ const entries = fs.readdirSync(root, { withFileTypes: true });
103
+ const out = [];
104
+ for (const entry of entries) {
105
+ if (!entry.isDirectory())
106
+ continue;
107
+ const metaPath = path.join(root, entry.name, 'meta.json');
108
+ const meta = readJsonFile(metaPath, null);
109
+ if (meta)
110
+ out.push(meta);
111
+ }
112
+ return out.sort((a, b) => (b.updatedAt ?? '').localeCompare(a.updatedAt ?? ''));
113
+ }
114
+ export function setCurrentWorkflow(workspaceRoot, slug) {
115
+ writeJsonFile(getCliStateFile(workspaceRoot, CURRENT_POINTER_FILE), { slug, at: new Date().toISOString() });
116
+ }
117
+ export function getCurrentWorkflow(workspaceRoot) {
118
+ const ptr = readJsonFile(getCliStateFile(workspaceRoot, CURRENT_POINTER_FILE), null);
119
+ return ptr?.slug;
120
+ }
121
+ /**
122
+ * Path (relative to workspace root) the LLM should `write_file` to for a
123
+ * given artifact. We return a workspace-relative path because that's the
124
+ * unit `write_file` expects.
125
+ */
126
+ export function artifactRelativePath(workspaceRoot, slug, artifact) {
127
+ // Normalize the base to its real path so we don't return a `../../private/...` style
128
+ // relative path on macOS, where /var → /private/var via realpath.
129
+ const normalizedRoot = fs.realpathSync(workspaceRoot);
130
+ const abs = path.join(getWorkflowDir(workspaceRoot, slug), artifact);
131
+ return path.relative(normalizedRoot, abs);
132
+ }
133
+ export function readArtifact(workspaceRoot, slug, artifact) {
134
+ const dir = getWorkflowDir(workspaceRoot, slug);
135
+ const filePath = path.join(dir, artifact);
136
+ if (!fs.existsSync(filePath))
137
+ return undefined;
138
+ return fs.readFileSync(filePath, 'utf8');
139
+ }
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@kinqs/brainrouter-cli",
3
+ "version": "0.3.4",
4
+ "description": "Memory-native terminal coding agent. Talks to the BrainRouter MCP cognitive engine for recall, skills, capture, persona, focus scenes, and contradiction tracking.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "brainrouter": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ ".env.example"
14
+ ],
15
+ "scripts": {
16
+ "clean": "rm -rf dist",
17
+ "build": "npm run clean && tsc",
18
+ "dev": "tsx src/index.ts",
19
+ "start": "node dist/index.js",
20
+ "test": "npm run build && node --test \"dist/**/*.test.js\"",
21
+ "prepack": "npm run build && find dist -name '*.test.*' -delete"
22
+ },
23
+ "dependencies": {
24
+ "@kinqs/brainrouter-sdk": "^0.3.4",
25
+ "@kinqs/brainrouter-types": "^0.3.4",
26
+ "@modelcontextprotocol/sdk": "^1.11.0",
27
+ "chalk": "^5.3.0",
28
+ "commander": "^12.1.0",
29
+ "dotenv": "^16.4.5",
30
+ "inquirer": "^9.3.2",
31
+ "marked": "^12.0.1",
32
+ "marked-terminal": "^7.0.0",
33
+ "ora": "^8.0.1"
34
+ },
35
+ "devDependencies": {
36
+ "@types/inquirer": "^9.0.7",
37
+ "@types/marked": "^4.0.8",
38
+ "@types/node": "^22.0.0",
39
+ "tsx": "^4.7.0",
40
+ "typescript": "^5.5.4"
41
+ },
42
+ "engines": {
43
+ "node": ">=22.0.0"
44
+ },
45
+ "keywords": [
46
+ "ai",
47
+ "agent",
48
+ "cli",
49
+ "mcp",
50
+ "model-context-protocol",
51
+ "memory",
52
+ "llm",
53
+ "coding-agent",
54
+ "terminal",
55
+ "brainrouter"
56
+ ],
57
+ "license": "MIT",
58
+ "author": "BrainRouter contributors",
59
+ "homepage": "https://github.com/kinqsradiollc/BrainRouter#readme",
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "git+https://github.com/kinqsradiollc/BrainRouter.git",
63
+ "directory": "brainrouter-cli"
64
+ },
65
+ "bugs": {
66
+ "url": "https://github.com/kinqsradiollc/BrainRouter/issues"
67
+ },
68
+ "publishConfig": {
69
+ "access": "public"
70
+ }
71
+ }