@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.
Files changed (38) hide show
  1. package/dist/lib/gait-path.d.ts +7 -5
  2. package/dist/lib/gait-path.js +23 -17
  3. package/dist/lib/project-paths.d.ts +2 -2
  4. package/dist/lib/project-paths.js +8 -5
  5. package/dist/lib/storage/file-storage.js +4 -4
  6. package/dist/lib/symlinks/symlink-manager.js +40 -24
  7. package/dist/lib/terminal/context-helper.js +27 -27
  8. package/dist/lib/terminal/shell-utils.js +6 -6
  9. package/dist/lib/terminal/terminal-manager.d.ts +2 -0
  10. package/dist/lib/terminal/terminal-manager.js +31 -9
  11. package/dist/routes/api/v1/agents/[id]/route.js +4 -4
  12. package/dist/routes/api/v1/agents/route.js +4 -4
  13. package/dist/routes/api/v1/ai/route.js +3 -6
  14. package/dist/routes/api/v1/chats/[id]/route.js +4 -4
  15. package/dist/routes/api/v1/chats/route.js +4 -4
  16. package/dist/routes/api/v1/config/index.js +2 -1
  17. package/dist/routes/api/v1/config/route.d.ts +13 -0
  18. package/dist/routes/api/v1/config/route.js +69 -0
  19. package/dist/routes/api/v1/context/knowledge/[filename]/route.js +4 -4
  20. package/dist/routes/api/v1/context/knowledge/route.js +4 -4
  21. package/dist/routes/api/v1/mcp/config/index.d.ts +1 -0
  22. package/dist/routes/api/v1/mcp/config/index.js +1 -0
  23. package/dist/routes/api/v1/mcp/config/route.d.ts +3 -0
  24. package/dist/routes/api/v1/mcp/config/route.js +59 -0
  25. package/dist/routes/api/v1/mcp/index.js +2 -2
  26. package/dist/routes/api/v1/proposals/[id]/route.d.ts +0 -2
  27. package/dist/routes/api/v1/proposals/route.d.ts +0 -2
  28. package/dist/routes/api/v1/resources/[id]/route.js +4 -4
  29. package/dist/routes/api/v1/resources/[id]/thumbnail/route.js +4 -4
  30. package/dist/routes/api/v1/resources/route.js +4 -4
  31. package/dist/routes/api/v1/symlinks/index.d.ts +1 -0
  32. package/dist/routes/api/v1/symlinks/index.js +1 -0
  33. package/dist/routes/api/v1/symlinks/route.d.ts +3 -0
  34. package/dist/routes/api/v1/symlinks/route.js +135 -0
  35. package/dist/server.js +2 -0
  36. package/package.json +5 -5
  37. package/static/assets/index-DnaNaR1E.js +714 -0
  38. package/static/index.html +1 -1
@@ -1,13 +1,15 @@
1
1
  /**
2
- * Find the .gait directory by traversing up from the current working directory
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 findGaitDirectory(startDir?: string): Promise<string | null>;
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 .gait directory
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 .gait config to read storage path settings
12
+ * Get the .nut config to read storage path settings
12
13
  */
13
- export declare function getGaitConfig(): Promise<any | null>;
14
+ export declare function getNutConfig(): Promise<any | null>;
15
+ export declare const getGaitConfig: typeof getNutConfig;
@@ -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 .gait directory by traversing up from the current working directory
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 findGaitDirectory(startDir) {
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 gaitPath = path.join(process.env.GAIT_DATA_PATH, '.gait');
12
+ const nutPath = path.join(process.env.GAIT_DATA_PATH, NUT_DIR_NAME);
11
13
  try {
12
- await fs.access(gaitPath);
13
- return gaitPath;
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 gaitPath = path.join(currentDir, '.gait');
24
+ const nutPath = path.join(currentDir, NUT_DIR_NAME);
23
25
  try {
24
- await fs.access(gaitPath);
25
- return gaitPath;
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 .gait directory
38
+ * Get the path to a context file, automatically finding the .nut directory
35
39
  */
36
40
  export async function getContextFilePath(fileName) {
37
- const gaitDir = await findGaitDirectory();
38
- if (!gaitDir)
41
+ const nutDir = await findNutDirectory();
42
+ if (!nutDir)
39
43
  return null;
40
- return path.join(gaitDir, 'context', fileName);
44
+ return path.join(nutDir, 'context', fileName);
41
45
  }
42
46
  /**
43
- * Get the .gait config to read storage path settings
47
+ * Get the .nut config to read storage path settings
44
48
  */
45
- export async function getGaitConfig() {
46
- const gaitDir = await findGaitDirectory();
47
- if (!gaitDir)
49
+ export async function getNutConfig() {
50
+ const nutDir = await findNutDirectory();
51
+ if (!nutDir)
48
52
  return null;
49
- const configPath = path.join(gaitDir, 'config.json');
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 GAIT operations.
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 .gait/rules
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 GAIT operations.
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
- const hasGaitDir = await fs.access(path.join(possibleRoot, '.gait')).then(() => true).catch(() => false);
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 && hasGaitDir && hasPnpmWorkspace) {
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 .gait/rules
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, '.gait', 'rules', 'CLAUDE.md');
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 .gait directory
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, '.gait');
17
+ this.basePath = path.join(process.env.GAIT_DATA_PATH, '.nut');
18
18
  }
19
19
  else {
20
- // Fallback: use current directory .gait
21
- this.basePath = '.gait';
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, '.gait', 'symlinks.json');
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 .gait directory exists
27
- const gaitDir = path.join(this.projectRoot, '.gait');
28
- await fs.mkdir(gaitDir, { recursive: true });
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: '.gait/rules/CLAUDE.md',
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
- // Try to find the project root by looking for distinctive files
411
- const cwd = process.cwd();
412
- // Check if we're in packages/web
413
- if (cwd.includes('packages/web')) {
414
- projectRoot = path.resolve(cwd, '../..');
415
- }
416
- else if (cwd.includes('packages')) {
417
- projectRoot = path.resolve(cwd, '..');
418
- }
419
- else {
420
- projectRoot = cwd;
421
- }
422
- // Verify this is the right directory by checking for .gait
423
- try {
424
- await fs.access(path.join(projectRoot, '.gait'));
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
- catch {
427
- console.warn(`Could not find .gait directory at ${projectRoot}, using cwd`);
428
- projectRoot = cwd;
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│ GAIT Terminal Session │\x1b[0m
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
- GAIT_PROPOSAL_ID="${context.proposalId}"
14
- GAIT_CONTEXT_PATH="${context.contextPath}"
15
- GAIT_PROPOSAL_PATH="${context.proposalPath}"
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[32mgait-help\x1b[0m - Show this help message
19
- \x1b[32mgait-proposal\x1b[0m - View current proposal
20
- \x1b[32mgait-files\x1b[0m - List context files
21
- \x1b[32mgait-claude\x1b[0m - Setup Claude Code CLI
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 'gait-claude' to configure Claude Code for this proposal\x1b[0m
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": "GAIT Proposal ${context.proposalId}",`,
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
- # GAIT Terminal Initialization Script
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
- gait-context-help() {
93
- cat << 'GAIT_HELP_EOF'
92
+ coconut-context-help() {
93
+ cat << 'COCONUT_HELP_EOF'
94
94
  ${generateWelcomeMessage(context)}
95
- GAIT_HELP_EOF
95
+ COCONUT_HELP_EOF
96
96
  }
97
97
 
98
98
  # Function to show proposal content
99
- gait-proposal() {
100
- if [ -f "$GAIT_PROPOSAL_PATH" ]; then
101
- echo "\\x1b[36m=== Proposal: $GAIT_PROPOSAL_ID ===\\x1b[0m"
102
- cat "$GAIT_PROPOSAL_PATH"
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: $GAIT_PROPOSAL_PATH\\x1b[0m"
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
- gait-files() {
109
+ coconut-files() {
110
110
  echo "\\x1b[36m=== Context Files ===\\x1b[0m"
111
- if [ -d "$GAIT_CONTEXT_PATH" ]; then
112
- find "$GAIT_CONTEXT_PATH" -name "*.md" -type f | sort
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: $GAIT_CONTEXT_PATH\\x1b[0m"
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
- gait-claude() {
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": "GAIT Proposal ${context.proposalId}",
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 gait-help='gait-context-help'
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
- gait-context-help
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}[${GAIT_PROPOSAL_ID}]%f %~ %# "');
142
+ .replace(/export PS1=.*$/m, 'export PROMPT="%F{cyan}[${COCONUT_PROPOSAL_ID}]%f %~ %# "');
143
143
  return {
144
144
  script: `#!/bin/zsh
145
- # GAIT Zsh Initialization
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 GAIT initialization
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)"[$GAIT_PROPOSAL_ID]"(set_color normal)" "(prompt_pwd)" > "\nend');
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\\][$GAIT_PROPOSAL_ID]\\[\\033[0m\\] \\w \\$ "');
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 GAIT initialization
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 .gait directory in development; project root is its parent
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, '.gait', 'proposals', `${proposalId}.md`),
34
- contextPath: path.join(projectRoot, '.gait', 'context'),
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, '.gait', 'tmp', `zsh-${sessionId}`);
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, '.gait', 'tmp', `init-${sessionId}.sh`);
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, '.gait', 'sessions');
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
- GAIT_PROPOSAL_ID: proposalId,
87
- GAIT_CONTEXT_PATH: path.join(projectRoot, '.gait', 'context'),
88
- GAIT_PROPOSAL_PATH: path.join(projectRoot, '.gait', 'proposals', `${proposalId}.md`),
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 .gait directory
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, '.gait');
14
+ basePath = path.resolve(process.env.GAIT_DATA_PATH, '.nut');
15
15
  }
16
16
  else {
17
- // Fallback: use current directory .gait
18
- basePath = path.resolve(process.cwd(), '.gait');
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 .gait directory
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, '.gait');
14
+ basePath = path.resolve(process.env.GAIT_DATA_PATH, '.nut');
15
15
  }
16
16
  else {
17
- // Fallback: use current directory .gait
18
- basePath = path.resolve(process.cwd(), '.gait');
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
- // Try environment variable first, then global config
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 OPENROUTER_API_KEY environment variable or run 'coconut config set-key -p openrouter' to configure globally"
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 .gait directory
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, '.gait');
11
+ basePath = resolve(process.env.GAIT_DATA_PATH, '.nut');
12
12
  }
13
13
  else {
14
- // Fallback: use current directory .gait
15
- basePath = resolve(process.cwd(), '.gait');
14
+ // Fallback: use current directory .nut
15
+ basePath = resolve(process.cwd(), '.nut');
16
16
  }
17
17
  return join(basePath, 'chats');
18
18
  }