struere 0.12.8 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/struere.js +1683 -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 +1683 -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/extractor.d.ts +6 -0
- package/dist/cli/utils/extractor.d.ts.map +1 -1
- 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/dist/types.d.ts +1 -1
- package/dist/types.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() };
|
|
@@ -3110,7 +3123,8 @@ function extractAgentPayload(agent, customToolsMap) {
|
|
|
3110
3123
|
model: {
|
|
3111
3124
|
model: agent.model?.model || "openai/gpt-5-mini",
|
|
3112
3125
|
temperature: agent.model?.temperature,
|
|
3113
|
-
maxTokens: agent.model?.maxTokens
|
|
3126
|
+
maxTokens: agent.model?.maxTokens,
|
|
3127
|
+
reasoning: agent.model?.reasoning
|
|
3114
3128
|
},
|
|
3115
3129
|
tools
|
|
3116
3130
|
};
|
|
@@ -3163,10 +3177,11 @@ function validateResources(payload, resources) {
|
|
|
3163
3177
|
}
|
|
3164
3178
|
}
|
|
3165
3179
|
}
|
|
3180
|
+
const BUILTIN_ENTITY_TYPES = new Set(["payment"]);
|
|
3166
3181
|
const entityTypeSlugs = new Set(payload.entityTypes.map((et) => et.slug));
|
|
3167
3182
|
if (payload.triggers) {
|
|
3168
3183
|
for (const trigger of payload.triggers) {
|
|
3169
|
-
if (trigger.entityType && !entityTypeSlugs.has(trigger.entityType)) {
|
|
3184
|
+
if (trigger.entityType && !entityTypeSlugs.has(trigger.entityType) && !BUILTIN_ENTITY_TYPES.has(trigger.entityType)) {
|
|
3170
3185
|
errors.push(`Trigger "${trigger.name}" references entity type "${trigger.entityType}" but no entity type with that slug exists`);
|
|
3171
3186
|
}
|
|
3172
3187
|
}
|
|
@@ -3687,7 +3702,7 @@ import chalk8 from "chalk";
|
|
|
3687
3702
|
import ora7 from "ora";
|
|
3688
3703
|
import { confirm as confirm4 } from "@inquirer/prompts";
|
|
3689
3704
|
init_convex();
|
|
3690
|
-
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) => {
|
|
3691
3706
|
const spinner = ora7();
|
|
3692
3707
|
const cwd = process.cwd();
|
|
3693
3708
|
const nonInteractive = !isInteractive();
|
|
@@ -3776,6 +3791,24 @@ var deployCommand = new Command7("deploy").description("Deploy all resources to
|
|
|
3776
3791
|
}
|
|
3777
3792
|
process.exit(1);
|
|
3778
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
|
+
}
|
|
3779
3812
|
if (resources.agents.length === 0) {
|
|
3780
3813
|
if (jsonMode) {
|
|
3781
3814
|
console.log(JSON.stringify({ success: false, error: "No agents found to deploy" }));
|
|
@@ -3819,8 +3852,10 @@ var deployCommand = new Command7("deploy").description("Deploy all resources to
|
|
|
3819
3852
|
if (!jsonMode)
|
|
3820
3853
|
spinner.stop();
|
|
3821
3854
|
} catch {
|
|
3822
|
-
if (!jsonMode)
|
|
3855
|
+
if (!jsonMode) {
|
|
3823
3856
|
spinner.stop();
|
|
3857
|
+
console.log(chalk8.yellow("Could not check remote state for deletions \u2014 proceeding without deletion warnings"));
|
|
3858
|
+
}
|
|
3824
3859
|
}
|
|
3825
3860
|
if (options.dryRun) {
|
|
3826
3861
|
if (jsonMode) {
|
|
@@ -3890,25 +3925,30 @@ var deployCommand = new Command7("deploy").description("Deploy all resources to
|
|
|
3890
3925
|
}
|
|
3891
3926
|
if (!jsonMode)
|
|
3892
3927
|
spinner.start("Deploying to production");
|
|
3928
|
+
let syncResult;
|
|
3929
|
+
const startTime = Date.now();
|
|
3893
3930
|
try {
|
|
3894
|
-
|
|
3931
|
+
syncResult = await syncOrganization({
|
|
3895
3932
|
agents: payload.agents,
|
|
3896
3933
|
tools: payload.tools,
|
|
3897
3934
|
entityTypes: payload.entityTypes,
|
|
3898
3935
|
roles: payload.roles,
|
|
3899
3936
|
triggers: payload.triggers,
|
|
3937
|
+
routers: payload.routers,
|
|
3900
3938
|
organizationId: project.organization.id,
|
|
3901
3939
|
environment: "production"
|
|
3902
3940
|
});
|
|
3903
3941
|
if (!syncResult.success) {
|
|
3904
3942
|
throw new Error(syncResult.error || "Deploy failed");
|
|
3905
3943
|
}
|
|
3944
|
+
const elapsed = Date.now() - startTime;
|
|
3906
3945
|
if (!jsonMode)
|
|
3907
|
-
spinner.succeed(
|
|
3946
|
+
spinner.succeed(`Deployed to production in ${(elapsed / 1000).toFixed(1)}s`);
|
|
3908
3947
|
if (jsonMode) {
|
|
3909
3948
|
console.log(JSON.stringify({
|
|
3910
3949
|
success: true,
|
|
3911
3950
|
environment: "production",
|
|
3951
|
+
durationMs: elapsed,
|
|
3912
3952
|
agents: {
|
|
3913
3953
|
created: syncResult.agents?.created || [],
|
|
3914
3954
|
updated: syncResult.agents?.updated || [],
|
|
@@ -3923,26 +3963,71 @@ var deployCommand = new Command7("deploy").description("Deploy all resources to
|
|
|
3923
3963
|
created: syncResult.roles?.created || [],
|
|
3924
3964
|
updated: syncResult.roles?.updated || [],
|
|
3925
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 || []
|
|
3926
3982
|
}
|
|
3927
3983
|
}));
|
|
3928
3984
|
} else {
|
|
3929
3985
|
console.log();
|
|
3930
3986
|
console.log(chalk8.green("Success!"), "All resources deployed to production");
|
|
3931
3987
|
console.log();
|
|
3932
|
-
if (
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
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
|
+
}
|
|
3937
4009
|
}
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
const
|
|
3943
|
-
|
|
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
|
+
}
|
|
3944
4025
|
}
|
|
3945
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
|
+
}
|
|
3946
4031
|
console.log();
|
|
3947
4032
|
console.log(chalk8.gray("Test your agents:"));
|
|
3948
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"}'`));
|
|
@@ -3970,6 +4055,22 @@ var deployCommand = new Command7("deploy").description("Deploy all resources to
|
|
|
3970
4055
|
spinner.fail("Deployment failed");
|
|
3971
4056
|
console.log();
|
|
3972
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
|
+
}
|
|
3973
4074
|
console.log();
|
|
3974
4075
|
}
|
|
3975
4076
|
process.exit(1);
|
|
@@ -7317,6 +7418,7 @@ init_credentials();
|
|
|
7317
7418
|
import { Command as Command18 } from "commander";
|
|
7318
7419
|
import chalk20 from "chalk";
|
|
7319
7420
|
import ora14 from "ora";
|
|
7421
|
+
init_convex();
|
|
7320
7422
|
|
|
7321
7423
|
// src/cli/utils/triggers.ts
|
|
7322
7424
|
init_credentials();
|
|
@@ -7456,6 +7558,20 @@ async function retryImmediateExecution(eventId, environment) {
|
|
|
7456
7558
|
}
|
|
7457
7559
|
|
|
7458
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
|
+
}
|
|
7459
7575
|
async function ensureAuth5() {
|
|
7460
7576
|
const cwd = process.cwd();
|
|
7461
7577
|
const nonInteractive = !isInteractive();
|
|
@@ -7882,11 +7998,11 @@ triggersCommand.command("logs [slug]").description("View trigger execution histo
|
|
|
7882
7998
|
const spinner = ora14();
|
|
7883
7999
|
try {
|
|
7884
8000
|
spinner.start("Fetching execution logs");
|
|
7885
|
-
const executions = await listTriggerExecutions({
|
|
8001
|
+
const executions = await withTriggerAuthRetry(() => listTriggerExecutions({
|
|
7886
8002
|
environment: opts.env,
|
|
7887
8003
|
triggerSlug: slug,
|
|
7888
8004
|
limit: parseInt(opts.limit, 10)
|
|
7889
|
-
});
|
|
8005
|
+
}));
|
|
7890
8006
|
spinner.succeed(`Found ${executions.length} executions`);
|
|
7891
8007
|
if (opts.json) {
|
|
7892
8008
|
console.log(JSON.stringify(executions, null, 2));
|
|
@@ -7945,15 +8061,20 @@ triggersCommand.command("log <identifier>").description("View detailed trigger e
|
|
|
7945
8061
|
await ensureAuth5();
|
|
7946
8062
|
const spinner = ora14();
|
|
7947
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
|
+
}
|
|
7948
8069
|
let eventId = identifier;
|
|
7949
|
-
const isConvexId = identifier
|
|
8070
|
+
const isConvexId = /^[0-9a-zA-Z]{20,}$/.test(identifier);
|
|
7950
8071
|
if (!isConvexId) {
|
|
7951
8072
|
spinner.start("Resolving trigger slug to latest execution");
|
|
7952
|
-
const executions = await listTriggerExecutions({
|
|
8073
|
+
const executions = await withTriggerAuthRetry(() => listTriggerExecutions({
|
|
7953
8074
|
environment: opts.env,
|
|
7954
8075
|
triggerSlug: identifier,
|
|
7955
|
-
limit:
|
|
7956
|
-
});
|
|
8076
|
+
limit: nth
|
|
8077
|
+
}));
|
|
7957
8078
|
if (!executions.length) {
|
|
7958
8079
|
spinner.fail(`No executions found for trigger "${identifier}" in ${opts.env}`);
|
|
7959
8080
|
if (opts.json) {
|
|
@@ -7961,16 +8082,16 @@ triggersCommand.command("log <identifier>").description("View detailed trigger e
|
|
|
7961
8082
|
}
|
|
7962
8083
|
process.exit(1);
|
|
7963
8084
|
}
|
|
7964
|
-
const idx =
|
|
8085
|
+
const idx = nth - 1;
|
|
7965
8086
|
if (idx >= executions.length) {
|
|
7966
|
-
spinner.fail(`Only ${executions.length} executions found, cannot get #${
|
|
8087
|
+
spinner.fail(`Only ${executions.length} executions found, cannot get #${nth}`);
|
|
7967
8088
|
process.exit(1);
|
|
7968
8089
|
}
|
|
7969
8090
|
eventId = executions[idx]._id;
|
|
7970
8091
|
spinner.succeed(`Found execution for "${identifier}"`);
|
|
7971
8092
|
}
|
|
7972
8093
|
spinner.start("Fetching execution detail");
|
|
7973
|
-
const event = await getTriggerExecutionDetail(eventId, opts.env);
|
|
8094
|
+
const event = await withTriggerAuthRetry(() => getTriggerExecutionDetail(eventId, opts.env));
|
|
7974
8095
|
if (!event) {
|
|
7975
8096
|
spinner.fail("Execution not found");
|
|
7976
8097
|
if (opts.json) {
|
|
@@ -8250,27 +8371,182 @@ triggersCommand.command("fire <slug>").description("Manually fire a trigger").op
|
|
|
8250
8371
|
}
|
|
8251
8372
|
});
|
|
8252
8373
|
|
|
8253
|
-
// src/cli/commands/
|
|
8374
|
+
// src/cli/commands/threads.ts
|
|
8254
8375
|
init_credentials();
|
|
8255
8376
|
import { Command as Command19 } from "commander";
|
|
8256
8377
|
import chalk21 from "chalk";
|
|
8257
8378
|
import ora15 from "ora";
|
|
8379
|
+
|
|
8380
|
+
// src/cli/utils/threads.ts
|
|
8381
|
+
init_credentials();
|
|
8382
|
+
init_config();
|
|
8258
8383
|
init_convex();
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
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() {
|
|
8264
8545
|
const cwd = process.cwd();
|
|
8265
8546
|
const nonInteractive = !isInteractive();
|
|
8266
|
-
const jsonMode = !!options.json;
|
|
8267
8547
|
if (!hasProject(cwd)) {
|
|
8268
8548
|
if (nonInteractive) {
|
|
8269
|
-
|
|
8270
|
-
console.log(JSON.stringify({ success: false, error: "No struere.json found" }));
|
|
8271
|
-
} else {
|
|
8272
|
-
console.log(chalk21.red("No struere.json found. Run struere init first."));
|
|
8273
|
-
}
|
|
8549
|
+
console.error(chalk21.red("No struere.json found. Run struere init first."));
|
|
8274
8550
|
process.exit(1);
|
|
8275
8551
|
}
|
|
8276
8552
|
console.log(chalk21.yellow("No struere.json found - initializing project..."));
|
|
@@ -8281,24 +8557,11 @@ var compilePromptCommand = new Command19("compile-prompt").description("Compile
|
|
|
8281
8557
|
}
|
|
8282
8558
|
console.log();
|
|
8283
8559
|
}
|
|
8284
|
-
const project = loadProject(cwd);
|
|
8285
|
-
if (!project) {
|
|
8286
|
-
if (jsonMode) {
|
|
8287
|
-
console.log(JSON.stringify({ success: false, error: "Failed to load struere.json" }));
|
|
8288
|
-
} else {
|
|
8289
|
-
console.log(chalk21.red("Failed to load struere.json"));
|
|
8290
|
-
}
|
|
8291
|
-
process.exit(1);
|
|
8292
|
-
}
|
|
8293
8560
|
let credentials = loadCredentials();
|
|
8294
8561
|
const apiKey = getApiKey();
|
|
8295
8562
|
if (!credentials && !apiKey) {
|
|
8296
8563
|
if (nonInteractive) {
|
|
8297
|
-
|
|
8298
|
-
console.log(JSON.stringify({ success: false, error: "Not authenticated. Set STRUERE_API_KEY or run struere login." }));
|
|
8299
|
-
} else {
|
|
8300
|
-
console.log(chalk21.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
8301
|
-
}
|
|
8564
|
+
console.error(chalk21.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
8302
8565
|
process.exit(1);
|
|
8303
8566
|
}
|
|
8304
8567
|
console.log(chalk21.yellow("Not logged in - authenticating..."));
|
|
@@ -8310,55 +8573,351 @@ var compilePromptCommand = new Command19("compile-prompt").description("Compile
|
|
|
8310
8573
|
}
|
|
8311
8574
|
console.log();
|
|
8312
8575
|
}
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
}
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
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);
|
|
8331
8618
|
}
|
|
8332
|
-
|
|
8333
|
-
|
|
8334
|
-
|
|
8335
|
-
|
|
8336
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
8339
|
-
|
|
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)
|
|
8340
8630
|
});
|
|
8341
|
-
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
console.log(JSON.stringify({ success: false, error }));
|
|
8346
|
-
} else {
|
|
8347
|
-
spinner.fail("Failed to compile prompt");
|
|
8348
|
-
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;
|
|
8349
8635
|
}
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
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 }));
|
|
8355
8658
|
} else {
|
|
8356
|
-
|
|
8659
|
+
console.log(chalk21.red("Error:"), message);
|
|
8357
8660
|
}
|
|
8358
8661
|
process.exit(1);
|
|
8359
8662
|
}
|
|
8360
|
-
|
|
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)
|
|
8920
|
+
spinner.succeed("Compiled prompt");
|
|
8362
8921
|
if (jsonMode) {
|
|
8363
8922
|
console.log(JSON.stringify({
|
|
8364
8923
|
success: true,
|
|
@@ -8368,27 +8927,33 @@ var compilePromptCommand = new Command19("compile-prompt").description("Compile
|
|
|
8368
8927
|
}, null, 2));
|
|
8369
8928
|
} else if (options.raw) {
|
|
8370
8929
|
console.log();
|
|
8371
|
-
console.log(
|
|
8372
|
-
console.log(
|
|
8930
|
+
console.log(chalk22.bold("Raw System Prompt"));
|
|
8931
|
+
console.log(chalk22.gray("\u2500".repeat(60)));
|
|
8373
8932
|
console.log(result.raw);
|
|
8374
|
-
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
|
+
}
|
|
8375
8937
|
} else {
|
|
8376
8938
|
console.log();
|
|
8377
|
-
console.log(
|
|
8378
|
-
console.log(
|
|
8939
|
+
console.log(chalk22.bold("Compiled System Prompt"));
|
|
8940
|
+
console.log(chalk22.gray("\u2500".repeat(60)));
|
|
8379
8941
|
console.log(result.compiled);
|
|
8380
|
-
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
|
+
}
|
|
8381
8946
|
}
|
|
8382
8947
|
});
|
|
8383
8948
|
|
|
8384
8949
|
// src/cli/commands/run-tool.ts
|
|
8385
8950
|
init_credentials();
|
|
8386
|
-
import { Command as
|
|
8387
|
-
import
|
|
8388
|
-
import
|
|
8951
|
+
import { Command as Command21 } from "commander";
|
|
8952
|
+
import chalk23 from "chalk";
|
|
8953
|
+
import ora17 from "ora";
|
|
8389
8954
|
init_convex();
|
|
8390
|
-
var runToolCommand = new
|
|
8391
|
-
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();
|
|
8392
8957
|
const cwd = process.cwd();
|
|
8393
8958
|
const nonInteractive = !isInteractive();
|
|
8394
8959
|
const jsonMode = !!options.json;
|
|
@@ -8397,11 +8962,11 @@ var runToolCommand = new Command20("run-tool").description("Run a tool as it wou
|
|
|
8397
8962
|
if (jsonMode) {
|
|
8398
8963
|
console.log(JSON.stringify({ success: false, error: "No struere.json found" }));
|
|
8399
8964
|
} else {
|
|
8400
|
-
console.log(
|
|
8965
|
+
console.log(chalk23.red("No struere.json found. Run struere init first."));
|
|
8401
8966
|
}
|
|
8402
8967
|
process.exit(1);
|
|
8403
8968
|
}
|
|
8404
|
-
console.log(
|
|
8969
|
+
console.log(chalk23.yellow("No struere.json found - initializing project..."));
|
|
8405
8970
|
console.log();
|
|
8406
8971
|
const success = await runInit(cwd);
|
|
8407
8972
|
if (!success) {
|
|
@@ -8414,7 +8979,7 @@ var runToolCommand = new Command20("run-tool").description("Run a tool as it wou
|
|
|
8414
8979
|
if (jsonMode) {
|
|
8415
8980
|
console.log(JSON.stringify({ success: false, error: "Failed to load struere.json" }));
|
|
8416
8981
|
} else {
|
|
8417
|
-
console.log(
|
|
8982
|
+
console.log(chalk23.red("Failed to load struere.json"));
|
|
8418
8983
|
}
|
|
8419
8984
|
process.exit(1);
|
|
8420
8985
|
}
|
|
@@ -8425,15 +8990,15 @@ var runToolCommand = new Command20("run-tool").description("Run a tool as it wou
|
|
|
8425
8990
|
if (jsonMode) {
|
|
8426
8991
|
console.log(JSON.stringify({ success: false, error: "Not authenticated. Set STRUERE_API_KEY or run struere login." }));
|
|
8427
8992
|
} else {
|
|
8428
|
-
console.log(
|
|
8993
|
+
console.log(chalk23.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
8429
8994
|
}
|
|
8430
8995
|
process.exit(1);
|
|
8431
8996
|
}
|
|
8432
|
-
console.log(
|
|
8997
|
+
console.log(chalk23.yellow("Not logged in - authenticating..."));
|
|
8433
8998
|
console.log();
|
|
8434
8999
|
credentials = await performLogin();
|
|
8435
9000
|
if (!credentials) {
|
|
8436
|
-
console.log(
|
|
9001
|
+
console.log(chalk23.red("Authentication failed"));
|
|
8437
9002
|
process.exit(1);
|
|
8438
9003
|
}
|
|
8439
9004
|
console.log();
|
|
@@ -8451,26 +9016,26 @@ var runToolCommand = new Command20("run-tool").description("Run a tool as it wou
|
|
|
8451
9016
|
if (jsonMode) {
|
|
8452
9017
|
console.log(JSON.stringify({ success: false, error: `Invalid JSON: ${err instanceof Error ? err.message : String(err)}` }));
|
|
8453
9018
|
} else {
|
|
8454
|
-
console.log(
|
|
9019
|
+
console.log(chalk23.red(`Invalid JSON: ${err instanceof Error ? err.message : String(err)}`));
|
|
8455
9020
|
}
|
|
8456
9021
|
process.exit(1);
|
|
8457
9022
|
}
|
|
8458
9023
|
const environment = options.env;
|
|
8459
9024
|
if (!options.agent && !toolName.includes(".") && !jsonMode) {
|
|
8460
|
-
console.log(
|
|
9025
|
+
console.log(chalk23.dim(`Tip: Running without agent context. Use --agent <slug> to run with a specific agent.`));
|
|
8461
9026
|
}
|
|
8462
9027
|
if (environment === "production" && !options.confirm && !nonInteractive) {
|
|
8463
9028
|
const readline = await import("readline");
|
|
8464
9029
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
8465
9030
|
await new Promise((resolve) => {
|
|
8466
|
-
rl.question(
|
|
9031
|
+
rl.question(chalk23.yellow(`WARNING: Running tool against PRODUCTION environment.
|
|
8467
9032
|
This will execute real operations with real data.
|
|
8468
9033
|
Press Enter to continue or Ctrl+C to cancel: `), resolve);
|
|
8469
9034
|
});
|
|
8470
9035
|
rl.close();
|
|
8471
9036
|
}
|
|
8472
9037
|
if (!jsonMode) {
|
|
8473
|
-
spinner.start(`Running ${
|
|
9038
|
+
spinner.start(`Running ${chalk23.cyan(toolName)}${options.agent ? ` on ${chalk23.cyan(options.agent)}` : ""} (${environment})`);
|
|
8474
9039
|
}
|
|
8475
9040
|
const doRunTool = async () => {
|
|
8476
9041
|
return runTool({
|
|
@@ -8487,7 +9052,7 @@ var runToolCommand = new Command20("run-tool").description("Run a tool as it wou
|
|
|
8487
9052
|
console.log(JSON.stringify({ success: false, error }));
|
|
8488
9053
|
} else {
|
|
8489
9054
|
spinner.fail("Failed to run tool");
|
|
8490
|
-
console.log(
|
|
9055
|
+
console.log(chalk23.red("Error:"), error);
|
|
8491
9056
|
}
|
|
8492
9057
|
process.exit(1);
|
|
8493
9058
|
}
|
|
@@ -8503,45 +9068,51 @@ var runToolCommand = new Command20("run-tool").description("Run a tool as it wou
|
|
|
8503
9068
|
if (jsonMode) {
|
|
8504
9069
|
console.log(JSON.stringify({ success: false, error: `${result.errorType}: ${result.message}`, result }));
|
|
8505
9070
|
} else {
|
|
8506
|
-
spinner.fail(
|
|
9071
|
+
spinner.fail(chalk23.red(`${result.errorType}: ${result.message}`));
|
|
8507
9072
|
}
|
|
8508
9073
|
process.exit(1);
|
|
8509
9074
|
}
|
|
8510
9075
|
if (!jsonMode) {
|
|
8511
|
-
spinner.succeed(`Ran ${
|
|
9076
|
+
spinner.succeed(`Ran ${chalk23.cyan(toolName)}${result.agent ? ` on ${chalk23.cyan(result.agent.slug)}` : ""} (${result.environment}) in ${result.durationMs}ms`);
|
|
8512
9077
|
}
|
|
8513
9078
|
if (jsonMode) {
|
|
8514
9079
|
console.log(JSON.stringify(result, null, 2));
|
|
8515
9080
|
} else {
|
|
8516
9081
|
console.log();
|
|
8517
|
-
console.log(
|
|
9082
|
+
console.log(chalk23.dim("\u2500".repeat(50)));
|
|
8518
9083
|
console.log(JSON.stringify(result.result, null, 2));
|
|
8519
|
-
console.log(
|
|
9084
|
+
console.log(chalk23.dim("\u2500".repeat(50)));
|
|
8520
9085
|
console.log();
|
|
8521
|
-
console.log(
|
|
9086
|
+
console.log(chalk23.dim(`Identity: ${result.identity.actorType} (${result.identity.identityMode} mode)`));
|
|
8522
9087
|
}
|
|
8523
9088
|
});
|
|
8524
9089
|
|
|
8525
9090
|
// src/cli/commands/chat.ts
|
|
8526
9091
|
init_credentials();
|
|
8527
|
-
import { Command as
|
|
8528
|
-
import
|
|
8529
|
-
import
|
|
9092
|
+
import { Command as Command22 } from "commander";
|
|
9093
|
+
import chalk24 from "chalk";
|
|
9094
|
+
import ora18 from "ora";
|
|
8530
9095
|
import readline from "readline";
|
|
8531
9096
|
init_convex();
|
|
8532
9097
|
function printExecutionMeta(meta) {
|
|
8533
|
-
console.log(
|
|
9098
|
+
console.log(chalk24.dim(`Model: ${meta.model} | Duration: ${meta.durationMs}ms`));
|
|
8534
9099
|
if (meta.toolCallSummary.length > 0) {
|
|
8535
|
-
console.log(
|
|
9100
|
+
console.log(chalk24.dim("Tool calls:"));
|
|
8536
9101
|
for (const tc of meta.toolCallSummary) {
|
|
8537
|
-
const status = tc.status === "success" ?
|
|
8538
|
-
const err = tc.errorMessage ?
|
|
8539
|
-
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}`));
|
|
8540
9105
|
}
|
|
8541
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
|
+
}
|
|
8542
9113
|
}
|
|
8543
|
-
var chatCommand = new
|
|
8544
|
-
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();
|
|
8545
9116
|
const cwd = process.cwd();
|
|
8546
9117
|
const nonInteractive = !isInteractive();
|
|
8547
9118
|
const jsonMode = !!options.json;
|
|
@@ -8550,11 +9121,11 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8550
9121
|
if (jsonMode) {
|
|
8551
9122
|
console.log(JSON.stringify({ success: false, error: "No struere.json found" }));
|
|
8552
9123
|
} else {
|
|
8553
|
-
console.log(
|
|
9124
|
+
console.log(chalk24.red("No struere.json found. Run struere init first."));
|
|
8554
9125
|
}
|
|
8555
9126
|
process.exit(1);
|
|
8556
9127
|
}
|
|
8557
|
-
console.log(
|
|
9128
|
+
console.log(chalk24.yellow("No struere.json found - initializing project..."));
|
|
8558
9129
|
console.log();
|
|
8559
9130
|
const success = await runInit(cwd);
|
|
8560
9131
|
if (!success) {
|
|
@@ -8567,7 +9138,7 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8567
9138
|
if (jsonMode) {
|
|
8568
9139
|
console.log(JSON.stringify({ success: false, error: "Failed to load struere.json" }));
|
|
8569
9140
|
} else {
|
|
8570
|
-
console.log(
|
|
9141
|
+
console.log(chalk24.red("Failed to load struere.json"));
|
|
8571
9142
|
}
|
|
8572
9143
|
process.exit(1);
|
|
8573
9144
|
}
|
|
@@ -8578,15 +9149,15 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8578
9149
|
if (jsonMode) {
|
|
8579
9150
|
console.log(JSON.stringify({ success: false, error: "Not authenticated. Set STRUERE_API_KEY or run struere login." }));
|
|
8580
9151
|
} else {
|
|
8581
|
-
console.log(
|
|
9152
|
+
console.log(chalk24.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
8582
9153
|
}
|
|
8583
9154
|
process.exit(1);
|
|
8584
9155
|
}
|
|
8585
|
-
console.log(
|
|
9156
|
+
console.log(chalk24.yellow("Not logged in - authenticating..."));
|
|
8586
9157
|
console.log();
|
|
8587
9158
|
credentials = await performLogin();
|
|
8588
9159
|
if (!credentials) {
|
|
8589
|
-
console.log(
|
|
9160
|
+
console.log(chalk24.red("Authentication failed"));
|
|
8590
9161
|
process.exit(1);
|
|
8591
9162
|
}
|
|
8592
9163
|
console.log();
|
|
@@ -8597,14 +9168,14 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8597
9168
|
if (jsonMode) {
|
|
8598
9169
|
console.log(JSON.stringify({ success: false, error: "--phone is required when using --router" }));
|
|
8599
9170
|
} else {
|
|
8600
|
-
console.log(
|
|
9171
|
+
console.log(chalk24.red("--phone is required when using --router"));
|
|
8601
9172
|
}
|
|
8602
9173
|
process.exit(1);
|
|
8603
9174
|
}
|
|
8604
9175
|
if (environment === "production" && !nonInteractive && !options.confirm) {
|
|
8605
9176
|
const confirmRl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
8606
9177
|
await new Promise((resolve) => {
|
|
8607
|
-
confirmRl.question(
|
|
9178
|
+
confirmRl.question(chalk24.yellow(`WARNING: Chatting with agent in PRODUCTION environment.
|
|
8608
9179
|
Press Enter to continue or Ctrl+C to cancel: `), resolve);
|
|
8609
9180
|
});
|
|
8610
9181
|
confirmRl.close();
|
|
@@ -8632,6 +9203,7 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8632
9203
|
signal
|
|
8633
9204
|
});
|
|
8634
9205
|
};
|
|
9206
|
+
let threadId = options.thread;
|
|
8635
9207
|
if (options.message) {
|
|
8636
9208
|
if (!jsonMode) {
|
|
8637
9209
|
spinner.start("Sending message...");
|
|
@@ -8646,7 +9218,7 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8646
9218
|
if (jsonMode) {
|
|
8647
9219
|
console.log(JSON.stringify({ success: false, error: "Authentication failed" }));
|
|
8648
9220
|
} else {
|
|
8649
|
-
console.log(
|
|
9221
|
+
console.log(chalk24.red("Authentication failed"));
|
|
8650
9222
|
}
|
|
8651
9223
|
process.exit(1);
|
|
8652
9224
|
}
|
|
@@ -8661,7 +9233,7 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8661
9233
|
console.log(JSON.stringify({ success: false, error }));
|
|
8662
9234
|
} else {
|
|
8663
9235
|
spinner.fail("Failed to send message");
|
|
8664
|
-
console.log(
|
|
9236
|
+
console.log(chalk24.red("Error:"), error);
|
|
8665
9237
|
}
|
|
8666
9238
|
process.exit(1);
|
|
8667
9239
|
}
|
|
@@ -8688,36 +9260,39 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8688
9260
|
console.log("\u2500".repeat(60));
|
|
8689
9261
|
console.log();
|
|
8690
9262
|
if (routerResult.routedToAgent) {
|
|
8691
|
-
console.log(
|
|
9263
|
+
console.log(chalk24.green(`${routerResult.routedToAgent}`) + chalk24.dim(` (${routerResult.routedToAgentSlug})`) + chalk24.magenta(" \u2190 routed") + chalk24.dim(":"));
|
|
8692
9264
|
} else {
|
|
8693
|
-
console.log(
|
|
9265
|
+
console.log(chalk24.green("Agent:"));
|
|
8694
9266
|
}
|
|
8695
9267
|
console.log(result.message);
|
|
8696
9268
|
console.log();
|
|
8697
9269
|
if (options.verbose) {
|
|
8698
|
-
console.log(
|
|
8699
|
-
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)`));
|
|
8700
9272
|
if (result._executionMeta) {
|
|
8701
9273
|
printExecutionMeta(result._executionMeta);
|
|
8702
9274
|
}
|
|
8703
9275
|
} else {
|
|
8704
|
-
console.log(
|
|
9276
|
+
console.log(chalk24.dim(`Thread: ${result.threadId} | Tokens: ${result.usage.totalTokens}`));
|
|
8705
9277
|
}
|
|
8706
9278
|
console.log();
|
|
8707
9279
|
console.log("\u2500".repeat(60));
|
|
8708
9280
|
}
|
|
8709
|
-
|
|
9281
|
+
if (options.follow && isInteractive()) {
|
|
9282
|
+
threadId = result.threadId;
|
|
9283
|
+
} else {
|
|
9284
|
+
return;
|
|
9285
|
+
}
|
|
8710
9286
|
}
|
|
8711
|
-
const headerLabel = isRouterMode ? `router ${
|
|
8712
|
-
console.log(
|
|
8713
|
-
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"));
|
|
8714
9290
|
console.log();
|
|
8715
|
-
let threadId = options.thread;
|
|
8716
9291
|
let processing = false;
|
|
8717
9292
|
let generation = 0;
|
|
8718
9293
|
let currentAbort = null;
|
|
8719
9294
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
8720
|
-
rl.setPrompt(
|
|
9295
|
+
rl.setPrompt(chalk24.cyan("You: "));
|
|
8721
9296
|
rl.prompt();
|
|
8722
9297
|
rl.on("SIGINT", () => {
|
|
8723
9298
|
if (processing) {
|
|
@@ -8729,7 +9304,7 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8729
9304
|
spinner.stop();
|
|
8730
9305
|
processing = false;
|
|
8731
9306
|
console.log();
|
|
8732
|
-
console.log(
|
|
9307
|
+
console.log(chalk24.yellow("Cancelled"));
|
|
8733
9308
|
console.log();
|
|
8734
9309
|
rl.resume();
|
|
8735
9310
|
rl.prompt();
|
|
@@ -8767,7 +9342,7 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8767
9342
|
if (thisGeneration !== generation)
|
|
8768
9343
|
return;
|
|
8769
9344
|
if (!credentials) {
|
|
8770
|
-
console.log(
|
|
9345
|
+
console.log(chalk24.red("Authentication failed"));
|
|
8771
9346
|
rl.close();
|
|
8772
9347
|
return;
|
|
8773
9348
|
}
|
|
@@ -8781,7 +9356,7 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8781
9356
|
}
|
|
8782
9357
|
if (error) {
|
|
8783
9358
|
spinner.fail("");
|
|
8784
|
-
console.log(
|
|
9359
|
+
console.log(chalk24.red("Error:"), error);
|
|
8785
9360
|
processing = false;
|
|
8786
9361
|
currentAbort = null;
|
|
8787
9362
|
rl.resume();
|
|
@@ -8801,20 +9376,20 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8801
9376
|
const interactiveRouterResult = result;
|
|
8802
9377
|
console.log();
|
|
8803
9378
|
if (interactiveRouterResult.routedToAgent) {
|
|
8804
|
-
console.log(
|
|
9379
|
+
console.log(chalk24.green(`${interactiveRouterResult.routedToAgent}`) + chalk24.dim(` (${interactiveRouterResult.routedToAgentSlug})`) + chalk24.magenta(" \u2190 routed") + chalk24.dim(":"));
|
|
8805
9380
|
} else {
|
|
8806
|
-
console.log(
|
|
9381
|
+
console.log(chalk24.green("Agent:"));
|
|
8807
9382
|
}
|
|
8808
9383
|
console.log(result.message);
|
|
8809
9384
|
console.log();
|
|
8810
9385
|
if (options.verbose) {
|
|
8811
|
-
console.log(
|
|
8812
|
-
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)`));
|
|
8813
9388
|
if (result._executionMeta) {
|
|
8814
9389
|
printExecutionMeta(result._executionMeta);
|
|
8815
9390
|
}
|
|
8816
9391
|
} else {
|
|
8817
|
-
console.log(
|
|
9392
|
+
console.log(chalk24.dim(`Thread: ${result.threadId} | Tokens: ${result.usage.totalTokens}`));
|
|
8818
9393
|
}
|
|
8819
9394
|
console.log();
|
|
8820
9395
|
processing = false;
|
|
@@ -8824,24 +9399,24 @@ var chatCommand = new Command21("chat").description("Chat with an agent or via a
|
|
|
8824
9399
|
});
|
|
8825
9400
|
rl.on("close", () => {
|
|
8826
9401
|
console.log();
|
|
8827
|
-
console.log(
|
|
9402
|
+
console.log(chalk24.dim("Goodbye!"));
|
|
8828
9403
|
process.exit(0);
|
|
8829
9404
|
});
|
|
8830
9405
|
});
|
|
8831
9406
|
|
|
8832
9407
|
// src/cli/commands/whatsapp.ts
|
|
8833
9408
|
init_credentials();
|
|
8834
|
-
import { Command as
|
|
8835
|
-
import
|
|
8836
|
-
async function
|
|
9409
|
+
import { Command as Command23 } from "commander";
|
|
9410
|
+
import chalk25 from "chalk";
|
|
9411
|
+
async function ensureAuth7() {
|
|
8837
9412
|
const cwd = process.cwd();
|
|
8838
9413
|
const nonInteractive = !isInteractive();
|
|
8839
9414
|
if (!hasProject(cwd)) {
|
|
8840
9415
|
if (nonInteractive) {
|
|
8841
|
-
console.error(
|
|
9416
|
+
console.error(chalk25.red("No struere.json found. Run struere init first."));
|
|
8842
9417
|
process.exit(1);
|
|
8843
9418
|
}
|
|
8844
|
-
console.log(
|
|
9419
|
+
console.log(chalk25.yellow("No struere.json found - initializing project..."));
|
|
8845
9420
|
console.log();
|
|
8846
9421
|
const success = await runInit(cwd);
|
|
8847
9422
|
if (!success) {
|
|
@@ -8853,14 +9428,14 @@ async function ensureAuth6() {
|
|
|
8853
9428
|
const apiKey = getApiKey();
|
|
8854
9429
|
if (!credentials && !apiKey) {
|
|
8855
9430
|
if (nonInteractive) {
|
|
8856
|
-
console.error(
|
|
9431
|
+
console.error(chalk25.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
8857
9432
|
process.exit(1);
|
|
8858
9433
|
}
|
|
8859
|
-
console.log(
|
|
9434
|
+
console.log(chalk25.yellow("Not logged in - authenticating..."));
|
|
8860
9435
|
console.log();
|
|
8861
9436
|
credentials = await performLogin();
|
|
8862
9437
|
if (!credentials) {
|
|
8863
|
-
console.log(
|
|
9438
|
+
console.log(chalk25.red("Authentication failed"));
|
|
8864
9439
|
process.exit(1);
|
|
8865
9440
|
}
|
|
8866
9441
|
console.log();
|
|
@@ -8903,13 +9478,13 @@ function connectionLabel(conn) {
|
|
|
8903
9478
|
function statusColor5(status) {
|
|
8904
9479
|
switch (status) {
|
|
8905
9480
|
case "connected":
|
|
8906
|
-
return
|
|
9481
|
+
return chalk25.green(status);
|
|
8907
9482
|
case "pending_setup":
|
|
8908
|
-
return
|
|
9483
|
+
return chalk25.yellow("pending");
|
|
8909
9484
|
case "disconnected":
|
|
8910
|
-
return
|
|
9485
|
+
return chalk25.red(status);
|
|
8911
9486
|
default:
|
|
8912
|
-
return
|
|
9487
|
+
return chalk25.gray(status);
|
|
8913
9488
|
}
|
|
8914
9489
|
}
|
|
8915
9490
|
function assignmentLabel(conn) {
|
|
@@ -8921,11 +9496,11 @@ function assignmentLabel(conn) {
|
|
|
8921
9496
|
return `Router: ${conn.routerId.slice(-8)}`;
|
|
8922
9497
|
if (conn.agentId)
|
|
8923
9498
|
return `Agent: ${conn.agentId.slice(-8)}`;
|
|
8924
|
-
return
|
|
9499
|
+
return chalk25.gray("none");
|
|
8925
9500
|
}
|
|
8926
|
-
var whatsappCommand = new
|
|
9501
|
+
var whatsappCommand = new Command23("whatsapp").description("Manage WhatsApp connections and routing");
|
|
8927
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) => {
|
|
8928
|
-
await
|
|
9503
|
+
await ensureAuth7();
|
|
8929
9504
|
const env = opts.env;
|
|
8930
9505
|
const out = createOutput();
|
|
8931
9506
|
out.start("Fetching WhatsApp connections");
|
|
@@ -8943,7 +9518,7 @@ whatsappCommand.command("list").description("List WhatsApp connections with rout
|
|
|
8943
9518
|
}
|
|
8944
9519
|
console.log();
|
|
8945
9520
|
if (connections.length === 0) {
|
|
8946
|
-
console.log(
|
|
9521
|
+
console.log(chalk25.gray(" No WhatsApp connections found"));
|
|
8947
9522
|
console.log();
|
|
8948
9523
|
return;
|
|
8949
9524
|
}
|
|
@@ -8954,8 +9529,8 @@ whatsappCommand.command("list").description("List WhatsApp connections with rout
|
|
|
8954
9529
|
{ key: "assignment", label: "Assignment", width: 30 },
|
|
8955
9530
|
{ key: "id", label: "ID", width: 16 }
|
|
8956
9531
|
], connections.map((c) => ({
|
|
8957
|
-
label: c.label ||
|
|
8958
|
-
phone: c.phoneNumber ? `+${c.phoneNumber}` :
|
|
9532
|
+
label: c.label || chalk25.gray("-"),
|
|
9533
|
+
phone: c.phoneNumber ? `+${c.phoneNumber}` : chalk25.gray("-"),
|
|
8959
9534
|
status: statusColor5(c.status),
|
|
8960
9535
|
assignment: assignmentLabel(c),
|
|
8961
9536
|
id: c._id.slice(-12)
|
|
@@ -8963,7 +9538,7 @@ whatsappCommand.command("list").description("List WhatsApp connections with rout
|
|
|
8963
9538
|
console.log();
|
|
8964
9539
|
});
|
|
8965
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) => {
|
|
8966
|
-
await
|
|
9541
|
+
await ensureAuth7();
|
|
8967
9542
|
const env = opts.env;
|
|
8968
9543
|
const out = createOutput();
|
|
8969
9544
|
const conn = await resolveConnection(env, connection, out);
|
|
@@ -8999,7 +9574,7 @@ whatsappCommand.command("set-router <connection> <router-slug>").description("As
|
|
|
8999
9574
|
console.log();
|
|
9000
9575
|
});
|
|
9001
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) => {
|
|
9002
|
-
await
|
|
9577
|
+
await ensureAuth7();
|
|
9003
9578
|
const env = opts.env;
|
|
9004
9579
|
const out = createOutput();
|
|
9005
9580
|
const conn = await resolveConnection(env, connection, out);
|
|
@@ -9025,10 +9600,927 @@ whatsappCommand.command("set-agent <connection> <agent-slug>").description("Assi
|
|
|
9025
9600
|
out.succeed(`Connection ${connectionLabel(conn)} now assigned to agent ${agent.name} (${agentSlug})`);
|
|
9026
9601
|
console.log();
|
|
9027
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
|
+
});
|
|
9028
10520
|
// package.json
|
|
9029
10521
|
var package_default = {
|
|
9030
10522
|
name: "struere",
|
|
9031
|
-
version: "0.
|
|
10523
|
+
version: "0.13.0",
|
|
9032
10524
|
description: "Build, test, and deploy AI agents",
|
|
9033
10525
|
keywords: [
|
|
9034
10526
|
"ai",
|
|
@@ -9146,8 +10638,11 @@ program.addCommand(evalCommand);
|
|
|
9146
10638
|
program.addCommand(templatesCommand);
|
|
9147
10639
|
program.addCommand(integrationCommand);
|
|
9148
10640
|
program.addCommand(triggersCommand);
|
|
10641
|
+
program.addCommand(threadsCommand);
|
|
9149
10642
|
program.addCommand(compilePromptCommand);
|
|
9150
10643
|
program.addCommand(runToolCommand);
|
|
9151
10644
|
program.addCommand(chatCommand);
|
|
9152
10645
|
program.addCommand(whatsappCommand);
|
|
10646
|
+
program.addCommand(diffCommand);
|
|
10647
|
+
program.addCommand(doctorCommand);
|
|
9153
10648
|
program.parse();
|