youmd 0.10.2 → 0.12.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.
- package/dist/commands/brain.d.ts.map +1 -1
- package/dist/commands/brain.js +91 -28
- package/dist/commands/brain.js.map +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +6 -1
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/conversations.d.ts +5 -0
- package/dist/commands/conversations.d.ts.map +1 -0
- package/dist/commands/conversations.js +116 -0
- package/dist/commands/conversations.js.map +1 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +16 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/founder.d.ts +13 -0
- package/dist/commands/founder.d.ts.map +1 -0
- package/dist/commands/founder.js +189 -0
- package/dist/commands/founder.js.map +1 -0
- package/dist/commands/handoff.d.ts +5 -0
- package/dist/commands/handoff.d.ts.map +1 -0
- package/dist/commands/handoff.js +432 -0
- package/dist/commands/handoff.js.map +1 -0
- package/dist/commands/orchestrate.d.ts +15 -0
- package/dist/commands/orchestrate.d.ts.map +1 -1
- package/dist/commands/orchestrate.js +294 -0
- package/dist/commands/orchestrate.js.map +1 -1
- package/dist/commands/project.d.ts.map +1 -1
- package/dist/commands/project.js +8 -1
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/reconcile.d.ts +15 -0
- package/dist/commands/reconcile.d.ts.map +1 -0
- package/dist/commands/reconcile.js +260 -0
- package/dist/commands/reconcile.js.map +1 -0
- package/dist/commands/session.d.ts +28 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +895 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/stack.d.ts +2 -0
- package/dist/commands/stack.d.ts.map +1 -1
- package/dist/commands/stack.js +26 -0
- package/dist/commands/stack.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +11 -0
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/tasks.d.ts +57 -0
- package/dist/commands/tasks.d.ts.map +1 -1
- package/dist/commands/tasks.js +236 -31
- package/dist/commands/tasks.js.map +1 -1
- package/dist/generated/capability-registry.d.ts +311 -71
- package/dist/generated/capability-registry.d.ts.map +1 -1
- package/dist/generated/capability-registry.js +346 -71
- package/dist/generated/capability-registry.js.map +1 -1
- package/dist/index.js +113 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +362 -1
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +171 -0
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/brain/adapters.d.ts +20 -1
- package/dist/lib/brain/adapters.d.ts.map +1 -1
- package/dist/lib/brain/adapters.js +113 -2
- package/dist/lib/brain/adapters.js.map +1 -1
- package/dist/lib/brain/approval-key-trust.d.ts +29 -0
- package/dist/lib/brain/approval-key-trust.d.ts.map +1 -0
- package/dist/lib/brain/approval-key-trust.js +139 -0
- package/dist/lib/brain/approval-key-trust.js.map +1 -0
- package/dist/lib/brain/cache.d.ts +2 -2
- package/dist/lib/brain/cache.d.ts.map +1 -1
- package/dist/lib/brain/cache.js +4 -4
- package/dist/lib/brain/cache.js.map +1 -1
- package/dist/lib/brain/content.d.ts +56 -0
- package/dist/lib/brain/content.d.ts.map +1 -0
- package/dist/lib/brain/content.js +362 -0
- package/dist/lib/brain/content.js.map +1 -0
- package/dist/lib/brain/descriptor-validator.d.ts +11 -0
- package/dist/lib/brain/descriptor-validator.d.ts.map +1 -0
- package/dist/lib/brain/descriptor-validator.js +185 -0
- package/dist/lib/brain/descriptor-validator.js.map +1 -0
- package/dist/lib/brain/discovery.d.ts.map +1 -1
- package/dist/lib/brain/discovery.js +103 -60
- package/dist/lib/brain/discovery.js.map +1 -1
- package/dist/lib/brain/index.d.ts +2 -0
- package/dist/lib/brain/index.d.ts.map +1 -1
- package/dist/lib/brain/index.js +8 -2
- package/dist/lib/brain/index.js.map +1 -1
- package/dist/lib/brain/proposal.d.ts +53 -5
- package/dist/lib/brain/proposal.d.ts.map +1 -1
- package/dist/lib/brain/proposal.js +341 -64
- package/dist/lib/brain/proposal.js.map +1 -1
- package/dist/lib/brain/safe-file.d.ts +25 -1
- package/dist/lib/brain/safe-file.d.ts.map +1 -1
- package/dist/lib/brain/safe-file.js +250 -30
- package/dist/lib/brain/safe-file.js.map +1 -1
- package/dist/lib/brain/safe-open.d.ts +2 -0
- package/dist/lib/brain/safe-open.d.ts.map +1 -0
- package/dist/lib/brain/safe-open.js +77 -0
- package/dist/lib/brain/safe-open.js.map +1 -0
- package/dist/lib/brain/safety.d.ts.map +1 -1
- package/dist/lib/brain/safety.js +5 -0
- package/dist/lib/brain/safety.js.map +1 -1
- package/dist/lib/brain/staged-apply.d.ts +2 -1
- package/dist/lib/brain/staged-apply.d.ts.map +1 -1
- package/dist/lib/brain/staged-apply.js +6 -3
- package/dist/lib/brain/staged-apply.js.map +1 -1
- package/dist/lib/brain/store.d.ts +3 -0
- package/dist/lib/brain/store.d.ts.map +1 -1
- package/dist/lib/brain/store.js +68 -0
- package/dist/lib/brain/store.js.map +1 -1
- package/dist/lib/brain/sync.d.ts +87 -0
- package/dist/lib/brain/sync.d.ts.map +1 -0
- package/dist/lib/brain/sync.js +238 -0
- package/dist/lib/brain/sync.js.map +1 -0
- package/dist/lib/brain/types.d.ts +23 -0
- package/dist/lib/brain/types.d.ts.map +1 -1
- package/dist/lib/config.d.ts +7 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +3 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/daemon.js +1 -1
- package/dist/lib/daemon.js.map +1 -1
- package/dist/lib/goalRegistryFile.d.ts +169 -0
- package/dist/lib/goalRegistryFile.d.ts.map +1 -0
- package/dist/lib/goalRegistryFile.js +518 -0
- package/dist/lib/goalRegistryFile.js.map +1 -0
- package/dist/lib/goalsFile.d.ts +101 -0
- package/dist/lib/goalsFile.d.ts.map +1 -0
- package/dist/lib/goalsFile.js +413 -0
- package/dist/lib/goalsFile.js.map +1 -0
- package/dist/lib/handoff.d.ts +170 -0
- package/dist/lib/handoff.d.ts.map +1 -0
- package/dist/lib/handoff.js +316 -0
- package/dist/lib/handoff.js.map +1 -0
- package/dist/lib/machine-identity.d.ts +2 -0
- package/dist/lib/machine-identity.d.ts.map +1 -1
- package/dist/lib/machine-identity.js +13 -6
- package/dist/lib/machine-identity.js.map +1 -1
- package/dist/lib/onboarding.d.ts +4 -1
- package/dist/lib/onboarding.d.ts.map +1 -1
- package/dist/lib/onboarding.js +11 -3
- package/dist/lib/onboarding.js.map +1 -1
- package/dist/lib/orchestrator/loop.d.ts +7 -0
- package/dist/lib/orchestrator/loop.d.ts.map +1 -1
- package/dist/lib/orchestrator/loop.js +53 -0
- package/dist/lib/orchestrator/loop.js.map +1 -1
- package/dist/lib/orchestrator/run-journal.d.ts +178 -0
- package/dist/lib/orchestrator/run-journal.d.ts.map +1 -0
- package/dist/lib/orchestrator/run-journal.js +912 -0
- package/dist/lib/orchestrator/run-journal.js.map +1 -0
- package/dist/lib/orchestrator/supervisor.d.ts +11 -0
- package/dist/lib/orchestrator/supervisor.d.ts.map +1 -1
- package/dist/lib/orchestrator/supervisor.js +304 -116
- package/dist/lib/orchestrator/supervisor.js.map +1 -1
- package/dist/lib/orchestrator/tools.d.ts +2 -0
- package/dist/lib/orchestrator/tools.d.ts.map +1 -1
- package/dist/lib/orchestrator/tools.js +214 -1
- package/dist/lib/orchestrator/tools.js.map +1 -1
- package/dist/lib/redact.d.ts.map +1 -1
- package/dist/lib/redact.js +31 -6
- package/dist/lib/redact.js.map +1 -1
- package/dist/lib/remote-agent-event.d.ts +19 -0
- package/dist/lib/remote-agent-event.d.ts.map +1 -0
- package/dist/lib/remote-agent-event.js +85 -0
- package/dist/lib/remote-agent-event.js.map +1 -0
- package/dist/lib/remote-command.d.ts.map +1 -1
- package/dist/lib/remote-command.js +19 -0
- package/dist/lib/remote-command.js.map +1 -1
- package/dist/lib/remote-executor.d.ts +7 -1
- package/dist/lib/remote-executor.d.ts.map +1 -1
- package/dist/lib/remote-executor.js +391 -0
- package/dist/lib/remote-executor.js.map +1 -1
- package/dist/lib/sessionCheckpoint.d.ts +104 -0
- package/dist/lib/sessionCheckpoint.d.ts.map +1 -0
- package/dist/lib/sessionCheckpoint.js +191 -0
- package/dist/lib/sessionCheckpoint.js.map +1 -0
- package/dist/lib/sessionHandoff.d.ts +47 -0
- package/dist/lib/sessionHandoff.d.ts.map +1 -0
- package/dist/lib/sessionHandoff.js +305 -0
- package/dist/lib/sessionHandoff.js.map +1 -0
- package/dist/lib/stream.d.ts +3 -1
- package/dist/lib/stream.d.ts.map +1 -1
- package/dist/lib/stream.js +8 -3
- package/dist/lib/stream.js.map +1 -1
- package/dist/lib/taskLedgerProposal.d.ts +102 -0
- package/dist/lib/taskLedgerProposal.d.ts.map +1 -0
- package/dist/lib/taskLedgerProposal.js +408 -0
- package/dist/lib/taskLedgerProposal.js.map +1 -0
- package/dist/lib/tasksFile.d.ts +23 -2
- package/dist/lib/tasksFile.d.ts.map +1 -1
- package/dist/lib/tasksFile.js +122 -10
- package/dist/lib/tasksFile.js.map +1 -1
- package/dist/mcp/registry.d.ts.map +1 -1
- package/dist/mcp/registry.js +110 -0
- package/dist/mcp/registry.js.map +1 -1
- package/native/prebuilds/darwin-universal/youmd_safe_open.node +0 -0
- package/native/safe-open/youmd_safe_open.c +224 -0
- package/package.json +11 -3
- package/scripts/build-safe-open.mjs +63 -0
- package/scripts/skillstack-sync/com.you.context-sync.plist +1 -1
- package/scripts/skillstack-sync/com.you.identity-sync.plist +1 -1
- package/scripts/skillstack-sync/com.you.orchestrator-watch.plist +1 -1
- package/scripts/skillstack-sync/com.you.realtime-sync.plist +7 -1
- package/scripts/skillstack-sync/com.you.skillstack-sync.plist +1 -1
- package/scripts/skillstack-sync/context-sync.sh +19 -7
- package/scripts/skillstack-sync/install-daemons-linux.sh +6 -0
- package/scripts/smoke-safe-open-node.cjs +50 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#define NAPI_VERSION 8
|
|
2
|
+
#include <node_api.h>
|
|
3
|
+
|
|
4
|
+
#include <errno.h>
|
|
5
|
+
#include <fcntl.h>
|
|
6
|
+
#include <limits.h>
|
|
7
|
+
#include <stdint.h>
|
|
8
|
+
#include <stdlib.h>
|
|
9
|
+
#include <string.h>
|
|
10
|
+
#include <sys/stat.h>
|
|
11
|
+
#include <unistd.h>
|
|
12
|
+
|
|
13
|
+
#ifndef O_CLOEXEC
|
|
14
|
+
#define O_CLOEXEC 0
|
|
15
|
+
#endif
|
|
16
|
+
|
|
17
|
+
static napi_value fail(napi_env env, const char *reason) {
|
|
18
|
+
napi_throw_error(env, reason, reason);
|
|
19
|
+
return NULL;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static const char *errno_reason(int value) {
|
|
23
|
+
if (value == ELOOP) return "symlink_rejected";
|
|
24
|
+
if (value == ENOTDIR || value == EINVAL || value == ENAMETOOLONG) return "unsafe_path";
|
|
25
|
+
if (value == ENOENT || value == ESTALE) return "stale_claim";
|
|
26
|
+
return "unreadable";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
static char *get_string(napi_env env, napi_value value, size_t maximum) {
|
|
30
|
+
size_t length = 0;
|
|
31
|
+
if (napi_get_value_string_utf8(env, value, NULL, 0, &length) != napi_ok || length > maximum) {
|
|
32
|
+
fail(env, "unsafe_path");
|
|
33
|
+
return NULL;
|
|
34
|
+
}
|
|
35
|
+
char *result = malloc(length + 1);
|
|
36
|
+
if (result == NULL) {
|
|
37
|
+
fail(env, "unreadable");
|
|
38
|
+
return NULL;
|
|
39
|
+
}
|
|
40
|
+
if (napi_get_value_string_utf8(env, value, result, length + 1, &length) != napi_ok) {
|
|
41
|
+
free(result);
|
|
42
|
+
fail(env, "unsafe_path");
|
|
43
|
+
return NULL;
|
|
44
|
+
}
|
|
45
|
+
if (memchr(result, '\0', length) != NULL) {
|
|
46
|
+
free(result);
|
|
47
|
+
fail(env, "unsafe_path");
|
|
48
|
+
return NULL;
|
|
49
|
+
}
|
|
50
|
+
result[length] = '\0';
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static int get_u64(napi_env env, napi_value value, uint64_t *result) {
|
|
55
|
+
char *text = get_string(env, value, 32);
|
|
56
|
+
if (text == NULL) return 0;
|
|
57
|
+
char *end = NULL;
|
|
58
|
+
errno = 0;
|
|
59
|
+
unsigned long long parsed = strtoull(text, &end, 10);
|
|
60
|
+
int valid = errno == 0 && end != text && *end == '\0';
|
|
61
|
+
free(text);
|
|
62
|
+
if (!valid) {
|
|
63
|
+
fail(env, "unsafe_path");
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
*result = (uint64_t)parsed;
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static int invoke_hook(napi_env env, napi_value hook, const char *stage, uint32_t index, const char *segment) {
|
|
71
|
+
napi_valuetype type;
|
|
72
|
+
if (hook == NULL || napi_typeof(env, hook, &type) != napi_ok || type != napi_function) return 1;
|
|
73
|
+
napi_value global, args[3], ignored;
|
|
74
|
+
if (napi_get_global(env, &global) != napi_ok ||
|
|
75
|
+
napi_create_string_utf8(env, stage, NAPI_AUTO_LENGTH, &args[0]) != napi_ok ||
|
|
76
|
+
napi_create_uint32(env, index, &args[1]) != napi_ok ||
|
|
77
|
+
napi_create_string_utf8(env, segment == NULL ? "" : segment, NAPI_AUTO_LENGTH, &args[2]) != napi_ok) {
|
|
78
|
+
fail(env, "unreadable");
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
return napi_call_function(env, global, hook, 3, args, &ignored) == napi_ok;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static int unchanged(const struct stat *before, const struct stat *after) {
|
|
85
|
+
return before->st_dev == after->st_dev && before->st_ino == after->st_ino && before->st_size == after->st_size &&
|
|
86
|
+
#if defined(__APPLE__)
|
|
87
|
+
before->st_mtimespec.tv_sec == after->st_mtimespec.tv_sec && before->st_mtimespec.tv_nsec == after->st_mtimespec.tv_nsec &&
|
|
88
|
+
before->st_ctimespec.tv_sec == after->st_ctimespec.tv_sec && before->st_ctimespec.tv_nsec == after->st_ctimespec.tv_nsec;
|
|
89
|
+
#else
|
|
90
|
+
before->st_mtim.tv_sec == after->st_mtim.tv_sec && before->st_mtim.tv_nsec == after->st_mtim.tv_nsec &&
|
|
91
|
+
before->st_ctim.tv_sec == after->st_ctim.tv_sec && before->st_ctim.tv_nsec == after->st_ctim.tv_nsec;
|
|
92
|
+
#endif
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
static napi_value safe_read(napi_env env, napi_callback_info info) {
|
|
96
|
+
size_t argc = 6;
|
|
97
|
+
napi_value argv[6];
|
|
98
|
+
if (napi_get_cb_info(env, info, &argc, argv, NULL, NULL) != napi_ok || argc < 5) return fail(env, "unsafe_path");
|
|
99
|
+
|
|
100
|
+
char *root = get_string(env, argv[0], PATH_MAX - 1);
|
|
101
|
+
if (root == NULL) return NULL;
|
|
102
|
+
char *relative = get_string(env, argv[1], PATH_MAX - 1);
|
|
103
|
+
if (relative == NULL) { free(root); return NULL; }
|
|
104
|
+
uint32_t maximum = 0;
|
|
105
|
+
uint64_t expected_dev = 0, expected_ino = 0;
|
|
106
|
+
if (napi_get_value_uint32(env, argv[2], &maximum) != napi_ok || maximum == 0 ||
|
|
107
|
+
!get_u64(env, argv[3], &expected_dev) || !get_u64(env, argv[4], &expected_ino)) {
|
|
108
|
+
free(root); free(relative); return NULL;
|
|
109
|
+
}
|
|
110
|
+
napi_value hook = argc >= 6 ? argv[5] : NULL;
|
|
111
|
+
size_t root_length = strlen(root);
|
|
112
|
+
if (root[0] != '/' || (root_length > 1 && root[root_length - 1] == '/') ||
|
|
113
|
+
relative[0] == '\0' || relative[0] == '/' || relative[strlen(relative) - 1] == '/') {
|
|
114
|
+
free(root); free(relative); return fail(env, "unsafe_path");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Anchor at /, then traverse every root component with openat. Opening the
|
|
118
|
+
absolute root pathname directly would still follow symlinks in parents. */
|
|
119
|
+
int directory_fd = open("/", O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
|
|
120
|
+
if (directory_fd < 0) {
|
|
121
|
+
const char *reason = errno_reason(errno); free(root); free(relative); return fail(env, reason);
|
|
122
|
+
}
|
|
123
|
+
char *root_cursor = root + 1;
|
|
124
|
+
uint32_t root_index = 0;
|
|
125
|
+
while (*root_cursor != '\0') {
|
|
126
|
+
char *slash = strchr(root_cursor, '/');
|
|
127
|
+
size_t length = slash == NULL ? strlen(root_cursor) : (size_t)(slash - root_cursor);
|
|
128
|
+
if (length == 0 || length > NAME_MAX || (length == 1 && root_cursor[0] == '.') ||
|
|
129
|
+
(length == 2 && root_cursor[0] == '.' && root_cursor[1] == '.')) {
|
|
130
|
+
close(directory_fd); free(root); free(relative); return fail(env, "unsafe_path");
|
|
131
|
+
}
|
|
132
|
+
char segment[NAME_MAX + 1];
|
|
133
|
+
memcpy(segment, root_cursor, length); segment[length] = '\0';
|
|
134
|
+
if (!invoke_hook(env, hook, "before_root_directory_open", root_index, segment)) {
|
|
135
|
+
close(directory_fd); free(root); free(relative); return NULL;
|
|
136
|
+
}
|
|
137
|
+
int next_fd = openat(directory_fd, segment, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
|
|
138
|
+
if (next_fd < 0) {
|
|
139
|
+
const char *reason = errno_reason(errno); close(directory_fd); free(root); free(relative); return fail(env, reason);
|
|
140
|
+
}
|
|
141
|
+
close(directory_fd); directory_fd = next_fd;
|
|
142
|
+
if (!invoke_hook(env, hook, "after_root_directory_open", root_index, segment)) {
|
|
143
|
+
close(directory_fd); free(root); free(relative); return NULL;
|
|
144
|
+
}
|
|
145
|
+
if (slash == NULL) break;
|
|
146
|
+
root_cursor = slash + 1;
|
|
147
|
+
root_index += 1;
|
|
148
|
+
}
|
|
149
|
+
free(root);
|
|
150
|
+
struct stat root_stat;
|
|
151
|
+
if (fstat(directory_fd, &root_stat) != 0 || !S_ISDIR(root_stat.st_mode)) {
|
|
152
|
+
close(directory_fd); free(relative); return fail(env, "unsafe_path");
|
|
153
|
+
}
|
|
154
|
+
if ((uint64_t)root_stat.st_dev != expected_dev || (uint64_t)root_stat.st_ino != expected_ino) {
|
|
155
|
+
close(directory_fd); free(relative); return fail(env, "stale_claim");
|
|
156
|
+
}
|
|
157
|
+
if (!invoke_hook(env, hook, "after_root_open", 0, "")) { close(directory_fd); free(relative); return NULL; }
|
|
158
|
+
|
|
159
|
+
char *cursor = relative;
|
|
160
|
+
uint32_t index = 0;
|
|
161
|
+
int file_fd = -1;
|
|
162
|
+
while (*cursor != '\0') {
|
|
163
|
+
char *slash = strchr(cursor, '/');
|
|
164
|
+
size_t length = slash == NULL ? strlen(cursor) : (size_t)(slash - cursor);
|
|
165
|
+
if (length == 0 || length > NAME_MAX || (length == 1 && cursor[0] == '.') ||
|
|
166
|
+
(length == 2 && cursor[0] == '.' && cursor[1] == '.')) {
|
|
167
|
+
close(directory_fd); free(relative); return fail(env, "unsafe_path");
|
|
168
|
+
}
|
|
169
|
+
char segment[NAME_MAX + 1];
|
|
170
|
+
memcpy(segment, cursor, length); segment[length] = '\0';
|
|
171
|
+
int final = slash == NULL;
|
|
172
|
+
if (!invoke_hook(env, hook, final ? "before_file_open" : "before_directory_open", index, segment)) {
|
|
173
|
+
close(directory_fd); free(relative); return NULL;
|
|
174
|
+
}
|
|
175
|
+
int next_fd = openat(directory_fd, segment, O_RDONLY | O_NOFOLLOW | O_CLOEXEC |
|
|
176
|
+
(final ? O_NONBLOCK : O_DIRECTORY));
|
|
177
|
+
if (next_fd < 0) {
|
|
178
|
+
const char *reason = errno_reason(errno); close(directory_fd); free(relative); return fail(env, reason);
|
|
179
|
+
}
|
|
180
|
+
if (final) { file_fd = next_fd; break; }
|
|
181
|
+
close(directory_fd); directory_fd = next_fd;
|
|
182
|
+
if (!invoke_hook(env, hook, "after_directory_open", index, segment)) { close(directory_fd); free(relative); return NULL; }
|
|
183
|
+
cursor = slash + 1; index += 1;
|
|
184
|
+
}
|
|
185
|
+
free(relative); close(directory_fd);
|
|
186
|
+
if (file_fd < 0) return fail(env, "unsafe_path");
|
|
187
|
+
|
|
188
|
+
struct stat before;
|
|
189
|
+
if (fstat(file_fd, &before) != 0 || !S_ISREG(before.st_mode)) { close(file_fd); return fail(env, "unsafe_path"); }
|
|
190
|
+
if (before.st_size < 0 || (uint64_t)before.st_size > maximum) { close(file_fd); return fail(env, "budget_exceeded"); }
|
|
191
|
+
size_t size = (size_t)before.st_size;
|
|
192
|
+
unsigned char *bytes = malloc(size == 0 ? 1 : size);
|
|
193
|
+
if (bytes == NULL) { close(file_fd); return fail(env, "unreadable"); }
|
|
194
|
+
size_t offset = 0;
|
|
195
|
+
while (offset < size) {
|
|
196
|
+
ssize_t count = pread(file_fd, bytes + offset, size - offset, (off_t)offset);
|
|
197
|
+
if (count < 0 && errno == EINTR) continue;
|
|
198
|
+
if (count <= 0) { free(bytes); close(file_fd); return fail(env, "unreadable"); }
|
|
199
|
+
offset += (size_t)count;
|
|
200
|
+
}
|
|
201
|
+
unsigned char extra;
|
|
202
|
+
ssize_t extra_count;
|
|
203
|
+
do { extra_count = pread(file_fd, &extra, 1, (off_t)size); } while (extra_count < 0 && errno == EINTR);
|
|
204
|
+
struct stat after;
|
|
205
|
+
int stable = fstat(file_fd, &after) == 0 && extra_count == 0 && unchanged(&before, &after);
|
|
206
|
+
close(file_fd);
|
|
207
|
+
if (!stable) { free(bytes); return fail(env, "stale_claim"); }
|
|
208
|
+
|
|
209
|
+
napi_value result;
|
|
210
|
+
napi_status status = napi_create_buffer_copy(env, size, bytes, NULL, &result);
|
|
211
|
+
free(bytes);
|
|
212
|
+
return status == napi_ok ? result : fail(env, "unreadable");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
static napi_value init(napi_env env, napi_value exports) {
|
|
216
|
+
napi_value function, version;
|
|
217
|
+
if (napi_create_function(env, "safeRead", NAPI_AUTO_LENGTH, safe_read, NULL, &function) != napi_ok ||
|
|
218
|
+
napi_set_named_property(env, exports, "safeRead", function) != napi_ok ||
|
|
219
|
+
napi_create_uint32(env, NAPI_VERSION, &version) != napi_ok ||
|
|
220
|
+
napi_set_named_property(env, exports, "napiVersion", version) != napi_ok) return NULL;
|
|
221
|
+
return exports;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
NAPI_MODULE(youmd_safe_open, init)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "youmd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Identity context protocol for the agent internet — an MCP where the context is you. CLI for the You.md platform.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"you": "dist/you.js",
|
|
@@ -10,17 +10,25 @@
|
|
|
10
10
|
"main": "dist/index.js",
|
|
11
11
|
"files": [
|
|
12
12
|
"dist/**/*",
|
|
13
|
+
"native/**/*",
|
|
13
14
|
"skills/**/*",
|
|
14
15
|
"examples/**/*",
|
|
15
16
|
"scripts/**/*"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
18
19
|
"build": "tsc",
|
|
20
|
+
"build:native": "node scripts/build-safe-open.mjs",
|
|
19
21
|
"dev": "ts-node src/index.ts",
|
|
20
22
|
"start": "node dist/index.js",
|
|
23
|
+
"pretest": "npm run build:native",
|
|
21
24
|
"test": "vitest run",
|
|
25
|
+
"pretest:brain-content": "npm run build:native",
|
|
26
|
+
"test:brain-content": "vitest run src/__tests__/brain-content.test.ts src/__tests__/brain-safe-open-native.test.ts",
|
|
27
|
+
"pretest:brain-content:abi": "npm run build:native",
|
|
28
|
+
"test:brain-content:abi": "node scripts/smoke-safe-open-node.cjs",
|
|
22
29
|
"test:watch": "vitest",
|
|
23
|
-
"
|
|
30
|
+
"prepack": "npm run build:native && npm run build",
|
|
31
|
+
"prepublishOnly": "node scripts/check-version-exists.mjs && npm run build:native && npm run build",
|
|
24
32
|
"postinstall": "node -e \"try{require('./dist/postinstall.js')}catch(e){if(e&&e.code!=='MODULE_NOT_FOUND')throw e}\""
|
|
25
33
|
},
|
|
26
34
|
"keywords": [
|
|
@@ -45,7 +53,7 @@
|
|
|
45
53
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
46
54
|
"chalk": "^4.1.2",
|
|
47
55
|
"commander": "^12.1.0",
|
|
48
|
-
"convex": "1.
|
|
56
|
+
"convex": "1.43.0",
|
|
49
57
|
"gray-matter": "^4.0.3",
|
|
50
58
|
"jimp": "^0.22.12",
|
|
51
59
|
"js-yaml": "^4.1.1",
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const cliRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
+
const outputDirectory = path.join(cliRoot, "native", "prebuilds", "darwin-universal");
|
|
11
|
+
const output = path.join(outputDirectory, "youmd_safe_open.node");
|
|
12
|
+
const source = path.join(cliRoot, "native", "safe-open", "youmd_safe_open.c");
|
|
13
|
+
|
|
14
|
+
if (process.platform !== "darwin") {
|
|
15
|
+
console.error("safe-open: non-darwin build skipped (Linux uses procfd; other runtimes degrade to metadata-only)");
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const includeCandidates = [
|
|
20
|
+
process.env.NODE_INCLUDE_DIR,
|
|
21
|
+
process.env.npm_config_nodedir && path.join(process.env.npm_config_nodedir, "include", "node"),
|
|
22
|
+
path.resolve(path.dirname(process.execPath), "..", "include", "node"),
|
|
23
|
+
"/opt/homebrew/include/node",
|
|
24
|
+
"/usr/local/include/node",
|
|
25
|
+
].filter(Boolean);
|
|
26
|
+
const includeDirectory = includeCandidates.find((candidate) => fs.existsSync(path.join(candidate, "node_api.h")));
|
|
27
|
+
if (!includeDirectory) throw new Error("safe-open: node_api.h not found; set NODE_INCLUDE_DIR");
|
|
28
|
+
|
|
29
|
+
const xcrun = (args) => execFileSync("xcrun", args, { encoding: "utf8" }).trim();
|
|
30
|
+
const clang = xcrun(["--find", "clang"]);
|
|
31
|
+
const sdk = xcrun(["--show-sdk-path"]);
|
|
32
|
+
fs.mkdirSync(outputDirectory, { recursive: true });
|
|
33
|
+
const temporary = `${output}.${process.pid}.tmp`;
|
|
34
|
+
try {
|
|
35
|
+
execFileSync(clang, [
|
|
36
|
+
"-std=c11",
|
|
37
|
+
"-O2",
|
|
38
|
+
"-Wall",
|
|
39
|
+
"-Wextra",
|
|
40
|
+
"-Werror",
|
|
41
|
+
"-fvisibility=hidden",
|
|
42
|
+
"-bundle",
|
|
43
|
+
"-undefined", "dynamic_lookup",
|
|
44
|
+
"-mmacosx-version-min=10.15",
|
|
45
|
+
"-isysroot", sdk,
|
|
46
|
+
"-I", includeDirectory,
|
|
47
|
+
"-arch", "arm64",
|
|
48
|
+
"-arch", "x86_64",
|
|
49
|
+
source,
|
|
50
|
+
"-o", temporary,
|
|
51
|
+
], { stdio: "inherit" });
|
|
52
|
+
execFileSync("codesign", ["--force", "--sign", "-", temporary], { stdio: "inherit" });
|
|
53
|
+
fs.renameSync(temporary, output);
|
|
54
|
+
} finally {
|
|
55
|
+
if (fs.existsSync(temporary)) fs.unlinkSync(temporary);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const lipo = execFileSync("lipo", ["-info", output], { encoding: "utf8" }).trim();
|
|
59
|
+
if (!lipo.includes("x86_64") || !lipo.includes("arm64")) throw new Error(`safe-open: expected universal binary; ${lipo}`);
|
|
60
|
+
const addon = createRequire(import.meta.url)(output);
|
|
61
|
+
if (addon.napiVersion !== 8 || typeof addon.safeRead !== "function") throw new Error("safe-open: invalid Node-API v8 exports");
|
|
62
|
+
// npm pack --json reserves stdout for its JSON document.
|
|
63
|
+
console.error(`safe-open: built ${path.relative(cliRoot, output)} (${lipo})`);
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<key>HOME</key>
|
|
33
33
|
<string>__HOME__</string>
|
|
34
34
|
<key>PATH</key>
|
|
35
|
-
<string
|
|
35
|
+
<string>__HOME__/.you/bin:__HOME__/.youmd/bin:__HOME__/.youmd/npm-global/bin:__HOME__/.npm-global/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
36
36
|
</dict>
|
|
37
37
|
</dict>
|
|
38
38
|
</plist>
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<key>HOME</key>
|
|
33
33
|
<string>__HOME__</string>
|
|
34
34
|
<key>PATH</key>
|
|
35
|
-
<string
|
|
35
|
+
<string>__HOME__/.you/bin:__HOME__/.youmd/bin:__HOME__/.youmd/npm-global/bin:__HOME__/.npm-global/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
36
36
|
</dict>
|
|
37
37
|
</dict>
|
|
38
38
|
</plist>
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<key>HOME</key>
|
|
33
33
|
<string>__HOME__</string>
|
|
34
34
|
<key>PATH</key>
|
|
35
|
-
<string
|
|
35
|
+
<string>__HOME__/.you/bin:__HOME__/.youmd/bin:__HOME__/.youmd/npm-global/bin:__HOME__/.npm-global/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
36
36
|
</dict>
|
|
37
37
|
</dict>
|
|
38
38
|
</plist>
|
|
@@ -29,7 +29,13 @@
|
|
|
29
29
|
<key>HOME</key>
|
|
30
30
|
<string>__HOME__</string>
|
|
31
31
|
<key>PATH</key>
|
|
32
|
-
<string
|
|
32
|
+
<string>__HOME__/.you/bin:__HOME__/.youmd/bin:__HOME__/.youmd/npm-global/bin:__HOME__/.npm-global/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
33
|
+
<!-- Dedicated launchd jobs own these cadences; the realtime worker
|
|
34
|
+
must not spawn duplicate project/stack sync processes. -->
|
|
35
|
+
<key>YOUMD_LIVE_SYNC_STACK</key>
|
|
36
|
+
<string>0</string>
|
|
37
|
+
<key>YOUMD_LIVE_SYNC_CONTEXT</key>
|
|
38
|
+
<string>0</string>
|
|
33
39
|
</dict>
|
|
34
40
|
</dict>
|
|
35
41
|
</plist>
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<key>HOME</key>
|
|
33
33
|
<string>__HOME__</string>
|
|
34
34
|
<key>PATH</key>
|
|
35
|
-
<string
|
|
35
|
+
<string>__HOME__/.you/bin:__HOME__/.youmd/bin:__HOME__/.youmd/npm-global/bin:__HOME__/.npm-global/bin:/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
36
36
|
</dict>
|
|
37
37
|
</dict>
|
|
38
38
|
</plist>
|
|
@@ -33,8 +33,10 @@
|
|
|
33
33
|
# ./context-sync.sh --dry-run # log intended actions only, write nothing
|
|
34
34
|
#
|
|
35
35
|
# Env overrides:
|
|
36
|
-
#
|
|
37
|
-
#
|
|
36
|
+
# CONTEXT_ROOTS space-separated absolute roots to scan. Defaults to the
|
|
37
|
+
# standard CODE_YOU, CODE_2025, and CODE_2026 machine roots.
|
|
38
|
+
# CONTEXT_REPOS space-separated list of repo DIRECTORY NAMES under each
|
|
39
|
+
# context root (not full paths)
|
|
38
40
|
# GIT_USER_NAME commit author name (default: Houston Golden)
|
|
39
41
|
# GIT_USER_EMAIL commit author email (default: houston@bamf.ai)
|
|
40
42
|
|
|
@@ -50,7 +52,9 @@ LOG_FILE="${LOG_DIR}/context-sync.log"
|
|
|
50
52
|
GIT_USER_NAME="${GIT_USER_NAME:-Houston Golden}"
|
|
51
53
|
GIT_USER_EMAIL="${GIT_USER_EMAIL:-houston@bamf.ai}"
|
|
52
54
|
|
|
53
|
-
|
|
55
|
+
DEFAULT_CONTEXT_ROOTS="${HOME}/Desktop/CODE_YOU ${HOME}/Desktop/CODE_2025 ${HOME}/Desktop/CODE_2026"
|
|
56
|
+
CONTEXT_ROOTS="${CONTEXT_ROOTS:-${DEFAULT_CONTEXT_ROOTS}}"
|
|
57
|
+
CODE_ROOT=""
|
|
54
58
|
|
|
55
59
|
# Curated list of Houston's OWN repos with rich agent context.
|
|
56
60
|
# These all have houstongolden/* or hubify-projects/* remotes — confirmed safe
|
|
@@ -60,7 +64,7 @@ CODE_ROOT="${HOME}/Desktop/CODE_2025"
|
|
|
60
64
|
# - disler/*, coffeefuelbump/*, etc. → not Houston's remote
|
|
61
65
|
# - repos with no origin → detected and skipped at runtime
|
|
62
66
|
# - gstack, agent-shared, scistack → handled by sync.sh
|
|
63
|
-
DEFAULT_CONTEXT_REPOS="bamfaiapp bigbounce hubify-aios myo hubifycode badapp claws"
|
|
67
|
+
DEFAULT_CONTEXT_REPOS="youmd bamfaiapp bigbounce hubify-aios myo hubifycode badapp claws"
|
|
64
68
|
CONTEXT_REPOS="${CONTEXT_REPOS:-${DEFAULT_CONTEXT_REPOS}}"
|
|
65
69
|
|
|
66
70
|
# The ONLY paths we will ever stage. Nothing else is touched.
|
|
@@ -395,13 +399,21 @@ main() {
|
|
|
395
399
|
|
|
396
400
|
log "============================================================"
|
|
397
401
|
log "context-sync START [${mode}] on $(hostname_short)"
|
|
402
|
+
log "Roots: ${CONTEXT_ROOTS}"
|
|
398
403
|
log "Repos: ${CONTEXT_REPOS}"
|
|
399
404
|
log "Context paths: ${CONTEXT_PATHSPECS}"
|
|
400
405
|
log "============================================================"
|
|
401
406
|
|
|
402
|
-
local repo_name
|
|
403
|
-
for
|
|
404
|
-
|
|
407
|
+
local code_root repo_name
|
|
408
|
+
for code_root in ${CONTEXT_ROOTS}; do
|
|
409
|
+
if [ ! -d "${code_root}" ]; then
|
|
410
|
+
log_warn "Skipping context root: directory not found at ${code_root}."
|
|
411
|
+
continue
|
|
412
|
+
fi
|
|
413
|
+
CODE_ROOT="${code_root}"
|
|
414
|
+
for repo_name in ${CONTEXT_REPOS}; do
|
|
415
|
+
sync_context_repo "${repo_name}" || true
|
|
416
|
+
done
|
|
405
417
|
done
|
|
406
418
|
|
|
407
419
|
log "============================================================"
|
|
@@ -79,6 +79,12 @@ write_service() {
|
|
|
79
79
|
printf 'Environment=HOME=%s\n' "${HOME}"
|
|
80
80
|
printf 'Environment=YOU_HOME=%s\n' "${YOU_HOME}"
|
|
81
81
|
printf 'Environment=PATH=%s\n' "${UNIT_PATH}"
|
|
82
|
+
if [ "${unit}" = "you-realtime-sync.service" ]; then
|
|
83
|
+
# The interval units below own stack/context sync. Keep the realtime
|
|
84
|
+
# brain focused on live state, inventory, repair, and remote commands.
|
|
85
|
+
printf 'Environment=YOUMD_LIVE_SYNC_STACK=0\n'
|
|
86
|
+
printf 'Environment=YOUMD_LIVE_SYNC_CONTEXT=0\n'
|
|
87
|
+
fi
|
|
82
88
|
# Run via a login shell so PATH is sourced from the user's profile — the CLI shells out to
|
|
83
89
|
# node/git and the claude/codex/cursor harnesses by name, and those may live under nvm/fnm
|
|
84
90
|
# prefixes not in the static Environment=PATH above (mirrors the macOS `/bin/zsh -lc` plists).
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
2
|
+
const assert = require("node:assert/strict");
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const os = require("node:os");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
|
|
7
|
+
const addon = require(path.resolve(
|
|
8
|
+
__dirname,
|
|
9
|
+
"../native/prebuilds/darwin-universal/youmd_safe_open.node",
|
|
10
|
+
));
|
|
11
|
+
|
|
12
|
+
assert.equal(addon.napiVersion, 8, "expected the ABI-stable Node-API v8 addon");
|
|
13
|
+
assert.equal(typeof addon.safeRead, "function", "safeRead export is missing");
|
|
14
|
+
|
|
15
|
+
const root = fs.realpathSync.native(fs.mkdtempSync(path.join(os.tmpdir(), "youmd-node-abi-")));
|
|
16
|
+
const outside = fs.realpathSync.native(fs.mkdtempSync(path.join(os.tmpdir(), "youmd-node-abi-outside-")));
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
fs.mkdirSync(path.join(root, "project-context"));
|
|
20
|
+
fs.writeFileSync(path.join(root, "project-context", "PRD.md"), "verified\n");
|
|
21
|
+
fs.writeFileSync(path.join(outside, "PRD.md"), "outside\n");
|
|
22
|
+
|
|
23
|
+
const stat = fs.lstatSync(root, { bigint: true });
|
|
24
|
+
const bytes = addon.safeRead(
|
|
25
|
+
root,
|
|
26
|
+
"project-context/PRD.md",
|
|
27
|
+
1024,
|
|
28
|
+
stat.dev.toString(),
|
|
29
|
+
stat.ino.toString(),
|
|
30
|
+
);
|
|
31
|
+
assert.equal(bytes.toString("utf8"), "verified\n");
|
|
32
|
+
|
|
33
|
+
fs.rmSync(path.join(root, "project-context"), { recursive: true });
|
|
34
|
+
fs.symlinkSync(outside, path.join(root, "project-context"), "dir");
|
|
35
|
+
assert.throws(
|
|
36
|
+
() => addon.safeRead(
|
|
37
|
+
root,
|
|
38
|
+
"project-context/PRD.md",
|
|
39
|
+
1024,
|
|
40
|
+
stat.dev.toString(),
|
|
41
|
+
stat.ino.toString(),
|
|
42
|
+
),
|
|
43
|
+
/symlink_rejected|unsafe_path/,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
console.log(`safe-open ABI smoke passed on Node ${process.version}`);
|
|
47
|
+
} finally {
|
|
48
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
49
|
+
fs.rmSync(outside, { recursive: true, force: true });
|
|
50
|
+
}
|