shmakk 1.2.4 → 1.2.5

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 (51) hide show
  1. package/.env.example +11 -0
  2. package/README.md +75 -1
  3. package/docs/index.html +154 -16
  4. package/docs/mcp.md +78 -0
  5. package/docs/ssh.md +82 -0
  6. package/docs/vibedit-analysis.md +375 -0
  7. package/docs/vim.md +110 -0
  8. package/docs/voice.md +4 -0
  9. package/package.json +9 -5
  10. package/scripts/test-vibedit.js +45 -0
  11. package/scripts/vibedit-demo.sh +52 -0
  12. package/skills/shmakk-skill-creator.md +269 -0
  13. package/src/_check.js +7 -0
  14. package/src/_check_schema.js +5 -0
  15. package/src/_cleanup.js +18 -0
  16. package/src/_fix.js +9 -0
  17. package/src/_test_import.js +15 -0
  18. package/src/agent.js +11 -4
  19. package/src/browser-daemon.js +209 -0
  20. package/src/browser.js +10 -0
  21. package/src/cli/browserDaemon.js +60 -0
  22. package/src/cli/connectBrowser.js +137 -0
  23. package/src/cli.js +235 -8
  24. package/src/completions.js +8 -0
  25. package/src/control.js +273 -1
  26. package/src/core/browserConnector.js +523 -0
  27. package/src/electron.js +305 -0
  28. package/src/endpoints.js +74 -9
  29. package/src/index.js +24 -1
  30. package/src/llm.js +501 -61
  31. package/src/mobile.js +307 -0
  32. package/src/notify.js +51 -3
  33. package/src/orchestrator.js +35 -1
  34. package/src/pty.js +11 -6
  35. package/src/review.js +45 -11
  36. package/src/self-commands.js +153 -0
  37. package/src/session-convert.js +508 -0
  38. package/src/session-search.js +31 -0
  39. package/src/session.js +384 -46
  40. package/src/skills/browserActions.ts +984 -0
  41. package/src/skills.js +451 -24
  42. package/src/system-prompt.js +31 -25
  43. package/src/tools.js +81 -0
  44. package/src/vibedit/control.js +534 -0
  45. package/src/vibedit/electron.js +108 -0
  46. package/src/vibedit/files.js +171 -0
  47. package/src/vibedit/index.js +298 -0
  48. package/src/vibedit/overlay.js +1482 -0
  49. package/src/vibedit/prompts.js +245 -0
  50. package/src/vibedit/state.js +32 -0
  51. package/src/vim.js +410 -0
package/src/control.js CHANGED
@@ -439,4 +439,276 @@ function mcpStatus() {
439
439
  return 0;
440
440
  }
441
441
 
442
- module.exports = { status, exitParent, restartParent, resetConversation, setProfileAndRestart, profileSignalPath, resumeStatus, compactContext, stats, loadSkill, listSkills, listSkillCategories, findSkills, skillStatus, unloadSkill, installSkill, showPlan, mcpStatus };
442
+ function consolidateWorkspace() {
443
+ const fs = require('fs');
444
+ const path = require('path');
445
+
446
+ const cwd = process.cwd();
447
+ const rootShmakk = path.join(cwd, '.shmakk');
448
+ const rootSkills = path.join(rootShmakk, 'skills');
449
+ const rootState = path.join(rootShmakk, 'state');
450
+
451
+ // Ensure root .shmakk dirs exist
452
+ fs.mkdirSync(rootShmakk, { recursive: true });
453
+ fs.mkdirSync(rootSkills, { recursive: true });
454
+ fs.mkdirSync(rootState, { recursive: true });
455
+
456
+ // Find all nested .shmakk dirs (skip root itself, skip node_modules)
457
+ const nested = [];
458
+ function walk(dir, depth) {
459
+ if (depth > 50) return; // safety limit
460
+ let entries;
461
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
462
+ for (const e of entries) {
463
+ if (!e.isDirectory()) continue;
464
+ if (e.name === 'node_modules' || e.name === '.git') continue;
465
+ const full = path.join(dir, e.name);
466
+ if (e.name === '.shmakk' && depth > 0) {
467
+ nested.push(full);
468
+ } else if (!e.name.startsWith('.')) {
469
+ walk(full, depth + 1);
470
+ }
471
+ }
472
+ }
473
+ walk(cwd, 0);
474
+
475
+ if (!nested.length) {
476
+ process.stdout.write('shmakk: no nested .shmakk directories found to consolidate\n');
477
+ return 1;
478
+ }
479
+
480
+ process.stdout.write(`shmakk: found ${nested.length} nested .shmakk director${nested.length > 1 ? 'ies' : 'y'}\n`);
481
+
482
+ let mergedSkills = 0;
483
+ let mergedMemory = 0;
484
+ let mergedRules = 0;
485
+ let mergedMcp = 0;
486
+ let mergedHosts = 0;
487
+ let mergedState = 0;
488
+
489
+ // Helper: read file if exists
490
+ const read = (p) => { try { return fs.readFileSync(p, 'utf8'); } catch { return null; } };
491
+
492
+ // Helper: read JSON if exists
493
+ const readJSON = (p) => { try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; } };
494
+
495
+ // Helper: write JSON
496
+ const writeJSON = (p, obj) => fs.writeFileSync(p, JSON.stringify(obj, null, 2) + '\n');
497
+
498
+ // Helper: append line-deduplicated content (for memory.md, rules.md)
499
+ function mergeLines(rootFile, nestedFile) {
500
+ const rootContent = read(rootFile) || '';
501
+ const rootLines = new Set(rootContent.split('\n').map(l => l.trim()).filter(Boolean));
502
+ const nestedContent = read(nestedFile);
503
+ if (!nestedContent) return 0;
504
+ const newLines = nestedContent.split('\n')
505
+ .map(l => l.trim())
506
+ .filter(l => Boolean(l) && !rootLines.has(l));
507
+ if (!newLines.length) return 0;
508
+ const append = (rootContent.endsWith('\n') ? '' : '\n') + newLines.join('\n') + '\n';
509
+ fs.appendFileSync(rootFile, append);
510
+ return newLines.length;
511
+ }
512
+
513
+ // Helper: merge JSON objects shallowly (root wins)
514
+ function mergeJSON(rootFile, nestedFile) {
515
+ const root = readJSON(rootFile) || {};
516
+ const nested = readJSON(nestedFile);
517
+ if (!nested) return 0;
518
+ let added = 0;
519
+ for (const [k, v] of Object.entries(nested)) {
520
+ if (!(k in root)) { root[k] = v; added++; }
521
+ }
522
+ if (added > 0) writeJSON(rootFile, root);
523
+ return added;
524
+ }
525
+
526
+ for (const nsm of nested) {
527
+ const rel = path.relative(cwd, path.dirname(nsm)) || '(root)';
528
+ process.stdout.write(` merging ${rel}/.shmakk/\n`);
529
+
530
+ // memory.md
531
+ const memRoot = path.join(rootShmakk, 'memory.md');
532
+ const memNested = path.join(nsm, 'memory.md');
533
+ const memAdded = mergeLines(memRoot, memNested);
534
+ if (memAdded) process.stdout.write(` memory.md: +${memAdded} line(s)\n`);
535
+ mergedMemory += memAdded;
536
+
537
+ // rules.md
538
+ const rulesRoot = path.join(rootShmakk, 'rules.md');
539
+ const rulesNested = path.join(nsm, 'rules.md');
540
+ const rulesAdded = mergeLines(rulesRoot, rulesNested);
541
+ if (rulesAdded) process.stdout.write(` rules.md: +${rulesAdded} line(s)\n`);
542
+ mergedRules += rulesAdded;
543
+
544
+ // skills/*.md
545
+ const skillsNested = path.join(nsm, 'skills');
546
+ if (fs.existsSync(skillsNested)) {
547
+ let entries;
548
+ try { entries = fs.readdirSync(skillsNested, { withFileTypes: true }); } catch { entries = []; }
549
+ for (const e of entries) {
550
+ if (e.isFile() && e.name.endsWith('.md')) {
551
+ const dst = path.join(rootSkills, e.name);
552
+ if (!fs.existsSync(dst)) {
553
+ fs.copyFileSync(path.join(skillsNested, e.name), dst);
554
+ process.stdout.write(` skills/${e.name}: copied\n`);
555
+ mergedSkills++;
556
+ }
557
+ } else if (e.isDirectory()) {
558
+ const skillFile = path.join(skillsNested, e.name, 'SKILL.md');
559
+ const dstDir = path.join(rootSkills, e.name);
560
+ const dstFile = path.join(dstDir, 'SKILL.md');
561
+ if (fs.existsSync(skillFile) && !fs.existsSync(dstFile)) {
562
+ fs.mkdirSync(dstDir, { recursive: true });
563
+ fs.copyFileSync(skillFile, dstFile);
564
+ // Also copy sibling files if any
565
+ let subEntries;
566
+ try { subEntries = fs.readdirSync(path.join(skillsNested, e.name)); } catch { subEntries = []; }
567
+ for (const se of subEntries) {
568
+ const srcSub = path.join(skillsNested, e.name, se);
569
+ const dstSub = path.join(dstDir, se);
570
+ if (se !== 'SKILL.md' && !fs.existsSync(dstSub)) {
571
+ try { fs.copyFileSync(srcSub, dstSub); } catch {}
572
+ }
573
+ }
574
+ process.stdout.write(` skills/${e.name}/: copied\n`);
575
+ mergedSkills++;
576
+ }
577
+ }
578
+ }
579
+ }
580
+
581
+ // mcp.json
582
+ const mcpRoot = path.join(rootShmakk, 'mcp.json');
583
+ const mcpNested = path.join(nsm, 'mcp.json');
584
+ const mcpRootObj = readJSON(mcpRoot) || {};
585
+ const mcpNestedObj = readJSON(mcpNested);
586
+ if (mcpNestedObj?.mcpServers) {
587
+ if (!mcpRootObj.mcpServers) mcpRootObj.mcpServers = {};
588
+ let added = 0;
589
+ for (const [k, v] of Object.entries(mcpNestedObj.mcpServers)) {
590
+ if (!(k in mcpRootObj.mcpServers)) {
591
+ mcpRootObj.mcpServers[k] = v;
592
+ added++;
593
+ }
594
+ }
595
+ if (added) {
596
+ writeJSON(mcpRoot, mcpRootObj);
597
+ process.stdout.write(` mcp.json: +${added} server(s)\n`);
598
+ mergedMcp += added;
599
+ }
600
+ }
601
+
602
+ // hosts.json
603
+ const hostsRoot = path.join(rootShmakk, 'hosts.json');
604
+ const hostsNested = path.join(nsm, 'hosts.json');
605
+ const hAdded = mergeJSON(hostsRoot, hostsNested);
606
+ if (hAdded) {
607
+ process.stdout.write(` hosts.json: +${hAdded} host(s)\n`);
608
+ mergedHosts += hAdded;
609
+ }
610
+
611
+ // state/* — merge/copy all state files from nested into root
612
+ const stateNested = path.join(nsm, 'state');
613
+ if (fs.existsSync(stateNested)) {
614
+ let stateEntries;
615
+ try { stateEntries = fs.readdirSync(stateNested, { withFileTypes: true }); } catch { stateEntries = []; }
616
+ for (const se of stateEntries) {
617
+ if (!se.isFile()) continue;
618
+ const src = path.join(stateNested, se.name);
619
+ const dst = path.join(rootState, se.name);
620
+
621
+ // command-freq.json: merge maps, root wins
622
+ if (se.name === 'command-freq.json') {
623
+ const rootObj = readJSON(dst) || {};
624
+ const nestedObj = readJSON(src);
625
+ if (nestedObj) {
626
+ let added = 0;
627
+ for (const [k, v] of Object.entries(nestedObj)) {
628
+ if (!(k in rootObj)) { rootObj[k] = v; added++; }
629
+ }
630
+ if (added) {
631
+ writeJSON(dst, rootObj);
632
+ process.stdout.write(` state/${se.name}: +${added} command(s)\n`);
633
+ mergedState += added;
634
+ }
635
+ }
636
+ continue;
637
+ }
638
+
639
+ // task-journal.json: merge arrays deduped by id
640
+ if (se.name === 'task-journal.json') {
641
+ const rootArr = readJSON(dst) || [];
642
+ const nestedArr = readJSON(src);
643
+ if (Array.isArray(nestedArr) && nestedArr.length) {
644
+ const rootIds = new Set(rootArr.map(e => e.id || JSON.stringify(e)));
645
+ const newEntries = nestedArr.filter(e => !rootIds.has(e.id || JSON.stringify(e)));
646
+ if (newEntries.length) {
647
+ rootArr.push(...newEntries);
648
+ writeJSON(dst, rootArr);
649
+ process.stdout.write(` state/${se.name}: +${newEntries.length} task(s)\n`);
650
+ mergedState += newEntries.length;
651
+ }
652
+ }
653
+ continue;
654
+ }
655
+
656
+ // Any other JSON file: shallow merge, root wins
657
+ if (se.name.endsWith('.json')) {
658
+ const rootObj = readJSON(dst) || {};
659
+ const nestedObj = readJSON(src);
660
+ if (nestedObj && typeof nestedObj === 'object') {
661
+ let added = 0;
662
+ if (Array.isArray(nestedObj)) {
663
+ const rootJson = JSON.stringify(rootObj);
664
+ for (const item of nestedObj) {
665
+ if (rootJson.indexOf(JSON.stringify(item)) === -1) {
666
+ rootObj.push(item);
667
+ added++;
668
+ }
669
+ }
670
+ } else {
671
+ for (const [k, v] of Object.entries(nestedObj)) {
672
+ if (!(k in rootObj)) { rootObj[k] = v; added++; }
673
+ }
674
+ }
675
+ if (added) {
676
+ writeJSON(dst, rootObj);
677
+ process.stdout.write(` state/${se.name}: +${added} entry(ies)\n`);
678
+ mergedState += added;
679
+ }
680
+ }
681
+ continue;
682
+ }
683
+
684
+ // Non-JSON files: copy if not already in root
685
+ if (!fs.existsSync(dst)) {
686
+ fs.copyFileSync(src, dst);
687
+ process.stdout.write(` state/${se.name}: copied\n`);
688
+ mergedState++;
689
+ }
690
+ }
691
+ }
692
+ }
693
+
694
+ // Remove nested .shmakk dirs after successful merge
695
+ for (const nsm of nested) {
696
+ try { fs.rmSync(nsm, { recursive: true, force: true }); } catch {}
697
+ }
698
+
699
+ // Summary
700
+ const total = mergedMemory + mergedRules + mergedSkills + mergedMcp + mergedHosts + mergedState;
701
+ process.stdout.write('\nshmakk: consolidation complete\n');
702
+ process.stdout.write(` memory: ${mergedMemory} line(s)\n`);
703
+ process.stdout.write(` rules: ${mergedRules} line(s)\n`);
704
+ process.stdout.write(` skills: ${mergedSkills} file(s)\n`);
705
+ process.stdout.write(` mcp.json: ${mergedMcp} server(s)\n`);
706
+ process.stdout.write(` hosts.json: ${mergedHosts} host(s)\n`);
707
+ process.stdout.write(` state: ${mergedState} entry(ies)\n`);
708
+ process.stdout.write(` total merged: ${total}\n`);
709
+ process.stdout.write(`\nshmakk: removed ${nested.length} nested .shmakk director${nested.length > 1 ? 'ies' : 'y'}\n`);
710
+
711
+ return 0;
712
+ }
713
+
714
+ module.exports = { status, exitParent, restartParent, resetConversation, setProfileAndRestart, profileSignalPath, resumeStatus, compactContext, stats, loadSkill, listSkills, listSkillCategories, findSkills, skillStatus, unloadSkill, installSkill, showPlan, mcpStatus, consolidateWorkspace };