struere 0.12.9 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/struere.js +1682 -188
- package/dist/cli/commands/chat.d.ts.map +1 -1
- package/dist/cli/commands/compile-prompt.d.ts.map +1 -1
- package/dist/cli/commands/deploy.d.ts.map +1 -1
- package/dist/cli/commands/diff.d.ts +3 -0
- package/dist/cli/commands/diff.d.ts.map +1 -0
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/threads.d.ts +3 -0
- package/dist/cli/commands/threads.d.ts.map +1 -0
- package/dist/cli/commands/triggers.d.ts.map +1 -1
- package/dist/cli/index.js +1682 -188
- package/dist/cli/utils/convex.d.ts +10 -0
- package/dist/cli/utils/convex.d.ts.map +1 -1
- package/dist/cli/utils/diff.d.ts +9 -0
- package/dist/cli/utils/diff.d.ts.map +1 -0
- package/dist/cli/utils/threads.d.ts +60 -0
- package/dist/cli/utils/threads.d.ts.map +1 -0
- package/dist/cli/utils/validator.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/bin/struere.js
CHANGED
|
@@ -218,7 +218,19 @@ async function syncViaHttp(apiKey, payload) {
|
|
|
218
218
|
return { success: false, error: text || `HTTP ${response.status}` };
|
|
219
219
|
}
|
|
220
220
|
if (!response.ok) {
|
|
221
|
-
const
|
|
221
|
+
const parts = [];
|
|
222
|
+
if (json.error)
|
|
223
|
+
parts.push(String(json.error));
|
|
224
|
+
if (json.message)
|
|
225
|
+
parts.push(String(json.message));
|
|
226
|
+
if (json.details)
|
|
227
|
+
parts.push(String(json.details));
|
|
228
|
+
if (Array.isArray(json.errors)) {
|
|
229
|
+
for (const e of json.errors) {
|
|
230
|
+
parts.push(typeof e === "string" ? e : JSON.stringify(e));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const msg = parts.length > 0 ? parts.join(" \u2014 ") : `HTTP ${response.status}: ${text}`;
|
|
222
234
|
return { success: false, error: msg };
|
|
223
235
|
}
|
|
224
236
|
return json;
|
|
@@ -268,15 +280,16 @@ async function _syncOrganization(payload) {
|
|
|
268
280
|
return { success: false, error: text || `HTTP ${response.status}` };
|
|
269
281
|
}
|
|
270
282
|
const extractError = () => {
|
|
283
|
+
const code = typeof json.errorData === "object" && json.errorData?.code ? `[${json.errorData.code}] ` : "";
|
|
271
284
|
if (typeof json.errorData === "string")
|
|
272
|
-
return json.errorData
|
|
285
|
+
return `${code}${json.errorData}`;
|
|
273
286
|
if (json.errorData?.message)
|
|
274
|
-
return json.errorData.message
|
|
287
|
+
return `${code}${json.errorData.message}`;
|
|
275
288
|
if (json.errorMessage)
|
|
276
|
-
return json.errorMessage
|
|
289
|
+
return `${code}${json.errorMessage}`;
|
|
277
290
|
if (json.message)
|
|
278
|
-
return json.message
|
|
279
|
-
return text
|
|
291
|
+
return `${code}${json.message}`;
|
|
292
|
+
return `${code}${text}`;
|
|
280
293
|
};
|
|
281
294
|
if (!response.ok) {
|
|
282
295
|
return { success: false, error: extractError() };
|
|
@@ -3164,10 +3177,11 @@ function validateResources(payload, resources) {
|
|
|
3164
3177
|
}
|
|
3165
3178
|
}
|
|
3166
3179
|
}
|
|
3180
|
+
const BUILTIN_ENTITY_TYPES = new Set(["payment"]);
|
|
3167
3181
|
const entityTypeSlugs = new Set(payload.entityTypes.map((et) => et.slug));
|
|
3168
3182
|
if (payload.triggers) {
|
|
3169
3183
|
for (const trigger of payload.triggers) {
|
|
3170
|
-
if (trigger.entityType && !entityTypeSlugs.has(trigger.entityType)) {
|
|
3184
|
+
if (trigger.entityType && !entityTypeSlugs.has(trigger.entityType) && !BUILTIN_ENTITY_TYPES.has(trigger.entityType)) {
|
|
3171
3185
|
errors.push(`Trigger "${trigger.name}" references entity type "${trigger.entityType}" but no entity type with that slug exists`);
|
|
3172
3186
|
}
|
|
3173
3187
|
}
|
|
@@ -3688,7 +3702,7 @@ import chalk8 from "chalk";
|
|
|
3688
3702
|
import ora7 from "ora";
|
|
3689
3703
|
import { confirm as confirm4 } from "@inquirer/prompts";
|
|
3690
3704
|
init_convex();
|
|
3691
|
-
var deployCommand = new Command7("deploy").description("Deploy all resources to production").option("--dry-run", "Show what would be deployed without deploying").option("--force", "Skip destructive sync confirmation").option("--json", "Output results as JSON").action(async (options) => {
|
|
3705
|
+
var deployCommand = new Command7("deploy").description("Deploy all resources to production").option("--dry-run", "Show what would be deployed without deploying").option("--force", "Skip destructive sync confirmation").option("--json", "Output results as JSON").option("-v, --verbose", "Show detailed resource information").action(async (options) => {
|
|
3692
3706
|
const spinner = ora7();
|
|
3693
3707
|
const cwd = process.cwd();
|
|
3694
3708
|
const nonInteractive = !isInteractive();
|
|
@@ -3777,6 +3791,24 @@ var deployCommand = new Command7("deploy").description("Deploy all resources to
|
|
|
3777
3791
|
}
|
|
3778
3792
|
process.exit(1);
|
|
3779
3793
|
}
|
|
3794
|
+
if (options.verbose && !jsonMode) {
|
|
3795
|
+
console.log();
|
|
3796
|
+
if (resources.agents.length > 0)
|
|
3797
|
+
console.log(chalk8.gray(`Loaded ${resources.agents.length} agents: ${resources.agents.map((a) => a.slug).join(", ")}`));
|
|
3798
|
+
if (resources.entityTypes.length > 0)
|
|
3799
|
+
console.log(chalk8.gray(`Loaded ${resources.entityTypes.length} data types: ${resources.entityTypes.map((et) => et.slug).join(", ")}`));
|
|
3800
|
+
if (resources.roles.length > 0)
|
|
3801
|
+
console.log(chalk8.gray(`Loaded ${resources.roles.length} roles: ${resources.roles.map((r) => r.name).join(", ")}`));
|
|
3802
|
+
if (resources.triggers.length > 0)
|
|
3803
|
+
console.log(chalk8.gray(`Loaded ${resources.triggers.length} triggers: ${resources.triggers.map((t) => t.slug).join(", ")}`));
|
|
3804
|
+
if (resources.routers.length > 0)
|
|
3805
|
+
console.log(chalk8.gray(`Loaded ${resources.routers.length} routers: ${resources.routers.map((r) => r.slug).join(", ")}`));
|
|
3806
|
+
if (resources.evalSuites.length > 0)
|
|
3807
|
+
console.log(chalk8.gray(`Loaded ${resources.evalSuites.length} eval suites: ${resources.evalSuites.map((es) => es.suite).join(", ")}`));
|
|
3808
|
+
if (resources.customTools.length > 0)
|
|
3809
|
+
console.log(chalk8.gray(`Loaded ${resources.customTools.length} custom tools: ${resources.customTools.map((t) => t.name).join(", ")}`));
|
|
3810
|
+
console.log();
|
|
3811
|
+
}
|
|
3780
3812
|
if (resources.agents.length === 0) {
|
|
3781
3813
|
if (jsonMode) {
|
|
3782
3814
|
console.log(JSON.stringify({ success: false, error: "No agents found to deploy" }));
|
|
@@ -3820,8 +3852,10 @@ var deployCommand = new Command7("deploy").description("Deploy all resources to
|
|
|
3820
3852
|
if (!jsonMode)
|
|
3821
3853
|
spinner.stop();
|
|
3822
3854
|
} catch {
|
|
3823
|
-
if (!jsonMode)
|
|
3855
|
+
if (!jsonMode) {
|
|
3824
3856
|
spinner.stop();
|
|
3857
|
+
console.log(chalk8.yellow("Could not check remote state for deletions \u2014 proceeding without deletion warnings"));
|
|
3858
|
+
}
|
|
3825
3859
|
}
|
|
3826
3860
|
if (options.dryRun) {
|
|
3827
3861
|
if (jsonMode) {
|
|
@@ -3891,25 +3925,30 @@ var deployCommand = new Command7("deploy").description("Deploy all resources to
|
|
|
3891
3925
|
}
|
|
3892
3926
|
if (!jsonMode)
|
|
3893
3927
|
spinner.start("Deploying to production");
|
|
3928
|
+
let syncResult;
|
|
3929
|
+
const startTime = Date.now();
|
|
3894
3930
|
try {
|
|
3895
|
-
|
|
3931
|
+
syncResult = await syncOrganization({
|
|
3896
3932
|
agents: payload.agents,
|
|
3897
3933
|
tools: payload.tools,
|
|
3898
3934
|
entityTypes: payload.entityTypes,
|
|
3899
3935
|
roles: payload.roles,
|
|
3900
3936
|
triggers: payload.triggers,
|
|
3937
|
+
routers: payload.routers,
|
|
3901
3938
|
organizationId: project.organization.id,
|
|
3902
3939
|
environment: "production"
|
|
3903
3940
|
});
|
|
3904
3941
|
if (!syncResult.success) {
|
|
3905
3942
|
throw new Error(syncResult.error || "Deploy failed");
|
|
3906
3943
|
}
|
|
3944
|
+
const elapsed = Date.now() - startTime;
|
|
3907
3945
|
if (!jsonMode)
|
|
3908
|
-
spinner.succeed(
|
|
3946
|
+
spinner.succeed(`Deployed to production in ${(elapsed / 1000).toFixed(1)}s`);
|
|
3909
3947
|
if (jsonMode) {
|
|
3910
3948
|
console.log(JSON.stringify({
|
|
3911
3949
|
success: true,
|
|
3912
3950
|
environment: "production",
|
|
3951
|
+
durationMs: elapsed,
|
|
3913
3952
|
agents: {
|
|
3914
3953
|
created: syncResult.agents?.created || [],
|
|
3915
3954
|
updated: syncResult.agents?.updated || [],
|
|
@@ -3924,26 +3963,71 @@ var deployCommand = new Command7("deploy").description("Deploy all resources to
|
|
|
3924
3963
|
created: syncResult.roles?.created || [],
|
|
3925
3964
|
updated: syncResult.roles?.updated || [],
|
|
3926
3965
|
deleted: syncResult.roles?.deleted || []
|
|
3966
|
+
},
|
|
3967
|
+
triggers: {
|
|
3968
|
+
created: syncResult.triggers?.created || [],
|
|
3969
|
+
updated: syncResult.triggers?.updated || [],
|
|
3970
|
+
deleted: syncResult.triggers?.deleted || []
|
|
3971
|
+
},
|
|
3972
|
+
routers: {
|
|
3973
|
+
created: syncResult.routers?.created || [],
|
|
3974
|
+
updated: syncResult.routers?.updated || [],
|
|
3975
|
+
deleted: syncResult.routers?.deleted || []
|
|
3976
|
+
},
|
|
3977
|
+
evalSuites: {
|
|
3978
|
+
created: syncResult.evalSuites?.created || [],
|
|
3979
|
+
updated: syncResult.evalSuites?.updated || [],
|
|
3980
|
+
deleted: syncResult.evalSuites?.deleted || [],
|
|
3981
|
+
skipped: syncResult.evalSuites?.skipped || []
|
|
3927
3982
|
}
|
|
3928
3983
|
}));
|
|
3929
3984
|
} else {
|
|
3930
3985
|
console.log();
|
|
3931
3986
|
console.log(chalk8.green("Success!"), "All resources deployed to production");
|
|
3932
3987
|
console.log();
|
|
3933
|
-
if (
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3988
|
+
if (options.verbose) {
|
|
3989
|
+
const types = [
|
|
3990
|
+
{ label: "agent", data: syncResult.agents },
|
|
3991
|
+
{ label: "entity type", data: syncResult.entityTypes },
|
|
3992
|
+
{ label: "role", data: syncResult.roles },
|
|
3993
|
+
{ label: "trigger", data: syncResult.triggers },
|
|
3994
|
+
{ label: "router", data: syncResult.routers },
|
|
3995
|
+
{ label: "eval suite", data: syncResult.evalSuites }
|
|
3996
|
+
];
|
|
3997
|
+
for (const { label, data } of types) {
|
|
3998
|
+
if (!data)
|
|
3999
|
+
continue;
|
|
4000
|
+
for (const name of data.created) {
|
|
4001
|
+
console.log(chalk8.green(`Created ${label}: ${name}`));
|
|
4002
|
+
}
|
|
4003
|
+
for (const name of data.updated) {
|
|
4004
|
+
console.log(chalk8.blue(`Updated ${label}: ${name}`));
|
|
4005
|
+
}
|
|
4006
|
+
for (const name of data.deleted) {
|
|
4007
|
+
console.log(chalk8.red(`Deleted ${label}: ${name}`));
|
|
4008
|
+
}
|
|
3938
4009
|
}
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
const
|
|
3944
|
-
|
|
4010
|
+
console.log();
|
|
4011
|
+
} else {
|
|
4012
|
+
if (syncResult.agents?.created && syncResult.agents.created.length > 0) {
|
|
4013
|
+
console.log("New agents:");
|
|
4014
|
+
for (const slug of syncResult.agents.created) {
|
|
4015
|
+
const agent = resources.agents.find((a) => a.slug === slug);
|
|
4016
|
+
console.log(chalk8.gray(" -"), chalk8.cyan(agent?.name || slug));
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
if (syncResult.agents?.updated && syncResult.agents.updated.length > 0) {
|
|
4020
|
+
console.log("Updated agents:");
|
|
4021
|
+
for (const slug of syncResult.agents.updated) {
|
|
4022
|
+
const agent = resources.agents.find((a) => a.slug === slug);
|
|
4023
|
+
console.log(chalk8.gray(" -"), chalk8.cyan(agent?.name || slug));
|
|
4024
|
+
}
|
|
3945
4025
|
}
|
|
3946
4026
|
}
|
|
4027
|
+
if (syncResult.evalSuites?.skipped && syncResult.evalSuites.skipped.length > 0) {
|
|
4028
|
+
console.log(chalk8.yellow(`Skipped eval suites (agent not found): ${syncResult.evalSuites.skipped.join(", ")}`));
|
|
4029
|
+
console.log();
|
|
4030
|
+
}
|
|
3947
4031
|
console.log();
|
|
3948
4032
|
console.log(chalk8.gray("Test your agents:"));
|
|
3949
4033
|
console.log(chalk8.gray(" $"), chalk8.cyan(`curl -X POST ${getSiteUrl()}/v1/agents/<agent-slug>/chat -H "Authorization: Bearer YOUR_API_KEY" -d '{"message": "Hello"}'`));
|
|
@@ -3971,6 +4055,22 @@ var deployCommand = new Command7("deploy").description("Deploy all resources to
|
|
|
3971
4055
|
spinner.fail("Deployment failed");
|
|
3972
4056
|
console.log();
|
|
3973
4057
|
console.log(chalk8.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
4058
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
4059
|
+
if (errMsg.includes("ArgumentValidationError")) {
|
|
4060
|
+
const fieldMatch = errMsg.match(/Argument "([^"]+)"/);
|
|
4061
|
+
if (fieldMatch) {
|
|
4062
|
+
console.log(chalk8.yellow(`Hint: field "${fieldMatch[1]}" failed validation. Run with --dry-run to inspect your payload.`));
|
|
4063
|
+
} else {
|
|
4064
|
+
console.log(chalk8.yellow("Hint: a field failed validation. Run with --dry-run to inspect your payload."));
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
if (syncResult) {
|
|
4068
|
+
const agentCount = (syncResult.agents?.created?.length || 0) + (syncResult.agents?.updated?.length || 0);
|
|
4069
|
+
const entityTypeCount = (syncResult.entityTypes?.created?.length || 0) + (syncResult.entityTypes?.updated?.length || 0);
|
|
4070
|
+
if (agentCount > 0 || entityTypeCount > 0) {
|
|
4071
|
+
console.log(chalk8.yellow(`Partial sync: ${agentCount} agent(s), ${entityTypeCount} entity type(s) synced before failure`));
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
3974
4074
|
console.log();
|
|
3975
4075
|
}
|
|
3976
4076
|
process.exit(1);
|
|
@@ -6688,9 +6788,9 @@ function statusColor2(status) {
|
|
|
6688
6788
|
}
|
|
6689
6789
|
}
|
|
6690
6790
|
var templatesCommand = new Command16("templates").description("Manage WhatsApp message templates");
|
|
6691
|
-
templatesCommand.command("list").description("List all message templates").option("--env <environment>", "Environment to find connection (development|production|eval)").option("--
|
|
6791
|
+
templatesCommand.command("list").description("List all message templates").option("--env <environment>", "Environment to find connection (development|production|eval)").option("--json", "Output raw JSON").action(async (opts) => {
|
|
6692
6792
|
await ensureAuth3();
|
|
6693
|
-
const connectionId = await resolveConnectionId(opts.env ?? "production"
|
|
6793
|
+
const connectionId = await resolveConnectionId(opts.env ?? "production");
|
|
6694
6794
|
const out = createOutput();
|
|
6695
6795
|
out.start("Fetching templates");
|
|
6696
6796
|
const { data, error } = await listTemplates(connectionId);
|
|
@@ -7318,6 +7418,7 @@ init_credentials();
|
|
|
7318
7418
|
import { Command as Command18 } from "commander";
|
|
7319
7419
|
import chalk20 from "chalk";
|
|
7320
7420
|
import ora14 from "ora";
|
|
7421
|
+
init_convex();
|
|
7321
7422
|
|
|
7322
7423
|
// src/cli/utils/triggers.ts
|
|
7323
7424
|
init_credentials();
|
|
@@ -7457,6 +7558,20 @@ async function retryImmediateExecution(eventId, environment) {
|
|
|
7457
7558
|
}
|
|
7458
7559
|
|
|
7459
7560
|
// src/cli/commands/triggers.ts
|
|
7561
|
+
async function withTriggerAuthRetry(fn) {
|
|
7562
|
+
try {
|
|
7563
|
+
return await fn();
|
|
7564
|
+
} catch (err) {
|
|
7565
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
7566
|
+
if (msg.includes("Unauthenticated") || msg.includes("OIDC") || msg.includes("token") || msg.includes("expired")) {
|
|
7567
|
+
const refreshed = await refreshToken();
|
|
7568
|
+
if (!refreshed)
|
|
7569
|
+
throw err;
|
|
7570
|
+
return fn();
|
|
7571
|
+
}
|
|
7572
|
+
throw err;
|
|
7573
|
+
}
|
|
7574
|
+
}
|
|
7460
7575
|
async function ensureAuth5() {
|
|
7461
7576
|
const cwd = process.cwd();
|
|
7462
7577
|
const nonInteractive = !isInteractive();
|
|
@@ -7883,11 +7998,11 @@ triggersCommand.command("logs [slug]").description("View trigger execution histo
|
|
|
7883
7998
|
const spinner = ora14();
|
|
7884
7999
|
try {
|
|
7885
8000
|
spinner.start("Fetching execution logs");
|
|
7886
|
-
const executions = await listTriggerExecutions({
|
|
8001
|
+
const executions = await withTriggerAuthRetry(() => listTriggerExecutions({
|
|
7887
8002
|
environment: opts.env,
|
|
7888
8003
|
triggerSlug: slug,
|
|
7889
8004
|
limit: parseInt(opts.limit, 10)
|
|
7890
|
-
});
|
|
8005
|
+
}));
|
|
7891
8006
|
spinner.succeed(`Found ${executions.length} executions`);
|
|
7892
8007
|
if (opts.json) {
|
|
7893
8008
|
console.log(JSON.stringify(executions, null, 2));
|
|
@@ -7946,15 +8061,20 @@ triggersCommand.command("log <identifier>").description("View detailed trigger e
|
|
|
7946
8061
|
await ensureAuth5();
|
|
7947
8062
|
const spinner = ora14();
|
|
7948
8063
|
try {
|
|
8064
|
+
const nth = parseInt(opts.nth, 10);
|
|
8065
|
+
if (isNaN(nth) || nth < 1) {
|
|
8066
|
+
console.error(chalk20.red("Error:"), "--nth must be a positive integer (e.g., --nth 1 for most recent)");
|
|
8067
|
+
process.exit(1);
|
|
8068
|
+
}
|
|
7949
8069
|
let eventId = identifier;
|
|
7950
|
-
const isConvexId = identifier
|
|
8070
|
+
const isConvexId = /^[0-9a-zA-Z]{20,}$/.test(identifier);
|
|
7951
8071
|
if (!isConvexId) {
|
|
7952
8072
|
spinner.start("Resolving trigger slug to latest execution");
|
|
7953
|
-
const executions = await listTriggerExecutions({
|
|
8073
|
+
const executions = await withTriggerAuthRetry(() => listTriggerExecutions({
|
|
7954
8074
|
environment: opts.env,
|
|
7955
8075
|
triggerSlug: identifier,
|
|
7956
|
-
limit:
|
|
7957
|
-
});
|
|
8076
|
+
limit: nth
|
|
8077
|
+
}));
|
|
7958
8078
|
if (!executions.length) {
|
|
7959
8079
|
spinner.fail(`No executions found for trigger "${identifier}" in ${opts.env}`);
|
|
7960
8080
|
if (opts.json) {
|
|
@@ -7962,16 +8082,16 @@ triggersCommand.command("log <identifier>").description("View detailed trigger e
|
|
|
7962
8082
|
}
|
|
7963
8083
|
process.exit(1);
|
|
7964
8084
|
}
|
|
7965
|
-
const idx =
|
|
8085
|
+
const idx = nth - 1;
|
|
7966
8086
|
if (idx >= executions.length) {
|
|
7967
|
-
spinner.fail(`Only ${executions.length} executions found, cannot get #${
|
|
8087
|
+
spinner.fail(`Only ${executions.length} executions found, cannot get #${nth}`);
|
|
7968
8088
|
process.exit(1);
|
|
7969
8089
|
}
|
|
7970
8090
|
eventId = executions[idx]._id;
|
|
7971
8091
|
spinner.succeed(`Found execution for "${identifier}"`);
|
|
7972
8092
|
}
|
|
7973
8093
|
spinner.start("Fetching execution detail");
|
|
7974
|
-
const event = await getTriggerExecutionDetail(eventId, opts.env);
|
|
8094
|
+
const event = await withTriggerAuthRetry(() => getTriggerExecutionDetail(eventId, opts.env));
|
|
7975
8095
|
if (!event) {
|
|
7976
8096
|
spinner.fail("Execution not found");
|
|
7977
8097
|
if (opts.json) {
|
|
@@ -8251,27 +8371,182 @@ triggersCommand.command("fire <slug>").description("Manually fire a trigger").op
|
|
|
8251
8371
|
}
|
|
8252
8372
|
});
|
|
8253
8373
|
|
|
8254
|
-
// src/cli/commands/
|
|
8374
|
+
// src/cli/commands/threads.ts
|
|
8255
8375
|
init_credentials();
|
|
8256
8376
|
import { Command as Command19 } from "commander";
|
|
8257
8377
|
import chalk21 from "chalk";
|
|
8258
8378
|
import ora15 from "ora";
|
|
8379
|
+
|
|
8380
|
+
// src/cli/utils/threads.ts
|
|
8381
|
+
init_credentials();
|
|
8382
|
+
init_config();
|
|
8259
8383
|
init_convex();
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8384
|
+
function getToken4() {
|
|
8385
|
+
const credentials = loadCredentials();
|
|
8386
|
+
const apiKey = getApiKey();
|
|
8387
|
+
const token = apiKey || credentials?.token;
|
|
8388
|
+
if (!token)
|
|
8389
|
+
throw new Error("Not authenticated");
|
|
8390
|
+
return token;
|
|
8391
|
+
}
|
|
8392
|
+
async function threadsApiCall(path, body) {
|
|
8393
|
+
const credentials = loadCredentials();
|
|
8394
|
+
const apiKey = getApiKey();
|
|
8395
|
+
if (apiKey && !credentials?.token) {
|
|
8396
|
+
const siteUrl2 = getSiteUrl();
|
|
8397
|
+
const response2 = await fetch(`${siteUrl2}${path}`, {
|
|
8398
|
+
method: "POST",
|
|
8399
|
+
headers: {
|
|
8400
|
+
"Content-Type": "application/json",
|
|
8401
|
+
Authorization: `Bearer ${apiKey}`
|
|
8402
|
+
},
|
|
8403
|
+
body: JSON.stringify(body),
|
|
8404
|
+
signal: AbortSignal.timeout(30000)
|
|
8405
|
+
});
|
|
8406
|
+
const text2 = await response2.text();
|
|
8407
|
+
let json2;
|
|
8408
|
+
try {
|
|
8409
|
+
json2 = JSON.parse(text2);
|
|
8410
|
+
} catch {
|
|
8411
|
+
throw new Error(text2 || `HTTP ${response2.status}`);
|
|
8412
|
+
}
|
|
8413
|
+
if (!response2.ok) {
|
|
8414
|
+
throw new Error(json2.error || text2);
|
|
8415
|
+
}
|
|
8416
|
+
return json2;
|
|
8417
|
+
}
|
|
8418
|
+
if (credentials?.sessionId) {
|
|
8419
|
+
await refreshToken();
|
|
8420
|
+
}
|
|
8421
|
+
const freshCredentials = loadCredentials();
|
|
8422
|
+
const token = apiKey || freshCredentials?.token;
|
|
8423
|
+
if (!token) {
|
|
8424
|
+
throw new Error("Not authenticated");
|
|
8425
|
+
}
|
|
8426
|
+
const siteUrl = getSiteUrl();
|
|
8427
|
+
const response = await fetch(`${siteUrl}${path}`, {
|
|
8428
|
+
method: "POST",
|
|
8429
|
+
headers: {
|
|
8430
|
+
"Content-Type": "application/json",
|
|
8431
|
+
Authorization: `Bearer ${token}`
|
|
8432
|
+
},
|
|
8433
|
+
body: JSON.stringify(body),
|
|
8434
|
+
signal: AbortSignal.timeout(30000)
|
|
8435
|
+
});
|
|
8436
|
+
const text = await response.text();
|
|
8437
|
+
let json;
|
|
8438
|
+
try {
|
|
8439
|
+
json = JSON.parse(text);
|
|
8440
|
+
} catch {
|
|
8441
|
+
throw new Error(text || `HTTP ${response.status}`);
|
|
8442
|
+
}
|
|
8443
|
+
if (!response.ok) {
|
|
8444
|
+
throw new Error(json.error || text);
|
|
8445
|
+
}
|
|
8446
|
+
return json;
|
|
8447
|
+
}
|
|
8448
|
+
async function convexQuery7(path, args) {
|
|
8449
|
+
const token = getToken4();
|
|
8450
|
+
const response = await fetch(`${CONVEX_URL}/api/query`, {
|
|
8451
|
+
method: "POST",
|
|
8452
|
+
headers: {
|
|
8453
|
+
"Content-Type": "application/json",
|
|
8454
|
+
Authorization: `Bearer ${token}`
|
|
8455
|
+
},
|
|
8456
|
+
body: JSON.stringify({ path, args })
|
|
8457
|
+
});
|
|
8458
|
+
const text = await response.text();
|
|
8459
|
+
let json;
|
|
8460
|
+
try {
|
|
8461
|
+
json = JSON.parse(text);
|
|
8462
|
+
} catch {
|
|
8463
|
+
throw new Error(text || `HTTP ${response.status}`);
|
|
8464
|
+
}
|
|
8465
|
+
if (!response.ok) {
|
|
8466
|
+
throw new Error(json.errorData?.message || json.errorMessage || text);
|
|
8467
|
+
}
|
|
8468
|
+
if (json.status === "error") {
|
|
8469
|
+
throw new Error(json.errorMessage || "Unknown error from Convex");
|
|
8470
|
+
}
|
|
8471
|
+
return json.value;
|
|
8472
|
+
}
|
|
8473
|
+
async function listThreads(options) {
|
|
8474
|
+
const apiKey = getApiKey();
|
|
8475
|
+
const credentials = loadCredentials();
|
|
8476
|
+
if (apiKey && !credentials?.token) {
|
|
8477
|
+
const result = await threadsApiCall("/v1/threads/list", {
|
|
8478
|
+
channel: options.channel,
|
|
8479
|
+
limit: options.limit
|
|
8480
|
+
});
|
|
8481
|
+
return result.data;
|
|
8482
|
+
}
|
|
8483
|
+
return convexQuery7("threads:listWithPreviews", {
|
|
8484
|
+
environment: options.environment,
|
|
8485
|
+
channel: options.channel,
|
|
8486
|
+
limit: options.limit
|
|
8487
|
+
});
|
|
8488
|
+
}
|
|
8489
|
+
async function getThreadWithMessages(options) {
|
|
8490
|
+
return convexQuery7("threads:getWithMessages", {
|
|
8491
|
+
id: options.threadId
|
|
8492
|
+
});
|
|
8493
|
+
}
|
|
8494
|
+
async function archiveThread(options) {
|
|
8495
|
+
const apiKey = getApiKey();
|
|
8496
|
+
const credentials = loadCredentials();
|
|
8497
|
+
if (apiKey && !credentials?.token) {
|
|
8498
|
+
return threadsApiCall("/v1/threads/archive", {
|
|
8499
|
+
threadId: options.threadId
|
|
8500
|
+
});
|
|
8501
|
+
}
|
|
8502
|
+
const token = getToken4();
|
|
8503
|
+
const siteUrl = getSiteUrl();
|
|
8504
|
+
const response = await fetch(`${siteUrl}/v1/threads/archive`, {
|
|
8505
|
+
method: "POST",
|
|
8506
|
+
headers: {
|
|
8507
|
+
"Content-Type": "application/json",
|
|
8508
|
+
Authorization: `Bearer ${token}`
|
|
8509
|
+
},
|
|
8510
|
+
body: JSON.stringify({ threadId: options.threadId }),
|
|
8511
|
+
signal: AbortSignal.timeout(30000)
|
|
8512
|
+
});
|
|
8513
|
+
const text = await response.text();
|
|
8514
|
+
let json;
|
|
8515
|
+
try {
|
|
8516
|
+
json = JSON.parse(text);
|
|
8517
|
+
} catch {
|
|
8518
|
+
throw new Error(text || `HTTP ${response.status}`);
|
|
8519
|
+
}
|
|
8520
|
+
if (!response.ok) {
|
|
8521
|
+
throw new Error(json.error || text);
|
|
8522
|
+
}
|
|
8523
|
+
return { success: true };
|
|
8524
|
+
}
|
|
8525
|
+
async function findThreadByPhone(options) {
|
|
8526
|
+
const threads = await listThreads({
|
|
8527
|
+
environment: options.environment,
|
|
8528
|
+
organizationId: options.organizationId,
|
|
8529
|
+
limit: 100
|
|
8530
|
+
});
|
|
8531
|
+
const normalize = (p) => p.replace(/\D/g, "");
|
|
8532
|
+
const normalizedInput = normalize(options.phone);
|
|
8533
|
+
return threads.find((t) => {
|
|
8534
|
+
const phoneNumber = t.channelParams?.phoneNumber;
|
|
8535
|
+
if (phoneNumber && normalize(phoneNumber) === normalizedInput)
|
|
8536
|
+
return true;
|
|
8537
|
+
if (t.externalId && t.externalId.includes(normalizedInput))
|
|
8538
|
+
return true;
|
|
8539
|
+
return false;
|
|
8540
|
+
}) ?? null;
|
|
8541
|
+
}
|
|
8542
|
+
|
|
8543
|
+
// src/cli/commands/threads.ts
|
|
8544
|
+
async function ensureAuth6() {
|
|
8265
8545
|
const cwd = process.cwd();
|
|
8266
8546
|
const nonInteractive = !isInteractive();
|
|
8267
|
-
const jsonMode = !!options.json;
|
|
8268
8547
|
if (!hasProject(cwd)) {
|
|
8269
8548
|
if (nonInteractive) {
|
|
8270
|
-
|
|
8271
|
-
console.log(JSON.stringify({ success: false, error: "No struere.json found" }));
|
|
8272
|
-
} else {
|
|
8273
|
-
console.log(chalk21.red("No struere.json found. Run struere init first."));
|
|
8274
|
-
}
|
|
8549
|
+
console.error(chalk21.red("No struere.json found. Run struere init first."));
|
|
8275
8550
|
process.exit(1);
|
|
8276
8551
|
}
|
|
8277
8552
|
console.log(chalk21.yellow("No struere.json found - initializing project..."));
|
|
@@ -8282,24 +8557,11 @@ var compilePromptCommand = new Command19("compile-prompt").description("Compile
|
|
|
8282
8557
|
}
|
|
8283
8558
|
console.log();
|
|
8284
8559
|
}
|
|
8285
|
-
const project = loadProject(cwd);
|
|
8286
|
-
if (!project) {
|
|
8287
|
-
if (jsonMode) {
|
|
8288
|
-
console.log(JSON.stringify({ success: false, error: "Failed to load struere.json" }));
|
|
8289
|
-
} else {
|
|
8290
|
-
console.log(chalk21.red("Failed to load struere.json"));
|
|
8291
|
-
}
|
|
8292
|
-
process.exit(1);
|
|
8293
|
-
}
|
|
8294
8560
|
let credentials = loadCredentials();
|
|
8295
8561
|
const apiKey = getApiKey();
|
|
8296
8562
|
if (!credentials && !apiKey) {
|
|
8297
8563
|
if (nonInteractive) {
|
|
8298
|
-
|
|
8299
|
-
console.log(JSON.stringify({ success: false, error: "Not authenticated. Set STRUERE_API_KEY or run struere login." }));
|
|
8300
|
-
} else {
|
|
8301
|
-
console.log(chalk21.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
8302
|
-
}
|
|
8564
|
+
console.error(chalk21.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
8303
8565
|
process.exit(1);
|
|
8304
8566
|
}
|
|
8305
8567
|
console.log(chalk21.yellow("Not logged in - authenticating..."));
|
|
@@ -8311,54 +8573,350 @@ var compilePromptCommand = new Command19("compile-prompt").description("Compile
|
|
|
8311
8573
|
}
|
|
8312
8574
|
console.log();
|
|
8313
8575
|
}
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
}
|
|
8329
|
-
|
|
8330
|
-
|
|
8331
|
-
|
|
8576
|
+
return true;
|
|
8577
|
+
}
|
|
8578
|
+
function relativeTime3(ts) {
|
|
8579
|
+
const diff = Date.now() - ts;
|
|
8580
|
+
const seconds = Math.floor(diff / 1000);
|
|
8581
|
+
if (seconds < 60)
|
|
8582
|
+
return `${seconds}s ago`;
|
|
8583
|
+
const minutes = Math.floor(seconds / 60);
|
|
8584
|
+
if (minutes < 60)
|
|
8585
|
+
return `${minutes}m ago`;
|
|
8586
|
+
const hours = Math.floor(minutes / 60);
|
|
8587
|
+
if (hours < 24)
|
|
8588
|
+
return `${hours}h ago`;
|
|
8589
|
+
const days = Math.floor(hours / 24);
|
|
8590
|
+
return `${days}d ago`;
|
|
8591
|
+
}
|
|
8592
|
+
function channelColor(channel) {
|
|
8593
|
+
switch (channel) {
|
|
8594
|
+
case "whatsapp":
|
|
8595
|
+
return chalk21.green(channel);
|
|
8596
|
+
case "api":
|
|
8597
|
+
return chalk21.blue(channel);
|
|
8598
|
+
case "widget":
|
|
8599
|
+
return chalk21.magenta(channel);
|
|
8600
|
+
case "dashboard":
|
|
8601
|
+
return chalk21.cyan(channel);
|
|
8602
|
+
default:
|
|
8603
|
+
return chalk21.gray(channel ?? "-");
|
|
8604
|
+
}
|
|
8605
|
+
}
|
|
8606
|
+
function roleColor(role) {
|
|
8607
|
+
switch (role) {
|
|
8608
|
+
case "user":
|
|
8609
|
+
return chalk21.cyan(role);
|
|
8610
|
+
case "assistant":
|
|
8611
|
+
return chalk21.green(role);
|
|
8612
|
+
case "system":
|
|
8613
|
+
return chalk21.yellow(role);
|
|
8614
|
+
case "tool":
|
|
8615
|
+
return chalk21.magenta(role);
|
|
8616
|
+
default:
|
|
8617
|
+
return chalk21.gray(role);
|
|
8332
8618
|
}
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
8340
|
-
|
|
8619
|
+
}
|
|
8620
|
+
var threadsCommand = new Command19("threads").description("Manage conversation threads");
|
|
8621
|
+
threadsCommand.command("list", { isDefault: true }).description("List conversation threads").option("--env <environment>", "Environment (development|production|eval)", "development").option("--channel <channel>", "Filter by channel (whatsapp|api|widget|dashboard)").option("--limit <n>", "Maximum results", "25").option("--json", "Output raw JSON").action(async (opts) => {
|
|
8622
|
+
await ensureAuth6();
|
|
8623
|
+
const spinner = ora15();
|
|
8624
|
+
try {
|
|
8625
|
+
spinner.start("Fetching threads");
|
|
8626
|
+
const threads = await listThreads({
|
|
8627
|
+
environment: opts.env,
|
|
8628
|
+
channel: opts.channel,
|
|
8629
|
+
limit: parseInt(opts.limit, 10)
|
|
8341
8630
|
});
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
console.log(JSON.stringify({ success: false, error }));
|
|
8347
|
-
} else {
|
|
8348
|
-
spinner.fail("Failed to compile prompt");
|
|
8349
|
-
console.log(chalk21.red("Error:"), error);
|
|
8631
|
+
spinner.succeed(`Found ${threads.length} threads`);
|
|
8632
|
+
if (opts.json) {
|
|
8633
|
+
console.log(JSON.stringify(threads, null, 2));
|
|
8634
|
+
return;
|
|
8350
8635
|
}
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8636
|
+
console.log();
|
|
8637
|
+
renderTable([
|
|
8638
|
+
{ key: "id", label: "ID", width: 36 },
|
|
8639
|
+
{ key: "channel", label: "Channel", width: 10 },
|
|
8640
|
+
{ key: "participant", label: "Participant", width: 20 },
|
|
8641
|
+
{ key: "agent", label: "Agent", width: 18 },
|
|
8642
|
+
{ key: "lastMessage", label: "Last Message", width: 40 },
|
|
8643
|
+
{ key: "time", label: "Time", width: 10 }
|
|
8644
|
+
], threads.map((t) => ({
|
|
8645
|
+
id: t._id,
|
|
8646
|
+
channel: channelColor(t.channel),
|
|
8647
|
+
participant: t.participantName ?? "Unknown",
|
|
8648
|
+
agent: t.agentName ?? "Unknown",
|
|
8649
|
+
lastMessage: t.lastMessage ? t.lastMessage.content.slice(0, 40) : chalk21.gray("-"),
|
|
8650
|
+
time: relativeTime3(t.updatedAt ?? t.createdAt)
|
|
8651
|
+
})));
|
|
8652
|
+
console.log();
|
|
8653
|
+
} catch (err) {
|
|
8654
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8655
|
+
spinner.fail("Failed to fetch threads");
|
|
8656
|
+
if (opts.json) {
|
|
8657
|
+
console.log(JSON.stringify({ success: false, error: message }));
|
|
8356
8658
|
} else {
|
|
8357
|
-
|
|
8659
|
+
console.log(chalk21.red("Error:"), message);
|
|
8358
8660
|
}
|
|
8359
8661
|
process.exit(1);
|
|
8360
8662
|
}
|
|
8361
|
-
|
|
8663
|
+
});
|
|
8664
|
+
threadsCommand.command("view <id>").description("View thread details and messages").option("--env <environment>", "Environment", "development").option("--json", "Output raw JSON").action(async (id, opts) => {
|
|
8665
|
+
await ensureAuth6();
|
|
8666
|
+
const spinner = ora15();
|
|
8667
|
+
try {
|
|
8668
|
+
spinner.start("Fetching thread");
|
|
8669
|
+
const thread = await getThreadWithMessages({
|
|
8670
|
+
threadId: id,
|
|
8671
|
+
environment: opts.env
|
|
8672
|
+
});
|
|
8673
|
+
if (!thread) {
|
|
8674
|
+
spinner.fail("Thread not found");
|
|
8675
|
+
if (opts.json) {
|
|
8676
|
+
console.log(JSON.stringify({ success: false, error: "Thread not found" }));
|
|
8677
|
+
}
|
|
8678
|
+
process.exit(1);
|
|
8679
|
+
}
|
|
8680
|
+
spinner.succeed("Thread loaded");
|
|
8681
|
+
if (opts.json) {
|
|
8682
|
+
console.log(JSON.stringify(thread, null, 2));
|
|
8683
|
+
return;
|
|
8684
|
+
}
|
|
8685
|
+
console.log();
|
|
8686
|
+
console.log(` ${chalk21.gray("ID:")} ${chalk21.cyan(thread._id)}`);
|
|
8687
|
+
console.log(` ${chalk21.gray("Channel:")} ${channelColor(thread.channel)}`);
|
|
8688
|
+
if (thread.externalId) {
|
|
8689
|
+
console.log(` ${chalk21.gray("External:")} ${chalk21.cyan(thread.externalId)}`);
|
|
8690
|
+
}
|
|
8691
|
+
console.log(` ${chalk21.gray("Agent:")} ${chalk21.cyan(thread.agentId)}`);
|
|
8692
|
+
if (thread.currentAgentId) {
|
|
8693
|
+
console.log(` ${chalk21.gray("Current:")} ${chalk21.cyan(thread.currentAgentId)}`);
|
|
8694
|
+
}
|
|
8695
|
+
console.log(` ${chalk21.gray("Created:")} ${chalk21.cyan(new Date(thread.createdAt).toISOString())}`);
|
|
8696
|
+
console.log(` ${chalk21.gray("Updated:")} ${chalk21.cyan(new Date(thread.updatedAt).toISOString())}`);
|
|
8697
|
+
if (thread.messages?.length) {
|
|
8698
|
+
console.log();
|
|
8699
|
+
console.log(chalk21.bold("Messages"));
|
|
8700
|
+
console.log(chalk21.gray("\u2500".repeat(60)));
|
|
8701
|
+
for (const msg of thread.messages) {
|
|
8702
|
+
if (msg.toolCalls?.length)
|
|
8703
|
+
continue;
|
|
8704
|
+
const time = new Date(msg.createdAt).toLocaleTimeString();
|
|
8705
|
+
const role = roleColor(msg.role);
|
|
8706
|
+
const content = msg.content.length > 200 ? msg.content.slice(0, 200) + "..." : msg.content;
|
|
8707
|
+
console.log(` ${chalk21.gray(time)} ${role}: ${content}`);
|
|
8708
|
+
}
|
|
8709
|
+
}
|
|
8710
|
+
console.log();
|
|
8711
|
+
} catch (err) {
|
|
8712
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8713
|
+
spinner.fail("Failed to fetch thread");
|
|
8714
|
+
if (opts.json) {
|
|
8715
|
+
console.log(JSON.stringify({ success: false, error: message }));
|
|
8716
|
+
} else {
|
|
8717
|
+
console.log(chalk21.red("Error:"), message);
|
|
8718
|
+
}
|
|
8719
|
+
process.exit(1);
|
|
8720
|
+
}
|
|
8721
|
+
});
|
|
8722
|
+
threadsCommand.command("archive <id>").description("Archive a thread (frees its externalId)").option("--env <environment>", "Environment", "development").option("--confirm", "Skip production confirmation").option("--json", "Output raw JSON").action(async (id, opts) => {
|
|
8723
|
+
await ensureAuth6();
|
|
8724
|
+
const spinner = ora15();
|
|
8725
|
+
const environment = opts.env;
|
|
8726
|
+
if (environment === "production" && !opts.confirm && isInteractive()) {
|
|
8727
|
+
const readline = await import("readline");
|
|
8728
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
8729
|
+
await new Promise((resolve) => {
|
|
8730
|
+
rl.question(chalk21.yellow(`WARNING: Archiving thread in PRODUCTION environment.
|
|
8731
|
+
Press Enter to continue or Ctrl+C to cancel: `), resolve);
|
|
8732
|
+
});
|
|
8733
|
+
rl.close();
|
|
8734
|
+
}
|
|
8735
|
+
try {
|
|
8736
|
+
spinner.start("Archiving thread...");
|
|
8737
|
+
const result = await archiveThread({
|
|
8738
|
+
threadId: id,
|
|
8739
|
+
environment
|
|
8740
|
+
});
|
|
8741
|
+
spinner.succeed(chalk21.green(`Thread ${id} archived`));
|
|
8742
|
+
if (opts.json) {
|
|
8743
|
+
console.log(JSON.stringify({ success: true, threadId: id }));
|
|
8744
|
+
}
|
|
8745
|
+
} catch (err) {
|
|
8746
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8747
|
+
spinner.fail("Failed to archive thread");
|
|
8748
|
+
if (opts.json) {
|
|
8749
|
+
console.log(JSON.stringify({ success: false, error: message }));
|
|
8750
|
+
} else {
|
|
8751
|
+
console.log(chalk21.red("Error:"), message);
|
|
8752
|
+
}
|
|
8753
|
+
process.exit(1);
|
|
8754
|
+
}
|
|
8755
|
+
});
|
|
8756
|
+
threadsCommand.command("reset").description("Archive a thread by phone number").requiredOption("--phone <number>", "Phone number to find and archive").option("--env <environment>", "Environment", "production").option("--confirm", "Skip production confirmation").option("--json", "Output raw JSON").action(async (opts) => {
|
|
8757
|
+
await ensureAuth6();
|
|
8758
|
+
const spinner = ora15();
|
|
8759
|
+
const environment = opts.env;
|
|
8760
|
+
if (environment === "production" && !opts.confirm && isInteractive()) {
|
|
8761
|
+
const readline = await import("readline");
|
|
8762
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
8763
|
+
await new Promise((resolve) => {
|
|
8764
|
+
rl.question(chalk21.yellow(`WARNING: Resetting thread in PRODUCTION environment.
|
|
8765
|
+
Press Enter to continue or Ctrl+C to cancel: `), resolve);
|
|
8766
|
+
});
|
|
8767
|
+
rl.close();
|
|
8768
|
+
}
|
|
8769
|
+
try {
|
|
8770
|
+
spinner.start(`Finding thread for phone ${opts.phone}...`);
|
|
8771
|
+
const thread = await findThreadByPhone({
|
|
8772
|
+
phone: opts.phone,
|
|
8773
|
+
environment
|
|
8774
|
+
});
|
|
8775
|
+
if (!thread) {
|
|
8776
|
+
spinner.fail(`No thread found for phone number ${opts.phone}`);
|
|
8777
|
+
if (opts.json) {
|
|
8778
|
+
console.log(JSON.stringify({ success: false, error: "Thread not found for phone number" }));
|
|
8779
|
+
}
|
|
8780
|
+
process.exit(1);
|
|
8781
|
+
}
|
|
8782
|
+
spinner.text = `Archiving thread ${thread._id}...`;
|
|
8783
|
+
await archiveThread({
|
|
8784
|
+
threadId: thread._id,
|
|
8785
|
+
environment
|
|
8786
|
+
});
|
|
8787
|
+
spinner.succeed(chalk21.green(`Thread ${thread._id} archived (phone: ${opts.phone})`));
|
|
8788
|
+
if (opts.json) {
|
|
8789
|
+
console.log(JSON.stringify({ success: true, threadId: thread._id, phone: opts.phone }));
|
|
8790
|
+
}
|
|
8791
|
+
} catch (err) {
|
|
8792
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8793
|
+
spinner.fail("Failed to reset thread");
|
|
8794
|
+
if (opts.json) {
|
|
8795
|
+
console.log(JSON.stringify({ success: false, error: message }));
|
|
8796
|
+
} else {
|
|
8797
|
+
console.log(chalk21.red("Error:"), message);
|
|
8798
|
+
}
|
|
8799
|
+
process.exit(1);
|
|
8800
|
+
}
|
|
8801
|
+
});
|
|
8802
|
+
|
|
8803
|
+
// src/cli/commands/compile-prompt.ts
|
|
8804
|
+
init_credentials();
|
|
8805
|
+
import { Command as Command20 } from "commander";
|
|
8806
|
+
import chalk22 from "chalk";
|
|
8807
|
+
import ora16 from "ora";
|
|
8808
|
+
init_convex();
|
|
8809
|
+
var compilePromptCommand = new Command20("compile-prompt").description("Compile and preview an agent's system prompt after template processing").argument("<agent-slug>", "Agent slug to compile prompt for").option("--env <env>", "Environment: development | production | eval", "development").option("--message <msg>", "Sample message for template context").option("--channel <channel>", "Sample channel (whatsapp, widget, api, dashboard)").option("--param <key=value...>", "Custom thread param (repeatable)", (val, acc) => {
|
|
8810
|
+
acc.push(val);
|
|
8811
|
+
return acc;
|
|
8812
|
+
}, []).option("--phone <number>", "Shorthand for --param phoneNumber=<number>").option("--json", "Output full JSON (raw + compiled + context)").option("--raw", "Show raw uncompiled template instead of compiled").action(async (agentSlug, options) => {
|
|
8813
|
+
const spinner = ora16();
|
|
8814
|
+
const cwd = process.cwd();
|
|
8815
|
+
const nonInteractive = !isInteractive();
|
|
8816
|
+
const jsonMode = !!options.json;
|
|
8817
|
+
if (!hasProject(cwd)) {
|
|
8818
|
+
if (nonInteractive) {
|
|
8819
|
+
if (jsonMode) {
|
|
8820
|
+
console.log(JSON.stringify({ success: false, error: "No struere.json found" }));
|
|
8821
|
+
} else {
|
|
8822
|
+
console.log(chalk22.red("No struere.json found. Run struere init first."));
|
|
8823
|
+
}
|
|
8824
|
+
process.exit(1);
|
|
8825
|
+
}
|
|
8826
|
+
console.log(chalk22.yellow("No struere.json found - initializing project..."));
|
|
8827
|
+
console.log();
|
|
8828
|
+
const success = await runInit(cwd);
|
|
8829
|
+
if (!success) {
|
|
8830
|
+
process.exit(1);
|
|
8831
|
+
}
|
|
8832
|
+
console.log();
|
|
8833
|
+
}
|
|
8834
|
+
const project = loadProject(cwd);
|
|
8835
|
+
if (!project) {
|
|
8836
|
+
if (jsonMode) {
|
|
8837
|
+
console.log(JSON.stringify({ success: false, error: "Failed to load struere.json" }));
|
|
8838
|
+
} else {
|
|
8839
|
+
console.log(chalk22.red("Failed to load struere.json"));
|
|
8840
|
+
}
|
|
8841
|
+
process.exit(1);
|
|
8842
|
+
}
|
|
8843
|
+
let credentials = loadCredentials();
|
|
8844
|
+
const apiKey = getApiKey();
|
|
8845
|
+
if (!credentials && !apiKey) {
|
|
8846
|
+
if (nonInteractive) {
|
|
8847
|
+
if (jsonMode) {
|
|
8848
|
+
console.log(JSON.stringify({ success: false, error: "Not authenticated. Set STRUERE_API_KEY or run struere login." }));
|
|
8849
|
+
} else {
|
|
8850
|
+
console.log(chalk22.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
8851
|
+
}
|
|
8852
|
+
process.exit(1);
|
|
8853
|
+
}
|
|
8854
|
+
console.log(chalk22.yellow("Not logged in - authenticating..."));
|
|
8855
|
+
console.log();
|
|
8856
|
+
credentials = await performLogin();
|
|
8857
|
+
if (!credentials) {
|
|
8858
|
+
console.log(chalk22.red("Authentication failed"));
|
|
8859
|
+
process.exit(1);
|
|
8860
|
+
}
|
|
8861
|
+
console.log();
|
|
8862
|
+
}
|
|
8863
|
+
const threadMetadata = {};
|
|
8864
|
+
for (const param of options.param) {
|
|
8865
|
+
const eqIndex = param.indexOf("=");
|
|
8866
|
+
if (eqIndex === -1) {
|
|
8867
|
+
if (jsonMode) {
|
|
8868
|
+
console.log(JSON.stringify({ success: false, error: `Invalid param format: ${param}. Use key=value.` }));
|
|
8869
|
+
} else {
|
|
8870
|
+
console.log(chalk22.red(`Invalid param format: ${param}. Use key=value.`));
|
|
8871
|
+
}
|
|
8872
|
+
process.exit(1);
|
|
8873
|
+
}
|
|
8874
|
+
const key = param.slice(0, eqIndex);
|
|
8875
|
+
const value = param.slice(eqIndex + 1);
|
|
8876
|
+
let parsedValue = value;
|
|
8877
|
+
try {
|
|
8878
|
+
parsedValue = JSON.parse(value);
|
|
8879
|
+
} catch {
|
|
8880
|
+
parsedValue = value;
|
|
8881
|
+
}
|
|
8882
|
+
threadMetadata[key] = parsedValue;
|
|
8883
|
+
}
|
|
8884
|
+
if (options.phone) {
|
|
8885
|
+
threadMetadata["phoneNumber"] = options.phone;
|
|
8886
|
+
}
|
|
8887
|
+
const environment = options.env;
|
|
8888
|
+
if (!jsonMode) {
|
|
8889
|
+
spinner.start(`Compiling prompt for ${chalk22.cyan(agentSlug)} (${environment})`);
|
|
8890
|
+
}
|
|
8891
|
+
const doCompile = async () => {
|
|
8892
|
+
return compilePrompt({
|
|
8893
|
+
slug: agentSlug,
|
|
8894
|
+
environment,
|
|
8895
|
+
organizationId: project?.organization.id,
|
|
8896
|
+
message: options.message,
|
|
8897
|
+
channel: options.channel,
|
|
8898
|
+
threadMetadata: Object.keys(threadMetadata).length > 0 ? threadMetadata : undefined
|
|
8899
|
+
});
|
|
8900
|
+
};
|
|
8901
|
+
const { result, error } = await doCompile();
|
|
8902
|
+
if (error) {
|
|
8903
|
+
if (jsonMode) {
|
|
8904
|
+
console.log(JSON.stringify({ success: false, error }));
|
|
8905
|
+
} else {
|
|
8906
|
+
spinner.fail("Failed to compile prompt");
|
|
8907
|
+
console.log(chalk22.red("Error:"), error);
|
|
8908
|
+
}
|
|
8909
|
+
process.exit(1);
|
|
8910
|
+
}
|
|
8911
|
+
if (!result) {
|
|
8912
|
+
if (jsonMode) {
|
|
8913
|
+
console.log(JSON.stringify({ success: false, error: "No result returned" }));
|
|
8914
|
+
} else {
|
|
8915
|
+
spinner.fail("No result returned");
|
|
8916
|
+
}
|
|
8917
|
+
process.exit(1);
|
|
8918
|
+
}
|
|
8919
|
+
if (!jsonMode)
|
|
8362
8920
|
spinner.succeed("Compiled prompt");
|
|
8363
8921
|
if (jsonMode) {
|
|
8364
8922
|
console.log(JSON.stringify({
|
|
@@ -8369,27 +8927,33 @@ var compilePromptCommand = new Command19("compile-prompt").description("Compile
|
|
|
8369
8927
|
}, null, 2));
|
|
8370
8928
|
} else if (options.raw) {
|
|
8371
8929
|
console.log();
|
|
8372
|
-
console.log(
|
|
8373
|
-
console.log(
|
|
8930
|
+
console.log(chalk22.bold("Raw System Prompt"));
|
|
8931
|
+
console.log(chalk22.gray("\u2500".repeat(60)));
|
|
8374
8932
|
console.log(result.raw);
|
|
8375
|
-
console.log(
|
|
8933
|
+
console.log(chalk22.gray("\u2500".repeat(60)));
|
|
8934
|
+
if (Object.keys(threadMetadata).length > 0) {
|
|
8935
|
+
console.log(chalk22.gray("Params:"), Object.entries(threadMetadata).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(", "));
|
|
8936
|
+
}
|
|
8376
8937
|
} else {
|
|
8377
8938
|
console.log();
|
|
8378
|
-
console.log(
|
|
8379
|
-
console.log(
|
|
8939
|
+
console.log(chalk22.bold("Compiled System Prompt"));
|
|
8940
|
+
console.log(chalk22.gray("\u2500".repeat(60)));
|
|
8380
8941
|
console.log(result.compiled);
|
|
8381
|
-
console.log(
|
|
8942
|
+
console.log(chalk22.gray("\u2500".repeat(60)));
|
|
8943
|
+
if (Object.keys(threadMetadata).length > 0) {
|
|
8944
|
+
console.log(chalk22.gray("Params:"), Object.entries(threadMetadata).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(", "));
|
|
8945
|
+
}
|
|
8382
8946
|
}
|
|
8383
8947
|
});
|
|
8384
8948
|
|
|
8385
8949
|
// src/cli/commands/run-tool.ts
|
|
8386
8950
|
init_credentials();
|
|
8387
|
-
import { Command as
|
|
8388
|
-
import
|
|
8389
|
-
import
|
|
8951
|
+
import { Command as Command21 } from "commander";
|
|
8952
|
+
import chalk23 from "chalk";
|
|
8953
|
+
import ora17 from "ora";
|
|
8390
8954
|
init_convex();
|
|
8391
|
-
var runToolCommand = new
|
|
8392
|
-
const spinner =
|
|
8955
|
+
var runToolCommand = new Command21("run-tool").description("Run a tool as it would execute during a real agent conversation").argument("<tool-name>", "Tool name (e.g., entity.query, run_scrapers)").option("--agent <slug>", "Agent slug (optional \u2014 omit to run without agent context)").option("--env <environment>", "Environment: development | production | eval", "development").option("--args <json>", "Tool arguments as JSON string", "{}").option("--args-file <path>", "Read tool arguments from a JSON file").option("--json", "Output full JSON result").option("--confirm", "Skip production confirmation prompt").action(async (toolName, options) => {
|
|
8956
|
+
const spinner = ora17();
|
|
8393
8957
|
const cwd = process.cwd();
|
|
8394
8958
|
const nonInteractive = !isInteractive();
|
|
8395
8959
|
const jsonMode = !!options.json;
|
|
@@ -8398,11 +8962,11 @@ var runToolCommand = new Command20("run-tool").description("Run a tool as it wou
|
|
|
8398
8962
|
if (jsonMode) {
|
|
8399
8963
|
console.log(JSON.stringify({ success: false, error: "No struere.json found" }));
|
|
8400
8964
|
} else {
|
|
8401
|
-
console.log(
|
|
8965
|
+
console.log(chalk23.red("No struere.json found. Run struere init first."));
|
|
8402
8966
|
}
|
|
8403
8967
|
process.exit(1);
|
|
8404
8968
|
}
|
|
8405
|
-
console.log(
|
|
8969
|
+
console.log(chalk23.yellow("No struere.json found - initializing project..."));
|
|
8406
8970
|
console.log();
|
|
8407
8971
|
const success = await runInit(cwd);
|
|
8408
8972
|
if (!success) {
|
|
@@ -8415,7 +8979,7 @@ var runToolCommand = new Command20("run-tool").description("Run a tool as it wou
|
|
|
8415
8979
|
if (jsonMode) {
|
|
8416
8980
|
console.log(JSON.stringify({ success: false, error: "Failed to load struere.json" }));
|
|
8417
8981
|
} else {
|
|
8418
|
-
console.log(
|
|
8982
|
+
console.log(chalk23.red("Failed to load struere.json"));
|
|
8419
8983
|
}
|
|
8420
8984
|
process.exit(1);
|
|
8421
8985
|
}
|
|
@@ -8426,15 +8990,15 @@ var runToolCommand = new Command20("run-tool").description("Run a tool as it wou
|
|
|
8426
8990
|
if (jsonMode) {
|
|
8427
8991
|
console.log(JSON.stringify({ success: false, error: "Not authenticated. Set STRUERE_API_KEY or run struere login." }));
|
|
8428
8992
|
} else {
|
|
8429
|
-
console.log(
|
|
8993
|
+
console.log(chalk23.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
8430
8994
|
}
|
|
8431
8995
|
process.exit(1);
|
|
8432
8996
|
}
|
|
8433
|
-
console.log(
|
|
8997
|
+
console.log(chalk23.yellow("Not logged in - authenticating..."));
|
|
8434
8998
|
console.log();
|
|
8435
8999
|
credentials = await performLogin();
|
|
8436
9000
|
if (!credentials) {
|
|
8437
|
-
console.log(
|
|
9001
|
+
console.log(chalk23.red("Authentication failed"));
|
|
8438
9002
|
process.exit(1);
|
|
8439
9003
|
}
|
|
8440
9004
|
console.log();
|
|
@@ -8452,26 +9016,26 @@ var runToolCommand = new Command20("run-tool").description("Run a tool as it wou
|
|
|
8452
9016
|
if (jsonMode) {
|
|
8453
9017
|
console.log(JSON.stringify({ success: false, error: `Invalid JSON: ${err instanceof Error ? err.message : String(err)}` }));
|
|
8454
9018
|
} else {
|
|
8455
|
-
console.log(
|
|
9019
|
+
console.log(chalk23.red(`Invalid JSON: ${err instanceof Error ? err.message : String(err)}`));
|
|
8456
9020
|
}
|
|
8457
9021
|
process.exit(1);
|
|
8458
9022
|
}
|
|
8459
9023
|
const environment = options.env;
|
|
8460
9024
|
if (!options.agent && !toolName.includes(".") && !jsonMode) {
|
|
8461
|
-
console.log(
|
|
9025
|
+
console.log(chalk23.dim(`Tip: Running without agent context. Use --agent <slug> to run with a specific agent.`));
|
|
8462
9026
|
}
|
|
8463
9027
|
if (environment === "production" && !options.confirm && !nonInteractive) {
|
|
8464
9028
|
const readline = await import("readline");
|
|
8465
9029
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
8466
9030
|
await new Promise((resolve) => {
|
|
8467
|
-
rl.question(
|
|
9031
|
+
rl.question(chalk23.yellow(`WARNING: Running tool against PRODUCTION environment.
|
|
8468
9032
|
This will execute real operations with real data.
|
|
8469
9033
|
Press Enter to continue or Ctrl+C to cancel: `), resolve);
|
|
8470
9034
|
});
|
|
8471
9035
|
rl.close();
|
|
8472
9036
|
}
|
|
8473
9037
|
if (!jsonMode) {
|
|
8474
|
-
spinner.start(`Running ${
|
|
9038
|
+
spinner.start(`Running ${chalk23.cyan(toolName)}${options.agent ? ` on ${chalk23.cyan(options.agent)}` : ""} (${environment})`);
|
|
8475
9039
|
}
|
|
8476
9040
|
const doRunTool = async () => {
|
|
8477
9041
|
return runTool({
|
|
@@ -8488,7 +9052,7 @@ var runToolCommand = new Command20("run-tool").description("Run a tool as it wou
|
|
|
8488
9052
|
console.log(JSON.stringify({ success: false, error }));
|
|
8489
9053
|
} else {
|
|
8490
9054
|
spinner.fail("Failed to run tool");
|
|
8491
|
-
console.log(
|
|
9055
|
+
console.log(chalk23.red("Error:"), error);
|
|
8492
9056
|
}
|
|
8493
9057
|
process.exit(1);
|
|
8494
9058
|
}
|
|
@@ -8504,45 +9068,51 @@ var runToolCommand = new Command20("run-tool").description("Run a tool as it wou
|
|
|
8504
9068
|
if (jsonMode) {
|
|
8505
9069
|
console.log(JSON.stringify({ success: false, error: `${result.errorType}: ${result.message}`, result }));
|
|
8506
9070
|
} else {
|
|
8507
|
-
spinner.fail(
|
|
9071
|
+
spinner.fail(chalk23.red(`${result.errorType}: ${result.message}`));
|
|
8508
9072
|
}
|
|
8509
9073
|
process.exit(1);
|
|
8510
9074
|
}
|
|
8511
9075
|
if (!jsonMode) {
|
|
8512
|
-
spinner.succeed(`Ran ${
|
|
9076
|
+
spinner.succeed(`Ran ${chalk23.cyan(toolName)}${result.agent ? ` on ${chalk23.cyan(result.agent.slug)}` : ""} (${result.environment}) in ${result.durationMs}ms`);
|
|
8513
9077
|
}
|
|
8514
9078
|
if (jsonMode) {
|
|
8515
9079
|
console.log(JSON.stringify(result, null, 2));
|
|
8516
9080
|
} else {
|
|
8517
9081
|
console.log();
|
|
8518
|
-
console.log(
|
|
9082
|
+
console.log(chalk23.dim("\u2500".repeat(50)));
|
|
8519
9083
|
console.log(JSON.stringify(result.result, null, 2));
|
|
8520
|
-
console.log(
|
|
9084
|
+
console.log(chalk23.dim("\u2500".repeat(50)));
|
|
8521
9085
|
console.log();
|
|
8522
|
-
console.log(
|
|
9086
|
+
console.log(chalk23.dim(`Identity: ${result.identity.actorType} (${result.identity.identityMode} mode)`));
|
|
8523
9087
|
}
|
|
8524
9088
|
});
|
|
8525
9089
|
|
|
8526
9090
|
// src/cli/commands/chat.ts
|
|
8527
9091
|
init_credentials();
|
|
8528
|
-
import { Command as
|
|
8529
|
-
import
|
|
8530
|
-
import
|
|
9092
|
+
import { Command as Command22 } from "commander";
|
|
9093
|
+
import chalk24 from "chalk";
|
|
9094
|
+
import ora18 from "ora";
|
|
8531
9095
|
import readline from "readline";
|
|
8532
9096
|
init_convex();
|
|
8533
9097
|
function printExecutionMeta(meta) {
|
|
8534
|
-
console.log(
|
|
9098
|
+
console.log(chalk24.dim(`Model: ${meta.model} | Duration: ${meta.durationMs}ms`));
|
|
8535
9099
|
if (meta.toolCallSummary.length > 0) {
|
|
8536
|
-
console.log(
|
|
9100
|
+
console.log(chalk24.dim("Tool calls:"));
|
|
8537
9101
|
for (const tc of meta.toolCallSummary) {
|
|
8538
|
-
const status = tc.status === "success" ?
|
|
8539
|
-
const err = tc.errorMessage ?
|
|
8540
|
-
console.log(
|
|
9102
|
+
const status = tc.status === "success" ? chalk24.green("ok") : chalk24.red(tc.status);
|
|
9103
|
+
const err = tc.errorMessage ? chalk24.red(` \u2014 ${tc.errorMessage}`) : "";
|
|
9104
|
+
console.log(chalk24.dim(` ${tc.name} ${status} ${tc.durationMs}ms${err}`));
|
|
8541
9105
|
}
|
|
8542
9106
|
}
|
|
9107
|
+
if (meta.permissionDenialCount > 0) {
|
|
9108
|
+
console.log(chalk24.dim(`Permission denials: ${chalk24.red(String(meta.permissionDenialCount))}`));
|
|
9109
|
+
}
|
|
9110
|
+
if (meta.errorCount > 0) {
|
|
9111
|
+
console.log(chalk24.dim(`Tool errors: ${chalk24.yellow(String(meta.errorCount))}`));
|
|
9112
|
+
}
|
|
8543
9113
|
}
|
|
8544
|
-
var chatCommand = new
|
|
8545
|
-
const spinner =
|
|
9114
|
+
var chatCommand = new Command22("chat").description("Chat with an agent or via a router").argument("<slug>", "Agent slug (or router slug when --router is used)").option("--env <environment>", "Environment: development | production | eval", "development").option("--thread <id>", "Continue an existing thread").option("--message <msg>", "Single message mode (send and exit)").option("--json", "Output JSON").option("--channel <channel>", "Channel identifier", "api").option("-v, --verbose", "Show execution metadata (model, duration, tool calls)").option("--confirm", "Skip production warning prompt").option("--router", "Chat via a router instead of directly with an agent").option("--phone <number>", "Sender phone number for routing rules").option("--follow", "Continue in interactive mode after sending a message").action(async (slug, options) => {
|
|
9115
|
+
const spinner = ora18();
|
|
8546
9116
|
const cwd = process.cwd();
|
|
8547
9117
|
const nonInteractive = !isInteractive();
|
|
8548
9118
|
const jsonMode = !!options.json;
|
|
@@ -8551,11 +9121,11 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8551
9121
|
if (jsonMode) {
|
|
8552
9122
|
console.log(JSON.stringify({ success: false, error: "No struere.json found" }));
|
|
8553
9123
|
} else {
|
|
8554
|
-
console.log(
|
|
9124
|
+
console.log(chalk24.red("No struere.json found. Run struere init first."));
|
|
8555
9125
|
}
|
|
8556
9126
|
process.exit(1);
|
|
8557
9127
|
}
|
|
8558
|
-
console.log(
|
|
9128
|
+
console.log(chalk24.yellow("No struere.json found - initializing project..."));
|
|
8559
9129
|
console.log();
|
|
8560
9130
|
const success = await runInit(cwd);
|
|
8561
9131
|
if (!success) {
|
|
@@ -8568,7 +9138,7 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8568
9138
|
if (jsonMode) {
|
|
8569
9139
|
console.log(JSON.stringify({ success: false, error: "Failed to load struere.json" }));
|
|
8570
9140
|
} else {
|
|
8571
|
-
console.log(
|
|
9141
|
+
console.log(chalk24.red("Failed to load struere.json"));
|
|
8572
9142
|
}
|
|
8573
9143
|
process.exit(1);
|
|
8574
9144
|
}
|
|
@@ -8579,15 +9149,15 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8579
9149
|
if (jsonMode) {
|
|
8580
9150
|
console.log(JSON.stringify({ success: false, error: "Not authenticated. Set STRUERE_API_KEY or run struere login." }));
|
|
8581
9151
|
} else {
|
|
8582
|
-
console.log(
|
|
9152
|
+
console.log(chalk24.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
8583
9153
|
}
|
|
8584
9154
|
process.exit(1);
|
|
8585
9155
|
}
|
|
8586
|
-
console.log(
|
|
9156
|
+
console.log(chalk24.yellow("Not logged in - authenticating..."));
|
|
8587
9157
|
console.log();
|
|
8588
9158
|
credentials = await performLogin();
|
|
8589
9159
|
if (!credentials) {
|
|
8590
|
-
console.log(
|
|
9160
|
+
console.log(chalk24.red("Authentication failed"));
|
|
8591
9161
|
process.exit(1);
|
|
8592
9162
|
}
|
|
8593
9163
|
console.log();
|
|
@@ -8598,14 +9168,14 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8598
9168
|
if (jsonMode) {
|
|
8599
9169
|
console.log(JSON.stringify({ success: false, error: "--phone is required when using --router" }));
|
|
8600
9170
|
} else {
|
|
8601
|
-
console.log(
|
|
9171
|
+
console.log(chalk24.red("--phone is required when using --router"));
|
|
8602
9172
|
}
|
|
8603
9173
|
process.exit(1);
|
|
8604
9174
|
}
|
|
8605
9175
|
if (environment === "production" && !nonInteractive && !options.confirm) {
|
|
8606
9176
|
const confirmRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
8607
9177
|
await new Promise((resolve) => {
|
|
8608
|
-
confirmRl.question(
|
|
9178
|
+
confirmRl.question(chalk24.yellow(`WARNING: Chatting with agent in PRODUCTION environment.
|
|
8609
9179
|
Press Enter to continue or Ctrl+C to cancel: `), resolve);
|
|
8610
9180
|
});
|
|
8611
9181
|
confirmRl.close();
|
|
@@ -8633,6 +9203,7 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8633
9203
|
signal
|
|
8634
9204
|
});
|
|
8635
9205
|
};
|
|
9206
|
+
let threadId = options.thread;
|
|
8636
9207
|
if (options.message) {
|
|
8637
9208
|
if (!jsonMode) {
|
|
8638
9209
|
spinner.start("Sending message...");
|
|
@@ -8647,7 +9218,7 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8647
9218
|
if (jsonMode) {
|
|
8648
9219
|
console.log(JSON.stringify({ success: false, error: "Authentication failed" }));
|
|
8649
9220
|
} else {
|
|
8650
|
-
console.log(
|
|
9221
|
+
console.log(chalk24.red("Authentication failed"));
|
|
8651
9222
|
}
|
|
8652
9223
|
process.exit(1);
|
|
8653
9224
|
}
|
|
@@ -8662,7 +9233,7 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8662
9233
|
console.log(JSON.stringify({ success: false, error }));
|
|
8663
9234
|
} else {
|
|
8664
9235
|
spinner.fail("Failed to send message");
|
|
8665
|
-
console.log(
|
|
9236
|
+
console.log(chalk24.red("Error:"), error);
|
|
8666
9237
|
}
|
|
8667
9238
|
process.exit(1);
|
|
8668
9239
|
}
|
|
@@ -8689,36 +9260,39 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8689
9260
|
console.log("\u2500".repeat(60));
|
|
8690
9261
|
console.log();
|
|
8691
9262
|
if (routerResult.routedToAgent) {
|
|
8692
|
-
console.log(
|
|
9263
|
+
console.log(chalk24.green(`${routerResult.routedToAgent}`) + chalk24.dim(` (${routerResult.routedToAgentSlug})`) + chalk24.magenta(" \u2190 routed") + chalk24.dim(":"));
|
|
8693
9264
|
} else {
|
|
8694
|
-
console.log(
|
|
9265
|
+
console.log(chalk24.green("Agent:"));
|
|
8695
9266
|
}
|
|
8696
9267
|
console.log(result.message);
|
|
8697
9268
|
console.log();
|
|
8698
9269
|
if (options.verbose) {
|
|
8699
|
-
console.log(
|
|
8700
|
-
console.log(
|
|
9270
|
+
console.log(chalk24.dim(`Thread: ${result.threadId}`));
|
|
9271
|
+
console.log(chalk24.dim(`Tokens: ${result.usage.inputTokens} in / ${result.usage.outputTokens} out (${result.usage.totalTokens} total)`));
|
|
8701
9272
|
if (result._executionMeta) {
|
|
8702
9273
|
printExecutionMeta(result._executionMeta);
|
|
8703
9274
|
}
|
|
8704
9275
|
} else {
|
|
8705
|
-
console.log(
|
|
9276
|
+
console.log(chalk24.dim(`Thread: ${result.threadId} | Tokens: ${result.usage.totalTokens}`));
|
|
8706
9277
|
}
|
|
8707
9278
|
console.log();
|
|
8708
9279
|
console.log("\u2500".repeat(60));
|
|
8709
9280
|
}
|
|
8710
|
-
|
|
9281
|
+
if (options.follow && isInteractive()) {
|
|
9282
|
+
threadId = result.threadId;
|
|
9283
|
+
} else {
|
|
9284
|
+
return;
|
|
9285
|
+
}
|
|
8711
9286
|
}
|
|
8712
|
-
const headerLabel = isRouterMode ? `router ${
|
|
8713
|
-
console.log(
|
|
8714
|
-
console.log(
|
|
9287
|
+
const headerLabel = isRouterMode ? `router ${chalk24.cyan(slug)}` : chalk24.cyan(slug);
|
|
9288
|
+
console.log(chalk24.bold(`Chat with ${headerLabel} (${environment})`));
|
|
9289
|
+
console.log(chalk24.dim("Type 'exit' to quit"));
|
|
8715
9290
|
console.log();
|
|
8716
|
-
let threadId = options.thread;
|
|
8717
9291
|
let processing = false;
|
|
8718
9292
|
let generation = 0;
|
|
8719
9293
|
let currentAbort = null;
|
|
8720
9294
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
8721
|
-
rl.setPrompt(
|
|
9295
|
+
rl.setPrompt(chalk24.cyan("You: "));
|
|
8722
9296
|
rl.prompt();
|
|
8723
9297
|
rl.on("SIGINT", () => {
|
|
8724
9298
|
if (processing) {
|
|
@@ -8730,7 +9304,7 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8730
9304
|
spinner.stop();
|
|
8731
9305
|
processing = false;
|
|
8732
9306
|
console.log();
|
|
8733
|
-
console.log(
|
|
9307
|
+
console.log(chalk24.yellow("Cancelled"));
|
|
8734
9308
|
console.log();
|
|
8735
9309
|
rl.resume();
|
|
8736
9310
|
rl.prompt();
|
|
@@ -8768,7 +9342,7 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8768
9342
|
if (thisGeneration !== generation)
|
|
8769
9343
|
return;
|
|
8770
9344
|
if (!credentials) {
|
|
8771
|
-
console.log(
|
|
9345
|
+
console.log(chalk24.red("Authentication failed"));
|
|
8772
9346
|
rl.close();
|
|
8773
9347
|
return;
|
|
8774
9348
|
}
|
|
@@ -8782,7 +9356,7 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8782
9356
|
}
|
|
8783
9357
|
if (error) {
|
|
8784
9358
|
spinner.fail("");
|
|
8785
|
-
console.log(
|
|
9359
|
+
console.log(chalk24.red("Error:"), error);
|
|
8786
9360
|
processing = false;
|
|
8787
9361
|
currentAbort = null;
|
|
8788
9362
|
rl.resume();
|
|
@@ -8802,20 +9376,20 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8802
9376
|
const interactiveRouterResult = result;
|
|
8803
9377
|
console.log();
|
|
8804
9378
|
if (interactiveRouterResult.routedToAgent) {
|
|
8805
|
-
console.log(
|
|
9379
|
+
console.log(chalk24.green(`${interactiveRouterResult.routedToAgent}`) + chalk24.dim(` (${interactiveRouterResult.routedToAgentSlug})`) + chalk24.magenta(" \u2190 routed") + chalk24.dim(":"));
|
|
8806
9380
|
} else {
|
|
8807
|
-
console.log(
|
|
9381
|
+
console.log(chalk24.green("Agent:"));
|
|
8808
9382
|
}
|
|
8809
9383
|
console.log(result.message);
|
|
8810
9384
|
console.log();
|
|
8811
9385
|
if (options.verbose) {
|
|
8812
|
-
console.log(
|
|
8813
|
-
console.log(
|
|
9386
|
+
console.log(chalk24.dim(`Thread: ${result.threadId}`));
|
|
9387
|
+
console.log(chalk24.dim(`Tokens: ${result.usage.inputTokens} in / ${result.usage.outputTokens} out (${result.usage.totalTokens} total)`));
|
|
8814
9388
|
if (result._executionMeta) {
|
|
8815
9389
|
printExecutionMeta(result._executionMeta);
|
|
8816
9390
|
}
|
|
8817
9391
|
} else {
|
|
8818
|
-
console.log(
|
|
9392
|
+
console.log(chalk24.dim(`Thread: ${result.threadId} | Tokens: ${result.usage.totalTokens}`));
|
|
8819
9393
|
}
|
|
8820
9394
|
console.log();
|
|
8821
9395
|
processing = false;
|
|
@@ -8825,24 +9399,24 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8825
9399
|
});
|
|
8826
9400
|
rl.on("close", () => {
|
|
8827
9401
|
console.log();
|
|
8828
|
-
console.log(
|
|
9402
|
+
console.log(chalk24.dim("Goodbye!"));
|
|
8829
9403
|
process.exit(0);
|
|
8830
9404
|
});
|
|
8831
9405
|
});
|
|
8832
9406
|
|
|
8833
9407
|
// src/cli/commands/whatsapp.ts
|
|
8834
9408
|
init_credentials();
|
|
8835
|
-
import { Command as
|
|
8836
|
-
import
|
|
8837
|
-
async function
|
|
9409
|
+
import { Command as Command23 } from "commander";
|
|
9410
|
+
import chalk25 from "chalk";
|
|
9411
|
+
async function ensureAuth7() {
|
|
8838
9412
|
const cwd = process.cwd();
|
|
8839
9413
|
const nonInteractive = !isInteractive();
|
|
8840
9414
|
if (!hasProject(cwd)) {
|
|
8841
9415
|
if (nonInteractive) {
|
|
8842
|
-
console.error(
|
|
9416
|
+
console.error(chalk25.red("No struere.json found. Run struere init first."));
|
|
8843
9417
|
process.exit(1);
|
|
8844
9418
|
}
|
|
8845
|
-
console.log(
|
|
9419
|
+
console.log(chalk25.yellow("No struere.json found - initializing project..."));
|
|
8846
9420
|
console.log();
|
|
8847
9421
|
const success = await runInit(cwd);
|
|
8848
9422
|
if (!success) {
|
|
@@ -8854,14 +9428,14 @@ async function ensureAuth6() {
|
|
|
8854
9428
|
const apiKey = getApiKey();
|
|
8855
9429
|
if (!credentials && !apiKey) {
|
|
8856
9430
|
if (nonInteractive) {
|
|
8857
|
-
console.error(
|
|
9431
|
+
console.error(chalk25.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
8858
9432
|
process.exit(1);
|
|
8859
9433
|
}
|
|
8860
|
-
console.log(
|
|
9434
|
+
console.log(chalk25.yellow("Not logged in - authenticating..."));
|
|
8861
9435
|
console.log();
|
|
8862
9436
|
credentials = await performLogin();
|
|
8863
9437
|
if (!credentials) {
|
|
8864
|
-
console.log(
|
|
9438
|
+
console.log(chalk25.red("Authentication failed"));
|
|
8865
9439
|
process.exit(1);
|
|
8866
9440
|
}
|
|
8867
9441
|
console.log();
|
|
@@ -8904,13 +9478,13 @@ function connectionLabel(conn) {
|
|
|
8904
9478
|
function statusColor5(status) {
|
|
8905
9479
|
switch (status) {
|
|
8906
9480
|
case "connected":
|
|
8907
|
-
return
|
|
9481
|
+
return chalk25.green(status);
|
|
8908
9482
|
case "pending_setup":
|
|
8909
|
-
return
|
|
9483
|
+
return chalk25.yellow("pending");
|
|
8910
9484
|
case "disconnected":
|
|
8911
|
-
return
|
|
9485
|
+
return chalk25.red(status);
|
|
8912
9486
|
default:
|
|
8913
|
-
return
|
|
9487
|
+
return chalk25.gray(status);
|
|
8914
9488
|
}
|
|
8915
9489
|
}
|
|
8916
9490
|
function assignmentLabel(conn) {
|
|
@@ -8922,11 +9496,11 @@ function assignmentLabel(conn) {
|
|
|
8922
9496
|
return `Router: ${conn.routerId.slice(-8)}`;
|
|
8923
9497
|
if (conn.agentId)
|
|
8924
9498
|
return `Agent: ${conn.agentId.slice(-8)}`;
|
|
8925
|
-
return
|
|
9499
|
+
return chalk25.gray("none");
|
|
8926
9500
|
}
|
|
8927
|
-
var whatsappCommand = new
|
|
9501
|
+
var whatsappCommand = new Command23("whatsapp").description("Manage WhatsApp connections and routing");
|
|
8928
9502
|
whatsappCommand.command("list").description("List WhatsApp connections with routing assignments").option("--env <environment>", "Environment (development|production)", "production").option("--json", "Output raw JSON").action(async (opts) => {
|
|
8929
|
-
await
|
|
9503
|
+
await ensureAuth7();
|
|
8930
9504
|
const env = opts.env;
|
|
8931
9505
|
const out = createOutput();
|
|
8932
9506
|
out.start("Fetching WhatsApp connections");
|
|
@@ -8944,7 +9518,7 @@ whatsappCommand.command("list").description("List WhatsApp connections with rout
|
|
|
8944
9518
|
}
|
|
8945
9519
|
console.log();
|
|
8946
9520
|
if (connections.length === 0) {
|
|
8947
|
-
console.log(
|
|
9521
|
+
console.log(chalk25.gray(" No WhatsApp connections found"));
|
|
8948
9522
|
console.log();
|
|
8949
9523
|
return;
|
|
8950
9524
|
}
|
|
@@ -8955,8 +9529,8 @@ whatsappCommand.command("list").description("List WhatsApp connections with rout
|
|
|
8955
9529
|
{ key: "assignment", label: "Assignment", width: 30 },
|
|
8956
9530
|
{ key: "id", label: "ID", width: 16 }
|
|
8957
9531
|
], connections.map((c) => ({
|
|
8958
|
-
label: c.label ||
|
|
8959
|
-
phone: c.phoneNumber ? `+${c.phoneNumber}` :
|
|
9532
|
+
label: c.label || chalk25.gray("-"),
|
|
9533
|
+
phone: c.phoneNumber ? `+${c.phoneNumber}` : chalk25.gray("-"),
|
|
8960
9534
|
status: statusColor5(c.status),
|
|
8961
9535
|
assignment: assignmentLabel(c),
|
|
8962
9536
|
id: c._id.slice(-12)
|
|
@@ -8964,7 +9538,7 @@ whatsappCommand.command("list").description("List WhatsApp connections with rout
|
|
|
8964
9538
|
console.log();
|
|
8965
9539
|
});
|
|
8966
9540
|
whatsappCommand.command("set-router <connection> <router-slug>").description("Assign a router to a WhatsApp connection").option("--env <environment>", "Environment (development|production)", "production").action(async (connection, routerSlug, opts) => {
|
|
8967
|
-
await
|
|
9541
|
+
await ensureAuth7();
|
|
8968
9542
|
const env = opts.env;
|
|
8969
9543
|
const out = createOutput();
|
|
8970
9544
|
const conn = await resolveConnection(env, connection, out);
|
|
@@ -9000,7 +9574,7 @@ whatsappCommand.command("set-router <connection> <router-slug>").description("As
|
|
|
9000
9574
|
console.log();
|
|
9001
9575
|
});
|
|
9002
9576
|
whatsappCommand.command("set-agent <connection> <agent-slug>").description("Assign an agent directly to a WhatsApp connection").option("--env <environment>", "Environment (development|production)", "production").action(async (connection, agentSlug, opts) => {
|
|
9003
|
-
await
|
|
9577
|
+
await ensureAuth7();
|
|
9004
9578
|
const env = opts.env;
|
|
9005
9579
|
const out = createOutput();
|
|
9006
9580
|
const conn = await resolveConnection(env, connection, out);
|
|
@@ -9026,10 +9600,927 @@ whatsappCommand.command("set-agent <connection> <agent-slug>").description("Assi
|
|
|
9026
9600
|
out.succeed(`Connection ${connectionLabel(conn)} now assigned to agent ${agent.name} (${agentSlug})`);
|
|
9027
9601
|
console.log();
|
|
9028
9602
|
});
|
|
9603
|
+
|
|
9604
|
+
// src/cli/commands/diff.ts
|
|
9605
|
+
init_credentials();
|
|
9606
|
+
import { Command as Command24 } from "commander";
|
|
9607
|
+
import chalk27 from "chalk";
|
|
9608
|
+
import ora19 from "ora";
|
|
9609
|
+
init_convex();
|
|
9610
|
+
|
|
9611
|
+
// src/cli/utils/diff.ts
|
|
9612
|
+
import chalk26 from "chalk";
|
|
9613
|
+
function computeLCS(a, b) {
|
|
9614
|
+
const m = a.length;
|
|
9615
|
+
const n = b.length;
|
|
9616
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
9617
|
+
for (let i2 = 1;i2 <= m; i2++) {
|
|
9618
|
+
for (let j2 = 1;j2 <= n; j2++) {
|
|
9619
|
+
if (a[i2 - 1] === b[j2 - 1]) {
|
|
9620
|
+
dp[i2][j2] = dp[i2 - 1][j2 - 1] + 1;
|
|
9621
|
+
} else {
|
|
9622
|
+
dp[i2][j2] = Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
|
|
9623
|
+
}
|
|
9624
|
+
}
|
|
9625
|
+
}
|
|
9626
|
+
const inLCS = [
|
|
9627
|
+
Array(m).fill(false),
|
|
9628
|
+
Array(n).fill(false)
|
|
9629
|
+
];
|
|
9630
|
+
let i = m;
|
|
9631
|
+
let j = n;
|
|
9632
|
+
while (i > 0 && j > 0) {
|
|
9633
|
+
if (a[i - 1] === b[j - 1]) {
|
|
9634
|
+
inLCS[0][i - 1] = true;
|
|
9635
|
+
inLCS[1][j - 1] = true;
|
|
9636
|
+
i--;
|
|
9637
|
+
j--;
|
|
9638
|
+
} else if (dp[i - 1][j] >= dp[i][j - 1]) {
|
|
9639
|
+
i--;
|
|
9640
|
+
} else {
|
|
9641
|
+
j--;
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9644
|
+
return inLCS;
|
|
9645
|
+
}
|
|
9646
|
+
function diffLines(oldText, newText, contextLines = 3) {
|
|
9647
|
+
const oldLines = oldText.split(`
|
|
9648
|
+
`);
|
|
9649
|
+
const newLines = newText.split(`
|
|
9650
|
+
`);
|
|
9651
|
+
const lcs = computeLCS(oldLines, newLines);
|
|
9652
|
+
const oldInLCS = lcs[0];
|
|
9653
|
+
const newInLCS = lcs[1];
|
|
9654
|
+
const rawDiff = [];
|
|
9655
|
+
let oi = 0;
|
|
9656
|
+
let ni = 0;
|
|
9657
|
+
while (oi < oldLines.length || ni < newLines.length) {
|
|
9658
|
+
if (oi < oldLines.length && ni < newLines.length && oldInLCS[oi] && newInLCS[ni]) {
|
|
9659
|
+
rawDiff.push({ type: "context", content: oldLines[oi] });
|
|
9660
|
+
oi++;
|
|
9661
|
+
ni++;
|
|
9662
|
+
} else if (oi < oldLines.length && !oldInLCS[oi]) {
|
|
9663
|
+
rawDiff.push({ type: "removed", content: oldLines[oi] });
|
|
9664
|
+
oi++;
|
|
9665
|
+
} else if (ni < newLines.length && !newInLCS[ni]) {
|
|
9666
|
+
rawDiff.push({ type: "added", content: newLines[ni] });
|
|
9667
|
+
ni++;
|
|
9668
|
+
} else {
|
|
9669
|
+
if (oi < oldLines.length) {
|
|
9670
|
+
rawDiff.push({ type: "context", content: oldLines[oi] });
|
|
9671
|
+
oi++;
|
|
9672
|
+
}
|
|
9673
|
+
if (ni < newLines.length) {
|
|
9674
|
+
rawDiff.push({ type: "context", content: newLines[ni] });
|
|
9675
|
+
ni++;
|
|
9676
|
+
}
|
|
9677
|
+
}
|
|
9678
|
+
}
|
|
9679
|
+
const changeIndices = new Set;
|
|
9680
|
+
for (let idx = 0;idx < rawDiff.length; idx++) {
|
|
9681
|
+
if (rawDiff[idx].type !== "context") {
|
|
9682
|
+
for (let c = Math.max(0, idx - contextLines);c <= Math.min(rawDiff.length - 1, idx + contextLines); c++) {
|
|
9683
|
+
changeIndices.add(c);
|
|
9684
|
+
}
|
|
9685
|
+
}
|
|
9686
|
+
}
|
|
9687
|
+
if (changeIndices.size === 0) {
|
|
9688
|
+
return [];
|
|
9689
|
+
}
|
|
9690
|
+
const result = [];
|
|
9691
|
+
let lastIncluded = -1;
|
|
9692
|
+
const sorted = Array.from(changeIndices).sort((a, b) => a - b);
|
|
9693
|
+
for (const idx of sorted) {
|
|
9694
|
+
if (lastIncluded >= 0 && idx > lastIncluded + 1) {
|
|
9695
|
+
result.push({ type: "context", content: "..." });
|
|
9696
|
+
}
|
|
9697
|
+
result.push(rawDiff[idx]);
|
|
9698
|
+
lastIncluded = idx;
|
|
9699
|
+
}
|
|
9700
|
+
return result;
|
|
9701
|
+
}
|
|
9702
|
+
function diffObjects(a, b) {
|
|
9703
|
+
const oldText = JSON.stringify(a, null, 2) ?? "";
|
|
9704
|
+
const newText = JSON.stringify(b, null, 2) ?? "";
|
|
9705
|
+
return diffLines(oldText, newText);
|
|
9706
|
+
}
|
|
9707
|
+
function renderDiff(lines) {
|
|
9708
|
+
for (const line of lines) {
|
|
9709
|
+
switch (line.type) {
|
|
9710
|
+
case "removed":
|
|
9711
|
+
console.log(chalk26.red("- " + line.content));
|
|
9712
|
+
break;
|
|
9713
|
+
case "added":
|
|
9714
|
+
console.log(chalk26.green("+ " + line.content));
|
|
9715
|
+
break;
|
|
9716
|
+
case "context":
|
|
9717
|
+
console.log(chalk26.gray(" " + line.content));
|
|
9718
|
+
break;
|
|
9719
|
+
}
|
|
9720
|
+
}
|
|
9721
|
+
}
|
|
9722
|
+
function hasDiff(a, b) {
|
|
9723
|
+
return JSON.stringify(a) !== JSON.stringify(b);
|
|
9724
|
+
}
|
|
9725
|
+
|
|
9726
|
+
// src/cli/commands/diff.ts
|
|
9727
|
+
function buildLocalAgentComparable(agent) {
|
|
9728
|
+
return {
|
|
9729
|
+
systemPrompt: typeof agent.systemPrompt === "function" ? "" : agent.systemPrompt,
|
|
9730
|
+
model: agent.model || { model: "openai/gpt-5-mini" },
|
|
9731
|
+
tools: agent.tools || []
|
|
9732
|
+
};
|
|
9733
|
+
}
|
|
9734
|
+
function buildRemoteAgentComparable(agent) {
|
|
9735
|
+
return {
|
|
9736
|
+
systemPrompt: agent.systemPrompt || "",
|
|
9737
|
+
model: agent.model,
|
|
9738
|
+
tools: agent.tools || []
|
|
9739
|
+
};
|
|
9740
|
+
}
|
|
9741
|
+
function diffAgent(local, remote) {
|
|
9742
|
+
const changes = [];
|
|
9743
|
+
const lc = buildLocalAgentComparable(local);
|
|
9744
|
+
const rc = buildRemoteAgentComparable(remote);
|
|
9745
|
+
if (lc.systemPrompt !== rc.systemPrompt) {
|
|
9746
|
+
changes.push({ field: "systemPrompt", old: rc.systemPrompt, new: lc.systemPrompt });
|
|
9747
|
+
}
|
|
9748
|
+
if (hasDiff(lc.model, rc.model)) {
|
|
9749
|
+
changes.push({ field: "model", old: rc.model, new: lc.model });
|
|
9750
|
+
}
|
|
9751
|
+
if (hasDiff(lc.tools, rc.tools)) {
|
|
9752
|
+
changes.push({ field: "tools", old: rc.tools, new: lc.tools });
|
|
9753
|
+
}
|
|
9754
|
+
return changes;
|
|
9755
|
+
}
|
|
9756
|
+
function diffEntityType(local, remote) {
|
|
9757
|
+
const changes = [];
|
|
9758
|
+
if (hasDiff(local.schema, remote.schema)) {
|
|
9759
|
+
changes.push({ field: "schema", old: remote.schema, new: local.schema });
|
|
9760
|
+
}
|
|
9761
|
+
if (hasDiff(local.searchFields || [], remote.searchFields || [])) {
|
|
9762
|
+
changes.push({ field: "searchFields", old: remote.searchFields || [], new: local.searchFields || [] });
|
|
9763
|
+
}
|
|
9764
|
+
if (hasDiff(local.displayConfig || {}, remote.displayConfig || {})) {
|
|
9765
|
+
changes.push({ field: "displayConfig", old: remote.displayConfig || {}, new: local.displayConfig || {} });
|
|
9766
|
+
}
|
|
9767
|
+
return changes;
|
|
9768
|
+
}
|
|
9769
|
+
function diffRole(local, remote) {
|
|
9770
|
+
const changes = [];
|
|
9771
|
+
if ((local.description || "") !== (remote.description || "")) {
|
|
9772
|
+
changes.push({ field: "description", old: remote.description || "", new: local.description || "" });
|
|
9773
|
+
}
|
|
9774
|
+
if (hasDiff(local.policies, remote.policies)) {
|
|
9775
|
+
changes.push({ field: "policies", old: remote.policies, new: local.policies });
|
|
9776
|
+
}
|
|
9777
|
+
if (hasDiff(local.scopeRules || [], remote.scopeRules || [])) {
|
|
9778
|
+
changes.push({ field: "scopeRules", old: remote.scopeRules || [], new: local.scopeRules || [] });
|
|
9779
|
+
}
|
|
9780
|
+
if (hasDiff(local.fieldMasks || [], remote.fieldMasks || [])) {
|
|
9781
|
+
changes.push({ field: "fieldMasks", old: remote.fieldMasks || [], new: local.fieldMasks || [] });
|
|
9782
|
+
}
|
|
9783
|
+
return changes;
|
|
9784
|
+
}
|
|
9785
|
+
function buildLocalTriggerOn(trigger) {
|
|
9786
|
+
return trigger.on;
|
|
9787
|
+
}
|
|
9788
|
+
function buildRemoteTriggerOn(trigger) {
|
|
9789
|
+
if (trigger.cronSchedule) {
|
|
9790
|
+
const on2 = { schedule: trigger.cronSchedule };
|
|
9791
|
+
if (trigger.cronTimezone)
|
|
9792
|
+
on2.timezone = trigger.cronTimezone;
|
|
9793
|
+
return on2;
|
|
9794
|
+
}
|
|
9795
|
+
const on = {};
|
|
9796
|
+
if (trigger.entityType)
|
|
9797
|
+
on.entityType = trigger.entityType;
|
|
9798
|
+
if (trigger.action)
|
|
9799
|
+
on.action = trigger.action;
|
|
9800
|
+
if (trigger.condition)
|
|
9801
|
+
on.condition = trigger.condition;
|
|
9802
|
+
return on;
|
|
9803
|
+
}
|
|
9804
|
+
function diffTrigger(local, remote) {
|
|
9805
|
+
const changes = [];
|
|
9806
|
+
const localOn = buildLocalTriggerOn(local);
|
|
9807
|
+
const remoteOn = buildRemoteTriggerOn(remote);
|
|
9808
|
+
if (hasDiff(localOn, remoteOn)) {
|
|
9809
|
+
changes.push({ field: "on", old: remoteOn, new: localOn });
|
|
9810
|
+
}
|
|
9811
|
+
if (hasDiff(local.actions, remote.actions)) {
|
|
9812
|
+
changes.push({ field: "actions", old: remote.actions, new: local.actions });
|
|
9813
|
+
}
|
|
9814
|
+
return changes;
|
|
9815
|
+
}
|
|
9816
|
+
function diffRouter(local, remote) {
|
|
9817
|
+
const changes = [];
|
|
9818
|
+
if (local.mode !== remote.mode) {
|
|
9819
|
+
changes.push({ field: "mode", old: remote.mode, new: local.mode });
|
|
9820
|
+
}
|
|
9821
|
+
if (hasDiff(local.agents, remote.agents)) {
|
|
9822
|
+
changes.push({ field: "agents", old: remote.agents, new: local.agents });
|
|
9823
|
+
}
|
|
9824
|
+
if (hasDiff(local.rules || [], remote.rules || [])) {
|
|
9825
|
+
changes.push({ field: "rules", old: remote.rules || [], new: local.rules || [] });
|
|
9826
|
+
}
|
|
9827
|
+
if (local.fallback !== remote.fallback) {
|
|
9828
|
+
changes.push({ field: "fallback", old: remote.fallback, new: local.fallback });
|
|
9829
|
+
}
|
|
9830
|
+
return changes;
|
|
9831
|
+
}
|
|
9832
|
+
function renderChanges(changes) {
|
|
9833
|
+
for (const change of changes) {
|
|
9834
|
+
console.log(chalk27.cyan(` ${change.field}:`));
|
|
9835
|
+
if (change.field === "systemPrompt") {
|
|
9836
|
+
const lines = diffLines(String(change.old), String(change.new));
|
|
9837
|
+
if (lines.length > 0) {
|
|
9838
|
+
renderDiff(lines);
|
|
9839
|
+
}
|
|
9840
|
+
} else if (change.field === "tools") {
|
|
9841
|
+
const oldTools = change.old || [];
|
|
9842
|
+
const newTools = change.new || [];
|
|
9843
|
+
const added = newTools.filter((t) => !oldTools.includes(t));
|
|
9844
|
+
const removed = oldTools.filter((t) => !newTools.includes(t));
|
|
9845
|
+
for (const t of removed) {
|
|
9846
|
+
console.log(chalk27.red(`- ${t}`));
|
|
9847
|
+
}
|
|
9848
|
+
for (const t of added) {
|
|
9849
|
+
console.log(chalk27.green(`+ ${t}`));
|
|
9850
|
+
}
|
|
9851
|
+
} else {
|
|
9852
|
+
const lines = diffObjects(change.old, change.new);
|
|
9853
|
+
if (lines.length > 0) {
|
|
9854
|
+
renderDiff(lines);
|
|
9855
|
+
}
|
|
9856
|
+
}
|
|
9857
|
+
}
|
|
9858
|
+
}
|
|
9859
|
+
var diffCommand = new Command24("diff").description("Show content differences between local and remote resources").option("--env <environment>", "Environment to diff against", "development").option("--resource <type>", "Filter to resource type (agents|triggers|entity-types|roles|routers)").option("--name <slug>", "Filter to specific resource by slug/name").option("--json", "Output raw JSON diff").option("--stat", "Show summary stats only (no content diff)").action(async (opts) => {
|
|
9860
|
+
const spinner = ora19();
|
|
9861
|
+
const cwd = process.cwd();
|
|
9862
|
+
const jsonMode = !!opts.json;
|
|
9863
|
+
const nonInteractive = !isInteractive();
|
|
9864
|
+
if (!jsonMode) {
|
|
9865
|
+
console.log();
|
|
9866
|
+
console.log(chalk27.bold("Struere Diff"));
|
|
9867
|
+
console.log();
|
|
9868
|
+
}
|
|
9869
|
+
if (!hasProject(cwd)) {
|
|
9870
|
+
if (nonInteractive) {
|
|
9871
|
+
if (jsonMode) {
|
|
9872
|
+
console.log(JSON.stringify({ error: "No struere.json found. Run struere init first." }));
|
|
9873
|
+
} else {
|
|
9874
|
+
console.log(chalk27.red("No struere.json found. Run struere init first."));
|
|
9875
|
+
}
|
|
9876
|
+
process.exit(1);
|
|
9877
|
+
}
|
|
9878
|
+
console.log(chalk27.yellow("No struere.json found - initializing project..."));
|
|
9879
|
+
console.log();
|
|
9880
|
+
const success = await runInit(cwd);
|
|
9881
|
+
if (!success) {
|
|
9882
|
+
process.exit(1);
|
|
9883
|
+
}
|
|
9884
|
+
console.log();
|
|
9885
|
+
}
|
|
9886
|
+
const project = loadProject(cwd);
|
|
9887
|
+
if (!project) {
|
|
9888
|
+
if (jsonMode) {
|
|
9889
|
+
console.log(JSON.stringify({ error: "Failed to load struere.json" }));
|
|
9890
|
+
} else {
|
|
9891
|
+
console.log(chalk27.red("Failed to load struere.json"));
|
|
9892
|
+
}
|
|
9893
|
+
process.exit(1);
|
|
9894
|
+
}
|
|
9895
|
+
let credentials = loadCredentials();
|
|
9896
|
+
const apiKey = getApiKey();
|
|
9897
|
+
if (!credentials && !apiKey) {
|
|
9898
|
+
if (nonInteractive) {
|
|
9899
|
+
if (jsonMode) {
|
|
9900
|
+
console.log(JSON.stringify({ error: "Not authenticated. Set STRUERE_API_KEY or run struere login." }));
|
|
9901
|
+
} else {
|
|
9902
|
+
console.log(chalk27.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
9903
|
+
}
|
|
9904
|
+
process.exit(1);
|
|
9905
|
+
}
|
|
9906
|
+
console.log(chalk27.yellow("Not logged in - authenticating..."));
|
|
9907
|
+
console.log();
|
|
9908
|
+
credentials = await performLogin();
|
|
9909
|
+
if (!credentials) {
|
|
9910
|
+
console.log(chalk27.red("Authentication failed"));
|
|
9911
|
+
process.exit(1);
|
|
9912
|
+
}
|
|
9913
|
+
console.log();
|
|
9914
|
+
}
|
|
9915
|
+
if (!jsonMode)
|
|
9916
|
+
spinner.start("Loading local resources");
|
|
9917
|
+
let localResources;
|
|
9918
|
+
try {
|
|
9919
|
+
localResources = await loadAllResources(cwd);
|
|
9920
|
+
if (!jsonMode)
|
|
9921
|
+
spinner.succeed("Local resources loaded");
|
|
9922
|
+
} catch (error2) {
|
|
9923
|
+
if (jsonMode) {
|
|
9924
|
+
console.log(JSON.stringify({ error: error2 instanceof Error ? error2.message : String(error2) }));
|
|
9925
|
+
} else {
|
|
9926
|
+
spinner.fail("Failed to load local resources");
|
|
9927
|
+
console.log(chalk27.red("Error:"), error2 instanceof Error ? error2.message : String(error2));
|
|
9928
|
+
}
|
|
9929
|
+
process.exit(1);
|
|
9930
|
+
}
|
|
9931
|
+
if (!jsonMode)
|
|
9932
|
+
spinner.start("Fetching remote state");
|
|
9933
|
+
const environment = opts.env;
|
|
9934
|
+
const { state, error } = await getPullState(project.organization.id, environment);
|
|
9935
|
+
if (error || !state) {
|
|
9936
|
+
if (jsonMode) {
|
|
9937
|
+
console.log(JSON.stringify({ error: error || "Failed to fetch remote state" }));
|
|
9938
|
+
} else {
|
|
9939
|
+
spinner.fail("Failed to fetch remote state");
|
|
9940
|
+
console.log(chalk27.red("Error:"), error || "Unknown error");
|
|
9941
|
+
}
|
|
9942
|
+
process.exit(1);
|
|
9943
|
+
}
|
|
9944
|
+
if (!jsonMode)
|
|
9945
|
+
spinner.succeed("Remote state fetched");
|
|
9946
|
+
const entries = [];
|
|
9947
|
+
const shouldInclude = (type, key) => {
|
|
9948
|
+
if (opts.resource && opts.resource !== type)
|
|
9949
|
+
return false;
|
|
9950
|
+
if (opts.name && opts.name !== key)
|
|
9951
|
+
return false;
|
|
9952
|
+
return true;
|
|
9953
|
+
};
|
|
9954
|
+
const localAgentSlugs = new Set(localResources.agents.map((a) => a.slug));
|
|
9955
|
+
const remoteAgentSlugs = new Set(state.agents.map((a) => a.slug));
|
|
9956
|
+
for (const local of localResources.agents) {
|
|
9957
|
+
if (!shouldInclude("agents", local.slug))
|
|
9958
|
+
continue;
|
|
9959
|
+
const remote = state.agents.find((a) => a.slug === local.slug);
|
|
9960
|
+
if (!remote) {
|
|
9961
|
+
entries.push({ type: "agents", slug: local.slug, status: "new" });
|
|
9962
|
+
} else {
|
|
9963
|
+
const changes = diffAgent(local, remote);
|
|
9964
|
+
if (changes.length > 0) {
|
|
9965
|
+
entries.push({ type: "agents", slug: local.slug, status: "modified", changes });
|
|
9966
|
+
}
|
|
9967
|
+
}
|
|
9968
|
+
}
|
|
9969
|
+
for (const remote of state.agents) {
|
|
9970
|
+
if (!shouldInclude("agents", remote.slug))
|
|
9971
|
+
continue;
|
|
9972
|
+
if (!localAgentSlugs.has(remote.slug)) {
|
|
9973
|
+
entries.push({ type: "agents", slug: remote.slug, status: "deleted" });
|
|
9974
|
+
}
|
|
9975
|
+
}
|
|
9976
|
+
const localEntityTypeSlugs = new Set(localResources.entityTypes.map((e) => e.slug));
|
|
9977
|
+
const remoteEntityTypeSlugs = new Set(state.entityTypes.map((e) => e.slug));
|
|
9978
|
+
for (const local of localResources.entityTypes) {
|
|
9979
|
+
if (!shouldInclude("entity-types", local.slug))
|
|
9980
|
+
continue;
|
|
9981
|
+
const remote = state.entityTypes.find((e) => e.slug === local.slug);
|
|
9982
|
+
if (!remote) {
|
|
9983
|
+
entries.push({ type: "entity-types", slug: local.slug, status: "new" });
|
|
9984
|
+
} else {
|
|
9985
|
+
const changes = diffEntityType(local, remote);
|
|
9986
|
+
if (changes.length > 0) {
|
|
9987
|
+
entries.push({ type: "entity-types", slug: local.slug, status: "modified", changes });
|
|
9988
|
+
}
|
|
9989
|
+
}
|
|
9990
|
+
}
|
|
9991
|
+
for (const remote of state.entityTypes) {
|
|
9992
|
+
if (!shouldInclude("entity-types", remote.slug))
|
|
9993
|
+
continue;
|
|
9994
|
+
if (!localEntityTypeSlugs.has(remote.slug)) {
|
|
9995
|
+
entries.push({ type: "entity-types", slug: remote.slug, status: "deleted" });
|
|
9996
|
+
}
|
|
9997
|
+
}
|
|
9998
|
+
const localRoleNames = new Set(localResources.roles.map((r) => r.name));
|
|
9999
|
+
const remoteRoleNames = new Set(state.roles.map((r) => r.name));
|
|
10000
|
+
for (const local of localResources.roles) {
|
|
10001
|
+
if (!shouldInclude("roles", local.name))
|
|
10002
|
+
continue;
|
|
10003
|
+
const remote = state.roles.find((r) => r.name === local.name);
|
|
10004
|
+
if (!remote) {
|
|
10005
|
+
entries.push({ type: "roles", slug: local.name, status: "new" });
|
|
10006
|
+
} else {
|
|
10007
|
+
const changes = diffRole(local, remote);
|
|
10008
|
+
if (changes.length > 0) {
|
|
10009
|
+
entries.push({ type: "roles", slug: local.name, status: "modified", changes });
|
|
10010
|
+
}
|
|
10011
|
+
}
|
|
10012
|
+
}
|
|
10013
|
+
for (const remote of state.roles) {
|
|
10014
|
+
if (!shouldInclude("roles", remote.name))
|
|
10015
|
+
continue;
|
|
10016
|
+
if (!localRoleNames.has(remote.name)) {
|
|
10017
|
+
entries.push({ type: "roles", slug: remote.name, status: "deleted" });
|
|
10018
|
+
}
|
|
10019
|
+
}
|
|
10020
|
+
const localTriggerSlugs = new Set(localResources.triggers.map((t) => t.slug));
|
|
10021
|
+
const remoteTriggers = state.triggers || [];
|
|
10022
|
+
const remoteTriggerSlugs = new Set(remoteTriggers.map((t) => t.slug));
|
|
10023
|
+
for (const local of localResources.triggers) {
|
|
10024
|
+
if (!shouldInclude("triggers", local.slug))
|
|
10025
|
+
continue;
|
|
10026
|
+
const remote = remoteTriggers.find((t) => t.slug === local.slug);
|
|
10027
|
+
if (!remote) {
|
|
10028
|
+
entries.push({ type: "triggers", slug: local.slug, status: "new" });
|
|
10029
|
+
} else {
|
|
10030
|
+
const changes = diffTrigger(local, remote);
|
|
10031
|
+
if (changes.length > 0) {
|
|
10032
|
+
entries.push({ type: "triggers", slug: local.slug, status: "modified", changes });
|
|
10033
|
+
}
|
|
10034
|
+
}
|
|
10035
|
+
}
|
|
10036
|
+
for (const remote of remoteTriggers) {
|
|
10037
|
+
if (!shouldInclude("triggers", remote.slug))
|
|
10038
|
+
continue;
|
|
10039
|
+
if (!localTriggerSlugs.has(remote.slug)) {
|
|
10040
|
+
entries.push({ type: "triggers", slug: remote.slug, status: "deleted" });
|
|
10041
|
+
}
|
|
10042
|
+
}
|
|
10043
|
+
const localRouterSlugs = new Set(localResources.routers.map((r) => r.slug));
|
|
10044
|
+
const remoteRouters = state.routers || [];
|
|
10045
|
+
const remoteRouterSlugs = new Set(remoteRouters.map((r) => r.slug));
|
|
10046
|
+
for (const local of localResources.routers) {
|
|
10047
|
+
if (!shouldInclude("routers", local.slug))
|
|
10048
|
+
continue;
|
|
10049
|
+
const remote = remoteRouters.find((r) => r.slug === local.slug);
|
|
10050
|
+
if (!remote) {
|
|
10051
|
+
entries.push({ type: "routers", slug: local.slug, status: "new" });
|
|
10052
|
+
} else {
|
|
10053
|
+
const changes = diffRouter(local, remote);
|
|
10054
|
+
if (changes.length > 0) {
|
|
10055
|
+
entries.push({ type: "routers", slug: local.slug, status: "modified", changes });
|
|
10056
|
+
}
|
|
10057
|
+
}
|
|
10058
|
+
}
|
|
10059
|
+
for (const remote of remoteRouters) {
|
|
10060
|
+
if (!shouldInclude("routers", remote.slug))
|
|
10061
|
+
continue;
|
|
10062
|
+
if (!localRouterSlugs.has(remote.slug)) {
|
|
10063
|
+
entries.push({ type: "routers", slug: remote.slug, status: "deleted" });
|
|
10064
|
+
}
|
|
10065
|
+
}
|
|
10066
|
+
const modified = entries.filter((e) => e.status === "modified").length;
|
|
10067
|
+
const newCount = entries.filter((e) => e.status === "new").length;
|
|
10068
|
+
const deleted = entries.filter((e) => e.status === "deleted").length;
|
|
10069
|
+
if (jsonMode) {
|
|
10070
|
+
console.log(JSON.stringify({
|
|
10071
|
+
resources: entries.map((e) => ({
|
|
10072
|
+
type: e.type,
|
|
10073
|
+
slug: e.slug,
|
|
10074
|
+
status: e.status,
|
|
10075
|
+
changes: e.changes?.map((c) => ({ field: c.field, old: c.old, new: c.new }))
|
|
10076
|
+
})),
|
|
10077
|
+
summary: { modified, new: newCount, deleted }
|
|
10078
|
+
}));
|
|
10079
|
+
return;
|
|
10080
|
+
}
|
|
10081
|
+
console.log();
|
|
10082
|
+
console.log(chalk27.bold(`struere diff`) + chalk27.gray(` (vs ${environment})`));
|
|
10083
|
+
console.log();
|
|
10084
|
+
if (entries.length === 0) {
|
|
10085
|
+
console.log(chalk27.green(" No differences found."));
|
|
10086
|
+
console.log();
|
|
10087
|
+
return;
|
|
10088
|
+
}
|
|
10089
|
+
for (const entry of entries) {
|
|
10090
|
+
const filePath = `${entry.type}/${entry.slug}.ts`;
|
|
10091
|
+
if (entry.status === "new") {
|
|
10092
|
+
console.log(chalk27.green(` ${filePath} (new \u2014 not yet synced)`));
|
|
10093
|
+
} else if (entry.status === "deleted") {
|
|
10094
|
+
console.log(chalk27.red(` ${filePath} (deleted \u2014 only on remote)`));
|
|
10095
|
+
} else if (entry.status === "modified") {
|
|
10096
|
+
console.log(chalk27.yellow(` ${filePath} (modified)`));
|
|
10097
|
+
if (!opts.stat) {
|
|
10098
|
+
console.log(chalk27.gray(" " + "\u2500".repeat(37)));
|
|
10099
|
+
renderChanges(entry.changes);
|
|
10100
|
+
}
|
|
10101
|
+
}
|
|
10102
|
+
if (!opts.stat)
|
|
10103
|
+
console.log();
|
|
10104
|
+
}
|
|
10105
|
+
console.log(chalk27.gray(` Summary: ${modified} modified, ${newCount} new, ${deleted} deleted`));
|
|
10106
|
+
console.log();
|
|
10107
|
+
});
|
|
10108
|
+
|
|
10109
|
+
// src/cli/commands/doctor.ts
|
|
10110
|
+
init_credentials();
|
|
10111
|
+
import { Command as Command25 } from "commander";
|
|
10112
|
+
import chalk28 from "chalk";
|
|
10113
|
+
import ora20 from "ora";
|
|
10114
|
+
init_convex();
|
|
10115
|
+
init_convex();
|
|
10116
|
+
var BUILTIN_ENTITY_TYPES = new Set(["payment"]);
|
|
10117
|
+
async function ensureAuth8() {
|
|
10118
|
+
const cwd = process.cwd();
|
|
10119
|
+
const nonInteractive = !isInteractive();
|
|
10120
|
+
if (!hasProject(cwd)) {
|
|
10121
|
+
if (nonInteractive) {
|
|
10122
|
+
console.error(chalk28.red("No struere.json found. Run struere init first."));
|
|
10123
|
+
process.exit(1);
|
|
10124
|
+
}
|
|
10125
|
+
console.log(chalk28.yellow("No struere.json found - initializing project..."));
|
|
10126
|
+
console.log();
|
|
10127
|
+
const success = await runInit(cwd);
|
|
10128
|
+
if (!success) {
|
|
10129
|
+
process.exit(1);
|
|
10130
|
+
}
|
|
10131
|
+
console.log();
|
|
10132
|
+
}
|
|
10133
|
+
let credentials = loadCredentials();
|
|
10134
|
+
const apiKey = getApiKey();
|
|
10135
|
+
if (!credentials && !apiKey) {
|
|
10136
|
+
if (nonInteractive) {
|
|
10137
|
+
console.error(chalk28.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
10138
|
+
process.exit(1);
|
|
10139
|
+
}
|
|
10140
|
+
console.log(chalk28.yellow("Not logged in - authenticating..."));
|
|
10141
|
+
console.log();
|
|
10142
|
+
credentials = await performLogin();
|
|
10143
|
+
if (!credentials) {
|
|
10144
|
+
console.log(chalk28.red("Authentication failed"));
|
|
10145
|
+
process.exit(1);
|
|
10146
|
+
}
|
|
10147
|
+
console.log();
|
|
10148
|
+
}
|
|
10149
|
+
return true;
|
|
10150
|
+
}
|
|
10151
|
+
async function withAuthRetry2(fn) {
|
|
10152
|
+
try {
|
|
10153
|
+
return await fn();
|
|
10154
|
+
} catch (err) {
|
|
10155
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10156
|
+
if (msg.includes("Unauthenticated") || msg.includes("OIDC") || msg.includes("token") || msg.includes("expired")) {
|
|
10157
|
+
const refreshed = await refreshToken();
|
|
10158
|
+
if (!refreshed)
|
|
10159
|
+
throw err;
|
|
10160
|
+
return fn();
|
|
10161
|
+
}
|
|
10162
|
+
throw err;
|
|
10163
|
+
}
|
|
10164
|
+
}
|
|
10165
|
+
function checkWhatsAppConnection(resources) {
|
|
10166
|
+
const whatsappTools = new Set([
|
|
10167
|
+
"whatsapp.send",
|
|
10168
|
+
"whatsapp.sendTemplate",
|
|
10169
|
+
"whatsapp.sendInteractive",
|
|
10170
|
+
"whatsapp.sendMedia",
|
|
10171
|
+
"whatsapp.listTemplates",
|
|
10172
|
+
"whatsapp.getConversation",
|
|
10173
|
+
"whatsapp.getStatus"
|
|
10174
|
+
]);
|
|
10175
|
+
const agentsUsingWhatsapp = resources.agents.filter((a) => a.tools?.some((t) => whatsappTools.has(t)));
|
|
10176
|
+
if (agentsUsingWhatsapp.length === 0) {
|
|
10177
|
+
return {
|
|
10178
|
+
id: "whatsapp",
|
|
10179
|
+
label: "WhatsApp connection",
|
|
10180
|
+
status: "ok",
|
|
10181
|
+
message: "No WhatsApp tools used"
|
|
10182
|
+
};
|
|
10183
|
+
}
|
|
10184
|
+
const names = agentsUsingWhatsapp.map((a) => a.name).join(", ");
|
|
10185
|
+
return {
|
|
10186
|
+
id: "whatsapp",
|
|
10187
|
+
label: "WhatsApp connection",
|
|
10188
|
+
status: "warn",
|
|
10189
|
+
message: `${agentsUsingWhatsapp.length} agent(s) use WhatsApp tools (${names}) - verify connection`,
|
|
10190
|
+
hint: "Run `struere integration whatsapp` to configure"
|
|
10191
|
+
};
|
|
10192
|
+
}
|
|
10193
|
+
function checkTemplateApprovals(resources) {
|
|
10194
|
+
const triggersUsingTemplates = resources.triggers.filter((t) => t.actions.some((a) => a.tool === "whatsapp.sendTemplate"));
|
|
10195
|
+
const agentsUsingTemplates = resources.agents.filter((a) => a.tools?.includes("whatsapp.sendTemplate"));
|
|
10196
|
+
const total = triggersUsingTemplates.length + agentsUsingTemplates.length;
|
|
10197
|
+
if (total === 0) {
|
|
10198
|
+
return {
|
|
10199
|
+
id: "templates",
|
|
10200
|
+
label: "Template approvals",
|
|
10201
|
+
status: "ok",
|
|
10202
|
+
message: "No template tools used"
|
|
10203
|
+
};
|
|
10204
|
+
}
|
|
10205
|
+
return {
|
|
10206
|
+
id: "templates",
|
|
10207
|
+
label: "Template approvals",
|
|
10208
|
+
status: "warn",
|
|
10209
|
+
message: `${total} resource(s) use whatsapp.sendTemplate - verify templates are approved`,
|
|
10210
|
+
hint: "Run `struere templates list` to check approval status"
|
|
10211
|
+
};
|
|
10212
|
+
}
|
|
10213
|
+
function checkEntityTypeReferences(resources) {
|
|
10214
|
+
const entityTypeSlugs = new Set(resources.entityTypes.map((et) => et.slug));
|
|
10215
|
+
const dangling = [];
|
|
10216
|
+
for (const trigger of resources.triggers) {
|
|
10217
|
+
const on = trigger.on;
|
|
10218
|
+
if (on.entityType && !entityTypeSlugs.has(on.entityType) && !BUILTIN_ENTITY_TYPES.has(on.entityType)) {
|
|
10219
|
+
dangling.push(`Trigger "${trigger.name}" references "${on.entityType}"`);
|
|
10220
|
+
}
|
|
10221
|
+
}
|
|
10222
|
+
if (dangling.length === 0) {
|
|
10223
|
+
return {
|
|
10224
|
+
id: "entity-refs",
|
|
10225
|
+
label: "Entity type references",
|
|
10226
|
+
status: "ok",
|
|
10227
|
+
message: "All triggers reference valid types"
|
|
10228
|
+
};
|
|
10229
|
+
}
|
|
10230
|
+
return {
|
|
10231
|
+
id: "entity-refs",
|
|
10232
|
+
label: "Entity type references",
|
|
10233
|
+
status: "error",
|
|
10234
|
+
message: dangling.join("; "),
|
|
10235
|
+
hint: "Create the missing entity type with `struere add data-type <name>`"
|
|
10236
|
+
};
|
|
10237
|
+
}
|
|
10238
|
+
function checkModelConfig(resources) {
|
|
10239
|
+
const issues = [];
|
|
10240
|
+
for (const agent of resources.agents) {
|
|
10241
|
+
if (!agent.model)
|
|
10242
|
+
continue;
|
|
10243
|
+
if (agent.model.temperature !== undefined && (agent.model.temperature < 0 || agent.model.temperature > 2)) {
|
|
10244
|
+
issues.push(`Temperature ${agent.model.temperature} in agent "${agent.name}" (should be 0-2)`);
|
|
10245
|
+
}
|
|
10246
|
+
if (agent.model.maxTokens !== undefined && agent.model.maxTokens <= 0) {
|
|
10247
|
+
issues.push(`maxTokens ${agent.model.maxTokens} in agent "${agent.name}" (should be > 0)`);
|
|
10248
|
+
}
|
|
10249
|
+
}
|
|
10250
|
+
if (issues.length === 0) {
|
|
10251
|
+
return {
|
|
10252
|
+
id: "model-config",
|
|
10253
|
+
label: "Model config",
|
|
10254
|
+
status: "ok",
|
|
10255
|
+
message: "All agent model configs valid"
|
|
10256
|
+
};
|
|
10257
|
+
}
|
|
10258
|
+
return {
|
|
10259
|
+
id: "model-config",
|
|
10260
|
+
label: "Model config",
|
|
10261
|
+
status: "warn",
|
|
10262
|
+
message: issues.join("; "),
|
|
10263
|
+
hint: "Check agent model config - temperature should be 0-2"
|
|
10264
|
+
};
|
|
10265
|
+
}
|
|
10266
|
+
async function checkStuckThreads(environment) {
|
|
10267
|
+
const threads = await withAuthRetry2(() => listThreads({ environment, limit: 100 }));
|
|
10268
|
+
const issues = [];
|
|
10269
|
+
const now = Date.now();
|
|
10270
|
+
const fiveMinutes = 5 * 60 * 1000;
|
|
10271
|
+
for (const thread of threads) {
|
|
10272
|
+
if (thread.messageCount > 500) {
|
|
10273
|
+
issues.push(`Thread ${thread._id.slice(-12)} has ${thread.messageCount} messages`);
|
|
10274
|
+
}
|
|
10275
|
+
if (thread.lastMessage?.role === "assistant" && !thread.lastMessage.content) {
|
|
10276
|
+
issues.push(`Thread ${thread._id.slice(-12)} has empty assistant response`);
|
|
10277
|
+
}
|
|
10278
|
+
}
|
|
10279
|
+
if (issues.length === 0) {
|
|
10280
|
+
return {
|
|
10281
|
+
id: "stuck-threads",
|
|
10282
|
+
label: "Stuck threads",
|
|
10283
|
+
status: "ok",
|
|
10284
|
+
message: "No stuck threads detected"
|
|
10285
|
+
};
|
|
10286
|
+
}
|
|
10287
|
+
const hasEmpty = issues.some((i) => i.includes("empty assistant"));
|
|
10288
|
+
return {
|
|
10289
|
+
id: "stuck-threads",
|
|
10290
|
+
label: "Stuck threads",
|
|
10291
|
+
status: hasEmpty ? "error" : "warn",
|
|
10292
|
+
message: `${issues.length} issue(s): ${issues.join("; ")}`,
|
|
10293
|
+
hint: "Run `struere threads archive <id>` to archive stuck threads"
|
|
10294
|
+
};
|
|
10295
|
+
}
|
|
10296
|
+
async function checkTriggerHealth(environment) {
|
|
10297
|
+
const executions = await withAuthRetry2(() => listTriggerExecutions({ environment, limit: 100 }));
|
|
10298
|
+
if (executions.length === 0) {
|
|
10299
|
+
return {
|
|
10300
|
+
id: "trigger-health",
|
|
10301
|
+
label: "Trigger health",
|
|
10302
|
+
status: "ok",
|
|
10303
|
+
message: "No recent trigger executions"
|
|
10304
|
+
};
|
|
10305
|
+
}
|
|
10306
|
+
const bySlug = {};
|
|
10307
|
+
for (const exec of executions) {
|
|
10308
|
+
const slug = exec.payload?.triggerSlug ?? "unknown";
|
|
10309
|
+
if (!bySlug[slug])
|
|
10310
|
+
bySlug[slug] = { total: 0, failed: 0 };
|
|
10311
|
+
bySlug[slug].total++;
|
|
10312
|
+
if (exec.eventType === "trigger.failed")
|
|
10313
|
+
bySlug[slug].failed++;
|
|
10314
|
+
}
|
|
10315
|
+
const issues = [];
|
|
10316
|
+
let worstStatus = "ok";
|
|
10317
|
+
for (const [slug, stats] of Object.entries(bySlug)) {
|
|
10318
|
+
const rate = stats.failed / stats.total;
|
|
10319
|
+
if (rate > 0.5) {
|
|
10320
|
+
issues.push(`"${slug}" ${Math.round(rate * 100)}% failure rate (${stats.failed}/${stats.total})`);
|
|
10321
|
+
worstStatus = "error";
|
|
10322
|
+
} else if (rate > 0.2) {
|
|
10323
|
+
issues.push(`"${slug}" ${Math.round(rate * 100)}% failure rate (${stats.failed}/${stats.total})`);
|
|
10324
|
+
if (worstStatus !== "error")
|
|
10325
|
+
worstStatus = "warn";
|
|
10326
|
+
}
|
|
10327
|
+
}
|
|
10328
|
+
if (issues.length === 0) {
|
|
10329
|
+
return {
|
|
10330
|
+
id: "trigger-health",
|
|
10331
|
+
label: "Trigger health",
|
|
10332
|
+
status: "ok",
|
|
10333
|
+
message: "All triggers healthy"
|
|
10334
|
+
};
|
|
10335
|
+
}
|
|
10336
|
+
return {
|
|
10337
|
+
id: "trigger-health",
|
|
10338
|
+
label: "Trigger health",
|
|
10339
|
+
status: worstStatus,
|
|
10340
|
+
message: issues.join("; "),
|
|
10341
|
+
hint: "Run `struere triggers logs <slug>` to investigate failures"
|
|
10342
|
+
};
|
|
10343
|
+
}
|
|
10344
|
+
async function checkSyncDrift(resources, environment) {
|
|
10345
|
+
const { state, error } = await withAuthRetry2(() => getSyncState(undefined, environment));
|
|
10346
|
+
if (error || !state) {
|
|
10347
|
+
return {
|
|
10348
|
+
id: "sync-drift",
|
|
10349
|
+
label: "Sync drift",
|
|
10350
|
+
status: "warn",
|
|
10351
|
+
message: `Could not fetch remote state: ${error ?? "unknown"}`,
|
|
10352
|
+
hint: "Run `struere sync` to deploy local changes"
|
|
10353
|
+
};
|
|
10354
|
+
}
|
|
10355
|
+
const localAgentSlugs = new Set(resources.agents.map((a) => a.slug));
|
|
10356
|
+
const remoteAgentSlugs = new Set(state.agents.map((a) => a.slug));
|
|
10357
|
+
const localEntitySlugs = new Set(resources.entityTypes.map((e) => e.slug));
|
|
10358
|
+
const remoteEntitySlugs = new Set(state.entityTypes.map((e) => e.slug));
|
|
10359
|
+
const localRoleNames = new Set(resources.roles.map((r) => r.name));
|
|
10360
|
+
const remoteRoleNames = new Set(state.roles.map((r) => r.name));
|
|
10361
|
+
const localTriggerSlugs = new Set(resources.triggers.map((t) => t.slug));
|
|
10362
|
+
const remoteTriggerSlugs = new Set((state.triggers ?? []).map((t) => t.slug));
|
|
10363
|
+
const localRouterSlugs = new Set(resources.routers.map((r) => r.slug));
|
|
10364
|
+
const remoteRouterSlugs = new Set((state.routers ?? []).map((r) => r.slug));
|
|
10365
|
+
const localOnly = [];
|
|
10366
|
+
const remoteOnly = [];
|
|
10367
|
+
for (const slug of localAgentSlugs) {
|
|
10368
|
+
if (!remoteAgentSlugs.has(slug))
|
|
10369
|
+
localOnly.push(`agent:${slug}`);
|
|
10370
|
+
}
|
|
10371
|
+
for (const slug of remoteAgentSlugs) {
|
|
10372
|
+
if (!localAgentSlugs.has(slug))
|
|
10373
|
+
remoteOnly.push(`agent:${slug}`);
|
|
10374
|
+
}
|
|
10375
|
+
for (const slug of localEntitySlugs) {
|
|
10376
|
+
if (!remoteEntitySlugs.has(slug))
|
|
10377
|
+
localOnly.push(`data:${slug}`);
|
|
10378
|
+
}
|
|
10379
|
+
for (const slug of remoteEntitySlugs) {
|
|
10380
|
+
if (!localEntitySlugs.has(slug))
|
|
10381
|
+
remoteOnly.push(`data:${slug}`);
|
|
10382
|
+
}
|
|
10383
|
+
for (const name of localRoleNames) {
|
|
10384
|
+
if (!remoteRoleNames.has(name))
|
|
10385
|
+
localOnly.push(`role:${name}`);
|
|
10386
|
+
}
|
|
10387
|
+
for (const name of remoteRoleNames) {
|
|
10388
|
+
if (!localRoleNames.has(name))
|
|
10389
|
+
remoteOnly.push(`role:${name}`);
|
|
10390
|
+
}
|
|
10391
|
+
for (const slug of localTriggerSlugs) {
|
|
10392
|
+
if (!remoteTriggerSlugs.has(slug))
|
|
10393
|
+
localOnly.push(`trigger:${slug}`);
|
|
10394
|
+
}
|
|
10395
|
+
for (const slug of remoteTriggerSlugs) {
|
|
10396
|
+
if (!localTriggerSlugs.has(slug))
|
|
10397
|
+
remoteOnly.push(`trigger:${slug}`);
|
|
10398
|
+
}
|
|
10399
|
+
for (const slug of localRouterSlugs) {
|
|
10400
|
+
if (!remoteRouterSlugs.has(slug))
|
|
10401
|
+
localOnly.push(`router:${slug}`);
|
|
10402
|
+
}
|
|
10403
|
+
for (const slug of remoteRouterSlugs) {
|
|
10404
|
+
if (!localRouterSlugs.has(slug))
|
|
10405
|
+
remoteOnly.push(`router:${slug}`);
|
|
10406
|
+
}
|
|
10407
|
+
if (localOnly.length === 0 && remoteOnly.length === 0) {
|
|
10408
|
+
return {
|
|
10409
|
+
id: "sync-drift",
|
|
10410
|
+
label: "Sync drift",
|
|
10411
|
+
status: "ok",
|
|
10412
|
+
message: "Local and remote are in sync"
|
|
10413
|
+
};
|
|
10414
|
+
}
|
|
10415
|
+
if (remoteOnly.length > 0) {
|
|
10416
|
+
const parts = [];
|
|
10417
|
+
if (localOnly.length > 0)
|
|
10418
|
+
parts.push(`${localOnly.length} local-only (${localOnly.join(", ")})`);
|
|
10419
|
+
parts.push(`${remoteOnly.length} remote-only (${remoteOnly.join(", ")})`);
|
|
10420
|
+
return {
|
|
10421
|
+
id: "sync-drift",
|
|
10422
|
+
label: "Sync drift",
|
|
10423
|
+
status: "error",
|
|
10424
|
+
message: parts.join("; "),
|
|
10425
|
+
hint: "Run `struere pull` to fetch remote resources, or `struere sync --force` to delete them"
|
|
10426
|
+
};
|
|
10427
|
+
}
|
|
10428
|
+
return {
|
|
10429
|
+
id: "sync-drift",
|
|
10430
|
+
label: "Sync drift",
|
|
10431
|
+
status: "warn",
|
|
10432
|
+
message: `${localOnly.length} local resources not yet synced (${localOnly.join(", ")})`,
|
|
10433
|
+
hint: "Run `struere sync` to deploy local changes"
|
|
10434
|
+
};
|
|
10435
|
+
}
|
|
10436
|
+
async function runCheck(fn) {
|
|
10437
|
+
try {
|
|
10438
|
+
return await fn();
|
|
10439
|
+
} catch (err) {
|
|
10440
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10441
|
+
return {
|
|
10442
|
+
id: "unknown",
|
|
10443
|
+
label: "Unknown",
|
|
10444
|
+
status: "warn",
|
|
10445
|
+
message: `Check failed: ${message}`
|
|
10446
|
+
};
|
|
10447
|
+
}
|
|
10448
|
+
}
|
|
10449
|
+
var doctorCommand = new Command25("doctor").description("Run diagnostic checks on your Struere project").option("--env <environment>", "Environment to check", "development").option("--json", "Output raw JSON").action(async (opts) => {
|
|
10450
|
+
await ensureAuth8();
|
|
10451
|
+
const spinner = ora20();
|
|
10452
|
+
const cwd = process.cwd();
|
|
10453
|
+
const environment = opts.env;
|
|
10454
|
+
try {
|
|
10455
|
+
spinner.start("Loading resources...");
|
|
10456
|
+
const resources = await loadAllResources(cwd);
|
|
10457
|
+
spinner.succeed("Resources loaded");
|
|
10458
|
+
spinner.start("Running diagnostics...");
|
|
10459
|
+
const results = await Promise.allSettled([
|
|
10460
|
+
runCheck(() => checkWhatsAppConnection(resources)),
|
|
10461
|
+
runCheck(() => checkTemplateApprovals(resources)),
|
|
10462
|
+
runCheck(() => checkEntityTypeReferences(resources)),
|
|
10463
|
+
runCheck(() => checkModelConfig(resources)),
|
|
10464
|
+
runCheck(() => checkStuckThreads(environment)),
|
|
10465
|
+
runCheck(() => checkTriggerHealth(environment)),
|
|
10466
|
+
runCheck(() => checkSyncDrift(resources, environment))
|
|
10467
|
+
]);
|
|
10468
|
+
spinner.stop();
|
|
10469
|
+
const checks = results.map((r) => r.status === "fulfilled" ? r.value : { id: "unknown", label: "Unknown", status: "warn", message: `Check failed: ${r.reason}` });
|
|
10470
|
+
const summary = {
|
|
10471
|
+
ok: checks.filter((c) => c.status === "ok").length,
|
|
10472
|
+
warn: checks.filter((c) => c.status === "warn").length,
|
|
10473
|
+
error: checks.filter((c) => c.status === "error").length
|
|
10474
|
+
};
|
|
10475
|
+
if (opts.json) {
|
|
10476
|
+
console.log(JSON.stringify({ checks, summary }, null, 2));
|
|
10477
|
+
return;
|
|
10478
|
+
}
|
|
10479
|
+
console.log();
|
|
10480
|
+
console.log(chalk28.bold(`Struere Doctor`) + chalk28.gray(` (${environment})`));
|
|
10481
|
+
console.log();
|
|
10482
|
+
const maxLabelLen = Math.max(...checks.map((c) => c.label.length));
|
|
10483
|
+
for (const check of checks) {
|
|
10484
|
+
const icon = check.status === "ok" ? chalk28.green("\u25CF") : check.status === "warn" ? chalk28.yellow("\u25CB") : chalk28.red("\u2716");
|
|
10485
|
+
const paddedLabel = check.label.padEnd(maxLabelLen);
|
|
10486
|
+
console.log(` ${icon} ${paddedLabel} ${check.message}`);
|
|
10487
|
+
}
|
|
10488
|
+
console.log();
|
|
10489
|
+
const parts = [];
|
|
10490
|
+
if (summary.ok > 0)
|
|
10491
|
+
parts.push(chalk28.green(`${summary.ok} passed`));
|
|
10492
|
+
if (summary.warn > 0)
|
|
10493
|
+
parts.push(chalk28.yellow(`${summary.warn} warning${summary.warn > 1 ? "s" : ""}`));
|
|
10494
|
+
if (summary.error > 0)
|
|
10495
|
+
parts.push(chalk28.red(`${summary.error} error${summary.error > 1 ? "s" : ""}`));
|
|
10496
|
+
console.log(` Summary: ${parts.join(", ")}`);
|
|
10497
|
+
const hints = checks.filter((c) => c.hint && c.status !== "ok");
|
|
10498
|
+
if (hints.length > 0) {
|
|
10499
|
+
console.log();
|
|
10500
|
+
for (const h of hints) {
|
|
10501
|
+
const color = h.status === "error" ? chalk28.red : chalk28.yellow;
|
|
10502
|
+
console.log(` ${color("\u2192")} ${chalk28.dim(h.hint)}`);
|
|
10503
|
+
}
|
|
10504
|
+
}
|
|
10505
|
+
console.log();
|
|
10506
|
+
if (summary.error > 0) {
|
|
10507
|
+
process.exit(1);
|
|
10508
|
+
}
|
|
10509
|
+
} catch (err) {
|
|
10510
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
10511
|
+
spinner.fail("Diagnostics failed");
|
|
10512
|
+
if (opts.json) {
|
|
10513
|
+
console.log(JSON.stringify({ success: false, error: message }));
|
|
10514
|
+
} else {
|
|
10515
|
+
console.log(chalk28.red("Error:"), message);
|
|
10516
|
+
}
|
|
10517
|
+
process.exit(1);
|
|
10518
|
+
}
|
|
10519
|
+
});
|
|
9029
10520
|
// package.json
|
|
9030
10521
|
var package_default = {
|
|
9031
10522
|
name: "struere",
|
|
9032
|
-
version: "0.
|
|
10523
|
+
version: "0.13.1",
|
|
9033
10524
|
description: "Build, test, and deploy AI agents",
|
|
9034
10525
|
keywords: [
|
|
9035
10526
|
"ai",
|
|
@@ -9147,8 +10638,11 @@ program.addCommand(evalCommand);
|
|
|
9147
10638
|
program.addCommand(templatesCommand);
|
|
9148
10639
|
program.addCommand(integrationCommand);
|
|
9149
10640
|
program.addCommand(triggersCommand);
|
|
10641
|
+
program.addCommand(threadsCommand);
|
|
9150
10642
|
program.addCommand(compilePromptCommand);
|
|
9151
10643
|
program.addCommand(runToolCommand);
|
|
9152
10644
|
program.addCommand(chatCommand);
|
|
9153
10645
|
program.addCommand(whatsappCommand);
|
|
10646
|
+
program.addCommand(diffCommand);
|
|
10647
|
+
program.addCommand(doctorCommand);
|
|
9154
10648
|
program.parse();
|