@lotics/cli 0.205.1 → 0.205.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/AGENTS.md +7 -4
- package/README.md +9 -6
- package/dist/src/cli.js +515 -263
- package/dist/src/client.d.ts +6 -1
- package/dist/src/client.js +9 -2
- package/docs/building_an_app.md +23 -0
- package/docs/cli_reference.md +4 -4
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -15559,8 +15559,15 @@ var LoticsClient = class {
|
|
|
15559
15559
|
* no caller has to remember that a partial payload silently drops
|
|
15560
15560
|
* `instructions`, `outputs` and the model pin.
|
|
15561
15561
|
*/
|
|
15562
|
-
async setAppAgent(app_id, alias, patch) {
|
|
15563
|
-
return this.execute("set_app_agent", {
|
|
15562
|
+
async setAppAgent(app_id, alias, patch, acknowledge_breaking_api_change) {
|
|
15563
|
+
return this.execute("set_app_agent", {
|
|
15564
|
+
app_id,
|
|
15565
|
+
alias,
|
|
15566
|
+
...patch,
|
|
15567
|
+
// Sent only when the caller asked for it: absent means "refuse a break",
|
|
15568
|
+
// which is the answer a caller who said nothing gave.
|
|
15569
|
+
...acknowledge_breaking_api_change ? { acknowledge_breaking_api_change: true } : {}
|
|
15570
|
+
});
|
|
15564
15571
|
}
|
|
15565
15572
|
/**
|
|
15566
15573
|
* Fetch one app workflow's faithful source + bound input/output schemas via
|
|
@@ -34267,6 +34274,9 @@ var appAgentDeclarationSchema = zod_default.object({
|
|
|
34267
34274
|
"Set by the server when a registry copy materialized this agent. An upgrade replaces the declaration only while it still matches this fingerprint."
|
|
34268
34275
|
)
|
|
34269
34276
|
});
|
|
34277
|
+
function declaresSchema(half) {
|
|
34278
|
+
return half !== void 0 && Object.keys(half).length > 0;
|
|
34279
|
+
}
|
|
34270
34280
|
var appThemeSchema = zod_default.object({
|
|
34271
34281
|
color: optionColorSchema.nullable().optional().describe("Theme color for the app")
|
|
34272
34282
|
});
|
|
@@ -40111,7 +40121,7 @@ function resultSideEffects(result) {
|
|
|
40111
40121
|
}
|
|
40112
40122
|
|
|
40113
40123
|
// src/version.ts
|
|
40114
|
-
var VERSION = "0.205.
|
|
40124
|
+
var VERSION = "0.205.2";
|
|
40115
40125
|
|
|
40116
40126
|
// src/timezone.ts
|
|
40117
40127
|
function machineTimezone() {
|
|
@@ -40671,6 +40681,12 @@ var COMMANDS = [
|
|
|
40671
40681
|
" lotics app create <name> [path] Create a new custom-code app + scaffold locally",
|
|
40672
40682
|
" [--from <model.json>#<app>] \u2026 with the plan's screens: each a registry shape",
|
|
40673
40683
|
" over the live table its entity became",
|
|
40684
|
+
" [--api] \u2026 with NO screens: the app's declared queries and",
|
|
40685
|
+
" workflows are the whole of what it offers, called",
|
|
40686
|
+
" over HTTP by the customer's own site, server or",
|
|
40687
|
+
" agent. No bundle, so nothing is built or deployed \u2014",
|
|
40688
|
+
" app query set / app workflow set publish each",
|
|
40689
|
+
" declaration, and app api publish snapshots them",
|
|
40674
40690
|
" lotics app pull <app_id> [path] Bootstrap full local env (source + npm install + types)",
|
|
40675
40691
|
" lotics app deploy [--prune] [-m <msg>]",
|
|
40676
40692
|
" Typecheck + build + upload current dir as a new version \u2014 COMMIT",
|
|
@@ -40692,7 +40708,7 @@ var COMMANDS = [
|
|
|
40692
40708
|
" promises. Without it such a release is refused and",
|
|
40693
40709
|
" every breaking change is named. It counts only from",
|
|
40694
40710
|
" an organization admin. Same flag on app query set,",
|
|
40695
|
-
" app workflow set and app upgrade",
|
|
40711
|
+
" app workflow set, app agent set and app upgrade",
|
|
40696
40712
|
" lotics app versions [app_id] Show deploy history newest-first (version,",
|
|
40697
40713
|
" timestamp, deployer, build status, -m message;",
|
|
40698
40714
|
" * marks the currently served version)",
|
|
@@ -40767,6 +40783,9 @@ var COMMANDS = [
|
|
|
40767
40783
|
" fields keep whatever is bound (a manifest is a",
|
|
40768
40784
|
" snapshot; replaying it would revert them). Change",
|
|
40769
40785
|
" those with set_app_agent, then pull.",
|
|
40786
|
+
" [--acknowledge-breaking-api] \u2026 even though it breaks what the app's published API",
|
|
40787
|
+
" promises: an agent's alias and its declared edges",
|
|
40788
|
+
" are in the contract too",
|
|
40770
40789
|
" lotics app subdomain <new-subdomain> Rename the app's public address (its subdomain)",
|
|
40771
40790
|
` lotics app rename "<new name>" Rename the app's display name (launcher title)`,
|
|
40772
40791
|
" lotics app dev [path] [--port=<n>] [--vite-port=<n>]",
|
|
@@ -41049,136 +41068,237 @@ var VITEST_SETUP_CONTENT = `// Runs before every test file (wired by vite.config
|
|
|
41049
41068
|
// timezone pinning, anything your suite needs before it collects.
|
|
41050
41069
|
export {};
|
|
41051
41070
|
`;
|
|
41071
|
+
function packageNameFrom(appName) {
|
|
41072
|
+
const folded = appName.normalize("NFD").replace(/[\u{0300}-\u{036f}]/gu, "").replace(/[đĐ]/g, (c) => c === "\u0111" ? "d" : "D");
|
|
41073
|
+
return folded.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 64) || "lotics-app";
|
|
41074
|
+
}
|
|
41075
|
+
function manifestFile(args) {
|
|
41076
|
+
return {
|
|
41077
|
+
path: "package.json",
|
|
41078
|
+
content: JSON.stringify(
|
|
41079
|
+
{
|
|
41080
|
+
name: packageNameFrom(args.app_name),
|
|
41081
|
+
version: "0.0.1",
|
|
41082
|
+
private: true,
|
|
41083
|
+
type: "module",
|
|
41084
|
+
scripts: args.scripts,
|
|
41085
|
+
...args.dependencies === void 0 ? {} : { dependencies: args.dependencies },
|
|
41086
|
+
devDependencies: args.devDependencies,
|
|
41087
|
+
lotics: {
|
|
41088
|
+
app_id: args.app_id,
|
|
41089
|
+
workspace_id: args.workspace_id,
|
|
41090
|
+
current_version_id: null,
|
|
41091
|
+
version_number: null
|
|
41092
|
+
}
|
|
41093
|
+
},
|
|
41094
|
+
null,
|
|
41095
|
+
2
|
|
41096
|
+
) + "\n"
|
|
41097
|
+
};
|
|
41098
|
+
}
|
|
41099
|
+
function tsconfigFile() {
|
|
41100
|
+
return {
|
|
41101
|
+
path: "tsconfig.json",
|
|
41102
|
+
content: JSON.stringify(
|
|
41103
|
+
{
|
|
41104
|
+
compilerOptions: {
|
|
41105
|
+
target: "ES2022",
|
|
41106
|
+
useDefineForClassFields: true,
|
|
41107
|
+
lib: ["ES2022", "DOM", "DOM.Iterable"],
|
|
41108
|
+
module: "ESNext",
|
|
41109
|
+
skipLibCheck: true,
|
|
41110
|
+
moduleResolution: "Bundler",
|
|
41111
|
+
allowImportingTsExtensions: true,
|
|
41112
|
+
resolveJsonModule: true,
|
|
41113
|
+
isolatedModules: true,
|
|
41114
|
+
noEmit: true,
|
|
41115
|
+
jsx: "react-jsx",
|
|
41116
|
+
strict: true,
|
|
41117
|
+
noFallthroughCasesInSwitch: true
|
|
41118
|
+
},
|
|
41119
|
+
// Restrict tsc to the project's own sources — a `noEmit` program has
|
|
41120
|
+
// no reason to walk anything else, and under the `LOTICS_UI_SRC`
|
|
41121
|
+
// dev-link `@lotics/ui` resolves to a working copy of the kit's own
|
|
41122
|
+
// .tsx, which strict rules (noUnusedLocals etc.) would then fire on.
|
|
41123
|
+
// `.lotics/**/*` (not bare `.lotics`): TypeScript's include glob walk
|
|
41124
|
+
// SKIPS dot-directories, so a bare `.lotics` silently loads zero of the
|
|
41125
|
+
// generated `.d.ts` augmentations and typed useWorkflow/useQuery/useAgentRun
|
|
41126
|
+
// fall to `unknown`. The explicit glob makes the dot-dir the non-wildcard
|
|
41127
|
+
// base, which IS read. (GAP-38.)
|
|
41128
|
+
include: ["src", ".lotics/**/*"],
|
|
41129
|
+
// src/workflows/<alias>.ts bodies run on the server (workflow globals:
|
|
41130
|
+
// trigger / runtime / tool calls), and each pulls its own per-alias
|
|
41131
|
+
// ambient globals under `.lotics/workflows/`. BOTH are excluded from
|
|
41132
|
+
// the main typecheck: the bodies would apply the app's DOM lib (the
|
|
41133
|
+
// server doesn't), and the per-alias `.globals.d.ts` files each declare
|
|
41134
|
+
// their OWN ambient `trigger` — loading several into one program
|
|
41135
|
+
// collides those declarations and reports false errors. Type-check the
|
|
41136
|
+
// bodies with `lotics app workflow check` instead — it builds ONE
|
|
41137
|
+
// isolated program per alias (mirroring the server's set-time verify),
|
|
41138
|
+
// so the ambient `trigger` is unambiguous. The server stays the single
|
|
41139
|
+
// verifier on `lotics app workflow set`.
|
|
41140
|
+
exclude: ["node_modules", "src/workflows", ".lotics/workflows"]
|
|
41141
|
+
},
|
|
41142
|
+
null,
|
|
41143
|
+
2
|
|
41144
|
+
) + "\n"
|
|
41145
|
+
};
|
|
41146
|
+
}
|
|
41147
|
+
function gitignoreFile() {
|
|
41148
|
+
return {
|
|
41149
|
+
path: ".gitignore",
|
|
41150
|
+
content: `node_modules
|
|
41151
|
+
dist
|
|
41152
|
+
*.tsbuildinfo
|
|
41153
|
+
.DS_Store
|
|
41154
|
+
.lotics
|
|
41155
|
+
coverage
|
|
41156
|
+
`
|
|
41157
|
+
};
|
|
41158
|
+
}
|
|
41159
|
+
function ciWorkflowFile(steps) {
|
|
41160
|
+
return {
|
|
41161
|
+
path: ".github/workflows/ci.yml",
|
|
41162
|
+
content: `name: CI
|
|
41163
|
+
|
|
41164
|
+
on:
|
|
41165
|
+
push:
|
|
41166
|
+
branches: [main]
|
|
41167
|
+
pull_request:
|
|
41168
|
+
|
|
41169
|
+
jobs:
|
|
41170
|
+
ci:
|
|
41171
|
+
runs-on: ubuntu-latest
|
|
41172
|
+
steps:
|
|
41173
|
+
- uses: actions/checkout@v4
|
|
41174
|
+
- uses: actions/setup-node@v4
|
|
41175
|
+
with:
|
|
41176
|
+
node-version: 22
|
|
41177
|
+
- run: npm ci
|
|
41178
|
+
${steps.map((step) => ` - run: ${step}
|
|
41179
|
+
`).join("")}`
|
|
41180
|
+
};
|
|
41181
|
+
}
|
|
41182
|
+
function workflowsReadmeFile() {
|
|
41183
|
+
return {
|
|
41184
|
+
path: "src/workflows/README.md",
|
|
41185
|
+
content: `# Workflow bodies
|
|
41186
|
+
|
|
41187
|
+
Editable JS-subset bodies of this app's workflows live here, one file per alias:
|
|
41188
|
+
\`src/workflows/<alias>.ts\`.
|
|
41189
|
+
|
|
41190
|
+
## Loop
|
|
41191
|
+
|
|
41192
|
+
\`\`\`bash
|
|
41193
|
+
lotics app workflow pull # write/refresh every src/workflows/<alias>.ts from the server
|
|
41194
|
+
# edit src/workflows/<alias>.ts
|
|
41195
|
+
lotics app workflow check <alias> # typecheck the body locally (same verdict as \`set\`)
|
|
41196
|
+
lotics app workflow set <alias> # push it back through set_app_workflow (the server verifies)
|
|
41197
|
+
\`\`\`
|
|
41198
|
+
|
|
41199
|
+
\`lotics app pull <app_id>\` also writes these files (alongside the rest of the project).
|
|
41200
|
+
|
|
41201
|
+
## What a body is
|
|
41202
|
+
|
|
41203
|
+
A workflow body is a **JS-subset expression** that runs server-side using workflow
|
|
41204
|
+
globals (\`trigger\`, \`runtime\`, tool calls) and ends with \`return({ data })\`. Each
|
|
41205
|
+
pulled file wraps the body in the server's \`__workflow\` envelope and references its
|
|
41206
|
+
per-alias ambient globals at \`.lotics/workflows/<alias>.globals.d.ts\`, so it **is**
|
|
41207
|
+
locally typecheckable:
|
|
41208
|
+
|
|
41209
|
+
\`\`\`bash
|
|
41210
|
+
lotics app workflow check # check every body; [alias] to check one
|
|
41211
|
+
\`\`\`
|
|
41212
|
+
|
|
41213
|
+
\`check\` builds ONE isolated program per alias (the app's own \`typescript\`, the
|
|
41214
|
+
server's compile options \u2014 strict, lib es2022 with no DOM), so each body's ambient
|
|
41215
|
+
\`trigger\` is unambiguous and the verdict mirrors set-time verify. The main
|
|
41216
|
+
\`npm run typecheck\` excludes \`src/workflows\` + \`.lotics/workflows\` (the bodies would
|
|
41217
|
+
apply the app's DOM lib, and the per-alias globals collide on \`trigger\`). The
|
|
41218
|
+
verification that matters still runs on the **server** when you \`set\` \u2014 the same
|
|
41219
|
+
guarantee as authoring via \`set_app_workflow\` directly. Edit only the body BETWEEN
|
|
41220
|
+
the wrapper lines; the wrapper, the \`/// <reference>\`, and the \`export {};\` marker
|
|
41221
|
+
are CLI bookkeeping (stripped on \`set\`). Do not rename a file (the filename is the
|
|
41222
|
+
alias the binding is keyed by).
|
|
41223
|
+
|
|
41224
|
+
## Authority
|
|
41225
|
+
|
|
41226
|
+
\`lotics app deploy\` never authors workflows \u2014 it carries code, queries, and
|
|
41227
|
+
capabilities only. \`apps.workflows\` has exactly one author: \`set_app_workflow\`
|
|
41228
|
+
(which \`lotics app workflow set\` calls). The typed \`inputs\`/\`outputs\` schema for
|
|
41229
|
+
each alias lives in \`package.json#lotics.workflows.<alias>\`.
|
|
41230
|
+
`
|
|
41231
|
+
};
|
|
41232
|
+
}
|
|
41052
41233
|
function buildStarterTemplate(args) {
|
|
41053
41234
|
const uiVersion = args.ui_version;
|
|
41054
41235
|
const sdkVersion = args.sdk_version;
|
|
41055
|
-
const folded = args.app_name.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[đĐ]/g, (c) => c === "\u0111" ? "d" : "D");
|
|
41056
|
-
const sanitizedPkgName = folded.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 64) || "lotics-app";
|
|
41057
41236
|
return [
|
|
41058
|
-
{
|
|
41059
|
-
|
|
41060
|
-
|
|
41061
|
-
|
|
41062
|
-
|
|
41063
|
-
|
|
41064
|
-
|
|
41065
|
-
|
|
41066
|
-
|
|
41067
|
-
|
|
41068
|
-
|
|
41069
|
-
|
|
41070
|
-
|
|
41071
|
-
|
|
41072
|
-
|
|
41073
|
-
|
|
41074
|
-
|
|
41075
|
-
|
|
41076
|
-
|
|
41077
|
-
|
|
41078
|
-
|
|
41079
|
-
|
|
41080
|
-
|
|
41081
|
-
|
|
41082
|
-
|
|
41083
|
-
|
|
41084
|
-
|
|
41085
|
-
|
|
41086
|
-
|
|
41087
|
-
|
|
41088
|
-
|
|
41089
|
-
|
|
41090
|
-
|
|
41091
|
-
|
|
41092
|
-
|
|
41093
|
-
|
|
41094
|
-
|
|
41095
|
-
|
|
41096
|
-
|
|
41097
|
-
|
|
41098
|
-
|
|
41099
|
-
|
|
41100
|
-
|
|
41101
|
-
|
|
41102
|
-
|
|
41103
|
-
|
|
41104
|
-
|
|
41105
|
-
|
|
41106
|
-
|
|
41107
|
-
|
|
41108
|
-
|
|
41109
|
-
|
|
41110
|
-
|
|
41111
|
-
|
|
41112
|
-
|
|
41113
|
-
|
|
41114
|
-
|
|
41115
|
-
|
|
41116
|
-
|
|
41117
|
-
|
|
41118
|
-
|
|
41119
|
-
|
|
41120
|
-
|
|
41121
|
-
|
|
41122
|
-
|
|
41123
|
-
vitest: "^2.1.0"
|
|
41124
|
-
},
|
|
41125
|
-
lotics: {
|
|
41126
|
-
app_id: args.app_id,
|
|
41127
|
-
workspace_id: args.workspace_id,
|
|
41128
|
-
current_version_id: null,
|
|
41129
|
-
version_number: null
|
|
41130
|
-
}
|
|
41131
|
-
},
|
|
41132
|
-
null,
|
|
41133
|
-
2
|
|
41134
|
-
) + "\n"
|
|
41135
|
-
},
|
|
41136
|
-
{
|
|
41137
|
-
path: "tsconfig.json",
|
|
41138
|
-
content: JSON.stringify(
|
|
41139
|
-
{
|
|
41140
|
-
compilerOptions: {
|
|
41141
|
-
target: "ES2022",
|
|
41142
|
-
useDefineForClassFields: true,
|
|
41143
|
-
lib: ["ES2022", "DOM", "DOM.Iterable"],
|
|
41144
|
-
module: "ESNext",
|
|
41145
|
-
skipLibCheck: true,
|
|
41146
|
-
moduleResolution: "Bundler",
|
|
41147
|
-
allowImportingTsExtensions: true,
|
|
41148
|
-
resolveJsonModule: true,
|
|
41149
|
-
isolatedModules: true,
|
|
41150
|
-
noEmit: true,
|
|
41151
|
-
jsx: "react-jsx",
|
|
41152
|
-
strict: true,
|
|
41153
|
-
noFallthroughCasesInSwitch: true
|
|
41154
|
-
},
|
|
41155
|
-
// Restrict tsc to the project's own sources — a `noEmit` program has
|
|
41156
|
-
// no reason to walk anything else, and under the `LOTICS_UI_SRC`
|
|
41157
|
-
// dev-link `@lotics/ui` resolves to a working copy of the kit's own
|
|
41158
|
-
// .tsx, which strict rules (noUnusedLocals etc.) would then fire on.
|
|
41159
|
-
// `.lotics/**/*` (not bare `.lotics`): TypeScript's include glob walk
|
|
41160
|
-
// SKIPS dot-directories, so a bare `.lotics` silently loads zero of the
|
|
41161
|
-
// generated `.d.ts` augmentations and typed useWorkflow/useQuery/useAgentRun
|
|
41162
|
-
// fall to `unknown`. The explicit glob makes the dot-dir the non-wildcard
|
|
41163
|
-
// base, which IS read. (GAP-38.)
|
|
41164
|
-
include: ["src", ".lotics/**/*"],
|
|
41165
|
-
// src/workflows/<alias>.ts bodies run on the server (workflow globals:
|
|
41166
|
-
// trigger / runtime / tool calls), and each pulls its own per-alias
|
|
41167
|
-
// ambient globals under `.lotics/workflows/`. BOTH are excluded from
|
|
41168
|
-
// the main typecheck: the bodies would apply the app's DOM lib (the
|
|
41169
|
-
// server doesn't), and the per-alias `.globals.d.ts` files each declare
|
|
41170
|
-
// their OWN ambient `trigger` — loading several into one program
|
|
41171
|
-
// collides those declarations and reports false errors. Type-check the
|
|
41172
|
-
// bodies with `lotics app workflow check` instead — it builds ONE
|
|
41173
|
-
// isolated program per alias (mirroring the server's set-time verify),
|
|
41174
|
-
// so the ambient `trigger` is unambiguous. The server stays the single
|
|
41175
|
-
// verifier on `lotics app workflow set`.
|
|
41176
|
-
exclude: ["node_modules", "src/workflows", ".lotics/workflows"]
|
|
41177
|
-
},
|
|
41178
|
-
null,
|
|
41179
|
-
2
|
|
41180
|
-
) + "\n"
|
|
41181
|
-
},
|
|
41237
|
+
manifestFile({
|
|
41238
|
+
app_name: args.app_name,
|
|
41239
|
+
app_id: args.app_id,
|
|
41240
|
+
workspace_id: args.workspace_id,
|
|
41241
|
+
scripts: {
|
|
41242
|
+
dev: "vite",
|
|
41243
|
+
build: "vite build",
|
|
41244
|
+
preview: "vite preview",
|
|
41245
|
+
typecheck: "tsc --noEmit",
|
|
41246
|
+
// Scope to src/ — oxlint's default scan walks node_modules too.
|
|
41247
|
+
lint: "oxlint src",
|
|
41248
|
+
// No `--passWithNoTests`, deliberately. It would be justified by a
|
|
41249
|
+
// TEMPORARY condition ("the scaffold seeds nothing") and then ship
|
|
41250
|
+
// forever in every app's package.json and CI, where its only
|
|
41251
|
+
// remaining effect is to turn "vitest discovered no test files" into
|
|
41252
|
+
// success — a green suite over a renamed `src/`, a changed `include`
|
|
41253
|
+
// or a broken vitest config, indefinitely. A detector that cannot
|
|
41254
|
+
// fire when the thing is ABSENT rather than excessive is not one.
|
|
41255
|
+
//
|
|
41256
|
+
// What the scaffold seeds instead is `src/harness.test.tsx`, which
|
|
41257
|
+
// asserts the HARNESS and nothing else. The file it replaces
|
|
41258
|
+
// (`src/App.test.tsx`) asserted the scaffold's placeholder copy
|
|
41259
|
+
// ("First item"), so it failed the moment the author did the very
|
|
41260
|
+
// thing the closing instructions tell them to do — edit
|
|
41261
|
+
// `src/App.tsx` — and a smoke test that renders `<App/>` would fail
|
|
41262
|
+
// the same way for any real app whose first screen reaches the host
|
|
41263
|
+
// bridge on mount. The harness test touches neither.
|
|
41264
|
+
test: "vitest run"
|
|
41265
|
+
},
|
|
41266
|
+
dependencies: {
|
|
41267
|
+
"@lotics/app-sdk": sdkVersion,
|
|
41268
|
+
// The FilePreview engines (pdfjs-dist / @lotics/docx / @lotics/xlsx) are
|
|
41269
|
+
// NOT listed here: they ship as @lotics/ui dependencies (7.14.0+), so
|
|
41270
|
+
// every app gets them transitively — one version source, no skew. An app
|
|
41271
|
+
// that IMPORTS @lotics/docx or @lotics/xlsx directly (its own doc/excel
|
|
41272
|
+
// builders) declares its own dependency then.
|
|
41273
|
+
"@lotics/ui": uiVersion,
|
|
41274
|
+
// @base-ui/react is deliberately absent: @lotics/ui declares it as an
|
|
41275
|
+
// EXACT peer, npm installs a peer, and a second declaration here is a
|
|
41276
|
+
// version that drifts from the kit's — which Base UI punishes
|
|
41277
|
+
// silently, since it passes state through React context and a nested
|
|
41278
|
+
// copy renders parts that never open.
|
|
41279
|
+
"lucide-react": "^0.562.0",
|
|
41280
|
+
react: "^19.0.0",
|
|
41281
|
+
"react-dom": "^19.0.0",
|
|
41282
|
+
// In-app routing for the scaffolded list→detail example. The app owns
|
|
41283
|
+
// its routing; the SDK's `isEmbedded()` picks memory vs browser history. `react-router` is the
|
|
41284
|
+
// canonical package (react-router-dom is a shim over it).
|
|
41285
|
+
"react-router": "^8.3.0"
|
|
41286
|
+
},
|
|
41287
|
+
devDependencies: {
|
|
41288
|
+
"@testing-library/react": "^16.1.0",
|
|
41289
|
+
"@types/react": "^19.0.0",
|
|
41290
|
+
"@types/react-dom": "^19.0.0",
|
|
41291
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
41292
|
+
jsdom: "^25.0.0",
|
|
41293
|
+
oxlint: "^0.13.0",
|
|
41294
|
+
// Track the platform's TS major so `lotics app workflow check` (which runs
|
|
41295
|
+
// the app's OWN typescript) gives the same verdict the server does at set-time.
|
|
41296
|
+
typescript: "~6.0.3",
|
|
41297
|
+
vite: "^5.4.0",
|
|
41298
|
+
vitest: "^2.1.0"
|
|
41299
|
+
}
|
|
41300
|
+
}),
|
|
41301
|
+
tsconfigFile(),
|
|
41182
41302
|
{
|
|
41183
41303
|
path: "vite.config.ts",
|
|
41184
41304
|
content: `/// <reference types="vitest" />
|
|
@@ -41508,63 +41628,7 @@ export default function App() {
|
|
|
41508
41628
|
}
|
|
41509
41629
|
`
|
|
41510
41630
|
},
|
|
41511
|
-
|
|
41512
|
-
// The home for editable workflow bodies. `lotics app pull` writes one
|
|
41513
|
-
// `src/workflows/<alias>.ts` per bound workflow (faithful server source);
|
|
41514
|
-
// edit it, then `lotics app workflow set <alias>` pushes it back through
|
|
41515
|
-
// set_app_workflow (the server verifies). A new app has no bound workflows
|
|
41516
|
-
// yet, so this is just the docked directory + the loop reference. It's a
|
|
41517
|
-
// .md (not a .ts) so tsc's `include: ["src"]` never tries to compile it —
|
|
41518
|
-
// a workflow body is a JS-subset expression over server globals
|
|
41519
|
-
// (trigger / runtime / tool calls) that does NOT typecheck standalone.
|
|
41520
|
-
path: "src/workflows/README.md",
|
|
41521
|
-
content: `# Workflow bodies
|
|
41522
|
-
|
|
41523
|
-
Editable JS-subset bodies of this app's workflows live here, one file per alias:
|
|
41524
|
-
\`src/workflows/<alias>.ts\`.
|
|
41525
|
-
|
|
41526
|
-
## Loop
|
|
41527
|
-
|
|
41528
|
-
\`\`\`bash
|
|
41529
|
-
lotics app workflow pull # write/refresh every src/workflows/<alias>.ts from the server
|
|
41530
|
-
# edit src/workflows/<alias>.ts
|
|
41531
|
-
lotics app workflow check <alias> # typecheck the body locally (same verdict as \`set\`)
|
|
41532
|
-
lotics app workflow set <alias> # push it back through set_app_workflow (the server verifies)
|
|
41533
|
-
\`\`\`
|
|
41534
|
-
|
|
41535
|
-
\`lotics app pull <app_id>\` also writes these files (alongside the rest of the project).
|
|
41536
|
-
|
|
41537
|
-
## What a body is
|
|
41538
|
-
|
|
41539
|
-
A workflow body is a **JS-subset expression** that runs server-side using workflow
|
|
41540
|
-
globals (\`trigger\`, \`runtime\`, tool calls) and ends with \`return({ data })\`. Each
|
|
41541
|
-
pulled file wraps the body in the server's \`__workflow\` envelope and references its
|
|
41542
|
-
per-alias ambient globals at \`.lotics/workflows/<alias>.globals.d.ts\`, so it **is**
|
|
41543
|
-
locally typecheckable:
|
|
41544
|
-
|
|
41545
|
-
\`\`\`bash
|
|
41546
|
-
lotics app workflow check # check every body; [alias] to check one
|
|
41547
|
-
\`\`\`
|
|
41548
|
-
|
|
41549
|
-
\`check\` builds ONE isolated program per alias (the app's own \`typescript\`, the
|
|
41550
|
-
server's compile options \u2014 strict, lib es2022 with no DOM), so each body's ambient
|
|
41551
|
-
\`trigger\` is unambiguous and the verdict mirrors set-time verify. The main
|
|
41552
|
-
\`npm run typecheck\` excludes \`src/workflows\` + \`.lotics/workflows\` (the bodies would
|
|
41553
|
-
apply the app's DOM lib, and the per-alias globals collide on \`trigger\`). The
|
|
41554
|
-
verification that matters still runs on the **server** when you \`set\` \u2014 the same
|
|
41555
|
-
guarantee as authoring via \`set_app_workflow\` directly. Edit only the body BETWEEN
|
|
41556
|
-
the wrapper lines; the wrapper, the \`/// <reference>\`, and the \`export {};\` marker
|
|
41557
|
-
are CLI bookkeeping (stripped on \`set\`). Do not rename a file (the filename is the
|
|
41558
|
-
alias the binding is keyed by).
|
|
41559
|
-
|
|
41560
|
-
## Authority
|
|
41561
|
-
|
|
41562
|
-
\`lotics app deploy\` never authors workflows \u2014 it carries code, queries, and
|
|
41563
|
-
capabilities only. \`apps.workflows\` has exactly one author: \`set_app_workflow\`
|
|
41564
|
-
(which \`lotics app workflow set\` calls). The typed \`inputs\`/\`outputs\` schema for
|
|
41565
|
-
each alias lives in \`package.json#lotics.workflows.<alias>\`.
|
|
41566
|
-
`
|
|
41567
|
-
},
|
|
41631
|
+
workflowsReadmeFile(),
|
|
41568
41632
|
{
|
|
41569
41633
|
path: "src/css_modules.d.ts",
|
|
41570
41634
|
content: `declare module "*.module.css" {
|
|
@@ -41610,40 +41674,8 @@ describe("test harness", () => {
|
|
|
41610
41674
|
path: VITEST_SETUP_FILENAME,
|
|
41611
41675
|
content: VITEST_SETUP_CONTENT
|
|
41612
41676
|
},
|
|
41613
|
-
|
|
41614
|
-
|
|
41615
|
-
content: `name: CI
|
|
41616
|
-
|
|
41617
|
-
on:
|
|
41618
|
-
push:
|
|
41619
|
-
branches: [main]
|
|
41620
|
-
pull_request:
|
|
41621
|
-
|
|
41622
|
-
jobs:
|
|
41623
|
-
ci:
|
|
41624
|
-
runs-on: ubuntu-latest
|
|
41625
|
-
steps:
|
|
41626
|
-
- uses: actions/checkout@v4
|
|
41627
|
-
- uses: actions/setup-node@v4
|
|
41628
|
-
with:
|
|
41629
|
-
node-version: 22
|
|
41630
|
-
- run: npm ci
|
|
41631
|
-
- run: npm run typecheck
|
|
41632
|
-
- run: npm run lint
|
|
41633
|
-
- run: npm test
|
|
41634
|
-
- run: npm run build
|
|
41635
|
-
`
|
|
41636
|
-
},
|
|
41637
|
-
{
|
|
41638
|
-
path: ".gitignore",
|
|
41639
|
-
content: `node_modules
|
|
41640
|
-
dist
|
|
41641
|
-
*.tsbuildinfo
|
|
41642
|
-
.DS_Store
|
|
41643
|
-
.lotics
|
|
41644
|
-
coverage
|
|
41645
|
-
`
|
|
41646
|
-
},
|
|
41677
|
+
ciWorkflowFile(["npm run typecheck", "npm run lint", "npm test", "npm run build"]),
|
|
41678
|
+
gitignoreFile(),
|
|
41647
41679
|
{
|
|
41648
41680
|
path: "AGENTS.md",
|
|
41649
41681
|
content: `# ${args.app_name} \u2014 for the agent editing this app
|
|
@@ -41767,6 +41799,144 @@ building.
|
|
|
41767
41799
|
}
|
|
41768
41800
|
];
|
|
41769
41801
|
}
|
|
41802
|
+
function buildApiStarterTemplate(args) {
|
|
41803
|
+
return [
|
|
41804
|
+
manifestFile({
|
|
41805
|
+
app_name: args.app_name,
|
|
41806
|
+
app_id: args.app_id,
|
|
41807
|
+
workspace_id: args.workspace_id,
|
|
41808
|
+
// `typecheck` alone. There is no bundle to build, no component to render
|
|
41809
|
+
// under jsdom and no DOM source for a linter to scan — a script that
|
|
41810
|
+
// exists and does nothing is a green check that means nothing.
|
|
41811
|
+
scripts: { typecheck: "tsc --noEmit" },
|
|
41812
|
+
devDependencies: { typescript: "~6.0.3" }
|
|
41813
|
+
}),
|
|
41814
|
+
tsconfigFile(),
|
|
41815
|
+
workflowsReadmeFile(),
|
|
41816
|
+
// `.lotics/` is generated and gitignored, so a fresh clone holds no types at
|
|
41817
|
+
// all and `tsc` would refuse a program with no inputs. `app codegen` writes
|
|
41818
|
+
// them from the manifest with no credential and no network, which is exactly
|
|
41819
|
+
// what a CI runner has.
|
|
41820
|
+
ciWorkflowFile(["npx --yes @lotics/cli app codegen", "npm run typecheck"]),
|
|
41821
|
+
gitignoreFile(),
|
|
41822
|
+
{
|
|
41823
|
+
path: "AGENTS.md",
|
|
41824
|
+
content: `# ${args.app_name} \u2014 for the agent editing this app
|
|
41825
|
+
|
|
41826
|
+
A Lotics app with **no screens**. What it offers is its declared queries and workflows, called
|
|
41827
|
+
over HTTP by something outside Lotics \u2014 a website, a server, another agent. There is no React
|
|
41828
|
+
here, no bundle, and nothing to deploy: \`lotics app query set\` and \`lotics app workflow set\`
|
|
41829
|
+
publish each declaration on their own.
|
|
41830
|
+
|
|
41831
|
+
## Read in this order, and stop when you have the answer
|
|
41832
|
+
|
|
41833
|
+
1. **\`lotics docs data_model\`** \u2014 tables, fields and records as a query meets them. A query is
|
|
41834
|
+
written against the workspace's real schema, so read the schema before the query.
|
|
41835
|
+
2. **\`lotics docs building_an_app\`** \u2014 the sequence: clarify the ask, model the data, declare
|
|
41836
|
+
the reads, declare the writes. Skip its screen sections; everything before them applies.
|
|
41837
|
+
3. **\`lotics docs cli_reference\`** \u2014 every command's real behaviour, flags and refusals.
|
|
41838
|
+
4. **\`lotics run query_tables '{}'\`** and \`lotics run query_records '{"table_id":"tbl_\u2026"}'\` \u2014
|
|
41839
|
+
the workspace itself. **Verify real VALUES, never just that a field exists.**
|
|
41840
|
+
|
|
41841
|
+
## The rules that do not change
|
|
41842
|
+
|
|
41843
|
+
- **A query NAMES the columns it returns.** Declare a \`project\` over the fields the caller
|
|
41844
|
+
reads. Without one the app is promising whatever the table happens to hold, so the first
|
|
41845
|
+
\`lotics app api publish\` is refused by alias and never publishes a shape that can move.
|
|
41846
|
+
- **A workflow is the only way this app writes.** Queries read. Every write is a body in
|
|
41847
|
+
\`src/workflows/<alias>.ts\` with declared \`inputs\`, so a caller's bad value is a \`field_errors\`
|
|
41848
|
+
reply rather than a half-written row.
|
|
41849
|
+
- **Declare what it changes.** \`package.json#lotics.writes\` names the fields the workflows
|
|
41850
|
+
write; \`lotics app check\` refuses a body that writes outside it.
|
|
41851
|
+
- **No concrete id in source.** \`rec_\`, \`fld_\` and \`opt_\` literals belong in \`.lotics/\`, which
|
|
41852
|
+
is generated. \`lotics app codegen\` writes the \`F\`/\`OPT\`/\`TBL\` maps to type against.
|
|
41853
|
+
- **An alias is a promise once the API is published.** Renaming one, or narrowing what it takes,
|
|
41854
|
+
breaks a caller nobody here can redeploy \u2014 which is why such a write is refused unless it
|
|
41855
|
+
carries \`--acknowledge-breaking-api\`.
|
|
41856
|
+
|
|
41857
|
+
## Before you hand it over
|
|
41858
|
+
|
|
41859
|
+
1. **\`lotics app check\`** \u2014 bindings, declarations, workflow bodies, id portability. It exits
|
|
41860
|
+
non-zero on anything a release would refuse.
|
|
41861
|
+
2. **Every query projects.** No alias returns a row shape nobody declared.
|
|
41862
|
+
3. **Every workflow declares its inputs**, and its body reads each one.
|
|
41863
|
+
4. **Call it the way the consumer will** \u2014 the query endpoint with a key, and again without one
|
|
41864
|
+
if the app is public. What you get back is what you are promising.
|
|
41865
|
+
5. **\`lotics app api publish\`**, then read the warnings: they name what the published surface
|
|
41866
|
+
exposes that you may not have meant to.
|
|
41867
|
+
`
|
|
41868
|
+
},
|
|
41869
|
+
{
|
|
41870
|
+
path: "README.md",
|
|
41871
|
+
content: `# ${args.app_name}
|
|
41872
|
+
|
|
41873
|
+
A Lotics app with **no screens**. Its declared queries and workflows are the API a caller
|
|
41874
|
+
outside Lotics reads and writes through \u2014 your own site, your own server, an agent.
|
|
41875
|
+
|
|
41876
|
+
## The loop
|
|
41877
|
+
|
|
41878
|
+
\`\`\`bash
|
|
41879
|
+
# 1. Declare a query in package.json#lotics.queries.<alias>, and NAME THE COLUMNS
|
|
41880
|
+
# it returns (a \`project\` node) \u2014 they are the shape you are promising.
|
|
41881
|
+
lotics app query set <alias>
|
|
41882
|
+
|
|
41883
|
+
# 2. Write a body in src/workflows/<alias>.ts, declare its inputs in
|
|
41884
|
+
# package.json#lotics.workflows.<alias>, then push it (the server verifies).
|
|
41885
|
+
lotics app workflow check <alias>
|
|
41886
|
+
lotics app workflow set <alias>
|
|
41887
|
+
|
|
41888
|
+
# 3. Everything a release would refuse, without shipping one.
|
|
41889
|
+
lotics app check
|
|
41890
|
+
|
|
41891
|
+
# 4. Let something outside reach it: share the app publicly (no key at all), or
|
|
41892
|
+
# give an API key access to this app \u2014 Settings \u2192 API keys \u2192 Only selected ones.
|
|
41893
|
+
|
|
41894
|
+
# 5. Snapshot what those aliases promise, as a numbered contract version.
|
|
41895
|
+
lotics app api publish
|
|
41896
|
+
lotics app api spec -o api.openapi.json # the OpenAPI 3.1 document of that snapshot
|
|
41897
|
+
lotics app api status # which version callers hold
|
|
41898
|
+
\`\`\`
|
|
41899
|
+
|
|
41900
|
+
## Calling it
|
|
41901
|
+
|
|
41902
|
+
\`\`\`bash
|
|
41903
|
+
curl -X POST https://api.lotics.ai/v1/apps/<app_id>/queries/<alias> \\
|
|
41904
|
+
-H "Content-Type: application/json" \\
|
|
41905
|
+
-H "Authorization: Bearer ltk_\u2026" \\
|
|
41906
|
+
-d '{"params": {}, "limit": 50}'
|
|
41907
|
+
|
|
41908
|
+
curl -X POST https://api.lotics.ai/v1/apps/<app_id>/workflows/<alias>/execute \\
|
|
41909
|
+
-H "Content-Type: application/json" \\
|
|
41910
|
+
-H "Authorization: Bearer ltk_\u2026" \\
|
|
41911
|
+
-d '{"inputs": {}}'
|
|
41912
|
+
\`\`\`
|
|
41913
|
+
|
|
41914
|
+
A query answers \`rows\` (one object per row, keyed by the column names the query declares),
|
|
41915
|
+
\`columns\`, and \u2014 when asked for \u2014 \`total\`, \`next_cursor\` and \`truncated\`. A workflow answers
|
|
41916
|
+
\`status\`, an optional \`message\`, \`data\`, \`files\`, and \`field_errors\` keyed by input name when it
|
|
41917
|
+
refused a value. On a **public** app, send no \`Authorization\` header at all.
|
|
41918
|
+
|
|
41919
|
+
## While you author
|
|
41920
|
+
|
|
41921
|
+
There is no \`npm run dev\` and no \`lotics app dev\` here \u2014 both serve a Vite bundle, and this app
|
|
41922
|
+
has none. Read the workspace with \`lotics run query_records '{"table_id":"tbl_\u2026"}'\`, and call the
|
|
41923
|
+
endpoints above once the app is shared or a key reaches it.
|
|
41924
|
+
|
|
41925
|
+
\`\`\`bash
|
|
41926
|
+
lotics app codegen # regenerate .lotics/* (types + field/option ids) from the manifest
|
|
41927
|
+
npm run typecheck # tsc --noEmit over the generated types
|
|
41928
|
+
\`\`\`
|
|
41929
|
+
|
|
41930
|
+
## References
|
|
41931
|
+
|
|
41932
|
+
\`AGENTS.md\` in this directory is the reading order and the checks before you hand this over.
|
|
41933
|
+
\`lotics docs\` prints the rest one page at a time \u2014 \`lotics docs building_an_app\` for the
|
|
41934
|
+
sequence, \`lotics docs data_model\` for the schema, \`lotics docs cli_reference\` for every
|
|
41935
|
+
command's real behaviour.
|
|
41936
|
+
`
|
|
41937
|
+
}
|
|
41938
|
+
];
|
|
41939
|
+
}
|
|
41770
41940
|
function escapeHtml(s) {
|
|
41771
41941
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
41772
41942
|
}
|
|
@@ -42281,10 +42451,10 @@ declare module "@lotics/app-sdk" {
|
|
|
42281
42451
|
const inputLines = [];
|
|
42282
42452
|
const resultLines = [];
|
|
42283
42453
|
for (const [alias, declaration] of entries2) {
|
|
42284
|
-
const valueType = declaration.inputs ? inputsToType(declaration.inputs, { nullableOptional: true, optionsByFieldKey }) : "Record<string, unknown>";
|
|
42454
|
+
const valueType = declaresSchema(declaration.inputs) ? inputsToType(declaration.inputs, { nullableOptional: true, optionsByFieldKey }) : "Record<string, unknown>";
|
|
42285
42455
|
const aliasKey = isValidIdentifier(alias) ? alias : JSON.stringify(alias);
|
|
42286
42456
|
inputLines.push(` ${aliasKey}: ${valueType};`);
|
|
42287
|
-
if (declaration.outputs) {
|
|
42457
|
+
if (declaresSchema(declaration.outputs)) {
|
|
42288
42458
|
resultLines.push(
|
|
42289
42459
|
` ${aliasKey}: ${objectFieldsToType(declaration.outputs, optionsByFieldKey)};`
|
|
42290
42460
|
);
|
|
@@ -42325,10 +42495,10 @@ declare module "@lotics/app-sdk" {
|
|
|
42325
42495
|
const inputLines = [];
|
|
42326
42496
|
const resultLines = [];
|
|
42327
42497
|
for (const [alias, declaration] of entries2) {
|
|
42328
|
-
const valueType = declaration.inputs ? inputsToType(declaration.inputs, { optionsByFieldKey }) : "Record<string, unknown>";
|
|
42498
|
+
const valueType = declaresSchema(declaration.inputs) ? inputsToType(declaration.inputs, { optionsByFieldKey }) : "Record<string, unknown>";
|
|
42329
42499
|
const aliasKey = isValidIdentifier(alias) ? alias : JSON.stringify(alias);
|
|
42330
42500
|
inputLines.push(` ${aliasKey}: ${valueType};`);
|
|
42331
|
-
if (declaration.outputs) {
|
|
42501
|
+
if (declaresSchema(declaration.outputs)) {
|
|
42332
42502
|
resultLines.push(
|
|
42333
42503
|
` ${aliasKey}: ${objectFieldsToType(declaration.outputs, optionsByFieldKey)};`
|
|
42334
42504
|
);
|
|
@@ -46112,6 +46282,12 @@ async function postModel(client, model, options) {
|
|
|
46112
46282
|
}
|
|
46113
46283
|
}
|
|
46114
46284
|
|
|
46285
|
+
// ../shared/src/app_reach.ts
|
|
46286
|
+
function reachedOnlyThroughApi(app) {
|
|
46287
|
+
if (app.current_version_id != null) return false;
|
|
46288
|
+
return Object.keys(app.queries ?? {}).length > 0 || Object.keys(app.workflows ?? {}).length > 0 || Object.keys(app.agents ?? {}).length > 0;
|
|
46289
|
+
}
|
|
46290
|
+
|
|
46115
46291
|
// src/dev/server.ts
|
|
46116
46292
|
import http from "node:http";
|
|
46117
46293
|
import net from "node:net";
|
|
@@ -54287,8 +54463,8 @@ async function fetchWorkflowGlobals(client, projectDir, app_id, alias, declarati
|
|
|
54287
54463
|
}
|
|
54288
54464
|
}
|
|
54289
54465
|
async function fetchWorkflowDts(client, app_id, alias, declaration) {
|
|
54290
|
-
const
|
|
54291
|
-
if (
|
|
54466
|
+
const statesSchema = declaration !== void 0 && (declaration.inputs !== void 0 || declaration.outputs !== void 0);
|
|
54467
|
+
if (statesSchema) return client.getAppWorkflowDts(app_id, alias, declaration);
|
|
54292
54468
|
return client.getAppWorkflowDts(app_id, alias);
|
|
54293
54469
|
}
|
|
54294
54470
|
function runTar(args, cwd) {
|
|
@@ -54525,10 +54701,11 @@ function ensureAppTsconfig(projectDir) {
|
|
|
54525
54701
|
changes.push(`added ${toAdd.join(", ")} to "exclude"`);
|
|
54526
54702
|
}
|
|
54527
54703
|
const existingExtends = parsed.extends;
|
|
54528
|
-
|
|
54704
|
+
const linked = fs11.existsSync(path12.join(projectDir, LINK_TSCONFIG_PATH));
|
|
54705
|
+
if (linked && existingExtends === void 0) {
|
|
54529
54706
|
parsed.extends = `./${LINK_TSCONFIG_PATH}`;
|
|
54530
54707
|
changes.push(`added "extends": "./${LINK_TSCONFIG_PATH}" (so tsc resolves the same @lotics/ui as Vite)`);
|
|
54531
|
-
} else if (existingExtends !== `./${LINK_TSCONFIG_PATH}`) {
|
|
54708
|
+
} else if (linked && existingExtends !== `./${LINK_TSCONFIG_PATH}`) {
|
|
54532
54709
|
console.error(
|
|
54533
54710
|
`\u26A0 tsconfig.json already extends ${JSON.stringify(existingExtends)}, so the dev-link config was not added \u2014 \`tsc\` will keep resolving @lotics/ui from node_modules even under LOTICS_UI_SRC. Add "./${LINK_TSCONFIG_PATH}" to "extends" (it accepts an array) to fix it.`
|
|
54534
54711
|
);
|
|
@@ -54582,9 +54759,16 @@ function readKitPeerNames(projectDir) {
|
|
|
54582
54759
|
return peers && typeof peers === "object" ? Object.keys(peers) : [];
|
|
54583
54760
|
}
|
|
54584
54761
|
function writeAppDtsKitLinks(projectDir) {
|
|
54762
|
+
if (!declaresKit(projectDir)) return;
|
|
54585
54763
|
writeDevLinkTsconfig(projectDir);
|
|
54586
54764
|
writeKitTypeAugmentation(projectDir);
|
|
54587
54765
|
}
|
|
54766
|
+
function declaresKit(projectDir) {
|
|
54767
|
+
const manifest = path12.join(projectDir, "package.json");
|
|
54768
|
+
if (!fs11.existsSync(manifest)) return false;
|
|
54769
|
+
const pkg = JSON.parse(fs11.readFileSync(manifest, "utf-8"));
|
|
54770
|
+
return "@lotics/ui" in { ...pkg.dependencies, ...pkg.devDependencies };
|
|
54771
|
+
}
|
|
54588
54772
|
function writeAppDts(projectDir, manifest, optionsByFieldKey) {
|
|
54589
54773
|
const dotLotics = path12.join(projectDir, ".lotics");
|
|
54590
54774
|
fs11.mkdirSync(dotLotics, { recursive: true });
|
|
@@ -54667,8 +54851,9 @@ function warnAppFieldsUnwritten(projectDir, err) {
|
|
|
54667
54851
|
function warnAboutDevLink(projectDir, command) {
|
|
54668
54852
|
const uiSrc = process.env.LOTICS_UI_SRC;
|
|
54669
54853
|
if (!uiSrc) return;
|
|
54670
|
-
const viteConfigPath =
|
|
54671
|
-
|
|
54854
|
+
const viteConfigPath = viteConfigIn(projectDir);
|
|
54855
|
+
if (viteConfigPath === null) return;
|
|
54856
|
+
const config2 = fs11.readFileSync(viteConfigPath, "utf-8");
|
|
54672
54857
|
const linked = config2.includes("loticsResolve") || config2.includes("LOTICS_UI_SRC");
|
|
54673
54858
|
if (!linked) {
|
|
54674
54859
|
console.error(
|
|
@@ -54823,14 +55008,20 @@ function inlinesKitUnderVitest(viteConfig) {
|
|
|
54823
55008
|
}
|
|
54824
55009
|
});
|
|
54825
55010
|
}
|
|
55011
|
+
function viteConfigIn(projectDir) {
|
|
55012
|
+
const name = VITE_CONFIG_NAMES.find(
|
|
55013
|
+
(candidate) => fs11.existsSync(path12.join(projectDir, candidate))
|
|
55014
|
+
);
|
|
55015
|
+
return name === void 0 ? null : path12.join(projectDir, name);
|
|
55016
|
+
}
|
|
54826
55017
|
function ensureAppVitestSetup(projectDir) {
|
|
55018
|
+
const viteConfigPath = viteConfigIn(projectDir);
|
|
55019
|
+
if (viteConfigPath === null) return;
|
|
54827
55020
|
const setupPath = path12.join(projectDir, VITEST_SETUP_FILENAME);
|
|
54828
55021
|
if (!fs11.existsSync(setupPath)) {
|
|
54829
55022
|
fs11.writeFileSync(setupPath, VITEST_SETUP_CONTENT);
|
|
54830
55023
|
console.error(`Wrote ${VITEST_SETUP_FILENAME} (wired by vite.config.ts; empty until you fill it).`);
|
|
54831
55024
|
}
|
|
54832
|
-
const viteConfigPath = path12.join(projectDir, "vite.config.ts");
|
|
54833
|
-
if (!fs11.existsSync(viteConfigPath)) return;
|
|
54834
55025
|
const viteConfig = fs11.readFileSync(viteConfigPath, "utf-8");
|
|
54835
55026
|
if (!(/setupFiles\s*:/.test(viteConfig) && viteConfig.includes(VITEST_SETUP_FILENAME))) {
|
|
54836
55027
|
console.error(
|
|
@@ -55005,7 +55196,19 @@ async function materializeScaffold(targetPath, files) {
|
|
|
55005
55196
|
await runNpmInstall(targetPath);
|
|
55006
55197
|
writeAppDtsKitLinks(targetPath);
|
|
55007
55198
|
}
|
|
55199
|
+
async function starterDependencyRanges() {
|
|
55200
|
+
const [ui, sdk] = await Promise.all([
|
|
55201
|
+
requireLatestNpmVersion("@lotics/ui"),
|
|
55202
|
+
requireLatestNpmVersion("@lotics/app-sdk")
|
|
55203
|
+
]);
|
|
55204
|
+
return { ui, sdk };
|
|
55205
|
+
}
|
|
55008
55206
|
async function appCreate(client, args) {
|
|
55207
|
+
if (args.api === true && args.from !== void 0) {
|
|
55208
|
+
throw new Error(
|
|
55209
|
+
`--api and --from are different apps: a plan describes screens, and an --api app has none. Drop one.`
|
|
55210
|
+
);
|
|
55211
|
+
}
|
|
55009
55212
|
const targetPath = path12.resolve(args.targetPath ?? appDirName(args.name));
|
|
55010
55213
|
if (fs11.existsSync(targetPath)) {
|
|
55011
55214
|
const entries2 = fs11.readdirSync(targetPath);
|
|
@@ -55016,10 +55219,7 @@ async function appCreate(client, args) {
|
|
|
55016
55219
|
const plan = args.from === void 0 ? null : await resolvePlanForCreate(client, args.from);
|
|
55017
55220
|
if (plan !== null) await reportPlanCoverage(client, plan);
|
|
55018
55221
|
fs11.mkdirSync(targetPath, { recursive: true });
|
|
55019
|
-
const
|
|
55020
|
-
requireLatestNpmVersion("@lotics/ui"),
|
|
55021
|
-
requireLatestNpmVersion("@lotics/app-sdk")
|
|
55022
|
-
]);
|
|
55222
|
+
const ranges = args.api === true ? null : await starterDependencyRanges();
|
|
55023
55223
|
const app = await client.createApp({
|
|
55024
55224
|
name: args.name,
|
|
55025
55225
|
...plan?.app.description == null ? {} : { description: plan.app.description },
|
|
@@ -55027,12 +55227,16 @@ async function appCreate(client, args) {
|
|
|
55027
55227
|
...plan?.app.theme?.color == null ? {} : { theme: { color: plan.app.theme.color } }
|
|
55028
55228
|
});
|
|
55029
55229
|
console.error(`Created app: ${app.name} (${app.id})`);
|
|
55030
|
-
const starter =
|
|
55230
|
+
const starter = ranges === null ? buildApiStarterTemplate({
|
|
55231
|
+
app_name: args.name,
|
|
55232
|
+
app_id: app.id,
|
|
55233
|
+
workspace_id: app.workspace_id
|
|
55234
|
+
}) : buildStarterTemplate({
|
|
55031
55235
|
app_name: args.name,
|
|
55032
55236
|
app_id: app.id,
|
|
55033
55237
|
workspace_id: app.workspace_id,
|
|
55034
|
-
ui_version: `^${
|
|
55035
|
-
sdk_version: `^${
|
|
55238
|
+
ui_version: `^${ranges.ui}`,
|
|
55239
|
+
sdk_version: `^${ranges.sdk}`
|
|
55036
55240
|
});
|
|
55037
55241
|
const files = plan === null ? starter : withPlanFiles(starter, plan);
|
|
55038
55242
|
await materializeScaffold(targetPath, files);
|
|
@@ -55045,23 +55249,34 @@ async function appCreate(client, args) {
|
|
|
55045
55249
|
);
|
|
55046
55250
|
}
|
|
55047
55251
|
}
|
|
55048
|
-
|
|
55049
|
-
|
|
55050
|
-
|
|
55051
|
-
|
|
55052
|
-
|
|
55252
|
+
if (ranges !== null) {
|
|
55253
|
+
console.error("Building initial version...");
|
|
55254
|
+
await appDeploy(client, {
|
|
55255
|
+
projectDir: targetPath,
|
|
55256
|
+
message: "Initial version"
|
|
55257
|
+
});
|
|
55258
|
+
}
|
|
55053
55259
|
console.error(`
|
|
55054
55260
|
Ready. Next steps:`);
|
|
55055
55261
|
console.error(` cd ${path12.relative(process.cwd(), targetPath) || "."}`);
|
|
55056
55262
|
console.error(` lotics docs # the contracts, at the versions installed here`);
|
|
55057
|
-
if (
|
|
55263
|
+
if (ranges === null) {
|
|
55264
|
+
console.error(` # declare a query in package.json#lotics.queries \u2014 name the columns it returns \u2014 then:`);
|
|
55265
|
+
console.error(` lotics app query set <alias> # publish that read, no deploy`);
|
|
55266
|
+
console.error(` # write src/workflows/<alias>.ts for each write, then:`);
|
|
55267
|
+
console.error(` lotics app workflow set <alias> # push the body (the server verifies it)`);
|
|
55268
|
+
console.error(` lotics app check # every pre-flight a release would run`);
|
|
55269
|
+
console.error(` # share the app publicly, or give an API key access to it, then:`);
|
|
55270
|
+
console.error(` lotics app api publish # snapshot what those aliases promise`);
|
|
55271
|
+
} else if (plan === null) {
|
|
55058
55272
|
console.error(` # edit src/App.tsx, then:`);
|
|
55059
55273
|
console.error(` lotics app check # every pre-flight a deploy runs`);
|
|
55274
|
+
console.error(` lotics app deploy`);
|
|
55060
55275
|
} else {
|
|
55061
55276
|
console.error(` # edit src/screens/*.tsx \u2014 one per plan screen \u2014 then:`);
|
|
55062
55277
|
console.error(` lotics app check --screens # every pre-flight a deploy runs, and the probes over each screen`);
|
|
55278
|
+
console.error(` lotics app deploy`);
|
|
55063
55279
|
}
|
|
55064
|
-
console.error(` lotics app deploy`);
|
|
55065
55280
|
}
|
|
55066
55281
|
async function resolvePlanForCreate(client, from) {
|
|
55067
55282
|
const hash2 = from.lastIndexOf("#");
|
|
@@ -55549,7 +55764,7 @@ async function appPull(client, args) {
|
|
|
55549
55764
|
const app = await client.getApp(args.app_id);
|
|
55550
55765
|
if (!app.current_version_id && args.version === void 0) {
|
|
55551
55766
|
throw new Error(
|
|
55552
|
-
`App ${app.id} has no published version yet. Deploy from another machine first, or use 'lotics app create' to scaffold a new app.`
|
|
55767
|
+
reachedOnlyThroughApi(app) ? `App ${app.id} has no bundle \u2014 it is reached through its API. Check out its project from version control; 'lotics app codegen' rebuilds .lotics/ there.` : `App ${app.id} has no published version yet. Deploy from another machine first, or use 'lotics app create' to scaffold a new app.`
|
|
55553
55768
|
);
|
|
55554
55769
|
}
|
|
55555
55770
|
const versionId = args.version ?? app.current_version_id;
|
|
@@ -55677,6 +55892,12 @@ function reportPortabilityIds(findings) {
|
|
|
55677
55892
|
async function appDeploy(client, args) {
|
|
55678
55893
|
const projectDir = path12.resolve(args.projectDir ?? process.cwd());
|
|
55679
55894
|
const meta3 = readAppMeta(projectDir);
|
|
55895
|
+
const scripts = JSON.parse(fs11.readFileSync(path12.join(projectDir, "package.json"), "utf-8")).scripts;
|
|
55896
|
+
if (!scripts?.build) {
|
|
55897
|
+
fail(
|
|
55898
|
+
`package.json declares no \`build\` script, so there is no bundle to deploy \u2014 an app reached through its API goes live through \`lotics app query set\` and \`app workflow set\`, and \`lotics app api publish\` promises them.`
|
|
55899
|
+
);
|
|
55900
|
+
}
|
|
55680
55901
|
warnAboutDevLink(projectDir, "deploy");
|
|
55681
55902
|
const liveApp = await client.getApp(meta3.app_id);
|
|
55682
55903
|
assertProjectIsCurrent(meta3, liveApp);
|
|
@@ -56040,6 +56261,11 @@ async function readLiveQueries(client, appId) {
|
|
|
56040
56261
|
async function appCheck(client, args = {}) {
|
|
56041
56262
|
const projectDir = path12.resolve(args.projectDir ?? process.cwd());
|
|
56042
56263
|
const meta3 = readAppMeta(projectDir);
|
|
56264
|
+
if (args.screens === true && viteConfigIn(projectDir) === null) {
|
|
56265
|
+
fail(
|
|
56266
|
+
`--screens renders this app's screens, and ${path12.basename(projectDir)} has no vite.config \u2014 it is reached through its API, not through a bundle. Run \`lotics app check\` on its own.`
|
|
56267
|
+
);
|
|
56268
|
+
}
|
|
56043
56269
|
const app = await client.getApp(meta3.app_id);
|
|
56044
56270
|
assertProjectIsCurrent(meta3, app);
|
|
56045
56271
|
writeAppDts(projectDir, { workflows: meta3.workflows, queries: meta3.queries, agents: meta3.agents });
|
|
@@ -56214,7 +56440,7 @@ Pushing ${plan.length} binding${plan.length === 1 ? "" : "s"} ahead of the app:
|
|
|
56214
56440
|
await appWorkflowSet(client, { alias, projectDir, acknowledgeBreakingApi });
|
|
56215
56441
|
}
|
|
56216
56442
|
for (const alias of pending.agents) {
|
|
56217
|
-
await appAgentSet(client, { alias, projectDir });
|
|
56443
|
+
await appAgentSet(client, { alias, projectDir, acknowledgeBreakingApi });
|
|
56218
56444
|
}
|
|
56219
56445
|
}
|
|
56220
56446
|
function pendingBindings(args) {
|
|
@@ -56270,7 +56496,10 @@ function divergedAgentAliases(manifestAgents, liveAgents) {
|
|
|
56270
56496
|
for (const [alias, declared] of Object.entries(manifestAgents ?? {})) {
|
|
56271
56497
|
const live = (liveAgents ?? {})[alias];
|
|
56272
56498
|
if (!live) continue;
|
|
56273
|
-
|
|
56499
|
+
const half = (value) => declaresSchema(value) ? value : void 0;
|
|
56500
|
+
if (AGENT_TYPED_KEYS.some((key) => !sameSchema(half(declared[key]), half(live[key])))) {
|
|
56501
|
+
out.push(alias);
|
|
56502
|
+
}
|
|
56274
56503
|
}
|
|
56275
56504
|
return out;
|
|
56276
56505
|
}
|
|
@@ -56440,8 +56669,7 @@ async function pruneOrphanedBindings(client, projectDir, app, orphans, undeclare
|
|
|
56440
56669
|
// guard: a run recorded against an alias means something outside the
|
|
56441
56670
|
// bundle reaches it.
|
|
56442
56671
|
...overrideInvocationGuard ? { even_if_invoked: true } : {},
|
|
56443
|
-
|
|
56444
|
-
...acknowledgeBreakingApi && target.kind !== "agents" ? { acknowledge_breaking_api_change: true } : {}
|
|
56672
|
+
...acknowledgeBreakingApi ? { acknowledge_breaking_api_change: true } : {}
|
|
56445
56673
|
});
|
|
56446
56674
|
if (res.error) {
|
|
56447
56675
|
console.error(` \u2717 could not unbind ${target.label} \u2014 ${res.error}`);
|
|
@@ -56868,6 +57096,11 @@ function resolveDeclaredVitePort(projectDir, flagPort) {
|
|
|
56868
57096
|
async function appDev(client, args) {
|
|
56869
57097
|
const projectDir = path12.resolve(args.projectDir ?? process.cwd());
|
|
56870
57098
|
const meta3 = readAppMeta(projectDir);
|
|
57099
|
+
if (viteConfigIn(projectDir) === null) {
|
|
57100
|
+
fail(
|
|
57101
|
+
`${path12.basename(projectDir)} has no vite.config, so there is no bundle to serve \u2014 it is reached through its API. Call its queries and workflows directly, or \`lotics app check\`.`
|
|
57102
|
+
);
|
|
57103
|
+
}
|
|
56871
57104
|
warnAboutDevLink(projectDir, "dev");
|
|
56872
57105
|
const vitePort = resolveDeclaredVitePort(projectDir, args.vitePort);
|
|
56873
57106
|
writeAppDts(projectDir, { workflows: meta3.workflows, queries: meta3.queries, agents: meta3.agents });
|
|
@@ -57015,16 +57248,29 @@ async function appAgentSet(client, args) {
|
|
|
57015
57248
|
process.exit(1);
|
|
57016
57249
|
}
|
|
57017
57250
|
const proseBaseline = readSynced(projectDir).agents[args.alias]?.live;
|
|
57018
|
-
const res = await client.setAppAgent(
|
|
57019
|
-
|
|
57020
|
-
|
|
57021
|
-
|
|
57022
|
-
|
|
57023
|
-
|
|
57024
|
-
|
|
57025
|
-
|
|
57251
|
+
const res = await client.setAppAgent(
|
|
57252
|
+
meta3.app_id,
|
|
57253
|
+
args.alias,
|
|
57254
|
+
{
|
|
57255
|
+
// Prose and its precondition ride together. Gating a declaration-only push on
|
|
57256
|
+
// the prose baseline would refuse it because someone edited a PROMPT — a
|
|
57257
|
+
// precondition must guard what this call actually writes.
|
|
57258
|
+
...instructions !== void 0 ? { instructions, ...proseBaseline ? { expected_instructions_sha: proseBaseline } : {} } : {},
|
|
57259
|
+
...declared?.inputs !== void 0 ? { inputs: declared.inputs } : {},
|
|
57260
|
+
...declared?.outputs !== void 0 ? { outputs: declared.outputs } : {}
|
|
57261
|
+
},
|
|
57262
|
+
// The author's answer to a published contract this declaration would break.
|
|
57263
|
+
// Absent, the server refuses such a write and names each change.
|
|
57264
|
+
args.acknowledgeBreakingApi
|
|
57265
|
+
);
|
|
57026
57266
|
if (res.error) {
|
|
57027
|
-
console.error(
|
|
57267
|
+
console.error(
|
|
57268
|
+
`Failed to set agent "${args.alias}": ` + refusalAtThisSurface(
|
|
57269
|
+
res,
|
|
57270
|
+
`lotics app agent set ${args.alias}`,
|
|
57271
|
+
args.acknowledgeBreakingApi
|
|
57272
|
+
)
|
|
57273
|
+
);
|
|
57028
57274
|
process.exit(1);
|
|
57029
57275
|
}
|
|
57030
57276
|
if (instructions !== void 0) {
|
|
@@ -57608,6 +57854,7 @@ function parseArgs(argv) {
|
|
|
57608
57854
|
prune: false,
|
|
57609
57855
|
pruneInvoked: [],
|
|
57610
57856
|
acknowledgeBreakingApi: false,
|
|
57857
|
+
api: false,
|
|
57611
57858
|
screens: false,
|
|
57612
57859
|
screen: [],
|
|
57613
57860
|
width: [],
|
|
@@ -57788,6 +58035,9 @@ function parseArgs(argv) {
|
|
|
57788
58035
|
case "--acknowledge-breaking-api":
|
|
57789
58036
|
flags.acknowledgeBreakingApi = true;
|
|
57790
58037
|
break;
|
|
58038
|
+
case "--api":
|
|
58039
|
+
flags.api = true;
|
|
58040
|
+
break;
|
|
57791
58041
|
case "--screens":
|
|
57792
58042
|
flags.screens = true;
|
|
57793
58043
|
break;
|
|
@@ -59652,6 +59902,8 @@ async function main() {
|
|
|
59652
59902
|
if (command === "app" && !subcommand) {
|
|
59653
59903
|
console.error("Usage:");
|
|
59654
59904
|
console.error(" lotics app create <name> [path] Scaffold a new app locally");
|
|
59905
|
+
console.error(" lotics app create <name> --api ...with no screens: its queries and workflows");
|
|
59906
|
+
console.error(" are the API something outside Lotics calls");
|
|
59655
59907
|
console.error(" lotics app pull <app_id> [path] Pull an existing app for local editing");
|
|
59656
59908
|
console.error(" lotics app deploy -m <message> Build + upload the current directory (-m required)");
|
|
59657
59909
|
console.error(" lotics app versions [app_id] Show deploy history (version, when, who, message)");
|
|
@@ -59956,7 +60208,7 @@ Available workspaces:`);
|
|
|
59956
60208
|
process.exit(1);
|
|
59957
60209
|
}
|
|
59958
60210
|
const targetPath = restArgs[0];
|
|
59959
|
-
await appCreate(client, { name, targetPath, from: flags.from });
|
|
60211
|
+
await appCreate(client, { name, targetPath, from: flags.from, api: flags.api });
|
|
59960
60212
|
return;
|
|
59961
60213
|
}
|
|
59962
60214
|
if (subcommand === "pull") {
|
|
@@ -60119,7 +60371,7 @@ Available workspaces:`);
|
|
|
60119
60371
|
console.error(" Edits src/agents/<alias>.md (written by 'lotics app pull').");
|
|
60120
60372
|
process.exit(1);
|
|
60121
60373
|
}
|
|
60122
|
-
await appAgentSet(client, { alias });
|
|
60374
|
+
await appAgentSet(client, { alias, acknowledgeBreakingApi: flags.acknowledgeBreakingApi });
|
|
60123
60375
|
return;
|
|
60124
60376
|
}
|
|
60125
60377
|
agentUsage();
|