struere 0.8.2 → 0.9.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 +878 -321
- package/dist/cli/commands/deploy.d.ts.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/docs.d.ts.map +1 -1
- package/dist/cli/commands/entities.d.ts.map +1 -1
- package/dist/cli/commands/eval.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/pull.d.ts.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/sync.d.ts +15 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/templates.d.ts.map +1 -1
- package/dist/cli/commands/whoami.d.ts.map +1 -1
- package/dist/cli/index.js +1175 -611
- package/dist/cli/utils/runtime.d.ts +15 -0
- package/dist/cli/utils/runtime.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/bin/struere.js
CHANGED
|
@@ -19780,7 +19780,7 @@ async function browserLoginInternal(spinner) {
|
|
|
19780
19780
|
}
|
|
19781
19781
|
function printNextSteps() {
|
|
19782
19782
|
console.log(source_default.gray("You can now use:"));
|
|
19783
|
-
console.log(source_default.gray(" \u2022"), source_default.cyan("struere
|
|
19783
|
+
console.log(source_default.gray(" \u2022"), source_default.cyan("struere sync"), source_default.gray("- Sync resources to development"));
|
|
19784
19784
|
console.log(source_default.gray(" \u2022"), source_default.cyan("struere deploy"), source_default.gray("- Deploy your agent"));
|
|
19785
19785
|
console.log(source_default.gray(" \u2022"), source_default.cyan("struere status"), source_default.gray("- Compare local vs remote state"));
|
|
19786
19786
|
console.log();
|
|
@@ -20883,6 +20883,33 @@ function buildDocument(projectContext) {
|
|
|
20883
20883
|
lines.push("");
|
|
20884
20884
|
lines.push(`> This is a Struere workspace project. You define agents, entity types, roles, triggers, and custom tools here. The CLI syncs them to the Convex backend.`);
|
|
20885
20885
|
lines.push("");
|
|
20886
|
+
lines.push(`## Agent Usage`);
|
|
20887
|
+
lines.push("");
|
|
20888
|
+
lines.push("If you are an AI coding agent (Claude Code, Cursor, Copilot, etc.), use these patterns:");
|
|
20889
|
+
lines.push("");
|
|
20890
|
+
lines.push("**Auth**: Set `STRUERE_API_KEY` environment variable (no browser login needed)");
|
|
20891
|
+
lines.push("");
|
|
20892
|
+
lines.push("**Sync**: Use `struere sync` instead of `struere dev` \u2014 it syncs once and exits (no watch loop)");
|
|
20893
|
+
lines.push("```bash");
|
|
20894
|
+
lines.push("struere sync # sync to development + eval, then exit");
|
|
20895
|
+
lines.push("struere sync --json # machine-readable JSON output");
|
|
20896
|
+
lines.push("struere sync --env production # sync to production");
|
|
20897
|
+
lines.push("struere sync --force # skip deletion confirmations");
|
|
20898
|
+
lines.push("```");
|
|
20899
|
+
lines.push("");
|
|
20900
|
+
lines.push("**Deploy**: `struere deploy --force` skips confirmation prompts");
|
|
20901
|
+
lines.push("");
|
|
20902
|
+
lines.push("**JSON output**: Most commands support `--json` for structured output:");
|
|
20903
|
+
lines.push("```bash");
|
|
20904
|
+
lines.push("struere entities list <type> --json");
|
|
20905
|
+
lines.push("struere status --json");
|
|
20906
|
+
lines.push("struere deploy --json --force");
|
|
20907
|
+
lines.push("```");
|
|
20908
|
+
lines.push("");
|
|
20909
|
+
lines.push("**Non-interactive mode** is auto-detected when `STRUERE_API_KEY` is set or stdout is not a TTY. In this mode, all confirmation prompts are auto-accepted and spinners are replaced with plain text.");
|
|
20910
|
+
lines.push("");
|
|
20911
|
+
lines.push("**Exit codes**: All commands exit `0` on success, `1` on error. Check `$?` after execution.");
|
|
20912
|
+
lines.push("");
|
|
20886
20913
|
if (projectContext) {
|
|
20887
20914
|
lines.push(projectContext);
|
|
20888
20915
|
lines.push("");
|
|
@@ -20904,6 +20931,7 @@ function buildDocument(projectContext) {
|
|
|
20904
20931
|
lines.push("");
|
|
20905
20932
|
lines.push("| Command | Description |");
|
|
20906
20933
|
lines.push("|---------|-------------|");
|
|
20934
|
+
lines.push("| `struere sync` | One-shot sync to Convex and exit (agent-friendly) |");
|
|
20907
20935
|
lines.push("| `struere dev` | Watch files and sync to Convex on save |");
|
|
20908
20936
|
lines.push("| `struere deploy` | Push development config to production |");
|
|
20909
20937
|
lines.push("| `struere add agent\\|entity-type\\|role\\|trigger\\|eval\\|fixture <name>` | Scaffold a new resource |");
|
|
@@ -21014,7 +21042,7 @@ function buildDocument(projectContext) {
|
|
|
21014
21042
|
lines.push("- **Keep tools under 10 per agent.** Agents perform significantly worse when they have too many tools to choose from. If an agent needs more, split it into specialist agents and use `agent.chat` to orchestrate");
|
|
21015
21043
|
lines.push("- **Always ask the user before making assumptions.** The user may not be technical \u2014 help them accomplish what they want by asking the right questions and offering clear options");
|
|
21016
21044
|
lines.push("- **Always check the documentation before making changes.** Fetch the relevant doc link below to verify the correct API, field names, and patterns. Do not guess \u2014 wrong field names or patterns will cause silent failures");
|
|
21017
|
-
lines.push("- **Use `struere
|
|
21045
|
+
lines.push("- **Use `struere sync` to validate changes.** Run after editing files to sync to Convex. Use `struere dev` for continuous watch mode during manual development");
|
|
21018
21046
|
lines.push("- **Test with evals.** Write eval suites to catch regressions in agent behavior (`struere add eval <name>`)");
|
|
21019
21047
|
lines.push("");
|
|
21020
21048
|
lines.push(`## Documentation`);
|
|
@@ -21046,6 +21074,7 @@ function buildDocument(projectContext) {
|
|
|
21046
21074
|
lines.push("### CLI");
|
|
21047
21075
|
lines.push(`- [CLI Overview](${DOCS_BASE}/cli/overview.md)`);
|
|
21048
21076
|
lines.push(`- [struere init](${DOCS_BASE}/cli/init.md)`);
|
|
21077
|
+
lines.push(`- [struere sync](${DOCS_BASE}/cli/sync.md)`);
|
|
21049
21078
|
lines.push(`- [struere dev](${DOCS_BASE}/cli/dev.md)`);
|
|
21050
21079
|
lines.push(`- [struere add](${DOCS_BASE}/cli/add.md)`);
|
|
21051
21080
|
lines.push(`- [struere deploy](${DOCS_BASE}/cli/deploy.md)`);
|
|
@@ -21133,11 +21162,84 @@ var docsCommand = new Command("docs").description("Generate AI context files (CL
|
|
|
21133
21162
|
console.log();
|
|
21134
21163
|
});
|
|
21135
21164
|
|
|
21165
|
+
// src/cli/utils/runtime.ts
|
|
21166
|
+
function isInteractive2() {
|
|
21167
|
+
return process.stdout.isTTY === true && !process.env.CI && !getApiKey();
|
|
21168
|
+
}
|
|
21169
|
+
function createOutput() {
|
|
21170
|
+
if (isInteractive2()) {
|
|
21171
|
+
const spinner = ora();
|
|
21172
|
+
return {
|
|
21173
|
+
start(msg) {
|
|
21174
|
+
spinner.start(msg);
|
|
21175
|
+
},
|
|
21176
|
+
succeed(msg) {
|
|
21177
|
+
spinner.succeed(msg);
|
|
21178
|
+
},
|
|
21179
|
+
fail(msg) {
|
|
21180
|
+
spinner.fail(msg);
|
|
21181
|
+
},
|
|
21182
|
+
stop() {
|
|
21183
|
+
spinner.stop();
|
|
21184
|
+
},
|
|
21185
|
+
info(msg) {
|
|
21186
|
+
console.log(source_default.gray(msg));
|
|
21187
|
+
},
|
|
21188
|
+
warn(msg) {
|
|
21189
|
+
console.log(source_default.yellow(msg));
|
|
21190
|
+
},
|
|
21191
|
+
error(msg) {
|
|
21192
|
+
console.error(source_default.red(msg));
|
|
21193
|
+
},
|
|
21194
|
+
json(data) {
|
|
21195
|
+
console.log(JSON.stringify(data, null, 2));
|
|
21196
|
+
}
|
|
21197
|
+
};
|
|
21198
|
+
}
|
|
21199
|
+
return {
|
|
21200
|
+
start(msg) {
|
|
21201
|
+
console.log(msg);
|
|
21202
|
+
},
|
|
21203
|
+
succeed(msg) {
|
|
21204
|
+
console.log(msg);
|
|
21205
|
+
},
|
|
21206
|
+
fail(msg) {
|
|
21207
|
+
console.error(msg);
|
|
21208
|
+
},
|
|
21209
|
+
stop() {},
|
|
21210
|
+
info(msg) {
|
|
21211
|
+
console.log(msg);
|
|
21212
|
+
},
|
|
21213
|
+
warn(msg) {
|
|
21214
|
+
console.log(msg);
|
|
21215
|
+
},
|
|
21216
|
+
error(msg) {
|
|
21217
|
+
console.error(msg);
|
|
21218
|
+
},
|
|
21219
|
+
json(data) {
|
|
21220
|
+
console.log(JSON.stringify(data));
|
|
21221
|
+
}
|
|
21222
|
+
};
|
|
21223
|
+
}
|
|
21224
|
+
function isAuthError(error) {
|
|
21225
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
21226
|
+
return message.includes("Unauthenticated") || message.includes("OIDC") || message.includes("token") || message.includes("expired");
|
|
21227
|
+
}
|
|
21228
|
+
function isOrgAccessError(error) {
|
|
21229
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
21230
|
+
return message.includes("Access denied") || message.includes("not a member") || message.includes("Organization not found");
|
|
21231
|
+
}
|
|
21232
|
+
|
|
21136
21233
|
// src/cli/commands/init.ts
|
|
21137
21234
|
async function runInit(cwd, selectedOrg) {
|
|
21138
21235
|
const spinner = ora();
|
|
21236
|
+
const nonInteractive = !isInteractive2();
|
|
21139
21237
|
let credentials = loadCredentials();
|
|
21140
21238
|
if (!credentials) {
|
|
21239
|
+
if (nonInteractive) {
|
|
21240
|
+
console.log(source_default.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
21241
|
+
return false;
|
|
21242
|
+
}
|
|
21141
21243
|
console.log(source_default.yellow("Not logged in - authenticating..."));
|
|
21142
21244
|
console.log();
|
|
21143
21245
|
credentials = await performLogin();
|
|
@@ -21202,18 +21304,24 @@ async function runInit(cwd, selectedOrg) {
|
|
|
21202
21304
|
var initCommand = new Command("init").description("Initialize a new Struere organization project").argument("[project-name]", "Project name").option("-y, --yes", "Skip prompts and use defaults").option("--org <slug>", "Organization slug").action(async (projectNameArg, options) => {
|
|
21203
21305
|
const cwd = process.cwd();
|
|
21204
21306
|
const spinner = ora();
|
|
21307
|
+
const nonInteractive = !isInteractive2();
|
|
21205
21308
|
console.log();
|
|
21206
21309
|
console.log(source_default.bold("Struere CLI"));
|
|
21207
21310
|
console.log();
|
|
21208
21311
|
if (hasProject(cwd)) {
|
|
21209
21312
|
console.log(source_default.yellow("This project is already initialized."));
|
|
21210
21313
|
console.log();
|
|
21211
|
-
console.log(source_default.gray("Run"), source_default.cyan("struere
|
|
21314
|
+
console.log(source_default.gray("Run"), source_default.cyan("struere sync"), source_default.gray("to sync changes"));
|
|
21212
21315
|
console.log();
|
|
21213
21316
|
return;
|
|
21214
21317
|
}
|
|
21215
21318
|
let credentials = loadCredentials();
|
|
21216
|
-
|
|
21319
|
+
const apiKey = getApiKey();
|
|
21320
|
+
if (!credentials && !apiKey) {
|
|
21321
|
+
if (nonInteractive) {
|
|
21322
|
+
console.log(source_default.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
21323
|
+
process.exit(1);
|
|
21324
|
+
}
|
|
21217
21325
|
console.log(source_default.yellow("Not logged in - authenticating..."));
|
|
21218
21326
|
console.log();
|
|
21219
21327
|
credentials = await performLogin();
|
|
@@ -21223,8 +21331,10 @@ var initCommand = new Command("init").description("Initialize a new Struere orga
|
|
|
21223
21331
|
}
|
|
21224
21332
|
console.log();
|
|
21225
21333
|
}
|
|
21226
|
-
|
|
21227
|
-
|
|
21334
|
+
if (credentials) {
|
|
21335
|
+
console.log(source_default.green("\u2713"), "Logged in as", source_default.cyan(credentials.user.name || credentials.user.email));
|
|
21336
|
+
}
|
|
21337
|
+
const { organizations, error } = await listMyOrganizations(credentials?.token || "");
|
|
21228
21338
|
if (error) {
|
|
21229
21339
|
console.log(source_default.red("Failed to fetch organizations:"), error);
|
|
21230
21340
|
process.exit(1);
|
|
@@ -21244,6 +21354,10 @@ var initCommand = new Command("init").description("Initialize a new Struere orga
|
|
|
21244
21354
|
selectedOrg = found;
|
|
21245
21355
|
} else if (organizations.length === 1) {
|
|
21246
21356
|
selectedOrg = organizations[0];
|
|
21357
|
+
} else if (nonInteractive) {
|
|
21358
|
+
console.log(source_default.red("Multiple organizations found. Use --org <slug> to specify one."));
|
|
21359
|
+
console.log(source_default.gray("Available:"), organizations.map((o) => o.slug).join(", "));
|
|
21360
|
+
process.exit(1);
|
|
21247
21361
|
} else {
|
|
21248
21362
|
selectedOrg = await esm_default4({
|
|
21249
21363
|
message: "Select organization:",
|
|
@@ -21292,7 +21406,7 @@ var initCommand = new Command("init").description("Initialize a new Struere orga
|
|
|
21292
21406
|
console.log();
|
|
21293
21407
|
console.log(source_default.gray("Next steps:"));
|
|
21294
21408
|
console.log(source_default.gray(" 1."), source_default.cyan("struere add agent my-agent"), source_default.gray("- Create an agent"));
|
|
21295
|
-
console.log(source_default.gray(" 2."), source_default.cyan("struere
|
|
21409
|
+
console.log(source_default.gray(" 2."), source_default.cyan("struere sync"), source_default.gray("- Sync to development"));
|
|
21296
21410
|
console.log();
|
|
21297
21411
|
});
|
|
21298
21412
|
function slugify(name) {
|
|
@@ -21756,17 +21870,298 @@ function extractHandlerCode(handler) {
|
|
|
21756
21870
|
return code;
|
|
21757
21871
|
}
|
|
21758
21872
|
|
|
21873
|
+
// src/cli/commands/sync.ts
|
|
21874
|
+
async function performDevSync(cwd, organizationId) {
|
|
21875
|
+
const resources = await loadAllResources(cwd);
|
|
21876
|
+
if (resources.errors.length > 0) {
|
|
21877
|
+
throw new Error(`${resources.errors.length} resource loading error(s):
|
|
21878
|
+
${resources.errors.join(`
|
|
21879
|
+
`)}`);
|
|
21880
|
+
}
|
|
21881
|
+
const payload = extractSyncPayload(resources);
|
|
21882
|
+
const devResult = await syncOrganization({
|
|
21883
|
+
agents: payload.agents,
|
|
21884
|
+
entityTypes: payload.entityTypes,
|
|
21885
|
+
roles: payload.roles,
|
|
21886
|
+
triggers: payload.triggers,
|
|
21887
|
+
organizationId,
|
|
21888
|
+
environment: "development"
|
|
21889
|
+
});
|
|
21890
|
+
if (!devResult.success) {
|
|
21891
|
+
throw new Error(devResult.error || "Dev sync failed");
|
|
21892
|
+
}
|
|
21893
|
+
const hasEvalContent = payload.evalSuites && payload.evalSuites.length > 0 || payload.fixtures && payload.fixtures.length > 0;
|
|
21894
|
+
if (hasEvalContent) {
|
|
21895
|
+
const evalResult = await syncOrganization({
|
|
21896
|
+
agents: payload.agents,
|
|
21897
|
+
entityTypes: payload.entityTypes,
|
|
21898
|
+
roles: payload.roles,
|
|
21899
|
+
evalSuites: payload.evalSuites,
|
|
21900
|
+
fixtures: payload.fixtures,
|
|
21901
|
+
organizationId,
|
|
21902
|
+
environment: "eval"
|
|
21903
|
+
});
|
|
21904
|
+
if (!evalResult.success) {
|
|
21905
|
+
throw new Error(evalResult.error || "Eval sync failed");
|
|
21906
|
+
}
|
|
21907
|
+
}
|
|
21908
|
+
return devResult;
|
|
21909
|
+
}
|
|
21910
|
+
async function checkForDeletions(resources, organizationId, environment) {
|
|
21911
|
+
const { state: remoteState } = await getSyncState(organizationId, environment);
|
|
21912
|
+
if (!remoteState)
|
|
21913
|
+
return [];
|
|
21914
|
+
const payload = extractSyncPayload(resources);
|
|
21915
|
+
const localSlugs = {
|
|
21916
|
+
agents: new Set(payload.agents.map((a) => a.slug)),
|
|
21917
|
+
entityTypes: new Set(payload.entityTypes.map((et) => et.slug)),
|
|
21918
|
+
roles: new Set(payload.roles.map((r) => r.name)),
|
|
21919
|
+
evalSuites: new Set((payload.evalSuites || []).map((es) => es.slug)),
|
|
21920
|
+
triggers: new Set((payload.triggers || []).map((t) => t.slug))
|
|
21921
|
+
};
|
|
21922
|
+
const deletions = [];
|
|
21923
|
+
const deletedAgents = remoteState.agents.filter((a) => !localSlugs.agents.has(a.slug)).map((a) => a.name);
|
|
21924
|
+
if (deletedAgents.length > 0)
|
|
21925
|
+
deletions.push({ type: "Agents", remote: remoteState.agents.length, local: payload.agents.length, deleted: deletedAgents });
|
|
21926
|
+
const deletedEntityTypes = remoteState.entityTypes.filter((et) => !localSlugs.entityTypes.has(et.slug)).map((et) => et.name);
|
|
21927
|
+
if (deletedEntityTypes.length > 0)
|
|
21928
|
+
deletions.push({ type: "Entity types", remote: remoteState.entityTypes.length, local: payload.entityTypes.length, deleted: deletedEntityTypes });
|
|
21929
|
+
const deletedRoles = remoteState.roles.filter((r) => !localSlugs.roles.has(r.name)).map((r) => r.name);
|
|
21930
|
+
if (deletedRoles.length > 0)
|
|
21931
|
+
deletions.push({ type: "Roles", remote: remoteState.roles.length, local: payload.roles.length, deleted: deletedRoles });
|
|
21932
|
+
const remoteEvalSuites = remoteState.evalSuites || [];
|
|
21933
|
+
const deletedEvalSuites = remoteEvalSuites.filter((es) => !localSlugs.evalSuites.has(es.slug)).map((es) => es.name);
|
|
21934
|
+
if (deletedEvalSuites.length > 0)
|
|
21935
|
+
deletions.push({ type: "Eval suites", remote: remoteEvalSuites.length, local: (payload.evalSuites || []).length, deleted: deletedEvalSuites });
|
|
21936
|
+
const remoteTriggers = remoteState.triggers || [];
|
|
21937
|
+
const deletedTriggers = remoteTriggers.filter((t) => !localSlugs.triggers.has(t.slug)).map((t) => t.name);
|
|
21938
|
+
if (deletedTriggers.length > 0)
|
|
21939
|
+
deletions.push({ type: "Triggers", remote: remoteTriggers.length, local: (payload.triggers || []).length, deleted: deletedTriggers });
|
|
21940
|
+
return deletions;
|
|
21941
|
+
}
|
|
21942
|
+
async function syncToEnvironment(cwd, organizationId, environment) {
|
|
21943
|
+
const resources = await loadAllResources(cwd);
|
|
21944
|
+
if (resources.errors.length > 0) {
|
|
21945
|
+
throw new Error(`${resources.errors.length} resource loading error(s):
|
|
21946
|
+
${resources.errors.join(`
|
|
21947
|
+
`)}`);
|
|
21948
|
+
}
|
|
21949
|
+
const payload = extractSyncPayload(resources);
|
|
21950
|
+
if (environment === "eval") {
|
|
21951
|
+
const result = await syncOrganization({
|
|
21952
|
+
agents: payload.agents,
|
|
21953
|
+
entityTypes: payload.entityTypes,
|
|
21954
|
+
roles: payload.roles,
|
|
21955
|
+
evalSuites: payload.evalSuites,
|
|
21956
|
+
fixtures: payload.fixtures,
|
|
21957
|
+
organizationId,
|
|
21958
|
+
environment: "eval"
|
|
21959
|
+
});
|
|
21960
|
+
if (!result.success)
|
|
21961
|
+
throw new Error(result.error || "Eval sync failed");
|
|
21962
|
+
return result;
|
|
21963
|
+
}
|
|
21964
|
+
if (environment === "production") {
|
|
21965
|
+
const result = await syncOrganization({
|
|
21966
|
+
...payload,
|
|
21967
|
+
organizationId,
|
|
21968
|
+
environment: "production"
|
|
21969
|
+
});
|
|
21970
|
+
if (!result.success)
|
|
21971
|
+
throw new Error(result.error || "Production sync failed");
|
|
21972
|
+
return result;
|
|
21973
|
+
}
|
|
21974
|
+
return performDevSync(cwd, organizationId);
|
|
21975
|
+
}
|
|
21976
|
+
var syncCommand = new Command("sync").description("Sync resources to Convex and exit").option("--force", "Skip destructive sync confirmation").option("--json", "Output results as JSON").option("--dry-run", "Show what would be synced without syncing").option("--env <environment>", "Target environment (development|production|eval)").action(async (options) => {
|
|
21977
|
+
const cwd = process.cwd();
|
|
21978
|
+
const jsonMode = !!options.json;
|
|
21979
|
+
const output = createOutput();
|
|
21980
|
+
const interactive = isInteractive2();
|
|
21981
|
+
const shouldForce = options.force || !interactive;
|
|
21982
|
+
const environment = options.env || "development";
|
|
21983
|
+
const syncEval = !options.env || options.env === "development";
|
|
21984
|
+
if (!hasProject(cwd)) {
|
|
21985
|
+
if (jsonMode) {
|
|
21986
|
+
console.log(JSON.stringify({ success: false, error: "No struere.json found" }));
|
|
21987
|
+
} else {
|
|
21988
|
+
output.fail("No struere.json found. Run struere init first.");
|
|
21989
|
+
}
|
|
21990
|
+
process.exit(1);
|
|
21991
|
+
}
|
|
21992
|
+
const project = loadProject(cwd);
|
|
21993
|
+
if (!project) {
|
|
21994
|
+
if (jsonMode) {
|
|
21995
|
+
console.log(JSON.stringify({ success: false, error: "Failed to load struere.json" }));
|
|
21996
|
+
} else {
|
|
21997
|
+
output.fail("Failed to load struere.json");
|
|
21998
|
+
}
|
|
21999
|
+
process.exit(1);
|
|
22000
|
+
}
|
|
22001
|
+
const credentials = loadCredentials();
|
|
22002
|
+
const apiKey = getApiKey();
|
|
22003
|
+
if (!credentials && !apiKey) {
|
|
22004
|
+
if (jsonMode) {
|
|
22005
|
+
console.log(JSON.stringify({ success: false, error: "Not authenticated. Set STRUERE_API_KEY or run struere login." }));
|
|
22006
|
+
} else {
|
|
22007
|
+
output.fail("Not authenticated. Set STRUERE_API_KEY or run struere login.");
|
|
22008
|
+
}
|
|
22009
|
+
process.exit(1);
|
|
22010
|
+
}
|
|
22011
|
+
if (!jsonMode) {
|
|
22012
|
+
output.info(`Organization: ${project.organization.name}`);
|
|
22013
|
+
output.info(`Environment: ${environment}${syncEval && environment === "development" ? " + eval" : ""}`);
|
|
22014
|
+
console.log();
|
|
22015
|
+
}
|
|
22016
|
+
if (!jsonMode)
|
|
22017
|
+
output.start("Loading resources");
|
|
22018
|
+
let resources;
|
|
22019
|
+
try {
|
|
22020
|
+
resources = await loadAllResources(cwd);
|
|
22021
|
+
if (resources.errors.length > 0) {
|
|
22022
|
+
if (jsonMode) {
|
|
22023
|
+
console.log(JSON.stringify({ success: false, error: `${resources.errors.length} resource loading error(s)`, errors: resources.errors }));
|
|
22024
|
+
} else {
|
|
22025
|
+
output.fail("Failed to load resources");
|
|
22026
|
+
for (const err of resources.errors) {
|
|
22027
|
+
output.error(` ${err}`);
|
|
22028
|
+
}
|
|
22029
|
+
}
|
|
22030
|
+
process.exit(1);
|
|
22031
|
+
}
|
|
22032
|
+
if (!jsonMode && !options.dryRun)
|
|
22033
|
+
output.succeed(`Loaded ${resources.agents.length} agents, ${resources.entityTypes.length} entity types, ${resources.roles.length} roles`);
|
|
22034
|
+
} catch (error) {
|
|
22035
|
+
if (jsonMode) {
|
|
22036
|
+
console.log(JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) }));
|
|
22037
|
+
} else {
|
|
22038
|
+
output.fail("Failed to load resources");
|
|
22039
|
+
output.error(error instanceof Error ? error.message : String(error));
|
|
22040
|
+
}
|
|
22041
|
+
process.exit(1);
|
|
22042
|
+
}
|
|
22043
|
+
if (options.dryRun) {
|
|
22044
|
+
const payload = extractSyncPayload(resources);
|
|
22045
|
+
if (!jsonMode)
|
|
22046
|
+
output.stop();
|
|
22047
|
+
let deletions = [];
|
|
22048
|
+
try {
|
|
22049
|
+
deletions = await checkForDeletions(resources, project.organization.id, environment);
|
|
22050
|
+
} catch {}
|
|
22051
|
+
if (jsonMode) {
|
|
22052
|
+
console.log(JSON.stringify({
|
|
22053
|
+
dryRun: true,
|
|
22054
|
+
environment,
|
|
22055
|
+
agents: payload.agents.map((a) => a.slug),
|
|
22056
|
+
entityTypes: payload.entityTypes.map((et) => et.slug),
|
|
22057
|
+
roles: payload.roles.map((r) => r.name),
|
|
22058
|
+
triggers: (payload.triggers || []).map((t) => t.slug),
|
|
22059
|
+
deletions: deletions.map((d) => ({ type: d.type, names: d.deleted }))
|
|
22060
|
+
}));
|
|
22061
|
+
} else {
|
|
22062
|
+
console.log(source_default.bold("Dry run \u2014 nothing will be synced"));
|
|
22063
|
+
console.log();
|
|
22064
|
+
console.log(source_default.gray(" Agents:"), payload.agents.map((a) => a.slug).join(", ") || "none");
|
|
22065
|
+
console.log(source_default.gray(" Entity types:"), payload.entityTypes.map((et) => et.slug).join(", ") || "none");
|
|
22066
|
+
console.log(source_default.gray(" Roles:"), payload.roles.map((r) => r.name).join(", ") || "none");
|
|
22067
|
+
console.log(source_default.gray(" Triggers:"), (payload.triggers || []).map((t) => t.slug).join(", ") || "none");
|
|
22068
|
+
if (deletions.length > 0) {
|
|
22069
|
+
console.log();
|
|
22070
|
+
console.log(source_default.yellow.bold(" Would delete:"));
|
|
22071
|
+
for (const d of deletions) {
|
|
22072
|
+
for (const name of d.deleted) {
|
|
22073
|
+
console.log(source_default.red(` - ${d.type}: ${name}`));
|
|
22074
|
+
}
|
|
22075
|
+
}
|
|
22076
|
+
}
|
|
22077
|
+
console.log();
|
|
22078
|
+
}
|
|
22079
|
+
return;
|
|
22080
|
+
}
|
|
22081
|
+
if (!shouldForce) {
|
|
22082
|
+
if (!jsonMode)
|
|
22083
|
+
output.start("Checking remote state");
|
|
22084
|
+
try {
|
|
22085
|
+
const deletions = await checkForDeletions(resources, project.organization.id, environment);
|
|
22086
|
+
if (!jsonMode)
|
|
22087
|
+
output.stop();
|
|
22088
|
+
if (deletions.length > 0) {
|
|
22089
|
+
console.log(source_default.yellow.bold(" Warning: this sync will DELETE remote resources:"));
|
|
22090
|
+
console.log();
|
|
22091
|
+
for (const d of deletions) {
|
|
22092
|
+
console.log(source_default.yellow(` ${d.type}:`.padEnd(20)), `${d.remote} remote \u2192 ${d.local} local`, source_default.red(`(${d.deleted.length} will be deleted)`));
|
|
22093
|
+
for (const name of d.deleted) {
|
|
22094
|
+
console.log(source_default.red(` - ${name}`));
|
|
22095
|
+
}
|
|
22096
|
+
}
|
|
22097
|
+
console.log();
|
|
22098
|
+
console.log(source_default.gray(" Run"), source_default.cyan("struere pull"), source_default.gray("first to download remote resources."));
|
|
22099
|
+
console.log();
|
|
22100
|
+
const shouldContinue = await esm_default2({ message: "Continue anyway?", default: false });
|
|
22101
|
+
if (!shouldContinue) {
|
|
22102
|
+
console.log(source_default.gray("Aborted."));
|
|
22103
|
+
process.exit(0);
|
|
22104
|
+
}
|
|
22105
|
+
console.log();
|
|
22106
|
+
}
|
|
22107
|
+
} catch {
|
|
22108
|
+
if (!jsonMode)
|
|
22109
|
+
output.stop();
|
|
22110
|
+
}
|
|
22111
|
+
}
|
|
22112
|
+
if (!jsonMode)
|
|
22113
|
+
output.start("Syncing to Convex");
|
|
22114
|
+
try {
|
|
22115
|
+
const result = await syncToEnvironment(cwd, project.organization.id, environment);
|
|
22116
|
+
if (!jsonMode)
|
|
22117
|
+
output.succeed(`Synced to ${environment}`);
|
|
22118
|
+
if (jsonMode) {
|
|
22119
|
+
console.log(JSON.stringify({
|
|
22120
|
+
success: true,
|
|
22121
|
+
environment,
|
|
22122
|
+
agents: {
|
|
22123
|
+
created: result.agents?.created || [],
|
|
22124
|
+
updated: result.agents?.updated || [],
|
|
22125
|
+
deleted: result.agents?.deleted || []
|
|
22126
|
+
},
|
|
22127
|
+
entityTypes: {
|
|
22128
|
+
created: result.entityTypes?.created || [],
|
|
22129
|
+
updated: result.entityTypes?.updated || [],
|
|
22130
|
+
deleted: result.entityTypes?.deleted || []
|
|
22131
|
+
},
|
|
22132
|
+
roles: {
|
|
22133
|
+
created: result.roles?.created || [],
|
|
22134
|
+
updated: result.roles?.updated || [],
|
|
22135
|
+
deleted: result.roles?.deleted || []
|
|
22136
|
+
}
|
|
22137
|
+
}));
|
|
22138
|
+
}
|
|
22139
|
+
} catch (error) {
|
|
22140
|
+
if (jsonMode) {
|
|
22141
|
+
console.log(JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) }));
|
|
22142
|
+
} else {
|
|
22143
|
+
output.fail("Sync failed");
|
|
22144
|
+
output.error(error instanceof Error ? error.message : String(error));
|
|
22145
|
+
}
|
|
22146
|
+
process.exit(1);
|
|
22147
|
+
}
|
|
22148
|
+
});
|
|
22149
|
+
|
|
21759
22150
|
// src/cli/commands/dev.ts
|
|
21760
|
-
var devCommand = new Command("dev").description("
|
|
22151
|
+
var devCommand = new Command("dev").description("Watch files and sync to development on change (long-running)").option("--force", "Skip destructive sync confirmation").action(async (options) => {
|
|
21761
22152
|
const spinner = ora();
|
|
21762
22153
|
const cwd = process.cwd();
|
|
21763
22154
|
const apiKey = getApiKey();
|
|
21764
|
-
const
|
|
22155
|
+
const nonInteractive = !isInteractive2();
|
|
22156
|
+
if (nonInteractive) {
|
|
22157
|
+
console.error("Error: struere dev is a long-running watch process. Use struere sync instead.");
|
|
22158
|
+
process.exit(1);
|
|
22159
|
+
}
|
|
21765
22160
|
console.log();
|
|
21766
22161
|
console.log(source_default.bold("Struere Dev"));
|
|
21767
22162
|
console.log();
|
|
21768
22163
|
if (!hasProject(cwd)) {
|
|
21769
|
-
if (
|
|
22164
|
+
if (nonInteractive) {
|
|
21770
22165
|
console.log(source_default.red("No struere.json found. Cannot run init in headless mode."));
|
|
21771
22166
|
process.exit(1);
|
|
21772
22167
|
}
|
|
@@ -21783,8 +22178,8 @@ var devCommand = new Command("dev").description("Sync all resources to developme
|
|
|
21783
22178
|
generateTypeDeclarations(cwd);
|
|
21784
22179
|
console.log(source_default.gray("Organization:"), source_default.cyan(project.organization.name));
|
|
21785
22180
|
console.log(source_default.gray("Environment:"), source_default.cyan("development"), "+", source_default.cyan("eval"));
|
|
21786
|
-
if (
|
|
21787
|
-
console.log(source_default.gray("Auth:"), source_default.cyan("
|
|
22181
|
+
if (nonInteractive) {
|
|
22182
|
+
console.log(source_default.gray("Auth:"), source_default.cyan("non-interactive"));
|
|
21788
22183
|
}
|
|
21789
22184
|
console.log();
|
|
21790
22185
|
let credentials = loadCredentials();
|
|
@@ -21809,53 +22204,6 @@ var devCommand = new Command("dev").description("Sync all resources to developme
|
|
|
21809
22204
|
console.log(source_default.yellow("\u26A0"), "Could not fetch docs for CLAUDE.md");
|
|
21810
22205
|
}
|
|
21811
22206
|
}
|
|
21812
|
-
const isAuthError = (error) => {
|
|
21813
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
21814
|
-
return message.includes("Unauthenticated") || message.includes("OIDC") || message.includes("token") || message.includes("expired");
|
|
21815
|
-
};
|
|
21816
|
-
const isOrgAccessError = (error) => {
|
|
21817
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
21818
|
-
return message.includes("Access denied") || message.includes("not a member") || message.includes("Organization not found");
|
|
21819
|
-
};
|
|
21820
|
-
const performSync = async () => {
|
|
21821
|
-
const resources = await loadAllResources(cwd);
|
|
21822
|
-
if (resources.errors.length > 0) {
|
|
21823
|
-
for (const err of resources.errors) {
|
|
21824
|
-
console.log(source_default.red(" \u2716"), err);
|
|
21825
|
-
}
|
|
21826
|
-
throw new Error(`${resources.errors.length} resource loading error(s):
|
|
21827
|
-
${resources.errors.join(`
|
|
21828
|
-
`)}`);
|
|
21829
|
-
}
|
|
21830
|
-
const payload = extractSyncPayload(resources);
|
|
21831
|
-
const devResult = await syncOrganization({
|
|
21832
|
-
agents: payload.agents,
|
|
21833
|
-
entityTypes: payload.entityTypes,
|
|
21834
|
-
roles: payload.roles,
|
|
21835
|
-
triggers: payload.triggers,
|
|
21836
|
-
organizationId: project.organization.id,
|
|
21837
|
-
environment: "development"
|
|
21838
|
-
});
|
|
21839
|
-
if (!devResult.success) {
|
|
21840
|
-
throw new Error(devResult.error || "Dev sync failed");
|
|
21841
|
-
}
|
|
21842
|
-
const hasEvalContent = payload.evalSuites && payload.evalSuites.length > 0 || payload.fixtures && payload.fixtures.length > 0;
|
|
21843
|
-
if (hasEvalContent) {
|
|
21844
|
-
const evalResult = await syncOrganization({
|
|
21845
|
-
agents: payload.agents,
|
|
21846
|
-
entityTypes: payload.entityTypes,
|
|
21847
|
-
roles: payload.roles,
|
|
21848
|
-
evalSuites: payload.evalSuites,
|
|
21849
|
-
fixtures: payload.fixtures,
|
|
21850
|
-
organizationId: project.organization.id,
|
|
21851
|
-
environment: "eval"
|
|
21852
|
-
});
|
|
21853
|
-
if (!evalResult.success) {
|
|
21854
|
-
throw new Error(evalResult.error || "Eval sync failed");
|
|
21855
|
-
}
|
|
21856
|
-
}
|
|
21857
|
-
return true;
|
|
21858
|
-
};
|
|
21859
22207
|
let initialSyncOk = false;
|
|
21860
22208
|
let loadedResources = null;
|
|
21861
22209
|
spinner.start("Loading resources");
|
|
@@ -21872,59 +22220,31 @@ ${resources.errors.join(`
|
|
|
21872
22220
|
spinner.fail("Failed to load resources");
|
|
21873
22221
|
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
21874
22222
|
}
|
|
21875
|
-
const shouldSkipConfirmation = options.force ||
|
|
22223
|
+
const shouldSkipConfirmation = options.force || nonInteractive;
|
|
21876
22224
|
if (initialSyncOk && !shouldSkipConfirmation && loadedResources) {
|
|
21877
22225
|
spinner.start("Checking remote state");
|
|
21878
22226
|
try {
|
|
21879
|
-
const
|
|
22227
|
+
const deletions = await checkForDeletions(loadedResources, project.organization.id, "development");
|
|
21880
22228
|
spinner.stop();
|
|
21881
|
-
if (
|
|
21882
|
-
|
|
21883
|
-
|
|
21884
|
-
|
|
21885
|
-
|
|
21886
|
-
|
|
21887
|
-
|
|
21888
|
-
triggers: new Set((payload.triggers || []).map((t) => t.slug))
|
|
21889
|
-
};
|
|
21890
|
-
const deletions = [];
|
|
21891
|
-
const deletedAgents = remoteState.agents.filter((a) => !localSlugs.agents.has(a.slug)).map((a) => a.name);
|
|
21892
|
-
if (deletedAgents.length > 0)
|
|
21893
|
-
deletions.push({ type: "Agents", remote: remoteState.agents.length, local: payload.agents.length, deleted: deletedAgents });
|
|
21894
|
-
const deletedEntityTypes = remoteState.entityTypes.filter((et) => !localSlugs.entityTypes.has(et.slug)).map((et) => et.name);
|
|
21895
|
-
if (deletedEntityTypes.length > 0)
|
|
21896
|
-
deletions.push({ type: "Entity types", remote: remoteState.entityTypes.length, local: payload.entityTypes.length, deleted: deletedEntityTypes });
|
|
21897
|
-
const deletedRoles = remoteState.roles.filter((r) => !localSlugs.roles.has(r.name)).map((r) => r.name);
|
|
21898
|
-
if (deletedRoles.length > 0)
|
|
21899
|
-
deletions.push({ type: "Roles", remote: remoteState.roles.length, local: payload.roles.length, deleted: deletedRoles });
|
|
21900
|
-
const remoteEvalSuites = remoteState.evalSuites || [];
|
|
21901
|
-
const deletedEvalSuites = remoteEvalSuites.filter((es) => !localSlugs.evalSuites.has(es.slug)).map((es) => es.name);
|
|
21902
|
-
if (deletedEvalSuites.length > 0)
|
|
21903
|
-
deletions.push({ type: "Eval suites", remote: remoteEvalSuites.length, local: (payload.evalSuites || []).length, deleted: deletedEvalSuites });
|
|
21904
|
-
const remoteTriggers = remoteState.triggers || [];
|
|
21905
|
-
const deletedTriggers = remoteTriggers.filter((t) => !localSlugs.triggers.has(t.slug)).map((t) => t.name);
|
|
21906
|
-
if (deletedTriggers.length > 0)
|
|
21907
|
-
deletions.push({ type: "Triggers", remote: remoteTriggers.length, local: (payload.triggers || []).length, deleted: deletedTriggers });
|
|
21908
|
-
if (deletions.length > 0) {
|
|
21909
|
-
console.log(source_default.yellow.bold(" Warning: this sync will DELETE remote resources:"));
|
|
21910
|
-
console.log();
|
|
21911
|
-
for (const d of deletions) {
|
|
21912
|
-
console.log(source_default.yellow(` ${d.type}:`.padEnd(20)), `${d.remote} remote \u2192 ${d.local} local`, source_default.red(`(${d.deleted.length} will be deleted)`));
|
|
21913
|
-
for (const name of d.deleted) {
|
|
21914
|
-
console.log(source_default.red(` - ${name}`));
|
|
21915
|
-
}
|
|
21916
|
-
}
|
|
21917
|
-
console.log();
|
|
21918
|
-
console.log(source_default.gray(" Run"), source_default.cyan("struere pull"), source_default.gray("first to download remote resources."));
|
|
21919
|
-
console.log();
|
|
21920
|
-
const shouldContinue = await esm_default2({ message: "Continue anyway?", default: false });
|
|
21921
|
-
if (!shouldContinue) {
|
|
21922
|
-
console.log();
|
|
21923
|
-
console.log(source_default.gray("Aborted."));
|
|
21924
|
-
process.exit(0);
|
|
22229
|
+
if (deletions.length > 0) {
|
|
22230
|
+
console.log(source_default.yellow.bold(" Warning: this sync will DELETE remote resources:"));
|
|
22231
|
+
console.log();
|
|
22232
|
+
for (const d of deletions) {
|
|
22233
|
+
console.log(source_default.yellow(` ${d.type}:`.padEnd(20)), `${d.remote} remote \u2192 ${d.local} local`, source_default.red(`(${d.deleted.length} will be deleted)`));
|
|
22234
|
+
for (const name of d.deleted) {
|
|
22235
|
+
console.log(source_default.red(` - ${name}`));
|
|
21925
22236
|
}
|
|
22237
|
+
}
|
|
22238
|
+
console.log();
|
|
22239
|
+
console.log(source_default.gray(" Run"), source_default.cyan("struere pull"), source_default.gray("first to download remote resources."));
|
|
22240
|
+
console.log();
|
|
22241
|
+
const shouldContinue = await esm_default2({ message: "Continue anyway?", default: false });
|
|
22242
|
+
if (!shouldContinue) {
|
|
21926
22243
|
console.log();
|
|
22244
|
+
console.log(source_default.gray("Aborted."));
|
|
22245
|
+
process.exit(0);
|
|
21927
22246
|
}
|
|
22247
|
+
console.log();
|
|
21928
22248
|
}
|
|
21929
22249
|
} catch {
|
|
21930
22250
|
spinner.stop();
|
|
@@ -21933,10 +22253,10 @@ ${resources.errors.join(`
|
|
|
21933
22253
|
if (initialSyncOk) {
|
|
21934
22254
|
spinner.start("Syncing to Convex");
|
|
21935
22255
|
try {
|
|
21936
|
-
await
|
|
22256
|
+
await performDevSync(cwd, project.organization.id);
|
|
21937
22257
|
spinner.succeed("Synced to development");
|
|
21938
22258
|
} catch (error) {
|
|
21939
|
-
if (isAuthError(error) && !
|
|
22259
|
+
if (isAuthError(error) && !nonInteractive) {
|
|
21940
22260
|
spinner.fail("Session expired - re-authenticating...");
|
|
21941
22261
|
clearCredentials();
|
|
21942
22262
|
credentials = await performLogin();
|
|
@@ -21946,13 +22266,13 @@ ${resources.errors.join(`
|
|
|
21946
22266
|
}
|
|
21947
22267
|
spinner.start("Syncing to Convex");
|
|
21948
22268
|
try {
|
|
21949
|
-
await
|
|
22269
|
+
await performDevSync(cwd, project.organization.id);
|
|
21950
22270
|
spinner.succeed("Synced to development");
|
|
21951
22271
|
} catch (retryError) {
|
|
21952
22272
|
spinner.fail("Sync failed");
|
|
21953
22273
|
console.log(source_default.red("Error:"), retryError instanceof Error ? retryError.message : String(retryError));
|
|
21954
22274
|
}
|
|
21955
|
-
} else if (isAuthError(error) &&
|
|
22275
|
+
} else if (isAuthError(error) && nonInteractive) {
|
|
21956
22276
|
spinner.fail("API key authentication failed");
|
|
21957
22277
|
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
21958
22278
|
console.log(source_default.gray("Check that STRUERE_API_KEY is valid and not expired."));
|
|
@@ -21992,15 +22312,15 @@ ${resources.errors.join(`
|
|
|
21992
22312
|
persistent: true,
|
|
21993
22313
|
usePolling: false
|
|
21994
22314
|
});
|
|
21995
|
-
|
|
22315
|
+
const handleFileChange = async (path, action) => {
|
|
21996
22316
|
const relativePath = path.replace(cwd, ".");
|
|
21997
|
-
console.log(source_default.gray(
|
|
22317
|
+
console.log(source_default.gray(`${action}: ${relativePath}`));
|
|
21998
22318
|
const syncSpinner = ora("Syncing...").start();
|
|
21999
22319
|
try {
|
|
22000
|
-
await
|
|
22320
|
+
await performDevSync(cwd, project.organization.id);
|
|
22001
22321
|
syncSpinner.succeed("Synced");
|
|
22002
22322
|
} catch (error) {
|
|
22003
|
-
if (isAuthError(error) && !
|
|
22323
|
+
if (isAuthError(error) && !nonInteractive) {
|
|
22004
22324
|
syncSpinner.fail("Session expired - re-authenticating...");
|
|
22005
22325
|
clearCredentials();
|
|
22006
22326
|
const newCredentials = await performLogin();
|
|
@@ -22010,7 +22330,7 @@ ${resources.errors.join(`
|
|
|
22010
22330
|
}
|
|
22011
22331
|
const retrySyncSpinner = ora("Syncing...").start();
|
|
22012
22332
|
try {
|
|
22013
|
-
await
|
|
22333
|
+
await performDevSync(cwd, project.organization.id);
|
|
22014
22334
|
retrySyncSpinner.succeed("Synced");
|
|
22015
22335
|
} catch (retryError) {
|
|
22016
22336
|
retrySyncSpinner.fail("Sync failed");
|
|
@@ -22021,31 +22341,10 @@ ${resources.errors.join(`
|
|
|
22021
22341
|
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
22022
22342
|
}
|
|
22023
22343
|
}
|
|
22024
|
-
}
|
|
22025
|
-
watcher.on("
|
|
22026
|
-
|
|
22027
|
-
|
|
22028
|
-
const syncSpinner = ora("Syncing...").start();
|
|
22029
|
-
try {
|
|
22030
|
-
await performSync();
|
|
22031
|
-
syncSpinner.succeed("Synced");
|
|
22032
|
-
} catch (error) {
|
|
22033
|
-
syncSpinner.fail("Sync failed");
|
|
22034
|
-
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
22035
|
-
}
|
|
22036
|
-
});
|
|
22037
|
-
watcher.on("unlink", async (path) => {
|
|
22038
|
-
const relativePath = path.replace(cwd, ".");
|
|
22039
|
-
console.log(source_default.gray(`Removed: ${relativePath}`));
|
|
22040
|
-
const syncSpinner = ora("Syncing...").start();
|
|
22041
|
-
try {
|
|
22042
|
-
await performSync();
|
|
22043
|
-
syncSpinner.succeed("Synced");
|
|
22044
|
-
} catch (error) {
|
|
22045
|
-
syncSpinner.fail("Sync failed");
|
|
22046
|
-
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
22047
|
-
}
|
|
22048
|
-
});
|
|
22344
|
+
};
|
|
22345
|
+
watcher.on("change", (path) => handleFileChange(path, "Changed"));
|
|
22346
|
+
watcher.on("add", (path) => handleFileChange(path, "Added"));
|
|
22347
|
+
watcher.on("unlink", (path) => handleFileChange(path, "Removed"));
|
|
22049
22348
|
process.on("SIGINT", () => {
|
|
22050
22349
|
console.log();
|
|
22051
22350
|
console.log(source_default.gray("Stopping..."));
|
|
@@ -22055,21 +22354,26 @@ ${resources.errors.join(`
|
|
|
22055
22354
|
});
|
|
22056
22355
|
|
|
22057
22356
|
// src/cli/commands/deploy.ts
|
|
22058
|
-
var
|
|
22059
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
22060
|
-
return message.includes("Unauthenticated") || message.includes("OIDC") || message.includes("token") || message.includes("expired");
|
|
22061
|
-
};
|
|
22062
|
-
var isOrgAccessError = (error) => {
|
|
22063
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
22064
|
-
return message.includes("Access denied") || message.includes("not a member") || message.includes("Organization not found");
|
|
22065
|
-
};
|
|
22066
|
-
var deployCommand = new Command("deploy").description("Deploy all resources to production").option("--dry-run", "Show what would be deployed without deploying").action(async (options) => {
|
|
22357
|
+
var deployCommand = new Command("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) => {
|
|
22067
22358
|
const spinner = ora();
|
|
22068
22359
|
const cwd = process.cwd();
|
|
22069
|
-
|
|
22070
|
-
|
|
22071
|
-
|
|
22360
|
+
const nonInteractive = !isInteractive2();
|
|
22361
|
+
const shouldForce = options.force || nonInteractive;
|
|
22362
|
+
const jsonMode = !!options.json;
|
|
22363
|
+
if (!jsonMode) {
|
|
22364
|
+
console.log();
|
|
22365
|
+
console.log(source_default.bold("Deploying to Production"));
|
|
22366
|
+
console.log();
|
|
22367
|
+
}
|
|
22072
22368
|
if (!hasProject(cwd)) {
|
|
22369
|
+
if (nonInteractive) {
|
|
22370
|
+
if (jsonMode) {
|
|
22371
|
+
console.log(JSON.stringify({ success: false, error: "No struere.json found" }));
|
|
22372
|
+
} else {
|
|
22373
|
+
console.log(source_default.red("No struere.json found. Run struere init first."));
|
|
22374
|
+
}
|
|
22375
|
+
process.exit(1);
|
|
22376
|
+
}
|
|
22073
22377
|
console.log(source_default.yellow("No struere.json found - initializing project..."));
|
|
22074
22378
|
console.log();
|
|
22075
22379
|
const success = await runInit(cwd);
|
|
@@ -22080,15 +22384,29 @@ var deployCommand = new Command("deploy").description("Deploy all resources to p
|
|
|
22080
22384
|
}
|
|
22081
22385
|
const project = loadProject(cwd);
|
|
22082
22386
|
if (!project) {
|
|
22083
|
-
|
|
22387
|
+
if (jsonMode) {
|
|
22388
|
+
console.log(JSON.stringify({ success: false, error: "Failed to load struere.json" }));
|
|
22389
|
+
} else {
|
|
22390
|
+
console.log(source_default.red("Failed to load struere.json"));
|
|
22391
|
+
}
|
|
22084
22392
|
process.exit(1);
|
|
22085
22393
|
}
|
|
22086
|
-
|
|
22087
|
-
|
|
22088
|
-
|
|
22394
|
+
if (!jsonMode) {
|
|
22395
|
+
console.log(source_default.gray("Organization:"), source_default.cyan(project.organization.name));
|
|
22396
|
+
console.log(source_default.gray("Environment:"), source_default.cyan("production"));
|
|
22397
|
+
console.log();
|
|
22398
|
+
}
|
|
22089
22399
|
let credentials = loadCredentials();
|
|
22090
22400
|
const apiKey = getApiKey();
|
|
22091
22401
|
if (!credentials && !apiKey) {
|
|
22402
|
+
if (nonInteractive) {
|
|
22403
|
+
if (jsonMode) {
|
|
22404
|
+
console.log(JSON.stringify({ success: false, error: "Not authenticated. Set STRUERE_API_KEY or run struere login." }));
|
|
22405
|
+
} else {
|
|
22406
|
+
console.log(source_default.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
22407
|
+
}
|
|
22408
|
+
process.exit(1);
|
|
22409
|
+
}
|
|
22092
22410
|
console.log(source_default.yellow("Not logged in - authenticating..."));
|
|
22093
22411
|
console.log();
|
|
22094
22412
|
credentials = await performLogin();
|
|
@@ -22098,66 +22416,68 @@ var deployCommand = new Command("deploy").description("Deploy all resources to p
|
|
|
22098
22416
|
}
|
|
22099
22417
|
console.log();
|
|
22100
22418
|
}
|
|
22101
|
-
|
|
22419
|
+
if (!jsonMode)
|
|
22420
|
+
spinner.start("Loading resources");
|
|
22102
22421
|
let resources;
|
|
22103
22422
|
try {
|
|
22104
22423
|
resources = await loadAllResources(cwd);
|
|
22105
|
-
|
|
22424
|
+
if (!jsonMode)
|
|
22425
|
+
spinner.succeed(`Loaded ${resources.agents.length} agents, ${resources.entityTypes.length} entity types, ${resources.roles.length} roles, ${resources.customTools.length} custom tools, ${resources.evalSuites.length} eval suites`);
|
|
22106
22426
|
for (const err of resources.errors) {
|
|
22107
|
-
|
|
22427
|
+
if (!jsonMode)
|
|
22428
|
+
console.log(source_default.red(" \u2716"), err);
|
|
22108
22429
|
}
|
|
22109
22430
|
if (resources.errors.length > 0) {
|
|
22431
|
+
if (jsonMode) {
|
|
22432
|
+
console.log(JSON.stringify({ success: false, error: `${resources.errors.length} resource loading error(s)`, errors: resources.errors }));
|
|
22433
|
+
}
|
|
22110
22434
|
process.exit(1);
|
|
22111
22435
|
}
|
|
22112
22436
|
} catch (error) {
|
|
22113
|
-
|
|
22114
|
-
|
|
22437
|
+
if (jsonMode) {
|
|
22438
|
+
console.log(JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) }));
|
|
22439
|
+
} else {
|
|
22440
|
+
spinner.fail("Failed to load resources");
|
|
22441
|
+
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
22442
|
+
}
|
|
22115
22443
|
process.exit(1);
|
|
22116
22444
|
}
|
|
22117
22445
|
if (resources.agents.length === 0) {
|
|
22118
|
-
|
|
22119
|
-
|
|
22120
|
-
|
|
22121
|
-
|
|
22122
|
-
|
|
22446
|
+
if (jsonMode) {
|
|
22447
|
+
console.log(JSON.stringify({ success: false, error: "No agents found to deploy" }));
|
|
22448
|
+
} else {
|
|
22449
|
+
console.log();
|
|
22450
|
+
console.log(source_default.yellow("No agents found to deploy"));
|
|
22451
|
+
console.log();
|
|
22452
|
+
console.log(source_default.gray("Run"), source_default.cyan("struere add agent my-agent"), source_default.gray("to create an agent"));
|
|
22453
|
+
console.log();
|
|
22454
|
+
}
|
|
22123
22455
|
return;
|
|
22124
22456
|
}
|
|
22125
22457
|
const payload = extractSyncPayload(resources);
|
|
22126
|
-
|
|
22458
|
+
if (!jsonMode)
|
|
22459
|
+
spinner.start("Checking remote state");
|
|
22127
22460
|
let deletions = [];
|
|
22128
22461
|
try {
|
|
22129
|
-
|
|
22130
|
-
|
|
22131
|
-
|
|
22132
|
-
const localSlugs = {
|
|
22133
|
-
agents: new Set(payload.agents.map((a) => a.slug)),
|
|
22134
|
-
entityTypes: new Set(payload.entityTypes.map((et) => et.slug)),
|
|
22135
|
-
roles: new Set(payload.roles.map((r) => r.name)),
|
|
22136
|
-
evalSuites: new Set((payload.evalSuites || []).map((es) => es.slug)),
|
|
22137
|
-
triggers: new Set((payload.triggers || []).map((t) => t.slug))
|
|
22138
|
-
};
|
|
22139
|
-
const deletedAgents = remoteState.agents.filter((a) => !localSlugs.agents.has(a.slug)).map((a) => a.name);
|
|
22140
|
-
if (deletedAgents.length > 0)
|
|
22141
|
-
deletions.push({ type: "Agents", remote: remoteState.agents.length, local: payload.agents.length, deleted: deletedAgents });
|
|
22142
|
-
const deletedEntityTypes = remoteState.entityTypes.filter((et) => !localSlugs.entityTypes.has(et.slug)).map((et) => et.name);
|
|
22143
|
-
if (deletedEntityTypes.length > 0)
|
|
22144
|
-
deletions.push({ type: "Entity types", remote: remoteState.entityTypes.length, local: payload.entityTypes.length, deleted: deletedEntityTypes });
|
|
22145
|
-
const deletedRoles = remoteState.roles.filter((r) => !localSlugs.roles.has(r.name)).map((r) => r.name);
|
|
22146
|
-
if (deletedRoles.length > 0)
|
|
22147
|
-
deletions.push({ type: "Roles", remote: remoteState.roles.length, local: payload.roles.length, deleted: deletedRoles });
|
|
22148
|
-
const remoteEvalSuites = remoteState.evalSuites || [];
|
|
22149
|
-
const deletedEvalSuites = remoteEvalSuites.filter((es) => !localSlugs.evalSuites.has(es.slug)).map((es) => es.name);
|
|
22150
|
-
if (deletedEvalSuites.length > 0)
|
|
22151
|
-
deletions.push({ type: "Eval suites", remote: remoteEvalSuites.length, local: (payload.evalSuites || []).length, deleted: deletedEvalSuites });
|
|
22152
|
-
const remoteTriggers = remoteState.triggers || [];
|
|
22153
|
-
const deletedTriggers = remoteTriggers.filter((t) => !localSlugs.triggers.has(t.slug)).map((t) => t.name);
|
|
22154
|
-
if (deletedTriggers.length > 0)
|
|
22155
|
-
deletions.push({ type: "Triggers", remote: remoteTriggers.length, local: (payload.triggers || []).length, deleted: deletedTriggers });
|
|
22156
|
-
}
|
|
22462
|
+
deletions = await checkForDeletions(resources, project.organization.id, "production");
|
|
22463
|
+
if (!jsonMode)
|
|
22464
|
+
spinner.stop();
|
|
22157
22465
|
} catch {
|
|
22158
|
-
|
|
22466
|
+
if (!jsonMode)
|
|
22467
|
+
spinner.stop();
|
|
22159
22468
|
}
|
|
22160
22469
|
if (options.dryRun) {
|
|
22470
|
+
if (jsonMode) {
|
|
22471
|
+
console.log(JSON.stringify({
|
|
22472
|
+
success: true,
|
|
22473
|
+
dryRun: true,
|
|
22474
|
+
agents: resources.agents.map((a) => ({ name: a.name, slug: a.slug, version: a.version })),
|
|
22475
|
+
entityTypes: resources.entityTypes.map((et) => ({ name: et.name, slug: et.slug })),
|
|
22476
|
+
roles: resources.roles.map((r) => ({ name: r.name })),
|
|
22477
|
+
deletions: deletions.flatMap((d) => d.deleted.map((name) => ({ type: d.type.toLowerCase(), name })))
|
|
22478
|
+
}));
|
|
22479
|
+
return;
|
|
22480
|
+
}
|
|
22161
22481
|
console.log();
|
|
22162
22482
|
console.log(source_default.yellow("Dry run mode - no changes will be made"));
|
|
22163
22483
|
console.log();
|
|
@@ -22194,7 +22514,7 @@ var deployCommand = new Command("deploy").description("Deploy all resources to p
|
|
|
22194
22514
|
console.log();
|
|
22195
22515
|
return;
|
|
22196
22516
|
}
|
|
22197
|
-
if (deletions.length > 0) {
|
|
22517
|
+
if (deletions.length > 0 && !shouldForce) {
|
|
22198
22518
|
console.log(source_default.yellow.bold(" Warning: this deploy will DELETE production resources:"));
|
|
22199
22519
|
console.log();
|
|
22200
22520
|
for (const d of deletions) {
|
|
@@ -22212,7 +22532,8 @@ var deployCommand = new Command("deploy").description("Deploy all resources to p
|
|
|
22212
22532
|
}
|
|
22213
22533
|
console.log();
|
|
22214
22534
|
}
|
|
22215
|
-
|
|
22535
|
+
if (!jsonMode)
|
|
22536
|
+
spinner.start("Deploying to production");
|
|
22216
22537
|
try {
|
|
22217
22538
|
const syncResult = await syncOrganization({
|
|
22218
22539
|
...payload,
|
|
@@ -22222,38 +22543,67 @@ var deployCommand = new Command("deploy").description("Deploy all resources to p
|
|
|
22222
22543
|
if (!syncResult.success) {
|
|
22223
22544
|
throw new Error(syncResult.error || "Deploy failed");
|
|
22224
22545
|
}
|
|
22225
|
-
|
|
22226
|
-
|
|
22227
|
-
|
|
22228
|
-
|
|
22229
|
-
|
|
22230
|
-
|
|
22231
|
-
|
|
22232
|
-
|
|
22233
|
-
|
|
22546
|
+
if (!jsonMode)
|
|
22547
|
+
spinner.succeed("Deployed to production");
|
|
22548
|
+
if (jsonMode) {
|
|
22549
|
+
console.log(JSON.stringify({
|
|
22550
|
+
success: true,
|
|
22551
|
+
environment: "production",
|
|
22552
|
+
agents: {
|
|
22553
|
+
created: syncResult.agents?.created || [],
|
|
22554
|
+
updated: syncResult.agents?.updated || [],
|
|
22555
|
+
deleted: syncResult.agents?.deleted || []
|
|
22556
|
+
},
|
|
22557
|
+
entityTypes: {
|
|
22558
|
+
created: syncResult.entityTypes?.created || [],
|
|
22559
|
+
updated: syncResult.entityTypes?.updated || [],
|
|
22560
|
+
deleted: syncResult.entityTypes?.deleted || []
|
|
22561
|
+
},
|
|
22562
|
+
roles: {
|
|
22563
|
+
created: syncResult.roles?.created || [],
|
|
22564
|
+
updated: syncResult.roles?.updated || [],
|
|
22565
|
+
deleted: syncResult.roles?.deleted || []
|
|
22566
|
+
}
|
|
22567
|
+
}));
|
|
22568
|
+
} else {
|
|
22569
|
+
console.log();
|
|
22570
|
+
console.log(source_default.green("Success!"), "All resources deployed to production");
|
|
22571
|
+
console.log();
|
|
22572
|
+
if (syncResult.agents?.created && syncResult.agents.created.length > 0) {
|
|
22573
|
+
console.log("New agents:");
|
|
22574
|
+
for (const slug of syncResult.agents.created) {
|
|
22575
|
+
const agent = resources.agents.find((a) => a.slug === slug);
|
|
22576
|
+
console.log(source_default.gray(" -"), source_default.cyan(agent?.name || slug));
|
|
22577
|
+
}
|
|
22234
22578
|
}
|
|
22235
|
-
|
|
22236
|
-
|
|
22237
|
-
|
|
22238
|
-
|
|
22239
|
-
|
|
22240
|
-
|
|
22579
|
+
if (syncResult.agents?.updated && syncResult.agents.updated.length > 0) {
|
|
22580
|
+
console.log("Updated agents:");
|
|
22581
|
+
for (const slug of syncResult.agents.updated) {
|
|
22582
|
+
const agent = resources.agents.find((a) => a.slug === slug);
|
|
22583
|
+
console.log(source_default.gray(" -"), source_default.cyan(agent?.name || slug));
|
|
22584
|
+
}
|
|
22241
22585
|
}
|
|
22586
|
+
console.log();
|
|
22587
|
+
console.log(source_default.gray("Test your agents:"));
|
|
22588
|
+
console.log(source_default.gray(" $"), source_default.cyan(`curl -X POST https://<agent-slug>.struere.dev/chat -H "Authorization: Bearer YOUR_API_KEY" -d '{"message": "Hello"}'`));
|
|
22589
|
+
console.log();
|
|
22242
22590
|
}
|
|
22243
|
-
console.log();
|
|
22244
|
-
console.log(source_default.gray("Test your agents:"));
|
|
22245
|
-
console.log(source_default.gray(" $"), source_default.cyan(`curl -X POST https://<agent-slug>.struere.dev/chat -H "Authorization: Bearer YOUR_API_KEY" -d '{"message": "Hello"}'`));
|
|
22246
|
-
console.log();
|
|
22247
22591
|
} catch (error) {
|
|
22248
|
-
if (isAuthError(error)) {
|
|
22249
|
-
|
|
22592
|
+
if (isAuthError(error) && !nonInteractive) {
|
|
22593
|
+
if (!jsonMode)
|
|
22594
|
+
spinner.fail("Session expired - re-authenticating...");
|
|
22250
22595
|
clearCredentials();
|
|
22251
22596
|
credentials = await performLogin();
|
|
22252
22597
|
if (!credentials) {
|
|
22253
|
-
|
|
22598
|
+
if (jsonMode) {
|
|
22599
|
+
console.log(JSON.stringify({ success: false, error: "Authentication failed" }));
|
|
22600
|
+
} else {
|
|
22601
|
+
console.log(source_default.red("Authentication failed"));
|
|
22602
|
+
}
|
|
22254
22603
|
process.exit(1);
|
|
22255
22604
|
}
|
|
22256
|
-
|
|
22605
|
+
if (!jsonMode)
|
|
22606
|
+
spinner.start("Deploying to production");
|
|
22257
22607
|
try {
|
|
22258
22608
|
const syncResult = await syncOrganization({
|
|
22259
22609
|
...payload,
|
|
@@ -22263,30 +22613,46 @@ var deployCommand = new Command("deploy").description("Deploy all resources to p
|
|
|
22263
22613
|
if (!syncResult.success) {
|
|
22264
22614
|
throw new Error(syncResult.error || "Deploy failed");
|
|
22265
22615
|
}
|
|
22266
|
-
|
|
22267
|
-
|
|
22268
|
-
|
|
22269
|
-
|
|
22616
|
+
if (!jsonMode) {
|
|
22617
|
+
spinner.succeed("Deployed to production");
|
|
22618
|
+
console.log();
|
|
22619
|
+
console.log(source_default.green("Success!"), "All resources deployed to production");
|
|
22620
|
+
console.log();
|
|
22621
|
+
} else {
|
|
22622
|
+
console.log(JSON.stringify({ success: true, environment: "production" }));
|
|
22623
|
+
}
|
|
22270
22624
|
} catch (retryError) {
|
|
22271
|
-
|
|
22272
|
-
|
|
22625
|
+
if (jsonMode) {
|
|
22626
|
+
console.log(JSON.stringify({ success: false, error: retryError instanceof Error ? retryError.message : String(retryError) }));
|
|
22627
|
+
} else {
|
|
22628
|
+
spinner.fail("Deployment failed");
|
|
22629
|
+
console.log(source_default.red("Error:"), retryError instanceof Error ? retryError.message : String(retryError));
|
|
22630
|
+
}
|
|
22273
22631
|
process.exit(1);
|
|
22274
22632
|
}
|
|
22275
22633
|
} else if (isOrgAccessError(error)) {
|
|
22276
|
-
|
|
22277
|
-
|
|
22278
|
-
|
|
22279
|
-
|
|
22280
|
-
|
|
22281
|
-
|
|
22282
|
-
|
|
22283
|
-
|
|
22634
|
+
if (jsonMode) {
|
|
22635
|
+
console.log(JSON.stringify({ success: false, error: `Organization access denied: ${project.organization.name}` }));
|
|
22636
|
+
} else {
|
|
22637
|
+
spinner.fail("Organization access denied");
|
|
22638
|
+
console.log();
|
|
22639
|
+
console.log(source_default.red("You do not have access to organization:"), source_default.cyan(project.organization.name));
|
|
22640
|
+
console.log();
|
|
22641
|
+
console.log(source_default.gray("To fix this:"));
|
|
22642
|
+
console.log(source_default.gray(" 1."), "Check that you have access to this organization");
|
|
22643
|
+
console.log(source_default.gray(" 2."), "Or run", source_default.cyan("struere init"), "to select a different organization");
|
|
22644
|
+
console.log();
|
|
22645
|
+
}
|
|
22284
22646
|
process.exit(1);
|
|
22285
22647
|
} else {
|
|
22286
|
-
|
|
22287
|
-
|
|
22288
|
-
|
|
22289
|
-
|
|
22648
|
+
if (jsonMode) {
|
|
22649
|
+
console.log(JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) }));
|
|
22650
|
+
} else {
|
|
22651
|
+
spinner.fail("Deployment failed");
|
|
22652
|
+
console.log();
|
|
22653
|
+
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
22654
|
+
console.log();
|
|
22655
|
+
}
|
|
22290
22656
|
process.exit(1);
|
|
22291
22657
|
}
|
|
22292
22658
|
}
|
|
@@ -22308,32 +22674,47 @@ var logoutCommand = new Command("logout").description("Log out of Struere").acti
|
|
|
22308
22674
|
});
|
|
22309
22675
|
|
|
22310
22676
|
// src/cli/commands/whoami.ts
|
|
22311
|
-
var whoamiCommand = new Command("whoami").description("Show current logged in user").option("--refresh", "Refresh user info from server").action(async (options) => {
|
|
22312
|
-
|
|
22677
|
+
var whoamiCommand = new Command("whoami").description("Show current logged in user").option("--refresh", "Refresh user info from server").option("--json", "Output raw JSON").action(async (options) => {
|
|
22678
|
+
const jsonMode = !!options.json;
|
|
22313
22679
|
const credentials = loadCredentials();
|
|
22314
|
-
|
|
22315
|
-
|
|
22316
|
-
|
|
22317
|
-
|
|
22318
|
-
|
|
22680
|
+
const apiKey = getApiKey();
|
|
22681
|
+
if (!credentials && !apiKey) {
|
|
22682
|
+
if (jsonMode) {
|
|
22683
|
+
console.log(JSON.stringify({ authenticated: false }));
|
|
22684
|
+
} else {
|
|
22685
|
+
console.log();
|
|
22686
|
+
console.log(source_default.yellow("Not logged in"));
|
|
22687
|
+
console.log();
|
|
22688
|
+
console.log(source_default.gray("Run"), source_default.cyan("struere login"), source_default.gray("to log in"));
|
|
22689
|
+
console.log();
|
|
22690
|
+
}
|
|
22319
22691
|
return;
|
|
22320
22692
|
}
|
|
22321
|
-
if (options.refresh) {
|
|
22322
|
-
const spinner = ora("Fetching user info").start();
|
|
22693
|
+
if (options.refresh && credentials) {
|
|
22694
|
+
const spinner = jsonMode ? null : ora("Fetching user info").start();
|
|
22323
22695
|
const { userInfo, error } = await getUserInfo(credentials.token);
|
|
22324
22696
|
if (error || !userInfo) {
|
|
22325
|
-
|
|
22326
|
-
|
|
22327
|
-
if (error?.includes("401") || error?.includes("unauthorized")) {
|
|
22328
|
-
console.log(source_default.red("Session expired. Please log in again."));
|
|
22697
|
+
if (jsonMode) {
|
|
22698
|
+
console.log(JSON.stringify({ authenticated: false, error: error || "Unknown error" }));
|
|
22329
22699
|
} else {
|
|
22330
|
-
|
|
22700
|
+
spinner?.fail("Failed to fetch user info");
|
|
22701
|
+
console.log();
|
|
22702
|
+
if (error?.includes("401") || error?.includes("unauthorized")) {
|
|
22703
|
+
console.log(source_default.red("Session expired. Please log in again."));
|
|
22704
|
+
} else {
|
|
22705
|
+
console.log(source_default.red("Error:"), error || "Unknown error");
|
|
22706
|
+
}
|
|
22707
|
+
console.log();
|
|
22331
22708
|
}
|
|
22332
|
-
console.log();
|
|
22333
22709
|
process.exit(1);
|
|
22334
22710
|
}
|
|
22335
|
-
spinner
|
|
22711
|
+
spinner?.stop();
|
|
22336
22712
|
const { user, organizations } = userInfo;
|
|
22713
|
+
if (jsonMode) {
|
|
22714
|
+
console.log(JSON.stringify({ user, organizations }));
|
|
22715
|
+
return;
|
|
22716
|
+
}
|
|
22717
|
+
console.log();
|
|
22337
22718
|
console.log(source_default.bold("Logged in as:"));
|
|
22338
22719
|
console.log();
|
|
22339
22720
|
console.log(source_default.gray(" User: "), source_default.cyan(user.name || user.email), source_default.gray(`<${user.email}>`));
|
|
@@ -22349,13 +22730,36 @@ var whoamiCommand = new Command("whoami").description("Show current logged in us
|
|
|
22349
22730
|
}
|
|
22350
22731
|
console.log();
|
|
22351
22732
|
} else {
|
|
22352
|
-
|
|
22353
|
-
|
|
22354
|
-
|
|
22355
|
-
|
|
22356
|
-
|
|
22357
|
-
|
|
22358
|
-
|
|
22733
|
+
if (credentials) {
|
|
22734
|
+
if (jsonMode) {
|
|
22735
|
+
console.log(JSON.stringify({
|
|
22736
|
+
user: {
|
|
22737
|
+
id: credentials.user.id,
|
|
22738
|
+
name: credentials.user.name,
|
|
22739
|
+
email: credentials.user.email
|
|
22740
|
+
}
|
|
22741
|
+
}));
|
|
22742
|
+
return;
|
|
22743
|
+
}
|
|
22744
|
+
console.log();
|
|
22745
|
+
console.log(source_default.bold("Logged in as:"));
|
|
22746
|
+
console.log();
|
|
22747
|
+
console.log(source_default.gray(" User: "), source_default.cyan(credentials.user.name), source_default.gray(`<${credentials.user.email}>`));
|
|
22748
|
+
console.log(source_default.gray(" User ID: "), source_default.gray(credentials.user.id));
|
|
22749
|
+
console.log();
|
|
22750
|
+
console.log(source_default.gray("Use"), source_default.cyan("struere whoami --refresh"), source_default.gray("to fetch organizations"));
|
|
22751
|
+
console.log();
|
|
22752
|
+
} else {
|
|
22753
|
+
if (jsonMode) {
|
|
22754
|
+
console.log(JSON.stringify({ authenticated: true, authMethod: "api-key" }));
|
|
22755
|
+
} else {
|
|
22756
|
+
console.log();
|
|
22757
|
+
console.log(source_default.bold("Authenticated via API key"));
|
|
22758
|
+
console.log();
|
|
22759
|
+
console.log(source_default.gray("Use"), source_default.cyan("struere whoami --refresh"), source_default.gray("with browser login for full details"));
|
|
22760
|
+
console.log();
|
|
22761
|
+
}
|
|
22762
|
+
}
|
|
22359
22763
|
}
|
|
22360
22764
|
});
|
|
22361
22765
|
|
|
@@ -22420,7 +22824,7 @@ var addCommand = new Command("add").description("Scaffold a new resource").argum
|
|
|
22420
22824
|
console.log(source_default.gray(" \u2192"), file);
|
|
22421
22825
|
}
|
|
22422
22826
|
console.log();
|
|
22423
|
-
console.log(source_default.gray("Edit the YAML file, then run"), source_default.cyan("struere
|
|
22827
|
+
console.log(source_default.gray("Edit the YAML file, then run"), source_default.cyan("struere sync"), source_default.gray("to sync"));
|
|
22424
22828
|
} else {
|
|
22425
22829
|
console.log(source_default.yellow("Eval suite already exists:"), `evals/${slug}.eval.yaml`);
|
|
22426
22830
|
}
|
|
@@ -22444,7 +22848,7 @@ var addCommand = new Command("add").description("Scaffold a new resource").argum
|
|
|
22444
22848
|
console.log(source_default.gray(" \u2192"), file);
|
|
22445
22849
|
}
|
|
22446
22850
|
console.log();
|
|
22447
|
-
console.log(source_default.gray("Edit the YAML file, then run"), source_default.cyan("struere
|
|
22851
|
+
console.log(source_default.gray("Edit the YAML file, then run"), source_default.cyan("struere sync"), source_default.gray("to sync"));
|
|
22448
22852
|
} else {
|
|
22449
22853
|
console.log(source_default.yellow("Fixture already exists:"), `fixtures/${slug}.fixture.yaml`);
|
|
22450
22854
|
}
|
|
@@ -22463,7 +22867,7 @@ var addCommand = new Command("add").description("Scaffold a new resource").argum
|
|
|
22463
22867
|
process.exit(1);
|
|
22464
22868
|
}
|
|
22465
22869
|
console.log();
|
|
22466
|
-
console.log(source_default.gray("Run"), source_default.cyan("struere
|
|
22870
|
+
console.log(source_default.gray("Run"), source_default.cyan("struere sync"), source_default.gray("to sync changes"));
|
|
22467
22871
|
console.log();
|
|
22468
22872
|
});
|
|
22469
22873
|
function slugify2(name) {
|
|
@@ -22471,13 +22875,25 @@ function slugify2(name) {
|
|
|
22471
22875
|
}
|
|
22472
22876
|
|
|
22473
22877
|
// src/cli/commands/status.ts
|
|
22474
|
-
var statusCommand = new Command("status").description("Compare local vs remote state").action(async () => {
|
|
22878
|
+
var statusCommand = new Command("status").description("Compare local vs remote state").option("--json", "Output raw JSON").action(async (opts) => {
|
|
22475
22879
|
const spinner = ora();
|
|
22476
22880
|
const cwd = process.cwd();
|
|
22477
|
-
|
|
22478
|
-
|
|
22479
|
-
|
|
22881
|
+
const jsonMode = !!opts.json;
|
|
22882
|
+
const nonInteractive = !isInteractive2();
|
|
22883
|
+
if (!jsonMode) {
|
|
22884
|
+
console.log();
|
|
22885
|
+
console.log(source_default.bold("Struere Status"));
|
|
22886
|
+
console.log();
|
|
22887
|
+
}
|
|
22480
22888
|
if (!hasProject(cwd)) {
|
|
22889
|
+
if (nonInteractive) {
|
|
22890
|
+
if (jsonMode) {
|
|
22891
|
+
console.log(JSON.stringify({ error: "No struere.json found. Run struere init first." }));
|
|
22892
|
+
} else {
|
|
22893
|
+
console.log(source_default.red("No struere.json found. Run struere init first."));
|
|
22894
|
+
}
|
|
22895
|
+
process.exit(1);
|
|
22896
|
+
}
|
|
22481
22897
|
console.log(source_default.yellow("No struere.json found - initializing project..."));
|
|
22482
22898
|
console.log();
|
|
22483
22899
|
const success = await runInit(cwd);
|
|
@@ -22488,14 +22904,28 @@ var statusCommand = new Command("status").description("Compare local vs remote s
|
|
|
22488
22904
|
}
|
|
22489
22905
|
const project = loadProject(cwd);
|
|
22490
22906
|
if (!project) {
|
|
22491
|
-
|
|
22907
|
+
if (jsonMode) {
|
|
22908
|
+
console.log(JSON.stringify({ error: "Failed to load struere.json" }));
|
|
22909
|
+
} else {
|
|
22910
|
+
console.log(source_default.red("Failed to load struere.json"));
|
|
22911
|
+
}
|
|
22492
22912
|
process.exit(1);
|
|
22493
22913
|
}
|
|
22494
|
-
|
|
22495
|
-
|
|
22914
|
+
if (!jsonMode) {
|
|
22915
|
+
console.log(source_default.gray("Organization:"), source_default.cyan(project.organization.name));
|
|
22916
|
+
console.log();
|
|
22917
|
+
}
|
|
22496
22918
|
let credentials = loadCredentials();
|
|
22497
22919
|
const apiKey = getApiKey();
|
|
22498
22920
|
if (!credentials && !apiKey) {
|
|
22921
|
+
if (nonInteractive) {
|
|
22922
|
+
if (jsonMode) {
|
|
22923
|
+
console.log(JSON.stringify({ error: "Not authenticated. Set STRUERE_API_KEY or run struere login." }));
|
|
22924
|
+
} else {
|
|
22925
|
+
console.log(source_default.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
22926
|
+
}
|
|
22927
|
+
process.exit(1);
|
|
22928
|
+
}
|
|
22499
22929
|
console.log(source_default.yellow("Not logged in - authenticating..."));
|
|
22500
22930
|
console.log();
|
|
22501
22931
|
credentials = await performLogin();
|
|
@@ -22505,34 +22935,49 @@ var statusCommand = new Command("status").description("Compare local vs remote s
|
|
|
22505
22935
|
}
|
|
22506
22936
|
console.log();
|
|
22507
22937
|
}
|
|
22508
|
-
|
|
22938
|
+
if (!jsonMode)
|
|
22939
|
+
spinner.start("Loading local resources");
|
|
22509
22940
|
let localResources;
|
|
22510
22941
|
try {
|
|
22511
22942
|
localResources = await loadAllResources(cwd);
|
|
22512
|
-
|
|
22513
|
-
|
|
22514
|
-
|
|
22943
|
+
if (!jsonMode) {
|
|
22944
|
+
spinner.succeed(`Loaded ${localResources.agents.length} agents, ${localResources.entityTypes.length} entity types, ${localResources.roles.length} roles, ${localResources.customTools.length} custom tools, ${localResources.evalSuites.length} eval suites`);
|
|
22945
|
+
for (const err of localResources.errors) {
|
|
22946
|
+
console.log(source_default.red(" \u2716"), err);
|
|
22947
|
+
}
|
|
22515
22948
|
}
|
|
22516
22949
|
if (localResources.errors.length > 0) {
|
|
22950
|
+
if (jsonMode) {
|
|
22951
|
+
console.log(JSON.stringify({ error: `${localResources.errors.length} resource loading error(s)`, errors: localResources.errors }));
|
|
22952
|
+
}
|
|
22517
22953
|
process.exit(1);
|
|
22518
22954
|
}
|
|
22519
22955
|
} catch (error) {
|
|
22520
|
-
|
|
22521
|
-
|
|
22956
|
+
if (jsonMode) {
|
|
22957
|
+
console.log(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
|
|
22958
|
+
} else {
|
|
22959
|
+
spinner.fail("Failed to load local resources");
|
|
22960
|
+
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
22961
|
+
}
|
|
22522
22962
|
process.exit(1);
|
|
22523
22963
|
}
|
|
22524
|
-
|
|
22964
|
+
if (!jsonMode)
|
|
22965
|
+
spinner.start("Fetching remote state");
|
|
22525
22966
|
const [devResult, prodResult] = await Promise.all([
|
|
22526
22967
|
getSyncState(undefined, "development"),
|
|
22527
22968
|
getSyncState(undefined, "production")
|
|
22528
22969
|
]);
|
|
22529
22970
|
if (devResult.error || !devResult.state) {
|
|
22530
|
-
|
|
22531
|
-
|
|
22971
|
+
if (jsonMode) {
|
|
22972
|
+
console.log(JSON.stringify({ error: devResult.error || "Failed to fetch remote state" }));
|
|
22973
|
+
} else {
|
|
22974
|
+
spinner.fail("Failed to fetch remote state");
|
|
22975
|
+
console.log(source_default.red("Error:"), devResult.error || "Unknown error");
|
|
22976
|
+
}
|
|
22532
22977
|
process.exit(1);
|
|
22533
22978
|
}
|
|
22534
|
-
|
|
22535
|
-
|
|
22979
|
+
if (!jsonMode)
|
|
22980
|
+
spinner.succeed("Remote state fetched");
|
|
22536
22981
|
const devState = devResult.state;
|
|
22537
22982
|
const prodState = prodResult.state;
|
|
22538
22983
|
const localAgentSlugs = new Set(localResources.agents.map((a) => a.slug));
|
|
@@ -22541,6 +22986,24 @@ var statusCommand = new Command("status").description("Compare local vs remote s
|
|
|
22541
22986
|
const devEntityTypeSlugs = new Set(devState.entityTypes.map((et) => et.slug));
|
|
22542
22987
|
const localRoleNames = new Set(localResources.roles.map((r) => r.name));
|
|
22543
22988
|
const devRoleNames = new Set(devState.roles.map((r) => r.name));
|
|
22989
|
+
if (opts.json) {
|
|
22990
|
+
const classify = (localItems, remoteItems, useSlug) => {
|
|
22991
|
+
const localKeys = new Set(localItems.map((i) => useSlug ? i.slug : i.name));
|
|
22992
|
+
const remoteKeys = new Set(remoteItems.map((i) => useSlug ? i.slug : i.name));
|
|
22993
|
+
return {
|
|
22994
|
+
synced: localItems.filter((i) => remoteKeys.has(useSlug ? i.slug : i.name)).map((i) => useSlug ? i.slug : i.name),
|
|
22995
|
+
new: localItems.filter((i) => !remoteKeys.has(useSlug ? i.slug : i.name)).map((i) => useSlug ? i.slug : i.name),
|
|
22996
|
+
deleted: remoteItems.filter((i) => !localKeys.has(useSlug ? i.slug : i.name)).map((i) => useSlug ? i.slug : i.name)
|
|
22997
|
+
};
|
|
22998
|
+
};
|
|
22999
|
+
console.log(JSON.stringify({
|
|
23000
|
+
agents: classify(localResources.agents, devState.agents, true),
|
|
23001
|
+
entityTypes: classify(localResources.entityTypes, devState.entityTypes, true),
|
|
23002
|
+
roles: classify(localResources.roles, devState.roles, false)
|
|
23003
|
+
}));
|
|
23004
|
+
return;
|
|
23005
|
+
}
|
|
23006
|
+
console.log();
|
|
22544
23007
|
console.log(source_default.bold("Agents"));
|
|
22545
23008
|
console.log(source_default.gray("\u2500".repeat(60)));
|
|
22546
23009
|
if (localResources.agents.length === 0 && devState.agents.length === 0) {
|
|
@@ -22609,7 +23072,7 @@ var statusCommand = new Command("status").description("Compare local vs remote s
|
|
|
22609
23072
|
console.log(source_default.gray("Legend:"));
|
|
22610
23073
|
console.log(source_default.gray(" "), source_default.green("\u25CF"), "Synced", source_default.yellow("\u25CB"), "Not in production", source_default.blue("+"), "New", source_default.red("-"), "Will be deleted");
|
|
22611
23074
|
console.log();
|
|
22612
|
-
console.log(source_default.gray("Run"), source_default.cyan("struere
|
|
23075
|
+
console.log(source_default.gray("Run"), source_default.cyan("struere sync"), source_default.gray("to sync to development"));
|
|
22613
23076
|
console.log(source_default.gray("Run"), source_default.cyan("struere deploy"), source_default.gray("to deploy to production"));
|
|
22614
23077
|
console.log();
|
|
22615
23078
|
});
|
|
@@ -22908,13 +23371,25 @@ function collectCustomTools(agents) {
|
|
|
22908
23371
|
}
|
|
22909
23372
|
|
|
22910
23373
|
// src/cli/commands/pull.ts
|
|
22911
|
-
var pullCommand = new Command("pull").description("Pull remote resources to local files").option("--force", "Overwrite existing local files").option("--env <environment>", "Environment to pull from", "development").option("--dry-run", "Show what would be written without writing").action(async (options) => {
|
|
23374
|
+
var pullCommand = new Command("pull").description("Pull remote resources to local files").option("--force", "Overwrite existing local files").option("--env <environment>", "Environment to pull from", "development").option("--dry-run", "Show what would be written without writing").option("--json", "Output raw JSON").action(async (options) => {
|
|
22912
23375
|
const spinner = ora();
|
|
22913
23376
|
const cwd = process.cwd();
|
|
22914
|
-
|
|
22915
|
-
|
|
22916
|
-
|
|
23377
|
+
const jsonMode = !!options.json;
|
|
23378
|
+
const nonInteractive = !isInteractive2();
|
|
23379
|
+
if (!jsonMode) {
|
|
23380
|
+
console.log();
|
|
23381
|
+
console.log(source_default.bold("Struere Pull"));
|
|
23382
|
+
console.log();
|
|
23383
|
+
}
|
|
22917
23384
|
if (!hasProject(cwd)) {
|
|
23385
|
+
if (nonInteractive) {
|
|
23386
|
+
if (jsonMode) {
|
|
23387
|
+
console.log(JSON.stringify({ error: "No struere.json found. Run struere init first." }));
|
|
23388
|
+
} else {
|
|
23389
|
+
console.log(source_default.red("No struere.json found. Run struere init first."));
|
|
23390
|
+
}
|
|
23391
|
+
process.exit(1);
|
|
23392
|
+
}
|
|
22918
23393
|
console.log(source_default.yellow("No struere.json found - initializing project..."));
|
|
22919
23394
|
console.log();
|
|
22920
23395
|
const success = await runInit(cwd);
|
|
@@ -22925,15 +23400,29 @@ var pullCommand = new Command("pull").description("Pull remote resources to loca
|
|
|
22925
23400
|
}
|
|
22926
23401
|
const project = loadProject(cwd);
|
|
22927
23402
|
if (!project) {
|
|
22928
|
-
|
|
23403
|
+
if (jsonMode) {
|
|
23404
|
+
console.log(JSON.stringify({ error: "Failed to load struere.json" }));
|
|
23405
|
+
} else {
|
|
23406
|
+
console.log(source_default.red("Failed to load struere.json"));
|
|
23407
|
+
}
|
|
22929
23408
|
process.exit(1);
|
|
22930
23409
|
}
|
|
22931
|
-
|
|
22932
|
-
|
|
22933
|
-
|
|
23410
|
+
if (!jsonMode) {
|
|
23411
|
+
console.log(source_default.gray("Organization:"), source_default.cyan(project.organization.name));
|
|
23412
|
+
console.log(source_default.gray("Environment:"), source_default.cyan(options.env));
|
|
23413
|
+
console.log();
|
|
23414
|
+
}
|
|
22934
23415
|
let credentials = loadCredentials();
|
|
22935
23416
|
const apiKey = getApiKey();
|
|
22936
23417
|
if (!credentials && !apiKey) {
|
|
23418
|
+
if (nonInteractive) {
|
|
23419
|
+
if (jsonMode) {
|
|
23420
|
+
console.log(JSON.stringify({ error: "Not authenticated. Set STRUERE_API_KEY or run struere login." }));
|
|
23421
|
+
} else {
|
|
23422
|
+
console.log(source_default.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
23423
|
+
}
|
|
23424
|
+
process.exit(1);
|
|
23425
|
+
}
|
|
22937
23426
|
console.log(source_default.yellow("Not logged in - authenticating..."));
|
|
22938
23427
|
console.log();
|
|
22939
23428
|
credentials = await performLogin();
|
|
@@ -22943,16 +23432,23 @@ var pullCommand = new Command("pull").description("Pull remote resources to loca
|
|
|
22943
23432
|
}
|
|
22944
23433
|
console.log();
|
|
22945
23434
|
}
|
|
22946
|
-
|
|
23435
|
+
if (!jsonMode)
|
|
23436
|
+
spinner.start("Fetching remote state");
|
|
22947
23437
|
const environment = options.env;
|
|
22948
23438
|
const { state, error } = await getPullState(project.organization.id, environment);
|
|
22949
23439
|
if (error || !state) {
|
|
22950
|
-
|
|
22951
|
-
|
|
23440
|
+
if (jsonMode) {
|
|
23441
|
+
console.log(JSON.stringify({ error: error || "Failed to fetch remote state" }));
|
|
23442
|
+
} else {
|
|
23443
|
+
spinner.fail("Failed to fetch remote state");
|
|
23444
|
+
console.log(source_default.red("Error:"), error || "Unknown error");
|
|
23445
|
+
}
|
|
22952
23446
|
process.exit(1);
|
|
22953
23447
|
}
|
|
22954
|
-
|
|
22955
|
-
|
|
23448
|
+
if (!jsonMode) {
|
|
23449
|
+
spinner.succeed("Remote state fetched");
|
|
23450
|
+
console.log();
|
|
23451
|
+
}
|
|
22956
23452
|
const created = [];
|
|
22957
23453
|
const skipped = [];
|
|
22958
23454
|
const ensureDir2 = (dir) => {
|
|
@@ -23031,6 +23527,14 @@ var pullCommand = new Command("pull").description("Pull remote resources to loca
|
|
|
23031
23527
|
if (content)
|
|
23032
23528
|
writeOrSkip("triggers/index.ts", content);
|
|
23033
23529
|
}
|
|
23530
|
+
if (options.json) {
|
|
23531
|
+
console.log(JSON.stringify({
|
|
23532
|
+
created,
|
|
23533
|
+
skipped,
|
|
23534
|
+
dryRun: !!options.dryRun
|
|
23535
|
+
}));
|
|
23536
|
+
return;
|
|
23537
|
+
}
|
|
23034
23538
|
if (options.dryRun) {
|
|
23035
23539
|
console.log(source_default.cyan("Dry run - no files written"));
|
|
23036
23540
|
console.log();
|
|
@@ -23054,7 +23558,7 @@ var pullCommand = new Command("pull").description("Pull remote resources to loca
|
|
|
23054
23558
|
console.log();
|
|
23055
23559
|
}
|
|
23056
23560
|
if (!options.dryRun && created.length > 0) {
|
|
23057
|
-
console.log(source_default.gray("Run"), source_default.cyan("struere
|
|
23561
|
+
console.log(source_default.gray("Run"), source_default.cyan("struere sync"), source_default.gray("to sync changes"));
|
|
23058
23562
|
console.log();
|
|
23059
23563
|
}
|
|
23060
23564
|
});
|
|
@@ -23229,7 +23733,12 @@ function deriveColumnsFromSchema(schema, displayConfig) {
|
|
|
23229
23733
|
// src/cli/commands/entities.ts
|
|
23230
23734
|
async function ensureAuth() {
|
|
23231
23735
|
const cwd = process.cwd();
|
|
23736
|
+
const nonInteractive = !isInteractive2();
|
|
23232
23737
|
if (!hasProject(cwd)) {
|
|
23738
|
+
if (nonInteractive) {
|
|
23739
|
+
console.error(source_default.red("No struere.json found. Run struere init first."));
|
|
23740
|
+
process.exit(1);
|
|
23741
|
+
}
|
|
23233
23742
|
console.log(source_default.yellow("No struere.json found - initializing project..."));
|
|
23234
23743
|
console.log();
|
|
23235
23744
|
const success = await runInit(cwd);
|
|
@@ -23241,6 +23750,10 @@ async function ensureAuth() {
|
|
|
23241
23750
|
let credentials = loadCredentials();
|
|
23242
23751
|
const apiKey = getApiKey();
|
|
23243
23752
|
if (!credentials && !apiKey) {
|
|
23753
|
+
if (nonInteractive) {
|
|
23754
|
+
console.error(source_default.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
23755
|
+
process.exit(1);
|
|
23756
|
+
}
|
|
23244
23757
|
console.log(source_default.yellow("Not logged in - authenticating..."));
|
|
23245
23758
|
console.log();
|
|
23246
23759
|
credentials = await performLogin();
|
|
@@ -23377,6 +23890,9 @@ entitiesCommand.command("create <type>").description("Create a new entity").opti
|
|
|
23377
23890
|
console.log(source_default.red("Invalid JSON in --data"));
|
|
23378
23891
|
process.exit(1);
|
|
23379
23892
|
}
|
|
23893
|
+
} else if (!isInteractive2()) {
|
|
23894
|
+
console.log(source_default.red("--data <json> is required in non-interactive mode"));
|
|
23895
|
+
process.exit(1);
|
|
23380
23896
|
} else {
|
|
23381
23897
|
spinner.start(`Fetching ${type} schema`);
|
|
23382
23898
|
const { data: typeData, error: error2 } = await queryEntityTypeBySlug(type, env2);
|
|
@@ -23468,32 +23984,41 @@ entitiesCommand.command("update <id>").description("Update an entity").option("-
|
|
|
23468
23984
|
console.log();
|
|
23469
23985
|
}
|
|
23470
23986
|
});
|
|
23471
|
-
entitiesCommand.command("delete <id>").description("Delete an entity").option("--env <environment>", "Environment (development|production)", "development").option("--yes", "Skip confirmation").action(async (id, opts) => {
|
|
23987
|
+
entitiesCommand.command("delete <id>").description("Delete an entity").option("--env <environment>", "Environment (development|production)", "development").option("--yes", "Skip confirmation").option("--json", "Output raw JSON").action(async (id, opts) => {
|
|
23472
23988
|
await ensureAuth();
|
|
23473
23989
|
const spinner = ora();
|
|
23474
23990
|
const env2 = opts.env;
|
|
23475
|
-
|
|
23991
|
+
const jsonMode = !!opts.json;
|
|
23992
|
+
if (!jsonMode)
|
|
23993
|
+
spinner.start("Fetching entity");
|
|
23476
23994
|
const { data, error: fetchError } = await queryEntity(id, env2);
|
|
23477
23995
|
if (fetchError || !data) {
|
|
23478
|
-
|
|
23479
|
-
|
|
23996
|
+
if (jsonMode) {
|
|
23997
|
+
console.log(JSON.stringify({ success: false, error: fetchError || "Entity not found" }));
|
|
23998
|
+
} else {
|
|
23999
|
+
spinner.fail("Failed to fetch entity");
|
|
24000
|
+
console.log(source_default.red("Error:"), fetchError || "Entity not found");
|
|
24001
|
+
}
|
|
23480
24002
|
process.exit(1);
|
|
23481
24003
|
}
|
|
23482
|
-
|
|
24004
|
+
if (!jsonMode)
|
|
24005
|
+
spinner.succeed("Entity loaded");
|
|
23483
24006
|
const result = data;
|
|
23484
24007
|
const entity = result.entity;
|
|
23485
24008
|
const entityType = result.entityType;
|
|
23486
24009
|
const entityData = entity.data;
|
|
23487
|
-
|
|
23488
|
-
|
|
23489
|
-
|
|
23490
|
-
|
|
23491
|
-
|
|
23492
|
-
|
|
24010
|
+
if (!jsonMode) {
|
|
24011
|
+
console.log();
|
|
24012
|
+
console.log(source_default.bold(` ${entityType.name}`), source_default.gray(`(${entity._id})`));
|
|
24013
|
+
if (entityData) {
|
|
24014
|
+
const preview = Object.entries(entityData).slice(0, 3);
|
|
24015
|
+
for (const [key, value] of preview) {
|
|
24016
|
+
console.log(` ${source_default.gray(key + ":")} ${String(value ?? "")}`);
|
|
24017
|
+
}
|
|
23493
24018
|
}
|
|
24019
|
+
console.log();
|
|
23494
24020
|
}
|
|
23495
|
-
|
|
23496
|
-
if (!opts.yes) {
|
|
24021
|
+
if (!opts.yes && !jsonMode && isInteractive2()) {
|
|
23497
24022
|
const confirmed = await esm_default2({
|
|
23498
24023
|
message: "Are you sure you want to delete this entity?",
|
|
23499
24024
|
default: false
|
|
@@ -23503,15 +24028,24 @@ entitiesCommand.command("delete <id>").description("Delete an entity").option("-
|
|
|
23503
24028
|
return;
|
|
23504
24029
|
}
|
|
23505
24030
|
}
|
|
23506
|
-
|
|
24031
|
+
if (!jsonMode)
|
|
24032
|
+
spinner.start("Deleting entity");
|
|
23507
24033
|
const { error } = await removeEntity(id, env2);
|
|
23508
24034
|
if (error) {
|
|
23509
|
-
|
|
23510
|
-
|
|
24035
|
+
if (jsonMode) {
|
|
24036
|
+
console.log(JSON.stringify({ success: false, error }));
|
|
24037
|
+
} else {
|
|
24038
|
+
spinner.fail("Failed to delete entity");
|
|
24039
|
+
console.log(source_default.red("Error:"), error);
|
|
24040
|
+
}
|
|
23511
24041
|
process.exit(1);
|
|
23512
24042
|
}
|
|
23513
|
-
|
|
23514
|
-
|
|
24043
|
+
if (jsonMode) {
|
|
24044
|
+
console.log(JSON.stringify({ success: true, id }));
|
|
24045
|
+
} else {
|
|
24046
|
+
spinner.succeed("Entity deleted");
|
|
24047
|
+
console.log();
|
|
24048
|
+
}
|
|
23515
24049
|
});
|
|
23516
24050
|
entitiesCommand.command("search <type> <query>").description("Search entities").option("--env <environment>", "Environment (development|production)", "development").option("--limit <n>", "Maximum results", "25").option("--json", "Output raw JSON").action(async (type, query, opts) => {
|
|
23517
24051
|
await ensureAuth();
|
|
@@ -23791,7 +24325,7 @@ ${turn.assistantResponse}
|
|
|
23791
24325
|
}
|
|
23792
24326
|
return md;
|
|
23793
24327
|
}
|
|
23794
|
-
var runCommand = new Command("run").description("Run an eval suite").argument("<suite-slug>", "Eval suite slug to run").option("--case <name...>", "Run specific case(s) by name").option("--tag <tag...>", "Run cases matching tag(s)").action(async (suiteSlug, options) => {
|
|
24328
|
+
var runCommand = new Command("run").description("Run an eval suite").argument("<suite-slug>", "Eval suite slug to run").option("--case <name...>", "Run specific case(s) by name").option("--tag <tag...>", "Run cases matching tag(s)").option("--timeout <seconds>", "Max seconds to wait for results (default: 300)").action(async (suiteSlug, options) => {
|
|
23795
24329
|
const spinner = ora();
|
|
23796
24330
|
const cwd = process.cwd();
|
|
23797
24331
|
console.log();
|
|
@@ -23925,6 +24459,9 @@ var runCommand = new Command("run").description("Run an eval suite").argument("<
|
|
|
23925
24459
|
console.log(source_default.red("Failed to start run:"), error instanceof Error ? error.message : String(error));
|
|
23926
24460
|
process.exit(1);
|
|
23927
24461
|
}
|
|
24462
|
+
console.log(source_default.gray("Run ID:"), source_default.cyan(runId));
|
|
24463
|
+
const timeoutMs = (options.timeout ? parseInt(options.timeout, 10) : 300) * 1000;
|
|
24464
|
+
const startTime = Date.now();
|
|
23928
24465
|
spinner.start("Running...");
|
|
23929
24466
|
let run = null;
|
|
23930
24467
|
while (true) {
|
|
@@ -23937,6 +24474,11 @@ var runCommand = new Command("run").description("Run an eval suite").argument("<
|
|
|
23937
24474
|
if (run.status === "completed" || run.status === "failed" || run.status === "cancelled") {
|
|
23938
24475
|
break;
|
|
23939
24476
|
}
|
|
24477
|
+
if (Date.now() - startTime > timeoutMs) {
|
|
24478
|
+
spinner.fail(`Timed out after ${options.timeout || 300}s \u2014 run is still in progress`);
|
|
24479
|
+
console.log(source_default.gray("Run ID:"), source_default.cyan(runId));
|
|
24480
|
+
process.exit(2);
|
|
24481
|
+
}
|
|
23940
24482
|
spinner.text = `Running: ${run.completedCases}/${run.totalCases} cases (${run.passedCases} passed, ${run.failedCases} failed)`;
|
|
23941
24483
|
}
|
|
23942
24484
|
const duration = run.totalDurationMs ? formatDuration(run.totalDurationMs) : "-";
|
|
@@ -24094,7 +24636,12 @@ async function getTemplateStatus(connectionId, env2, name) {
|
|
|
24094
24636
|
// src/cli/commands/templates.ts
|
|
24095
24637
|
async function ensureAuth2() {
|
|
24096
24638
|
const cwd = process.cwd();
|
|
24639
|
+
const nonInteractive = !isInteractive2();
|
|
24097
24640
|
if (!hasProject(cwd)) {
|
|
24641
|
+
if (nonInteractive) {
|
|
24642
|
+
console.error(source_default.red("No struere.json found. Run struere init first."));
|
|
24643
|
+
process.exit(1);
|
|
24644
|
+
}
|
|
24098
24645
|
console.log(source_default.yellow("No struere.json found - initializing project..."));
|
|
24099
24646
|
console.log();
|
|
24100
24647
|
const success = await runInit(cwd);
|
|
@@ -24106,6 +24653,10 @@ async function ensureAuth2() {
|
|
|
24106
24653
|
let credentials = loadCredentials();
|
|
24107
24654
|
const apiKey = getApiKey();
|
|
24108
24655
|
if (!credentials && !apiKey) {
|
|
24656
|
+
if (nonInteractive) {
|
|
24657
|
+
console.error(source_default.red("Not authenticated. Set STRUERE_API_KEY or run struere login."));
|
|
24658
|
+
process.exit(1);
|
|
24659
|
+
}
|
|
24109
24660
|
console.log(source_default.yellow("Not logged in - authenticating..."));
|
|
24110
24661
|
console.log();
|
|
24111
24662
|
credentials = await performLogin();
|
|
@@ -24255,7 +24806,7 @@ templatesCommand.command("delete <name>").description("Delete a message template
|
|
|
24255
24806
|
await ensureAuth2();
|
|
24256
24807
|
const env2 = opts.env;
|
|
24257
24808
|
const connectionId = await resolveConnectionId(env2, opts.connection);
|
|
24258
|
-
if (!opts.yes) {
|
|
24809
|
+
if (!opts.yes && isInteractive2()) {
|
|
24259
24810
|
const confirmed = await esm_default2({
|
|
24260
24811
|
message: `Delete template "${name}"? This cannot be undone.`,
|
|
24261
24812
|
default: false
|
|
@@ -24314,7 +24865,7 @@ templatesCommand.command("status <name>").description("Check template approval s
|
|
|
24314
24865
|
// package.json
|
|
24315
24866
|
var package_default = {
|
|
24316
24867
|
name: "struere",
|
|
24317
|
-
version: "0.
|
|
24868
|
+
version: "0.9.0",
|
|
24318
24869
|
description: "Build, test, and deploy AI agents",
|
|
24319
24870
|
keywords: [
|
|
24320
24871
|
"ai",
|
|
@@ -24380,6 +24931,8 @@ var CURRENT_VERSION = package_default.version;
|
|
|
24380
24931
|
async function checkForUpdates() {
|
|
24381
24932
|
if (process.env.STRUERE_SKIP_UPDATE_CHECK)
|
|
24382
24933
|
return;
|
|
24934
|
+
if (process.env.STRUERE_API_KEY || !process.stdout.isTTY)
|
|
24935
|
+
return;
|
|
24383
24936
|
try {
|
|
24384
24937
|
const response = await fetch("https://registry.npmjs.org/struere/latest", {
|
|
24385
24938
|
signal: AbortSignal.timeout(2000)
|
|
@@ -24399,9 +24952,12 @@ async function checkForUpdates() {
|
|
|
24399
24952
|
return 0;
|
|
24400
24953
|
};
|
|
24401
24954
|
if (semverCompare(data.version, CURRENT_VERSION) > 0) {
|
|
24402
|
-
|
|
24403
|
-
|
|
24404
|
-
|
|
24955
|
+
process.stderr.write(`\x1B[33m\u26A0 Update available: ${CURRENT_VERSION} \u2192 ${data.version}\x1B[0m
|
|
24956
|
+
`);
|
|
24957
|
+
process.stderr.write(`\x1B[90m Run: npm install -g struere@${data.version}\x1B[0m
|
|
24958
|
+
`);
|
|
24959
|
+
process.stderr.write(`
|
|
24960
|
+
`);
|
|
24405
24961
|
}
|
|
24406
24962
|
}
|
|
24407
24963
|
}
|
|
@@ -24413,6 +24969,7 @@ program.addCommand(initCommand);
|
|
|
24413
24969
|
program.addCommand(loginCommand);
|
|
24414
24970
|
program.addCommand(logoutCommand);
|
|
24415
24971
|
program.addCommand(whoamiCommand);
|
|
24972
|
+
program.addCommand(syncCommand);
|
|
24416
24973
|
program.addCommand(devCommand);
|
|
24417
24974
|
program.addCommand(deployCommand);
|
|
24418
24975
|
program.addCommand(addCommand);
|