@lotics/cli 0.92.0 → 0.94.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.
- package/README.md +8 -0
- package/dist/src/cli.js +475 -62
- package/dist/src/client.d.ts +54 -1
- package/dist/src/client.js +34 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -217,6 +217,14 @@ lotics app workflow set issueInvoice # push the edited src/workflows/issu
|
|
|
217
217
|
# authoritative, so the next `app deploy` re-syncs it — keep the manifest current.
|
|
218
218
|
lotics app query set openInvoices # push package.json#lotics.queries.openInvoices
|
|
219
219
|
|
|
220
|
+
# Run a bound app agent end-to-end (no deployed UI needed — app row + declaration
|
|
221
|
+
# + member auth). Streams progress to stderr; reports the SETTLED run (structured
|
|
222
|
+
# output / final text) to stdout; exits 0 only when the run completed.
|
|
223
|
+
lotics app agent run app_abc recognize '{"image_file_id":"fil_..."}'
|
|
224
|
+
cat input.json | lotics app agent run app_abc recognize # inputs via stdin/@file
|
|
225
|
+
lotics app agent run app_abc recognize --json # full run summary to stdout
|
|
226
|
+
lotics app agent run app_abc recognize --session cli-123 '{}' # continue an existing thread
|
|
227
|
+
|
|
220
228
|
# Dev-link @lotics/ui to packages/ui/src for live HMR (Vite alias; deploy bundles it)
|
|
221
229
|
lotics ui link card # monorepo: packages/ui/src found automatically
|
|
222
230
|
lotics ui link card --ui-src /abs/monorepo/packages/ui/src # external app (e.g. ~/lotics_apps)
|
package/dist/src/cli.js
CHANGED
|
@@ -29586,6 +29586,7 @@ var require_lib3 = __commonJS({
|
|
|
29586
29586
|
import dns from "node:dns";
|
|
29587
29587
|
import net2 from "node:net";
|
|
29588
29588
|
import fs9 from "node:fs";
|
|
29589
|
+
import os2 from "node:os";
|
|
29589
29590
|
import path7 from "node:path";
|
|
29590
29591
|
import readline from "node:readline";
|
|
29591
29592
|
|
|
@@ -30198,11 +30199,18 @@ var LoticsClient = class {
|
|
|
30198
30199
|
* `async function __workflow(): …` wrapper the server compiles inside, so the
|
|
30199
30200
|
* local typecheck mirrors the set-time verdict. Mirrors
|
|
30200
30201
|
* POST /v1/apps/{app_id}/workflows/{alias}/dts.
|
|
30202
|
+
*
|
|
30203
|
+
* `declaration` (the manifest's `{ inputs?, outputs? }`) is posted as the body
|
|
30204
|
+
* `{ declaration }` ONLY when the alias isn't `set` on the server yet — the
|
|
30205
|
+
* server then synthesizes the dts from the declared schemas instead of 400ing
|
|
30206
|
+
* "no workflow alias". A registered alias needs no declaration (the server's
|
|
30207
|
+
* own bound contract wins), so the field is omitted in that case.
|
|
30201
30208
|
*/
|
|
30202
|
-
async getAppWorkflowDts(app_id, alias) {
|
|
30209
|
+
async getAppWorkflowDts(app_id, alias, declaration) {
|
|
30203
30210
|
return this.request(
|
|
30204
30211
|
"POST",
|
|
30205
|
-
`/v1/apps/${encodeURIComponent(app_id)}/workflows/${encodeURIComponent(alias)}/dts
|
|
30212
|
+
`/v1/apps/${encodeURIComponent(app_id)}/workflows/${encodeURIComponent(alias)}/dts`,
|
|
30213
|
+
declaration ? { declaration } : void 0
|
|
30206
30214
|
);
|
|
30207
30215
|
}
|
|
30208
30216
|
/**
|
|
@@ -30224,6 +30232,37 @@ var LoticsClient = class {
|
|
|
30224
30232
|
if (!res.ok) await this.throwResponseError(res);
|
|
30225
30233
|
return res;
|
|
30226
30234
|
}
|
|
30235
|
+
/**
|
|
30236
|
+
* Continue a PARKED (`awaiting_input`) agent run with the user's answer to its
|
|
30237
|
+
* pending `ask_user_choice` — returns the RAW streamed continuation `Response`,
|
|
30238
|
+
* exactly like `appAgentRunStream`. Mirrors
|
|
30239
|
+
* POST /v1/apps/{app_id}/agent-runs/{run_id}/continue.
|
|
30240
|
+
*/
|
|
30241
|
+
async appAgentRunContinueStream(app_id, run_id, body, signal) {
|
|
30242
|
+
const res = await fetch(
|
|
30243
|
+
`${this.baseUrl}/v1/apps/${encodeURIComponent(app_id)}/agent-runs/${encodeURIComponent(run_id)}/continue`,
|
|
30244
|
+
{
|
|
30245
|
+
method: "POST",
|
|
30246
|
+
headers: { ...this.buildHeaders(), "Content-Type": "application/json" },
|
|
30247
|
+
body: JSON.stringify(body),
|
|
30248
|
+
signal
|
|
30249
|
+
}
|
|
30250
|
+
);
|
|
30251
|
+
if (!res.ok) await this.throwResponseError(res);
|
|
30252
|
+
return res;
|
|
30253
|
+
}
|
|
30254
|
+
/**
|
|
30255
|
+
* A session's app-agent run history, oldest-first (the run just started is the
|
|
30256
|
+
* last, and its exact id is on the stream response's `x-app-agent-run-id`
|
|
30257
|
+
* header). Transcript excluded; structured `output`/`input` included. Mirrors
|
|
30258
|
+
* GET /v1/apps/{app_id}/agent-runs.
|
|
30259
|
+
*/
|
|
30260
|
+
async listAgentRuns(app_id, session_id) {
|
|
30261
|
+
return this.request(
|
|
30262
|
+
"GET",
|
|
30263
|
+
`/v1/apps/${encodeURIComponent(app_id)}/agent-runs?session_id=${encodeURIComponent(session_id)}`
|
|
30264
|
+
);
|
|
30265
|
+
}
|
|
30227
30266
|
/**
|
|
30228
30267
|
* Mint a presigned URL for uploading a file into an app. Mirrors
|
|
30229
30268
|
* POST /v1/apps/{app_id}/files/upload-url.
|
|
@@ -30555,6 +30594,16 @@ function upsertProfile(orgId, fields) {
|
|
|
30555
30594
|
latest_version: config2.latest_version
|
|
30556
30595
|
});
|
|
30557
30596
|
}
|
|
30597
|
+
function clearProfileWorkspace(orgId) {
|
|
30598
|
+
const config2 = loadGlobalConfig();
|
|
30599
|
+
const existing = config2?.profiles?.[orgId];
|
|
30600
|
+
if (!existing) return;
|
|
30601
|
+
const { workspace_id: _dropped, ...rest } = existing;
|
|
30602
|
+
saveGlobalConfig({
|
|
30603
|
+
...config2,
|
|
30604
|
+
profiles: { ...config2.profiles, [orgId]: { ...rest } }
|
|
30605
|
+
});
|
|
30606
|
+
}
|
|
30558
30607
|
function removeProfile(orgId) {
|
|
30559
30608
|
const config2 = loadGlobalConfig();
|
|
30560
30609
|
if (!config2?.profiles?.[orgId]) return;
|
|
@@ -30653,6 +30702,7 @@ var VERSION = pkg.version;
|
|
|
30653
30702
|
import fs4 from "node:fs";
|
|
30654
30703
|
import path5 from "node:path";
|
|
30655
30704
|
import { spawn as spawn2 } from "node:child_process";
|
|
30705
|
+
import { randomUUID } from "node:crypto";
|
|
30656
30706
|
import { tmpdir } from "node:os";
|
|
30657
30707
|
|
|
30658
30708
|
// src/starter_template.ts
|
|
@@ -31778,14 +31828,14 @@ function buildWrapperPage(args) {
|
|
|
31778
31828
|
// protocol expects (stream-chunk* -> stream-end). Mirrors the production
|
|
31779
31829
|
// host in frontend/features/app_ui/app_iframe_host.tsx.
|
|
31780
31830
|
const streamingRuns = {};
|
|
31781
|
-
async function handleAgentRun(id, payload) {
|
|
31831
|
+
async function handleAgentRun(id, op, payload) {
|
|
31782
31832
|
const controller = new AbortController();
|
|
31783
31833
|
streamingRuns[id] = controller;
|
|
31784
31834
|
try {
|
|
31785
31835
|
const res = await fetch("/_agent_run", {
|
|
31786
31836
|
method: "POST",
|
|
31787
31837
|
headers: { "content-type": "application/json" },
|
|
31788
|
-
body: JSON.stringify({ app_id: APP_ID, op:
|
|
31838
|
+
body: JSON.stringify({ app_id: APP_ID, op: op, payload: payload }),
|
|
31789
31839
|
signal: controller.signal,
|
|
31790
31840
|
});
|
|
31791
31841
|
if (!res.ok || !res.body) {
|
|
@@ -31793,6 +31843,9 @@ function buildWrapperPage(args) {
|
|
|
31793
31843
|
try { detail = JSON.parse(detail).message || detail; } catch (_) {}
|
|
31794
31844
|
throw new Error(detail || ("HTTP " + res.status));
|
|
31795
31845
|
}
|
|
31846
|
+
// Forward the run id (poll-recovery / cancel / continue key on it).
|
|
31847
|
+
const runId = res.headers.get("x-app-agent-run-id");
|
|
31848
|
+
if (runId) iframe.contentWindow.postMessage({ id: id, type: "run-id", runId: runId }, VITE_ORIGIN);
|
|
31796
31849
|
const reader = res.body.getReader();
|
|
31797
31850
|
const decoder = new TextDecoder();
|
|
31798
31851
|
for (;;) {
|
|
@@ -31826,7 +31879,7 @@ function buildWrapperPage(args) {
|
|
|
31826
31879
|
}
|
|
31827
31880
|
if (typeof msg.op !== "string") return;
|
|
31828
31881
|
// Streaming agent run \u2014 many messages back, not a single result.
|
|
31829
|
-
if (msg.op === "agentRun") { handleAgentRun(msg.id, msg.payload); return; }
|
|
31882
|
+
if (msg.op === "agentRun" || msg.op === "agentRunContinue") { handleAgentRun(msg.id, msg.op, msg.payload); return; }
|
|
31830
31883
|
const startedAt = performance.now();
|
|
31831
31884
|
try {
|
|
31832
31885
|
const data = msg.op === "upload"
|
|
@@ -32065,18 +32118,37 @@ async function startDevServer(args) {
|
|
|
32065
32118
|
});
|
|
32066
32119
|
try {
|
|
32067
32120
|
const body = await readJson(req);
|
|
32068
|
-
|
|
32069
|
-
if (
|
|
32070
|
-
|
|
32071
|
-
|
|
32072
|
-
|
|
32073
|
-
|
|
32074
|
-
|
|
32075
|
-
|
|
32076
|
-
|
|
32077
|
-
|
|
32121
|
+
let upstream;
|
|
32122
|
+
if (body.op === "agentRunContinue") {
|
|
32123
|
+
const p = body.payload ?? {};
|
|
32124
|
+
if (typeof p.run_id !== "string" || typeof p.tool_call_id !== "string" || typeof p.output !== "object" || p.output === null) {
|
|
32125
|
+
throw new Error("agentRunContinue payload must include `run_id`, `tool_call_id`, and `output`");
|
|
32126
|
+
}
|
|
32127
|
+
upstream = await args.client.appAgentRunContinueStream(
|
|
32128
|
+
body.app_id,
|
|
32129
|
+
p.run_id,
|
|
32130
|
+
{ tool_call_id: p.tool_call_id, output: p.output },
|
|
32131
|
+
ac.signal
|
|
32132
|
+
);
|
|
32133
|
+
} else {
|
|
32134
|
+
const p = body.payload ?? {};
|
|
32135
|
+
if (typeof p.alias !== "string" || typeof p.session_id !== "string") {
|
|
32136
|
+
throw new Error("agentRun payload must include `alias` and `session_id`");
|
|
32137
|
+
}
|
|
32138
|
+
upstream = await args.client.appAgentRunStream(
|
|
32139
|
+
body.app_id,
|
|
32140
|
+
p.alias,
|
|
32141
|
+
{ session_id: p.session_id, input: p.input ?? {} },
|
|
32142
|
+
ac.signal
|
|
32143
|
+
);
|
|
32144
|
+
}
|
|
32078
32145
|
if (!upstream.body) throw new Error("agent run returned no stream body");
|
|
32079
|
-
|
|
32146
|
+
const runId = upstream.headers.get("x-app-agent-run-id");
|
|
32147
|
+
res.writeHead(200, {
|
|
32148
|
+
"Content-Type": "text/event-stream",
|
|
32149
|
+
"Cache-Control": "no-cache, no-transform",
|
|
32150
|
+
...runId ? { "x-app-agent-run-id": runId } : {}
|
|
32151
|
+
});
|
|
32080
32152
|
const reader = upstream.body.getReader();
|
|
32081
32153
|
for (; ; ) {
|
|
32082
32154
|
const { value, done } = await reader.read();
|
|
@@ -32932,6 +33004,12 @@ var LOTICS_INCLUDE_GLOB = ".lotics/**/*";
|
|
|
32932
33004
|
var STALE_LOTICS_INCLUDES = /* @__PURE__ */ new Set([".lotics", "./.lotics", ".lotics/"]);
|
|
32933
33005
|
var FALLBACK_ENVELOPE_PREFIX = "async function __workflow(): Promise<__WorkflowReturn | void> {\n";
|
|
32934
33006
|
var FALLBACK_ENVELOPE_SUFFIX = "\n}";
|
|
33007
|
+
function toWorkflowDtsDeclaration(d) {
|
|
33008
|
+
return { inputs: d.inputs, outputs: d.outputs };
|
|
33009
|
+
}
|
|
33010
|
+
function isUnknownWorkflowAliasError(err2) {
|
|
33011
|
+
return err2 instanceof Error && /^400:/.test(err2.message) && /has no workflow alias/i.test(err2.message);
|
|
33012
|
+
}
|
|
32935
33013
|
function workflowFileHeader(alias) {
|
|
32936
33014
|
const refPath = path5.join("..", "..", WORKFLOW_GLOBALS_DIR, `${alias}.globals.d.ts`).split(path5.sep).join("/");
|
|
32937
33015
|
return `/// <reference path="${refPath}" />
|
|
@@ -32992,9 +33070,9 @@ function stripWorkflowHeader(content) {
|
|
|
32992
33070
|
}
|
|
32993
33071
|
return lines.slice(start + 1).join("\n").replace(/\s+$/, "");
|
|
32994
33072
|
}
|
|
32995
|
-
async function writeWorkflowFiles(client, projectDir, app_id,
|
|
33073
|
+
async function writeWorkflowFiles(client, projectDir, app_id, workflows) {
|
|
32996
33074
|
const written = [];
|
|
32997
|
-
for (const alias of
|
|
33075
|
+
for (const [alias, declaration] of Object.entries(workflows)) {
|
|
32998
33076
|
const res = await client.getAppWorkflow(app_id, alias);
|
|
32999
33077
|
const source = res.error || res.result === null || typeof res.result !== "object" ? null : res.result.source;
|
|
33000
33078
|
if (typeof source !== "string" || source.trim() === "") {
|
|
@@ -33003,15 +33081,26 @@ async function writeWorkflowFiles(client, projectDir, app_id, aliases) {
|
|
|
33003
33081
|
);
|
|
33004
33082
|
continue;
|
|
33005
33083
|
}
|
|
33006
|
-
const envelope = await fetchWorkflowGlobals(
|
|
33084
|
+
const envelope = await fetchWorkflowGlobals(
|
|
33085
|
+
client,
|
|
33086
|
+
projectDir,
|
|
33087
|
+
app_id,
|
|
33088
|
+
alias,
|
|
33089
|
+
toWorkflowDtsDeclaration(declaration)
|
|
33090
|
+
);
|
|
33007
33091
|
writeWorkflowFile(projectDir, alias, source, envelope);
|
|
33008
33092
|
written.push(alias);
|
|
33009
33093
|
}
|
|
33010
33094
|
return written;
|
|
33011
33095
|
}
|
|
33012
|
-
async function fetchWorkflowGlobals(client, projectDir, app_id, alias) {
|
|
33096
|
+
async function fetchWorkflowGlobals(client, projectDir, app_id, alias, declaration) {
|
|
33013
33097
|
try {
|
|
33014
|
-
const { dts, envelope_prefix, envelope_suffix } = await
|
|
33098
|
+
const { dts, envelope_prefix, envelope_suffix } = await fetchWorkflowDts(
|
|
33099
|
+
client,
|
|
33100
|
+
app_id,
|
|
33101
|
+
alias,
|
|
33102
|
+
declaration
|
|
33103
|
+
);
|
|
33015
33104
|
writeWorkflowGlobals(projectDir, alias, dts);
|
|
33016
33105
|
return { prefix: envelope_prefix, suffix: envelope_suffix };
|
|
33017
33106
|
} catch (err2) {
|
|
@@ -33021,6 +33110,16 @@ async function fetchWorkflowGlobals(client, projectDir, app_id, alias) {
|
|
|
33021
33110
|
return { prefix: FALLBACK_ENVELOPE_PREFIX, suffix: FALLBACK_ENVELOPE_SUFFIX };
|
|
33022
33111
|
}
|
|
33023
33112
|
}
|
|
33113
|
+
async function fetchWorkflowDts(client, app_id, alias, declaration) {
|
|
33114
|
+
try {
|
|
33115
|
+
return await client.getAppWorkflowDts(app_id, alias);
|
|
33116
|
+
} catch (err2) {
|
|
33117
|
+
if (declaration && isUnknownWorkflowAliasError(err2)) {
|
|
33118
|
+
return client.getAppWorkflowDts(app_id, alias, declaration);
|
|
33119
|
+
}
|
|
33120
|
+
throw err2;
|
|
33121
|
+
}
|
|
33122
|
+
}
|
|
33024
33123
|
function runTar(args, cwd) {
|
|
33025
33124
|
return new Promise((resolve2, reject2) => {
|
|
33026
33125
|
const proc = spawn2("tar", args, { cwd, stdio: ["ignore", "ignore", "pipe"] });
|
|
@@ -33067,10 +33166,37 @@ function readAppMeta(projectDir) {
|
|
|
33067
33166
|
capabilities: pkg2.lotics.capabilities
|
|
33068
33167
|
};
|
|
33069
33168
|
}
|
|
33169
|
+
var APP_META_KEYS = Object.keys({
|
|
33170
|
+
app_id: true,
|
|
33171
|
+
workspace_id: true,
|
|
33172
|
+
current_version_id: true,
|
|
33173
|
+
version_number: true,
|
|
33174
|
+
workflows: true,
|
|
33175
|
+
queries: true,
|
|
33176
|
+
agents: true,
|
|
33177
|
+
capabilities: true
|
|
33178
|
+
});
|
|
33070
33179
|
function writeAppMeta(projectDir, meta3) {
|
|
33071
33180
|
const pkgPath2 = path5.join(projectDir, "package.json");
|
|
33072
33181
|
const pkg2 = JSON.parse(fs4.readFileSync(pkgPath2, "utf-8"));
|
|
33073
|
-
pkg2.lotics
|
|
33182
|
+
const existing = pkg2.lotics && typeof pkg2.lotics === "object" ? pkg2.lotics : {};
|
|
33183
|
+
const preserved = {};
|
|
33184
|
+
for (const [key, value] of Object.entries(existing)) {
|
|
33185
|
+
if (!APP_META_KEYS.includes(key)) preserved[key] = value;
|
|
33186
|
+
}
|
|
33187
|
+
pkg2.lotics = { ...meta3, ...preserved };
|
|
33188
|
+
fs4.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
|
|
33189
|
+
}
|
|
33190
|
+
function writeWorkflowOutputs(projectDir, alias, outputs) {
|
|
33191
|
+
const pkgPath2 = path5.join(projectDir, "package.json");
|
|
33192
|
+
const pkg2 = JSON.parse(fs4.readFileSync(pkgPath2, "utf-8"));
|
|
33193
|
+
const declaration = pkg2.lotics?.workflows?.[alias];
|
|
33194
|
+
if (!declaration) {
|
|
33195
|
+
throw new Error(
|
|
33196
|
+
`package.json#lotics.workflows.${alias} disappeared before the derived-outputs write-back.`
|
|
33197
|
+
);
|
|
33198
|
+
}
|
|
33199
|
+
declaration.outputs = outputs;
|
|
33074
33200
|
fs4.writeFileSync(pkgPath2, JSON.stringify(pkg2, null, 2) + "\n");
|
|
33075
33201
|
}
|
|
33076
33202
|
function ensureAppTsconfig(projectDir) {
|
|
@@ -33205,19 +33331,29 @@ async function appCodegen(args) {
|
|
|
33205
33331
|
`\u26A0 Could not regenerate .lotics/app_fields.ts (${err2 instanceof Error ? err2.message : String(err2)}). Kept the existing file.`
|
|
33206
33332
|
);
|
|
33207
33333
|
}
|
|
33208
|
-
await refreshWorkflowGlobals(args.client, projectDir, meta3.app_id,
|
|
33334
|
+
await refreshWorkflowGlobals(args.client, projectDir, meta3.app_id, meta3.workflows ?? {});
|
|
33209
33335
|
}
|
|
33210
|
-
async function refreshWorkflowGlobals(client, projectDir, app_id,
|
|
33211
|
-
for (const alias of
|
|
33212
|
-
const
|
|
33213
|
-
|
|
33214
|
-
|
|
33215
|
-
|
|
33216
|
-
|
|
33217
|
-
|
|
33218
|
-
|
|
33336
|
+
async function refreshWorkflowGlobals(client, projectDir, app_id, workflows) {
|
|
33337
|
+
for (const [alias, declaration] of Object.entries(workflows)) {
|
|
33338
|
+
const refreshed = await refreshWorkflowTypes(
|
|
33339
|
+
client,
|
|
33340
|
+
projectDir,
|
|
33341
|
+
app_id,
|
|
33342
|
+
alias,
|
|
33343
|
+
toWorkflowDtsDeclaration(declaration)
|
|
33344
|
+
);
|
|
33345
|
+
if (refreshed) console.error(`Refreshed workflow types for ${alias}`);
|
|
33219
33346
|
}
|
|
33220
33347
|
}
|
|
33348
|
+
async function refreshWorkflowTypes(client, projectDir, app_id, alias, declaration) {
|
|
33349
|
+
const file2 = workflowFilePath(projectDir, alias);
|
|
33350
|
+
if (!fs4.existsSync(file2)) return false;
|
|
33351
|
+
const body = stripWorkflowHeader(fs4.readFileSync(file2, "utf-8"));
|
|
33352
|
+
if (body.trim() === "") return false;
|
|
33353
|
+
const envelope = await fetchWorkflowGlobals(client, projectDir, app_id, alias, declaration);
|
|
33354
|
+
writeWorkflowFile(projectDir, alias, body, envelope);
|
|
33355
|
+
return true;
|
|
33356
|
+
}
|
|
33221
33357
|
function stampPulledManifest(projectDir, args) {
|
|
33222
33358
|
writeAppMeta(projectDir, {
|
|
33223
33359
|
app_id: args.app_id,
|
|
@@ -33380,9 +33516,9 @@ async function appPull(client, args) {
|
|
|
33380
33516
|
queries: app.queries ?? {},
|
|
33381
33517
|
agents: app.agents ?? {}
|
|
33382
33518
|
});
|
|
33383
|
-
const
|
|
33384
|
-
if (
|
|
33385
|
-
const written = await writeWorkflowFiles(client, targetPath, app.id,
|
|
33519
|
+
const workflows = app.workflows ?? {};
|
|
33520
|
+
if (Object.keys(workflows).length > 0) {
|
|
33521
|
+
const written = await writeWorkflowFiles(client, targetPath, app.id, workflows);
|
|
33386
33522
|
if (written.length > 0) {
|
|
33387
33523
|
console.error(
|
|
33388
33524
|
`Wrote ${written.length} workflow ${written.length === 1 ? "body" : "bodies"} to ${WORKFLOWS_DIR}/ (${written.join(", ")})`
|
|
@@ -33660,6 +33796,96 @@ async function appExecuteWorkflow(client, args) {
|
|
|
33660
33796
|
}
|
|
33661
33797
|
if (status === "error" || cleanupFailed) process.exit(1);
|
|
33662
33798
|
}
|
|
33799
|
+
async function streamAgentTextDeltas(body, onText) {
|
|
33800
|
+
const reader = body.getReader();
|
|
33801
|
+
const decoder = new TextDecoder();
|
|
33802
|
+
let buffer = "";
|
|
33803
|
+
let accumulated = "";
|
|
33804
|
+
try {
|
|
33805
|
+
for (; ; ) {
|
|
33806
|
+
const { value, done } = await reader.read();
|
|
33807
|
+
if (done) break;
|
|
33808
|
+
buffer += decoder.decode(value, { stream: true });
|
|
33809
|
+
const frames = buffer.split("\n\n");
|
|
33810
|
+
buffer = frames.pop() ?? "";
|
|
33811
|
+
for (const frame of frames) {
|
|
33812
|
+
for (const line of frame.split("\n")) {
|
|
33813
|
+
if (!line.startsWith("data:")) continue;
|
|
33814
|
+
const payload = line.slice(5).trim();
|
|
33815
|
+
if (!payload || payload === "[DONE]") continue;
|
|
33816
|
+
let chunk;
|
|
33817
|
+
try {
|
|
33818
|
+
chunk = JSON.parse(payload);
|
|
33819
|
+
} catch {
|
|
33820
|
+
continue;
|
|
33821
|
+
}
|
|
33822
|
+
if (chunk.type === "text-delta" && chunk.delta) {
|
|
33823
|
+
accumulated += chunk.delta;
|
|
33824
|
+
onText(chunk.delta);
|
|
33825
|
+
}
|
|
33826
|
+
}
|
|
33827
|
+
}
|
|
33828
|
+
}
|
|
33829
|
+
} catch {
|
|
33830
|
+
} finally {
|
|
33831
|
+
reader.releaseLock();
|
|
33832
|
+
}
|
|
33833
|
+
return accumulated;
|
|
33834
|
+
}
|
|
33835
|
+
var AGENT_RUN_POLL = {
|
|
33836
|
+
intervalMs: 1e3,
|
|
33837
|
+
settleTimeoutMs: 21 * 60 * 1e3,
|
|
33838
|
+
existenceTimeoutMs: 5e3
|
|
33839
|
+
};
|
|
33840
|
+
var sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
33841
|
+
async function fetchSettledAgentRun(client, appId, sessionId, runId, timing) {
|
|
33842
|
+
const settleDeadline = Date.now() + timing.settleTimeoutMs;
|
|
33843
|
+
const existenceDeadline = Date.now() + timing.existenceTimeoutMs;
|
|
33844
|
+
for (; ; ) {
|
|
33845
|
+
const { runs } = await client.listAgentRuns(appId, sessionId);
|
|
33846
|
+
const target = runId ? runs.find((r) => r.id === runId) : runs[runs.length - 1];
|
|
33847
|
+
if (target) {
|
|
33848
|
+
if (target.status !== "running" || Date.now() >= settleDeadline) return target;
|
|
33849
|
+
} else if (Date.now() >= existenceDeadline) {
|
|
33850
|
+
return void 0;
|
|
33851
|
+
}
|
|
33852
|
+
await sleep(timing.intervalMs);
|
|
33853
|
+
}
|
|
33854
|
+
}
|
|
33855
|
+
async function appAgentRun(client, args, timing = AGENT_RUN_POLL) {
|
|
33856
|
+
const sessionId = args.sessionId ?? `cli-${randomUUID()}`;
|
|
33857
|
+
const continuing = args.sessionId !== void 0;
|
|
33858
|
+
const res = await client.appAgentRunStream(args.app_id, args.alias, {
|
|
33859
|
+
session_id: sessionId,
|
|
33860
|
+
input: args.input
|
|
33861
|
+
});
|
|
33862
|
+
const runId = res.headers.get("x-app-agent-run-id") ?? void 0;
|
|
33863
|
+
if (res.body) {
|
|
33864
|
+
await streamAgentTextDeltas(res.body, (piece) => process.stderr.write(piece));
|
|
33865
|
+
}
|
|
33866
|
+
const run = await fetchSettledAgentRun(client, args.app_id, sessionId, runId, timing);
|
|
33867
|
+
if (!run) {
|
|
33868
|
+
console.error(`
|
|
33869
|
+
Could not find the settled run for session ${sessionId} on ${args.app_id}.`);
|
|
33870
|
+
console.error("The run may still be in progress \u2014 re-check with `lotics run` against the app's agent-runs.");
|
|
33871
|
+
process.exit(1);
|
|
33872
|
+
}
|
|
33873
|
+
if (args.json) {
|
|
33874
|
+
console.log(JSON.stringify(run, null, 2));
|
|
33875
|
+
} else if (run.output !== null && typeof run.output === "object") {
|
|
33876
|
+
console.log(JSON.stringify(run.output, null, 2));
|
|
33877
|
+
} else if (typeof run.output === "string") {
|
|
33878
|
+
console.log(run.output);
|
|
33879
|
+
}
|
|
33880
|
+
console.error(
|
|
33881
|
+
`
|
|
33882
|
+
Agent "${args.alias}" run ${run.id} \u2192 ${run.status}${run.error_message ? `: ${run.error_message}` : ""}`
|
|
33883
|
+
);
|
|
33884
|
+
console.error(
|
|
33885
|
+
continuing ? `Session: ${sessionId}` : `Session: ${sessionId} (fresh \u2014 pass --session ${sessionId} to continue this thread)`
|
|
33886
|
+
);
|
|
33887
|
+
if (run.status !== "completed") process.exit(1);
|
|
33888
|
+
}
|
|
33663
33889
|
async function appWorkflowSet(client, args) {
|
|
33664
33890
|
const projectDir = process.cwd();
|
|
33665
33891
|
const meta3 = readAppMeta(projectDir);
|
|
@@ -33694,8 +33920,19 @@ async function appWorkflowSet(client, args) {
|
|
|
33694
33920
|
const result = res.result ?? {};
|
|
33695
33921
|
const workflowId = typeof result.workflow_id === "string" ? result.workflow_id : "(unknown)";
|
|
33696
33922
|
console.error(`Set workflow "${args.alias}" \u2192 ${workflowId}`);
|
|
33697
|
-
|
|
33698
|
-
|
|
33923
|
+
const derivedOutputs = result.outputs !== null && typeof result.outputs === "object" && !Array.isArray(result.outputs) ? result.outputs : null;
|
|
33924
|
+
if (!declaration.outputs && derivedOutputs && Object.keys(derivedOutputs).length > 0) {
|
|
33925
|
+
writeWorkflowOutputs(projectDir, args.alias, derivedOutputs);
|
|
33926
|
+
console.error(
|
|
33927
|
+
` Wrote the derived result.data schema into package.json#lotics.workflows.${args.alias}.outputs.`
|
|
33928
|
+
);
|
|
33929
|
+
const refreshed = await refreshWorkflowTypes(client, projectDir, meta3.app_id, args.alias, {
|
|
33930
|
+
inputs: declaration.inputs,
|
|
33931
|
+
outputs: derivedOutputs
|
|
33932
|
+
});
|
|
33933
|
+
if (refreshed) console.error(` Refreshed workflow types for ${args.alias}.`);
|
|
33934
|
+
} else if (derivedOutputs) {
|
|
33935
|
+
console.error(` result.data schema: ${JSON.stringify(derivedOutputs)}`);
|
|
33699
33936
|
}
|
|
33700
33937
|
}
|
|
33701
33938
|
async function appQuerySet(client, args) {
|
|
@@ -33721,12 +33958,12 @@ async function appWorkflowPull(client) {
|
|
|
33721
33958
|
const projectDir = process.cwd();
|
|
33722
33959
|
const meta3 = readAppMeta(projectDir);
|
|
33723
33960
|
const app = await client.getApp(meta3.app_id);
|
|
33724
|
-
const
|
|
33725
|
-
if (
|
|
33961
|
+
const workflows = app.workflows ?? {};
|
|
33962
|
+
if (Object.keys(workflows).length === 0) {
|
|
33726
33963
|
console.error(`App ${meta3.app_id} has no bound workflows.`);
|
|
33727
33964
|
return;
|
|
33728
33965
|
}
|
|
33729
|
-
const written = await writeWorkflowFiles(client, projectDir, meta3.app_id,
|
|
33966
|
+
const written = await writeWorkflowFiles(client, projectDir, meta3.app_id, workflows);
|
|
33730
33967
|
console.error(
|
|
33731
33968
|
`Wrote ${written.length} workflow ${written.length === 1 ? "body" : "bodies"} to ${WORKFLOWS_DIR}/` + (written.length > 0 ? ` (${written.join(", ")})` : "")
|
|
33732
33969
|
);
|
|
@@ -33891,6 +34128,7 @@ function parseArgs(argv) {
|
|
|
33891
34128
|
content: void 0,
|
|
33892
34129
|
timezone: void 0,
|
|
33893
34130
|
message: void 0,
|
|
34131
|
+
session: void 0,
|
|
33894
34132
|
local: false,
|
|
33895
34133
|
all: false,
|
|
33896
34134
|
yes: false,
|
|
@@ -33952,6 +34190,9 @@ function parseArgs(argv) {
|
|
|
33952
34190
|
case "--message":
|
|
33953
34191
|
flags.message = argv[++i2];
|
|
33954
34192
|
break;
|
|
34193
|
+
case "--session":
|
|
34194
|
+
flags.session = argv[++i2];
|
|
34195
|
+
break;
|
|
33955
34196
|
case "--local":
|
|
33956
34197
|
flags.local = true;
|
|
33957
34198
|
break;
|
|
@@ -34022,6 +34263,36 @@ async function ingestJsonArgs(opts) {
|
|
|
34022
34263
|
}
|
|
34023
34264
|
}
|
|
34024
34265
|
|
|
34266
|
+
// src/org_commands.ts
|
|
34267
|
+
function printWorkspaceList(workspaces, currentId) {
|
|
34268
|
+
for (const ws of workspaces) {
|
|
34269
|
+
const marker = ws.id === currentId ? " (current)" : "";
|
|
34270
|
+
console.error(` ${ws.id} ${ws.name} ${ws.timezone} ${ws.default_currency}${marker}`);
|
|
34271
|
+
}
|
|
34272
|
+
}
|
|
34273
|
+
async function validateOrgWorkspacePin(client, orgId, profile) {
|
|
34274
|
+
const pinned = profile.workspace_id;
|
|
34275
|
+
if (!pinned) return;
|
|
34276
|
+
let workspaces;
|
|
34277
|
+
try {
|
|
34278
|
+
workspaces = await client.listWorkspaces();
|
|
34279
|
+
} catch (error51) {
|
|
34280
|
+
const message = error51 instanceof Error ? error51.message : String(error51);
|
|
34281
|
+
console.error(`Could not validate the pinned workspace (${message}) \u2014 keeping ${pinned}.`);
|
|
34282
|
+
return;
|
|
34283
|
+
}
|
|
34284
|
+
if (workspaces.some((w) => w.id === pinned)) return;
|
|
34285
|
+
clearProfileWorkspace(orgId);
|
|
34286
|
+
console.error(`Pinned workspace ${pinned} is no longer in ${profile.org_name} \u2014 cleared the stale pin.`);
|
|
34287
|
+
if (workspaces.length === 0) {
|
|
34288
|
+
console.error("This organization has no workspaces yet.");
|
|
34289
|
+
return;
|
|
34290
|
+
}
|
|
34291
|
+
console.error("Select one with:\n");
|
|
34292
|
+
console.error(" lotics workspace select <id>\n");
|
|
34293
|
+
printWorkspaceList(workspaces);
|
|
34294
|
+
}
|
|
34295
|
+
|
|
34025
34296
|
// src/xlsx.ts
|
|
34026
34297
|
import fs6 from "node:fs";
|
|
34027
34298
|
|
|
@@ -47171,13 +47442,50 @@ function unmergeCellsCommand(workbook, sheetIndex, mergeRef) {
|
|
|
47171
47442
|
}
|
|
47172
47443
|
};
|
|
47173
47444
|
}
|
|
47445
|
+
function isInertCell(cell) {
|
|
47446
|
+
return (cell.value === null || cell.value === "") && cell.formula === void 0 && cell.styleIndex === 0 && (cell.originalXfIndex === void 0 || cell.originalXfIndex === 0) && (cell.numFmtCode === "General" || cell.numFmtCode === "") && cell.richText === void 0 && cell.error === void 0 && cell.content.type === "plain" && cell.content.text === "";
|
|
47447
|
+
}
|
|
47448
|
+
function clampInertCells(sheet) {
|
|
47449
|
+
let minRow = Infinity;
|
|
47450
|
+
let maxRow = 0;
|
|
47451
|
+
let minCol = Infinity;
|
|
47452
|
+
let maxCol = 0;
|
|
47453
|
+
let hasMeaningful = false;
|
|
47454
|
+
for (const [ref, cell] of sheet.cells) {
|
|
47455
|
+
if (isInertCell(cell)) continue;
|
|
47456
|
+
const rc = refToRowColSafe(ref);
|
|
47457
|
+
if (!rc) continue;
|
|
47458
|
+
hasMeaningful = true;
|
|
47459
|
+
if (rc.row < minRow) minRow = rc.row;
|
|
47460
|
+
if (rc.row > maxRow) maxRow = rc.row;
|
|
47461
|
+
if (rc.col < minCol) minCol = rc.col;
|
|
47462
|
+
if (rc.col > maxCol) maxCol = rc.col;
|
|
47463
|
+
}
|
|
47464
|
+
const pruned = /* @__PURE__ */ new Map();
|
|
47465
|
+
for (const [ref, cell] of sheet.cells) {
|
|
47466
|
+
if (!isInertCell(cell)) continue;
|
|
47467
|
+
if (!hasMeaningful) {
|
|
47468
|
+
pruned.set(ref, cell);
|
|
47469
|
+
continue;
|
|
47470
|
+
}
|
|
47471
|
+
const rc = refToRowColSafe(ref);
|
|
47472
|
+
if (!rc) continue;
|
|
47473
|
+
if (rc.row < minRow || rc.row > maxRow || rc.col < minCol || rc.col > maxCol) {
|
|
47474
|
+
pruned.set(ref, cell);
|
|
47475
|
+
}
|
|
47476
|
+
}
|
|
47477
|
+
for (const ref of pruned.keys()) sheet.cells.delete(ref);
|
|
47478
|
+
return pruned;
|
|
47479
|
+
}
|
|
47174
47480
|
function insertRowsCommand(workbook, sheetIndex, at, count) {
|
|
47175
47481
|
const sheet = workbook.sheets[sheetIndex];
|
|
47176
47482
|
let mergeSnapshot = [];
|
|
47483
|
+
let inertSnapshot = /* @__PURE__ */ new Map();
|
|
47177
47484
|
return {
|
|
47178
47485
|
description: `Insert ${count} row${count > 1 ? "s" : ""} at ${at}`,
|
|
47179
47486
|
execute() {
|
|
47180
47487
|
mergeSnapshot = [...sheet.mergedCells];
|
|
47488
|
+
inertSnapshot = clampInertCells(sheet);
|
|
47181
47489
|
const toMove = [];
|
|
47182
47490
|
for (const [ref] of sheet.cells) {
|
|
47183
47491
|
const rc = refToRowColSafe(ref);
|
|
@@ -47221,6 +47529,7 @@ function insertRowsCommand(workbook, sheetIndex, at, count) {
|
|
|
47221
47529
|
sheet.rowHeights.set(r - count, h);
|
|
47222
47530
|
}
|
|
47223
47531
|
for (let r = at; r < at + count; r++) sheet.rowHeights.delete(r);
|
|
47532
|
+
for (const [ref, cell] of inertSnapshot) sheet.cells.set(ref, cell);
|
|
47224
47533
|
workbook.emit({ type: "rows_deleted", sheet: sheetIndex, at, count });
|
|
47225
47534
|
}
|
|
47226
47535
|
};
|
|
@@ -47235,10 +47544,12 @@ function deleteRowsCommand(workbook, sheetIndex, at, count) {
|
|
|
47235
47544
|
}
|
|
47236
47545
|
}
|
|
47237
47546
|
let mergeSnapshot = [];
|
|
47547
|
+
let inertSnapshot = /* @__PURE__ */ new Map();
|
|
47238
47548
|
return {
|
|
47239
47549
|
description: `Delete ${count} row${count > 1 ? "s" : ""} at ${at}`,
|
|
47240
47550
|
execute() {
|
|
47241
47551
|
mergeSnapshot = [...sheet.mergedCells];
|
|
47552
|
+
inertSnapshot = clampInertCells(sheet);
|
|
47242
47553
|
for (const ref of snapshot.keys()) sheet.cells.delete(ref);
|
|
47243
47554
|
const toMove = [];
|
|
47244
47555
|
for (const [ref] of sheet.cells) {
|
|
@@ -47271,6 +47582,7 @@ function deleteRowsCommand(workbook, sheetIndex, at, count) {
|
|
|
47271
47582
|
for (const [ref, snap] of snapshot) {
|
|
47272
47583
|
sheet.set(ref, snap.value, sheet.styles?.get(snap.styleIndex), snap.numFmtCode, snap.formula);
|
|
47273
47584
|
}
|
|
47585
|
+
for (const [ref, cell] of inertSnapshot) sheet.cells.set(ref, cell);
|
|
47274
47586
|
workbook.emit({ type: "rows_inserted", sheet: sheetIndex, at, count });
|
|
47275
47587
|
}
|
|
47276
47588
|
};
|
|
@@ -47278,10 +47590,12 @@ function deleteRowsCommand(workbook, sheetIndex, at, count) {
|
|
|
47278
47590
|
function insertColsCommand(workbook, sheetIndex, at, count) {
|
|
47279
47591
|
const sheet = workbook.sheets[sheetIndex];
|
|
47280
47592
|
let mergeSnapshot = [];
|
|
47593
|
+
let inertSnapshot = /* @__PURE__ */ new Map();
|
|
47281
47594
|
return {
|
|
47282
47595
|
description: `Insert ${count} column${count > 1 ? "s" : ""} at ${at}`,
|
|
47283
47596
|
execute() {
|
|
47284
47597
|
mergeSnapshot = [...sheet.mergedCells];
|
|
47598
|
+
inertSnapshot = clampInertCells(sheet);
|
|
47285
47599
|
const toMove = [];
|
|
47286
47600
|
for (const [ref] of sheet.cells) {
|
|
47287
47601
|
const rc = refToRowColSafe(ref);
|
|
@@ -47325,6 +47639,7 @@ function insertColsCommand(workbook, sheetIndex, at, count) {
|
|
|
47325
47639
|
sheet.colWidths.set(c - count, w);
|
|
47326
47640
|
}
|
|
47327
47641
|
for (let c = at; c < at + count; c++) sheet.colWidths.delete(c);
|
|
47642
|
+
for (const [ref, cell] of inertSnapshot) sheet.cells.set(ref, cell);
|
|
47328
47643
|
workbook.emit({ type: "cols_deleted", sheet: sheetIndex, at, count });
|
|
47329
47644
|
}
|
|
47330
47645
|
};
|
|
@@ -47339,10 +47654,12 @@ function deleteColsCommand(workbook, sheetIndex, at, count) {
|
|
|
47339
47654
|
}
|
|
47340
47655
|
}
|
|
47341
47656
|
let mergeSnapshot = [];
|
|
47657
|
+
let inertSnapshot = /* @__PURE__ */ new Map();
|
|
47342
47658
|
return {
|
|
47343
47659
|
description: `Delete ${count} column${count > 1 ? "s" : ""} at ${at}`,
|
|
47344
47660
|
execute() {
|
|
47345
47661
|
mergeSnapshot = [...sheet.mergedCells];
|
|
47662
|
+
inertSnapshot = clampInertCells(sheet);
|
|
47346
47663
|
for (const ref of snapshot.keys()) sheet.cells.delete(ref);
|
|
47347
47664
|
const toMove = [];
|
|
47348
47665
|
for (const [ref] of sheet.cells) {
|
|
@@ -47375,6 +47692,7 @@ function deleteColsCommand(workbook, sheetIndex, at, count) {
|
|
|
47375
47692
|
for (const [ref, snap] of snapshot) {
|
|
47376
47693
|
sheet.set(ref, snap.value, sheet.styles?.get(snap.styleIndex), snap.numFmtCode, snap.formula);
|
|
47377
47694
|
}
|
|
47695
|
+
for (const [ref, cell] of inertSnapshot) sheet.cells.set(ref, cell);
|
|
47378
47696
|
workbook.emit({ type: "cols_inserted", sheet: sheetIndex, at, count });
|
|
47379
47697
|
}
|
|
47380
47698
|
};
|
|
@@ -47597,26 +47915,37 @@ function lazyEngine(workbook) {
|
|
|
47597
47915
|
}
|
|
47598
47916
|
};
|
|
47599
47917
|
}
|
|
47600
|
-
function readToJson(filePath) {
|
|
47918
|
+
function readToJson(filePath, filter2) {
|
|
47601
47919
|
const buffer = fs6.readFileSync(filePath);
|
|
47602
47920
|
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
47603
47921
|
const parsed = parseExcelBuffer(arrayBuffer);
|
|
47922
|
+
let sheets = parsed.sheets;
|
|
47923
|
+
if (filter2?.sheet !== void 0) {
|
|
47924
|
+
const match = sheets.filter((s) => s.name === filter2.sheet);
|
|
47925
|
+
if (match.length === 0) {
|
|
47926
|
+
const names = parsed.sheets.map((s) => `"${s.name}"`).join(", ");
|
|
47927
|
+
fail(`Sheet not found: "${filter2.sheet}". Available: ${names}`);
|
|
47928
|
+
}
|
|
47929
|
+
sheets = match;
|
|
47930
|
+
}
|
|
47604
47931
|
return {
|
|
47605
47932
|
activeSheetIndex: parsed.activeSheetIndex,
|
|
47606
|
-
sheets:
|
|
47933
|
+
sheets: sheets.map((s) => ({
|
|
47607
47934
|
name: s.name,
|
|
47608
47935
|
totalRowCount: s.totalRowCount,
|
|
47609
47936
|
truncated: s.truncated,
|
|
47610
|
-
cells: cellsFromParsedRows(s.rows),
|
|
47937
|
+
cells: cellsFromParsedRows(s.rows, filter2?.range),
|
|
47611
47938
|
merges: s.mergedCells.map((m) => `${rowColToRef(m.startRow, m.startCol)}:${rowColToRef(m.endRow, m.endCol)}`),
|
|
47612
47939
|
freeze: s.freezePane ? { row: s.freezePane.frozenRows, col: s.freezePane.frozenCols } : void 0
|
|
47613
47940
|
}))
|
|
47614
47941
|
};
|
|
47615
47942
|
}
|
|
47616
|
-
function cellsFromParsedRows(rows) {
|
|
47943
|
+
function cellsFromParsedRows(rows, range) {
|
|
47617
47944
|
const out = {};
|
|
47618
47945
|
for (const row of rows) {
|
|
47946
|
+
if (range && (row.index < range.startRow || row.index > range.endRow)) continue;
|
|
47619
47947
|
for (const cell of row.cells) {
|
|
47948
|
+
if (range && (cell.column < range.startCol || cell.column > range.endCol)) continue;
|
|
47620
47949
|
const ref = rowColToRef(row.index, cell.column);
|
|
47621
47950
|
const value = cell.typedValue ?? cell.value;
|
|
47622
47951
|
const entry = { value };
|
|
@@ -47688,9 +48017,34 @@ function valueToInput(value) {
|
|
|
47688
48017
|
if (value === null || value === void 0) return "";
|
|
47689
48018
|
return String(value);
|
|
47690
48019
|
}
|
|
47691
|
-
function
|
|
47692
|
-
|
|
47693
|
-
|
|
48020
|
+
function readOptionFlag(rest, name) {
|
|
48021
|
+
const i2 = rest.indexOf(name);
|
|
48022
|
+
if (i2 < 0) return void 0;
|
|
48023
|
+
const val = rest[i2 + 1];
|
|
48024
|
+
if (val === void 0 || val.startsWith("--")) fail(`Missing value for ${name}`);
|
|
48025
|
+
return val;
|
|
48026
|
+
}
|
|
48027
|
+
function splitOptionalSheetRef(spec) {
|
|
48028
|
+
const idx = spec.indexOf("!");
|
|
48029
|
+
if (idx < 0) return { sheet: void 0, ref: spec };
|
|
48030
|
+
return { sheet: spec.slice(0, idx), ref: spec.slice(idx + 1) };
|
|
48031
|
+
}
|
|
48032
|
+
function xlsxRead(filePath, rest) {
|
|
48033
|
+
if (!filePath) fail("Usage: lotics xlsx read <file> [--sheet <name>] [--range <sheet>!<A1:G60>]");
|
|
48034
|
+
const rangeArg = readOptionFlag(rest, "--range");
|
|
48035
|
+
const sheetArg = readOptionFlag(rest, "--sheet");
|
|
48036
|
+
let filter2;
|
|
48037
|
+
if (rangeArg !== void 0) {
|
|
48038
|
+
const { sheet, ref } = splitOptionalSheetRef(rangeArg);
|
|
48039
|
+
const targetSheet = sheet ?? sheetArg;
|
|
48040
|
+
if (targetSheet === void 0) {
|
|
48041
|
+
fail("--range without a <sheet>! prefix requires --sheet <name>");
|
|
48042
|
+
}
|
|
48043
|
+
filter2 = { sheet: targetSheet, range: parseRange2(ref) };
|
|
48044
|
+
} else if (sheetArg !== void 0) {
|
|
48045
|
+
filter2 = { sheet: sheetArg };
|
|
48046
|
+
}
|
|
48047
|
+
const data = readToJson(filePath, filter2);
|
|
47694
48048
|
console.log(JSON.stringify(data, null, 2));
|
|
47695
48049
|
}
|
|
47696
48050
|
function xlsxWrite(filePath, json2) {
|
|
@@ -47927,7 +48281,10 @@ function printXlsxHelp() {
|
|
|
47927
48281
|
console.error(`Lotics xlsx commands \u2014 manipulate .xlsx files in place.
|
|
47928
48282
|
Uses Lotics' own xlsx engine; round-trips faithfully with the Lotics editor and templates.
|
|
47929
48283
|
|
|
47930
|
-
lotics xlsx read <file>
|
|
48284
|
+
lotics xlsx read <file> [--sheet <name>] [--range <sheet>!<A1:G60>]
|
|
48285
|
+
Dump file as JSON (sheets, cells, merges, freeze).
|
|
48286
|
+
--sheet limits to one sheet; --range to a cell window
|
|
48287
|
+
(its <sheet>! prefix is optional when --sheet is given)
|
|
47931
48288
|
lotics xlsx write <file> '<json>' Create .xlsx from {"sheets":[{"name","cells":{"A1":...}}]}
|
|
47932
48289
|
lotics xlsx set-cell <file> <sheet>!<ref> '<v>' Set cell value (prefix '=' for formula)
|
|
47933
48290
|
lotics xlsx clear-range <file> <sheet>!<range> Clear all cells in a range
|
|
@@ -47949,7 +48306,7 @@ Edit ops mutate the file atomically (temp file + rename).`);
|
|
|
47949
48306
|
async function runXlsxCommand(subcommand, toolArgs, restArgs) {
|
|
47950
48307
|
switch (subcommand) {
|
|
47951
48308
|
case "read":
|
|
47952
|
-
return xlsxRead(toolArgs);
|
|
48309
|
+
return xlsxRead(toolArgs, restArgs);
|
|
47953
48310
|
case "write":
|
|
47954
48311
|
return xlsxWrite(toolArgs, restArgs[0]);
|
|
47955
48312
|
case "set-cell":
|
|
@@ -67999,7 +68356,7 @@ import { readFileSync as readFileSync2, writeFileSync, existsSync, mkdtempSync,
|
|
|
67999
68356
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
68000
68357
|
import { join, dirname, resolve, extname, basename } from "node:path";
|
|
68001
68358
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
68002
|
-
import { setTimeout as
|
|
68359
|
+
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
68003
68360
|
var HERE = dirname(fileURLToPath2(import.meta.url));
|
|
68004
68361
|
function fail2(msg) {
|
|
68005
68362
|
console.error(msg);
|
|
@@ -68057,8 +68414,14 @@ async function cdpConnect(wsUrl) {
|
|
|
68057
68414
|
});
|
|
68058
68415
|
return { send, close: () => ws.close() };
|
|
68059
68416
|
}
|
|
68417
|
+
function isStoredFileId(target) {
|
|
68418
|
+
return /^fil_[A-Za-z0-9]+$/.test(target);
|
|
68419
|
+
}
|
|
68420
|
+
function defaultPreviewOutputPath(filename, cwd) {
|
|
68421
|
+
return join(cwd, basename(filename, extname(filename)) + ".png");
|
|
68422
|
+
}
|
|
68060
68423
|
async function runPreviewCommand(filePath, flags) {
|
|
68061
|
-
if (!filePath) fail2("Usage: lotics preview <file.docx|.xlsx> [
|
|
68424
|
+
if (!filePath) fail2("Usage: lotics preview <file.docx|.xlsx | fil_id> [-o <file.png>]");
|
|
68062
68425
|
const abs = resolve(filePath);
|
|
68063
68426
|
if (!existsSync(abs)) fail2(`File not found: ${abs}`);
|
|
68064
68427
|
const ext = extname(abs).toLowerCase();
|
|
@@ -68125,7 +68488,7 @@ async function runPreviewCommand(filePath, flags) {
|
|
|
68125
68488
|
const p = parseInt(readFileSync2(portFile, "utf8").split("\n")[0], 10);
|
|
68126
68489
|
if (p) cdpPort = p;
|
|
68127
68490
|
}
|
|
68128
|
-
if (!cdpPort) await
|
|
68491
|
+
if (!cdpPort) await sleep2(100);
|
|
68129
68492
|
}
|
|
68130
68493
|
if (!cdpPort) throw new Error("Chrome did not expose a debugging port (launch failed?).");
|
|
68131
68494
|
let target;
|
|
@@ -68135,7 +68498,7 @@ async function runPreviewCommand(filePath, flags) {
|
|
|
68135
68498
|
target = list.find((t) => t.type === "page");
|
|
68136
68499
|
} catch {
|
|
68137
68500
|
}
|
|
68138
|
-
if (!target?.webSocketDebuggerUrl) await
|
|
68501
|
+
if (!target?.webSocketDebuggerUrl) await sleep2(100);
|
|
68139
68502
|
}
|
|
68140
68503
|
if (!target?.webSocketDebuggerUrl) throw new Error("No Chrome page target available.");
|
|
68141
68504
|
const cdp = await cdpConnect(target.webSocketDebuggerUrl);
|
|
@@ -68157,7 +68520,7 @@ async function runPreviewCommand(filePath, flags) {
|
|
|
68157
68520
|
if (v.warnings?.length) warnings.push(...v.warnings);
|
|
68158
68521
|
break;
|
|
68159
68522
|
}
|
|
68160
|
-
await
|
|
68523
|
+
await sleep2(75);
|
|
68161
68524
|
}
|
|
68162
68525
|
if (!done) throw new Error("Render timed out (page never signaled completion).");
|
|
68163
68526
|
if (err2) throw new Error(`Render engine error: ${err2}`);
|
|
@@ -68260,6 +68623,10 @@ COMMANDS
|
|
|
68260
68623
|
lotics app query set <alias> Push package.json#lotics.queries.<alias> to
|
|
68261
68624
|
apps.queries via set_app_query (no deploy;
|
|
68262
68625
|
re-synced by the next deploy from the manifest)
|
|
68626
|
+
lotics app agent run <app_id> <alias> '<json>' Run a bound app agent end-to-end
|
|
68627
|
+
(inputs: inline JSON, @file, or stdin; streams
|
|
68628
|
+
progress to stderr, reports the settled run;
|
|
68629
|
+
--session <id> continues a thread; --json)
|
|
68263
68630
|
lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address
|
|
68264
68631
|
lotics app rename "<new name>" Rename the app's display name (launcher title)
|
|
68265
68632
|
lotics app dev [path] Run the app locally with HMR (RPC forwarded to prod)
|
|
@@ -68287,8 +68654,10 @@ COMMANDS
|
|
|
68287
68654
|
lotics file download <file_id> Download a file by ID (alias: lotics download)
|
|
68288
68655
|
lotics file download record <record_id> <field_key>
|
|
68289
68656
|
Download all files on a record file field
|
|
68290
|
-
lotics file preview <file> [-o png]
|
|
68291
|
-
|
|
68657
|
+
lotics file preview <file|fil_id> [-o png]
|
|
68658
|
+
Render a .docx/.xlsx to a PNG \u2014 a local path OR a
|
|
68659
|
+
stored file id (downloaded first). Frontend engines;
|
|
68660
|
+
needs a Chrome/Chromium on the machine
|
|
68292
68661
|
|
|
68293
68662
|
FLAGS
|
|
68294
68663
|
--json Full JSON output (default is human-readable text)
|
|
@@ -68495,12 +68864,6 @@ var SOURCE_LABELS = {
|
|
|
68495
68864
|
local_pointer: "local .lotics/config.json (pin)",
|
|
68496
68865
|
global_profile: "global active profile"
|
|
68497
68866
|
};
|
|
68498
|
-
function printWorkspaceList(workspaces, currentId) {
|
|
68499
|
-
for (const ws of workspaces) {
|
|
68500
|
-
const marker = ws.id === currentId ? " (current)" : "";
|
|
68501
|
-
console.error(` ${ws.id} ${ws.name} ${ws.timezone} ${ws.default_currency}${marker}`);
|
|
68502
|
-
}
|
|
68503
|
-
}
|
|
68504
68867
|
async function resolveWorkspace(client, ctx) {
|
|
68505
68868
|
if (ctx.workspaceId) {
|
|
68506
68869
|
client.setWorkspaceId(ctx.workspaceId);
|
|
@@ -68654,6 +69017,18 @@ async function main() {
|
|
|
68654
69017
|
return;
|
|
68655
69018
|
}
|
|
68656
69019
|
if (command === "preview") {
|
|
69020
|
+
if (subcommand && isStoredFileId(subcommand)) {
|
|
69021
|
+
const { client: client2 } = requireClient(flags);
|
|
69022
|
+
const tmpDir = fs9.mkdtempSync(path7.join(os2.tmpdir(), "lotics-preview-"));
|
|
69023
|
+
try {
|
|
69024
|
+
const { path: localPath, filename } = await client2.downloadFileById(subcommand, tmpDir);
|
|
69025
|
+
const output = flags.output ?? defaultPreviewOutputPath(filename, process.cwd());
|
|
69026
|
+
await runPreviewCommand(localPath, { output });
|
|
69027
|
+
} finally {
|
|
69028
|
+
fs9.rmSync(tmpDir, { recursive: true, force: true });
|
|
69029
|
+
}
|
|
69030
|
+
return;
|
|
69031
|
+
}
|
|
68657
69032
|
await runPreviewCommand(subcommand, flags);
|
|
68658
69033
|
return;
|
|
68659
69034
|
}
|
|
@@ -68715,6 +69090,7 @@ async function main() {
|
|
|
68715
69090
|
console.error("Note: a local pin (.lotics/config.json) overrides the global default in this directory. Use --local to change the pin here.");
|
|
68716
69091
|
}
|
|
68717
69092
|
}
|
|
69093
|
+
await validateOrgWorkspacePin(new LoticsClient({ apiKey: profile.api_key }), orgId, profile);
|
|
68718
69094
|
return;
|
|
68719
69095
|
}
|
|
68720
69096
|
if (subcommand && subcommand !== "list") {
|
|
@@ -68764,6 +69140,7 @@ async function main() {
|
|
|
68764
69140
|
console.error(" lotics app workflow pull Rewrite src/workflows/*.ts from the server");
|
|
68765
69141
|
console.error(" lotics app workflow check [alias] Typecheck src/workflows bodies locally");
|
|
68766
69142
|
console.error(" lotics app query set <alias> Push lotics.queries.<alias> to apps.queries (no deploy)");
|
|
69143
|
+
console.error(" lotics app agent run <app_id> <alias> '<json>' Run a bound app agent (streams progress, reports the settled run)");
|
|
68767
69144
|
console.error(" lotics app subdomain <new-subdomain> Rename the app's public <slug>.lotics.app address");
|
|
68768
69145
|
console.error(` lotics app rename "<new name>" Rename the app's display name (launcher title)`);
|
|
68769
69146
|
console.error(" lotics app dev [path] Run the app locally with HMR + RPC forwarding");
|
|
@@ -69022,6 +69399,42 @@ Available workspaces:`);
|
|
|
69022
69399
|
}
|
|
69023
69400
|
workflowUsage();
|
|
69024
69401
|
}
|
|
69402
|
+
if (subcommand === "agent") {
|
|
69403
|
+
const action = toolArgs;
|
|
69404
|
+
const agentUsage = () => {
|
|
69405
|
+
console.error("Usage: lotics app agent run <app_id> <alias> ['<json>'|@inputs.json|stdin] [--session <id>] [--json]");
|
|
69406
|
+
console.error(" cat inputs.json | lotics app agent run <app_id> <alias> (read inputs from stdin)");
|
|
69407
|
+
console.error("Streams the run's progress to stderr; reports the settled run (structured output / text) to stdout.");
|
|
69408
|
+
console.error("--session <id> continues an existing thread; omitted mints a fresh session per run.");
|
|
69409
|
+
process.exit(1);
|
|
69410
|
+
};
|
|
69411
|
+
if (action === "run") {
|
|
69412
|
+
const appId = restArgs[0];
|
|
69413
|
+
const alias = restArgs[1];
|
|
69414
|
+
if (!appId || !alias) {
|
|
69415
|
+
agentUsage();
|
|
69416
|
+
}
|
|
69417
|
+
const ingested = await ingestJsonArgs({
|
|
69418
|
+
rawArg: restArgs[2],
|
|
69419
|
+
stdinIsTTY: process.stdin.isTTY ?? false,
|
|
69420
|
+
readFile: (p) => fs9.readFileSync(p, "utf-8"),
|
|
69421
|
+
readStdin
|
|
69422
|
+
});
|
|
69423
|
+
if (ingested.kind === "error") {
|
|
69424
|
+
console.error(ingested.message);
|
|
69425
|
+
process.exit(1);
|
|
69426
|
+
}
|
|
69427
|
+
await appAgentRun(client, {
|
|
69428
|
+
app_id: appId,
|
|
69429
|
+
alias,
|
|
69430
|
+
input: ingested.args,
|
|
69431
|
+
sessionId: flags.session,
|
|
69432
|
+
json: flags.json
|
|
69433
|
+
});
|
|
69434
|
+
return;
|
|
69435
|
+
}
|
|
69436
|
+
agentUsage();
|
|
69437
|
+
}
|
|
69025
69438
|
if (subcommand === "query") {
|
|
69026
69439
|
const action = toolArgs;
|
|
69027
69440
|
if (action === "set") {
|
package/dist/src/client.d.ts
CHANGED
|
@@ -50,6 +50,31 @@ export interface ToolExecuteResult {
|
|
|
50
50
|
model_output?: string;
|
|
51
51
|
error?: string;
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* A settled (or in-flight) app-agent run, the transcript-excluded projection
|
|
55
|
+
* `GET /v1/apps/{app_id}/agent-runs` returns. `output` is the STRUCTURED result
|
|
56
|
+
* for a typed agent (an object) or the final text for a free-text agent (a
|
|
57
|
+
* string); `status` is `running` until the run settles to `completed` / `error`
|
|
58
|
+
* / `aborted`. The authoritative record `lotics app agent run` reports from
|
|
59
|
+
* (never the stream).
|
|
60
|
+
*/
|
|
61
|
+
export interface AppAgentRunSummary {
|
|
62
|
+
id: string;
|
|
63
|
+
app_id: string;
|
|
64
|
+
agent_alias: string;
|
|
65
|
+
session_id: string;
|
|
66
|
+
status: string;
|
|
67
|
+
input: Record<string, unknown> | null;
|
|
68
|
+
output: string | Record<string, unknown> | null;
|
|
69
|
+
usage: {
|
|
70
|
+
input_tokens: number;
|
|
71
|
+
output_tokens: number;
|
|
72
|
+
} | null;
|
|
73
|
+
error_message: string | null;
|
|
74
|
+
triggered_by_member_id: string | null;
|
|
75
|
+
started_at: string;
|
|
76
|
+
completed_at: string | null;
|
|
77
|
+
}
|
|
53
78
|
export interface ToolInfo {
|
|
54
79
|
name: string;
|
|
55
80
|
description: string;
|
|
@@ -858,8 +883,17 @@ export declare class LoticsClient {
|
|
|
858
883
|
* `async function __workflow(): …` wrapper the server compiles inside, so the
|
|
859
884
|
* local typecheck mirrors the set-time verdict. Mirrors
|
|
860
885
|
* POST /v1/apps/{app_id}/workflows/{alias}/dts.
|
|
886
|
+
*
|
|
887
|
+
* `declaration` (the manifest's `{ inputs?, outputs? }`) is posted as the body
|
|
888
|
+
* `{ declaration }` ONLY when the alias isn't `set` on the server yet — the
|
|
889
|
+
* server then synthesizes the dts from the declared schemas instead of 400ing
|
|
890
|
+
* "no workflow alias". A registered alias needs no declaration (the server's
|
|
891
|
+
* own bound contract wins), so the field is omitted in that case.
|
|
861
892
|
*/
|
|
862
|
-
getAppWorkflowDts(app_id: string, alias: string
|
|
893
|
+
getAppWorkflowDts(app_id: string, alias: string, declaration?: {
|
|
894
|
+
inputs?: Record<string, unknown>;
|
|
895
|
+
outputs?: Record<string, unknown>;
|
|
896
|
+
}): Promise<{
|
|
863
897
|
dts: string;
|
|
864
898
|
envelope_prefix: string;
|
|
865
899
|
envelope_suffix: string;
|
|
@@ -874,6 +908,25 @@ export declare class LoticsClient {
|
|
|
874
908
|
session_id: string;
|
|
875
909
|
input: Record<string, unknown>;
|
|
876
910
|
}, signal?: AbortSignal): Promise<Response>;
|
|
911
|
+
/**
|
|
912
|
+
* Continue a PARKED (`awaiting_input`) agent run with the user's answer to its
|
|
913
|
+
* pending `ask_user_choice` — returns the RAW streamed continuation `Response`,
|
|
914
|
+
* exactly like `appAgentRunStream`. Mirrors
|
|
915
|
+
* POST /v1/apps/{app_id}/agent-runs/{run_id}/continue.
|
|
916
|
+
*/
|
|
917
|
+
appAgentRunContinueStream(app_id: string, run_id: string, body: {
|
|
918
|
+
tool_call_id: string;
|
|
919
|
+
output: Record<string, unknown>;
|
|
920
|
+
}, signal?: AbortSignal): Promise<Response>;
|
|
921
|
+
/**
|
|
922
|
+
* A session's app-agent run history, oldest-first (the run just started is the
|
|
923
|
+
* last, and its exact id is on the stream response's `x-app-agent-run-id`
|
|
924
|
+
* header). Transcript excluded; structured `output`/`input` included. Mirrors
|
|
925
|
+
* GET /v1/apps/{app_id}/agent-runs.
|
|
926
|
+
*/
|
|
927
|
+
listAgentRuns(app_id: string, session_id: string): Promise<{
|
|
928
|
+
runs: AppAgentRunSummary[];
|
|
929
|
+
}>;
|
|
877
930
|
/**
|
|
878
931
|
* Mint a presigned URL for uploading a file into an app. Mirrors
|
|
879
932
|
* POST /v1/apps/{app_id}/files/upload-url.
|
package/dist/src/client.js
CHANGED
|
@@ -608,9 +608,15 @@ export class LoticsClient {
|
|
|
608
608
|
* `async function __workflow(): …` wrapper the server compiles inside, so the
|
|
609
609
|
* local typecheck mirrors the set-time verdict. Mirrors
|
|
610
610
|
* POST /v1/apps/{app_id}/workflows/{alias}/dts.
|
|
611
|
+
*
|
|
612
|
+
* `declaration` (the manifest's `{ inputs?, outputs? }`) is posted as the body
|
|
613
|
+
* `{ declaration }` ONLY when the alias isn't `set` on the server yet — the
|
|
614
|
+
* server then synthesizes the dts from the declared schemas instead of 400ing
|
|
615
|
+
* "no workflow alias". A registered alias needs no declaration (the server's
|
|
616
|
+
* own bound contract wins), so the field is omitted in that case.
|
|
611
617
|
*/
|
|
612
|
-
async getAppWorkflowDts(app_id, alias) {
|
|
613
|
-
return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/workflows/${encodeURIComponent(alias)}/dts
|
|
618
|
+
async getAppWorkflowDts(app_id, alias, declaration) {
|
|
619
|
+
return this.request("POST", `/v1/apps/${encodeURIComponent(app_id)}/workflows/${encodeURIComponent(alias)}/dts`, declaration ? { declaration } : undefined);
|
|
614
620
|
}
|
|
615
621
|
/**
|
|
616
622
|
* Open a streaming agent run and return the RAW streamed `Response` (the
|
|
@@ -629,6 +635,32 @@ export class LoticsClient {
|
|
|
629
635
|
await this.throwResponseError(res);
|
|
630
636
|
return res;
|
|
631
637
|
}
|
|
638
|
+
/**
|
|
639
|
+
* Continue a PARKED (`awaiting_input`) agent run with the user's answer to its
|
|
640
|
+
* pending `ask_user_choice` — returns the RAW streamed continuation `Response`,
|
|
641
|
+
* exactly like `appAgentRunStream`. Mirrors
|
|
642
|
+
* POST /v1/apps/{app_id}/agent-runs/{run_id}/continue.
|
|
643
|
+
*/
|
|
644
|
+
async appAgentRunContinueStream(app_id, run_id, body, signal) {
|
|
645
|
+
const res = await fetch(`${this.baseUrl}/v1/apps/${encodeURIComponent(app_id)}/agent-runs/${encodeURIComponent(run_id)}/continue`, {
|
|
646
|
+
method: "POST",
|
|
647
|
+
headers: { ...this.buildHeaders(), "Content-Type": "application/json" },
|
|
648
|
+
body: JSON.stringify(body),
|
|
649
|
+
signal,
|
|
650
|
+
});
|
|
651
|
+
if (!res.ok)
|
|
652
|
+
await this.throwResponseError(res);
|
|
653
|
+
return res;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* A session's app-agent run history, oldest-first (the run just started is the
|
|
657
|
+
* last, and its exact id is on the stream response's `x-app-agent-run-id`
|
|
658
|
+
* header). Transcript excluded; structured `output`/`input` included. Mirrors
|
|
659
|
+
* GET /v1/apps/{app_id}/agent-runs.
|
|
660
|
+
*/
|
|
661
|
+
async listAgentRuns(app_id, session_id) {
|
|
662
|
+
return this.request("GET", `/v1/apps/${encodeURIComponent(app_id)}/agent-runs?session_id=${encodeURIComponent(session_id)}`);
|
|
663
|
+
}
|
|
632
664
|
/**
|
|
633
665
|
* Mint a presigned URL for uploading a file into an app. Mirrors
|
|
634
666
|
* POST /v1/apps/{app_id}/files/upload-url.
|