moshcode 0.24.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 (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +580 -0
  3. package/bin/moshcode.mjs +674 -0
  4. package/bin/moshscript.mjs +29 -0
  5. package/examples/alive.mosh +6 -0
  6. package/examples/scripting-the-cli.mosh +21 -0
  7. package/examples/team-secrets.mosh +20 -0
  8. package/examples/templates/bun-caddy-sqlite/.env.example +14 -0
  9. package/examples/templates/bun-caddy-sqlite/Caddyfile +18 -0
  10. package/examples/templates/bun-caddy-sqlite/README.md +97 -0
  11. package/examples/templates/bun-caddy-sqlite/deploy/moshcode-dns.service +39 -0
  12. package/examples/templates/bun-caddy-sqlite/deploy/moshpit-service.service +38 -0
  13. package/examples/templates/bun-caddy-sqlite/package.json +15 -0
  14. package/examples/templates/bun-caddy-sqlite/src/db.ts +47 -0
  15. package/examples/templates/bun-caddy-sqlite/src/server.ts +44 -0
  16. package/examples/templates/bun-caddy-sqlite/template.json +10 -0
  17. package/examples/templates/caddy-proxy/Caddyfile +36 -0
  18. package/examples/templates/caddy-proxy/README.md +104 -0
  19. package/examples/templates/caddy-proxy/deploy/moshcode-dns.service +39 -0
  20. package/examples/templates/caddy-proxy/template.json +8 -0
  21. package/examples/templates/caddy-static/Caddyfile +16 -0
  22. package/examples/templates/caddy-static/README.md +90 -0
  23. package/examples/templates/caddy-static/deploy/moshcode-dns.service +39 -0
  24. package/examples/templates/caddy-static/site/index.html +11 -0
  25. package/examples/templates/caddy-static/template.json +8 -0
  26. package/install.sh +194 -0
  27. package/package.json +28 -0
  28. package/prd/0000-template.md +49 -0
  29. package/prd/0001-wrap-ugig-and-coinpay-clis.md +121 -0
  30. package/prd/0002-separate-agent-and-raw-engine-launches.md +113 -0
  31. package/prd/0003-cross-engine-mcp-and-skill-installation.md +165 -0
  32. package/prd/0004-moshscript-run-programmable-moshcode.md +344 -0
  33. package/prd/0005-hosted-moshpit-resolver.md +192 -0
  34. package/prd/0006-help.md +359 -0
  35. package/prd/0007-profullstack-site-init.md +1183 -0
  36. package/prd/README.md +26 -0
  37. package/src/ads.mjs +58 -0
  38. package/src/auth.mjs +193 -0
  39. package/src/cli-schema.mjs +533 -0
  40. package/src/cli.mjs +118 -0
  41. package/src/commands.mjs +259 -0
  42. package/src/completion.mjs +594 -0
  43. package/src/console.mjs +244 -0
  44. package/src/dns-system.mjs +404 -0
  45. package/src/dns.mjs +2872 -0
  46. package/src/doh-server.mjs +256 -0
  47. package/src/doh.mjs +218 -0
  48. package/src/engines.mjs +385 -0
  49. package/src/escalate.mjs +85 -0
  50. package/src/help.mjs +443 -0
  51. package/src/integrations.mjs +265 -0
  52. package/src/mcp-catalog.mjs +50 -0
  53. package/src/mcp.mjs +155 -0
  54. package/src/mirror.mjs +187 -0
  55. package/src/notify.mjs +86 -0
  56. package/src/open-url.mjs +34 -0
  57. package/src/parking-http.mjs +65 -0
  58. package/src/pins.mjs +190 -0
  59. package/src/pit-url.mjs +13 -0
  60. package/src/prd.mjs +341 -0
  61. package/src/pty.mjs +176 -0
  62. package/src/pwd.mjs +103 -0
  63. package/src/registry.mjs +37 -0
  64. package/src/release-install.mjs +191 -0
  65. package/src/runtime.mjs +161 -0
  66. package/src/selfupdate.mjs +215 -0
  67. package/src/serve.mjs +502 -0
  68. package/src/skills.mjs +93 -0
  69. package/src/tabs.mjs +144 -0
  70. package/src/templates.mjs +456 -0
  71. package/src/tools.mjs +231 -0
  72. package/src/trade.mjs +137 -0
  73. package/src/trust.mjs +712 -0
  74. package/src/tui.mjs +736 -0
  75. package/src/ui.mjs +49 -0
  76. package/src/uninstall.mjs +113 -0
  77. package/src/upgrade.mjs +217 -0
package/src/tools.mjs ADDED
@@ -0,0 +1,231 @@
1
+ // Adjacent workflow CLIs moshcode can install and transparently invoke.
2
+ // These are deliberately separate from coding engines: UGig owns marketplace
3
+ // workflows, CoinPay owns payment workflows, c0mpute owns the compute network,
4
+ // the cloud CLIs below own deploys/secrets/infra, and moshcode only conducts
5
+ // their native command lines.
6
+ import { homedir } from "node:os";
7
+ import path from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ import { isInstalled, openPassthrough } from "./engines.mjs";
11
+
12
+ // gh, supabase, and doctl publish only GitHub release binaries — no official
13
+ // cross-platform install script between them — so their install spec runs our
14
+ // own downloader instead of a vendor one. See src/release-install.mjs.
15
+ const RELEASE_INSTALLER = path.join(path.dirname(fileURLToPath(import.meta.url)), "release-install.mjs");
16
+ const releaseInstall = (tool) => ({ cmd: process.execPath, args: [RELEASE_INSTALLER, tool] });
17
+
18
+ export const TOOLS = {
19
+ ugig: {
20
+ desc: "UGig — freelance marketplace CLI for humans and agents",
21
+ bin: "ugig",
22
+ // UGig isn't published to npm — it ships via its own install script.
23
+ install: { cmd: "bash", args: ["-c", "curl -fsSL https://ugig.net/install.sh | bash"] },
24
+ },
25
+ coinpay: {
26
+ desc: "CoinPay — wallets, payments, swaps, escrow, and settlement",
27
+ bin: "coinpay",
28
+ // CoinPay ships via its own install script (fetched from GitHub), not npm.
29
+ install: { cmd: "sh", args: ["-c", "curl -fsSL https://coinpayportal.com/install.sh | sh"] },
30
+ },
31
+ c0mpute: {
32
+ desc: "c0mpute — decentralized compute network CLI",
33
+ bin: "c0mpute",
34
+ // c0mpute ships via its own install script (the v1 stack installer).
35
+ install: { cmd: "sh", args: ["-c", "curl -fsSL https://c0mpute.com/install.sh | sh"] },
36
+ },
37
+ secrets: {
38
+ desc: "LogicSRC — end-to-end-encrypted team credential sharing (login, teams, credentials)",
39
+ // The passthrough target is the `logicsrc` binary; the moshcode command is
40
+ // `/secrets` so it reads as "manage secrets". LOGICSRC_BIN points at a local
41
+ // build before logicsrc ships a global install.
42
+ bin: process.env.LOGICSRC_BIN || "logicsrc",
43
+ // LogicSRC ships via its own install script (same pattern as the others).
44
+ install: { cmd: "sh", args: ["-c", "curl -fsSL https://logicsrc.com/install.sh | sh"] },
45
+ },
46
+ railway: {
47
+ desc: "Railway — deploy and manage Railway projects, services, and env vars",
48
+ bin: "railway",
49
+ // Railway's shell installer wants bash process substitution
50
+ // (`bash <(curl …)`), which does not survive `sh -c "curl … | sh"`, and the
51
+ // agents.railway.com variant additionally runs `railway setup agent`, which
52
+ // rewrites local agent tool configs — a side effect an install command has
53
+ // no business having. The official npm package is cross-platform and
54
+ // idempotent, so it doubles as the upgrade path.
55
+ install: { cmd: "npm", args: ["install", "-g", "@railway/cli"] },
56
+ },
57
+ gh: {
58
+ desc: "GitHub CLI — repos, PRs, issues, releases, and Actions",
59
+ bin: "gh",
60
+ install: releaseInstall("gh"),
61
+ },
62
+ supabase: {
63
+ desc: "Supabase — local stack, migrations, edge functions, and projects",
64
+ bin: "supabase",
65
+ // Supabase does not support a global npm install, so fetching the release
66
+ // binary is what gives you a real `supabase` on PATH instead of `npx supabase`.
67
+ install: releaseInstall("supabase"),
68
+ },
69
+ doppler: {
70
+ desc: "Doppler — sync secrets and run commands with injected env",
71
+ bin: "doppler",
72
+ // Doppler's official script installs to /usr/local/bin via sudo by default;
73
+ // --install-path keeps it user-local (and implies --no-package-manager).
74
+ // The script verifies its own signature, so it needs `gpgv` on PATH — it
75
+ // exits 3 with an explicit message when gnupg is missing.
76
+ install: {
77
+ cmd: "sh",
78
+ args: [
79
+ "-c",
80
+ 'mkdir -p "$HOME/.local/bin" && curl -fsSL https://cli.doppler.com/install.sh | sh -s -- --install-path "$HOME/.local/bin"',
81
+ ],
82
+ },
83
+ upgrade: { cmd: "doppler", args: ["update"] },
84
+ },
85
+ doctl: {
86
+ desc: "DigitalOcean — droplets, apps, databases, Kubernetes, and Spaces",
87
+ bin: "doctl",
88
+ install: releaseInstall("doctl"),
89
+ },
90
+ turso: {
91
+ desc: "Turso — libSQL/SQLite at the edge (auth signup/login, db, replicas)",
92
+ bin: "turso",
93
+ // https://github.com/tursodatabase/turso-cli — the official installer, which
94
+ // unpacks to $HOME/.turso and appends that dir to your shell profile. It is
95
+ // therefore on PATH only for the NEXT shell, so PATH alone would report turso
96
+ // as missing (and /turso would fail to launch it) in the session that
97
+ // installed it — and in every already-running one. binDirs searches the
98
+ // install dir after PATH so the binary is found either way.
99
+ binDirs: [path.join(homedir(), ".turso")],
100
+ install: { cmd: "bash", args: ["-c", "curl -sSfL https://get.tur.so/install.sh | bash"] },
101
+ },
102
+ tailscale: {
103
+ desc: "Tailscale — WireGuard mesh VPN (up, status, ssh, serve, funnel)",
104
+ bin: "tailscale",
105
+ // The odd one out: tailscale is a system daemon, not a standalone binary, so
106
+ // it cannot be dropped in ~/.local/bin like gh/supabase/doctl. The official
107
+ // script goes through the distro package manager and enables `tailscaled`,
108
+ // which means it needs root — it finds sudo/doas itself and may prompt for a
109
+ // password (stdio is inherited, so the prompt works). On macOS the same
110
+ // script delegates to the App Store.
111
+ install: { cmd: "sh", args: ["-c", "curl -fsSL https://tailscale.com/install.sh | sh"] },
112
+ // Native updater on Linux (v1.36+) and Windows. macOS updates come from the
113
+ // App Store, so there it fails with tailscale's own message rather than
114
+ // silently re-adding package repos.
115
+ upgrade: { cmd: "tailscale", args: ["update"] },
116
+ },
117
+ alpaca: {
118
+ desc: "Alpaca — paper/live trading, market data, positions, and watchlists",
119
+ bin: "alpaca",
120
+ // Official Go install documented by Alpaca. Go writes to $GOBIN when set,
121
+ // otherwise $GOPATH/bin (normally ~/go/bin); binDirs covers that default in
122
+ // an already-running shell whose PATH has not picked it up yet.
123
+ binDirs: [path.join(homedir(), "go", "bin")],
124
+ install: {
125
+ cmd: "go",
126
+ args: ["install", "github.com/alpacahq/cli/cmd/alpaca@latest"],
127
+ },
128
+ installHelp: "Go is required to install Alpaca; install Go, then retry `moshcode install alpaca`.",
129
+ },
130
+ };
131
+
132
+ /** Resolve a name to `[key, tool]`, or null. */
133
+ export function resolveTool(token) {
134
+ if (!token) return null;
135
+ const key = String(token).trim().toLowerCase();
136
+ // Own properties only: TOOLS is a plain object literal, so a name like
137
+ // `constructor` or `__proto__` would otherwise resolve to something off
138
+ // Object.prototype and be handed on as a tool with no bin/install.
139
+ return Object.hasOwn(TOOLS, key) ? [key, TOOLS[key]] : null;
140
+ }
141
+
142
+ /** Tool entries annotated with native executable install status. */
143
+ export function toolStatus() {
144
+ return Object.entries(TOOLS).map(([key, tool]) => ({
145
+ key,
146
+ ...tool,
147
+ installed: isInstalled(tool.bin, tool.binDirs),
148
+ }));
149
+ }
150
+
151
+ export function toolList() {
152
+ return Object.entries(TOOLS)
153
+ .map(([key, tool]) => ` ${key.padEnd(10)} ${tool.desc}`)
154
+ .join("\n");
155
+ }
156
+
157
+ /** Prefer a native updater when one is added; npm installs are idempotent. */
158
+ export function toolUpgradeSpec(tool) {
159
+ return tool.upgrade || tool.install;
160
+ }
161
+
162
+ /** Invoke a tool without parsing or modifying its arguments or streams. */
163
+ export function openTool(tool, args = [], opts = {}) {
164
+ return openPassthrough(tool, args, opts);
165
+ }
166
+
167
+ // Generic utilities used by the app/package surface.
168
+
169
+ /**
170
+ * Format a number as currency
171
+ */
172
+ export function formatCurrency(amount, currency = 'USD') {
173
+ return new Intl.NumberFormat('en-US', {
174
+ style: 'currency',
175
+ currency
176
+ }).format(amount);
177
+ }
178
+
179
+ /**
180
+ * Generate a random ID
181
+ */
182
+ export function generateId(length = 8) {
183
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
184
+ let result = '';
185
+ for (let i = 0; i < length; i++) {
186
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
187
+ }
188
+ return result;
189
+ }
190
+
191
+ /**
192
+ * Debounce function calls
193
+ */
194
+ export function debounce(fn, delay = 300) {
195
+ let timeoutId;
196
+ return function (...args) {
197
+ clearTimeout(timeoutId);
198
+ timeoutId = setTimeout(() => fn.apply(this, args), delay);
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Deep clone an object
204
+ */
205
+ export function deepClone(obj) {
206
+ return JSON.parse(JSON.stringify(obj));
207
+ }
208
+
209
+ /**
210
+ * Sleep for ms milliseconds
211
+ */
212
+ export function sleep(ms) {
213
+ return new Promise(resolve => setTimeout(resolve, ms));
214
+ }
215
+
216
+ /**
217
+ * Retry a function with exponential backoff
218
+ */
219
+ export async function retry(fn, maxAttempts = 3, baseDelay = 1000) {
220
+ if (!Number.isInteger(maxAttempts) || maxAttempts < 1) {
221
+ throw new Error('retry maxAttempts must be a positive integer');
222
+ }
223
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
224
+ try {
225
+ return await fn();
226
+ } catch (error) {
227
+ if (attempt === maxAttempts) throw error;
228
+ await sleep(baseDelay * Math.pow(2, attempt - 1));
229
+ }
230
+ }
231
+ }
package/src/trade.mjs ADDED
@@ -0,0 +1,137 @@
1
+ // Friendly trading vocabulary over Alpaca's native CLI.
2
+ //
3
+ // Alpaca remains a workflow tool: authentication, API calls, structured output,
4
+ // and the full command tree all belong to the `alpaca` binary. This module only
5
+ // translates a small, memorable `moshcode trade` surface into native argv.
6
+ // `moshcode alpaca ...` remains the escape hatch for every command not covered
7
+ // here.
8
+
9
+ const USAGE = `usage: moshcode trade <command> [args…]
10
+
11
+ ticker <symbol> [flags…] look up a tradable asset
12
+ quote <symbol> [flags…] get the latest quote
13
+ analysis <symbol> [flags…] get an analysis-ready market snapshot
14
+ buy <symbol> <qty> [flags…] preview a buy; add --submit to place it
15
+ buy <symbol> --notional <usd> preview a dollar-value buy
16
+ sell <symbol> <qty> [flags…] preview a sell; add --submit to place it
17
+ watch <verb> [args…] manage Alpaca watchlists
18
+ positions [verb] [args…] list/get/close positions
19
+ orders [verb] [args…] list/get/replace/cancel orders
20
+ account [args…] show account details
21
+ login [args…] authenticate an Alpaca profile (paper by default)
22
+ clock [args…] show market status and next open/close
23
+ raw <alpaca args…> invoke the native Alpaca command tree
24
+
25
+ Orders are previews by default. --submit removes the injected --dry-run;
26
+ live trading still requires Alpaca's separate --live opt-in.`;
27
+
28
+ export function tradeUsage() {
29
+ return USAGE;
30
+ }
31
+
32
+ function symbolCommand(native, args) {
33
+ const [symbol, ...rest] = args;
34
+ if (!symbol || String(symbol).startsWith("-")) {
35
+ return { error: `trade ${native.label} requires a ticker symbol` };
36
+ }
37
+ return { args: [...native.argv, "--symbol", String(symbol).toUpperCase(), ...rest] };
38
+ }
39
+
40
+ function optionValue(args, name) {
41
+ const exact = args.indexOf(name);
42
+ if (exact >= 0) return { value: args[exact + 1], missing: args[exact + 1] == null || String(args[exact + 1]).startsWith("-") };
43
+ const prefix = `${name}=`;
44
+ const joined = args.find((arg) => String(arg).startsWith(prefix));
45
+ return joined == null ? null : { value: String(joined).slice(prefix.length), missing: String(joined).slice(prefix.length) === "" };
46
+ }
47
+
48
+ function positiveNumber(value) {
49
+ return value != null && Number.isFinite(Number(value)) && Number(value) > 0;
50
+ }
51
+
52
+ function orderCommand(side, args) {
53
+ const [symbol, ...afterSymbol] = args;
54
+ if (!symbol || String(symbol).startsWith("-")) {
55
+ return { error: `trade ${side} requires a ticker symbol and quantity or --notional` };
56
+ }
57
+
58
+ const positionalQty = afterSymbol[0] != null && !String(afterSymbol[0]).startsWith("-")
59
+ ? String(afterSymbol[0])
60
+ : null;
61
+ const tail = positionalQty == null ? afterSymbol : afterSymbol.slice(1);
62
+ const qtyOption = optionValue(tail, "--qty");
63
+ const notionalOption = optionValue(tail, "--notional");
64
+ const supplied = [positionalQty != null, qtyOption != null, notionalOption != null].filter(Boolean).length;
65
+ if (supplied === 0) {
66
+ return { error: `trade ${side} requires a positive quantity or --notional amount` };
67
+ }
68
+ if (supplied > 1) {
69
+ return { error: `trade ${side} accepts one of positional quantity, --qty, or --notional` };
70
+ }
71
+ if (qtyOption?.missing) return { error: `trade ${side} --qty requires a positive number` };
72
+ if (notionalOption?.missing) return { error: `trade ${side} --notional requires a positive number` };
73
+ const amount = positionalQty ?? qtyOption?.value ?? notionalOption?.value;
74
+ if (!positiveNumber(amount)) {
75
+ return { error: `trade ${side} ${notionalOption ? "notional amount" : "quantity"} must be a positive number` };
76
+ }
77
+
78
+ // Placing an order is the one place where a friendly shortcut should be
79
+ // safer than its native equivalent. Alpaca deliberately has no confirmation
80
+ // prompts, so buy/sell previews unless the caller explicitly says --submit.
81
+ const submit = tail.includes("--submit");
82
+ const nativeTail = tail.filter((arg) => arg !== "--submit");
83
+ const hasType = nativeTail.some((arg) => arg === "--type" || String(arg).startsWith("--type="));
84
+ const hasDryRun = nativeTail.includes("--dry-run");
85
+ return {
86
+ args: [
87
+ "order", "submit",
88
+ "--symbol", String(symbol).toUpperCase(),
89
+ "--side", side,
90
+ ...(positionalQty == null ? [] : ["--qty", positionalQty]),
91
+ ...(hasType ? [] : ["--type", "market"]),
92
+ ...nativeTail,
93
+ ...(!submit && !hasDryRun ? ["--dry-run"] : []),
94
+ ],
95
+ preview: !submit || hasDryRun,
96
+ };
97
+ }
98
+
99
+ /** Translate `trade` arguments into argv for the native `alpaca` binary. */
100
+ export function tradeArgs(input = []) {
101
+ const [rawCommand, ...rest] = input.map(String);
102
+ const command = rawCommand?.toLowerCase();
103
+ if (!command) return { usage: true };
104
+
105
+ if (command === "ticker" || command === "lookup" || command === "asset") {
106
+ const [symbol, ...tail] = rest;
107
+ if (!symbol || String(symbol).startsWith("-")) return { error: "trade ticker requires a ticker symbol" };
108
+ // Unlike market-data commands, Alpaca asset get names its lookup argument
109
+ // --symbol-or-asset-id (verified against v0.0.13's actual command tree).
110
+ return { args: ["asset", "get", "--symbol-or-asset-id", String(symbol).toUpperCase(), ...tail] };
111
+ }
112
+ if (command === "quote") {
113
+ return symbolCommand({ label: "quote", argv: ["data", "latest-quote"] }, rest);
114
+ }
115
+ if (command === "analysis" || command === "analyze") {
116
+ return symbolCommand({ label: "analysis", argv: ["data", "snapshot"] }, rest);
117
+ }
118
+ if (command === "buy" || command === "sell") return orderCommand(command, rest);
119
+
120
+ if (command === "watch" || command === "watchlist") {
121
+ return { args: ["watchlist", ...(rest.length ? rest : ["list"])] };
122
+ }
123
+ if (command === "positions" || command === "position") {
124
+ return { args: ["position", ...(rest.length ? rest : ["list"])] };
125
+ }
126
+ if (command === "orders" || command === "order") {
127
+ return { args: ["order", ...(rest.length ? rest : ["list"])] };
128
+ }
129
+ if (command === "account") return { args: ["account", ...(rest.length ? rest : ["get"])] };
130
+ if (command === "login") return { args: ["profile", "login", ...rest] };
131
+ if (command === "clock") return { args: ["clock", ...rest] };
132
+ if (command === "raw" || command === "alpaca") {
133
+ return rest.length ? { args: rest } : { error: "trade raw requires an Alpaca command" };
134
+ }
135
+
136
+ return { error: `unknown trade command ${JSON.stringify(rawCommand)}` };
137
+ }