nofax 0.2.1 → 0.2.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.
- package/package.json +1 -1
- package/src/cli.mjs +17 -1
- package/src/mcp-server.mjs +1 -1
- package/src/requests.mjs +45 -5
package/package.json
CHANGED
package/src/cli.mjs
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
1
2
|
import { initConfig, loadConfig } from './config.mjs';
|
|
2
3
|
import { requestApproval, requestRefinement, sendNotification } from './ntfy.mjs';
|
|
3
4
|
import { handleClaudePermissionRequest } from './adapters/claude.mjs';
|
|
4
5
|
import { handleCodexPermissionRequest } from './adapters/codex.mjs';
|
|
5
6
|
import { handleGeminiHook } from './adapters/gemini.mjs';
|
|
6
7
|
|
|
7
|
-
const HELP = `nofax - remote approvals and notifications for coding agents\n\nUsage:\n nofax init [--server URL] [--topic TOPIC] [--timeout SECONDS] [--force]\n nofax test\n nofax notify [--title TITLE] MESSAGE...\n nofax approve [--title TITLE] MESSAGE...\n nofax refine [--title TITLE] MESSAGE...\n nofax mcp\n nofax config\n nofax hook claude\n nofax hook codex\n nofax hook gemini\n\nEnvironment:\n NOFAX_HOME Override ~/.nofax\n`;
|
|
8
|
+
const HELP = `nofax - remote approvals and notifications for coding agents\n\nUsage:\n nofax --version\n nofax init [--server URL] [--topic TOPIC] [--timeout SECONDS] [--force]\n nofax test\n nofax notify [--title TITLE] MESSAGE...\n nofax approve [--title TITLE] MESSAGE...\n nofax refine [--title TITLE] MESSAGE...\n nofax mcp\n nofax config\n nofax hook claude\n nofax hook codex\n nofax hook gemini\n\nEnvironment:\n NOFAX_HOME Override ~/.nofax\n`;
|
|
9
|
+
|
|
10
|
+
const PACKAGE_JSON_URL = new URL('../package.json', import.meta.url);
|
|
11
|
+
|
|
12
|
+
async function readPackageVersion() {
|
|
13
|
+
const metadata = JSON.parse(await readFile(PACKAGE_JSON_URL, 'utf8'));
|
|
14
|
+
if (typeof metadata.version !== 'string' || metadata.version.length === 0) {
|
|
15
|
+
throw new Error('NOFAX_PACKAGE_VERSION_INVALID');
|
|
16
|
+
}
|
|
17
|
+
return metadata.version;
|
|
18
|
+
}
|
|
8
19
|
|
|
9
20
|
function parseArgs(args) {
|
|
10
21
|
const flags = {};
|
|
@@ -96,6 +107,11 @@ export async function runCli(args, overrides = {}) {
|
|
|
96
107
|
return 0;
|
|
97
108
|
}
|
|
98
109
|
|
|
110
|
+
if (args[0] === '--version' || args[0] === '-v') {
|
|
111
|
+
deps.stdout.write(`${await readPackageVersion()}\n`);
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
99
115
|
const command = args[0];
|
|
100
116
|
if (command === 'init') {
|
|
101
117
|
const { flags } = parseArgs(args.slice(1));
|
package/src/mcp-server.mjs
CHANGED
|
@@ -32,7 +32,7 @@ function result(value) {
|
|
|
32
32
|
|
|
33
33
|
export function buildMcpServer({ handlers = createMcpToolHandlers() } = {}) {
|
|
34
34
|
const server = new McpServer(
|
|
35
|
-
{ name: 'nofax', version: '0.2.
|
|
35
|
+
{ name: 'nofax', version: '0.2.2' },
|
|
36
36
|
{ instructions: SERVER_INSTRUCTIONS }
|
|
37
37
|
);
|
|
38
38
|
|
package/src/requests.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chmod, mkdir, readFile, readdir, rename, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { chmod, link, mkdir, readFile, readdir, rename, unlink, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { resolveNofaxHome } from './config.mjs';
|
|
4
4
|
|
|
@@ -47,7 +47,12 @@ function validateRequest(input) {
|
|
|
47
47
|
function paths({ home, env, requestId }) {
|
|
48
48
|
const root = resolveNofaxHome({ home, env });
|
|
49
49
|
const requestsRoot = join(root, 'requests');
|
|
50
|
-
|
|
50
|
+
const normalizedRequestId = validateRequestId(requestId);
|
|
51
|
+
return {
|
|
52
|
+
requestsRoot,
|
|
53
|
+
file: join(requestsRoot, `${normalizedRequestId}.json`),
|
|
54
|
+
terminal: join(requestsRoot, `${normalizedRequestId}.terminal.json`)
|
|
55
|
+
};
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
async function atomicWrite(path, value) {
|
|
@@ -58,6 +63,35 @@ async function atomicWrite(path, value) {
|
|
|
58
63
|
try { await chmod(path, 0o600); } catch {}
|
|
59
64
|
}
|
|
60
65
|
|
|
66
|
+
async function loadTerminalClaim(path) {
|
|
67
|
+
try {
|
|
68
|
+
const terminal = validateRequest(JSON.parse(await readFile(path, 'utf8')));
|
|
69
|
+
if (terminal.status !== 'resolved') throw new Error('NOFAX_REQUEST_TERMINAL_INVALID');
|
|
70
|
+
return terminal;
|
|
71
|
+
} catch (error) {
|
|
72
|
+
if (error?.code === 'ENOENT') return null;
|
|
73
|
+
if (error instanceof SyntaxError) throw new Error('NOFAX_REQUEST_TERMINAL_INVALID_JSON');
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function claimTerminal(path, value) {
|
|
79
|
+
const temp = `${path}.tmp-${process.pid}-${Math.random().toString(16).slice(2)}`;
|
|
80
|
+
await writeFile(temp, `${JSON.stringify(value, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
81
|
+
try { await chmod(temp, 0o600); } catch {}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await link(temp, path);
|
|
85
|
+
try { await chmod(path, 0o600); } catch {}
|
|
86
|
+
return true;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (error?.code === 'EEXIST') return false;
|
|
89
|
+
throw error;
|
|
90
|
+
} finally {
|
|
91
|
+
try { await unlink(temp); } catch {}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
61
95
|
export async function savePendingRequest({ home, env, request }) {
|
|
62
96
|
const normalized = validateRequest(request);
|
|
63
97
|
if (normalized.status !== 'pending') throw new Error('NOFAX_REQUEST_NOT_PENDING');
|
|
@@ -68,9 +102,12 @@ export async function savePendingRequest({ home, env, request }) {
|
|
|
68
102
|
}
|
|
69
103
|
|
|
70
104
|
export async function loadRequest({ home, env, requestId }) {
|
|
71
|
-
const { file } = paths({ home, env, requestId });
|
|
105
|
+
const { file, terminal } = paths({ home, env, requestId });
|
|
72
106
|
try {
|
|
73
|
-
|
|
107
|
+
const current = validateRequest(JSON.parse(await readFile(file, 'utf8')));
|
|
108
|
+
if (current.status === 'resolved') return current;
|
|
109
|
+
const claimed = await loadTerminalClaim(terminal);
|
|
110
|
+
return claimed ?? current;
|
|
74
111
|
} catch (error) {
|
|
75
112
|
if (error?.code === 'ENOENT') throw new Error('NOFAX_REQUEST_NOT_FOUND');
|
|
76
113
|
if (error instanceof SyntaxError) throw new Error('NOFAX_REQUEST_INVALID_JSON');
|
|
@@ -89,7 +126,10 @@ export async function resolveRequest({ home, env, requestId, response, resolvedA
|
|
|
89
126
|
decision: response.decision,
|
|
90
127
|
...(response.text === undefined ? {} : { text: response.text })
|
|
91
128
|
});
|
|
92
|
-
const { file } = paths({ home, env, requestId });
|
|
129
|
+
const { file, terminal } = paths({ home, env, requestId });
|
|
130
|
+
if (!await claimTerminal(terminal, resolved)) {
|
|
131
|
+
return loadRequest({ home, env, requestId });
|
|
132
|
+
}
|
|
93
133
|
await atomicWrite(file, resolved);
|
|
94
134
|
return resolved;
|
|
95
135
|
}
|