preclaim 0.1.0

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 (98) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/dist/commands/check.d.ts +2 -0
  3. package/dist/commands/check.d.ts.map +1 -0
  4. package/dist/commands/check.js +25 -0
  5. package/dist/commands/check.js.map +1 -0
  6. package/dist/commands/config.d.ts +5 -0
  7. package/dist/commands/config.d.ts.map +1 -0
  8. package/dist/commands/config.js +37 -0
  9. package/dist/commands/config.js.map +1 -0
  10. package/dist/commands/init.d.ts +5 -0
  11. package/dist/commands/init.d.ts.map +1 -0
  12. package/dist/commands/init.js +95 -0
  13. package/dist/commands/init.js.map +1 -0
  14. package/dist/commands/install-hooks.d.ts +2 -0
  15. package/dist/commands/install-hooks.d.ts.map +1 -0
  16. package/dist/commands/install-hooks.js +58 -0
  17. package/dist/commands/install-hooks.js.map +1 -0
  18. package/dist/commands/lock.d.ts +5 -0
  19. package/dist/commands/lock.d.ts.map +1 -0
  20. package/dist/commands/lock.js +27 -0
  21. package/dist/commands/lock.js.map +1 -0
  22. package/dist/commands/login.d.ts +2 -0
  23. package/dist/commands/login.d.ts.map +1 -0
  24. package/dist/commands/login.js +106 -0
  25. package/dist/commands/login.js.map +1 -0
  26. package/dist/commands/status.d.ts +2 -0
  27. package/dist/commands/status.d.ts.map +1 -0
  28. package/dist/commands/status.js +12 -0
  29. package/dist/commands/status.js.map +1 -0
  30. package/dist/commands/unlock.d.ts +5 -0
  31. package/dist/commands/unlock.d.ts.map +1 -0
  32. package/dist/commands/unlock.js +20 -0
  33. package/dist/commands/unlock.js.map +1 -0
  34. package/dist/commands/whoami.d.ts +2 -0
  35. package/dist/commands/whoami.d.ts.map +1 -0
  36. package/dist/commands/whoami.js +13 -0
  37. package/dist/commands/whoami.js.map +1 -0
  38. package/dist/hooks/heartbeat-daemon.d.ts +3 -0
  39. package/dist/hooks/heartbeat-daemon.d.ts.map +1 -0
  40. package/dist/hooks/heartbeat-daemon.js +41 -0
  41. package/dist/hooks/heartbeat-daemon.js.map +1 -0
  42. package/dist/hooks/post-tool-use.d.ts +3 -0
  43. package/dist/hooks/post-tool-use.d.ts.map +1 -0
  44. package/dist/hooks/post-tool-use.js +41 -0
  45. package/dist/hooks/post-tool-use.js.map +1 -0
  46. package/dist/hooks/pre-tool-use.d.ts +3 -0
  47. package/dist/hooks/pre-tool-use.d.ts.map +1 -0
  48. package/dist/hooks/pre-tool-use.js +101 -0
  49. package/dist/hooks/pre-tool-use.js.map +1 -0
  50. package/dist/hooks/session-start.d.ts +3 -0
  51. package/dist/hooks/session-start.d.ts.map +1 -0
  52. package/dist/hooks/session-start.js +77 -0
  53. package/dist/hooks/session-start.js.map +1 -0
  54. package/dist/hooks/stop.d.ts +3 -0
  55. package/dist/hooks/stop.d.ts.map +1 -0
  56. package/dist/hooks/stop.js +40 -0
  57. package/dist/hooks/stop.js.map +1 -0
  58. package/dist/index.d.ts +3 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +62 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/lib/auth.d.ts +3 -0
  63. package/dist/lib/auth.d.ts.map +1 -0
  64. package/dist/lib/auth.js +15 -0
  65. package/dist/lib/auth.js.map +1 -0
  66. package/dist/lib/client-factory.d.ts +8 -0
  67. package/dist/lib/client-factory.d.ts.map +1 -0
  68. package/dist/lib/client-factory.js +17 -0
  69. package/dist/lib/client-factory.js.map +1 -0
  70. package/dist/lib/hook-io.d.ts +13 -0
  71. package/dist/lib/hook-io.d.ts.map +1 -0
  72. package/dist/lib/hook-io.js +24 -0
  73. package/dist/lib/hook-io.js.map +1 -0
  74. package/dist/lib/output.d.ts +4 -0
  75. package/dist/lib/output.d.ts.map +1 -0
  76. package/dist/lib/output.js +17 -0
  77. package/dist/lib/output.js.map +1 -0
  78. package/package.json +24 -0
  79. package/src/commands/check.ts +28 -0
  80. package/src/commands/config.ts +43 -0
  81. package/src/commands/init.ts +109 -0
  82. package/src/commands/install-hooks.ts +72 -0
  83. package/src/commands/lock.ts +30 -0
  84. package/src/commands/login.ts +120 -0
  85. package/src/commands/status.ts +15 -0
  86. package/src/commands/unlock.ts +25 -0
  87. package/src/commands/whoami.ts +15 -0
  88. package/src/hooks/heartbeat-daemon.ts +49 -0
  89. package/src/hooks/post-tool-use.ts +44 -0
  90. package/src/hooks/pre-tool-use.ts +110 -0
  91. package/src/hooks/session-start.ts +87 -0
  92. package/src/hooks/stop.ts +43 -0
  93. package/src/index.ts +74 -0
  94. package/src/lib/auth.ts +17 -0
  95. package/src/lib/client-factory.ts +26 -0
  96. package/src/lib/hook-io.ts +37 -0
  97. package/src/lib/output.ts +20 -0
  98. package/tsconfig.json +8 -0
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ // Stop hook — cleanup
3
+ // Releases all locks and stops heartbeat daemon
4
+
5
+ import { readFile, unlink } from 'node:fs/promises';
6
+ import { join } from 'node:path';
7
+ import { PreclaimClient, findConfig, loadCredentials } from '@preclaim/core';
8
+ import { readHookInput } from '../lib/hook-io.js';
9
+
10
+ async function main() {
11
+ try {
12
+ const input = await readHookInput();
13
+
14
+ const found = await findConfig();
15
+ if (!found) return;
16
+
17
+ const creds = await loadCredentials();
18
+ if (!creds) return;
19
+
20
+ const client = new PreclaimClient({
21
+ baseUrl: found.config.backend,
22
+ accessToken: creds.accessToken,
23
+ timeoutMs: 3000,
24
+ });
25
+
26
+ // End session (releases all locks)
27
+ await client.endSession(input.session_id);
28
+
29
+ // Kill heartbeat daemon if running
30
+ const pidFile = join(process.cwd(), '.preclaim.pid');
31
+ try {
32
+ const pid = parseInt(await readFile(pidFile, 'utf-8'), 10);
33
+ process.kill(pid, 'SIGTERM');
34
+ await unlink(pidFile);
35
+ } catch {
36
+ // No daemon running or already stopped
37
+ }
38
+ } catch {
39
+ // Silent fail — cleanup is best-effort
40
+ }
41
+ }
42
+
43
+ main();
package/src/index.ts ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { initCommand } from './commands/init.js';
5
+ import { loginCommand } from './commands/login.js';
6
+ import { lockCommand } from './commands/lock.js';
7
+ import { unlockCommand } from './commands/unlock.js';
8
+ import { statusCommand } from './commands/status.js';
9
+ import { checkCommand } from './commands/check.js';
10
+ import { whoamiCommand } from './commands/whoami.js';
11
+ import { configCommand } from './commands/config.js';
12
+ import { installHooksCommand } from './commands/install-hooks.js';
13
+
14
+ const program = new Command();
15
+
16
+ program
17
+ .name('preclaim')
18
+ .description('AI File Coordination Layer — predictive file locking for AI coding agents')
19
+ .version('0.1.0');
20
+
21
+ program
22
+ .command('init')
23
+ .description('Initialize Preclaim in the current project')
24
+ .option('--backend <url>', 'Backend URL', 'https://preclaim.vercel.app')
25
+ .option('--project-id <id>', 'Project ID')
26
+ .action(initCommand);
27
+
28
+ program
29
+ .command('login')
30
+ .description('Authenticate with Preclaim')
31
+ .action(loginCommand);
32
+
33
+ program
34
+ .command('lock <file>')
35
+ .description('Lock a file')
36
+ .option('-s, --session <id>', 'Session ID')
37
+ .option('-t, --ttl <minutes>', 'Lock TTL in minutes')
38
+ .action(lockCommand);
39
+
40
+ program
41
+ .command('unlock [file]')
42
+ .description('Release a file lock')
43
+ .option('-s, --session <id>', 'Session ID')
44
+ .option('-a, --all', 'Release all locks for this session')
45
+ .action(unlockCommand);
46
+
47
+ program
48
+ .command('status')
49
+ .description('Show active locks for this project')
50
+ .action(statusCommand);
51
+
52
+ program
53
+ .command('check <files...>')
54
+ .description('Check lock status for files')
55
+ .action(checkCommand);
56
+
57
+ program
58
+ .command('whoami')
59
+ .description('Show current user info')
60
+ .action(whoamiCommand);
61
+
62
+ program
63
+ .command('config')
64
+ .description('View or modify project configuration')
65
+ .option('--get <key>', 'Get a config value')
66
+ .option('--set <key=value>', 'Set a config value')
67
+ .action(configCommand);
68
+
69
+ program
70
+ .command('install-hooks')
71
+ .description('Install Claude Code hooks in the current project')
72
+ .action(installHooksCommand);
73
+
74
+ program.parse();
@@ -0,0 +1,17 @@
1
+ import { loadCredentials, type PreclaimCredentials } from '@preclaim/core';
2
+
3
+ export async function requireAuth(): Promise<PreclaimCredentials> {
4
+ const creds = await loadCredentials();
5
+ if (!creds) {
6
+ console.error('Not logged in. Run `preclaim login` first.');
7
+ process.exit(1);
8
+ }
9
+
10
+ // Check expiry
11
+ if (new Date(creds.expiresAt) < new Date()) {
12
+ console.error('Session expired. Run `preclaim login` to re-authenticate.');
13
+ process.exit(1);
14
+ }
15
+
16
+ return creds;
17
+ }
@@ -0,0 +1,26 @@
1
+ import { PreclaimClient, findConfig, type PreclaimConfig, type PreclaimCredentials } from '@preclaim/core';
2
+ import { requireAuth } from './auth.js';
3
+
4
+ export interface ResolvedContext {
5
+ client: PreclaimClient;
6
+ config: PreclaimConfig;
7
+ credentials: PreclaimCredentials;
8
+ }
9
+
10
+ export async function resolveContext(): Promise<ResolvedContext> {
11
+ const credentials = await requireAuth();
12
+
13
+ const found = await findConfig();
14
+ if (!found) {
15
+ console.error('No .preclaim.json found. Run `preclaim init` in your project root.');
16
+ process.exit(1);
17
+ }
18
+
19
+ const client = new PreclaimClient({
20
+ baseUrl: found.config.backend,
21
+ accessToken: credentials.accessToken,
22
+ timeoutMs: 5000,
23
+ });
24
+
25
+ return { client, config: found.config, credentials };
26
+ }
@@ -0,0 +1,37 @@
1
+ // Hook I/O helpers for Claude Code hooks
2
+ // Reads hook input from stdin and writes hook output to stdout
3
+
4
+ export interface ClaudeHookInput {
5
+ session_id: string;
6
+ tool_name?: string;
7
+ tool_input?: Record<string, unknown>;
8
+ }
9
+
10
+ export interface ClaudeHookOutput {
11
+ permissionDecision?: 'allow' | 'deny';
12
+ reason?: string;
13
+ systemMessage?: string;
14
+ }
15
+
16
+ export async function readHookInput(): Promise<ClaudeHookInput> {
17
+ return new Promise((resolve, reject) => {
18
+ let data = '';
19
+ process.stdin.setEncoding('utf-8');
20
+ process.stdin.on('data', (chunk) => { data += chunk; });
21
+ process.stdin.on('end', () => {
22
+ try {
23
+ resolve(JSON.parse(data));
24
+ } catch {
25
+ reject(new Error('Failed to parse hook input'));
26
+ }
27
+ });
28
+ process.stdin.on('error', reject);
29
+
30
+ // Timeout after 3s
31
+ setTimeout(() => reject(new Error('Hook input timeout')), 3000);
32
+ });
33
+ }
34
+
35
+ export function writeHookOutput(output: ClaudeHookOutput): void {
36
+ process.stdout.write(JSON.stringify(output));
37
+ }
@@ -0,0 +1,20 @@
1
+ import type { Lock } from '@preclaim/core';
2
+
3
+ export function formatLock(lock: Lock): string {
4
+ const acquired = new Date(lock.acquired_at).toLocaleTimeString();
5
+ const expires = new Date(lock.expires_at).toLocaleTimeString();
6
+ return ` ${lock.file_path} (session: ${lock.session_id.slice(0, 8)}… acquired: ${acquired} expires: ${expires})`;
7
+ }
8
+
9
+ export function formatLockTable(locks: Lock[]): string {
10
+ if (locks.length === 0) {
11
+ return 'No active locks.';
12
+ }
13
+
14
+ const lines = ['Active locks:', ''];
15
+ for (const lock of locks) {
16
+ lines.push(formatLock(lock));
17
+ }
18
+ lines.push('', `Total: ${locks.length} lock(s)`);
19
+ return lines.join('\n');
20
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"]
8
+ }