@claudetools/tools 0.3.1 → 0.3.3

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 (2) hide show
  1. package/dist/setup.js +117 -43
  2. package/package.json +1 -1
package/dist/setup.js CHANGED
@@ -7,8 +7,9 @@ import chalk from 'chalk';
7
7
  import ora from 'ora';
8
8
  import { homedir, hostname, platform } from 'os';
9
9
  import { join, basename } from 'path';
10
- import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from 'fs';
10
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, realpathSync } from 'fs';
11
11
  import { randomUUID } from 'crypto';
12
+ import { execSync } from 'child_process';
12
13
  import { loadConfigFromFile, saveConfig, ensureConfigDir, getConfigPath, DEFAULT_CONFIG, } from './helpers/config-manager.js';
13
14
  import { GLOBAL_TEMPLATE, SECTION_START, SECTION_END, getProjectTemplate, } from './templates/claude-md.js';
14
15
  // -----------------------------------------------------------------------------
@@ -110,6 +111,29 @@ function initializeProjectsFile() {
110
111
  }, null, 2));
111
112
  }
112
113
  }
114
+ /**
115
+ * Detect git remote for a directory (secure implementation)
116
+ */
117
+ function detectGitRemote(localPath) {
118
+ try {
119
+ // Resolve symlinks and validate path to prevent path traversal attacks
120
+ const resolvedPath = realpathSync(localPath);
121
+ // Ensure path is absolute and doesn't contain path traversal
122
+ if (!resolvedPath.startsWith('/') || resolvedPath.includes('..')) {
123
+ return undefined;
124
+ }
125
+ const gitRemote = execSync('git config --get remote.origin.url', {
126
+ cwd: resolvedPath,
127
+ encoding: 'utf-8',
128
+ stdio: ['pipe', 'pipe', 'ignore'],
129
+ }).trim();
130
+ return gitRemote || undefined;
131
+ }
132
+ catch {
133
+ // Not a git repo or no remote configured
134
+ return undefined;
135
+ }
136
+ }
113
137
  // -----------------------------------------------------------------------------
114
138
  // Authentication
115
139
  // -----------------------------------------------------------------------------
@@ -480,9 +504,11 @@ if [ -f "$WATCHER_PID_FILE" ]; then
480
504
  fi
481
505
 
482
506
  # Start watcher in background if not running
507
+ # The watcher manages its own PID file, so don't write it here
483
508
  if [ ! -f "$WATCHER_PID_FILE" ] && command -v claudetools &> /dev/null; then
484
- nohup claudetools watch > /tmp/claudetools-watcher.log 2>&1 &
485
- echo $! > "$WATCHER_PID_FILE"
509
+ nohup claudetools watch >> /tmp/claudetools-watcher.log 2>&1 &
510
+ # Give the watcher a moment to start and write its PID file
511
+ sleep 1
486
512
  fi
487
513
 
488
514
  # --- Context Injection ---
@@ -1088,58 +1114,106 @@ export async function runInit() {
1088
1114
  error('ClaudeTools not configured. Run "claudetools --setup" first.');
1089
1115
  process.exit(1);
1090
1116
  }
1117
+ // Load system info
1118
+ const systemInfo = loadSystemInfo();
1119
+ if (!systemInfo?.system_id) {
1120
+ error('System not registered. Run "claudetools --setup" first.');
1121
+ process.exit(1);
1122
+ }
1123
+ // Detect git remote for this directory
1124
+ const gitRemote = detectGitRemote(cwd);
1091
1125
  // Try to register project with API
1092
1126
  const spinner = ora('Registering project...').start();
1093
1127
  let projectId;
1094
- try {
1095
- const response = await fetch(`${config.apiUrl || DEFAULT_CONFIG.apiUrl}/api/v1/projects/register`, {
1096
- method: 'POST',
1097
- headers: {
1098
- 'Content-Type': 'application/json',
1099
- 'Authorization': `Bearer ${config.apiKey}`,
1100
- },
1101
- body: JSON.stringify({
1102
- name: projectName,
1103
- local_path: cwd,
1104
- }),
1105
- });
1106
- if (response.ok) {
1107
- const data = await response.json();
1108
- projectId = data.project_id;
1109
- spinner.succeed(`Registered project: ${projectId}`);
1110
- // Update local projects.json
1111
- try {
1112
- const projectsData = existsSync(PROJECTS_FILE)
1113
- ? JSON.parse(readFileSync(PROJECTS_FILE, 'utf-8'))
1114
- : { bindings: [] };
1115
- // Check if already in bindings
1116
- const existingIdx = projectsData.bindings.findIndex((b) => b.local_path === cwd);
1117
- if (existingIdx >= 0) {
1118
- projectsData.bindings[existingIdx].project_id = projectId;
1128
+ // Use auto-bind endpoint if git remote exists
1129
+ if (gitRemote) {
1130
+ try {
1131
+ const response = await fetch(`${config.apiUrl || DEFAULT_CONFIG.apiUrl}/api/v1/systems/${systemInfo.system_id}/bindings/auto`, {
1132
+ method: 'POST',
1133
+ headers: {
1134
+ 'Content-Type': 'application/json',
1135
+ 'Authorization': `Bearer ${config.apiKey}`,
1136
+ },
1137
+ body: JSON.stringify({
1138
+ local_path: cwd,
1139
+ git_remote: gitRemote,
1140
+ project_name: projectName,
1141
+ }),
1142
+ });
1143
+ if (response.ok) {
1144
+ const data = await response.json();
1145
+ projectId = data.data?.project_id || `local_${projectName.toLowerCase().replace(/[^a-z0-9]/g, '_')}`;
1146
+ const registeredName = data.data?.project_name || projectName;
1147
+ spinner.succeed(`Registered project: ${registeredName} (${projectId})`);
1148
+ // Update local projects.json
1149
+ try {
1150
+ const projectsData = existsSync(PROJECTS_FILE)
1151
+ ? JSON.parse(readFileSync(PROJECTS_FILE, 'utf-8'))
1152
+ : { bindings: [], system_id: systemInfo.system_id };
1153
+ // Check if already in bindings
1154
+ const existingIdx = projectsData.bindings.findIndex((b) => b.local_path === cwd);
1155
+ if (existingIdx >= 0) {
1156
+ projectsData.bindings[existingIdx].project_id = projectId;
1157
+ projectsData.bindings[existingIdx].project_name = registeredName;
1158
+ projectsData.bindings[existingIdx].git_remote = gitRemote;
1159
+ }
1160
+ else {
1161
+ projectsData.bindings.push({
1162
+ project_id: projectId,
1163
+ local_path: cwd,
1164
+ project_name: registeredName,
1165
+ git_remote: gitRemote,
1166
+ system_id: systemInfo.system_id,
1167
+ cached_at: new Date().toISOString(),
1168
+ });
1169
+ }
1170
+ projectsData.last_sync = new Date().toISOString();
1171
+ writeFileSync(PROJECTS_FILE, JSON.stringify(projectsData, null, 2));
1119
1172
  }
1120
- else {
1121
- projectsData.bindings.push({
1122
- project_id: projectId,
1123
- local_path: cwd,
1124
- project_name: projectName,
1125
- cached_at: new Date().toISOString(),
1126
- });
1173
+ catch {
1174
+ // Non-fatal
1127
1175
  }
1128
- projectsData.last_sync = new Date().toISOString();
1129
- writeFileSync(PROJECTS_FILE, JSON.stringify(projectsData, null, 2));
1130
1176
  }
1131
- catch {
1132
- // Non-fatal
1177
+ else {
1178
+ const errorData = await response.json().catch(() => ({}));
1179
+ spinner.warn(`Could not register with API: ${errorData.error || response.status}`);
1180
+ projectId = `local_${projectName.toLowerCase().replace(/[^a-z0-9]/g, '_')}`;
1133
1181
  }
1134
1182
  }
1135
- else {
1136
- spinner.warn('Could not register with API, using local ID');
1183
+ catch (err) {
1184
+ spinner.warn('Could not reach API, using local ID');
1137
1185
  projectId = `local_${projectName.toLowerCase().replace(/[^a-z0-9]/g, '_')}`;
1138
1186
  }
1139
1187
  }
1140
- catch {
1141
- spinner.warn('Could not reach API, using local ID');
1188
+ else {
1189
+ // No git remote - use local ID
1190
+ spinner.info('No git remote detected, using local project ID');
1142
1191
  projectId = `local_${projectName.toLowerCase().replace(/[^a-z0-9]/g, '_')}`;
1192
+ // Still update local projects.json for session context
1193
+ try {
1194
+ const projectsData = existsSync(PROJECTS_FILE)
1195
+ ? JSON.parse(readFileSync(PROJECTS_FILE, 'utf-8'))
1196
+ : { bindings: [], system_id: systemInfo.system_id };
1197
+ const existingIdx = projectsData.bindings.findIndex((b) => b.local_path === cwd);
1198
+ if (existingIdx >= 0) {
1199
+ projectsData.bindings[existingIdx].project_id = projectId;
1200
+ projectsData.bindings[existingIdx].project_name = projectName;
1201
+ }
1202
+ else {
1203
+ projectsData.bindings.push({
1204
+ project_id: projectId,
1205
+ local_path: cwd,
1206
+ project_name: projectName,
1207
+ system_id: systemInfo.system_id,
1208
+ cached_at: new Date().toISOString(),
1209
+ });
1210
+ }
1211
+ projectsData.last_sync = new Date().toISOString();
1212
+ writeFileSync(PROJECTS_FILE, JSON.stringify(projectsData, null, 2));
1213
+ }
1214
+ catch {
1215
+ // Non-fatal
1216
+ }
1143
1217
  }
1144
1218
  // Create .claude directory if needed
1145
1219
  if (!existsSync(projectClaudeDir)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudetools/tools",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "Persistent AI memory, task management, and codebase intelligence for Claude Code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",