@lovelybunch/api 1.0.47 → 1.0.48
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/dist/lib/gait-path.d.ts +7 -5
- package/dist/lib/gait-path.js +23 -17
- package/dist/lib/project-paths.d.ts +2 -2
- package/dist/lib/project-paths.js +8 -5
- package/dist/lib/storage/file-storage.js +4 -4
- package/dist/lib/symlinks/symlink-manager.js +40 -24
- package/dist/lib/terminal/context-helper.js +27 -27
- package/dist/lib/terminal/shell-utils.js +6 -6
- package/dist/lib/terminal/terminal-manager.d.ts +2 -0
- package/dist/lib/terminal/terminal-manager.js +31 -9
- package/dist/routes/api/v1/agents/[id]/route.js +4 -4
- package/dist/routes/api/v1/agents/route.js +4 -4
- package/dist/routes/api/v1/ai/route.js +3 -6
- package/dist/routes/api/v1/chats/[id]/route.js +4 -4
- package/dist/routes/api/v1/chats/route.js +4 -4
- package/dist/routes/api/v1/config/index.js +2 -1
- package/dist/routes/api/v1/config/route.d.ts +13 -0
- package/dist/routes/api/v1/config/route.js +69 -0
- package/dist/routes/api/v1/context/knowledge/[filename]/route.js +4 -4
- package/dist/routes/api/v1/context/knowledge/route.js +4 -4
- package/dist/routes/api/v1/mcp/config/index.d.ts +1 -0
- package/dist/routes/api/v1/mcp/config/index.js +1 -0
- package/dist/routes/api/v1/mcp/config/route.d.ts +3 -0
- package/dist/routes/api/v1/mcp/config/route.js +59 -0
- package/dist/routes/api/v1/mcp/index.js +2 -2
- package/dist/routes/api/v1/proposals/[id]/route.d.ts +0 -2
- package/dist/routes/api/v1/proposals/route.d.ts +0 -2
- package/dist/routes/api/v1/resources/[id]/route.js +4 -4
- package/dist/routes/api/v1/resources/[id]/thumbnail/route.js +4 -4
- package/dist/routes/api/v1/resources/route.js +4 -4
- package/dist/routes/api/v1/symlinks/index.d.ts +1 -0
- package/dist/routes/api/v1/symlinks/index.js +1 -0
- package/dist/routes/api/v1/symlinks/route.d.ts +3 -0
- package/dist/routes/api/v1/symlinks/route.js +135 -0
- package/dist/server.js +2 -0
- package/package.json +5 -5
- package/static/assets/index-DnaNaR1E.js +714 -0
- package/static/index.html +1 -1
package/dist/lib/gait-path.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Find the .
|
|
2
|
+
* Find the .nut directory by traversing up from the current working directory
|
|
3
3
|
* If GAIT_DATA_PATH is set (from CLI), use that instead
|
|
4
4
|
*/
|
|
5
|
-
export declare function
|
|
5
|
+
export declare function findNutDirectory(startDir?: string): Promise<string | null>;
|
|
6
|
+
export declare const findGaitDirectory: typeof findNutDirectory;
|
|
6
7
|
/**
|
|
7
|
-
* Get the path to a context file, automatically finding the .
|
|
8
|
+
* Get the path to a context file, automatically finding the .nut directory
|
|
8
9
|
*/
|
|
9
10
|
export declare function getContextFilePath(fileName: string): Promise<string | null>;
|
|
10
11
|
/**
|
|
11
|
-
* Get the .
|
|
12
|
+
* Get the .nut config to read storage path settings
|
|
12
13
|
*/
|
|
13
|
-
export declare function
|
|
14
|
+
export declare function getNutConfig(): Promise<any | null>;
|
|
15
|
+
export declare const getGaitConfig: typeof getNutConfig;
|
package/dist/lib/gait-path.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { promises as fs } from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
// Directory constant
|
|
4
|
+
const NUT_DIR_NAME = '.nut';
|
|
3
5
|
/**
|
|
4
|
-
* Find the .
|
|
6
|
+
* Find the .nut directory by traversing up from the current working directory
|
|
5
7
|
* If GAIT_DATA_PATH is set (from CLI), use that instead
|
|
6
8
|
*/
|
|
7
|
-
export async function
|
|
9
|
+
export async function findNutDirectory(startDir) {
|
|
8
10
|
// If running from CLI (gait serve), use the directory where command was run
|
|
9
11
|
if (process.env.GAIT_DATA_PATH) {
|
|
10
|
-
const
|
|
12
|
+
const nutPath = path.join(process.env.GAIT_DATA_PATH, NUT_DIR_NAME);
|
|
11
13
|
try {
|
|
12
|
-
await fs.access(
|
|
13
|
-
return
|
|
14
|
+
await fs.access(nutPath);
|
|
15
|
+
return nutPath;
|
|
14
16
|
}
|
|
15
17
|
catch {
|
|
16
18
|
// Fall through to directory traversal
|
|
@@ -19,10 +21,10 @@ export async function findGaitDirectory(startDir) {
|
|
|
19
21
|
// Otherwise traverse up from start directory
|
|
20
22
|
let currentDir = startDir || process.cwd();
|
|
21
23
|
while (currentDir !== path.parse(currentDir).root) {
|
|
22
|
-
const
|
|
24
|
+
const nutPath = path.join(currentDir, NUT_DIR_NAME);
|
|
23
25
|
try {
|
|
24
|
-
await fs.access(
|
|
25
|
-
return
|
|
26
|
+
await fs.access(nutPath);
|
|
27
|
+
return nutPath;
|
|
26
28
|
}
|
|
27
29
|
catch {
|
|
28
30
|
currentDir = path.dirname(currentDir);
|
|
@@ -30,23 +32,25 @@ export async function findGaitDirectory(startDir) {
|
|
|
30
32
|
}
|
|
31
33
|
return null;
|
|
32
34
|
}
|
|
35
|
+
// Legacy function name for backward compatibility
|
|
36
|
+
export const findGaitDirectory = findNutDirectory;
|
|
33
37
|
/**
|
|
34
|
-
* Get the path to a context file, automatically finding the .
|
|
38
|
+
* Get the path to a context file, automatically finding the .nut directory
|
|
35
39
|
*/
|
|
36
40
|
export async function getContextFilePath(fileName) {
|
|
37
|
-
const
|
|
38
|
-
if (!
|
|
41
|
+
const nutDir = await findNutDirectory();
|
|
42
|
+
if (!nutDir)
|
|
39
43
|
return null;
|
|
40
|
-
return path.join(
|
|
44
|
+
return path.join(nutDir, 'context', fileName);
|
|
41
45
|
}
|
|
42
46
|
/**
|
|
43
|
-
* Get the .
|
|
47
|
+
* Get the .nut config to read storage path settings
|
|
44
48
|
*/
|
|
45
|
-
export async function
|
|
46
|
-
const
|
|
47
|
-
if (!
|
|
49
|
+
export async function getNutConfig() {
|
|
50
|
+
const nutDir = await findNutDirectory();
|
|
51
|
+
if (!nutDir)
|
|
48
52
|
return null;
|
|
49
|
-
const configPath = path.join(
|
|
53
|
+
const configPath = path.join(nutDir, 'config.json');
|
|
50
54
|
try {
|
|
51
55
|
const config = await fs.readFile(configPath, 'utf-8');
|
|
52
56
|
return JSON.parse(config);
|
|
@@ -55,3 +59,5 @@ export async function getGaitConfig() {
|
|
|
55
59
|
return null;
|
|
56
60
|
}
|
|
57
61
|
}
|
|
62
|
+
// Legacy function name for backward compatibility
|
|
63
|
+
export const getGaitConfig = getNutConfig;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Gets the consistent project root path for
|
|
2
|
+
* Gets the consistent project root path for Coconut operations.
|
|
3
3
|
* This ensures that both symlink operations use the same base path.
|
|
4
4
|
*/
|
|
5
5
|
export declare function getProjectRoot(): Promise<string>;
|
|
@@ -8,6 +8,6 @@ export declare function getProjectRoot(): Promise<string>;
|
|
|
8
8
|
*/
|
|
9
9
|
export declare function getSymlinkPath(): Promise<string>;
|
|
10
10
|
/**
|
|
11
|
-
* Gets the path to the actual CLAUDE.md file in .
|
|
11
|
+
* Gets the path to the actual CLAUDE.md file in .nut/rules
|
|
12
12
|
*/
|
|
13
13
|
export declare function getTargetPath(): Promise<string>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
|
+
// Directory constant
|
|
4
|
+
const NUT_DIR_NAME = '.nut';
|
|
3
5
|
/**
|
|
4
|
-
* Gets the consistent project root path for
|
|
6
|
+
* Gets the consistent project root path for Coconut operations.
|
|
5
7
|
* This ensures that both symlink operations use the same base path.
|
|
6
8
|
*/
|
|
7
9
|
export async function getProjectRoot() {
|
|
@@ -19,10 +21,11 @@ export async function getProjectRoot() {
|
|
|
19
21
|
// Check if we're at the project root by looking for distinctive files
|
|
20
22
|
const possibleRoot = currentPath;
|
|
21
23
|
const hasPackageJson = await fs.access(path.join(possibleRoot, 'package.json')).then(() => true).catch(() => false);
|
|
22
|
-
|
|
24
|
+
// Check for .nut directory
|
|
25
|
+
const hasNutDir = await fs.access(path.join(possibleRoot, NUT_DIR_NAME)).then(() => true).catch(() => false);
|
|
23
26
|
const hasPnpmWorkspace = await fs.access(path.join(possibleRoot, 'pnpm-workspace.yaml')).then(() => true).catch(() => false);
|
|
24
27
|
// If this looks like the project root, use it
|
|
25
|
-
if (hasPackageJson &&
|
|
28
|
+
if (hasPackageJson && hasNutDir && hasPnpmWorkspace) {
|
|
26
29
|
return possibleRoot;
|
|
27
30
|
}
|
|
28
31
|
// Otherwise, check parent directory
|
|
@@ -49,9 +52,9 @@ export async function getSymlinkPath() {
|
|
|
49
52
|
return path.join(projectRoot, 'CLAUDE.md');
|
|
50
53
|
}
|
|
51
54
|
/**
|
|
52
|
-
* Gets the path to the actual CLAUDE.md file in .
|
|
55
|
+
* Gets the path to the actual CLAUDE.md file in .nut/rules
|
|
53
56
|
*/
|
|
54
57
|
export async function getTargetPath() {
|
|
55
58
|
const projectRoot = await getProjectRoot();
|
|
56
|
-
return path.join(projectRoot, '.
|
|
59
|
+
return path.join(projectRoot, '.nut', 'rules', 'CLAUDE.md');
|
|
57
60
|
}
|
|
@@ -9,16 +9,16 @@ export class FileStorageAdapter {
|
|
|
9
9
|
this.basePath = basePath;
|
|
10
10
|
}
|
|
11
11
|
else if (process.env.NODE_ENV === 'development' && process.env.GAIT_DEV_ROOT) {
|
|
12
|
-
// Dev mode: use project root .
|
|
12
|
+
// Dev mode: use project root .nut directory
|
|
13
13
|
this.basePath = process.env.GAIT_DEV_ROOT;
|
|
14
14
|
}
|
|
15
15
|
else if (process.env.GAIT_DATA_PATH) {
|
|
16
16
|
// Production mode: use GAIT_DATA_PATH (set by CLI)
|
|
17
|
-
this.basePath = path.join(process.env.GAIT_DATA_PATH, '.
|
|
17
|
+
this.basePath = path.join(process.env.GAIT_DATA_PATH, '.nut');
|
|
18
18
|
}
|
|
19
19
|
else {
|
|
20
|
-
// Fallback: use current directory .
|
|
21
|
-
this.basePath = '.
|
|
20
|
+
// Fallback: use current directory .nut
|
|
21
|
+
this.basePath = '.nut';
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
async ensureDirectories() {
|
|
@@ -10,7 +10,7 @@ export class SymlinkManager {
|
|
|
10
10
|
state;
|
|
11
11
|
constructor(projectRoot) {
|
|
12
12
|
this.projectRoot = projectRoot;
|
|
13
|
-
this.configPath = path.join(projectRoot, '.
|
|
13
|
+
this.configPath = path.join(projectRoot, '.nut', 'symlinks.json');
|
|
14
14
|
this.state = {
|
|
15
15
|
version: '1.0.0',
|
|
16
16
|
symlinks: []
|
|
@@ -23,9 +23,9 @@ export class SymlinkManager {
|
|
|
23
23
|
try {
|
|
24
24
|
console.log(`[SymlinkManager] Initializing with project root: ${this.projectRoot}`);
|
|
25
25
|
console.log(`[SymlinkManager] Config path: ${this.configPath}`);
|
|
26
|
-
// Ensure .
|
|
27
|
-
const
|
|
28
|
-
await fs.mkdir(
|
|
26
|
+
// Ensure .nut directory exists
|
|
27
|
+
const nutDir = path.join(this.projectRoot, '.nut');
|
|
28
|
+
await fs.mkdir(nutDir, { recursive: true });
|
|
29
29
|
// Try to load existing configuration
|
|
30
30
|
try {
|
|
31
31
|
const configData = await fs.readFile(this.configPath, 'utf-8');
|
|
@@ -70,7 +70,7 @@ export class SymlinkManager {
|
|
|
70
70
|
name: 'Claude Rules',
|
|
71
71
|
description: 'Project-specific rules and guidelines for Claude AI assistant',
|
|
72
72
|
linkPath: 'CLAUDE.md',
|
|
73
|
-
targetPath: '.
|
|
73
|
+
targetPath: '.nut/rules/CLAUDE.md',
|
|
74
74
|
isActive,
|
|
75
75
|
createdAt: new Date().toISOString(),
|
|
76
76
|
updatedAt: new Date().toISOString()
|
|
@@ -121,7 +121,6 @@ export class SymlinkManager {
|
|
|
121
121
|
await fs.writeFile(tempPath, JSON.stringify(this.state, null, 2), 'utf-8');
|
|
122
122
|
// Atomic rename
|
|
123
123
|
await fs.rename(tempPath, this.configPath);
|
|
124
|
-
console.log(`[SymlinkManager] Saved state with ${this.state.symlinks.length} symlinks to ${this.configPath}`);
|
|
125
124
|
}
|
|
126
125
|
catch (error) {
|
|
127
126
|
console.error('[SymlinkManager] Failed to save state:', error);
|
|
@@ -407,25 +406,42 @@ export async function getSymlinkManager() {
|
|
|
407
406
|
// Determine project root more reliably
|
|
408
407
|
let projectRoot = process.env.GAIT_PROJECT_ROOT;
|
|
409
408
|
if (!projectRoot) {
|
|
410
|
-
//
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
409
|
+
// In development, we need to find the actual project root
|
|
410
|
+
// Starting from the current working directory
|
|
411
|
+
let currentPath = process.cwd();
|
|
412
|
+
// If we're in a Next.js app, we might be in packages/web
|
|
413
|
+
// We need to traverse up to find the actual project root
|
|
414
|
+
while (currentPath !== path.dirname(currentPath)) {
|
|
415
|
+
try {
|
|
416
|
+
// Check if we're at the project root by looking for distinctive files
|
|
417
|
+
const possibleRoot = currentPath;
|
|
418
|
+
const hasPackageJson = await fs.access(path.join(possibleRoot, 'package.json')).then(() => true).catch(() => false);
|
|
419
|
+
// Check for .nut directory
|
|
420
|
+
const hasNutDir = await fs.access(path.join(possibleRoot, '.nut')).then(() => true).catch(() => false);
|
|
421
|
+
const hasPnpmWorkspace = await fs.access(path.join(possibleRoot, 'pnpm-workspace.yaml')).then(() => true).catch(() => false);
|
|
422
|
+
// If this looks like the project root, use it
|
|
423
|
+
if (hasPackageJson && hasNutDir && hasPnpmWorkspace) {
|
|
424
|
+
projectRoot = possibleRoot;
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
// Otherwise, check parent directory
|
|
428
|
+
currentPath = path.dirname(currentPath);
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
currentPath = path.dirname(currentPath);
|
|
432
|
+
}
|
|
425
433
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
434
|
+
// Fallback: if we can't find the project root, use the current working directory
|
|
435
|
+
// But also try a known relative path from the web package
|
|
436
|
+
if (!projectRoot) {
|
|
437
|
+
const webPackagePath = process.cwd();
|
|
438
|
+
if (webPackagePath.includes('packages/web')) {
|
|
439
|
+
// We're in the web package, go up to the root
|
|
440
|
+
projectRoot = path.resolve(webPackagePath, '../..');
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
projectRoot = process.cwd();
|
|
444
|
+
}
|
|
429
445
|
}
|
|
430
446
|
}
|
|
431
447
|
console.log(`[SymlinkManager] Using project root: ${projectRoot}`);
|
|
@@ -3,24 +3,24 @@
|
|
|
3
3
|
export function generateWelcomeMessage(context) {
|
|
4
4
|
return `
|
|
5
5
|
\x1b[32m╭─────────────────────────────────────────────────────────────╮\x1b[0m
|
|
6
|
-
\x1b[32m│
|
|
6
|
+
\x1b[32m│ Coconut Terminal Session │\x1b[0m
|
|
7
7
|
\x1b[32m╰─────────────────────────────────────────────────────────────╯\x1b[0m
|
|
8
8
|
|
|
9
9
|
\x1b[36mProposal:\x1b[0m ${context.proposalId}
|
|
10
10
|
\x1b[36mProject Root:\x1b[0m ${context.projectRoot}
|
|
11
11
|
|
|
12
12
|
\x1b[33mAvailable Environment Variables:\x1b[0m
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
COCONUT_PROPOSAL_ID="${context.proposalId}"
|
|
14
|
+
COCONUT_CONTEXT_PATH="${context.contextPath}"
|
|
15
|
+
COCONUT_PROPOSAL_PATH="${context.proposalPath}"
|
|
16
16
|
|
|
17
17
|
\x1b[33mQuick Commands:\x1b[0m
|
|
18
|
-
\x1b[
|
|
19
|
-
\x1b[
|
|
20
|
-
\x1b[
|
|
21
|
-
\x1b[
|
|
18
|
+
\x1b[32mcoconut-help\x1b[0m - Show this help message
|
|
19
|
+
\x1b[32mcoconut-proposal\x1b[0m - View current proposal
|
|
20
|
+
\x1b[32mcoconut-files\x1b[0m - List context files
|
|
21
|
+
\x1b[32mcoconut-claude\x1b[0m - Setup Claude Code CLI
|
|
22
22
|
|
|
23
|
-
\x1b[90mTip: Use '
|
|
23
|
+
\x1b[90mTip: Use 'coconut-claude' to configure Claude Code for this proposal\x1b[0m
|
|
24
24
|
|
|
25
25
|
`;
|
|
26
26
|
}
|
|
@@ -36,7 +36,7 @@ export function generateClaudeSetupCommands(context) {
|
|
|
36
36
|
'# 2. Create a Claude Code project configuration',
|
|
37
37
|
'cat > .claude-project.json << EOF',
|
|
38
38
|
'{',
|
|
39
|
-
` "name": "
|
|
39
|
+
` "name": "Coconut Proposal ${context.proposalId}",`,
|
|
40
40
|
` "description": "Working on proposal ${context.proposalId}",`,
|
|
41
41
|
' "context": [',
|
|
42
42
|
` "${context.proposalPath}",`,
|
|
@@ -85,38 +85,38 @@ export function generateBashAliases(_context) {
|
|
|
85
85
|
}
|
|
86
86
|
export function createInitScript(context) {
|
|
87
87
|
return `#!/bin/bash
|
|
88
|
-
#
|
|
88
|
+
# Coconut Terminal Initialization Script
|
|
89
89
|
# This script is automatically sourced when a terminal session starts
|
|
90
90
|
|
|
91
91
|
# Function to show context help
|
|
92
|
-
|
|
93
|
-
cat << '
|
|
92
|
+
coconut-context-help() {
|
|
93
|
+
cat << 'COCONUT_HELP_EOF'
|
|
94
94
|
${generateWelcomeMessage(context)}
|
|
95
|
-
|
|
95
|
+
COCONUT_HELP_EOF
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
# Function to show proposal content
|
|
99
|
-
|
|
100
|
-
if [ -f "$
|
|
101
|
-
echo "\\x1b[36m=== Proposal: $
|
|
102
|
-
cat "$
|
|
99
|
+
coconut-proposal() {
|
|
100
|
+
if [ -f "$COCONUT_PROPOSAL_PATH" ]; then
|
|
101
|
+
echo "\\x1b[36m=== Proposal: $COCONUT_PROPOSAL_ID ===\\x1b[0m"
|
|
102
|
+
cat "$COCONUT_PROPOSAL_PATH"
|
|
103
103
|
else
|
|
104
|
-
echo "\\x1b[31mProposal file not found: $
|
|
104
|
+
echo "\\x1b[31mProposal file not found: $COCONUT_PROPOSAL_PATH\\x1b[0m"
|
|
105
105
|
fi
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
# Function to list context files
|
|
109
|
-
|
|
109
|
+
coconut-files() {
|
|
110
110
|
echo "\\x1b[36m=== Context Files ===\\x1b[0m"
|
|
111
|
-
if [ -d "$
|
|
112
|
-
find "$
|
|
111
|
+
if [ -d "$COCONUT_CONTEXT_PATH" ]; then
|
|
112
|
+
find "$COCONUT_CONTEXT_PATH" -name "*.md" -type f | sort
|
|
113
113
|
else
|
|
114
|
-
echo "\\x1b[31mContext directory not found: $
|
|
114
|
+
echo "\\x1b[31mContext directory not found: $COCONUT_CONTEXT_PATH\\x1b[0m"
|
|
115
115
|
fi
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
# Function to setup Claude Code
|
|
119
|
-
|
|
119
|
+
coconut-claude() {
|
|
120
120
|
echo "\\x1b[36m=== Claude Code Setup ===\\x1b[0m"
|
|
121
121
|
|
|
122
122
|
# Check if Claude Code is available
|
|
@@ -129,7 +129,7 @@ gait-claude() {
|
|
|
129
129
|
# Create project configuration
|
|
130
130
|
cat > .claude-project.json << 'EOF'
|
|
131
131
|
{
|
|
132
|
-
"name": "
|
|
132
|
+
"name": "Coconut Proposal ${context.proposalId}",
|
|
133
133
|
"description": "Working on proposal ${context.proposalId}",
|
|
134
134
|
"context": [
|
|
135
135
|
"${context.proposalPath}",
|
|
@@ -149,13 +149,13 @@ EOF
|
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
# Create helpful alias for help command
|
|
152
|
-
alias
|
|
152
|
+
alias coconut-help='coconut-context-help'
|
|
153
153
|
|
|
154
154
|
# Clear screen first to avoid any leftover content
|
|
155
155
|
clear
|
|
156
156
|
|
|
157
157
|
# Show welcome message
|
|
158
|
-
|
|
158
|
+
coconut-context-help
|
|
159
159
|
|
|
160
160
|
# Set a custom prompt to show the proposal ID
|
|
161
161
|
# This will be replaced with shell-specific syntax in prepareShellInit
|
|
@@ -139,15 +139,15 @@ export function prepareShellInit(shellPath, initScript) {
|
|
|
139
139
|
// Also fix the prompt to use zsh syntax
|
|
140
140
|
const zshScript = initScript
|
|
141
141
|
// Replace bash-style prompt with zsh-style prompt
|
|
142
|
-
.replace(/export PS1=.*$/m, 'export PROMPT="%F{cyan}[${
|
|
142
|
+
.replace(/export PS1=.*$/m, 'export PROMPT="%F{cyan}[${COCONUT_PROPOSAL_ID}]%f %~ %# "');
|
|
143
143
|
return {
|
|
144
144
|
script: `#!/bin/zsh
|
|
145
|
-
#
|
|
145
|
+
# Coconut Zsh Initialization
|
|
146
146
|
|
|
147
147
|
# Source system zshrc if it exists (suppress any output)
|
|
148
148
|
[ -f ~/.zshrc ] && source ~/.zshrc > /dev/null 2>&1 || true
|
|
149
149
|
|
|
150
|
-
# Custom
|
|
150
|
+
# Custom Coconut initialization
|
|
151
151
|
${zshScript}`,
|
|
152
152
|
needsEnvVar: { name: 'ZDOTDIR', value: '' } // Will be set to temp dir
|
|
153
153
|
};
|
|
@@ -157,19 +157,19 @@ ${zshScript}`,
|
|
|
157
157
|
.replace(/export\\s+(\\w+)=(.*)/g, 'set -gx $1 $2')
|
|
158
158
|
.replace(/alias\\s+(\\w+)=(.*)/g, 'alias $1 $2')
|
|
159
159
|
// Fish prompt syntax
|
|
160
|
-
.replace(/export PS1=.*$/m, 'function fish_prompt\n echo -n (set_color cyan)"[$
|
|
160
|
+
.replace(/export PS1=.*$/m, 'function fish_prompt\n echo -n (set_color cyan)"[$COCONUT_PROPOSAL_ID]"(set_color normal)" "(prompt_pwd)" > "\nend');
|
|
161
161
|
return {
|
|
162
162
|
script: fishScript
|
|
163
163
|
};
|
|
164
164
|
case 'bash':
|
|
165
165
|
default:
|
|
166
166
|
// Bash uses the script with proper PS1 escaping
|
|
167
|
-
const bashScript = initScript.replace(/export PS1="\[(.*)\] \\w \$ "/, 'export PS1="\\[\\033[36m\\][$
|
|
167
|
+
const bashScript = initScript.replace(/export PS1="\[(.*)\] \\w \$ "/, 'export PS1="\\[\\033[36m\\][$COCONUT_PROPOSAL_ID]\\[\\033[0m\\] \\w \\$ "');
|
|
168
168
|
return {
|
|
169
169
|
script: `# Source system bashrc if it exists
|
|
170
170
|
[ -f ~/.bashrc ] && source ~/.bashrc
|
|
171
171
|
|
|
172
|
-
# Custom
|
|
172
|
+
# Custom Coconut initialization
|
|
173
173
|
${bashScript}`
|
|
174
174
|
};
|
|
175
175
|
}
|
|
@@ -9,10 +9,12 @@ export interface TerminalSession {
|
|
|
9
9
|
lastActivity: Date;
|
|
10
10
|
enableLogging?: boolean;
|
|
11
11
|
logFilePath?: string;
|
|
12
|
+
backlog: string;
|
|
12
13
|
}
|
|
13
14
|
export declare class TerminalManager {
|
|
14
15
|
private sessions;
|
|
15
16
|
private cleanupInterval;
|
|
17
|
+
private static readonly MAX_BACKLOG_BYTES;
|
|
16
18
|
constructor();
|
|
17
19
|
createSession(proposalId: string, enableLogging?: boolean, shellPreference?: ShellType, startupCommand?: string): Promise<TerminalSession>;
|
|
18
20
|
getSession(sessionId: string): TerminalSession | undefined;
|
|
@@ -7,6 +7,7 @@ import { getShellPath, getShellArgs, prepareShellInit } from './shell-utils.js';
|
|
|
7
7
|
export class TerminalManager {
|
|
8
8
|
sessions = new Map();
|
|
9
9
|
cleanupInterval;
|
|
10
|
+
static MAX_BACKLOG_BYTES = 200_000; // ~200KB of recent output
|
|
10
11
|
constructor() {
|
|
11
12
|
// Clean up inactive sessions every 5 minutes
|
|
12
13
|
this.cleanupInterval = setInterval(() => {
|
|
@@ -18,7 +19,7 @@ export class TerminalManager {
|
|
|
18
19
|
// Get the project root directory
|
|
19
20
|
let projectRoot;
|
|
20
21
|
if (process.env.NODE_ENV === 'development' && process.env.GAIT_DEV_ROOT) {
|
|
21
|
-
// GAIT_DEV_ROOT points to the .
|
|
22
|
+
// GAIT_DEV_ROOT points to the .nut directory in development; project root is its parent
|
|
22
23
|
projectRoot = path.resolve(process.env.GAIT_DEV_ROOT, '..');
|
|
23
24
|
}
|
|
24
25
|
else if (process.env.GAIT_DATA_PATH) {
|
|
@@ -30,8 +31,8 @@ export class TerminalManager {
|
|
|
30
31
|
// Prepare context information
|
|
31
32
|
const contextInfo = {
|
|
32
33
|
proposalId,
|
|
33
|
-
proposalPath: path.join(projectRoot, '.
|
|
34
|
-
contextPath: path.join(projectRoot, '.
|
|
34
|
+
proposalPath: path.join(projectRoot, '.nut', 'proposals', `${proposalId}.md`),
|
|
35
|
+
contextPath: path.join(projectRoot, '.nut', 'context'),
|
|
35
36
|
projectRoot,
|
|
36
37
|
};
|
|
37
38
|
// Get the shell path based on user preference
|
|
@@ -45,13 +46,13 @@ export class TerminalManager {
|
|
|
45
46
|
let tempDirForZsh;
|
|
46
47
|
if (shellName === 'zsh' && needsEnvVar) {
|
|
47
48
|
// For zsh, create a temp directory with .zshrc
|
|
48
|
-
tempDirForZsh = path.join(projectRoot, '.
|
|
49
|
+
tempDirForZsh = path.join(projectRoot, '.nut', 'tmp', `zsh-${sessionId}`);
|
|
49
50
|
fs.mkdirSync(tempDirForZsh, { recursive: true });
|
|
50
51
|
initScriptPath = path.join(tempDirForZsh, '.zshrc');
|
|
51
52
|
}
|
|
52
53
|
else {
|
|
53
54
|
// For other shells, use regular init script
|
|
54
|
-
initScriptPath = path.join(projectRoot, '.
|
|
55
|
+
initScriptPath = path.join(projectRoot, '.nut', 'tmp', `init-${sessionId}.sh`);
|
|
55
56
|
const tmpDir = path.dirname(initScriptPath);
|
|
56
57
|
fs.mkdirSync(tmpDir, { recursive: true });
|
|
57
58
|
}
|
|
@@ -65,7 +66,7 @@ export class TerminalManager {
|
|
|
65
66
|
// Set up logging if enabled
|
|
66
67
|
let logFilePath;
|
|
67
68
|
if (enableLogging) {
|
|
68
|
-
const sessionsDir = path.join(projectRoot, '.
|
|
69
|
+
const sessionsDir = path.join(projectRoot, '.nut', 'sessions');
|
|
69
70
|
try {
|
|
70
71
|
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
71
72
|
logFilePath = path.join(sessionsDir, `${sessionId}.log`);
|
|
@@ -83,9 +84,9 @@ export class TerminalManager {
|
|
|
83
84
|
// Prepare environment variables
|
|
84
85
|
const env = {
|
|
85
86
|
...process.env,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
COCONUT_PROPOSAL_ID: proposalId,
|
|
88
|
+
COCONUT_CONTEXT_PATH: path.join(projectRoot, '.nut', 'context'),
|
|
89
|
+
COCONUT_PROPOSAL_PATH: path.join(projectRoot, '.nut', 'proposals', `${proposalId}.md`),
|
|
89
90
|
TERM: 'xterm-256color',
|
|
90
91
|
COLORTERM: 'truecolor',
|
|
91
92
|
};
|
|
@@ -112,6 +113,7 @@ export class TerminalManager {
|
|
|
112
113
|
lastActivity: new Date(),
|
|
113
114
|
enableLogging,
|
|
114
115
|
logFilePath,
|
|
116
|
+
backlog: '',
|
|
115
117
|
};
|
|
116
118
|
this.sessions.set(sessionId, session);
|
|
117
119
|
// Set up PTY event handlers
|
|
@@ -128,6 +130,17 @@ export class TerminalManager {
|
|
|
128
130
|
console.error('Error writing to session log:', error);
|
|
129
131
|
}
|
|
130
132
|
}
|
|
133
|
+
// Maintain in-memory backlog (trim to last MAX_BACKLOG_BYTES)
|
|
134
|
+
try {
|
|
135
|
+
// Append and trim to max size
|
|
136
|
+
session.backlog = (session.backlog + data);
|
|
137
|
+
if (session.backlog.length > TerminalManager.MAX_BACKLOG_BYTES) {
|
|
138
|
+
session.backlog = session.backlog.slice(-TerminalManager.MAX_BACKLOG_BYTES);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
// Best-effort; do not crash on backlog errors
|
|
143
|
+
}
|
|
131
144
|
if (session.websocket && session.websocket.readyState === WebSocket.OPEN) {
|
|
132
145
|
session.websocket.send(JSON.stringify({
|
|
133
146
|
type: 'data',
|
|
@@ -188,6 +201,15 @@ export class TerminalManager {
|
|
|
188
201
|
}
|
|
189
202
|
session.websocket = ws;
|
|
190
203
|
session.lastActivity = new Date();
|
|
204
|
+
// On attach, send a snapshot of recent backlog so refreshed clients render prior output
|
|
205
|
+
try {
|
|
206
|
+
if (session.backlog && session.backlog.length > 0 && ws.readyState === WebSocket.OPEN) {
|
|
207
|
+
ws.send(JSON.stringify({ type: 'snapshot', data: session.backlog }));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
// Non-fatal; continue with live streaming
|
|
212
|
+
}
|
|
191
213
|
// Set up WebSocket event handlers
|
|
192
214
|
ws.on('message', (message) => {
|
|
193
215
|
try {
|
|
@@ -6,16 +6,16 @@ const app = new Hono();
|
|
|
6
6
|
function getAgentsPath() {
|
|
7
7
|
let basePath;
|
|
8
8
|
if (process.env.NODE_ENV === 'development' && process.env.GAIT_DEV_ROOT) {
|
|
9
|
-
// Dev mode: use project root .
|
|
9
|
+
// Dev mode: use project root .nut directory
|
|
10
10
|
basePath = process.env.GAIT_DEV_ROOT;
|
|
11
11
|
}
|
|
12
12
|
else if (process.env.GAIT_DATA_PATH) {
|
|
13
13
|
// Production mode: use GAIT_DATA_PATH (set by CLI)
|
|
14
|
-
basePath = path.resolve(process.env.GAIT_DATA_PATH, '.
|
|
14
|
+
basePath = path.resolve(process.env.GAIT_DATA_PATH, '.nut');
|
|
15
15
|
}
|
|
16
16
|
else {
|
|
17
|
-
// Fallback: use current directory .
|
|
18
|
-
basePath = path.resolve(process.cwd(), '.
|
|
17
|
+
// Fallback: use current directory .nut
|
|
18
|
+
basePath = path.resolve(process.cwd(), '.nut');
|
|
19
19
|
}
|
|
20
20
|
return path.join(basePath, 'agents');
|
|
21
21
|
}
|
|
@@ -6,16 +6,16 @@ const app = new Hono();
|
|
|
6
6
|
function getAgentsPath() {
|
|
7
7
|
let basePath;
|
|
8
8
|
if (process.env.NODE_ENV === 'development' && process.env.GAIT_DEV_ROOT) {
|
|
9
|
-
// Dev mode: use project root .
|
|
9
|
+
// Dev mode: use project root .nut directory
|
|
10
10
|
basePath = process.env.GAIT_DEV_ROOT;
|
|
11
11
|
}
|
|
12
12
|
else if (process.env.GAIT_DATA_PATH) {
|
|
13
13
|
// Production mode: use GAIT_DATA_PATH (set by CLI)
|
|
14
|
-
basePath = path.resolve(process.env.GAIT_DATA_PATH, '.
|
|
14
|
+
basePath = path.resolve(process.env.GAIT_DATA_PATH, '.nut');
|
|
15
15
|
}
|
|
16
16
|
else {
|
|
17
|
-
// Fallback: use current directory .
|
|
18
|
-
basePath = path.resolve(process.cwd(), '.
|
|
17
|
+
// Fallback: use current directory .nut
|
|
18
|
+
basePath = path.resolve(process.cwd(), '.nut');
|
|
19
19
|
}
|
|
20
20
|
return path.join(basePath, 'agents');
|
|
21
21
|
}
|
|
@@ -35,15 +35,12 @@ export async function POST(c) {
|
|
|
35
35
|
if (!userMessage) {
|
|
36
36
|
return c.json({ error: "Message is required" }, 400);
|
|
37
37
|
}
|
|
38
|
-
//
|
|
39
|
-
let openRouterKey = process.env.OPENROUTER_API_KEY;
|
|
40
|
-
if (!openRouterKey) {
|
|
41
|
-
openRouterKey = getGlobalApiKey('openrouter');
|
|
42
|
-
}
|
|
38
|
+
// Prefer global config key (set via CLI/UI), fallback to env var
|
|
39
|
+
let openRouterKey = getGlobalApiKey('openrouter') || process.env.OPENROUTER_API_KEY;
|
|
43
40
|
if (!openRouterKey) {
|
|
44
41
|
return c.json({
|
|
45
42
|
error: "OpenRouter API key not configured",
|
|
46
|
-
hint: "Set
|
|
43
|
+
hint: "Set via 'coconut config set-key -p openrouter -k <KEY>' (or 'nut config set-key -p openrouter -k <KEY>'), or set OPENROUTER_API_KEY env var"
|
|
47
44
|
}, 500);
|
|
48
45
|
}
|
|
49
46
|
const baseSystem = systemOverride || getSystemPrompt(context, contextContent);
|
|
@@ -3,16 +3,16 @@ import { join, resolve } from "path";
|
|
|
3
3
|
function getChatsPath() {
|
|
4
4
|
let basePath;
|
|
5
5
|
if (process.env.NODE_ENV === 'development' && process.env.GAIT_DEV_ROOT) {
|
|
6
|
-
// Dev mode: use project root .
|
|
6
|
+
// Dev mode: use project root .nut directory
|
|
7
7
|
basePath = process.env.GAIT_DEV_ROOT;
|
|
8
8
|
}
|
|
9
9
|
else if (process.env.GAIT_DATA_PATH) {
|
|
10
10
|
// Production mode: use GAIT_DATA_PATH (set by CLI)
|
|
11
|
-
basePath = resolve(process.env.GAIT_DATA_PATH, '.
|
|
11
|
+
basePath = resolve(process.env.GAIT_DATA_PATH, '.nut');
|
|
12
12
|
}
|
|
13
13
|
else {
|
|
14
|
-
// Fallback: use current directory .
|
|
15
|
-
basePath = resolve(process.cwd(), '.
|
|
14
|
+
// Fallback: use current directory .nut
|
|
15
|
+
basePath = resolve(process.cwd(), '.nut');
|
|
16
16
|
}
|
|
17
17
|
return join(basePath, 'chats');
|
|
18
18
|
}
|