@lopecode/channel 0.1.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.
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Injects the @tomlarkworthy/claude-code-pairing module into a lopecode notebook HTML.
4
+ *
5
+ * Usage: node tools/channel/inject-module.js <input.html> <output.html>
6
+ *
7
+ * This:
8
+ * 1. Reads the source notebook HTML
9
+ * 2. Inserts the claude-code-pairing module as a <script> block before the bootloader
10
+ * 3. Adds "@tomlarkworthy/claude-code-pairing" to bootconf.json mains
11
+ * 4. Updates the hash URL to include the channel module in the layout
12
+ * 5. Writes the result to output.html
13
+ */
14
+
15
+ import { readFileSync, writeFileSync } from "fs";
16
+ import { resolve } from "path";
17
+
18
+ const [,, inputPath, outputPath] = process.argv;
19
+
20
+ if (!inputPath || !outputPath) {
21
+ console.error("Usage: node tools/channel/inject-module.js <input.html> <output.html>");
22
+ process.exit(1);
23
+ }
24
+
25
+ const html = readFileSync(resolve(inputPath), "utf8");
26
+ const moduleSource = readFileSync(resolve(import.meta.dirname, "claude-code-pairing-module.js"), "utf8");
27
+
28
+ // 1. Find the bootconf.json script and insert our module before it
29
+ const bootconfMarker = '<!-- Bootloader -->';
30
+ const bootconfIdx = html.lastIndexOf(bootconfMarker);
31
+ if (bootconfIdx === -1) {
32
+ console.error("Could not find '<!-- Bootloader -->' marker in HTML");
33
+ process.exit(1);
34
+ }
35
+
36
+ const moduleScript = `
37
+ <script id="@tomlarkworthy/claude-code-pairing"
38
+ type="text/plain"
39
+ data-mime="application/javascript"
40
+ >
41
+ ${moduleSource}
42
+ </script>
43
+
44
+ `;
45
+
46
+ let result = html.slice(0, bootconfIdx) + moduleScript + html.slice(bootconfIdx);
47
+
48
+ // 2. Find the actual bootconf.json script block
49
+ // Look for the specific pattern: <script id="bootconf.json" with type="text/plain" and data-mime="application/json"
50
+ // Use lastIndexOf to skip templates embedded in exporter modules
51
+ const bootconfPattern = 'id="bootconf.json" \n type="text/plain"\n data-mime="application/json"';
52
+ let bootconfScriptStart = result.lastIndexOf(bootconfPattern);
53
+ if (bootconfScriptStart === -1) {
54
+ // Try alternative formatting
55
+ bootconfScriptStart = result.lastIndexOf('id="bootconf.json"');
56
+ // Verify it's followed by data-mime="application/json"
57
+ const next200 = result.substring(bootconfScriptStart, bootconfScriptStart + 200);
58
+ if (!next200.includes('application/json')) {
59
+ console.error("Could not find bootconf.json with application/json mime type");
60
+ process.exit(1);
61
+ }
62
+ }
63
+ if (bootconfScriptStart === -1) {
64
+ console.error("Could not find bootconf.json script block");
65
+ process.exit(1);
66
+ }
67
+ const bootconfContentStart = result.indexOf('>', bootconfScriptStart) + 1;
68
+ const bootconfContentEnd = result.indexOf('</script>', bootconfContentStart);
69
+ let bootconfContent = result.slice(bootconfContentStart, bootconfContentEnd);
70
+
71
+ // Parse the JSON-like content
72
+ try {
73
+ const bootconf = JSON.parse(bootconfContent);
74
+
75
+ // Add claude-code-pairing to mains
76
+ if (!bootconf.mains.includes("@tomlarkworthy/claude-code-pairing")) {
77
+ bootconf.mains.push("@tomlarkworthy/claude-code-pairing");
78
+ }
79
+
80
+ // Update hash to include claude-code-pairing in layout
81
+ // Lopepage only supports flat R(S,S,...) — no nesting
82
+ // Extract existing module references and add ours as a new panel
83
+ const currentHash = bootconf.hash || "";
84
+ if (!currentHash.includes("claude-code-pairing")) {
85
+ // Parse existing modules from hash like R100(S70(@mod1),S30(@mod2))
86
+ const moduleRefs = [];
87
+ const modulePattern = /S(\d+)\(([^)]+)\)/g;
88
+ let m;
89
+ while ((m = modulePattern.exec(currentHash)) !== null) {
90
+ moduleRefs.push({ weight: parseInt(m[1]), module: m[2] });
91
+ }
92
+
93
+ if (moduleRefs.length > 0) {
94
+ // Scale existing weights to 75% and add claude-code-pairing at 25%
95
+ const totalWeight = moduleRefs.reduce((sum, r) => sum + r.weight, 0);
96
+ const scaled = moduleRefs.map(r => ({
97
+ weight: Math.round((r.weight / totalWeight) * 75),
98
+ module: r.module,
99
+ }));
100
+ scaled.push({ weight: 25, module: "@tomlarkworthy/claude-code-pairing" });
101
+ const parts = scaled.map(r => `S${r.weight}(${r.module})`).join(",");
102
+ bootconf.hash = `#view=R100(${parts})`;
103
+ } else {
104
+ // Simple fallback
105
+ bootconf.hash = "#view=R100(S75(@tomlarkworthy/debugger),S25(@tomlarkworthy/claude-code-pairing))";
106
+ }
107
+ }
108
+
109
+ const newBootconfContent = "\n" + JSON.stringify(bootconf, null, 2) + "\n";
110
+ result = result.slice(0, bootconfContentStart) + newBootconfContent + result.slice(bootconfContentEnd);
111
+ } catch (e) {
112
+ console.error("Failed to parse bootconf.json:", e.message);
113
+ console.error("Content:", bootconfContent.slice(0, 200));
114
+ process.exit(1);
115
+ }
116
+
117
+ // 4. Update the title
118
+ const titleRegex = /<title>[^<]*<\/title>/;
119
+ const titleMatch = result.match(titleRegex);
120
+ if (titleMatch) {
121
+ const currentTitle = titleMatch[0].replace(/<\/?title>/g, "");
122
+ if (!currentTitle.includes("claude-channel")) {
123
+ result = result.replace(titleRegex, `<title>${currentTitle} + claude-channel</title>`);
124
+ }
125
+ }
126
+
127
+ writeFileSync(resolve(outputPath), result);
128
+ const inputSize = (html.length / 1024 / 1024).toFixed(2);
129
+ const outputSize = (result.length / 1024 / 1024).toFixed(2);
130
+ console.log(`Injected @tomlarkworthy/claude-code-pairing into ${outputPath}`);
131
+ console.log(`Input: ${inputSize} MB → Output: ${outputSize} MB`);