@memorax/memorax-code 0.1.5 → 0.1.6
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/README.md +7 -18
- package/bin/memorax-code-npm-preinstall.mjs +28 -39
- package/bin/memorax-code-plugin-postinstall.mjs +23 -1537
- package/bin/memorax-code-setup.mjs +1615 -0
- package/bin/memorax-code.mjs +289 -5
- package/docs/configuration.md +66 -17
- package/docs/troubleshooting.md +105 -7
- package/lib/memorax-code-adapter-common/src/config-utils.d.mts +5 -1
- package/lib/memorax-code-adapter-common/src/config-utils.mjs +36 -3
- package/lib/memorax-code-adapter-common/src/credentials/linux-secret-service.mjs +110 -0
- package/lib/memorax-code-adapter-common/src/credentials/macos-keychain.mjs +224 -0
- package/lib/memorax-code-adapter-common/src/credentials/secure-command.mjs +340 -0
- package/lib/memorax-code-adapter-common/src/credentials/trial-credential-record.d.mts +82 -0
- package/lib/memorax-code-adapter-common/src/credentials/trial-credential-record.mjs +183 -0
- package/lib/memorax-code-adapter-common/src/credentials/trial-credential-store.d.mts +82 -0
- package/lib/memorax-code-adapter-common/src/credentials/trial-credential-store.mjs +271 -0
- package/lib/memorax-code-adapter-common/src/credentials/windows-dpapi.mjs +440 -0
- package/lib/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +7 -4
- package/lib/memorax-code-adapter-common/src/setup-completion.d.mts +81 -0
- package/lib/memorax-code-adapter-common/src/setup-completion.mjs +154 -0
- package/lib/memorax-code-backend/dist/clients/claude/memory-hook-runtime.js +21 -2
- package/lib/memorax-code-backend/dist/clients/codex/memory-hook-runtime.js +23 -3
- package/lib/memorax-code-backend/dist/clients/opencode/memory-hook-runtime.js +19 -1
- package/lib/memorax-code-backend/dist/entrypoints/backend-cli.js +17 -16
- package/lib/memorax-code-backend/dist/lifecycle/orchestrator.js +81 -22
- package/lib/memorax-code-backend/dist/memorax-cli.js +3 -0
- package/lib/memorax-code-backend/dist/memory/automatic-retrieval.js +8 -2
- package/lib/memorax-code-backend/dist/memory/automatic-writeback.js +13 -1
- package/lib/memorax-code-backend/dist/memory/cli.js +10 -0
- package/lib/memorax-code-backend/dist/memory/quota-notice.js +208 -0
- package/lib/memorax-code-backend/dist/memory/service.js +11 -0
- package/lib/memorax-code-backend/dist/provider/memorax/adapter.js +4 -2
- package/lib/memorax-code-backend/dist/provider/memorax/http.js +10 -3
- package/lib/memorax-code-backend/dist/provider/memorax/quota.js +26 -0
- package/lib/memorax-code-backend/package.json +1 -1
- package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-adapter/runtime-hooks/memory-turn.mjs +5 -3
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +2 -0
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +4 -2
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.d.mts +5 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.mjs +36 -3
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/linux-secret-service.mjs +110 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/macos-keychain.mjs +224 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/secure-command.mjs +340 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-record.d.mts +82 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-record.mjs +183 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-store.d.mts +82 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-store.mjs +271 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/windows-dpapi.mjs +440 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +7 -4
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/setup-completion.d.mts +81 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/setup-completion.mjs +154 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/memory-turn.mjs +5 -3
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +2 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +4 -2
- package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
- package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-codex-adapter/package.json +1 -1
- package/lib/memorax-code-codex-adapter/runtime-hooks/memory-skill-reminder.mjs +2 -0
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-add.md +2 -0
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-search.md +4 -2
- package/lib/memorax-code-dsh-adapter/hooks/repo-memory-job.mjs +5 -2
- package/lib/memorax-code-dsh-adapter/package.json +1 -1
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-add.md +2 -0
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-search.md +4 -2
- package/lib/memorax-code-dsh-adapter/src/profile-lifecycle.mjs +158 -7
- package/lib/memorax-code-dsh-adapter/src/runtime-state.mjs +21 -0
- package/lib/memorax-code-opencode-adapter/package.json +1 -1
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-add.md +2 -0
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-search.md +4 -2
- package/lib/memorax-code-opencode-adapter/src/plugin.mjs +20 -0
- package/lib/package-transition.mjs +311 -0
- package/lib/resolve-codex-command.mjs +32 -6
- package/lib/setup-memory-preferences.mjs +113 -0
- package/lib/setup-reconcile.mjs +128 -0
- package/lib/trial-plugin-mark.mjs +171 -0
- package/lib/trial-provision-client.mjs +307 -0
- package/lib/trial-provision-flow.mjs +215 -0
- package/lib/trial-setup.mjs +53 -0
- package/package.json +1 -1
|
@@ -127,14 +127,17 @@ export async function withJsonFileLockAsync(path, operation, options = {}) {
|
|
|
127
127
|
const timeoutMs = positiveInteger(options.timeoutMs, JSON_FILE_LOCK_TIMEOUT_MS);
|
|
128
128
|
const staleMs = positiveInteger(options.staleMs, JSON_FILE_LOCK_STALE_MS);
|
|
129
129
|
const retryMs = positiveInteger(options.retryMs, JSON_FILE_LOCK_RETRY_MS);
|
|
130
|
+
const signal = options.signal;
|
|
130
131
|
const lockPath = `${path}.lock`;
|
|
131
132
|
const ownerId = `${process.pid}:${randomUUID()}`;
|
|
132
133
|
const deadline = Date.now() + timeoutMs;
|
|
133
134
|
const observedProcessStarts = new Map();
|
|
134
135
|
const directory = dirname(path);
|
|
136
|
+
throwIfJsonFileLockAborted(signal, path, lockPath);
|
|
135
137
|
ensurePrivateDirectory(directory, { durableBoundary: directory });
|
|
136
138
|
|
|
137
139
|
while (!tryAcquireJsonFileLock(lockPath, ownerId)) {
|
|
140
|
+
throwIfJsonFileLockAborted(signal, path, lockPath);
|
|
138
141
|
if (removeStaleJsonFileLock(lockPath, staleMs, observedProcessStarts)) continue;
|
|
139
142
|
const remainingMs = deadline - Date.now();
|
|
140
143
|
if (remainingMs <= 0) {
|
|
@@ -144,10 +147,11 @@ export async function withJsonFileLockAsync(path, operation, options = {}) {
|
|
|
144
147
|
error.lockPath = lockPath;
|
|
145
148
|
throw error;
|
|
146
149
|
}
|
|
147
|
-
await sleep(Math.min(retryMs, remainingMs));
|
|
150
|
+
await sleep(Math.min(retryMs, remainingMs), signal, path, lockPath);
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
try {
|
|
154
|
+
throwIfJsonFileLockAborted(signal, path, lockPath);
|
|
151
155
|
return await operation();
|
|
152
156
|
} finally {
|
|
153
157
|
releaseJsonFileLock(lockPath, ownerId);
|
|
@@ -381,8 +385,37 @@ function sleepSync(milliseconds) {
|
|
|
381
385
|
Atomics.wait(LOCK_SLEEP_BUFFER, 0, 0, Math.max(1, Math.trunc(milliseconds)));
|
|
382
386
|
}
|
|
383
387
|
|
|
384
|
-
function sleep(milliseconds) {
|
|
385
|
-
|
|
388
|
+
function sleep(milliseconds, signal, path, lockPath) {
|
|
389
|
+
const delayMs = Math.max(1, Math.trunc(milliseconds));
|
|
390
|
+
if (!signal) return new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
391
|
+
throwIfJsonFileLockAborted(signal, path, lockPath);
|
|
392
|
+
return new Promise((resolve, reject) => {
|
|
393
|
+
let settled = false;
|
|
394
|
+
const finish = (error) => {
|
|
395
|
+
if (settled) return;
|
|
396
|
+
settled = true;
|
|
397
|
+
clearTimeout(timeout);
|
|
398
|
+
signal.removeEventListener?.("abort", onAbort);
|
|
399
|
+
if (error) reject(error);
|
|
400
|
+
else resolve();
|
|
401
|
+
};
|
|
402
|
+
const timeout = setTimeout(() => finish(), delayMs);
|
|
403
|
+
const onAbort = () => finish(jsonFileLockAbortError(path, lockPath));
|
|
404
|
+
signal.addEventListener?.("abort", onAbort, { once: true });
|
|
405
|
+
if (signal.aborted) onAbort();
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function throwIfJsonFileLockAborted(signal, path, lockPath) {
|
|
410
|
+
if (signal?.aborted) throw jsonFileLockAbortError(path, lockPath);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function jsonFileLockAbortError(path, lockPath) {
|
|
414
|
+
const error = new Error(`aborted while waiting for JSON state lock: ${lockPath}`);
|
|
415
|
+
error.code = "JSON_FILE_LOCK_ABORTED";
|
|
416
|
+
error.path = path;
|
|
417
|
+
error.lockPath = lockPath;
|
|
418
|
+
return error;
|
|
386
419
|
}
|
|
387
420
|
|
|
388
421
|
function positiveInteger(value, fallback) {
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_SECURE_COMMAND_OUTPUT_BYTES,
|
|
3
|
+
DEFAULT_SECURE_COMMAND_TIMEOUT_MS,
|
|
4
|
+
decodeSecureCredential,
|
|
5
|
+
encodeSecureCredential,
|
|
6
|
+
executeSecureCommand,
|
|
7
|
+
minimalEnvironment,
|
|
8
|
+
runSecureCommand,
|
|
9
|
+
secureCredentialBackendError,
|
|
10
|
+
validateSecureCredentialNamespace,
|
|
11
|
+
wipeSecureCommandResult,
|
|
12
|
+
} from "./secure-command.mjs";
|
|
13
|
+
|
|
14
|
+
const BACKEND = "linux-secret-service";
|
|
15
|
+
const SECRET_TOOL = "/usr/bin/secret-tool";
|
|
16
|
+
const LINUX_ENVIRONMENT_NAMES = Object.freeze([
|
|
17
|
+
"DBUS_SESSION_BUS_ADDRESS",
|
|
18
|
+
"XDG_RUNTIME_DIR",
|
|
19
|
+
"DISPLAY",
|
|
20
|
+
"WAYLAND_DISPLAY",
|
|
21
|
+
"HOME",
|
|
22
|
+
"LANG",
|
|
23
|
+
"LC_ALL",
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
export function createLinuxSecretServiceBackend(options) {
|
|
27
|
+
const namespace = validateSecureCredentialNamespace(options?.namespace, BACKEND);
|
|
28
|
+
const runner = options?.runner ?? runSecureCommand;
|
|
29
|
+
const environment = minimalEnvironment(options?.environment ?? process.env, LINUX_ENVIRONMENT_NAMES);
|
|
30
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_SECURE_COMMAND_TIMEOUT_MS;
|
|
31
|
+
const maxOutputBytes = options?.maxOutputBytes ?? DEFAULT_SECURE_COMMAND_OUTPUT_BYTES;
|
|
32
|
+
const attributes = Object.freeze([
|
|
33
|
+
"application",
|
|
34
|
+
"memorax-code",
|
|
35
|
+
"record",
|
|
36
|
+
"trial-credentials",
|
|
37
|
+
"namespace",
|
|
38
|
+
namespace,
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
async function invoke(operation, args, input = undefined) {
|
|
42
|
+
return executeSecureCommand(runner, {
|
|
43
|
+
backend: BACKEND,
|
|
44
|
+
operation,
|
|
45
|
+
command: SECRET_TOOL,
|
|
46
|
+
args,
|
|
47
|
+
input,
|
|
48
|
+
env: environment,
|
|
49
|
+
timeoutMs,
|
|
50
|
+
maxOutputBytes,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
async load() {
|
|
56
|
+
const result = await invoke("load", ["lookup", "--", ...attributes]);
|
|
57
|
+
try {
|
|
58
|
+
if (result.status === 1
|
|
59
|
+
&& result.stdout.length === 0
|
|
60
|
+
&& result.stderr.length === 0) return null;
|
|
61
|
+
if (result.status !== 0 || result.signal !== null) {
|
|
62
|
+
throw secureCredentialBackendError(BACKEND, "load", "command_failed");
|
|
63
|
+
}
|
|
64
|
+
return decodeSecureCredential(result.stdout, BACKEND, "load");
|
|
65
|
+
} finally {
|
|
66
|
+
wipeSecureCommandResult(result);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async save(serialized) {
|
|
71
|
+
const input = encodeSecureCredential(serialized, BACKEND, "save");
|
|
72
|
+
try {
|
|
73
|
+
const result = await invoke(
|
|
74
|
+
"save",
|
|
75
|
+
[
|
|
76
|
+
"store",
|
|
77
|
+
"--label=MemoraX Code trial credentials",
|
|
78
|
+
"--",
|
|
79
|
+
...attributes,
|
|
80
|
+
],
|
|
81
|
+
input,
|
|
82
|
+
);
|
|
83
|
+
try {
|
|
84
|
+
if (result.status !== 0 || result.signal !== null) {
|
|
85
|
+
throw secureCredentialBackendError(BACKEND, "save", "command_failed");
|
|
86
|
+
}
|
|
87
|
+
} finally {
|
|
88
|
+
wipeSecureCommandResult(result);
|
|
89
|
+
}
|
|
90
|
+
} finally {
|
|
91
|
+
input.fill(0);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
async delete() {
|
|
96
|
+
const result = await invoke("delete", ["clear", "--", ...attributes]);
|
|
97
|
+
try {
|
|
98
|
+
if (result.status === 1
|
|
99
|
+
&& result.stdout.length === 0
|
|
100
|
+
&& result.stderr.length === 0) return false;
|
|
101
|
+
if (result.status !== 0 || result.signal !== null) {
|
|
102
|
+
throw secureCredentialBackendError(BACKEND, "delete", "command_failed");
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
} finally {
|
|
106
|
+
wipeSecureCommandResult(result);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_SECURE_COMMAND_OUTPUT_BYTES,
|
|
3
|
+
DEFAULT_SECURE_COMMAND_TIMEOUT_MS,
|
|
4
|
+
decodeSecureCredential,
|
|
5
|
+
encodeSecureCredential,
|
|
6
|
+
executeSecureCommand,
|
|
7
|
+
minimalEnvironment,
|
|
8
|
+
runSecureCommand,
|
|
9
|
+
secureCredentialBackendError,
|
|
10
|
+
validateSecureCredentialNamespace,
|
|
11
|
+
wipeSecureCommandResult,
|
|
12
|
+
} from "./secure-command.mjs";
|
|
13
|
+
|
|
14
|
+
const BACKEND = "macos-keychain";
|
|
15
|
+
const OSASCRIPT = "/usr/bin/osascript";
|
|
16
|
+
const KEYCHAIN_SERVICE = "net.memorax.memorax-code.trial";
|
|
17
|
+
const ITEM_NOT_FOUND_EXIT = 44;
|
|
18
|
+
const INVALID_SECRET_EXIT = 65;
|
|
19
|
+
const MACOS_ENVIRONMENT_NAMES = Object.freeze([
|
|
20
|
+
"HOME",
|
|
21
|
+
"LANG",
|
|
22
|
+
"LC_ALL",
|
|
23
|
+
"LOGNAME",
|
|
24
|
+
"TMPDIR",
|
|
25
|
+
"USER",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const JXA_PREFIX = String.raw`
|
|
29
|
+
ObjC.import("Foundation");
|
|
30
|
+
ObjC.import("Security");
|
|
31
|
+
ObjC.bindFunction("exit", ["void", ["int"]]);
|
|
32
|
+
const errSecItemNotFound = -25300;
|
|
33
|
+
const errSecDuplicateItem = -25299;
|
|
34
|
+
const maxSecretBytes = 4096;
|
|
35
|
+
function quit(code) { $.exit(code); }
|
|
36
|
+
function utf8Length(value) {
|
|
37
|
+
return Number(value.lengthOfBytesUsingEncoding($.NSUTF8StringEncoding));
|
|
38
|
+
}
|
|
39
|
+
function itemIdentity(argv) {
|
|
40
|
+
if (!argv || argv.length !== 2) quit(70);
|
|
41
|
+
const service = $(argv[0]);
|
|
42
|
+
const account = $(argv[1]);
|
|
43
|
+
if (utf8Length(service) < 1 || utf8Length(account) < 1) quit(70);
|
|
44
|
+
return { service, account };
|
|
45
|
+
}
|
|
46
|
+
function findItem(identity, passwordLength, passwordData, item) {
|
|
47
|
+
return $.SecKeychainFindGenericPassword(
|
|
48
|
+
$.nil,
|
|
49
|
+
utf8Length(identity.service),
|
|
50
|
+
identity.service.UTF8String,
|
|
51
|
+
utf8Length(identity.account),
|
|
52
|
+
identity.account.UTF8String,
|
|
53
|
+
passwordLength,
|
|
54
|
+
passwordData,
|
|
55
|
+
item
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
59
|
+
|
|
60
|
+
const LOAD_SCRIPT = `${JXA_PREFIX}
|
|
61
|
+
function run(argv) {
|
|
62
|
+
const identity = itemIdentity(argv);
|
|
63
|
+
const passwordLength = Ref();
|
|
64
|
+
const passwordData = Ref();
|
|
65
|
+
const item = Ref();
|
|
66
|
+
const code = findItem(identity, passwordLength, passwordData, item);
|
|
67
|
+
if (code === errSecItemNotFound) quit(${ITEM_NOT_FOUND_EXIT});
|
|
68
|
+
if (code !== 0) quit(70);
|
|
69
|
+
let invalidSecret = false;
|
|
70
|
+
try {
|
|
71
|
+
const length = Number(passwordLength[0]);
|
|
72
|
+
if (length < 1 || length > maxSecretBytes) {
|
|
73
|
+
invalidSecret = true;
|
|
74
|
+
} else {
|
|
75
|
+
const secret = $.NSData.alloc.initWithBytesLength(passwordData[0], length);
|
|
76
|
+
$.NSFileHandle.fileHandleWithStandardOutput.writeData(secret);
|
|
77
|
+
}
|
|
78
|
+
} finally {
|
|
79
|
+
$.SecKeychainItemFreeContent($.nil, passwordData[0]);
|
|
80
|
+
}
|
|
81
|
+
if (invalidSecret) quit(${INVALID_SECRET_EXIT});
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
const SAVE_SCRIPT = `${JXA_PREFIX}
|
|
86
|
+
function run(argv) {
|
|
87
|
+
const identity = itemIdentity(argv);
|
|
88
|
+
const secret = $.NSFileHandle.fileHandleWithStandardInput.readDataToEndOfFile;
|
|
89
|
+
const length = Number(secret.length);
|
|
90
|
+
if (length < 1 || length > maxSecretBytes) quit(${INVALID_SECRET_EXIT});
|
|
91
|
+
const item = Ref();
|
|
92
|
+
let code = findItem(identity, null, null, item);
|
|
93
|
+
if (code === 0) {
|
|
94
|
+
code = $.SecKeychainItemModifyAttributesAndData(
|
|
95
|
+
item[0],
|
|
96
|
+
$.nil,
|
|
97
|
+
length,
|
|
98
|
+
secret.bytes
|
|
99
|
+
);
|
|
100
|
+
} else if (code === errSecItemNotFound) {
|
|
101
|
+
code = $.SecKeychainAddGenericPassword(
|
|
102
|
+
$.nil,
|
|
103
|
+
utf8Length(identity.service),
|
|
104
|
+
identity.service.UTF8String,
|
|
105
|
+
utf8Length(identity.account),
|
|
106
|
+
identity.account.UTF8String,
|
|
107
|
+
length,
|
|
108
|
+
secret.bytes,
|
|
109
|
+
item
|
|
110
|
+
);
|
|
111
|
+
if (code === errSecDuplicateItem) {
|
|
112
|
+
code = findItem(identity, null, null, item);
|
|
113
|
+
if (code === 0) {
|
|
114
|
+
code = $.SecKeychainItemModifyAttributesAndData(
|
|
115
|
+
item[0],
|
|
116
|
+
$.nil,
|
|
117
|
+
length,
|
|
118
|
+
secret.bytes
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (code !== 0) quit(70);
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
const DELETE_SCRIPT = `${JXA_PREFIX}
|
|
128
|
+
function run(argv) {
|
|
129
|
+
const identity = itemIdentity(argv);
|
|
130
|
+
const item = Ref();
|
|
131
|
+
const found = findItem(identity, null, null, item);
|
|
132
|
+
if (found === errSecItemNotFound) quit(${ITEM_NOT_FOUND_EXIT});
|
|
133
|
+
if (found !== 0) quit(70);
|
|
134
|
+
const deleted = $.SecKeychainItemDelete(item[0]);
|
|
135
|
+
if (deleted !== 0) quit(70);
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
|
|
139
|
+
export function createMacosKeychainBackend(options) {
|
|
140
|
+
const namespace = validateSecureCredentialNamespace(options?.namespace, BACKEND);
|
|
141
|
+
const runner = options?.runner ?? runSecureCommand;
|
|
142
|
+
const environment = minimalEnvironment(
|
|
143
|
+
options?.environment ?? process.env,
|
|
144
|
+
MACOS_ENVIRONMENT_NAMES,
|
|
145
|
+
);
|
|
146
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_SECURE_COMMAND_TIMEOUT_MS;
|
|
147
|
+
const maxOutputBytes = options?.maxOutputBytes ?? DEFAULT_SECURE_COMMAND_OUTPUT_BYTES;
|
|
148
|
+
const account = `credential-v1:${namespace}`;
|
|
149
|
+
|
|
150
|
+
async function invoke(operation, script, input = undefined) {
|
|
151
|
+
return executeSecureCommand(runner, {
|
|
152
|
+
backend: BACKEND,
|
|
153
|
+
operation,
|
|
154
|
+
command: OSASCRIPT,
|
|
155
|
+
args: ["-l", "JavaScript", "-e", script, "--", KEYCHAIN_SERVICE, account],
|
|
156
|
+
input,
|
|
157
|
+
env: environment,
|
|
158
|
+
timeoutMs,
|
|
159
|
+
maxOutputBytes,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function load() {
|
|
164
|
+
const result = await invoke("load", LOAD_SCRIPT);
|
|
165
|
+
try {
|
|
166
|
+
if (isEmptyExit(result, ITEM_NOT_FOUND_EXIT)) return null;
|
|
167
|
+
if (result.status === INVALID_SECRET_EXIT) {
|
|
168
|
+
throw secureCredentialBackendError(BACKEND, "load", "invalid_response");
|
|
169
|
+
}
|
|
170
|
+
if (result.status !== 0 || result.signal !== null || result.stderr.length !== 0) {
|
|
171
|
+
throw secureCredentialBackendError(BACKEND, "load", "command_failed");
|
|
172
|
+
}
|
|
173
|
+
return decodeSecureCredential(result.stdout, BACKEND, "load");
|
|
174
|
+
} finally {
|
|
175
|
+
wipeSecureCommandResult(result);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return Object.freeze({
|
|
180
|
+
load,
|
|
181
|
+
|
|
182
|
+
async save(serialized) {
|
|
183
|
+
const input = encodeSecureCredential(serialized, BACKEND, "save");
|
|
184
|
+
try {
|
|
185
|
+
const result = await invoke("save", SAVE_SCRIPT, input);
|
|
186
|
+
try {
|
|
187
|
+
if (result.status !== 0
|
|
188
|
+
|| result.signal !== null
|
|
189
|
+
|| result.stdout.length !== 0
|
|
190
|
+
|| result.stderr.length !== 0) {
|
|
191
|
+
throw secureCredentialBackendError(BACKEND, "save", "command_failed");
|
|
192
|
+
}
|
|
193
|
+
} finally {
|
|
194
|
+
wipeSecureCommandResult(result);
|
|
195
|
+
}
|
|
196
|
+
} finally {
|
|
197
|
+
input.fill(0);
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
async delete() {
|
|
202
|
+
const result = await invoke("delete", DELETE_SCRIPT);
|
|
203
|
+
try {
|
|
204
|
+
if (isEmptyExit(result, ITEM_NOT_FOUND_EXIT)) return false;
|
|
205
|
+
if (result.status !== 0
|
|
206
|
+
|| result.signal !== null
|
|
207
|
+
|| result.stdout.length !== 0
|
|
208
|
+
|| result.stderr.length !== 0) {
|
|
209
|
+
throw secureCredentialBackendError(BACKEND, "delete", "command_failed");
|
|
210
|
+
}
|
|
211
|
+
} finally {
|
|
212
|
+
wipeSecureCommandResult(result);
|
|
213
|
+
}
|
|
214
|
+
return true;
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function isEmptyExit(result, status) {
|
|
220
|
+
return result.status === status
|
|
221
|
+
&& result.signal === null
|
|
222
|
+
&& result.stdout.length === 0
|
|
223
|
+
&& result.stderr.length === 0;
|
|
224
|
+
}
|