@rozek/nanoclaw 0.0.5 → 0.0.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.
Files changed (132) hide show
  1. package/container/agent-runner/package-lock.json +1524 -0
  2. package/dist/cli.js +39 -4
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +34 -0
  6. package/dist/index.js.map +1 -1
  7. package/package.json +7 -1
  8. package/.claude/settings.json +0 -1
  9. package/.claude/skills/add-compact/SKILL.md +0 -135
  10. package/.claude/skills/add-discord/SKILL.md +0 -203
  11. package/.claude/skills/add-gmail/SKILL.md +0 -220
  12. package/.claude/skills/add-image-vision/SKILL.md +0 -94
  13. package/.claude/skills/add-ollama-tool/SKILL.md +0 -153
  14. package/.claude/skills/add-parallel/SKILL.md +0 -290
  15. package/.claude/skills/add-pdf-reader/SKILL.md +0 -104
  16. package/.claude/skills/add-reactions/SKILL.md +0 -117
  17. package/.claude/skills/add-slack/SKILL.md +0 -207
  18. package/.claude/skills/add-telegram/SKILL.md +0 -222
  19. package/.claude/skills/add-telegram-swarm/SKILL.md +0 -384
  20. package/.claude/skills/add-voice-transcription/SKILL.md +0 -148
  21. package/.claude/skills/add-whatsapp/SKILL.md +0 -372
  22. package/.claude/skills/convert-to-apple-container/SKILL.md +0 -175
  23. package/.claude/skills/customize/SKILL.md +0 -110
  24. package/.claude/skills/debug/SKILL.md +0 -349
  25. package/.claude/skills/get-qodo-rules/SKILL.md +0 -122
  26. package/.claude/skills/get-qodo-rules/references/output-format.md +0 -41
  27. package/.claude/skills/get-qodo-rules/references/pagination.md +0 -33
  28. package/.claude/skills/get-qodo-rules/references/repository-scope.md +0 -26
  29. package/.claude/skills/qodo-pr-resolver/SKILL.md +0 -326
  30. package/.claude/skills/qodo-pr-resolver/resources/providers.md +0 -329
  31. package/.claude/skills/setup/SKILL.md +0 -218
  32. package/.claude/skills/update-nanoclaw/SKILL.md +0 -235
  33. package/.claude/skills/update-skills/SKILL.md +0 -130
  34. package/.claude/skills/use-local-whisper/SKILL.md +0 -152
  35. package/.claude/skills/x-integration/SKILL.md +0 -417
  36. package/.claude/skills/x-integration/agent.ts +0 -243
  37. package/.claude/skills/x-integration/host.ts +0 -159
  38. package/.claude/skills/x-integration/lib/browser.ts +0 -148
  39. package/.claude/skills/x-integration/lib/config.ts +0 -62
  40. package/.claude/skills/x-integration/scripts/like.ts +0 -56
  41. package/.claude/skills/x-integration/scripts/post.ts +0 -66
  42. package/.claude/skills/x-integration/scripts/quote.ts +0 -80
  43. package/.claude/skills/x-integration/scripts/reply.ts +0 -74
  44. package/.claude/skills/x-integration/scripts/retweet.ts +0 -62
  45. package/.claude/skills/x-integration/scripts/setup.ts +0 -87
  46. package/.env.example +0 -1
  47. package/.github/CODEOWNERS +0 -10
  48. package/.github/PULL_REQUEST_TEMPLATE.md +0 -14
  49. package/.github/workflows/bump-version.yml +0 -32
  50. package/.github/workflows/ci.yml +0 -25
  51. package/.github/workflows/merge-forward-skills.yml +0 -160
  52. package/.github/workflows/update-tokens.yml +0 -42
  53. package/.husky/pre-commit +0 -1
  54. package/.mcp.json +0 -3
  55. package/.nvmrc +0 -1
  56. package/.prettierrc +0 -3
  57. package/CHANGELOG.md +0 -8
  58. package/CONTRIBUTING.md +0 -23
  59. package/CONTRIBUTORS.md +0 -15
  60. package/NanoClaw_with_Web-Support.md +0 -326
  61. package/README_zh.md +0 -200
  62. package/assets/nanoclaw-favicon.png +0 -0
  63. package/assets/nanoclaw-icon.png +0 -0
  64. package/assets/nanoclaw-logo-dark.png +0 -0
  65. package/assets/nanoclaw-logo.png +0 -0
  66. package/assets/nanoclaw-profile.jpeg +0 -0
  67. package/assets/nanoclaw-sales.png +0 -0
  68. package/assets/social-preview.jpg +0 -0
  69. package/config-examples/mount-allowlist.json +0 -25
  70. package/docs/APPLE-CONTAINER-NETWORKING.md +0 -90
  71. package/docs/DEBUG_CHECKLIST.md +0 -143
  72. package/docs/REQUIREMENTS.md +0 -196
  73. package/docs/SDK_DEEP_DIVE.md +0 -643
  74. package/docs/SECURITY.md +0 -122
  75. package/docs/SPEC.md +0 -785
  76. package/docs/docker-sandboxes.md +0 -359
  77. package/docs/nanoclaw-architecture-final.md +0 -1063
  78. package/docs/nanorepo-architecture.md +0 -168
  79. package/docs/skills-as-branches.md +0 -662
  80. package/groups/global/CLAUDE.md +0 -58
  81. package/groups/main/CLAUDE.md +0 -246
  82. package/launchd/com.nanoclaw.plist +0 -32
  83. package/repo-tokens/README.md +0 -113
  84. package/repo-tokens/action.yml +0 -186
  85. package/repo-tokens/badge.svg +0 -23
  86. package/repo-tokens/examples/green.svg +0 -14
  87. package/repo-tokens/examples/red.svg +0 -14
  88. package/repo-tokens/examples/yellow-green.svg +0 -14
  89. package/repo-tokens/examples/yellow.svg +0 -14
  90. package/scripts/run-migrations.ts +0 -105
  91. package/setup.sh +0 -161
  92. package/src/channels/index.ts +0 -15
  93. package/src/channels/registry.test.ts +0 -42
  94. package/src/channels/registry.ts +0 -32
  95. package/src/channels/web.ts +0 -1931
  96. package/src/cli.ts +0 -254
  97. package/src/config.ts +0 -73
  98. package/src/container-runner.test.ts +0 -210
  99. package/src/container-runner.ts +0 -768
  100. package/src/container-runtime.test.ts +0 -149
  101. package/src/container-runtime.ts +0 -127
  102. package/src/credential-proxy.test.ts +0 -192
  103. package/src/credential-proxy.ts +0 -125
  104. package/src/db.test.ts +0 -484
  105. package/src/db.ts +0 -803
  106. package/src/env.ts +0 -42
  107. package/src/formatting.test.ts +0 -256
  108. package/src/group-folder.test.ts +0 -43
  109. package/src/group-folder.ts +0 -44
  110. package/src/group-queue.test.ts +0 -484
  111. package/src/group-queue.ts +0 -379
  112. package/src/index.ts +0 -854
  113. package/src/ipc-auth.test.ts +0 -679
  114. package/src/ipc.ts +0 -461
  115. package/src/logger.ts +0 -16
  116. package/src/mount-security.ts +0 -419
  117. package/src/remote-control.test.ts +0 -397
  118. package/src/remote-control.ts +0 -224
  119. package/src/router.ts +0 -52
  120. package/src/routing.test.ts +0 -170
  121. package/src/sender-allowlist.test.ts +0 -216
  122. package/src/sender-allowlist.ts +0 -128
  123. package/src/session-commands.test.ts +0 -247
  124. package/src/session-commands.ts +0 -163
  125. package/src/task-scheduler.test.ts +0 -129
  126. package/src/task-scheduler.ts +0 -328
  127. package/src/timezone.test.ts +0 -29
  128. package/src/timezone.ts +0 -16
  129. package/src/types.ts +0 -109
  130. package/tsconfig.json +0 -20
  131. package/vitest.config.ts +0 -7
  132. package/vitest.skills.config.ts +0 -7
@@ -1,419 +0,0 @@
1
- /**
2
- * Mount Security Module for NanoClaw
3
- *
4
- * Validates additional mounts against an allowlist stored OUTSIDE the project root.
5
- * This prevents container agents from modifying security configuration.
6
- *
7
- * Allowlist location: ~/.config/nanoclaw/mount-allowlist.json
8
- */
9
- import fs from 'fs';
10
- import os from 'os';
11
- import path from 'path';
12
- import pino from 'pino';
13
-
14
- import { MOUNT_ALLOWLIST_PATH } from './config.js';
15
- import { AdditionalMount, AllowedRoot, MountAllowlist } from './types.js';
16
-
17
- const logger = pino({
18
- level: process.env.LOG_LEVEL || 'info',
19
- transport: { target: 'pino-pretty', options: { colorize: true } },
20
- });
21
-
22
- // Cache the allowlist in memory - only reloads on process restart
23
- let cachedAllowlist: MountAllowlist | null = null;
24
- let allowlistLoadError: string | null = null;
25
-
26
- /**
27
- * Default blocked patterns - paths that should never be mounted
28
- */
29
- const DEFAULT_BLOCKED_PATTERNS = [
30
- '.ssh',
31
- '.gnupg',
32
- '.gpg',
33
- '.aws',
34
- '.azure',
35
- '.gcloud',
36
- '.kube',
37
- '.docker',
38
- 'credentials',
39
- '.env',
40
- '.netrc',
41
- '.npmrc',
42
- '.pypirc',
43
- 'id_rsa',
44
- 'id_ed25519',
45
- 'private_key',
46
- '.secret',
47
- ];
48
-
49
- /**
50
- * Load the mount allowlist from the external config location.
51
- * Returns null if the file doesn't exist or is invalid.
52
- * Result is cached in memory for the lifetime of the process.
53
- */
54
- export function loadMountAllowlist(): MountAllowlist | null {
55
- if (cachedAllowlist !== null) {
56
- return cachedAllowlist;
57
- }
58
-
59
- if (allowlistLoadError !== null) {
60
- // Already tried and failed, don't spam logs
61
- return null;
62
- }
63
-
64
- try {
65
- if (!fs.existsSync(MOUNT_ALLOWLIST_PATH)) {
66
- allowlistLoadError = `Mount allowlist not found at ${MOUNT_ALLOWLIST_PATH}`;
67
- logger.warn(
68
- { path: MOUNT_ALLOWLIST_PATH },
69
- 'Mount allowlist not found - additional mounts will be BLOCKED. ' +
70
- 'Create the file to enable additional mounts.',
71
- );
72
- return null;
73
- }
74
-
75
- const content = fs.readFileSync(MOUNT_ALLOWLIST_PATH, 'utf-8');
76
- const allowlist = JSON.parse(content) as MountAllowlist;
77
-
78
- // Validate structure
79
- if (!Array.isArray(allowlist.allowedRoots)) {
80
- throw new Error('allowedRoots must be an array');
81
- }
82
-
83
- if (!Array.isArray(allowlist.blockedPatterns)) {
84
- throw new Error('blockedPatterns must be an array');
85
- }
86
-
87
- if (typeof allowlist.nonMainReadOnly !== 'boolean') {
88
- throw new Error('nonMainReadOnly must be a boolean');
89
- }
90
-
91
- // Merge with default blocked patterns
92
- const mergedBlockedPatterns = [
93
- ...new Set([...DEFAULT_BLOCKED_PATTERNS, ...allowlist.blockedPatterns]),
94
- ];
95
- allowlist.blockedPatterns = mergedBlockedPatterns;
96
-
97
- cachedAllowlist = allowlist;
98
- logger.info(
99
- {
100
- path: MOUNT_ALLOWLIST_PATH,
101
- allowedRoots: allowlist.allowedRoots.length,
102
- blockedPatterns: allowlist.blockedPatterns.length,
103
- },
104
- 'Mount allowlist loaded successfully',
105
- );
106
-
107
- return cachedAllowlist;
108
- } catch (err) {
109
- allowlistLoadError = err instanceof Error ? err.message : String(err);
110
- logger.error(
111
- {
112
- path: MOUNT_ALLOWLIST_PATH,
113
- error: allowlistLoadError,
114
- },
115
- 'Failed to load mount allowlist - additional mounts will be BLOCKED',
116
- );
117
- return null;
118
- }
119
- }
120
-
121
- /**
122
- * Expand ~ to home directory and resolve to absolute path
123
- */
124
- function expandPath(p: string): string {
125
- const homeDir = process.env.HOME || os.homedir();
126
- if (p.startsWith('~/')) {
127
- return path.join(homeDir, p.slice(2));
128
- }
129
- if (p === '~') {
130
- return homeDir;
131
- }
132
- return path.resolve(p);
133
- }
134
-
135
- /**
136
- * Get the real path, resolving symlinks.
137
- * Returns null if the path doesn't exist.
138
- */
139
- function getRealPath(p: string): string | null {
140
- try {
141
- return fs.realpathSync(p);
142
- } catch {
143
- return null;
144
- }
145
- }
146
-
147
- /**
148
- * Check if a path matches any blocked pattern
149
- */
150
- function matchesBlockedPattern(
151
- realPath: string,
152
- blockedPatterns: string[],
153
- ): string | null {
154
- const pathParts = realPath.split(path.sep);
155
-
156
- for (const pattern of blockedPatterns) {
157
- // Check if any path component matches the pattern
158
- for (const part of pathParts) {
159
- if (part === pattern || part.includes(pattern)) {
160
- return pattern;
161
- }
162
- }
163
-
164
- // Also check if the full path contains the pattern
165
- if (realPath.includes(pattern)) {
166
- return pattern;
167
- }
168
- }
169
-
170
- return null;
171
- }
172
-
173
- /**
174
- * Check if a real path is under an allowed root
175
- */
176
- function findAllowedRoot(
177
- realPath: string,
178
- allowedRoots: AllowedRoot[],
179
- ): AllowedRoot | null {
180
- for (const root of allowedRoots) {
181
- const expandedRoot = expandPath(root.path);
182
- const realRoot = getRealPath(expandedRoot);
183
-
184
- if (realRoot === null) {
185
- // Allowed root doesn't exist, skip it
186
- continue;
187
- }
188
-
189
- // Check if realPath is under realRoot
190
- const relative = path.relative(realRoot, realPath);
191
- if (!relative.startsWith('..') && !path.isAbsolute(relative)) {
192
- return root;
193
- }
194
- }
195
-
196
- return null;
197
- }
198
-
199
- /**
200
- * Validate the container path to prevent escaping /workspace/extra/
201
- */
202
- function isValidContainerPath(containerPath: string): boolean {
203
- // Must not contain .. to prevent path traversal
204
- if (containerPath.includes('..')) {
205
- return false;
206
- }
207
-
208
- // Must not be absolute (it will be prefixed with /workspace/extra/)
209
- if (containerPath.startsWith('/')) {
210
- return false;
211
- }
212
-
213
- // Must not be empty
214
- if (!containerPath || containerPath.trim() === '') {
215
- return false;
216
- }
217
-
218
- return true;
219
- }
220
-
221
- export interface MountValidationResult {
222
- allowed: boolean;
223
- reason: string;
224
- realHostPath?: string;
225
- resolvedContainerPath?: string;
226
- effectiveReadonly?: boolean;
227
- }
228
-
229
- /**
230
- * Validate a single additional mount against the allowlist.
231
- * Returns validation result with reason.
232
- */
233
- export function validateMount(
234
- mount: AdditionalMount,
235
- isMain: boolean,
236
- ): MountValidationResult {
237
- const allowlist = loadMountAllowlist();
238
-
239
- // If no allowlist, block all additional mounts
240
- if (allowlist === null) {
241
- return {
242
- allowed: false,
243
- reason: `No mount allowlist configured at ${MOUNT_ALLOWLIST_PATH}`,
244
- };
245
- }
246
-
247
- // Derive containerPath from hostPath basename if not specified
248
- const containerPath = mount.containerPath || path.basename(mount.hostPath);
249
-
250
- // Validate container path (cheap check)
251
- if (!isValidContainerPath(containerPath)) {
252
- return {
253
- allowed: false,
254
- reason: `Invalid container path: "${containerPath}" - must be relative, non-empty, and not contain ".."`,
255
- };
256
- }
257
-
258
- // Expand and resolve the host path
259
- const expandedPath = expandPath(mount.hostPath);
260
- const realPath = getRealPath(expandedPath);
261
-
262
- if (realPath === null) {
263
- return {
264
- allowed: false,
265
- reason: `Host path does not exist: "${mount.hostPath}" (expanded: "${expandedPath}")`,
266
- };
267
- }
268
-
269
- // Check against blocked patterns
270
- const blockedMatch = matchesBlockedPattern(
271
- realPath,
272
- allowlist.blockedPatterns,
273
- );
274
- if (blockedMatch !== null) {
275
- return {
276
- allowed: false,
277
- reason: `Path matches blocked pattern "${blockedMatch}": "${realPath}"`,
278
- };
279
- }
280
-
281
- // Check if under an allowed root
282
- const allowedRoot = findAllowedRoot(realPath, allowlist.allowedRoots);
283
- if (allowedRoot === null) {
284
- return {
285
- allowed: false,
286
- reason: `Path "${realPath}" is not under any allowed root. Allowed roots: ${allowlist.allowedRoots
287
- .map((r) => expandPath(r.path))
288
- .join(', ')}`,
289
- };
290
- }
291
-
292
- // Determine effective readonly status
293
- const requestedReadWrite = mount.readonly === false;
294
- let effectiveReadonly = true; // Default to readonly
295
-
296
- if (requestedReadWrite) {
297
- if (!isMain && allowlist.nonMainReadOnly) {
298
- // Non-main groups forced to read-only
299
- effectiveReadonly = true;
300
- logger.info(
301
- {
302
- mount: mount.hostPath,
303
- },
304
- 'Mount forced to read-only for non-main group',
305
- );
306
- } else if (!allowedRoot.allowReadWrite) {
307
- // Root doesn't allow read-write
308
- effectiveReadonly = true;
309
- logger.info(
310
- {
311
- mount: mount.hostPath,
312
- root: allowedRoot.path,
313
- },
314
- 'Mount forced to read-only - root does not allow read-write',
315
- );
316
- } else {
317
- // Read-write allowed
318
- effectiveReadonly = false;
319
- }
320
- }
321
-
322
- return {
323
- allowed: true,
324
- reason: `Allowed under root "${allowedRoot.path}"${allowedRoot.description ? ` (${allowedRoot.description})` : ''}`,
325
- realHostPath: realPath,
326
- resolvedContainerPath: containerPath,
327
- effectiveReadonly,
328
- };
329
- }
330
-
331
- /**
332
- * Validate all additional mounts for a group.
333
- * Returns array of validated mounts (only those that passed validation).
334
- * Logs warnings for rejected mounts.
335
- */
336
- export function validateAdditionalMounts(
337
- mounts: AdditionalMount[],
338
- groupName: string,
339
- isMain: boolean,
340
- ): Array<{
341
- hostPath: string;
342
- containerPath: string;
343
- readonly: boolean;
344
- }> {
345
- const validatedMounts: Array<{
346
- hostPath: string;
347
- containerPath: string;
348
- readonly: boolean;
349
- }> = [];
350
-
351
- for (const mount of mounts) {
352
- const result = validateMount(mount, isMain);
353
-
354
- if (result.allowed) {
355
- validatedMounts.push({
356
- hostPath: result.realHostPath!,
357
- containerPath: `/workspace/extra/${result.resolvedContainerPath}`,
358
- readonly: result.effectiveReadonly!,
359
- });
360
-
361
- logger.debug(
362
- {
363
- group: groupName,
364
- hostPath: result.realHostPath,
365
- containerPath: result.resolvedContainerPath,
366
- readonly: result.effectiveReadonly,
367
- reason: result.reason,
368
- },
369
- 'Mount validated successfully',
370
- );
371
- } else {
372
- logger.warn(
373
- {
374
- group: groupName,
375
- requestedPath: mount.hostPath,
376
- containerPath: mount.containerPath,
377
- reason: result.reason,
378
- },
379
- 'Additional mount REJECTED',
380
- );
381
- }
382
- }
383
-
384
- return validatedMounts;
385
- }
386
-
387
- /**
388
- * Generate a template allowlist file for users to customize
389
- */
390
- export function generateAllowlistTemplate(): string {
391
- const template: MountAllowlist = {
392
- allowedRoots: [
393
- {
394
- path: '~/projects',
395
- allowReadWrite: true,
396
- description: 'Development projects',
397
- },
398
- {
399
- path: '~/repos',
400
- allowReadWrite: true,
401
- description: 'Git repositories',
402
- },
403
- {
404
- path: '~/Documents/work',
405
- allowReadWrite: false,
406
- description: 'Work documents (read-only)',
407
- },
408
- ],
409
- blockedPatterns: [
410
- // Additional patterns beyond defaults
411
- 'password',
412
- 'secret',
413
- 'token',
414
- ],
415
- nonMainReadOnly: true,
416
- };
417
-
418
- return JSON.stringify(template, null, 2);
419
- }