@nubase/terminal 0.0.0-stage → 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.
Files changed (119) hide show
  1. package/bin/nubase.js +11 -0
  2. package/out/main/index.js +3218 -0
  3. package/out/preload/index.cjs +23 -0
  4. package/out/renderer/assets/BinaryPreviews-Dvmgctgl.js +262 -0
  5. package/out/renderer/assets/CodeEditor-BKx61p9c.js +2034 -0
  6. package/out/renderer/assets/DocumentPreviews-BA1gL-ZJ.js +66 -0
  7. package/out/renderer/assets/DocxPreview-CiY-SNbh.js +6955 -0
  8. package/out/renderer/assets/ImagePreview-DpnKfbxj.js +120 -0
  9. package/out/renderer/assets/PdfPreview-6oeuYaqY.js +24399 -0
  10. package/out/renderer/assets/PdfPreview-BT-DZbCt.css +6483 -0
  11. package/out/renderer/assets/SpreadsheetPreviews-B4Qby88A.js +23987 -0
  12. package/out/renderer/assets/abap-Cel38df4.js +1317 -0
  13. package/out/renderer/assets/apex-DzOmV_zF.js +355 -0
  14. package/out/renderer/assets/azcli-Xvapx-7p.js +56 -0
  15. package/out/renderer/assets/bat-BGMSIlWY.js +126 -0
  16. package/out/renderer/assets/bicep-CcpV5qxk.js +228 -0
  17. package/out/renderer/assets/cameligo-BfMDXVEx.js +233 -0
  18. package/out/renderer/assets/clojure-DFgIAJGu.js +801 -0
  19. package/out/renderer/assets/codicon-DF1abBS2.ttf +0 -0
  20. package/out/renderer/assets/coffee-Db4q5aZ8.js +308 -0
  21. package/out/renderer/assets/cpp-CEvPJv6f.js +450 -0
  22. package/out/renderer/assets/csharp-CVoBr97X.js +405 -0
  23. package/out/renderer/assets/csp-Cgzd5gxo.js +49 -0
  24. package/out/renderer/assets/css-D3M-RRVi.js +287 -0
  25. package/out/renderer/assets/css.worker-AXSlELAq.js +50031 -0
  26. package/out/renderer/assets/cssMode-CrMQfSUN.js +101 -0
  27. package/out/renderer/assets/cursor-editorTextHighlight-kG3uUOwL.svg +8 -0
  28. package/out/renderer/assets/cypher-DJJgZ_3Q.js +326 -0
  29. package/out/renderer/assets/dart-Bkg7I-Qe.js +341 -0
  30. package/out/renderer/assets/dockerfile-B1r36HyK.js +194 -0
  31. package/out/renderer/assets/ecl-C1Ji6tZG.js +531 -0
  32. package/out/renderer/assets/editor-CXfYqUZ6.css +4816 -0
  33. package/out/renderer/assets/editor.api-zF37fz_e.js +127071 -0
  34. package/out/renderer/assets/elixir-D_-n8lGJ.js +526 -0
  35. package/out/renderer/assets/fileTypes-nBt-Y6NI.js +56306 -0
  36. package/out/renderer/assets/flow9-CCdkJPNN.js +179 -0
  37. package/out/renderer/assets/freemarker2-CBlvIP9I.js +733 -0
  38. package/out/renderer/assets/fsharp-D5KoOpm3.js +248 -0
  39. package/out/renderer/assets/go-B7BFdMAL.js +264 -0
  40. package/out/renderer/assets/graphql-BqIezLde.js +177 -0
  41. package/out/renderer/assets/handlebars-CG4DAk5_.js +414 -0
  42. package/out/renderer/assets/hcl-C1GQCKC0.js +222 -0
  43. package/out/renderer/assets/html-C-_R4s2w.js +335 -0
  44. package/out/renderer/assets/html.worker-vQ8gO4kF.js +30584 -0
  45. package/out/renderer/assets/htmlMode-mGTQJ-lr.js +125 -0
  46. package/out/renderer/assets/index-CQHlaLTi.js +46080 -0
  47. package/out/renderer/assets/index-DBYJcRdn.css +3124 -0
  48. package/out/renderer/assets/ini-DDFxEGYN.js +97 -0
  49. package/out/renderer/assets/java-BqvgSBNj.js +272 -0
  50. package/out/renderer/assets/javascript-BuIet4p-.js +69 -0
  51. package/out/renderer/assets/json.worker-DBhrCMTN.js +23259 -0
  52. package/out/renderer/assets/jsonMode-OAArikPQ.js +769 -0
  53. package/out/renderer/assets/julia-D30HQT3I.js +688 -0
  54. package/out/renderer/assets/kotlin-Bql4-FwM.js +303 -0
  55. package/out/renderer/assets/less-D_rhcFqZ.js +245 -0
  56. package/out/renderer/assets/lexon-CjhVPeai.js +188 -0
  57. package/out/renderer/assets/liquid-DJNDWhYP.js +277 -0
  58. package/out/renderer/assets/lspLanguageFeatures-BIH0_3rO.css +24 -0
  59. package/out/renderer/assets/lspLanguageFeatures-Bvh4jxNk.js +2583 -0
  60. package/out/renderer/assets/lua-HQuy0vuU.js +203 -0
  61. package/out/renderer/assets/m3-CIJ7obE2.js +287 -0
  62. package/out/renderer/assets/markdown-I0GroHKS.js +265 -0
  63. package/out/renderer/assets/mdx-B4We-zdi.js +330 -0
  64. package/out/renderer/assets/mips-DY_Z1uin.js +190 -0
  65. package/out/renderer/assets/msdax-YkUW9rXl.js +416 -0
  66. package/out/renderer/assets/mysql-CIm9uV8f.js +921 -0
  67. package/out/renderer/assets/objective-c-JkhNEfNv.js +233 -0
  68. package/out/renderer/assets/pascal-Dvq-PxOC.js +289 -0
  69. package/out/renderer/assets/pascaligo-A9tZTvdV.js +204 -0
  70. package/out/renderer/assets/pdf_viewer-8SNFor7s.js +7639 -0
  71. package/out/renderer/assets/perl-C2Ujz2HB.js +720 -0
  72. package/out/renderer/assets/pgsql-BHB-cYm3.js +879 -0
  73. package/out/renderer/assets/php-DkNik5Vk.js +548 -0
  74. package/out/renderer/assets/pla-DQ-RrdDE.js +149 -0
  75. package/out/renderer/assets/postiats-f1cpw-r_.js +702 -0
  76. package/out/renderer/assets/powerquery-CwYp-rYn.js +940 -0
  77. package/out/renderer/assets/powershell-DNVb682q.js +252 -0
  78. package/out/renderer/assets/protobuf-C73bXEb8.js +663 -0
  79. package/out/renderer/assets/pug-BxvM69yY.js +419 -0
  80. package/out/renderer/assets/python-Hlw9kU7X.js +389 -0
  81. package/out/renderer/assets/qsharp-BCs2AvEL.js +335 -0
  82. package/out/renderer/assets/r-B7wnzr1V.js +282 -0
  83. package/out/renderer/assets/razor-BswxUzye.js +615 -0
  84. package/out/renderer/assets/redis-BQXcxMfX.js +324 -0
  85. package/out/renderer/assets/redshift-2dZdIUPH.js +838 -0
  86. package/out/renderer/assets/restructuredtext-CVT2zSWP.js +290 -0
  87. package/out/renderer/assets/ruby-DjmMI5Dp.js +612 -0
  88. package/out/renderer/assets/rust-BhSCZj0F.js +382 -0
  89. package/out/renderer/assets/sb-DaUIZery.js +146 -0
  90. package/out/renderer/assets/scala-CYIN7D7O.js +589 -0
  91. package/out/renderer/assets/scheme-DanUmhC2.js +173 -0
  92. package/out/renderer/assets/scss-BVILLTDt.js +420 -0
  93. package/out/renderer/assets/shell-Bpairu2a.js +293 -0
  94. package/out/renderer/assets/solidity-OLm4MSRm.js +1395 -0
  95. package/out/renderer/assets/sophia-DJz2Vk1M.js +234 -0
  96. package/out/renderer/assets/sparql-Bk5uRAQt.js +237 -0
  97. package/out/renderer/assets/sql-DQBN9zz5.js +858 -0
  98. package/out/renderer/assets/st-50mV4H0S.js +657 -0
  99. package/out/renderer/assets/swift-Yc0Q28XL.js +376 -0
  100. package/out/renderer/assets/systemverilog-Du7vdcmu.js +616 -0
  101. package/out/renderer/assets/tcl-DG7u3TBj.js +312 -0
  102. package/out/renderer/assets/toggleHighContrast-CKRp-vZ7.css +3691 -0
  103. package/out/renderer/assets/toggleHighContrast-NyS1pk1Z.js +51247 -0
  104. package/out/renderer/assets/twig-C49MsNA9.js +438 -0
  105. package/out/renderer/assets/typescript-F2yeVXIn.js +382 -0
  106. package/out/renderer/assets/typespec-DA1dz9gQ.js +245 -0
  107. package/out/renderer/assets/useAsync-Bi56CNnI.js +30 -0
  108. package/out/renderer/assets/vb-BDNIgfQk.js +447 -0
  109. package/out/renderer/assets/wgsl-j-0nJ_62.js +457 -0
  110. package/out/renderer/assets/xml-BfG5_Ah8.js +131 -0
  111. package/out/renderer/assets/yaml-7gp9FQUG.js +224 -0
  112. package/out/renderer/index.html +14 -0
  113. package/package.json +66 -4
  114. package/resources/skills/nubase-extension-authoring/SKILL.md +273 -0
  115. package/resources/templates/extension/package.json.template +13 -0
  116. package/resources/templates/extension/src/host.ts.template +12 -0
  117. package/resources/templates/extension/src/ui.tsx.template +26 -0
  118. package/resources/templates/extension/tsconfig.json.template +4 -0
  119. package/README.md +0 -3
@@ -0,0 +1,224 @@
1
+ import { c as languages } from "./editor.api-zF37fz_e.js";
2
+ //#region node_modules/monaco-editor/esm/vs/languages/definitions/yaml/yaml.js
3
+ var conf = {
4
+ comments: { lineComment: "#" },
5
+ brackets: [
6
+ ["{", "}"],
7
+ ["[", "]"],
8
+ ["(", ")"]
9
+ ],
10
+ autoClosingPairs: [
11
+ {
12
+ open: "{",
13
+ close: "}"
14
+ },
15
+ {
16
+ open: "[",
17
+ close: "]"
18
+ },
19
+ {
20
+ open: "(",
21
+ close: ")"
22
+ },
23
+ {
24
+ open: "\"",
25
+ close: "\""
26
+ },
27
+ {
28
+ open: "'",
29
+ close: "'"
30
+ }
31
+ ],
32
+ surroundingPairs: [
33
+ {
34
+ open: "{",
35
+ close: "}"
36
+ },
37
+ {
38
+ open: "[",
39
+ close: "]"
40
+ },
41
+ {
42
+ open: "(",
43
+ close: ")"
44
+ },
45
+ {
46
+ open: "\"",
47
+ close: "\""
48
+ },
49
+ {
50
+ open: "'",
51
+ close: "'"
52
+ }
53
+ ],
54
+ folding: { offSide: true },
55
+ onEnterRules: [{
56
+ beforeText: /:\s*$/,
57
+ action: { indentAction: languages.IndentAction.Indent }
58
+ }]
59
+ };
60
+ var language = {
61
+ tokenPostfix: ".yaml",
62
+ brackets: [{
63
+ token: "delimiter.bracket",
64
+ open: "{",
65
+ close: "}"
66
+ }, {
67
+ token: "delimiter.square",
68
+ open: "[",
69
+ close: "]"
70
+ }],
71
+ keywords: [
72
+ "true",
73
+ "True",
74
+ "TRUE",
75
+ "false",
76
+ "False",
77
+ "FALSE",
78
+ "null",
79
+ "Null",
80
+ "Null",
81
+ "~"
82
+ ],
83
+ numberInteger: /(?:0|[+-]?[0-9]+)/,
84
+ numberFloat: /(?:0|[+-]?[0-9]+)(?:\.[0-9]+)?(?:e[-+][1-9][0-9]*)?/,
85
+ numberOctal: /0o[0-7]+/,
86
+ numberHex: /0x[0-9a-fA-F]+/,
87
+ numberInfinity: /[+-]?\.(?:inf|Inf|INF)/,
88
+ numberNaN: /\.(?:nan|Nan|NAN)/,
89
+ numberDate: /\d{4}-\d\d-\d\d([Tt ]\d\d:\d\d:\d\d(\.\d+)?(( ?[+-]\d\d?(:\d\d)?)|Z)?)?/,
90
+ escapes: /\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/,
91
+ tokenizer: {
92
+ root: [
93
+ { include: "@whitespace" },
94
+ { include: "@comment" },
95
+ [/%[^ ]+.*$/, "meta.directive"],
96
+ [/---/, "operators.directivesEnd"],
97
+ [/\.{3}/, "operators.documentEnd"],
98
+ [/[-?:](?= )/, "operators"],
99
+ { include: "@anchor" },
100
+ { include: "@tagHandle" },
101
+ { include: "@flowCollections" },
102
+ { include: "@blockStyle" },
103
+ [/@numberInteger(?![ \t]*\S+)/, "number"],
104
+ [/@numberFloat(?![ \t]*\S+)/, "number.float"],
105
+ [/@numberOctal(?![ \t]*\S+)/, "number.octal"],
106
+ [/@numberHex(?![ \t]*\S+)/, "number.hex"],
107
+ [/@numberInfinity(?![ \t]*\S+)/, "number.infinity"],
108
+ [/@numberNaN(?![ \t]*\S+)/, "number.nan"],
109
+ [/@numberDate(?![ \t]*\S+)/, "number.date"],
110
+ [/(".*?"|'.*?'|[^#'"]*?)([ \t]*)(:)( |$)/, [
111
+ "type",
112
+ "white",
113
+ "operators",
114
+ "white"
115
+ ]],
116
+ { include: "@flowScalars" },
117
+ [/.+?(?=(\s+#|$))/, { cases: {
118
+ "@keywords": "keyword",
119
+ "@default": "string"
120
+ } }]
121
+ ],
122
+ object: [
123
+ { include: "@whitespace" },
124
+ { include: "@comment" },
125
+ [
126
+ /\}/,
127
+ "@brackets",
128
+ "@pop"
129
+ ],
130
+ [/,/, "delimiter.comma"],
131
+ [/:(?= )/, "operators"],
132
+ [/(?:".*?"|'.*?'|[^,\{\[]+?)(?=: )/, "type"],
133
+ { include: "@flowCollections" },
134
+ { include: "@flowScalars" },
135
+ { include: "@tagHandle" },
136
+ { include: "@anchor" },
137
+ { include: "@flowNumber" },
138
+ [/[^\},]+/, { cases: {
139
+ "@keywords": "keyword",
140
+ "@default": "string"
141
+ } }]
142
+ ],
143
+ array: [
144
+ { include: "@whitespace" },
145
+ { include: "@comment" },
146
+ [
147
+ /\]/,
148
+ "@brackets",
149
+ "@pop"
150
+ ],
151
+ [/,/, "delimiter.comma"],
152
+ { include: "@flowCollections" },
153
+ { include: "@flowScalars" },
154
+ { include: "@tagHandle" },
155
+ { include: "@anchor" },
156
+ { include: "@flowNumber" },
157
+ [/[^\],]+/, { cases: {
158
+ "@keywords": "keyword",
159
+ "@default": "string"
160
+ } }]
161
+ ],
162
+ multiString: [[
163
+ /^( +).+$/,
164
+ "string",
165
+ "@multiStringContinued.$1"
166
+ ]],
167
+ multiStringContinued: [[/^( *).+$/, { cases: {
168
+ "$1==$S2": "string",
169
+ "@default": {
170
+ token: "@rematch",
171
+ next: "@popall"
172
+ }
173
+ } }]],
174
+ whitespace: [[/[ \t\r\n]+/, "white"]],
175
+ comment: [[/#.*$/, "comment"]],
176
+ flowCollections: [[
177
+ /\[/,
178
+ "@brackets",
179
+ "@array"
180
+ ], [
181
+ /\{/,
182
+ "@brackets",
183
+ "@object"
184
+ ]],
185
+ flowScalars: [
186
+ [/"([^"\\]|\\.)*$/, "string.invalid"],
187
+ [/'([^'\\]|\\.)*$/, "string.invalid"],
188
+ [/'[^']*'/, "string"],
189
+ [
190
+ /"/,
191
+ "string",
192
+ "@doubleQuotedString"
193
+ ]
194
+ ],
195
+ doubleQuotedString: [
196
+ [/[^\\"]+/, "string"],
197
+ [/@escapes/, "string.escape"],
198
+ [/\\./, "string.escape.invalid"],
199
+ [
200
+ /"/,
201
+ "string",
202
+ "@pop"
203
+ ]
204
+ ],
205
+ blockStyle: [[
206
+ /[>|][0-9]*[+-]?$/,
207
+ "operators",
208
+ "@multiString"
209
+ ]],
210
+ flowNumber: [
211
+ [/@numberInteger(?=[ \t]*[,\]\}])/, "number"],
212
+ [/@numberFloat(?=[ \t]*[,\]\}])/, "number.float"],
213
+ [/@numberOctal(?=[ \t]*[,\]\}])/, "number.octal"],
214
+ [/@numberHex(?=[ \t]*[,\]\}])/, "number.hex"],
215
+ [/@numberInfinity(?=[ \t]*[,\]\}])/, "number.infinity"],
216
+ [/@numberNaN(?=[ \t]*[,\]\}])/, "number.nan"],
217
+ [/@numberDate(?=[ \t]*[,\]\}])/, "number.date"]
218
+ ],
219
+ tagHandle: [[/\![^ ]*/, "tag"]],
220
+ anchor: [[/[&*][^ ]+/, "namespace"]]
221
+ }
222
+ };
223
+ //#endregion
224
+ export { conf, language };
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en" data-theme="dark">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Nubase</title>
7
+ <script type="module" crossorigin src="./assets/index-CQHlaLTi.js"></script>
8
+ <link rel="modulepreload" crossorigin href="./assets/fileTypes-nBt-Y6NI.js">
9
+ <link rel="stylesheet" crossorigin href="./assets/index-DBYJcRdn.css">
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ </body>
14
+ </html>
package/package.json CHANGED
@@ -1,6 +1,68 @@
1
1
  {
2
2
  "name": "@nubase/terminal",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Nubase: an AI harness with a real, self-extending UI",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./out/main/index.js",
8
+ "bin": {
9
+ "nubase": "bin/nubase.js"
10
+ },
11
+ "files": [
12
+ "bin",
13
+ "out",
14
+ "resources"
15
+ ],
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "scripts": {
20
+ "dev": "electron-vite dev --watch",
21
+ "start": "electron-vite preview",
22
+ "build": "electron-vite build",
23
+ "postinstall": "electron-rebuild -f -o node-pty || true",
24
+ "test": "vitest run",
25
+ "typecheck": "tsc --noEmit",
26
+ "lint": "biome check .",
27
+ "lint:fix": "biome check . --write --unsafe"
28
+ },
29
+ "dependencies": {
30
+ "@earendil-works/pi-coding-agent": "1.5.2",
31
+ "@nubase/sdk": "*",
32
+ "@nubase/ui": "*",
33
+ "@tailwindcss/node": "^4.3.5",
34
+ "@tailwindcss/oxide": "^4.3.5",
35
+ "@types/node": "^22.19.0",
36
+ "@types/react": "^19.2.14",
37
+ "@types/react-dom": "^19.2.3",
38
+ "electron": "^45.3.2",
39
+ "esbuild": "^0.27.7",
40
+ "lucide-react": "^0.577.0",
41
+ "node-pty": "^1.1.0",
42
+ "tailwindcss": "^4.3.1",
43
+ "tw-animate-css": "^1.4.0",
44
+ "typescript": "^5.9.3",
45
+ "yauzl": "^3.4.0"
46
+ },
47
+ "devDependencies": {
48
+ "@electron/rebuild": "^4.2.1",
49
+ "@tailwindcss/vite": "^4.3.5",
50
+ "@types/papaparse": "^5.5.2",
51
+ "@types/yauzl": "^3.4.0",
52
+ "@vitejs/plugin-react": "^6.0.3",
53
+ "@xterm/addon-fit": "^0.11.0",
54
+ "@xterm/xterm": "^6.0.0",
55
+ "docx-preview": "^0.4.1",
56
+ "electron-vite": "^6.0.0",
57
+ "monaco-editor": "^0.57.0",
58
+ "papaparse": "^5.7.1",
59
+ "pdfjs-dist": "^6.5.438",
60
+ "react": "^19.2.5",
61
+ "react-dom": "^19.2.5",
62
+ "react-markdown": "^10.1.0",
63
+ "remark-gfm": "^4.0.1",
64
+ "vite": "^8.1.0",
65
+ "vitest": "^4.1.9",
66
+ "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
67
+ }
68
+ }
@@ -0,0 +1,273 @@
1
+ ---
2
+ name: nubase-extension-authoring
3
+ description: How to build Nubase extensions — widgets, commands, keybindings, themes and agent tools — that hot-load into the running Nubase UI. Read before creating or editing anything in the Nubase extensions folder.
4
+ ---
5
+
6
+ # Writing Nubase extensions
7
+
8
+ Nubase's UI is a dock of blocks. Each block shows a **widget**. Widgets,
9
+ commands, keybindings, themes and agent tools come from **extensions**:
10
+ small TypeScript projects in the Nubase extensions folder
11
+ (`~/.nubase/extensions/<id>/`). Built-in widgets use exactly this API.
12
+
13
+ Each top-level tab is a folder. `~/.nubase/extensions` is **global**: those
14
+ extensions load in every folder. A folder can also have its own
15
+ `<folder>/.nubase/extensions/<id>/`, which loads only in that folder's tab
16
+ and replaces a global extension with the same id. Create global extensions
17
+ unless the user asks for one specific to the current folder or project
18
+ (`nubase_create_extension` with `scope: "folder"`). Both kinds have the same
19
+ layout and API.
20
+
21
+ ## Workflow
22
+
23
+ 1. `nubase_create_extension` with a kebab-case `id` (set `withHost: true` if
24
+ you need secrets, HTTP without CORS, Node APIs, or agent tools).
25
+ 2. Edit `src/ui.tsx` (and `src/host.ts`). Never edit `dist/` or
26
+ `../tsconfig.base.json`.
27
+ 3. `nubase_build_extension` — fix **every** error it reports (esbuild,
28
+ TypeScript, Tailwind) and build again until it succeeds. The previous
29
+ working version stays loaded while a build is broken.
30
+ 4. `nubase_open_widget` with the widget id, then `nubase_screenshot` with the
31
+ returned block id to check how it looks. Iterate on visuals.
32
+ 5. Runtime errors: `nubase_extension_logs`. Bad change? `nubase_revert_extension`.
33
+
34
+ Successful builds are committed to git automatically.
35
+
36
+ ## Layout of an extension
37
+
38
+ ```
39
+ <id>/
40
+ package.json { "nubase": { "id", "displayName", "description", "ui": "src/ui.tsx", "host": "src/host.ts" } }
41
+ tsconfig.json { "extends": "../tsconfig.base.json", "include": ["src"] }
42
+ src/ui.tsx renderer half (React) — optional
43
+ src/host.ts Node half (Electron main) — optional
44
+ ```
45
+
46
+ `nubase.id` must equal the folder name. Widget and command ids should be
47
+ namespaced with the extension id: `jira.myTickets`, `jira.refresh`.
48
+
49
+ ## Available imports
50
+
51
+ Provided by Nubase at runtime (do **not** npm install these):
52
+
53
+ - `react`, `react-dom`, `react/jsx-runtime`
54
+ - `@nubase/sdk` (ui half) and `@nubase/sdk/host` (host half)
55
+ - `@nubase/ui` — components and utilities (below)
56
+ - `lucide-react` — icons
57
+
58
+ Any other npm package: run `npm install <pkg>` inside the extension folder;
59
+ it is bundled at build time. Prefer `fetch` over HTTP client libraries.
60
+
61
+ ## UI half (`src/ui.tsx`)
62
+
63
+ ```tsx
64
+ import { defineUiExtension, type WidgetProps } from "@nubase/sdk";
65
+ import { Button, Card, CardContent, CardHeader, CardTitle } from "@nubase/ui";
66
+ import { Ticket } from "lucide-react";
67
+
68
+ type State = { filter: string };
69
+
70
+ const TicketsWidget = ({ ctx, state, setState, setTitle, isFocused }: WidgetProps<State>) => {
71
+ // state: persisted per block (lives in the layout; survives reloads/restarts)
72
+ // setState(next): persist new state — keep it small & JSON-serializable
73
+ // setTitle("…"): block header title; ctx: the UiContext (see below)
74
+ return <div className="h-full overflow-auto p-3">…</div>;
75
+ };
76
+
77
+ export default defineUiExtension({
78
+ activate(ctx) {
79
+ ctx.widgets.register<State>({
80
+ id: "jira.myTickets",
81
+ title: "my tickets", // short, lowercase reads best in the launcher
82
+ description: "Jira tickets assigned to me",
83
+ icon: Ticket,
84
+ defaultState: { filter: "" },
85
+ singleton: false, // true → only one instance, reopening focuses it
86
+ component: TicketsWidget,
87
+ });
88
+ ctx.commands.register({
89
+ id: "jira.refresh",
90
+ title: "Refresh Tickets",
91
+ category: "Jira",
92
+ execute: async () => { /* … */ },
93
+ });
94
+ ctx.keybindings.register({ key: "meta+shift+j", command: "jira.refresh" });
95
+ // Optional: return extra Disposables (timers, listeners) to clean up on reload.
96
+ },
97
+ });
98
+ ```
99
+
100
+ Everything registered through `ctx` is removed automatically when the
101
+ extension reloads. Widgets are remounted on every rebuild — keep anything
102
+ that must survive in `state`, `ctx.storage` or the host half.
103
+
104
+ ### UiContext (`ctx`)
105
+
106
+ | API | Purpose |
107
+ | --- | --- |
108
+ | `ctx.widgets.register(def)` | Add a widget type (shows in the launcher) |
109
+ | `ctx.commands.register({id,title,category?,execute})` / `.execute(id,args)` | Commands appear in the ⌘K palette; the agent can run them too |
110
+ | `ctx.keybindings.register({key, command, args?, when?})` | `key` like `meta+k`, chords `"ctrl+k ctrl+c"`; `when` e.g. `focusedWidget == jira.myTickets` |
111
+ | `ctx.themes.register(theme)` / `.setActive(id)` / `.getAll()` / `.active()` | Contribute or switch color themes |
112
+ | `ctx.layout.openWidget(id, {placement, state})` / `.closeBlock` / `.focusBlock` | Arrange the dock (`placement`: right, below, left, above, replace) |
113
+ | `ctx.host.call<T>(method, ...args)` / `ctx.host.on(event, fn)` | Talk to **this extension's** host half |
114
+ | `ctx.workspace` | The folder this tab is open on: `{ id, folder, title, isHome }`. Default to `ctx.workspace.folder` for anything path-related |
115
+ | `ctx.fs` | `readText`, `writeText`, `stat`, `readDirectory`, `mkdir`, `delete`, `rename`, `watch`, `homeDir` (absolute paths); `resourceUrl(path)` — a URL for `<img>`/`<video>`/`<iframe>`/`fetch` (Range supported) that streams the file, for binary or large files; `thumbnail(path, size?)` — OS thumbnail data URL (macOS/Windows); `describe(path)` — a `PreviewFile` |
116
+ | `ctx.previewers.register(def)` | Add a file viewer to the files widget (see below) |
117
+ | `ctx.shell` | `openPath(path)` (default app), `showItemInFolder(path)`, `quickLook(path)` (macOS) |
118
+ | `ctx.agent` | `prompt(text, images?)` (images: `{ data: base64, mimeType }[]`), `onEvent`, `getMessages`, `getState`, … — drive the AI agent |
119
+ | `ctx.storage.get/set(key, value)` | Small persisted JSON per extension |
120
+ | `ctx.dialogs.confirm/input/select` | Native-looking modal dialogs (promise-based) |
121
+ | `ctx.notify(message, "info" \| "error")` | Toast |
122
+ | `ctx.openExternal(url)` | Open in the user's browser |
123
+
124
+ ### File previewers
125
+
126
+ The files widget shows each file with the highest-priority previewer that
127
+ accepts it; the others appear in its "view as" menu. Add one for a format
128
+ the user cares about (parquet, sqlite, GeoJSON, …) instead of a new widget:
129
+
130
+ ```tsx
131
+ import { definePreviewer, type PreviewerProps } from "@nubase/sdk";
132
+ import { lazy } from "react";
133
+
134
+ const GeoJsonMap = ({ file, ctx }: PreviewerProps) => {
135
+ // file: { path, name, extension, size, mtimeMs, url, isBinary }
136
+ // file.url streams the bytes and changes when the file changes: depend on it in effects.
137
+ // fetch(file.url).then((r) => r.json()) …, or <img src={file.url} />
138
+ return <div className="h-full overflow-auto p-3">…</div>;
139
+ };
140
+
141
+ // in activate(ctx):
142
+ ctx.previewers.register(
143
+ definePreviewer({
144
+ id: "geo.map",
145
+ title: "Map", // label in the "view as" menu
146
+ extensions: ["geojson"], // lowercase, no dot
147
+ match: (file) => file.size < 50e6, // optional extra test
148
+ priority: 100, // built-ins: viewers 100, text editor 10, hex -50, info -100
149
+ component: GeoJsonMap, // React.lazy(() => import("./heavy")) for big libraries
150
+ }),
151
+ );
152
+ ```
153
+
154
+ Registering an id a built-in already uses (e.g. `nubase.preview.pdf`)
155
+ replaces that viewer. To show a file to the user, run the
156
+ `files.preview` command: `ctx.commands.execute("files.preview", { path, placement: "right" })`.
157
+
158
+ ## Host half (`src/host.ts`)
159
+
160
+ Runs in Node inside Nubase. Use it for secrets, network calls (no CORS),
161
+ the file system, child processes, and agent tools.
162
+
163
+ ```ts
164
+ import { defineHostExtension } from "@nubase/sdk/host";
165
+
166
+ export default defineHostExtension({
167
+ activate(ctx) {
168
+ const token = async () =>
169
+ (await ctx.secrets.get("token")) ??
170
+ (await ctx.secrets.prompt("token", "Jira API token (id.atlassian.com → Security)"));
171
+
172
+ const search = async (jql: string) => {
173
+ const res = await fetch(`https://example.atlassian.net/rest/api/3/search?jql=${encodeURIComponent(jql)}`, {
174
+ headers: { Authorization: `Bearer ${await token()}`, Accept: "application/json" },
175
+ });
176
+ if (!res.ok) throw new Error(`Jira ${res.status}: ${await res.text()}`);
177
+ return (await res.json()).issues as { key: string; fields: { summary: string } }[];
178
+ };
179
+
180
+ return [
181
+ ctx.rpc.handle("myTickets", () => search("assignee = currentUser() AND resolution = Unresolved")),
182
+ ctx.agent.registerTool({
183
+ name: "jira_search", // snake_case, globally unique
184
+ description: "Search Jira issues with JQL. Returns key and summary.",
185
+ parameters: { // JSON Schema
186
+ type: "object",
187
+ properties: { jql: { type: "string" } },
188
+ required: ["jql"],
189
+ },
190
+ execute: async ({ jql }: { jql: string }) =>
191
+ (await search(jql)).map((i) => `${i.key}: ${i.fields.summary}`).join("\n") || "No issues.",
192
+ }),
193
+ ];
194
+ },
195
+ });
196
+ ```
197
+
198
+ | API | Purpose |
199
+ | --- | --- |
200
+ | `ctx.rpc.handle(method, fn)` | Expose a method to this extension's widgets (`ctx.host.call`) |
201
+ | `ctx.rpc.emit(event, payload)` | Push events to widgets (`ctx.host.on`) |
202
+ | `ctx.secrets.get/set/delete(key)`, `ctx.secrets.prompt(key, title)` | Encrypted secrets; `prompt` asks the user in a masked dialog. **Never** ask for secrets in chat or put them in code |
203
+ | `ctx.storage.get/set` | Same store as the ui half's `ctx.storage` |
204
+ | `ctx.agent.registerTool({...})` | Give the AI agent a new tool (available from its next turn) |
205
+ | `ctx.workspace` | The folder this activation belongs to. A host half is activated **once per open folder tab**, so keep per-folder state inside `activate` |
206
+ | `ctx.fs`, `ctx.log.info/warn/error` | File system; logs visible via `nubase_extension_logs` |
207
+
208
+ Calling the host from a widget, with loading and error states:
209
+
210
+ ```tsx
211
+ const [issues, setIssues] = useState<Issue[]>();
212
+ const [error, setError] = useState<string>();
213
+ useEffect(() => {
214
+ ctx.host.call<Issue[]>("myTickets").then(setIssues, (e) => setError(String(e)));
215
+ }, [ctx]);
216
+ if (error) return <Callout variant="danger" className="m-3">{error}</Callout>;
217
+ if (!issues) return <div className="flex h-full items-center justify-center"><ActivityIndicator /></div>;
218
+ ```
219
+
220
+ ## Look and feel
221
+
222
+ Widgets must look native. Use Tailwind classes with **theme tokens only** —
223
+ never hard-coded colors — so every theme works:
224
+
225
+ - Surfaces: `bg-background`, `bg-card`, `bg-muted`, `bg-secondary`, `bg-accent`
226
+ - Text: `text-foreground`, `text-muted-foreground`, `text-primary`, `text-destructive`
227
+ - Borders: `border` (uses `border-border`), `border-input`, `ring-ring`
228
+ - Accents for data: `text-chart-1` … `text-chart-5`, `bg-chart-2/15` (opacity modifiers work)
229
+ - Radius: `rounded-md`, `rounded-lg`; type scale `text-xs`/`text-sm` for dense UI
230
+
231
+ The widget fills its block: make the root `h-full` and let content scroll
232
+ with `overflow-auto`. Use `text-sm` by default, `p-3` padding, `gap-2`.
233
+
234
+ Nubase is compact like an editor, and its density comes from theme tokens:
235
+ the root font size, Tailwind's spacing unit and `--radius`. Size things with
236
+ the Tailwind scale (`text-xs`, `p-2`, `h-8`, `rounded-md`) so they follow it.
237
+ Never use pixel font sizes (`text-[13px]`, `style={{ fontSize: 13 }}`). For
238
+ renderers that need pixels (canvas, Monaco, xterm), read the tokens with
239
+ `readCssPixels("--code-font-size", 12)` from `@nubase/ui` (also
240
+ `--code-line-height`).
241
+ Mark copyable text with the `selectable` class (the app disables text
242
+ selection by default).
243
+
244
+ `@nubase/ui` components:
245
+
246
+ - `Button` (`variant`: default, secondary, outline, ghost, destructive; `size`: default, sm, icon, icon-sm; `isLoading`)
247
+ - `ActivityIndicator` (`size`: xs…xl) — use for every loading state, never a custom spinner
248
+ - `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`, `CardAction`
249
+ - `Callout` (`variant`: info, danger)
250
+ - `TextInput`, `SearchTextInput`, `Select` (`options: {value,label}[]`, `value`, `onChange`), `Checkbox`, `Switch`, `Label`, `FormControl` (`label`, `hint`, `error`)
251
+ - `InputGroup` + `InputGroupInput` / `InputGroupTextarea` + `InputGroupAddon` — a field with icons or buttons inside (search box, chat composer). Add `className="h-auto"` to the group around a textarea so it grows
252
+ - `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`
253
+ - `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem`, `DropdownMenuSeparator`, …
254
+ - `Toggle`, `ButtonBar`, `cn(...classes)` for conditional classes
255
+ - `showToast(message, "default" | "error")`
256
+
257
+ ## Rules
258
+
259
+ - **Never write a raw `<input>`, `<textarea>` or `<select>`.** Use the
260
+ `@nubase/ui` field components above. They all share one definition of the
261
+ field surface and focus style, so a hand-styled field always looks out of
262
+ place. Built-in widgets are linted for this. Change size with `className`
263
+ (e.g. `h-6 text-xs` in a toolbar), never the border, background or focus
264
+ styles.
265
+
266
+ - Export the extension with `export default defineUiExtension(...)` /
267
+ `export default defineHostExtension(...)`.
268
+ - Keep widget state JSON-serializable and small; cache bulky data in the host
269
+ half or `ctx.storage`.
270
+ - Clean up intervals and subscriptions in `useEffect` cleanups.
271
+ - Handle errors visibly in the widget (Callout) — don't fail silently.
272
+ - Polling: at most every 30s unless the user asks otherwise.
273
+ - After building, always screenshot the widget and fix visual problems.
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "nubase-extension-__ID__",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "nubase": {
7
+ "id": "__ID__",
8
+ "displayName": "__DISPLAY_NAME__",
9
+ "description": "__DESCRIPTION__",
10
+ "ui": "src/ui.tsx",
11
+ "host": "src/host.ts"
12
+ }
13
+ }
@@ -0,0 +1,12 @@
1
+ import { defineHostExtension } from "@nubase/sdk/host";
2
+
3
+ // Runs in Node (Electron main process). Use it for secrets, HTTP calls
4
+ // without CORS, the file system, and tools for the agent.
5
+ export default defineHostExtension({
6
+ activate(ctx) {
7
+ return [
8
+ // Widgets call this with: ctx.host.call<string>("hello", "world")
9
+ ctx.rpc.handle("hello", (name: string) => `Hello, ${name}!`),
10
+ ];
11
+ },
12
+ });
@@ -0,0 +1,26 @@
1
+ import { defineUiExtension, type WidgetProps } from "@nubase/sdk";
2
+ import { Button } from "@nubase/ui";
3
+ import { Sparkles } from "lucide-react";
4
+
5
+ type State = { count: number };
6
+
7
+ const MainWidget = ({ state, setState }: WidgetProps<State>) => (
8
+ <div className="flex h-full flex-col items-center justify-center gap-3 p-4">
9
+ <p className="text-muted-foreground text-sm">__DISPLAY_NAME__</p>
10
+ <p className="font-semibold text-3xl">{state.count}</p>
11
+ <Button onClick={() => setState({ count: state.count + 1 })}>Increment</Button>
12
+ </div>
13
+ );
14
+
15
+ export default defineUiExtension({
16
+ activate(ctx) {
17
+ ctx.widgets.register<State>({
18
+ id: "__WIDGET_ID__",
19
+ title: "__DISPLAY_NAME__",
20
+ description: "__DESCRIPTION__",
21
+ icon: Sparkles,
22
+ defaultState: { count: 0 },
23
+ component: MainWidget,
24
+ });
25
+ },
26
+ });
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "../tsconfig.base.json",
3
+ "include": ["src"]
4
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.