@lotics/cli 0.30.0 → 0.30.2
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.
- package/dist/src/app_commands.d.ts +8 -0
- package/dist/src/app_commands.js +10 -0
- package/dist/src/cli.js +13 -1
- package/dist/src/client.d.ts +9 -0
- package/dist/src/client.js +8 -0
- package/dist/src/dev/server.js +11 -2
- package/dist/src/dev/wrapper_page.d.ts +10 -5
- package/dist/src/dev/wrapper_page.js +24 -12
- package/dist/src/starter_template.js +6 -1
- package/package.json +1 -1
|
@@ -78,6 +78,14 @@ export declare function appCreate(client: LoticsClient, args: {
|
|
|
78
78
|
* 6. (TODO) Generate `.lotics/types.ts` with workspace tables augmentation —
|
|
79
79
|
* pending a `client.getWorkspaceSchema()` endpoint.
|
|
80
80
|
*/
|
|
81
|
+
/**
|
|
82
|
+
* `lotics app subdomain <new>` — rename the current app's public address.
|
|
83
|
+
* The app_id comes from the local `package.json` manifest. The old
|
|
84
|
+
* `<slug>.lotics.app` stops resolving once the change lands.
|
|
85
|
+
*/
|
|
86
|
+
export declare function appSetSubdomain(client: LoticsClient, args: {
|
|
87
|
+
subdomain: string;
|
|
88
|
+
}): Promise<void>;
|
|
81
89
|
export declare function appPull(client: LoticsClient, args: {
|
|
82
90
|
app_id: string;
|
|
83
91
|
targetPath?: string;
|
package/dist/src/app_commands.js
CHANGED
|
@@ -189,6 +189,16 @@ export async function appCreate(client, args) {
|
|
|
189
189
|
* 6. (TODO) Generate `.lotics/types.ts` with workspace tables augmentation —
|
|
190
190
|
* pending a `client.getWorkspaceSchema()` endpoint.
|
|
191
191
|
*/
|
|
192
|
+
/**
|
|
193
|
+
* `lotics app subdomain <new>` — rename the current app's public address.
|
|
194
|
+
* The app_id comes from the local `package.json` manifest. The old
|
|
195
|
+
* `<slug>.lotics.app` stops resolving once the change lands.
|
|
196
|
+
*/
|
|
197
|
+
export async function appSetSubdomain(client, args) {
|
|
198
|
+
const meta = readAppMeta(process.cwd());
|
|
199
|
+
const result = await client.setAppSubdomain(meta.app_id, args.subdomain);
|
|
200
|
+
console.error(`Public address set: https://${result.public_subdomain}.lotics.app`);
|
|
201
|
+
}
|
|
192
202
|
export async function appPull(client, args) {
|
|
193
203
|
const app = await client.getApp(args.app_id);
|
|
194
204
|
if (!app.current_version_id) {
|
package/dist/src/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import readline from "node:readline";
|
|
|
5
5
|
import { LoticsClient, API_BASE_URL } from "./client.js";
|
|
6
6
|
import { resolveAuth, loadConfig, saveConfig, deleteConfig, getConfigPath, checkForUpdate } from "./config.js";
|
|
7
7
|
import { VERSION } from "./version.js";
|
|
8
|
-
import { appCreate, appPull, appDeploy, appDev } from "./app_commands.js";
|
|
8
|
+
import { appCreate, appPull, appDeploy, appDev, appSetSubdomain } from "./app_commands.js";
|
|
9
9
|
import { parseArgs } from "./args.js";
|
|
10
10
|
function printHelp() {
|
|
11
11
|
console.log(`Lotics CLI v${VERSION} — AI agent interface for Lotics
|
|
@@ -51,6 +51,7 @@ COMMANDS
|
|
|
51
51
|
Build + upload current dir as a new version
|
|
52
52
|
(--force-workflow-sync wipes server-only
|
|
53
53
|
workflow aliases; default refuses)
|
|
54
|
+
lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
|
|
54
55
|
lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
|
|
55
56
|
|
|
56
57
|
FLAGS
|
|
@@ -359,6 +360,7 @@ async function main() {
|
|
|
359
360
|
console.error(" lotics app pull <app_id> [path] Pull an existing app for local editing");
|
|
360
361
|
console.error(" lotics app deploy [-m <message>] [--force-workflow-sync]");
|
|
361
362
|
console.error(" Build + upload the current directory");
|
|
363
|
+
console.error(" lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address");
|
|
362
364
|
console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
|
|
363
365
|
process.exit(1);
|
|
364
366
|
}
|
|
@@ -481,6 +483,16 @@ async function main() {
|
|
|
481
483
|
await appDeploy(client, { message, forceWorkflowSync: flags.forceWorkflowSync });
|
|
482
484
|
return;
|
|
483
485
|
}
|
|
486
|
+
if (subcommand === "subdomain") {
|
|
487
|
+
const newSubdomain = toolArgs;
|
|
488
|
+
if (!newSubdomain) {
|
|
489
|
+
console.error("Usage: lotics app subdomain <new-subdomain>");
|
|
490
|
+
console.error("Renames the app's public address — https://<slug>.lotics.app");
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
await appSetSubdomain(client, { subdomain: newSubdomain });
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
484
496
|
if (subcommand === "dev") {
|
|
485
497
|
// First positional is an optional project path (defaults to cwd).
|
|
486
498
|
// --port and --vite-port can override the wrapper / Vite ports.
|
package/dist/src/client.d.ts
CHANGED
|
@@ -105,6 +105,15 @@ export declare class LoticsClient {
|
|
|
105
105
|
workspace_id: string;
|
|
106
106
|
current_version_id: string | null;
|
|
107
107
|
}>;
|
|
108
|
+
/**
|
|
109
|
+
* Rename an app's public subdomain — its `<slug>.lotics.app` address.
|
|
110
|
+
* Mirrors PUT /v1/apps/{app_id}/subdomain. The old subdomain stops
|
|
111
|
+
* resolving once the change lands.
|
|
112
|
+
*/
|
|
113
|
+
setAppSubdomain(app_id: string, public_subdomain: string): Promise<{
|
|
114
|
+
app_id: string;
|
|
115
|
+
public_subdomain: string;
|
|
116
|
+
}>;
|
|
108
117
|
getAppVersion(app_id: string, version_id: string): Promise<{
|
|
109
118
|
id: string;
|
|
110
119
|
app_id: string;
|
package/dist/src/client.js
CHANGED
|
@@ -149,6 +149,14 @@ export class LoticsClient {
|
|
|
149
149
|
async createApp(body) {
|
|
150
150
|
return this.request("POST", "/v1/apps", { ...body, ui: { type: "custom_code" } });
|
|
151
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Rename an app's public subdomain — its `<slug>.lotics.app` address.
|
|
154
|
+
* Mirrors PUT /v1/apps/{app_id}/subdomain. The old subdomain stops
|
|
155
|
+
* resolving once the change lands.
|
|
156
|
+
*/
|
|
157
|
+
async setAppSubdomain(app_id, public_subdomain) {
|
|
158
|
+
return this.request("PUT", `/v1/apps/${encodeURIComponent(app_id)}/subdomain`, { public_subdomain });
|
|
159
|
+
}
|
|
152
160
|
async getAppVersion(app_id, version_id) {
|
|
153
161
|
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/versions/${encodeURIComponent(version_id)}`);
|
|
154
162
|
}
|
package/dist/src/dev/server.js
CHANGED
|
@@ -31,10 +31,19 @@ export async function startDevServer(args) {
|
|
|
31
31
|
});
|
|
32
32
|
// ── Vite child ──────────────────────────────────────────────────────────
|
|
33
33
|
// `npx vite` resolves to the project's installed Vite (devDependency in
|
|
34
|
-
// the starter).
|
|
34
|
+
// the starter). stdout/stderr are inherited so the dev sees Vite's banner
|
|
35
|
+
// + HMR logs.
|
|
36
|
+
//
|
|
37
|
+
// stdin is inherited ONLY under a TTY. Vite's dev server quits when a
|
|
38
|
+
// non-interactive stdin (a backgrounded launch, `</dev/null`, a closing
|
|
39
|
+
// pipe) reaches EOF — so inheriting it would kill the dev server the moment
|
|
40
|
+
// `lotics app dev` runs under an agent, CI, or `nohup`. A non-TTY launch
|
|
41
|
+
// gets `/dev/null` (`"ignore"`) instead, decoupling Vite's lifetime from
|
|
42
|
+
// however the wrapper was started. Vite's CLI shortcuts (`r`/`q`/`u`) are
|
|
43
|
+
// only meaningful under a TTY anyway.
|
|
35
44
|
const viteChild = spawn("npx", ["vite", "--port", String(vitePort), "--strictPort"], {
|
|
36
45
|
cwd: args.projectDir,
|
|
37
|
-
stdio: "inherit",
|
|
46
|
+
stdio: [process.stdin.isTTY ? "inherit" : "ignore", "inherit", "inherit"],
|
|
38
47
|
env: { ...process.env, FORCE_COLOR: "1" },
|
|
39
48
|
});
|
|
40
49
|
let stopped = false;
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
* Builds the wrapper HTML for `lotics app dev`.
|
|
3
3
|
*
|
|
4
4
|
* The wrapper page is served at http://localhost:<port>/ and embeds the
|
|
5
|
-
* project's Vite dev server in
|
|
6
|
-
* sandbox="allow-scripts" attribute production uses
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* project's Vite dev server in an iframe with the same
|
|
6
|
+
* sandbox="allow-scripts allow-same-origin" attribute production uses —
|
|
7
|
+
* safe because the Vite iframe is cross-origin to this wrapper (different
|
|
8
|
+
* port), so the app gets its own real origin (and Web Storage) without
|
|
9
|
+
* being able to reach the wrapper. The iframe sends postMessage RPCs to
|
|
10
|
+
* this wrapper, which forwards them to the local /_rpc endpoint, which
|
|
11
|
+
* dispatches to api.lotics.ai with the CLI's API key.
|
|
12
|
+
*
|
|
13
|
+
* postMessage is origin-locked both ways — the wrapper passes its origin
|
|
14
|
+
* via `?lotics_host=` and talks only to the Vite origin.
|
|
10
15
|
*
|
|
11
16
|
* Protocol matches `frontend/features/app_ui/app_iframe_host.tsx` exactly:
|
|
12
17
|
* iframe → wrapper: { id: number, op: string, payload: unknown }
|
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
* Builds the wrapper HTML for `lotics app dev`.
|
|
3
3
|
*
|
|
4
4
|
* The wrapper page is served at http://localhost:<port>/ and embeds the
|
|
5
|
-
* project's Vite dev server in
|
|
6
|
-
* sandbox="allow-scripts" attribute production uses
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* project's Vite dev server in an iframe with the same
|
|
6
|
+
* sandbox="allow-scripts allow-same-origin" attribute production uses —
|
|
7
|
+
* safe because the Vite iframe is cross-origin to this wrapper (different
|
|
8
|
+
* port), so the app gets its own real origin (and Web Storage) without
|
|
9
|
+
* being able to reach the wrapper. The iframe sends postMessage RPCs to
|
|
10
|
+
* this wrapper, which forwards them to the local /_rpc endpoint, which
|
|
11
|
+
* dispatches to api.lotics.ai with the CLI's API key.
|
|
12
|
+
*
|
|
13
|
+
* postMessage is origin-locked both ways — the wrapper passes its origin
|
|
14
|
+
* via `?lotics_host=` and talks only to the Vite origin.
|
|
10
15
|
*
|
|
11
16
|
* Protocol matches `frontend/features/app_ui/app_iframe_host.tsx` exactly:
|
|
12
17
|
* iframe → wrapper: { id: number, op: string, payload: unknown }
|
|
@@ -53,12 +58,19 @@ export function buildWrapperPage(args) {
|
|
|
53
58
|
<span>workspace <code>${escapeHtml(workspace_id)}</code></span>
|
|
54
59
|
<span>RPC → <code>${escapeHtml(api_url)}</code></span>
|
|
55
60
|
</header>
|
|
56
|
-
<iframe id="app" sandbox="allow-scripts" allow="clipboard-write"
|
|
61
|
+
<iframe id="app" sandbox="allow-scripts allow-same-origin" allow="clipboard-write"></iframe>
|
|
57
62
|
<script>
|
|
58
63
|
(function () {
|
|
59
64
|
const APP_ID = ${JSON.stringify(app_id)};
|
|
65
|
+
const VITE_URL = ${JSON.stringify(vite_url)};
|
|
66
|
+
const VITE_ORIGIN = new URL(VITE_URL).origin;
|
|
60
67
|
const iframe = document.getElementById("app");
|
|
61
68
|
|
|
69
|
+
// Pass the wrapper's own origin to the app via ?lotics_host= so the app
|
|
70
|
+
// SDK can origin-lock its postMessage bridge.
|
|
71
|
+
iframe.src = VITE_URL + (VITE_URL.indexOf("?") >= 0 ? "&" : "?")
|
|
72
|
+
+ "lotics_host=" + encodeURIComponent(window.location.origin);
|
|
73
|
+
|
|
62
74
|
async function rpc(op, payload) {
|
|
63
75
|
const res = await fetch("/_rpc", {
|
|
64
76
|
method: "POST",
|
|
@@ -104,7 +116,7 @@ export function buildWrapperPage(args) {
|
|
|
104
116
|
}
|
|
105
117
|
|
|
106
118
|
window.addEventListener("message", async function (event) {
|
|
107
|
-
if (event.source !== iframe.contentWindow) return;
|
|
119
|
+
if (event.source !== iframe.contentWindow || event.origin !== VITE_ORIGIN) return;
|
|
108
120
|
const msg = event.data;
|
|
109
121
|
if (!msg || typeof msg.id !== "number" || typeof msg.op !== "string") return;
|
|
110
122
|
const startedAt = performance.now();
|
|
@@ -114,13 +126,16 @@ export function buildWrapperPage(args) {
|
|
|
114
126
|
: await rpc(msg.op, msg.payload);
|
|
115
127
|
const ms = Math.round(performance.now() - startedAt);
|
|
116
128
|
console.debug("[lotics-dev] " + msg.op + " " + ms + "ms", data);
|
|
117
|
-
iframe.contentWindow.postMessage(
|
|
129
|
+
iframe.contentWindow.postMessage(
|
|
130
|
+
{ id: msg.id, type: "result", data: data },
|
|
131
|
+
VITE_ORIGIN
|
|
132
|
+
);
|
|
118
133
|
} catch (err) {
|
|
119
134
|
const message = err && err.message ? err.message : String(err);
|
|
120
135
|
console.error("[lotics-dev] " + msg.op + " failed:", message);
|
|
121
136
|
iframe.contentWindow.postMessage(
|
|
122
137
|
{ id: msg.id, type: "error", message: message },
|
|
123
|
-
|
|
138
|
+
VITE_ORIGIN
|
|
124
139
|
);
|
|
125
140
|
}
|
|
126
141
|
});
|
|
@@ -136,6 +151,3 @@ function escapeHtml(s) {
|
|
|
136
151
|
.replace(/</g, "<")
|
|
137
152
|
.replace(/>/g, ">");
|
|
138
153
|
}
|
|
139
|
-
function escapeAttr(s) {
|
|
140
|
-
return escapeHtml(s).replace(/"/g, """);
|
|
141
|
-
}
|
|
@@ -50,7 +50,7 @@ export function buildStarterTemplate(args) {
|
|
|
50
50
|
test: "vitest run",
|
|
51
51
|
},
|
|
52
52
|
dependencies: {
|
|
53
|
-
"@lotics/app-sdk": "^0.
|
|
53
|
+
"@lotics/app-sdk": "^0.7.0",
|
|
54
54
|
"@lotics/ui": "^1.3.0",
|
|
55
55
|
"@react-native-picker/picker": "^2.7.0",
|
|
56
56
|
"expo-image": "~3.0.9",
|
|
@@ -155,6 +155,11 @@ export default defineConfig({
|
|
|
155
155
|
// iframe loads modules from api.lotics.ai which already permits null
|
|
156
156
|
// origin via CORS.
|
|
157
157
|
cors: { origin: "*" },
|
|
158
|
+
// @lotics/ui/fonts.css references the API's /iframe/fonts/*.woff2 files
|
|
159
|
+
// by root-relative URL. A deployed app is served from the API origin so
|
|
160
|
+
// they resolve directly; under \`lotics app dev\` the app runs on
|
|
161
|
+
// localhost, so proxy /iframe to the API to load the real fonts.
|
|
162
|
+
proxy: { "/iframe": { target: "https://api.lotics.ai", changeOrigin: true } },
|
|
158
163
|
},
|
|
159
164
|
test: {
|
|
160
165
|
environment: "jsdom",
|