@claudetools/tools 0.3.1 → 0.3.2

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 +113 -41
  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
  // -----------------------------------------------------------------------------
@@ -1088,58 +1112,106 @@ export async function runInit() {
1088
1112
  error('ClaudeTools not configured. Run "claudetools --setup" first.');
1089
1113
  process.exit(1);
1090
1114
  }
1115
+ // Load system info
1116
+ const systemInfo = loadSystemInfo();
1117
+ if (!systemInfo?.system_id) {
1118
+ error('System not registered. Run "claudetools --setup" first.');
1119
+ process.exit(1);
1120
+ }
1121
+ // Detect git remote for this directory
1122
+ const gitRemote = detectGitRemote(cwd);
1091
1123
  // Try to register project with API
1092
1124
  const spinner = ora('Registering project...').start();
1093
1125
  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;
1126
+ // Use auto-bind endpoint if git remote exists
1127
+ if (gitRemote) {
1128
+ try {
1129
+ const response = await fetch(`${config.apiUrl || DEFAULT_CONFIG.apiUrl}/api/v1/systems/${systemInfo.system_id}/bindings/auto`, {
1130
+ method: 'POST',
1131
+ headers: {
1132
+ 'Content-Type': 'application/json',
1133
+ 'Authorization': `Bearer ${config.apiKey}`,
1134
+ },
1135
+ body: JSON.stringify({
1136
+ local_path: cwd,
1137
+ git_remote: gitRemote,
1138
+ project_name: projectName,
1139
+ }),
1140
+ });
1141
+ if (response.ok) {
1142
+ const data = await response.json();
1143
+ projectId = data.data?.project_id || `local_${projectName.toLowerCase().replace(/[^a-z0-9]/g, '_')}`;
1144
+ const registeredName = data.data?.project_name || projectName;
1145
+ spinner.succeed(`Registered project: ${registeredName} (${projectId})`);
1146
+ // Update local projects.json
1147
+ try {
1148
+ const projectsData = existsSync(PROJECTS_FILE)
1149
+ ? JSON.parse(readFileSync(PROJECTS_FILE, 'utf-8'))
1150
+ : { bindings: [], system_id: systemInfo.system_id };
1151
+ // Check if already in bindings
1152
+ const existingIdx = projectsData.bindings.findIndex((b) => b.local_path === cwd);
1153
+ if (existingIdx >= 0) {
1154
+ projectsData.bindings[existingIdx].project_id = projectId;
1155
+ projectsData.bindings[existingIdx].project_name = registeredName;
1156
+ projectsData.bindings[existingIdx].git_remote = gitRemote;
1157
+ }
1158
+ else {
1159
+ projectsData.bindings.push({
1160
+ project_id: projectId,
1161
+ local_path: cwd,
1162
+ project_name: registeredName,
1163
+ git_remote: gitRemote,
1164
+ system_id: systemInfo.system_id,
1165
+ cached_at: new Date().toISOString(),
1166
+ });
1167
+ }
1168
+ projectsData.last_sync = new Date().toISOString();
1169
+ writeFileSync(PROJECTS_FILE, JSON.stringify(projectsData, null, 2));
1119
1170
  }
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
- });
1171
+ catch {
1172
+ // Non-fatal
1127
1173
  }
1128
- projectsData.last_sync = new Date().toISOString();
1129
- writeFileSync(PROJECTS_FILE, JSON.stringify(projectsData, null, 2));
1130
1174
  }
1131
- catch {
1132
- // Non-fatal
1175
+ else {
1176
+ const errorData = await response.json().catch(() => ({}));
1177
+ spinner.warn(`Could not register with API: ${errorData.error || response.status}`);
1178
+ projectId = `local_${projectName.toLowerCase().replace(/[^a-z0-9]/g, '_')}`;
1133
1179
  }
1134
1180
  }
1135
- else {
1136
- spinner.warn('Could not register with API, using local ID');
1181
+ catch (err) {
1182
+ spinner.warn('Could not reach API, using local ID');
1137
1183
  projectId = `local_${projectName.toLowerCase().replace(/[^a-z0-9]/g, '_')}`;
1138
1184
  }
1139
1185
  }
1140
- catch {
1141
- spinner.warn('Could not reach API, using local ID');
1186
+ else {
1187
+ // No git remote - use local ID
1188
+ spinner.info('No git remote detected, using local project ID');
1142
1189
  projectId = `local_${projectName.toLowerCase().replace(/[^a-z0-9]/g, '_')}`;
1190
+ // Still update local projects.json for session context
1191
+ try {
1192
+ const projectsData = existsSync(PROJECTS_FILE)
1193
+ ? JSON.parse(readFileSync(PROJECTS_FILE, 'utf-8'))
1194
+ : { bindings: [], system_id: systemInfo.system_id };
1195
+ const existingIdx = projectsData.bindings.findIndex((b) => b.local_path === cwd);
1196
+ if (existingIdx >= 0) {
1197
+ projectsData.bindings[existingIdx].project_id = projectId;
1198
+ projectsData.bindings[existingIdx].project_name = projectName;
1199
+ }
1200
+ else {
1201
+ projectsData.bindings.push({
1202
+ project_id: projectId,
1203
+ local_path: cwd,
1204
+ project_name: projectName,
1205
+ system_id: systemInfo.system_id,
1206
+ cached_at: new Date().toISOString(),
1207
+ });
1208
+ }
1209
+ projectsData.last_sync = new Date().toISOString();
1210
+ writeFileSync(PROJECTS_FILE, JSON.stringify(projectsData, null, 2));
1211
+ }
1212
+ catch {
1213
+ // Non-fatal
1214
+ }
1143
1215
  }
1144
1216
  // Create .claude directory if needed
1145
1217
  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.2",
4
4
  "description": "Persistent AI memory, task management, and codebase intelligence for Claude Code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",