@tishlang/tish-desktop 1.0.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 (37) hide show
  1. package/LICENSE +13 -0
  2. package/bin/mode.js +73 -0
  3. package/package.json +45 -0
  4. package/src/commands/build.tish +55 -0
  5. package/src/commands/dev.tish +222 -0
  6. package/src/commands/distribute.tish +56 -0
  7. package/src/commands/doctor.tish +91 -0
  8. package/src/commands/icon.tish +42 -0
  9. package/src/commands/info.tish +68 -0
  10. package/src/commands/init.tish +198 -0
  11. package/src/commands/ios.tish +124 -0
  12. package/src/main.tish +105 -0
  13. package/src/paths.tish +215 -0
  14. package/templates/app/bridge-boot.js +6 -0
  15. package/templates/app/index.html +20 -0
  16. package/templates/app/package.json +32 -0
  17. package/templates/app/package.registry.json +32 -0
  18. package/templates/app/public/assets/.gitkeep +0 -0
  19. package/templates/app/scripts/build-css.tish +25 -0
  20. package/templates/app/src/main.tish +27 -0
  21. package/templates/app/ui/main.tish +43 -0
  22. package/templates/app/vite.config.mjs +38 -0
  23. package/templates/app/vite.registry.config.mjs +14 -0
  24. package/templates/bare/app/App.tish +27 -0
  25. package/templates/bare/app/Button.tish +11 -0
  26. package/templates/bare/app/Button.web.tish +11 -0
  27. package/templates/bare/app/Button.webview.tish +11 -0
  28. package/templates/bare/bridge-boot.js +6 -0
  29. package/templates/bare/index.html +20 -0
  30. package/templates/bare/package.json +31 -0
  31. package/templates/bare/package.registry.json +31 -0
  32. package/templates/bare/public/assets/.gitkeep +0 -0
  33. package/templates/bare/scripts/build-css.tish +35 -0
  34. package/templates/bare/src/main.tish +34 -0
  35. package/templates/bare/ui/main.tish +8 -0
  36. package/templates/bare/vite.config.mjs +39 -0
  37. package/templates/bare/vite.registry.config.mjs +19 -0
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Pay It Forward License (PIF)
2
+
3
+ Copyright (c) 2026-present The Tish Project Authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ This license includes a perpetual, worldwide, non-exclusive, royalty-free, irrevocable (except as stated below) grant for patent claims necessarily infringed by the use of the Software or any contributions submitted to it; terminating automatically when an entity initiates patent litigation (including a cross-claim or counterclaim) alleging that the Software or any portion thereof infringes a patent.
10
+
11
+ The software is provided "as is," without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software.
12
+
13
+ Recipients of the Software should pay it forward by contributing to the open-source community through actions including but not limited to providing code, documentation, tutorials, guides, support, feedback, or promoting open-source projects through advocacy or related efforts that advance innovation and community growth.
package/bin/mode.js ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ // npx entrypoint for `mode` (@tishlang/tish-desktop).
3
+ // Prefers the prebuilt native binary (`npm run build` → dist/mode);
4
+ // otherwise runs src/main.tish through `tish` with process+fs features.
5
+ //
6
+ // Rewrite `--platform` / `--surface` → `--desk-platform` / `--desk-surface` and
7
+ // strip TISH_PLATFORM/TISH_SURFACE from the host env. Those tokens make
8
+ // `tish run` of this CLI exit before the command body runs; doctor passes the
9
+ // desk-* values through to `tish resolve-id` only.
10
+
11
+ import { spawnSync } from "node:child_process"
12
+ import { existsSync } from "node:fs"
13
+ import path from "node:path"
14
+ import { fileURLToPath } from "node:url"
15
+
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
17
+ const root = path.join(__dirname, "..")
18
+ const nativeBin = path.join(
19
+ root,
20
+ "dist",
21
+ process.platform === "win32" ? "mode.exe" : "mode"
22
+ )
23
+ const FEATURES = "process,fs"
24
+
25
+ function rewritePlatformSurface(argv) {
26
+ const env = { ...process.env }
27
+ delete env.TISH_PLATFORM
28
+ delete env.TISH_SURFACE
29
+
30
+ const out = []
31
+ for (let i = 0; i < argv.length; i++) {
32
+ const a = argv[i]
33
+ if (a === "--platform" && i + 1 < argv.length) {
34
+ out.push("--desk-platform", argv[++i])
35
+ continue
36
+ }
37
+ if (a === "--surface" && i + 1 < argv.length) {
38
+ out.push("--desk-surface", argv[++i])
39
+ continue
40
+ }
41
+ if (typeof a === "string" && a.startsWith("--platform=")) {
42
+ out.push("--desk-platform", a.slice("--platform=".length))
43
+ continue
44
+ }
45
+ if (typeof a === "string" && a.startsWith("--surface=")) {
46
+ out.push("--desk-surface", a.slice("--surface=".length))
47
+ continue
48
+ }
49
+ out.push(a)
50
+ }
51
+ return { args: out, env }
52
+ }
53
+
54
+ const rawArgs = process.argv.slice(2)
55
+ const { args, env } = rewritePlatformSurface(rawArgs)
56
+
57
+ let result
58
+ if (existsSync(nativeBin)) {
59
+ result = spawnSync(nativeBin, rawArgs, { stdio: "inherit", env })
60
+ } else {
61
+ result = spawnSync(
62
+ "tish",
63
+ ["run", "--feature", FEATURES, path.join(root, "src/main.tish"), ...args],
64
+ { stdio: "inherit", cwd: root, env }
65
+ )
66
+ if (result.error && result.error.code === "ENOENT") {
67
+ process.stderr.write(
68
+ "mode: no prebuilt binary and `tish` is not installed. Run `npm run build` in cli/ first.\n"
69
+ )
70
+ process.exit(127)
71
+ }
72
+ }
73
+ process.exit(result.status === null ? 1 : result.status)
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@tishlang/tish-desktop",
3
+ "version": "1.0.0",
4
+ "description": "Tish Desktop CLI — init, dev, build, info, icon, distribute",
5
+ "license": "PIF",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/tishlang/tish-desktop.git",
10
+ "directory": "cli"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "bin": {
16
+ "mode": "bin/mode.js"
17
+ },
18
+ "files": [
19
+ "bin",
20
+ "src",
21
+ "templates",
22
+ "LICENSE"
23
+ ],
24
+ "tish": {
25
+ "source": "./src/main.tish",
26
+ "features": [
27
+ "process",
28
+ "fs"
29
+ ]
30
+ },
31
+ "scripts": {
32
+ "dev": "tish run --feature process,fs src/main.tish",
33
+ "build": "tish build --feature process,fs --target native --native-backend rust src/main.tish -o dist/mode",
34
+ "start": "node bin/mode.js"
35
+ },
36
+ "engines": {
37
+ "node": ">=20"
38
+ },
39
+ "keywords": [
40
+ "tish",
41
+ "desktop",
42
+ "tauri",
43
+ "cli"
44
+ ]
45
+ }
@@ -0,0 +1,55 @@
1
+ import { exit, cwd, exec } from "tish:process"
2
+ import {
3
+ firstPositional,
4
+ joinPath,
5
+ pathExists,
6
+ q,
7
+ repoRoot,
8
+ resolveTish,
9
+ } from "../paths.tish"
10
+
11
+ fn sh(cmd) {
12
+ console.log("-> " + cmd)
13
+ let code = exec(cmd)
14
+ if (code !== 0) {
15
+ console.error("x failed (exit " + String(code) + "): " + cmd)
16
+ exit(code)
17
+ }
18
+ }
19
+
20
+ export fn runBuild(args) {
21
+ let root = repoRoot()
22
+ let name = firstPositional(args)
23
+ let projectDir = cwd()
24
+
25
+ if (name !== null && pathExists(joinPath(root, "examples/" + name))) {
26
+ projectDir = joinPath(root, "examples/" + name)
27
+ } else if (name !== null) {
28
+ let candidate = name
29
+ if (!String(name).startsWith("/")) candidate = joinPath(cwd(), name)
30
+ if (pathExists(candidate)) projectDir = candidate
31
+ }
32
+
33
+ if (!pathExists(joinPath(projectDir, "package.json"))) {
34
+ console.error("build: no package.json in " + projectDir)
35
+ exit(1)
36
+ }
37
+
38
+ let tishBin = resolveTish()
39
+ console.log("[build] project " + projectDir)
40
+
41
+ sh("cd " + q(projectDir) + " && npm run build:ui")
42
+
43
+ // Prefer package script; fall back to a conventional shell output path.
44
+ let shellCode = exec("cd " + q(projectDir) + " && npm run build:shell")
45
+ if (shellCode !== 0) {
46
+ let out = "dist/app-shell"
47
+ console.log("[build] npm run build:shell missing/failed — compiling " + out)
48
+ sh(
49
+ "cd " + q(projectDir) + " && " + q(tishBin) +
50
+ " build --target native --native-backend rust src/main.tish -o " + q(out)
51
+ )
52
+ }
53
+
54
+ console.log("[build] done → " + projectDir + "/dist/")
55
+ }
@@ -0,0 +1,222 @@
1
+ // Port of scripts/dev-example.mjs — Tish-first via tish:process + tish:fs.
2
+ // tish:process has exec (blocking) but no spawn; long-running Vite is
3
+ // backgrounded with `sh -c '… &'`. Pass --legacy-node to call the Node script.
4
+ import { exit, cwd, exec, env } from "tish:process"
5
+ import { readFile } from "tish:fs"
6
+ import {
7
+ flagValue,
8
+ firstPositional,
9
+ hasFlag,
10
+ joinPath,
11
+ pathExists,
12
+ q,
13
+ repoRoot,
14
+ resolveTish,
15
+ } from "../paths.tish"
16
+
17
+ fn examplePort(name) {
18
+ if (name === "file-browser") return 5174
19
+ if (name === "native-chrome") return 5175
20
+ if (name === "byo-ui") return 5176
21
+ if (name === "hybrid") return 5177
22
+ return 5173
23
+ }
24
+
25
+ fn exampleShellOut(name) {
26
+ if (name === "basic") return "dist/basic-shell"
27
+ if (name === "file-browser") return "dist/file-browser-shell"
28
+ if (name === "native-chrome") return "dist/native-chrome-shell"
29
+ if (name === "byo-ui") return "dist/byo-ui-shell"
30
+ if (name === "hybrid") return "dist/hybrid-shell"
31
+ return "dist/" + name + "-shell"
32
+ }
33
+
34
+ fn sleepMs(ms) {
35
+ exec("sleep " + String(ms / 1000))
36
+ }
37
+
38
+ fn httpCode(url) {
39
+ let out = "/tmp/tish-desktop-http-code.txt"
40
+ exec("curl -s -o /dev/null -w '%{http_code}' " + q(url) + " > " + q(out) + " 2>/dev/null || echo 000 > " + q(out))
41
+ if (!pathExists(out)) return "000"
42
+ return String(readFile(out)).trim()
43
+ }
44
+
45
+ fn waitForHttp(url, timeoutMs) {
46
+ let maxTries = Math.floor(timeoutMs / 250)
47
+ if (maxTries < 1) maxTries = 1
48
+ let n = 0
49
+ while (n < maxTries) {
50
+ let code = httpCode(url)
51
+ let num = Number(code)
52
+ if (num >= 200 && num < 500) return true
53
+ sleepMs(250)
54
+ n = n + 1
55
+ }
56
+ return false
57
+ }
58
+
59
+ fn warmupVite(baseUrl) {
60
+ let urls = [
61
+ baseUrl + "/",
62
+ baseUrl + "/assets/app.css",
63
+ baseUrl + "/bridge-boot.js",
64
+ baseUrl + "/ui/main.tish",
65
+ ]
66
+ let i = 0
67
+ while (i < urls.length) {
68
+ exec("curl -s -o /dev/null " + q(urls[i]) + " >/dev/null 2>&1 || true")
69
+ i = i + 1
70
+ }
71
+ let bodyFile = "/tmp/tish-desktop-vite-main.txt"
72
+ exec(
73
+ "curl -s -H 'Accept: text/javascript' " + q(baseUrl + "/ui/main.tish") +
74
+ " > " + q(bodyFile) + " 2>/dev/null || true"
75
+ )
76
+ if (pathExists(bodyFile)) {
77
+ let text = String(readFile(bodyFile))
78
+ let parts = text.split('from "')
79
+ let p = 1
80
+ while (p < parts.length) {
81
+ let rest = parts[p]
82
+ let end = rest.indexOf('"')
83
+ if (end > 0) {
84
+ let spec = rest.slice(0, end)
85
+ let next = null
86
+ if (spec.startsWith("./")) {
87
+ next = baseUrl + "/ui/" + spec.slice(2)
88
+ } else if (spec.startsWith("/")) {
89
+ next = baseUrl + spec
90
+ }
91
+ if (next !== null) {
92
+ exec(
93
+ "curl -s -o /dev/null -H 'Accept: text/javascript' " + q(next) +
94
+ " >/dev/null 2>&1 || true"
95
+ )
96
+ }
97
+ }
98
+ p = p + 1
99
+ }
100
+ }
101
+ console.log("[dev] warmed key Vite modules via curl")
102
+ }
103
+
104
+ fn buildShell(tishBin, exampleDir, shellOut, shellPath) {
105
+ console.log("[dev] building shell with " + tishBin + " → " + shellOut)
106
+ let code = exec(
107
+ "cd " + q(exampleDir) + " && " + q(tishBin) +
108
+ " build --target native --native-backend rust src/main.tish -o " + q(shellOut)
109
+ )
110
+ if (code !== 0) {
111
+ console.error("[dev] shell build failed (exit " + String(code) + ")")
112
+ exit(code)
113
+ }
114
+ if (!pathExists(shellPath)) {
115
+ console.error("[dev] shell binary missing after build: " + shellPath)
116
+ exit(1)
117
+ }
118
+ }
119
+
120
+ fn shellNeedsRebuild(root, exampleDir, shellPath) {
121
+ if (!pathExists(shellPath)) return true
122
+ // Recursive: any file under host/shell sources newer than the binary.
123
+ let marker = "/tmp/tish-desktop-shell-stale.txt"
124
+ let hostSrc = joinPath(root, "crates/tish_desktop/src")
125
+ let hostToml = joinPath(root, "crates/tish_desktop/Cargo.toml")
126
+ let shellSrc = joinPath(exampleDir, "src")
127
+ exec(
128
+ "if [ -n \"$(find " + q(hostSrc) + " " + q(shellSrc) + " " + q(hostToml) +
129
+ " -type f -newer " + q(shellPath) + " 2>/dev/null | head -1)\" ]; then echo yes; else echo no; fi > " +
130
+ q(marker)
131
+ )
132
+ if (!pathExists(marker)) return true
133
+ return String(readFile(marker)).trim() === "yes"
134
+ }
135
+
136
+ fn killVite(port) {
137
+ exec("pkill -f " + q("vite --port " + String(port)) + " >/dev/null 2>&1 || true")
138
+ }
139
+
140
+ export fn runDev(args) {
141
+ let root = repoRoot()
142
+ let forceRebuild = hasFlag(args, "--rebuild")
143
+ let legacyNode = hasFlag(args, "--legacy-node")
144
+ if (env !== null && env["TISH_DESKTOP_DEV_LEGACY"] === "1") {
145
+ legacyNode = true
146
+ }
147
+
148
+ let name = flagValue(args, "--example")
149
+ if (name === null) name = firstPositional(args)
150
+ if (name === null) name = "basic"
151
+
152
+ if (legacyNode) {
153
+ let script = joinPath(root, "scripts/dev-example.mjs")
154
+ console.log("[dev] --legacy-node → node " + script)
155
+ let rebuildFlag = forceRebuild ? " --rebuild" : ""
156
+ let code = exec("node " + q(script) + " " + q(name) + rebuildFlag)
157
+ exit(code)
158
+ }
159
+
160
+ let exampleDir = joinPath(root, "examples/" + name)
161
+ if (!pathExists(exampleDir)) {
162
+ let here = cwd()
163
+ if (pathExists(joinPath(here, "src/main.tish")) && pathExists(joinPath(here, "package.json"))) {
164
+ exampleDir = here
165
+ name = "app"
166
+ } else {
167
+ console.error("[dev] example not found: " + exampleDir)
168
+ console.error(" pass --example basic|file-browser|native-chrome|byo-ui|hybrid, or run inside an app dir")
169
+ exit(1)
170
+ }
171
+ }
172
+
173
+ let port = examplePort(name)
174
+ if (name === "app") port = 5173
175
+ let shellOut = exampleShellOut(name)
176
+ if (name === "app") shellOut = "dist/app-shell"
177
+ let shellPath = joinPath(exampleDir, shellOut)
178
+
179
+ console.log("[dev] " + name + " — starting Vite :" + String(port) + ", then native desktop shell")
180
+
181
+ let cssScript = joinPath(exampleDir, "scripts/build-css.tish")
182
+ if (pathExists(cssScript)) {
183
+ let cssCode = exec(
184
+ "cd " + q(exampleDir) + " && tish run --feature fs scripts/build-css.tish"
185
+ )
186
+ if (cssCode !== 0) {
187
+ console.log("[dev] build-css failed (continuing; styles may be stale)")
188
+ }
189
+ }
190
+
191
+ killVite(port)
192
+ let viteCmd =
193
+ "cd " + q(exampleDir) +
194
+ " && npx vite --port " + String(port) + " --strictPort >/tmp/tish-desktop-vite-" +
195
+ String(port) + ".log 2>&1 &"
196
+ exec(viteCmd)
197
+
198
+ let baseUrl = "http://localhost:" + String(port)
199
+ if (!waitForHttp(baseUrl + "/", 60000)) {
200
+ console.error("[dev] timed out waiting for " + baseUrl + "/")
201
+ killVite(port)
202
+ exit(1)
203
+ }
204
+ console.log("[dev] Vite is ready at " + baseUrl + "/")
205
+ warmupVite(baseUrl)
206
+
207
+ let tishBin = resolveTish()
208
+ if (forceRebuild || shellNeedsRebuild(root, exampleDir, shellPath)) {
209
+ if (!forceRebuild && pathExists(shellPath)) {
210
+ console.log("[dev] host/shell sources newer than " + shellOut + " — rebuilding")
211
+ }
212
+ buildShell(tishBin, exampleDir, shellOut, shellPath)
213
+ } else {
214
+ console.log("[dev] using existing shell " + shellOut + " (pass --rebuild to recompile)")
215
+ }
216
+
217
+ console.log("[dev] launching desktop app: " + shellOut)
218
+ let appCode = exec("cd " + q(exampleDir) + " && " + q(shellPath))
219
+ console.log("[dev] desktop app exited (code=" + String(appCode) + ")")
220
+ killVite(port)
221
+ exit(appCode)
222
+ }
@@ -0,0 +1,56 @@
1
+ import { exit, exec } from "tish:process"
2
+ import { firstPositional, joinPath, pathExists, q, repoRoot } from "../paths.tish"
3
+
4
+ fn usage() {
5
+ console.log("Usage: mode distribute <step>")
6
+ console.log("")
7
+ console.log("Steps (scripts under scripts/distribute/):")
8
+ console.log(" build node scripts/distribute/build-release.mjs")
9
+ console.log(" sign bash scripts/distribute/sign-macos.sh")
10
+ console.log(" notarize bash scripts/distribute/notarize-macos.sh")
11
+ console.log(" updater node scripts/distribute/publish-updater.mjs")
12
+ console.log(" release build-release + publish-github-release")
13
+ console.log(" github node scripts/distribute/publish-github-release.mjs")
14
+ }
15
+
16
+ export fn runDistribute(args) {
17
+ let step = firstPositional(args)
18
+ if (step === null || step === "help" || step === "--help") {
19
+ usage()
20
+ exit(step === null ? 1 : 0)
21
+ }
22
+
23
+ let root = repoRoot()
24
+ let distDir = joinPath(root, "scripts/distribute")
25
+ if (!pathExists(distDir)) {
26
+ console.error("distribute: missing " + distDir)
27
+ console.error(" (scripts land in Phase 6 — create them or run from a complete checkout)")
28
+ exit(1)
29
+ }
30
+
31
+ let cmd = null
32
+ if (step === "build") {
33
+ cmd = "node " + q(joinPath(distDir, "build-release.mjs"))
34
+ } else if (step === "sign") {
35
+ cmd = "bash " + q(joinPath(distDir, "sign-macos.sh"))
36
+ } else if (step === "notarize") {
37
+ cmd = "bash " + q(joinPath(distDir, "notarize-macos.sh"))
38
+ } else if (step === "updater") {
39
+ cmd = "node " + q(joinPath(distDir, "publish-updater.mjs"))
40
+ } else if (step === "github") {
41
+ cmd = "node " + q(joinPath(distDir, "publish-github-release.mjs"))
42
+ } else if (step === "release") {
43
+ cmd =
44
+ "node " + q(joinPath(distDir, "build-release.mjs")) +
45
+ " && node " + q(joinPath(distDir, "publish-github-release.mjs"))
46
+ } else {
47
+ console.error("unknown distribute step: " + step)
48
+ usage()
49
+ exit(1)
50
+ }
51
+
52
+ console.log("[distribute] " + step)
53
+ console.log("-> " + cmd)
54
+ let code = exec("cd " + q(root) + " && " + cmd)
55
+ exit(code)
56
+ }
@@ -0,0 +1,91 @@
1
+ import { exit, env, cwd, exec } from "tish:process"
2
+ import { readFile } from "tish:fs"
3
+ import {
4
+ flagValue,
5
+ joinPath,
6
+ pathExists,
7
+ q,
8
+ resolveTish,
9
+ } from "../paths.tish"
10
+
11
+ fn capLevel(name, surface, platform) {
12
+ if (name === "notification") {
13
+ if (surface === "web") return "Partial"
14
+ return "Full"
15
+ }
16
+ if (name === "store") {
17
+ if (surface === "web" || platform === "ios") return "Unsupported"
18
+ return "Full"
19
+ }
20
+ if (name === "tray") {
21
+ if (surface === "web" || platform === "ios" || surface === "native") return "Unsupported"
22
+ return "Full"
23
+ }
24
+ return "Unsupported"
25
+ }
26
+
27
+ fn capture(cmd) {
28
+ let out = "/tmp/tish-desktop-doctor-resolve.txt"
29
+ let code = exec(cmd + " > " + q(out) + " 2>&1")
30
+ let text = ""
31
+ if (pathExists(out)) {
32
+ text = String(readFile(out)).trim()
33
+ }
34
+ return { code: code, text: text }
35
+ }
36
+
37
+ export fn runDoctor(args) {
38
+ // `--desk-platform` / `--desk-surface` are rewritten from `--platform` /
39
+ // `--surface` by cli/bin/mode.js so `tish run` is not disrupted.
40
+ let platform = flagValue(args, "--desk-platform")
41
+ if (platform === null) platform = flagValue(args, "--platform")
42
+ if (platform === null) platform = "macos"
43
+
44
+ let surface = flagValue(args, "--desk-surface")
45
+ if (surface === null) surface = flagValue(args, "--surface")
46
+ if (surface === null) surface = "webview"
47
+
48
+ let tish = resolveTish()
49
+ console.log("mode doctor")
50
+ console.log(" (alias of plan name: tish app doctor)")
51
+ console.log(" platform: " + platform)
52
+ console.log(" surface: " + surface)
53
+ console.log(" tish: " + tish)
54
+
55
+ let probe = flagValue(args, "--resolve")
56
+ if (probe === null) probe = "./Button"
57
+ let importer = flagValue(args, "--importer")
58
+ if (importer === null) {
59
+ let here = cwd()
60
+ importer = joinPath(here, "src/main.tish")
61
+ if (!pathExists(importer)) importer = joinPath(here, "ui/main.tish")
62
+ if (!pathExists(importer)) importer = joinPath(here, "app/App.tish")
63
+ }
64
+
65
+ console.log(" resolve: " + probe + " (importer " + importer + ")")
66
+ let resolveCmd = q(tish) + " resolve-id " + q(probe) + " --importer " + q(importer)
67
+ resolveCmd = resolveCmd + " --platform " + q(platform) + " --surface " + q(surface)
68
+ let r = capture(resolveCmd)
69
+ if (r.text !== "") {
70
+ console.log(" -> " + r.text.split("\n")[0])
71
+ } else {
72
+ console.log(" -> (resolve-id failed — rebuild tish CLI with platform resolve)")
73
+ }
74
+
75
+ console.log("")
76
+ console.log("Cap matrix (sample for this profile):")
77
+ console.log(" notification " + capLevel("notification", surface, platform))
78
+ console.log(" store " + capLevel("store", surface, platform))
79
+ console.log(" tray " + capLevel("tray", surface, platform))
80
+ console.log("")
81
+ console.log("Notes:")
82
+ console.log(" state.* Full (BrokerCore; path + revision) — not store.*")
83
+ console.log(" webview.* Partial (Tauri load/postMessage; apple WK via macos.webview*)")
84
+ console.log(" unsupported { ok:false, code:unsupported, capability, platform, message }")
85
+ console.log("")
86
+ console.log("Upgrade path (Web -> Webview -> Hybrid):")
87
+ console.log(" npm run build:web | build:shell | build:shell:apple / dev:hybrid")
88
+ console.log(" See docs/HYBRID.md · docs/UNIFIED_APP.md")
89
+ console.log("HMR: platform file edits reload; switching TISH_PLATFORM/SURFACE needs Vite restart.")
90
+ exit(0)
91
+ }
@@ -0,0 +1,42 @@
1
+ import { exit, cwd, exec } from "tish:process"
2
+ import { mkdir, writeFile } from "tish:fs"
3
+ import { firstPositional, joinPath, pathExists, q } from "../paths.tish"
4
+
5
+ export fn runIcon(args) {
6
+ let dir = firstPositional(args)
7
+ let projectDir = cwd()
8
+ if (dir !== null) {
9
+ if (String(dir).startsWith("/")) projectDir = dir
10
+ else projectDir = joinPath(cwd(), dir)
11
+ }
12
+
13
+ let iconsDir = joinPath(projectDir, "icons")
14
+ mkdir(iconsDir, true)
15
+
16
+ // Minimal placeholder SVG icon set. Real raster generation can land later
17
+ // (sips / ImageMagick) once distribute scripts ship.
18
+ let svg =
19
+ '<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">' +
20
+ '<rect width="1024" height="1024" rx="192" fill="#18181b"/>' +
21
+ '<circle cx="512" cy="512" r="280" fill="#10b981"/>' +
22
+ '<text x="512" y="580" text-anchor="middle" font-family="DM Sans, sans-serif" font-size="320" font-weight="700" fill="#ffffff">T</text>' +
23
+ "</svg>"
24
+
25
+ let svgPath = joinPath(iconsDir, "icon.svg")
26
+ writeFile(svgPath, svg)
27
+ console.log("[icon] wrote " + svgPath)
28
+
29
+ // Best-effort PNG via `sips` on macOS when available.
30
+ let pngPath = joinPath(iconsDir, "icon.png")
31
+ let code = exec(
32
+ "command -v sips >/dev/null 2>&1 && sips -s format png " +
33
+ q(svgPath) + " --out " + q(pngPath) + " >/dev/null 2>&1"
34
+ )
35
+ if (code === 0 && pathExists(pngPath)) {
36
+ console.log("[icon] wrote " + pngPath)
37
+ } else {
38
+ console.log("[icon] PNG skipped (install sips/ImageMagick or convert icon.svg manually)")
39
+ }
40
+
41
+ console.log("[icon] place platform icons under " + iconsDir + " and point tauri.conf / bundler at them")
42
+ }
@@ -0,0 +1,68 @@
1
+ import { cwd, exec, env } from "tish:process"
2
+ import { readFile } from "tish:fs"
3
+ import { joinPath, pathExists, repoRoot, resolveTish, cliRoot, q } from "../paths.tish"
4
+
5
+ fn trim(s) {
6
+ return String(s).trim()
7
+ }
8
+
9
+ fn capture(cmd) {
10
+ let out = "/tmp/tish-desktop-info-capture.txt"
11
+ let code = exec(cmd + " > " + q(out) + " 2>&1")
12
+ let text = ""
13
+ if (pathExists(out)) {
14
+ text = String(readFile(out))
15
+ }
16
+ return { code: code, text: trim(text) }
17
+ }
18
+
19
+ fn versionLine(label, cmd) {
20
+ let r = capture(cmd)
21
+ if (r.text !== "") {
22
+ let line = r.text.split("\n")[0]
23
+ console.log(label + ": " + line)
24
+ } else {
25
+ console.log(label + ": not found (exit " + String(r.code) + ")")
26
+ }
27
+ }
28
+
29
+ export fn runInfo(_args) {
30
+ let root = repoRoot()
31
+ let tishBin = resolveTish()
32
+ console.log("mode info")
33
+ console.log("repo: " + root)
34
+ console.log("cli: " + cliRoot())
35
+ console.log("cwd: " + cwd())
36
+ console.log("")
37
+ versionLine("tish", q(tishBin) + " --version")
38
+ versionLine("rustc", "rustc --version")
39
+ versionLine("cargo", "cargo --version")
40
+ versionLine("node", "node --version")
41
+
42
+ let crateToml = joinPath(root, "crates/tish_desktop/Cargo.toml")
43
+ if (pathExists(crateToml)) {
44
+ let text = String(readFile(crateToml))
45
+ let lines = text.split("\n")
46
+ let i = 0
47
+ while (i < lines.length) {
48
+ let line = trim(lines[i])
49
+ if (line.startsWith("version")) {
50
+ console.log("tishlang_desktop crate: " + line)
51
+ break
52
+ }
53
+ i = i + 1
54
+ }
55
+ }
56
+
57
+ let platform = "unknown"
58
+ if (env !== null && env["OSTYPE"] !== null && env["OSTYPE"] !== undefined) {
59
+ platform = String(env["OSTYPE"])
60
+ } else {
61
+ let u = capture("uname -s")
62
+ if (u.text !== "") platform = u.text.split("\n")[0]
63
+ }
64
+ console.log("platform: " + platform)
65
+ console.log("")
66
+ console.log("Plugins (host defaults): dialog, tray, menu, deepLink, opener, notifications")
67
+ console.log("Broker protocol: desktop/v1")
68
+ }