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,41 @@
1
+ #!/usr/bin/env node
2
+ // PostToolUse hook — commit detection
3
+ // Detects git commit commands and releases all session locks
4
+ import { PreclaimClient, findConfig, loadCredentials } from '@preclaim/core';
5
+ import { readHookInput } from '../lib/hook-io.js';
6
+ async function main() {
7
+ try {
8
+ const input = await readHookInput();
9
+ // Only match Bash tool calls
10
+ if (input.tool_name !== 'Bash')
11
+ return;
12
+ const command = input.tool_input?.command;
13
+ if (!command)
14
+ return;
15
+ // Detect git commit (not amend, not just `git commit --help`)
16
+ const isCommit = /\bgit\s+commit\b/.test(command) && !/--help/.test(command);
17
+ if (!isCommit)
18
+ return;
19
+ const found = await findConfig();
20
+ if (!found)
21
+ return;
22
+ const creds = await loadCredentials();
23
+ if (!creds)
24
+ return;
25
+ const client = new PreclaimClient({
26
+ baseUrl: found.config.backend,
27
+ accessToken: creds.accessToken,
28
+ timeoutMs: 3000,
29
+ });
30
+ // Release all locks for this session
31
+ await client.releaseLocks({
32
+ project_id: found.config.projectId,
33
+ session_id: input.session_id,
34
+ });
35
+ }
36
+ catch {
37
+ // Silent fail — non-critical
38
+ }
39
+ }
40
+ main();
41
+ //# sourceMappingURL=post-tool-use.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-tool-use.js","sourceRoot":"","sources":["../../src/hooks/post-tool-use.ts"],"names":[],"mappings":";AACA,sCAAsC;AACtC,6DAA6D;AAE7D,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAC;QAEpC,6BAA6B;QAC7B,IAAI,KAAK,CAAC,SAAS,KAAK,MAAM;YAAE,OAAO;QAEvC,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,EAAE,OAA6B,CAAC;QAChE,IAAI,CAAC,OAAO;YAAE,OAAO;QAErB,8DAA8D;QAC9D,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC7E,IAAI,CAAC,QAAQ;YAAE,OAAO;QAEtB,MAAM,KAAK,GAAG,MAAM,UAAU,EAAE,CAAC;QACjC,IAAI,CAAC,KAAK;YAAE,OAAO;QAEnB,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAC;QACtC,IAAI,CAAC,KAAK;YAAE,OAAO;QAEnB,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC;YAChC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;YAC7B,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;QAEH,qCAAqC;QACrC,MAAM,MAAM,CAAC,YAAY,CAAC;YACxB,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,SAAS;YAClC,UAAU,EAAE,KAAK,CAAC,UAAU;SAC7B,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,6BAA6B;IAC/B,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=pre-tool-use.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pre-tool-use.d.ts","sourceRoot":"","sources":["../../src/hooks/pre-tool-use.ts"],"names":[],"mappings":""}
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ // PreToolUse hook — the gatekeeper
3
+ // Intercepts Edit/Write/MultiEdit tool calls, claims file locks
4
+ import { PreclaimClient, findConfig, loadCredentials } from '@preclaim/core';
5
+ import { readHookInput, writeHookOutput } from '../lib/hook-io.js';
6
+ import { minimatch } from 'minimatch';
7
+ const WRITE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit']);
8
+ async function main() {
9
+ try {
10
+ const input = await readHookInput();
11
+ // Only intercept file-writing tools
12
+ if (!input.tool_name || !WRITE_TOOLS.has(input.tool_name)) {
13
+ return; // No output = allow
14
+ }
15
+ // Extract file path from tool input
16
+ const filePath = input.tool_input?.file_path;
17
+ if (!filePath) {
18
+ return; // No file path = allow
19
+ }
20
+ // Load config
21
+ const found = await findConfig();
22
+ if (!found) {
23
+ return; // No config = allow (not a preclaim project)
24
+ }
25
+ // Check ignore patterns
26
+ const relativePath = filePath.startsWith('/') ? filePath : filePath;
27
+ if (found.config.ignore.some(pattern => minimatch(relativePath, pattern))) {
28
+ return; // Ignored file = allow
29
+ }
30
+ // Load credentials
31
+ const creds = await loadCredentials();
32
+ if (!creds) {
33
+ if (found.config.failOpen)
34
+ return;
35
+ writeHookOutput({
36
+ permissionDecision: 'deny',
37
+ reason: 'Preclaim: not authenticated. Run `preclaim login`.',
38
+ });
39
+ return;
40
+ }
41
+ // Claim file
42
+ const client = new PreclaimClient({
43
+ baseUrl: found.config.backend,
44
+ accessToken: creds.accessToken,
45
+ timeoutMs: 2000, // Must be fast
46
+ });
47
+ const result = await client.claimFile({
48
+ project_id: found.config.projectId,
49
+ file_path: relativePath,
50
+ session_id: input.session_id,
51
+ ttl_minutes: found.config.ttl,
52
+ });
53
+ // Network error — fail open
54
+ if (result.error) {
55
+ if (found.config.failOpen) {
56
+ writeHookOutput({
57
+ permissionDecision: 'allow',
58
+ systemMessage: `[Preclaim] Warning: could not reach server (${result.error}). Proceeding without lock.`,
59
+ });
60
+ return;
61
+ }
62
+ writeHookOutput({
63
+ permissionDecision: 'deny',
64
+ reason: `Preclaim: server error — ${result.error}`,
65
+ });
66
+ return;
67
+ }
68
+ const data = result.data;
69
+ if (data.status === 'acquired') {
70
+ writeHookOutput({
71
+ permissionDecision: 'allow',
72
+ systemMessage: `[Preclaim] Locked: ${relativePath} (expires: ${data.expires_at})`,
73
+ });
74
+ }
75
+ else if (data.status === 'already_held') {
76
+ writeHookOutput({
77
+ permissionDecision: 'allow',
78
+ systemMessage: `[Preclaim] Lock extended: ${relativePath} (expires: ${data.expires_at})`,
79
+ });
80
+ }
81
+ else if (data.status === 'conflict') {
82
+ writeHookOutput({
83
+ permissionDecision: 'deny',
84
+ reason: [
85
+ `Preclaim: ${relativePath} is locked by another session.`,
86
+ ` Session: ${data.holder.session_id.slice(0, 8)}…`,
87
+ ` Since: ${new Date(data.holder.acquired_at).toLocaleTimeString()}`,
88
+ ` Expires: ${new Date(data.holder.expires_at).toLocaleTimeString()}`,
89
+ '',
90
+ 'Wait for the lock to expire or work on a different file.',
91
+ ].join('\n'),
92
+ });
93
+ }
94
+ }
95
+ catch {
96
+ // Fail open — never block development
97
+ return;
98
+ }
99
+ }
100
+ main();
101
+ //# sourceMappingURL=pre-tool-use.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pre-tool-use.js","sourceRoot":"","sources":["../../src/hooks/pre-tool-use.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=session-start.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-start.d.ts","sourceRoot":"","sources":["../../src/hooks/session-start.ts"],"names":[],"mappings":""}
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ // SessionStart hook — init
3
+ // Registers session, starts heartbeat daemon, injects system message
4
+ import { spawn } from 'node:child_process';
5
+ import { writeFile } from 'node:fs/promises';
6
+ import { join, dirname } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { PreclaimClient, findConfig, loadCredentials } from '@preclaim/core';
9
+ import { readHookInput, writeHookOutput } from '../lib/hook-io.js';
10
+ async function main() {
11
+ try {
12
+ const input = await readHookInput();
13
+ const found = await findConfig();
14
+ if (!found)
15
+ return;
16
+ const creds = await loadCredentials();
17
+ if (!creds) {
18
+ writeHookOutput({
19
+ systemMessage: '[Preclaim] Not authenticated. File locking disabled. Run `preclaim login` to enable.',
20
+ });
21
+ return;
22
+ }
23
+ const client = new PreclaimClient({
24
+ baseUrl: found.config.backend,
25
+ accessToken: creds.accessToken,
26
+ timeoutMs: 3000,
27
+ });
28
+ // Register session
29
+ const result = await client.registerSession({
30
+ session_id: input.session_id,
31
+ project_id: found.config.projectId,
32
+ provider: 'claude-code',
33
+ });
34
+ if (result.error) {
35
+ if (found.config.failOpen) {
36
+ writeHookOutput({
37
+ systemMessage: `[Preclaim] Warning: could not register session (${result.error}). File locking may not work.`,
38
+ });
39
+ return;
40
+ }
41
+ }
42
+ // Start heartbeat daemon
43
+ const __dirname = dirname(fileURLToPath(import.meta.url));
44
+ const heartbeatScript = join(__dirname, 'heartbeat-daemon.js');
45
+ const daemon = spawn('node', [heartbeatScript], {
46
+ detached: true,
47
+ stdio: 'ignore',
48
+ env: {
49
+ ...process.env,
50
+ PRECLAIM_SESSION_ID: input.session_id,
51
+ PRECLAIM_BACKEND: found.config.backend,
52
+ PRECLAIM_ACCESS_TOKEN: creds.accessToken,
53
+ },
54
+ });
55
+ daemon.unref();
56
+ // Save PID for cleanup
57
+ if (daemon.pid) {
58
+ await writeFile(join(process.cwd(), '.preclaim.pid'), String(daemon.pid));
59
+ }
60
+ writeHookOutput({
61
+ systemMessage: [
62
+ '[Preclaim] Session registered. File locking is active.',
63
+ '',
64
+ 'Preclaim coordinates file access across multiple AI sessions:',
65
+ '- Files are automatically locked when you edit them',
66
+ '- Locks prevent other sessions from editing the same files',
67
+ '- Locks are released when you commit or this session ends',
68
+ '- If a lock is denied, work on a different file',
69
+ ].join('\n'),
70
+ });
71
+ }
72
+ catch {
73
+ // Fail open
74
+ }
75
+ }
76
+ main();
77
+ //# sourceMappingURL=session-start.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-start.js","sourceRoot":"","sources":["../../src/hooks/session-start.ts"],"names":[],"mappings":";AACA,2BAA2B;AAC3B,qEAAqE;AAErE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEnE,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAC;QAEpC,MAAM,KAAK,GAAG,MAAM,UAAU,EAAE,CAAC;QACjC,IAAI,CAAC,KAAK;YAAE,OAAO;QAEnB,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAC;QACtC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,eAAe,CAAC;gBACd,aAAa,EAAE,sFAAsF;aACtG,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC;YAChC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;YAC7B,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;QAEH,mBAAmB;QACnB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC;YAC1C,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,SAAS;YAClC,QAAQ,EAAE,aAAa;SACxB,CAAC,CAAC;QAEH,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YACjB,IAAI,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;gBAC1B,eAAe,CAAC;oBACd,aAAa,EAAE,mDAAmD,MAAM,CAAC,KAAK,+BAA+B;iBAC9G,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;QACH,CAAC;QAED,yBAAyB;QACzB,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1D,MAAM,eAAe,GAAG,IAAI,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC;QAE/D,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,eAAe,CAAC,EAAE;YAC9C,QAAQ,EAAE,IAAI;YACd,KAAK,EAAE,QAAQ;YACf,GAAG,EAAE;gBACH,GAAG,OAAO,CAAC,GAAG;gBACd,mBAAmB,EAAE,KAAK,CAAC,UAAU;gBACrC,gBAAgB,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;gBACtC,qBAAqB,EAAE,KAAK,CAAC,WAAW;aACzC;SACF,CAAC,CAAC;QAEH,MAAM,CAAC,KAAK,EAAE,CAAC;QAEf,uBAAuB;QACvB,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;YACf,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5E,CAAC;QAED,eAAe,CAAC;YACd,aAAa,EAAE;gBACb,wDAAwD;gBACxD,EAAE;gBACF,+DAA+D;gBAC/D,qDAAqD;gBACrD,4DAA4D;gBAC5D,2DAA2D;gBAC3D,iDAAiD;aAClD,CAAC,IAAI,CAAC,IAAI,CAAC;SACb,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,YAAY;IACd,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=stop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop.d.ts","sourceRoot":"","sources":["../../src/hooks/stop.ts"],"names":[],"mappings":""}
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ // Stop hook — cleanup
3
+ // Releases all locks and stops heartbeat daemon
4
+ import { readFile, unlink } from 'node:fs/promises';
5
+ import { join } from 'node:path';
6
+ import { PreclaimClient, findConfig, loadCredentials } from '@preclaim/core';
7
+ import { readHookInput } from '../lib/hook-io.js';
8
+ async function main() {
9
+ try {
10
+ const input = await readHookInput();
11
+ const found = await findConfig();
12
+ if (!found)
13
+ return;
14
+ const creds = await loadCredentials();
15
+ if (!creds)
16
+ return;
17
+ const client = new PreclaimClient({
18
+ baseUrl: found.config.backend,
19
+ accessToken: creds.accessToken,
20
+ timeoutMs: 3000,
21
+ });
22
+ // End session (releases all locks)
23
+ await client.endSession(input.session_id);
24
+ // Kill heartbeat daemon if running
25
+ const pidFile = join(process.cwd(), '.preclaim.pid');
26
+ try {
27
+ const pid = parseInt(await readFile(pidFile, 'utf-8'), 10);
28
+ process.kill(pid, 'SIGTERM');
29
+ await unlink(pidFile);
30
+ }
31
+ catch {
32
+ // No daemon running or already stopped
33
+ }
34
+ }
35
+ catch {
36
+ // Silent fail — cleanup is best-effort
37
+ }
38
+ }
39
+ main();
40
+ //# sourceMappingURL=stop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop.js","sourceRoot":"","sources":["../../src/hooks/stop.ts"],"names":[],"mappings":";AACA,sBAAsB;AACtB,gDAAgD;AAEhD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAC;QAEpC,MAAM,KAAK,GAAG,MAAM,UAAU,EAAE,CAAC;QACjC,IAAI,CAAC,KAAK;YAAE,OAAO;QAEnB,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAC;QACtC,IAAI,CAAC,KAAK;YAAE,OAAO;QAEnB,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC;YAChC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;YAC7B,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,SAAS,EAAE,IAAI;SAChB,CAAC,CAAC;QAEH,mCAAmC;QACnC,MAAM,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAE1C,mCAAmC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,eAAe,CAAC,CAAC;QACrD,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3D,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YAC7B,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,uCAAuC;QACzC,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,uCAAuC;IACzC,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { initCommand } from './commands/init.js';
4
+ import { loginCommand } from './commands/login.js';
5
+ import { lockCommand } from './commands/lock.js';
6
+ import { unlockCommand } from './commands/unlock.js';
7
+ import { statusCommand } from './commands/status.js';
8
+ import { checkCommand } from './commands/check.js';
9
+ import { whoamiCommand } from './commands/whoami.js';
10
+ import { configCommand } from './commands/config.js';
11
+ import { installHooksCommand } from './commands/install-hooks.js';
12
+ const program = new Command();
13
+ program
14
+ .name('preclaim')
15
+ .description('AI File Coordination Layer — predictive file locking for AI coding agents')
16
+ .version('0.1.0');
17
+ program
18
+ .command('init')
19
+ .description('Initialize Preclaim in the current project')
20
+ .option('--backend <url>', 'Backend URL', 'https://preclaim.vercel.app')
21
+ .option('--project-id <id>', 'Project ID')
22
+ .action(initCommand);
23
+ program
24
+ .command('login')
25
+ .description('Authenticate with Preclaim')
26
+ .action(loginCommand);
27
+ program
28
+ .command('lock <file>')
29
+ .description('Lock a file')
30
+ .option('-s, --session <id>', 'Session ID')
31
+ .option('-t, --ttl <minutes>', 'Lock TTL in minutes')
32
+ .action(lockCommand);
33
+ program
34
+ .command('unlock [file]')
35
+ .description('Release a file lock')
36
+ .option('-s, --session <id>', 'Session ID')
37
+ .option('-a, --all', 'Release all locks for this session')
38
+ .action(unlockCommand);
39
+ program
40
+ .command('status')
41
+ .description('Show active locks for this project')
42
+ .action(statusCommand);
43
+ program
44
+ .command('check <files...>')
45
+ .description('Check lock status for files')
46
+ .action(checkCommand);
47
+ program
48
+ .command('whoami')
49
+ .description('Show current user info')
50
+ .action(whoamiCommand);
51
+ program
52
+ .command('config')
53
+ .description('View or modify project configuration')
54
+ .option('--get <key>', 'Get a config value')
55
+ .option('--set <key=value>', 'Set a config value')
56
+ .action(configCommand);
57
+ program
58
+ .command('install-hooks')
59
+ .description('Install Claude Code hooks in the current project')
60
+ .action(installHooksCommand);
61
+ program.parse();
62
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,UAAU,CAAC;KAChB,WAAW,CAAC,2EAA2E,CAAC;KACxF,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,4CAA4C,CAAC;KACzD,MAAM,CAAC,iBAAiB,EAAE,aAAa,EAAE,6BAA6B,CAAC;KACvE,MAAM,CAAC,mBAAmB,EAAE,YAAY,CAAC;KACzC,MAAM,CAAC,WAAW,CAAC,CAAC;AAEvB,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,4BAA4B,CAAC;KACzC,MAAM,CAAC,YAAY,CAAC,CAAC;AAExB,OAAO;KACJ,OAAO,CAAC,aAAa,CAAC;KACtB,WAAW,CAAC,aAAa,CAAC;KAC1B,MAAM,CAAC,oBAAoB,EAAE,YAAY,CAAC;KAC1C,MAAM,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;KACpD,MAAM,CAAC,WAAW,CAAC,CAAC;AAEvB,OAAO;KACJ,OAAO,CAAC,eAAe,CAAC;KACxB,WAAW,CAAC,qBAAqB,CAAC;KAClC,MAAM,CAAC,oBAAoB,EAAE,YAAY,CAAC;KAC1C,MAAM,CAAC,WAAW,EAAE,oCAAoC,CAAC;KACzD,MAAM,CAAC,aAAa,CAAC,CAAC;AAEzB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,oCAAoC,CAAC;KACjD,MAAM,CAAC,aAAa,CAAC,CAAC;AAEzB,OAAO;KACJ,OAAO,CAAC,kBAAkB,CAAC;KAC3B,WAAW,CAAC,6BAA6B,CAAC;KAC1C,MAAM,CAAC,YAAY,CAAC,CAAC;AAExB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,wBAAwB,CAAC;KACrC,MAAM,CAAC,aAAa,CAAC,CAAC;AAEzB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,sCAAsC,CAAC;KACnD,MAAM,CAAC,aAAa,EAAE,oBAAoB,CAAC;KAC3C,MAAM,CAAC,mBAAmB,EAAE,oBAAoB,CAAC;KACjD,MAAM,CAAC,aAAa,CAAC,CAAC;AAEzB,OAAO;KACJ,OAAO,CAAC,eAAe,CAAC;KACxB,WAAW,CAAC,kDAAkD,CAAC;KAC/D,MAAM,CAAC,mBAAmB,CAAC,CAAC;AAE/B,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { type PreclaimCredentials } from '@preclaim/core';
2
+ export declare function requireAuth(): Promise<PreclaimCredentials>;
3
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAE3E,wBAAsB,WAAW,IAAI,OAAO,CAAC,mBAAmB,CAAC,CAchE"}
@@ -0,0 +1,15 @@
1
+ import { loadCredentials } from '@preclaim/core';
2
+ export async function requireAuth() {
3
+ const creds = await loadCredentials();
4
+ if (!creds) {
5
+ console.error('Not logged in. Run `preclaim login` first.');
6
+ process.exit(1);
7
+ }
8
+ // Check expiry
9
+ if (new Date(creds.expiresAt) < new Date()) {
10
+ console.error('Session expired. Run `preclaim login` to re-authenticate.');
11
+ process.exit(1);
12
+ }
13
+ return creds;
14
+ }
15
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAA4B,MAAM,gBAAgB,CAAC;AAE3E,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,KAAK,GAAG,MAAM,eAAe,EAAE,CAAC;IACtC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAC5D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,eAAe;IACf,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC;QAC3C,OAAO,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;QAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { PreclaimClient, type PreclaimConfig, type PreclaimCredentials } from '@preclaim/core';
2
+ export interface ResolvedContext {
3
+ client: PreclaimClient;
4
+ config: PreclaimConfig;
5
+ credentials: PreclaimCredentials;
6
+ }
7
+ export declare function resolveContext(): Promise<ResolvedContext>;
8
+ //# sourceMappingURL=client-factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-factory.d.ts","sourceRoot":"","sources":["../../src/lib/client-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAc,KAAK,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAG3G,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,cAAc,CAAC;IACvB,WAAW,EAAE,mBAAmB,CAAC;CAClC;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,eAAe,CAAC,CAgB/D"}
@@ -0,0 +1,17 @@
1
+ import { PreclaimClient, findConfig } from '@preclaim/core';
2
+ import { requireAuth } from './auth.js';
3
+ export async function resolveContext() {
4
+ const credentials = await requireAuth();
5
+ const found = await findConfig();
6
+ if (!found) {
7
+ console.error('No .preclaim.json found. Run `preclaim init` in your project root.');
8
+ process.exit(1);
9
+ }
10
+ const client = new PreclaimClient({
11
+ baseUrl: found.config.backend,
12
+ accessToken: credentials.accessToken,
13
+ timeoutMs: 5000,
14
+ });
15
+ return { client, config: found.config, credentials };
16
+ }
17
+ //# sourceMappingURL=client-factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-factory.js","sourceRoot":"","sources":["../../src/lib/client-factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAiD,MAAM,gBAAgB,CAAC;AAC3G,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAQxC,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,WAAW,GAAG,MAAM,WAAW,EAAE,CAAC;IAExC,MAAM,KAAK,GAAG,MAAM,UAAU,EAAE,CAAC;IACjC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,oEAAoE,CAAC,CAAC;QACpF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC;QAChC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,OAAO;QAC7B,WAAW,EAAE,WAAW,CAAC,WAAW;QACpC,SAAS,EAAE,IAAI;KAChB,CAAC,CAAC;IAEH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC;AACvD,CAAC"}
@@ -0,0 +1,13 @@
1
+ export interface ClaudeHookInput {
2
+ session_id: string;
3
+ tool_name?: string;
4
+ tool_input?: Record<string, unknown>;
5
+ }
6
+ export interface ClaudeHookOutput {
7
+ permissionDecision?: 'allow' | 'deny';
8
+ reason?: string;
9
+ systemMessage?: string;
10
+ }
11
+ export declare function readHookInput(): Promise<ClaudeHookInput>;
12
+ export declare function writeHookOutput(output: ClaudeHookOutput): void;
13
+ //# sourceMappingURL=hook-io.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-io.d.ts","sourceRoot":"","sources":["../../src/lib/hook-io.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,kBAAkB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,eAAe,CAAC,CAiB9D;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAE9D"}
@@ -0,0 +1,24 @@
1
+ // Hook I/O helpers for Claude Code hooks
2
+ // Reads hook input from stdin and writes hook output to stdout
3
+ export async function readHookInput() {
4
+ return new Promise((resolve, reject) => {
5
+ let data = '';
6
+ process.stdin.setEncoding('utf-8');
7
+ process.stdin.on('data', (chunk) => { data += chunk; });
8
+ process.stdin.on('end', () => {
9
+ try {
10
+ resolve(JSON.parse(data));
11
+ }
12
+ catch {
13
+ reject(new Error('Failed to parse hook input'));
14
+ }
15
+ });
16
+ process.stdin.on('error', reject);
17
+ // Timeout after 3s
18
+ setTimeout(() => reject(new Error('Hook input timeout')), 3000);
19
+ });
20
+ }
21
+ export function writeHookOutput(output) {
22
+ process.stdout.write(JSON.stringify(output));
23
+ }
24
+ //# sourceMappingURL=hook-io.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-io.js","sourceRoot":"","sources":["../../src/lib/hook-io.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,+DAA+D;AAc/D,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QACnC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACxD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YAC3B,IAAI,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YAC5B,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,CAAC,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC,CAAC;YAClD,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAElC,mBAAmB;QACnB,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAwB;IACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Lock } from '@preclaim/core';
2
+ export declare function formatLock(lock: Lock): string;
3
+ export declare function formatLockTable(locks: Lock[]): string;
4
+ //# sourceMappingURL=output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAE3C,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAI7C;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,MAAM,CAWrD"}
@@ -0,0 +1,17 @@
1
+ export function formatLock(lock) {
2
+ const acquired = new Date(lock.acquired_at).toLocaleTimeString();
3
+ const expires = new Date(lock.expires_at).toLocaleTimeString();
4
+ return ` ${lock.file_path} (session: ${lock.session_id.slice(0, 8)}… acquired: ${acquired} expires: ${expires})`;
5
+ }
6
+ export function formatLockTable(locks) {
7
+ if (locks.length === 0) {
8
+ return 'No active locks.';
9
+ }
10
+ const lines = ['Active locks:', ''];
11
+ for (const lock of locks) {
12
+ lines.push(formatLock(lock));
13
+ }
14
+ lines.push('', `Total: ${locks.length} lock(s)`);
15
+ return lines.join('\n');
16
+ }
17
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,UAAU,CAAC,IAAU;IACnC,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,kBAAkB,EAAE,CAAC;IACjE,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,kBAAkB,EAAE,CAAC;IAC/D,OAAO,KAAK,IAAI,CAAC,SAAS,eAAe,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,gBAAgB,QAAQ,cAAc,OAAO,GAAG,CAAC;AACvH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,kBAAkB,CAAC;IAC5B,CAAC;IAED,MAAM,KAAK,GAAG,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC;IACpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/B,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,KAAK,CAAC,MAAM,UAAU,CAAC,CAAC;IACjD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "preclaim",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "preclaim": "./dist/index.js"
7
+ },
8
+ "main": "./dist/index.js",
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsc --watch",
12
+ "typecheck": "tsc --noEmit",
13
+ "clean": "rm -rf dist"
14
+ },
15
+ "dependencies": {
16
+ "@preclaim/core": "workspace:*",
17
+ "commander": "^13.1.0",
18
+ "minimatch": "^10.0.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^22.0.0",
22
+ "typescript": "^5.8.0"
23
+ }
24
+ }
@@ -0,0 +1,28 @@
1
+ import { resolveContext } from '../lib/client-factory.js';
2
+
3
+ export async function checkCommand(filePaths: string[]) {
4
+ if (filePaths.length === 0) {
5
+ console.error('Specify one or more file paths to check.');
6
+ process.exit(1);
7
+ }
8
+
9
+ const { client, config } = await resolveContext();
10
+
11
+ const result = await client.batchCheck({
12
+ project_id: config.projectId,
13
+ file_paths: filePaths,
14
+ });
15
+
16
+ if (result.error) {
17
+ console.error(`Failed to check: ${result.error}`);
18
+ process.exit(1);
19
+ }
20
+
21
+ for (const [path, lock] of Object.entries(result.data!.locks)) {
22
+ if (lock) {
23
+ console.log(`LOCKED ${path} (session: ${lock.session_id.slice(0, 8)}… expires: ${new Date(lock.expires_at).toLocaleTimeString()})`);
24
+ } else {
25
+ console.log(`FREE ${path}`);
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,43 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { findConfig, type PreclaimConfig } from '@preclaim/core';
3
+
4
+ export async function configCommand(opts: { get?: string; set?: string }) {
5
+ const found = await findConfig();
6
+ if (!found) {
7
+ console.error('No .preclaim.json found. Run `preclaim init` first.');
8
+ process.exit(1);
9
+ }
10
+
11
+ if (opts.get) {
12
+ const value = found.config[opts.get as keyof PreclaimConfig];
13
+ console.log(JSON.stringify(value, null, 2));
14
+ return;
15
+ }
16
+
17
+ if (opts.set) {
18
+ const [key, ...rest] = opts.set.split('=');
19
+ const value = rest.join('=');
20
+
21
+ if (!key || !value) {
22
+ console.error('Usage: preclaim config --set key=value');
23
+ process.exit(1);
24
+ }
25
+
26
+ const raw = await readFile(found.configPath, 'utf-8');
27
+ const config = JSON.parse(raw) as Record<string, unknown>;
28
+
29
+ // Try to parse as JSON, fallback to string
30
+ try {
31
+ config[key] = JSON.parse(value);
32
+ } catch {
33
+ config[key] = value;
34
+ }
35
+
36
+ await writeFile(found.configPath, JSON.stringify(config, null, 2) + '\n');
37
+ console.log(`Set ${key} = ${JSON.stringify(config[key])}`);
38
+ return;
39
+ }
40
+
41
+ // No flags — show full config
42
+ console.log(JSON.stringify(found.config, null, 2));
43
+ }