struere 0.3.11 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/struere.js +1123 -1168
- package/dist/cli/commands/add.d.ts +3 -0
- package/dist/cli/commands/add.d.ts.map +1 -0
- package/dist/cli/commands/deploy.d.ts.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/init.d.ts +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/index.js +1144 -1184
- package/dist/cli/templates/index.d.ts +8 -0
- package/dist/cli/templates/index.d.ts.map +1 -1
- package/dist/cli/utils/convex.d.ts +101 -2
- package/dist/cli/utils/convex.d.ts.map +1 -1
- package/dist/cli/utils/extractor.d.ts +54 -0
- package/dist/cli/utils/extractor.d.ts.map +1 -0
- package/dist/cli/utils/loader.d.ts +20 -0
- package/dist/cli/utils/loader.d.ts.map +1 -0
- package/dist/cli/utils/project.d.ts +12 -0
- package/dist/cli/utils/project.d.ts.map +1 -1
- package/dist/cli/utils/scaffold.d.ts +11 -0
- package/dist/cli/utils/scaffold.d.ts.map +1 -1
- package/dist/define/entityType.d.ts +3 -0
- package/dist/define/entityType.d.ts.map +1 -0
- package/dist/define/role.d.ts +3 -0
- package/dist/define/role.d.ts.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +50 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/bin/struere.js
CHANGED
|
@@ -16674,8 +16674,7 @@ function ora(options) {
|
|
|
16674
16674
|
}
|
|
16675
16675
|
|
|
16676
16676
|
// src/cli/commands/init.ts
|
|
16677
|
-
import {
|
|
16678
|
-
import { join as join4, basename } from "path";
|
|
16677
|
+
import { basename } from "path";
|
|
16679
16678
|
|
|
16680
16679
|
// src/cli/utils/credentials.ts
|
|
16681
16680
|
import { homedir } from "os";
|
|
@@ -16723,110 +16722,6 @@ function getApiKey() {
|
|
|
16723
16722
|
|
|
16724
16723
|
// src/cli/utils/convex.ts
|
|
16725
16724
|
var CONVEX_URL = process.env.STRUERE_CONVEX_URL || "https://rapid-wildebeest-172.convex.cloud";
|
|
16726
|
-
async function syncToConvex(agentId, config) {
|
|
16727
|
-
const credentials = loadCredentials();
|
|
16728
|
-
const apiKey = getApiKey();
|
|
16729
|
-
const token = apiKey || credentials?.token;
|
|
16730
|
-
if (!token) {
|
|
16731
|
-
return { success: false, error: "Not authenticated" };
|
|
16732
|
-
}
|
|
16733
|
-
const response = await fetch(`${CONVEX_URL}/api/mutation`, {
|
|
16734
|
-
method: "POST",
|
|
16735
|
-
headers: {
|
|
16736
|
-
"Content-Type": "application/json",
|
|
16737
|
-
Authorization: `Bearer ${token}`
|
|
16738
|
-
},
|
|
16739
|
-
body: JSON.stringify({
|
|
16740
|
-
path: "agents:syncDevelopment",
|
|
16741
|
-
args: {
|
|
16742
|
-
agentId,
|
|
16743
|
-
config
|
|
16744
|
-
}
|
|
16745
|
-
})
|
|
16746
|
-
});
|
|
16747
|
-
if (!response.ok) {
|
|
16748
|
-
const error = await response.text();
|
|
16749
|
-
return { success: false, error };
|
|
16750
|
-
}
|
|
16751
|
-
const result = await response.json();
|
|
16752
|
-
return { success: result.success ?? true };
|
|
16753
|
-
}
|
|
16754
|
-
async function deployToProduction(agentId) {
|
|
16755
|
-
const credentials = loadCredentials();
|
|
16756
|
-
const apiKey = getApiKey();
|
|
16757
|
-
const token = apiKey || credentials?.token;
|
|
16758
|
-
if (!token) {
|
|
16759
|
-
return { success: false, error: "Not authenticated" };
|
|
16760
|
-
}
|
|
16761
|
-
const response = await fetch(`${CONVEX_URL}/api/mutation`, {
|
|
16762
|
-
method: "POST",
|
|
16763
|
-
headers: {
|
|
16764
|
-
"Content-Type": "application/json",
|
|
16765
|
-
Authorization: `Bearer ${token}`
|
|
16766
|
-
},
|
|
16767
|
-
body: JSON.stringify({
|
|
16768
|
-
path: "agents:deploy",
|
|
16769
|
-
args: { agentId }
|
|
16770
|
-
})
|
|
16771
|
-
});
|
|
16772
|
-
if (!response.ok) {
|
|
16773
|
-
const error = await response.text();
|
|
16774
|
-
return { success: false, error };
|
|
16775
|
-
}
|
|
16776
|
-
const result = await response.json();
|
|
16777
|
-
return { success: result.success ?? true, configId: result.configId };
|
|
16778
|
-
}
|
|
16779
|
-
async function listAgents() {
|
|
16780
|
-
const credentials = loadCredentials();
|
|
16781
|
-
const apiKey = getApiKey();
|
|
16782
|
-
const token = apiKey || credentials?.token;
|
|
16783
|
-
if (!token) {
|
|
16784
|
-
return { agents: [], error: "Not authenticated" };
|
|
16785
|
-
}
|
|
16786
|
-
const response = await fetch(`${CONVEX_URL}/api/query`, {
|
|
16787
|
-
method: "POST",
|
|
16788
|
-
headers: {
|
|
16789
|
-
"Content-Type": "application/json",
|
|
16790
|
-
Authorization: `Bearer ${token}`
|
|
16791
|
-
},
|
|
16792
|
-
body: JSON.stringify({
|
|
16793
|
-
path: "agents:list",
|
|
16794
|
-
args: {}
|
|
16795
|
-
})
|
|
16796
|
-
});
|
|
16797
|
-
if (!response.ok) {
|
|
16798
|
-
const error = await response.text();
|
|
16799
|
-
return { agents: [], error };
|
|
16800
|
-
}
|
|
16801
|
-
const result = await response.json();
|
|
16802
|
-
const agents = Array.isArray(result) ? result : result?.value || [];
|
|
16803
|
-
return { agents };
|
|
16804
|
-
}
|
|
16805
|
-
async function createAgent(data) {
|
|
16806
|
-
const credentials = loadCredentials();
|
|
16807
|
-
const apiKey = getApiKey();
|
|
16808
|
-
const token = apiKey || credentials?.token;
|
|
16809
|
-
if (!token) {
|
|
16810
|
-
return { error: "Not authenticated" };
|
|
16811
|
-
}
|
|
16812
|
-
const response = await fetch(`${CONVEX_URL}/api/mutation`, {
|
|
16813
|
-
method: "POST",
|
|
16814
|
-
headers: {
|
|
16815
|
-
"Content-Type": "application/json",
|
|
16816
|
-
Authorization: `Bearer ${token}`
|
|
16817
|
-
},
|
|
16818
|
-
body: JSON.stringify({
|
|
16819
|
-
path: "agents:create",
|
|
16820
|
-
args: data
|
|
16821
|
-
})
|
|
16822
|
-
});
|
|
16823
|
-
if (!response.ok) {
|
|
16824
|
-
const error = await response.text();
|
|
16825
|
-
return { error };
|
|
16826
|
-
}
|
|
16827
|
-
const agentId = await response.json();
|
|
16828
|
-
return { agentId };
|
|
16829
|
-
}
|
|
16830
16725
|
async function getUserInfo(token) {
|
|
16831
16726
|
const ensureResponse = await fetch(`${CONVEX_URL}/api/mutation`, {
|
|
16832
16727
|
method: "POST",
|
|
@@ -16898,73 +16793,6 @@ async function getUserInfo(token) {
|
|
|
16898
16793
|
}
|
|
16899
16794
|
};
|
|
16900
16795
|
}
|
|
16901
|
-
function extractConfig(agent) {
|
|
16902
|
-
const BUILTIN_TOOLS = [
|
|
16903
|
-
"entity.create",
|
|
16904
|
-
"entity.get",
|
|
16905
|
-
"entity.query",
|
|
16906
|
-
"entity.update",
|
|
16907
|
-
"entity.delete",
|
|
16908
|
-
"entity.link",
|
|
16909
|
-
"entity.unlink",
|
|
16910
|
-
"event.emit",
|
|
16911
|
-
"event.query",
|
|
16912
|
-
"job.enqueue",
|
|
16913
|
-
"job.status"
|
|
16914
|
-
];
|
|
16915
|
-
let systemPrompt;
|
|
16916
|
-
if (typeof agent.systemPrompt === "function") {
|
|
16917
|
-
const result = agent.systemPrompt();
|
|
16918
|
-
if (result instanceof Promise) {
|
|
16919
|
-
throw new Error("Async system prompts must be resolved before syncing");
|
|
16920
|
-
}
|
|
16921
|
-
systemPrompt = result;
|
|
16922
|
-
} else {
|
|
16923
|
-
systemPrompt = agent.systemPrompt;
|
|
16924
|
-
}
|
|
16925
|
-
const tools = (agent.tools || []).map((tool) => {
|
|
16926
|
-
const isBuiltin = BUILTIN_TOOLS.includes(tool.name);
|
|
16927
|
-
let handlerCode;
|
|
16928
|
-
if (!isBuiltin && tool.handler) {
|
|
16929
|
-
handlerCode = extractHandlerCode(tool.handler);
|
|
16930
|
-
}
|
|
16931
|
-
return {
|
|
16932
|
-
name: tool.name,
|
|
16933
|
-
description: tool.description,
|
|
16934
|
-
parameters: tool.parameters || { type: "object", properties: {} },
|
|
16935
|
-
handlerCode,
|
|
16936
|
-
isBuiltin
|
|
16937
|
-
};
|
|
16938
|
-
});
|
|
16939
|
-
return {
|
|
16940
|
-
name: agent.name,
|
|
16941
|
-
version: agent.version || "0.0.1",
|
|
16942
|
-
systemPrompt,
|
|
16943
|
-
model: {
|
|
16944
|
-
provider: agent.model?.provider || "anthropic",
|
|
16945
|
-
name: agent.model?.name || "claude-sonnet-4-20250514",
|
|
16946
|
-
temperature: agent.model?.temperature,
|
|
16947
|
-
maxTokens: agent.model?.maxTokens
|
|
16948
|
-
},
|
|
16949
|
-
tools
|
|
16950
|
-
};
|
|
16951
|
-
}
|
|
16952
|
-
function extractHandlerCode(handler) {
|
|
16953
|
-
const code = handler.toString();
|
|
16954
|
-
const arrowMatch = code.match(/(?:async\s*)?\([^)]*\)\s*=>\s*\{?([\s\S]*)\}?$/);
|
|
16955
|
-
if (arrowMatch) {
|
|
16956
|
-
let body = arrowMatch[1].trim();
|
|
16957
|
-
if (body.startsWith("{") && body.endsWith("}")) {
|
|
16958
|
-
body = body.slice(1, -1).trim();
|
|
16959
|
-
}
|
|
16960
|
-
return body;
|
|
16961
|
-
}
|
|
16962
|
-
const funcMatch = code.match(/(?:async\s*)?function[^(]*\([^)]*\)\s*\{([\s\S]*)\}$/);
|
|
16963
|
-
if (funcMatch) {
|
|
16964
|
-
return funcMatch[1].trim();
|
|
16965
|
-
}
|
|
16966
|
-
return code;
|
|
16967
|
-
}
|
|
16968
16796
|
async function getRecentExecutions(limit = 100) {
|
|
16969
16797
|
const credentials = loadCredentials();
|
|
16970
16798
|
const apiKey = getApiKey();
|
|
@@ -17054,6 +16882,81 @@ async function runTestConversation(agentId, message, threadId) {
|
|
|
17054
16882
|
threadId: result.threadId
|
|
17055
16883
|
};
|
|
17056
16884
|
}
|
|
16885
|
+
async function syncOrganization(payload) {
|
|
16886
|
+
const credentials = loadCredentials();
|
|
16887
|
+
const apiKey = getApiKey();
|
|
16888
|
+
const token = apiKey || credentials?.token;
|
|
16889
|
+
if (!token) {
|
|
16890
|
+
return { success: false, error: "Not authenticated" };
|
|
16891
|
+
}
|
|
16892
|
+
const response = await fetch(`${CONVEX_URL}/api/mutation`, {
|
|
16893
|
+
method: "POST",
|
|
16894
|
+
headers: {
|
|
16895
|
+
"Content-Type": "application/json",
|
|
16896
|
+
Authorization: `Bearer ${token}`
|
|
16897
|
+
},
|
|
16898
|
+
body: JSON.stringify({
|
|
16899
|
+
path: "sync:syncOrganization",
|
|
16900
|
+
args: payload
|
|
16901
|
+
})
|
|
16902
|
+
});
|
|
16903
|
+
if (!response.ok) {
|
|
16904
|
+
const error = await response.text();
|
|
16905
|
+
return { success: false, error };
|
|
16906
|
+
}
|
|
16907
|
+
const result = await response.json();
|
|
16908
|
+
return result;
|
|
16909
|
+
}
|
|
16910
|
+
async function getSyncState() {
|
|
16911
|
+
const credentials = loadCredentials();
|
|
16912
|
+
const apiKey = getApiKey();
|
|
16913
|
+
const token = apiKey || credentials?.token;
|
|
16914
|
+
if (!token) {
|
|
16915
|
+
return { error: "Not authenticated" };
|
|
16916
|
+
}
|
|
16917
|
+
const response = await fetch(`${CONVEX_URL}/api/query`, {
|
|
16918
|
+
method: "POST",
|
|
16919
|
+
headers: {
|
|
16920
|
+
"Content-Type": "application/json",
|
|
16921
|
+
Authorization: `Bearer ${token}`
|
|
16922
|
+
},
|
|
16923
|
+
body: JSON.stringify({
|
|
16924
|
+
path: "sync:getSyncState",
|
|
16925
|
+
args: {}
|
|
16926
|
+
})
|
|
16927
|
+
});
|
|
16928
|
+
if (!response.ok) {
|
|
16929
|
+
const error = await response.text();
|
|
16930
|
+
return { error };
|
|
16931
|
+
}
|
|
16932
|
+
const result = await response.json();
|
|
16933
|
+
return { state: result.value };
|
|
16934
|
+
}
|
|
16935
|
+
async function deployAllAgents() {
|
|
16936
|
+
const credentials = loadCredentials();
|
|
16937
|
+
const apiKey = getApiKey();
|
|
16938
|
+
const token = apiKey || credentials?.token;
|
|
16939
|
+
if (!token) {
|
|
16940
|
+
return { success: false, error: "Not authenticated" };
|
|
16941
|
+
}
|
|
16942
|
+
const response = await fetch(`${CONVEX_URL}/api/mutation`, {
|
|
16943
|
+
method: "POST",
|
|
16944
|
+
headers: {
|
|
16945
|
+
"Content-Type": "application/json",
|
|
16946
|
+
Authorization: `Bearer ${token}`
|
|
16947
|
+
},
|
|
16948
|
+
body: JSON.stringify({
|
|
16949
|
+
path: "sync:deployAllAgents",
|
|
16950
|
+
args: {}
|
|
16951
|
+
})
|
|
16952
|
+
});
|
|
16953
|
+
if (!response.ok) {
|
|
16954
|
+
const error = await response.text();
|
|
16955
|
+
return { success: false, error };
|
|
16956
|
+
}
|
|
16957
|
+
const result = await response.json();
|
|
16958
|
+
return result;
|
|
16959
|
+
}
|
|
17057
16960
|
|
|
17058
16961
|
// src/cli/commands/login.ts
|
|
17059
16962
|
var AUTH_CALLBACK_PORT = 9876;
|
|
@@ -17240,40 +17143,50 @@ function loadProject(cwd) {
|
|
|
17240
17143
|
return null;
|
|
17241
17144
|
}
|
|
17242
17145
|
}
|
|
17243
|
-
function
|
|
17146
|
+
function loadProjectV2(cwd) {
|
|
17244
17147
|
const projectPath = join2(cwd, PROJECT_FILE);
|
|
17245
|
-
|
|
17246
|
-
|
|
17148
|
+
if (!existsSync2(projectPath)) {
|
|
17149
|
+
return null;
|
|
17150
|
+
}
|
|
17151
|
+
try {
|
|
17152
|
+
const data = readFileSync2(projectPath, "utf-8");
|
|
17153
|
+
const parsed = JSON.parse(data);
|
|
17154
|
+
if (parsed.version === "2.0") {
|
|
17155
|
+
return parsed;
|
|
17156
|
+
}
|
|
17157
|
+
return null;
|
|
17158
|
+
} catch {
|
|
17159
|
+
return null;
|
|
17160
|
+
}
|
|
17247
17161
|
}
|
|
17248
17162
|
function hasProject(cwd) {
|
|
17249
17163
|
return existsSync2(join2(cwd, PROJECT_FILE));
|
|
17250
17164
|
}
|
|
17165
|
+
function getProjectVersion(cwd) {
|
|
17166
|
+
const projectPath = join2(cwd, PROJECT_FILE);
|
|
17167
|
+
if (!existsSync2(projectPath)) {
|
|
17168
|
+
return null;
|
|
17169
|
+
}
|
|
17170
|
+
try {
|
|
17171
|
+
const data = readFileSync2(projectPath, "utf-8");
|
|
17172
|
+
const parsed = JSON.parse(data);
|
|
17173
|
+
if (parsed.version === "2.0") {
|
|
17174
|
+
return "2.0";
|
|
17175
|
+
}
|
|
17176
|
+
if (parsed.agentId) {
|
|
17177
|
+
return "1.0";
|
|
17178
|
+
}
|
|
17179
|
+
return null;
|
|
17180
|
+
} catch {
|
|
17181
|
+
return null;
|
|
17182
|
+
}
|
|
17183
|
+
}
|
|
17251
17184
|
|
|
17252
17185
|
// src/cli/utils/scaffold.ts
|
|
17253
17186
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync3, readFileSync as readFileSync3, appendFileSync } from "fs";
|
|
17254
17187
|
import { join as join3, dirname } from "path";
|
|
17255
17188
|
|
|
17256
17189
|
// src/cli/templates/index.ts
|
|
17257
|
-
function getPackageJson(name) {
|
|
17258
|
-
return JSON.stringify({
|
|
17259
|
-
name,
|
|
17260
|
-
version: "0.1.0",
|
|
17261
|
-
type: "module",
|
|
17262
|
-
scripts: {
|
|
17263
|
-
dev: "struere dev",
|
|
17264
|
-
build: "struere build",
|
|
17265
|
-
test: "struere test",
|
|
17266
|
-
deploy: "struere deploy"
|
|
17267
|
-
},
|
|
17268
|
-
dependencies: {
|
|
17269
|
-
struere: "^0.3.0"
|
|
17270
|
-
},
|
|
17271
|
-
devDependencies: {
|
|
17272
|
-
"bun-types": "^1.0.0",
|
|
17273
|
-
typescript: "^5.3.0"
|
|
17274
|
-
}
|
|
17275
|
-
}, null, 2);
|
|
17276
|
-
}
|
|
17277
17190
|
function getTsConfig() {
|
|
17278
17191
|
return JSON.stringify({
|
|
17279
17192
|
compilerOptions: {
|
|
@@ -17310,36 +17223,106 @@ export default defineConfig({
|
|
|
17310
17223
|
})
|
|
17311
17224
|
`;
|
|
17312
17225
|
}
|
|
17313
|
-
function
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
import { tools } from './tools'
|
|
17317
|
-
|
|
17318
|
-
export default defineAgent({
|
|
17319
|
-
name: '${name}',
|
|
17320
|
-
version: '0.1.0',
|
|
17321
|
-
description: '${displayName} Agent',
|
|
17322
|
-
model: {
|
|
17323
|
-
provider: 'anthropic',
|
|
17324
|
-
name: 'claude-sonnet-4-20250514',
|
|
17325
|
-
temperature: 0.7,
|
|
17326
|
-
maxTokens: 4096,
|
|
17327
|
-
},
|
|
17328
|
-
systemPrompt: \`You are ${displayName}, a helpful AI assistant.
|
|
17226
|
+
function getEnvExample() {
|
|
17227
|
+
return `# Anthropic API Key (default provider)
|
|
17228
|
+
ANTHROPIC_API_KEY=your_api_key_here
|
|
17329
17229
|
|
|
17330
|
-
|
|
17230
|
+
# Optional: OpenAI API Key (if using OpenAI models)
|
|
17231
|
+
# OPENAI_API_KEY=your_openai_api_key
|
|
17331
17232
|
|
|
17332
|
-
|
|
17333
|
-
|
|
17334
|
-
|
|
17335
|
-
|
|
17233
|
+
# Optional: Google AI API Key (if using Gemini models)
|
|
17234
|
+
# GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_key
|
|
17235
|
+
|
|
17236
|
+
# Optional: Custom Convex URL
|
|
17237
|
+
# STRUERE_CONVEX_URL=https://struere.convex.cloud
|
|
17238
|
+
`;
|
|
17239
|
+
}
|
|
17240
|
+
function getGitignore() {
|
|
17241
|
+
return `node_modules/
|
|
17242
|
+
dist/
|
|
17243
|
+
.env
|
|
17244
|
+
.env.local
|
|
17245
|
+
.env.*.local
|
|
17246
|
+
.idea/
|
|
17247
|
+
.vscode/
|
|
17248
|
+
*.swp
|
|
17249
|
+
*.swo
|
|
17250
|
+
.DS_Store
|
|
17251
|
+
Thumbs.db
|
|
17252
|
+
*.log
|
|
17253
|
+
logs/
|
|
17254
|
+
.vercel/
|
|
17255
|
+
`;
|
|
17256
|
+
}
|
|
17257
|
+
function getEntityTypeTs(name, slug) {
|
|
17258
|
+
const displayName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
17259
|
+
return `import { defineEntityType } from 'struere'
|
|
17260
|
+
|
|
17261
|
+
export default defineEntityType({
|
|
17262
|
+
name: "${displayName}",
|
|
17263
|
+
slug: "${slug}",
|
|
17264
|
+
schema: {
|
|
17265
|
+
type: "object",
|
|
17266
|
+
properties: {
|
|
17267
|
+
name: { type: "string", description: "Name" },
|
|
17268
|
+
email: { type: "string", format: "email", description: "Email address" },
|
|
17269
|
+
status: { type: "string", enum: ["active", "inactive"], description: "Status" },
|
|
17270
|
+
},
|
|
17271
|
+
required: ["name"],
|
|
17272
|
+
},
|
|
17273
|
+
searchFields: ["name", "email"],
|
|
17274
|
+
})
|
|
17275
|
+
`;
|
|
17276
|
+
}
|
|
17277
|
+
function getRoleTs(name) {
|
|
17278
|
+
return `import { defineRole } from 'struere'
|
|
17279
|
+
|
|
17280
|
+
export default defineRole({
|
|
17281
|
+
name: "${name}",
|
|
17282
|
+
description: "${name.charAt(0).toUpperCase() + name.slice(1)} role",
|
|
17283
|
+
policies: [
|
|
17284
|
+
{ resource: "*", actions: ["list", "read"], effect: "allow", priority: 50 },
|
|
17285
|
+
],
|
|
17286
|
+
scopeRules: [],
|
|
17287
|
+
fieldMasks: [],
|
|
17288
|
+
})
|
|
17289
|
+
`;
|
|
17290
|
+
}
|
|
17291
|
+
function getAgentTsV2(name, slug) {
|
|
17292
|
+
const displayName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
17293
|
+
return `import { defineAgent } from 'struere'
|
|
17294
|
+
|
|
17295
|
+
export default defineAgent({
|
|
17296
|
+
name: "${displayName}",
|
|
17297
|
+
slug: "${slug}",
|
|
17298
|
+
version: "0.1.0",
|
|
17299
|
+
description: "${displayName} Agent",
|
|
17300
|
+
model: {
|
|
17301
|
+
provider: "anthropic",
|
|
17302
|
+
name: "claude-sonnet-4-20250514",
|
|
17303
|
+
temperature: 0.7,
|
|
17304
|
+
maxTokens: 4096,
|
|
17305
|
+
},
|
|
17306
|
+
systemPrompt: \`You are ${displayName}, a helpful AI assistant.
|
|
17307
|
+
|
|
17308
|
+
Current time: {{datetime}}
|
|
17309
|
+
|
|
17310
|
+
Your capabilities:
|
|
17311
|
+
- Answer questions accurately and helpfully
|
|
17312
|
+
- Use available tools when appropriate
|
|
17313
|
+
- Maintain conversation context
|
|
17336
17314
|
|
|
17337
17315
|
Always be concise, accurate, and helpful.\`,
|
|
17338
|
-
tools,
|
|
17316
|
+
tools: ["entity.query", "entity.get", "event.emit"],
|
|
17339
17317
|
})
|
|
17340
17318
|
`;
|
|
17341
17319
|
}
|
|
17342
|
-
function
|
|
17320
|
+
function getIndexTs(type) {
|
|
17321
|
+
return `// Export all ${type} from this directory
|
|
17322
|
+
// Example: export { default as myAgent } from './my-agent'
|
|
17323
|
+
`;
|
|
17324
|
+
}
|
|
17325
|
+
function getToolsIndexTs() {
|
|
17343
17326
|
return `import { defineTools } from 'struere'
|
|
17344
17327
|
|
|
17345
17328
|
export const tools = defineTools([
|
|
@@ -17365,225 +17348,137 @@ export const tools = defineTools([
|
|
|
17365
17348
|
}
|
|
17366
17349
|
},
|
|
17367
17350
|
},
|
|
17368
|
-
{
|
|
17369
|
-
name: 'calculate',
|
|
17370
|
-
description: 'Perform a mathematical calculation',
|
|
17371
|
-
parameters: {
|
|
17372
|
-
type: 'object',
|
|
17373
|
-
properties: {
|
|
17374
|
-
expression: {
|
|
17375
|
-
type: 'string',
|
|
17376
|
-
description: 'Mathematical expression to evaluate (e.g., "2 + 2")',
|
|
17377
|
-
},
|
|
17378
|
-
},
|
|
17379
|
-
required: ['expression'],
|
|
17380
|
-
},
|
|
17381
|
-
handler: async (params) => {
|
|
17382
|
-
const expression = params.expression as string
|
|
17383
|
-
const sanitized = expression.replace(/[^0-9+*/().\\s-]/g, '')
|
|
17384
|
-
try {
|
|
17385
|
-
const result = new Function(\`return \${sanitized}\`)()
|
|
17386
|
-
return { expression, result }
|
|
17387
|
-
} catch {
|
|
17388
|
-
return { expression, error: 'Invalid expression' }
|
|
17389
|
-
}
|
|
17390
|
-
},
|
|
17391
|
-
},
|
|
17392
17351
|
])
|
|
17393
|
-
`;
|
|
17394
|
-
}
|
|
17395
|
-
function getBasicTestYaml() {
|
|
17396
|
-
return `name: Basic conversation test
|
|
17397
|
-
description: Verify the agent responds correctly to basic queries
|
|
17398
|
-
|
|
17399
|
-
conversation:
|
|
17400
|
-
- role: user
|
|
17401
|
-
content: Hello, what can you do?
|
|
17402
|
-
- role: assistant
|
|
17403
|
-
assertions:
|
|
17404
|
-
- type: contains
|
|
17405
|
-
value: help
|
|
17406
|
-
|
|
17407
|
-
- role: user
|
|
17408
|
-
content: What time is it?
|
|
17409
|
-
- role: assistant
|
|
17410
|
-
assertions:
|
|
17411
|
-
- type: toolCalled
|
|
17412
|
-
value: get_current_time
|
|
17413
|
-
`;
|
|
17414
|
-
}
|
|
17415
|
-
function getEnvExample() {
|
|
17416
|
-
return `# Anthropic API Key (default provider)
|
|
17417
|
-
ANTHROPIC_API_KEY=your_api_key_here
|
|
17418
|
-
|
|
17419
|
-
# Optional: OpenAI API Key (if using OpenAI models)
|
|
17420
|
-
# OPENAI_API_KEY=your_openai_api_key
|
|
17421
17352
|
|
|
17422
|
-
|
|
17423
|
-
# GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_key
|
|
17424
|
-
|
|
17425
|
-
# Optional: Custom Convex URL
|
|
17426
|
-
# STRUERE_CONVEX_URL=https://struere.convex.cloud
|
|
17353
|
+
export default tools
|
|
17427
17354
|
`;
|
|
17428
17355
|
}
|
|
17429
|
-
function
|
|
17430
|
-
return `node_modules/
|
|
17431
|
-
dist/
|
|
17432
|
-
.env
|
|
17433
|
-
.env.local
|
|
17434
|
-
.env.*.local
|
|
17435
|
-
.idea/
|
|
17436
|
-
.vscode/
|
|
17437
|
-
*.swp
|
|
17438
|
-
*.swo
|
|
17439
|
-
.DS_Store
|
|
17440
|
-
Thumbs.db
|
|
17441
|
-
*.log
|
|
17442
|
-
logs/
|
|
17443
|
-
.vercel/
|
|
17444
|
-
`;
|
|
17445
|
-
}
|
|
17446
|
-
function getStruereJson(agentId, team, slug, name) {
|
|
17356
|
+
function getStruereJsonV2(orgId, orgSlug, orgName) {
|
|
17447
17357
|
return JSON.stringify({
|
|
17448
|
-
|
|
17449
|
-
|
|
17450
|
-
|
|
17451
|
-
slug,
|
|
17452
|
-
name
|
|
17358
|
+
version: "2.0",
|
|
17359
|
+
organization: {
|
|
17360
|
+
id: orgId,
|
|
17361
|
+
slug: orgSlug,
|
|
17362
|
+
name: orgName
|
|
17453
17363
|
}
|
|
17454
17364
|
}, null, 2);
|
|
17455
17365
|
}
|
|
17456
|
-
function
|
|
17457
|
-
return
|
|
17458
|
-
|
|
17366
|
+
function getPackageJsonV2(name) {
|
|
17367
|
+
return JSON.stringify({
|
|
17368
|
+
name,
|
|
17369
|
+
version: "0.1.0",
|
|
17370
|
+
type: "module",
|
|
17371
|
+
scripts: {
|
|
17372
|
+
dev: "struere dev",
|
|
17373
|
+
build: "struere build",
|
|
17374
|
+
deploy: "struere deploy",
|
|
17375
|
+
status: "struere status"
|
|
17376
|
+
},
|
|
17377
|
+
dependencies: {
|
|
17378
|
+
struere: "^0.4.0"
|
|
17379
|
+
},
|
|
17380
|
+
devDependencies: {
|
|
17381
|
+
"bun-types": "^1.0.0",
|
|
17382
|
+
typescript: "^5.3.0"
|
|
17383
|
+
}
|
|
17384
|
+
}, null, 2);
|
|
17459
17385
|
}
|
|
17460
|
-
function
|
|
17461
|
-
|
|
17462
|
-
return `# ${displayName} Agent
|
|
17386
|
+
function getClaudeMDV2(orgName) {
|
|
17387
|
+
return `# ${orgName} - Struere Project
|
|
17463
17388
|
|
|
17464
|
-
This is a Struere
|
|
17389
|
+
This is a Struere organization project. Struere is a framework for building production AI agents with built-in data management, RBAC permissions, and job scheduling.
|
|
17465
17390
|
|
|
17466
17391
|
## Project Structure
|
|
17467
17392
|
|
|
17468
17393
|
\`\`\`
|
|
17469
|
-
|
|
17470
|
-
\u251C\u2500\u2500
|
|
17471
|
-
\
|
|
17472
|
-
\u2514\u2500\u2500 workflows/ # Multi-step workflow definitions
|
|
17473
|
-
tests/
|
|
17474
|
-
\u2514\u2500\u2500 *.test.yaml # YAML-based conversation tests
|
|
17475
|
-
struere.json # Project configuration (agentId, team, slug)
|
|
17476
|
-
struere.config.ts # Framework settings (port, CORS, logging)
|
|
17477
|
-
\`\`\`
|
|
17394
|
+
agents/ # Agent definitions
|
|
17395
|
+
\u251C\u2500\u2500 scheduler.ts # Example agent
|
|
17396
|
+
\u2514\u2500\u2500 index.ts # Re-exports all agents
|
|
17478
17397
|
|
|
17479
|
-
|
|
17398
|
+
entity-types/ # Entity type schemas
|
|
17399
|
+
\u251C\u2500\u2500 teacher.ts # Example entity type
|
|
17400
|
+
\u2514\u2500\u2500 index.ts # Re-exports all entity types
|
|
17480
17401
|
|
|
17481
|
-
|
|
17402
|
+
roles/ # Role + permission definitions
|
|
17403
|
+
\u251C\u2500\u2500 admin.ts # Example role with policies
|
|
17404
|
+
\u2514\u2500\u2500 index.ts # Re-exports all roles
|
|
17482
17405
|
|
|
17483
|
-
|
|
17484
|
-
|
|
17485
|
-
import { tools } from './tools'
|
|
17486
|
-
|
|
17487
|
-
export default defineAgent({
|
|
17488
|
-
name: 'my-agent',
|
|
17489
|
-
version: '0.1.0',
|
|
17490
|
-
description: 'My AI Agent',
|
|
17491
|
-
model: {
|
|
17492
|
-
provider: 'anthropic',
|
|
17493
|
-
name: 'claude-sonnet-4-20250514',
|
|
17494
|
-
temperature: 0.7,
|
|
17495
|
-
maxTokens: 4096,
|
|
17496
|
-
},
|
|
17497
|
-
systemPrompt: \\\`You are a helpful assistant.
|
|
17406
|
+
tools/ # Shared custom tools
|
|
17407
|
+
\u2514\u2500\u2500 index.ts # Custom tool definitions
|
|
17498
17408
|
|
|
17499
|
-
|
|
17500
|
-
|
|
17501
|
-
tools,
|
|
17502
|
-
})
|
|
17409
|
+
struere.json # Organization configuration
|
|
17410
|
+
struere.config.ts # Framework settings
|
|
17503
17411
|
\`\`\`
|
|
17504
17412
|
|
|
17505
|
-
##
|
|
17506
|
-
|
|
17507
|
-
System prompts support dynamic \`{{...}}\` templates that are resolved at runtime before the LLM call.
|
|
17508
|
-
|
|
17509
|
-
### Available Variables
|
|
17510
|
-
|
|
17511
|
-
| Variable | Description |
|
|
17512
|
-
|----------|-------------|
|
|
17513
|
-
| \`{{organizationId}}\` | Current organization ID |
|
|
17514
|
-
| \`{{userId}}\` | Current user ID |
|
|
17515
|
-
| \`{{threadId}}\` | Conversation thread ID |
|
|
17516
|
-
| \`{{agentId}}\` | Agent ID |
|
|
17517
|
-
| \`{{agent.name}}\` | Agent name |
|
|
17518
|
-
| \`{{agent.slug}}\` | Agent slug |
|
|
17519
|
-
| \`{{thread.metadata.X}}\` | Thread metadata field X |
|
|
17520
|
-
| \`{{message}}\` | Current user message |
|
|
17521
|
-
| \`{{timestamp}}\` | Unix timestamp (ms) |
|
|
17522
|
-
| \`{{datetime}}\` | ISO 8601 datetime |
|
|
17523
|
-
|
|
17524
|
-
### Function Calls
|
|
17525
|
-
|
|
17526
|
-
Call any agent tool directly in the system prompt:
|
|
17527
|
-
|
|
17528
|
-
\`\`\`
|
|
17529
|
-
{{entity.get({"id": "ent_123"})}}
|
|
17530
|
-
{{entity.query({"type": "customer", "limit": 5})}}
|
|
17531
|
-
{{event.query({"entityId": "ent_123", "limit": 10})}}
|
|
17532
|
-
\`\`\`
|
|
17413
|
+
## CLI Commands
|
|
17533
17414
|
|
|
17534
|
-
|
|
17415
|
+
| Command | Description |
|
|
17416
|
+
|---------|-------------|
|
|
17417
|
+
| \`struere dev\` | Watch and sync all resources to Convex |
|
|
17418
|
+
| \`struere deploy\` | Deploy all agents to production |
|
|
17419
|
+
| \`struere add <type> <name>\` | Scaffold new agent/entity-type/role |
|
|
17420
|
+
| \`struere status\` | Compare local vs remote state |
|
|
17535
17421
|
|
|
17536
|
-
|
|
17422
|
+
## Defining Resources
|
|
17537
17423
|
|
|
17538
|
-
|
|
17539
|
-
{{entity.get({"id": "{{thread.metadata.customerId}}"})}}
|
|
17540
|
-
\`\`\`
|
|
17424
|
+
### Agents (\`agents/*.ts\`)
|
|
17541
17425
|
|
|
17542
|
-
|
|
17426
|
+
\`\`\`typescript
|
|
17427
|
+
import { defineAgent } from 'struere'
|
|
17543
17428
|
|
|
17544
|
-
|
|
17545
|
-
|
|
17546
|
-
|
|
17547
|
-
|
|
17429
|
+
export default defineAgent({
|
|
17430
|
+
name: "Scheduler",
|
|
17431
|
+
slug: "scheduler",
|
|
17432
|
+
version: "0.1.0",
|
|
17433
|
+
systemPrompt: "You are a scheduling assistant...",
|
|
17434
|
+
model: { provider: "anthropic", name: "claude-sonnet-4-20250514" },
|
|
17435
|
+
tools: ["entity.create", "entity.query", "event.emit"],
|
|
17436
|
+
})
|
|
17548
17437
|
\`\`\`
|
|
17549
17438
|
|
|
17550
|
-
|
|
17551
|
-
|
|
17552
|
-
Define tools in \`src/tools.ts\`:
|
|
17439
|
+
### Entity Types (\`entity-types/*.ts\`)
|
|
17553
17440
|
|
|
17554
17441
|
\`\`\`typescript
|
|
17555
|
-
import {
|
|
17556
|
-
|
|
17557
|
-
export
|
|
17558
|
-
|
|
17559
|
-
|
|
17560
|
-
|
|
17561
|
-
|
|
17562
|
-
|
|
17563
|
-
|
|
17564
|
-
|
|
17565
|
-
|
|
17566
|
-
},
|
|
17567
|
-
required: ['query'],
|
|
17568
|
-
},
|
|
17569
|
-
handler: async (params) => {
|
|
17570
|
-
const results = await searchProducts(params.query, params.limit ?? 10)
|
|
17571
|
-
return { products: results }
|
|
17442
|
+
import { defineEntityType } from 'struere'
|
|
17443
|
+
|
|
17444
|
+
export default defineEntityType({
|
|
17445
|
+
name: "Teacher",
|
|
17446
|
+
slug: "teacher",
|
|
17447
|
+
schema: {
|
|
17448
|
+
type: "object",
|
|
17449
|
+
properties: {
|
|
17450
|
+
name: { type: "string" },
|
|
17451
|
+
email: { type: "string", format: "email" },
|
|
17452
|
+
hourlyRate: { type: "number" },
|
|
17572
17453
|
},
|
|
17454
|
+
required: ["name", "email"],
|
|
17573
17455
|
},
|
|
17574
|
-
]
|
|
17456
|
+
searchFields: ["name", "email"],
|
|
17457
|
+
})
|
|
17575
17458
|
\`\`\`
|
|
17576
17459
|
|
|
17577
|
-
|
|
17578
|
-
- api.openai.com, api.anthropic.com, api.stripe.com
|
|
17579
|
-
- api.sendgrid.com, api.twilio.com, hooks.slack.com
|
|
17580
|
-
- discord.com, api.github.com
|
|
17460
|
+
### Roles (\`roles/*.ts\`)
|
|
17581
17461
|
|
|
17582
|
-
|
|
17583
|
-
|
|
17584
|
-
|
|
17462
|
+
\`\`\`typescript
|
|
17463
|
+
import { defineRole } from 'struere'
|
|
17464
|
+
|
|
17465
|
+
export default defineRole({
|
|
17466
|
+
name: "teacher",
|
|
17467
|
+
description: "Tutors who conduct sessions",
|
|
17468
|
+
policies: [
|
|
17469
|
+
{ resource: "session", actions: ["list", "read", "update"], effect: "allow", priority: 50 },
|
|
17470
|
+
{ resource: "payment", actions: ["*"], effect: "deny", priority: 100 },
|
|
17471
|
+
],
|
|
17472
|
+
scopeRules: [
|
|
17473
|
+
{ entityType: "session", field: "data.teacherId", operator: "eq", value: "actor.userId" },
|
|
17474
|
+
],
|
|
17475
|
+
fieldMasks: [
|
|
17476
|
+
{ entityType: "session", fieldPath: "data.paymentId", maskType: "hide" },
|
|
17477
|
+
],
|
|
17478
|
+
})
|
|
17479
|
+
\`\`\`
|
|
17585
17480
|
|
|
17586
|
-
|
|
17481
|
+
## Built-in Tools
|
|
17587
17482
|
|
|
17588
17483
|
| Tool | Description |
|
|
17589
17484
|
|------|-------------|
|
|
@@ -17594,159 +17489,18 @@ Agents have access to these built-in tools for data management:
|
|
|
17594
17489
|
| \`entity.delete\` | Soft-delete entity |
|
|
17595
17490
|
| \`entity.link\` | Create entity relation |
|
|
17596
17491
|
| \`entity.unlink\` | Remove entity relation |
|
|
17597
|
-
|
|
17598
|
-
|
|
17599
|
-
|
|
17600
|
-
// entity.create
|
|
17601
|
-
{ "type": "customer", "data": { "name": "John", "email": "john@example.com" } }
|
|
17602
|
-
|
|
17603
|
-
// entity.query
|
|
17604
|
-
{ "type": "customer", "filters": { "status": "active" }, "limit": 10 }
|
|
17605
|
-
|
|
17606
|
-
// entity.update
|
|
17607
|
-
{ "id": "ent_123", "data": { "status": "vip" } }
|
|
17608
|
-
\`\`\`
|
|
17609
|
-
|
|
17610
|
-
### Event Tools
|
|
17611
|
-
|
|
17612
|
-
| Tool | Description |
|
|
17613
|
-
|------|-------------|
|
|
17614
|
-
| \`event.emit\` | Emit a custom event |
|
|
17615
|
-
| \`event.query\` | Query event history |
|
|
17616
|
-
|
|
17617
|
-
Example event operations:
|
|
17618
|
-
\`\`\`json
|
|
17619
|
-
// event.emit
|
|
17620
|
-
{ "entityId": "ent_123", "eventType": "order.placed", "payload": { "amount": 99.99 } }
|
|
17621
|
-
|
|
17622
|
-
// event.query
|
|
17623
|
-
{ "entityId": "ent_123", "eventType": "order.*", "limit": 20 }
|
|
17624
|
-
\`\`\`
|
|
17625
|
-
|
|
17626
|
-
### Job Tools
|
|
17627
|
-
|
|
17628
|
-
| Tool | Description |
|
|
17629
|
-
|------|-------------|
|
|
17630
|
-
| \`job.enqueue\` | Schedule a background job |
|
|
17492
|
+
| \`event.emit\` | Emit custom event |
|
|
17493
|
+
| \`event.query\` | Query events |
|
|
17494
|
+
| \`job.enqueue\` | Schedule background job |
|
|
17631
17495
|
| \`job.status\` | Get job status |
|
|
17632
17496
|
|
|
17633
|
-
Example job operations:
|
|
17634
|
-
\`\`\`json
|
|
17635
|
-
// job.enqueue
|
|
17636
|
-
{ "jobType": "send_email", "payload": { "to": "user@example.com" }, "scheduledFor": 1706745600000 }
|
|
17637
|
-
|
|
17638
|
-
// job.status
|
|
17639
|
-
{ "id": "job_abc123" }
|
|
17640
|
-
\`\`\`
|
|
17641
|
-
|
|
17642
|
-
## Testing
|
|
17643
|
-
|
|
17644
|
-
Write YAML-based conversation tests in \`tests/\`:
|
|
17645
|
-
|
|
17646
|
-
\`\`\`yaml
|
|
17647
|
-
name: Order flow test
|
|
17648
|
-
description: Test the complete order flow
|
|
17649
|
-
|
|
17650
|
-
conversation:
|
|
17651
|
-
- role: user
|
|
17652
|
-
content: I want to order a pizza
|
|
17653
|
-
- role: assistant
|
|
17654
|
-
assertions:
|
|
17655
|
-
- type: contains
|
|
17656
|
-
value: size
|
|
17657
|
-
- type: toolCalled
|
|
17658
|
-
value: get_menu
|
|
17659
|
-
|
|
17660
|
-
- role: user
|
|
17661
|
-
content: Large pepperoni please
|
|
17662
|
-
- role: assistant
|
|
17663
|
-
assertions:
|
|
17664
|
-
- type: toolCalled
|
|
17665
|
-
value: entity.create
|
|
17666
|
-
\`\`\`
|
|
17667
|
-
|
|
17668
|
-
### Assertion Types
|
|
17669
|
-
|
|
17670
|
-
| Type | Description |
|
|
17671
|
-
|------|-------------|
|
|
17672
|
-
| \`contains\` | Response contains substring |
|
|
17673
|
-
| \`matches\` | Response matches regex |
|
|
17674
|
-
| \`toolCalled\` | Specific tool was called |
|
|
17675
|
-
| \`noToolCalled\` | No tools were called |
|
|
17676
|
-
|
|
17677
|
-
Run tests with:
|
|
17678
|
-
\`\`\`bash
|
|
17679
|
-
bun run test
|
|
17680
|
-
\`\`\`
|
|
17681
|
-
|
|
17682
|
-
## CLI Commands
|
|
17683
|
-
|
|
17684
|
-
| Command | Description |
|
|
17685
|
-
|---------|-------------|
|
|
17686
|
-
| \`struere dev\` | Start development mode (live sync to Convex) |
|
|
17687
|
-
| \`struere build\` | Validate agent configuration |
|
|
17688
|
-
| \`struere deploy\` | Deploy agent to production |
|
|
17689
|
-
| \`struere test\` | Run YAML conversation tests |
|
|
17690
|
-
| \`struere logs\` | View recent execution logs |
|
|
17691
|
-
| \`struere state\` | Inspect conversation thread state |
|
|
17692
|
-
|
|
17693
|
-
## Thread Metadata
|
|
17694
|
-
|
|
17695
|
-
Set thread metadata when creating conversations to provide context:
|
|
17696
|
-
|
|
17697
|
-
\`\`\`typescript
|
|
17698
|
-
// Via API
|
|
17699
|
-
POST /v1/chat
|
|
17700
|
-
{
|
|
17701
|
-
"agentId": "agent_123",
|
|
17702
|
-
"message": "Hello",
|
|
17703
|
-
"metadata": {
|
|
17704
|
-
"customerId": "ent_customer_456",
|
|
17705
|
-
"channel": "web",
|
|
17706
|
-
"language": "en"
|
|
17707
|
-
}
|
|
17708
|
-
}
|
|
17709
|
-
\`\`\`
|
|
17710
|
-
|
|
17711
|
-
Access in system prompt:
|
|
17712
|
-
\`\`\`
|
|
17713
|
-
Customer: {{entity.get({"id": "{{thread.metadata.customerId}}"})}}
|
|
17714
|
-
Channel: {{thread.metadata.channel}}
|
|
17715
|
-
\`\`\`
|
|
17716
|
-
|
|
17717
17497
|
## Development Workflow
|
|
17718
17498
|
|
|
17719
|
-
1.
|
|
17720
|
-
2.
|
|
17721
|
-
3.
|
|
17722
|
-
4.
|
|
17723
|
-
5.
|
|
17724
|
-
|
|
17725
|
-
## API Endpoints
|
|
17726
|
-
|
|
17727
|
-
| Endpoint | Method | Description |
|
|
17728
|
-
|----------|--------|-------------|
|
|
17729
|
-
| \`/v1/chat\` | POST | Chat by agent ID |
|
|
17730
|
-
| \`/v1/agents/:slug/chat\` | POST | Chat by agent slug |
|
|
17731
|
-
|
|
17732
|
-
Authentication: Bearer token (API key from dashboard)
|
|
17733
|
-
|
|
17734
|
-
\`\`\`bash
|
|
17735
|
-
curl -X POST https://your-deployment.convex.cloud/v1/chat \\
|
|
17736
|
-
-H "Authorization: Bearer sk_live_..." \\
|
|
17737
|
-
-H "Content-Type: application/json" \\
|
|
17738
|
-
-d '{"agentId": "...", "message": "Hello"}'
|
|
17739
|
-
\`\`\`
|
|
17740
|
-
|
|
17741
|
-
## Best Practices
|
|
17742
|
-
|
|
17743
|
-
1. **System Prompts**: Use templates for dynamic data instead of hardcoding
|
|
17744
|
-
2. **Tools**: Keep tool handlers focused and stateless
|
|
17745
|
-
3. **Entities**: Model your domain data as entity types
|
|
17746
|
-
4. **Events**: Emit events for audit trails and analytics
|
|
17747
|
-
5. **Jobs**: Use jobs for async operations (emails, notifications)
|
|
17748
|
-
6. **Testing**: Write tests for critical conversation flows
|
|
17749
|
-
7. **Thread Metadata**: Use metadata for user-specific personalization
|
|
17499
|
+
1. Run \`struere dev\` to start watching for changes
|
|
17500
|
+
2. Edit agents, entity types, or roles
|
|
17501
|
+
3. Changes are automatically synced to Convex
|
|
17502
|
+
4. Test via API or dashboard
|
|
17503
|
+
5. Run \`struere deploy\` when ready for production
|
|
17750
17504
|
`;
|
|
17751
17505
|
}
|
|
17752
17506
|
|
|
@@ -17762,33 +17516,35 @@ function writeFile(cwd, relativePath, content) {
|
|
|
17762
17516
|
ensureDir(fullPath);
|
|
17763
17517
|
writeFileSync3(fullPath, content);
|
|
17764
17518
|
}
|
|
17765
|
-
function
|
|
17766
|
-
const result = {
|
|
17767
|
-
createdFiles: [],
|
|
17768
|
-
updatedFiles: []
|
|
17769
|
-
};
|
|
17770
|
-
writeFile(cwd, "struere.json", getStruereJson(options.agentId, options.team, options.agentSlug, options.agentName));
|
|
17771
|
-
result.createdFiles.push("struere.json");
|
|
17772
|
-
writeFile(cwd, ".env.local", getEnvLocal(options.deploymentUrl));
|
|
17773
|
-
result.createdFiles.push(".env.local");
|
|
17774
|
-
updateGitignore(cwd, result);
|
|
17775
|
-
return result;
|
|
17776
|
-
}
|
|
17777
|
-
function scaffoldAgentFiles(cwd, projectName) {
|
|
17519
|
+
function scaffoldProjectV2(cwd, options) {
|
|
17778
17520
|
const result = {
|
|
17779
17521
|
createdFiles: [],
|
|
17780
17522
|
updatedFiles: []
|
|
17781
17523
|
};
|
|
17524
|
+
const directories = [
|
|
17525
|
+
"agents",
|
|
17526
|
+
"entity-types",
|
|
17527
|
+
"roles",
|
|
17528
|
+
"tools"
|
|
17529
|
+
];
|
|
17530
|
+
for (const dir of directories) {
|
|
17531
|
+
const dirPath = join3(cwd, dir);
|
|
17532
|
+
if (!existsSync3(dirPath)) {
|
|
17533
|
+
mkdirSync2(dirPath, { recursive: true });
|
|
17534
|
+
}
|
|
17535
|
+
}
|
|
17782
17536
|
const files = {
|
|
17783
|
-
"
|
|
17537
|
+
"struere.json": getStruereJsonV2(options.orgId, options.orgSlug, options.orgName),
|
|
17538
|
+
"package.json": getPackageJsonV2(options.projectName),
|
|
17784
17539
|
"tsconfig.json": getTsConfig(),
|
|
17785
17540
|
"struere.config.ts": getStruereConfig(),
|
|
17786
|
-
"src/agent.ts": getAgentTs(projectName),
|
|
17787
|
-
"src/tools.ts": getToolsTs(),
|
|
17788
|
-
"src/workflows/.gitkeep": "",
|
|
17789
|
-
"tests/basic.test.yaml": getBasicTestYaml(),
|
|
17790
17541
|
".env.example": getEnvExample(),
|
|
17791
|
-
"
|
|
17542
|
+
".gitignore": getGitignore(),
|
|
17543
|
+
"CLAUDE.md": getClaudeMDV2(options.orgName),
|
|
17544
|
+
"agents/index.ts": getIndexTs("agents"),
|
|
17545
|
+
"entity-types/index.ts": getIndexTs("entity-types"),
|
|
17546
|
+
"roles/index.ts": getIndexTs("roles"),
|
|
17547
|
+
"tools/index.ts": getToolsIndexTs()
|
|
17792
17548
|
};
|
|
17793
17549
|
for (const [relativePath, content] of Object.entries(files)) {
|
|
17794
17550
|
const fullPath = join3(cwd, relativePath);
|
|
@@ -17798,166 +17554,161 @@ function scaffoldAgentFiles(cwd, projectName) {
|
|
|
17798
17554
|
writeFile(cwd, relativePath, content);
|
|
17799
17555
|
result.createdFiles.push(relativePath);
|
|
17800
17556
|
}
|
|
17801
|
-
updateGitignore(cwd, result);
|
|
17802
17557
|
return result;
|
|
17803
17558
|
}
|
|
17804
|
-
function
|
|
17805
|
-
const
|
|
17806
|
-
|
|
17807
|
-
|
|
17808
|
-
|
|
17809
|
-
|
|
17810
|
-
|
|
17811
|
-
|
|
17812
|
-
|
|
17813
|
-
|
|
17814
|
-
|
|
17815
|
-
|
|
17816
|
-
|
|
17817
|
-
|
|
17818
|
-
|
|
17819
|
-
|
|
17820
|
-
|
|
17821
|
-
|
|
17822
|
-
|
|
17559
|
+
function scaffoldAgent(cwd, name, slug) {
|
|
17560
|
+
const result = {
|
|
17561
|
+
createdFiles: [],
|
|
17562
|
+
updatedFiles: []
|
|
17563
|
+
};
|
|
17564
|
+
const agentsDir = join3(cwd, "agents");
|
|
17565
|
+
if (!existsSync3(agentsDir)) {
|
|
17566
|
+
mkdirSync2(agentsDir, { recursive: true });
|
|
17567
|
+
}
|
|
17568
|
+
const fileName = `${slug}.ts`;
|
|
17569
|
+
const filePath = join3(agentsDir, fileName);
|
|
17570
|
+
if (existsSync3(filePath)) {
|
|
17571
|
+
return result;
|
|
17572
|
+
}
|
|
17573
|
+
writeFileSync3(filePath, getAgentTsV2(name, slug));
|
|
17574
|
+
result.createdFiles.push(`agents/${fileName}`);
|
|
17575
|
+
return result;
|
|
17576
|
+
}
|
|
17577
|
+
function scaffoldEntityType(cwd, name, slug) {
|
|
17578
|
+
const result = {
|
|
17579
|
+
createdFiles: [],
|
|
17580
|
+
updatedFiles: []
|
|
17581
|
+
};
|
|
17582
|
+
const entityTypesDir = join3(cwd, "entity-types");
|
|
17583
|
+
if (!existsSync3(entityTypesDir)) {
|
|
17584
|
+
mkdirSync2(entityTypesDir, { recursive: true });
|
|
17585
|
+
}
|
|
17586
|
+
const fileName = `${slug}.ts`;
|
|
17587
|
+
const filePath = join3(entityTypesDir, fileName);
|
|
17588
|
+
if (existsSync3(filePath)) {
|
|
17589
|
+
return result;
|
|
17823
17590
|
}
|
|
17591
|
+
writeFileSync3(filePath, getEntityTypeTs(name, slug));
|
|
17592
|
+
result.createdFiles.push(`entity-types/${fileName}`);
|
|
17593
|
+
return result;
|
|
17824
17594
|
}
|
|
17825
|
-
function
|
|
17826
|
-
|
|
17595
|
+
function scaffoldRole(cwd, name) {
|
|
17596
|
+
const result = {
|
|
17597
|
+
createdFiles: [],
|
|
17598
|
+
updatedFiles: []
|
|
17599
|
+
};
|
|
17600
|
+
const rolesDir = join3(cwd, "roles");
|
|
17601
|
+
if (!existsSync3(rolesDir)) {
|
|
17602
|
+
mkdirSync2(rolesDir, { recursive: true });
|
|
17603
|
+
}
|
|
17604
|
+
const fileName = `${name}.ts`;
|
|
17605
|
+
const filePath = join3(rolesDir, fileName);
|
|
17606
|
+
if (existsSync3(filePath)) {
|
|
17607
|
+
return result;
|
|
17608
|
+
}
|
|
17609
|
+
writeFileSync3(filePath, getRoleTs(name));
|
|
17610
|
+
result.createdFiles.push(`roles/${fileName}`);
|
|
17611
|
+
return result;
|
|
17827
17612
|
}
|
|
17828
17613
|
|
|
17829
17614
|
// src/cli/commands/init.ts
|
|
17830
|
-
|
|
17615
|
+
async function runInit(cwd) {
|
|
17616
|
+
const spinner = ora();
|
|
17617
|
+
let credentials = loadCredentials();
|
|
17618
|
+
if (!credentials) {
|
|
17619
|
+
console.log(source_default.yellow("Not logged in - authenticating..."));
|
|
17620
|
+
console.log();
|
|
17621
|
+
credentials = await performLogin();
|
|
17622
|
+
if (!credentials) {
|
|
17623
|
+
console.log(source_default.red("Authentication failed"));
|
|
17624
|
+
return false;
|
|
17625
|
+
}
|
|
17626
|
+
console.log();
|
|
17627
|
+
}
|
|
17628
|
+
console.log(source_default.green("\u2713"), "Logged in as", source_default.cyan(credentials.user.name || credentials.user.email));
|
|
17629
|
+
console.log(source_default.gray(" Organization:"), source_default.cyan(credentials.organization.name));
|
|
17630
|
+
console.log();
|
|
17631
|
+
const projectName = slugify(basename(cwd));
|
|
17632
|
+
spinner.start("Creating project structure");
|
|
17633
|
+
const scaffoldResult = scaffoldProjectV2(cwd, {
|
|
17634
|
+
projectName,
|
|
17635
|
+
orgId: credentials.organization.id,
|
|
17636
|
+
orgSlug: credentials.organization.slug,
|
|
17637
|
+
orgName: credentials.organization.name
|
|
17638
|
+
});
|
|
17639
|
+
spinner.succeed("Project structure created");
|
|
17640
|
+
for (const file of scaffoldResult.createdFiles) {
|
|
17641
|
+
console.log(source_default.green("\u2713"), `Created ${file}`);
|
|
17642
|
+
}
|
|
17643
|
+
console.log();
|
|
17644
|
+
spinner.start("Installing dependencies");
|
|
17645
|
+
const installResult = Bun.spawnSync(["bun", "install"], {
|
|
17646
|
+
cwd,
|
|
17647
|
+
stdout: "pipe",
|
|
17648
|
+
stderr: "pipe"
|
|
17649
|
+
});
|
|
17650
|
+
if (installResult.exitCode === 0) {
|
|
17651
|
+
spinner.succeed("Dependencies installed");
|
|
17652
|
+
} else {
|
|
17653
|
+
spinner.warn("Could not install dependencies automatically");
|
|
17654
|
+
console.log(source_default.gray(" Run"), source_default.cyan("bun install"), source_default.gray("manually"));
|
|
17655
|
+
}
|
|
17656
|
+
console.log();
|
|
17657
|
+
console.log(source_default.green("\u2713"), "Project initialized");
|
|
17658
|
+
return true;
|
|
17659
|
+
}
|
|
17660
|
+
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").action(async (projectNameArg, options) => {
|
|
17831
17661
|
const cwd = process.cwd();
|
|
17832
17662
|
const spinner = ora();
|
|
17833
17663
|
console.log();
|
|
17834
17664
|
console.log(source_default.bold("Struere CLI"));
|
|
17835
17665
|
console.log();
|
|
17836
17666
|
if (hasProject(cwd)) {
|
|
17837
|
-
const
|
|
17838
|
-
if (
|
|
17839
|
-
console.log(source_default.yellow("This project is already initialized."));
|
|
17667
|
+
const version = getProjectVersion(cwd);
|
|
17668
|
+
if (version === "2.0") {
|
|
17669
|
+
console.log(source_default.yellow("This project is already initialized (v2.0)."));
|
|
17840
17670
|
console.log();
|
|
17841
|
-
console.log(source_default.gray("
|
|
17842
|
-
console.log(source_default.gray(" ID:"), source_default.gray(existingProject.agentId));
|
|
17843
|
-
console.log(source_default.gray(" Team:"), source_default.cyan(existingProject.team));
|
|
17671
|
+
console.log(source_default.gray("Run"), source_default.cyan("struere dev"), source_default.gray("to start development"));
|
|
17844
17672
|
console.log();
|
|
17845
|
-
|
|
17846
|
-
|
|
17847
|
-
|
|
17848
|
-
|
|
17849
|
-
|
|
17850
|
-
|
|
17851
|
-
|
|
17673
|
+
return;
|
|
17674
|
+
} else if (version === "1.0") {
|
|
17675
|
+
console.log(source_default.yellow("This is a v1 agent-centric project."));
|
|
17676
|
+
console.log(source_default.yellow("The new CLI uses an organization-centric structure."));
|
|
17677
|
+
console.log();
|
|
17678
|
+
console.log(source_default.gray("Please create a new project directory for the v2 structure."));
|
|
17679
|
+
console.log();
|
|
17680
|
+
return;
|
|
17852
17681
|
}
|
|
17853
17682
|
}
|
|
17854
17683
|
let credentials = loadCredentials();
|
|
17855
17684
|
if (!credentials) {
|
|
17856
|
-
console.log(source_default.
|
|
17685
|
+
console.log(source_default.yellow("Not logged in - authenticating..."));
|
|
17857
17686
|
console.log();
|
|
17858
17687
|
credentials = await performLogin();
|
|
17859
17688
|
if (!credentials) {
|
|
17860
17689
|
console.log(source_default.red("Authentication failed"));
|
|
17861
17690
|
process.exit(1);
|
|
17862
17691
|
}
|
|
17863
|
-
} else {
|
|
17864
|
-
console.log(source_default.green("\u2713"), "Logged in as", source_default.cyan(credentials.user.name || credentials.user.email));
|
|
17865
|
-
console.log();
|
|
17866
|
-
}
|
|
17867
|
-
let projectName = projectNameArg;
|
|
17868
|
-
if (!projectName) {
|
|
17869
|
-
projectName = await deriveProjectName(cwd);
|
|
17870
|
-
if (!options.yes) {
|
|
17871
|
-
const confirmed = await promptText("Agent name:", projectName);
|
|
17872
|
-
projectName = confirmed || projectName;
|
|
17873
|
-
}
|
|
17874
|
-
}
|
|
17875
|
-
projectName = slugify(projectName);
|
|
17876
|
-
spinner.start("Fetching agents");
|
|
17877
|
-
const { agents: existingAgents, error: listError } = await listAgents();
|
|
17878
|
-
if (listError) {
|
|
17879
|
-
spinner.fail("Failed to fetch agents");
|
|
17880
|
-
console.log();
|
|
17881
|
-
console.log(source_default.gray("Run"), source_default.cyan("struere login"), source_default.gray("to re-authenticate"));
|
|
17882
|
-
process.exit(1);
|
|
17883
|
-
}
|
|
17884
|
-
const agents = existingAgents.map((a) => ({ id: a._id, name: a.name, slug: a.slug }));
|
|
17885
|
-
spinner.succeed(`Found ${agents.length} existing agent(s)`);
|
|
17886
|
-
let selectedAgent = null;
|
|
17887
|
-
let deploymentUrl = "";
|
|
17888
|
-
if (agents.length > 0 && !options.yes) {
|
|
17889
|
-
console.log();
|
|
17890
|
-
const choice = await promptChoice("Create new agent or link existing?", [
|
|
17891
|
-
{ value: "new", label: "Create new agent" },
|
|
17892
|
-
...agents.map((a) => ({ value: a.id, label: `${a.name} (${a.slug})` }))
|
|
17893
|
-
]);
|
|
17894
|
-
if (choice !== "new") {
|
|
17895
|
-
selectedAgent = agents.find((a) => a.id === choice) || null;
|
|
17896
|
-
}
|
|
17897
|
-
}
|
|
17898
|
-
if (!selectedAgent) {
|
|
17899
|
-
const displayName2 = projectName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
17900
|
-
spinner.start("Creating agent");
|
|
17901
|
-
const { agentId, error: createError } = await createAgent({
|
|
17902
|
-
name: displayName2,
|
|
17903
|
-
slug: projectName,
|
|
17904
|
-
description: `${displayName2} Agent`
|
|
17905
|
-
});
|
|
17906
|
-
if (createError || !agentId) {
|
|
17907
|
-
spinner.fail("Failed to create agent");
|
|
17908
|
-
console.log();
|
|
17909
|
-
console.log(source_default.red("Error:"), createError || "Unknown error");
|
|
17910
|
-
process.exit(1);
|
|
17911
|
-
}
|
|
17912
|
-
selectedAgent = { id: agentId, name: displayName2, slug: projectName };
|
|
17913
|
-
deploymentUrl = `https://${projectName}-dev.struere.dev`;
|
|
17914
|
-
spinner.succeed(`Created agent "${projectName}"`);
|
|
17915
|
-
} else {
|
|
17916
|
-
deploymentUrl = `https://${selectedAgent.slug}-dev.struere.dev`;
|
|
17917
17692
|
console.log();
|
|
17918
|
-
console.log(source_default.green("\u2713"), `Linked to "${selectedAgent.name}"`);
|
|
17919
|
-
}
|
|
17920
|
-
saveProject(cwd, {
|
|
17921
|
-
agentId: selectedAgent.id,
|
|
17922
|
-
team: credentials.organization.slug,
|
|
17923
|
-
agent: {
|
|
17924
|
-
slug: selectedAgent.slug,
|
|
17925
|
-
name: selectedAgent.name
|
|
17926
|
-
}
|
|
17927
|
-
});
|
|
17928
|
-
console.log(source_default.green("\u2713"), "Created struere.json");
|
|
17929
|
-
const configResult = writeProjectConfig(cwd, {
|
|
17930
|
-
projectName,
|
|
17931
|
-
agentId: selectedAgent.id,
|
|
17932
|
-
team: credentials.organization.slug,
|
|
17933
|
-
agentSlug: selectedAgent.slug,
|
|
17934
|
-
agentName: selectedAgent.name,
|
|
17935
|
-
deploymentUrl
|
|
17936
|
-
});
|
|
17937
|
-
for (const file of configResult.createdFiles) {
|
|
17938
|
-
if (file !== "struere.json") {
|
|
17939
|
-
console.log(source_default.green("\u2713"), `Created ${file}`);
|
|
17940
|
-
}
|
|
17941
17693
|
}
|
|
17942
|
-
|
|
17943
|
-
|
|
17944
|
-
|
|
17945
|
-
|
|
17946
|
-
|
|
17947
|
-
|
|
17948
|
-
|
|
17949
|
-
|
|
17950
|
-
|
|
17951
|
-
|
|
17952
|
-
|
|
17953
|
-
|
|
17954
|
-
|
|
17955
|
-
|
|
17956
|
-
|
|
17957
|
-
|
|
17958
|
-
|
|
17959
|
-
|
|
17960
|
-
}
|
|
17694
|
+
console.log(source_default.green("\u2713"), "Logged in as", source_default.cyan(credentials.user.name || credentials.user.email));
|
|
17695
|
+
console.log(source_default.gray(" Organization:"), source_default.cyan(credentials.organization.name));
|
|
17696
|
+
console.log();
|
|
17697
|
+
let projectName = projectNameArg;
|
|
17698
|
+
if (!projectName) {
|
|
17699
|
+
projectName = slugify(basename(cwd));
|
|
17700
|
+
}
|
|
17701
|
+
projectName = slugify(projectName);
|
|
17702
|
+
spinner.start("Creating project structure");
|
|
17703
|
+
const scaffoldResult = scaffoldProjectV2(cwd, {
|
|
17704
|
+
projectName,
|
|
17705
|
+
orgId: credentials.organization.id,
|
|
17706
|
+
orgSlug: credentials.organization.slug,
|
|
17707
|
+
orgName: credentials.organization.name
|
|
17708
|
+
});
|
|
17709
|
+
spinner.succeed("Project structure created");
|
|
17710
|
+
for (const file of scaffoldResult.createdFiles) {
|
|
17711
|
+
console.log(source_default.green("\u2713"), `Created ${file}`);
|
|
17961
17712
|
}
|
|
17962
17713
|
console.log();
|
|
17963
17714
|
spinner.start("Installing dependencies");
|
|
@@ -17972,241 +17723,340 @@ var initCommand = new Command("init").description("Initialize a new Struere proj
|
|
|
17972
17723
|
spinner.warn("Could not install dependencies automatically");
|
|
17973
17724
|
console.log(source_default.gray(" Run"), source_default.cyan("bun install"), source_default.gray("manually"));
|
|
17974
17725
|
}
|
|
17975
|
-
spinner.start("Syncing initial config to Convex");
|
|
17976
|
-
const displayName = projectName.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
17977
|
-
const defaultConfig = {
|
|
17978
|
-
name: displayName,
|
|
17979
|
-
version: "0.1.0",
|
|
17980
|
-
systemPrompt: `You are ${displayName}, a helpful AI assistant. You help users with their questions and tasks.`,
|
|
17981
|
-
model: {
|
|
17982
|
-
provider: "anthropic",
|
|
17983
|
-
name: "claude-sonnet-4-20250514",
|
|
17984
|
-
temperature: 0.7,
|
|
17985
|
-
maxTokens: 4096
|
|
17986
|
-
},
|
|
17987
|
-
tools: []
|
|
17988
|
-
};
|
|
17989
|
-
const syncResult = await syncToConvex(selectedAgent.id, defaultConfig);
|
|
17990
|
-
if (syncResult.success) {
|
|
17991
|
-
spinner.succeed("Initial config synced");
|
|
17992
|
-
} else {
|
|
17993
|
-
spinner.warn("Could not sync initial config");
|
|
17994
|
-
console.log(source_default.gray(" Run"), source_default.cyan("struere dev"), source_default.gray("to sync manually"));
|
|
17995
|
-
}
|
|
17996
17726
|
console.log();
|
|
17997
17727
|
console.log(source_default.green("Success!"), "Project initialized");
|
|
17998
17728
|
console.log();
|
|
17729
|
+
console.log(source_default.gray("Project structure:"));
|
|
17730
|
+
console.log(source_default.gray(" agents/ "), source_default.cyan("Agent definitions"));
|
|
17731
|
+
console.log(source_default.gray(" entity-types/ "), source_default.cyan("Entity type schemas"));
|
|
17732
|
+
console.log(source_default.gray(" roles/ "), source_default.cyan("Role + permission definitions"));
|
|
17733
|
+
console.log(source_default.gray(" tools/ "), source_default.cyan("Shared custom tools"));
|
|
17734
|
+
console.log();
|
|
17999
17735
|
console.log(source_default.gray("Next steps:"));
|
|
18000
|
-
console.log(source_default.gray("
|
|
17736
|
+
console.log(source_default.gray(" 1."), source_default.cyan("struere add agent my-agent"), source_default.gray("- Create an agent"));
|
|
17737
|
+
console.log(source_default.gray(" 2."), source_default.cyan("struere dev"), source_default.gray("- Start development"));
|
|
18001
17738
|
console.log();
|
|
18002
17739
|
});
|
|
18003
|
-
async function deriveProjectName(cwd) {
|
|
18004
|
-
const packageJsonPath = join4(cwd, "package.json");
|
|
18005
|
-
if (existsSync4(packageJsonPath)) {
|
|
18006
|
-
try {
|
|
18007
|
-
const pkg = JSON.parse(readFileSync4(packageJsonPath, "utf-8"));
|
|
18008
|
-
if (pkg.name && typeof pkg.name === "string") {
|
|
18009
|
-
return slugify(pkg.name);
|
|
18010
|
-
}
|
|
18011
|
-
} catch {}
|
|
18012
|
-
}
|
|
18013
|
-
return slugify(basename(cwd));
|
|
18014
|
-
}
|
|
18015
17740
|
function slugify(name) {
|
|
18016
17741
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
18017
17742
|
}
|
|
18018
|
-
|
|
18019
|
-
|
|
18020
|
-
|
|
18021
|
-
|
|
17743
|
+
|
|
17744
|
+
// src/cli/commands/dev.ts
|
|
17745
|
+
var import_chokidar = __toESM(require_chokidar(), 1);
|
|
17746
|
+
import { join as join5 } from "path";
|
|
17747
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
17748
|
+
|
|
17749
|
+
// src/cli/utils/loader.ts
|
|
17750
|
+
import { existsSync as existsSync4, readdirSync } from "fs";
|
|
17751
|
+
import { join as join4 } from "path";
|
|
17752
|
+
async function loadAllResources(cwd) {
|
|
17753
|
+
const agents = await loadAllAgents(join4(cwd, "agents"));
|
|
17754
|
+
const entityTypes = await loadAllEntityTypes(join4(cwd, "entity-types"));
|
|
17755
|
+
const roles = await loadAllRoles(join4(cwd, "roles"));
|
|
17756
|
+
const customTools = await loadCustomTools(join4(cwd, "tools"));
|
|
17757
|
+
return { agents, entityTypes, roles, customTools };
|
|
18022
17758
|
}
|
|
18023
|
-
async function
|
|
18024
|
-
|
|
18025
|
-
|
|
18026
|
-
|
|
18027
|
-
|
|
17759
|
+
async function loadAllAgents(dir) {
|
|
17760
|
+
if (!existsSync4(dir)) {
|
|
17761
|
+
return [];
|
|
17762
|
+
}
|
|
17763
|
+
const indexPath = join4(dir, "index.ts");
|
|
17764
|
+
if (existsSync4(indexPath)) {
|
|
17765
|
+
return loadFromIndex(indexPath);
|
|
17766
|
+
}
|
|
17767
|
+
return loadFromDirectory(dir);
|
|
18028
17768
|
}
|
|
18029
|
-
async function
|
|
18030
|
-
|
|
18031
|
-
|
|
18032
|
-
for (let i = 0;i < choices.length; i++) {
|
|
18033
|
-
const prefix = i === 0 ? source_default.cyan("\u276F") : source_default.gray(" ");
|
|
18034
|
-
console.log(`${prefix} ${i + 1}. ${choices[i].label}`);
|
|
17769
|
+
async function loadAllEntityTypes(dir) {
|
|
17770
|
+
if (!existsSync4(dir)) {
|
|
17771
|
+
return [];
|
|
18035
17772
|
}
|
|
18036
|
-
|
|
18037
|
-
|
|
18038
|
-
|
|
18039
|
-
const num = parseInt(answer, 10);
|
|
18040
|
-
if (num >= 1 && num <= choices.length) {
|
|
18041
|
-
return choices[num - 1].value;
|
|
17773
|
+
const indexPath = join4(dir, "index.ts");
|
|
17774
|
+
if (existsSync4(indexPath)) {
|
|
17775
|
+
return loadFromIndex(indexPath);
|
|
18042
17776
|
}
|
|
18043
|
-
return
|
|
17777
|
+
return loadFromDirectory(dir);
|
|
18044
17778
|
}
|
|
18045
|
-
function
|
|
18046
|
-
|
|
18047
|
-
|
|
18048
|
-
|
|
18049
|
-
|
|
18050
|
-
|
|
18051
|
-
|
|
18052
|
-
|
|
18053
|
-
|
|
18054
|
-
buffer += data;
|
|
18055
|
-
if (buffer.includes(`
|
|
18056
|
-
`)) {
|
|
18057
|
-
process.stdin.removeListener("data", onData);
|
|
18058
|
-
process.stdin.pause();
|
|
18059
|
-
resolve(buffer.replace(/[\r\n]/g, "").trim());
|
|
18060
|
-
}
|
|
18061
|
-
};
|
|
18062
|
-
process.stdin.on("data", onData);
|
|
18063
|
-
});
|
|
17779
|
+
async function loadAllRoles(dir) {
|
|
17780
|
+
if (!existsSync4(dir)) {
|
|
17781
|
+
return [];
|
|
17782
|
+
}
|
|
17783
|
+
const indexPath = join4(dir, "index.ts");
|
|
17784
|
+
if (existsSync4(indexPath)) {
|
|
17785
|
+
return loadFromIndex(indexPath);
|
|
17786
|
+
}
|
|
17787
|
+
return loadFromDirectory(dir);
|
|
18064
17788
|
}
|
|
18065
|
-
|
|
18066
|
-
|
|
18067
|
-
|
|
18068
|
-
|
|
18069
|
-
|
|
18070
|
-
|
|
18071
|
-
|
|
18072
|
-
import { join as join5 } from "path";
|
|
18073
|
-
var defaultConfig = {
|
|
18074
|
-
port: 3000,
|
|
18075
|
-
host: "localhost",
|
|
18076
|
-
cors: {
|
|
18077
|
-
origins: ["http://localhost:3000"],
|
|
18078
|
-
credentials: true
|
|
18079
|
-
},
|
|
18080
|
-
logging: {
|
|
18081
|
-
level: "info",
|
|
18082
|
-
format: "pretty"
|
|
18083
|
-
},
|
|
18084
|
-
auth: {
|
|
18085
|
-
type: "none"
|
|
17789
|
+
async function loadCustomTools(dir) {
|
|
17790
|
+
if (!existsSync4(dir)) {
|
|
17791
|
+
return [];
|
|
17792
|
+
}
|
|
17793
|
+
const indexPath = join4(dir, "index.ts");
|
|
17794
|
+
if (!existsSync4(indexPath)) {
|
|
17795
|
+
return [];
|
|
18086
17796
|
}
|
|
18087
|
-
};
|
|
18088
|
-
async function loadConfig(cwd) {
|
|
18089
|
-
const configPath = join5(cwd, "struere.config.ts");
|
|
18090
17797
|
try {
|
|
18091
|
-
const module = await import(
|
|
18092
|
-
|
|
18093
|
-
|
|
18094
|
-
|
|
18095
|
-
|
|
18096
|
-
|
|
18097
|
-
|
|
18098
|
-
|
|
18099
|
-
},
|
|
18100
|
-
logging: {
|
|
18101
|
-
...defaultConfig.logging,
|
|
18102
|
-
...config.logging
|
|
18103
|
-
},
|
|
18104
|
-
auth: {
|
|
18105
|
-
...defaultConfig.auth,
|
|
18106
|
-
...config.auth
|
|
18107
|
-
}
|
|
18108
|
-
};
|
|
17798
|
+
const module = await import(indexPath);
|
|
17799
|
+
if (Array.isArray(module.default)) {
|
|
17800
|
+
return module.default;
|
|
17801
|
+
}
|
|
17802
|
+
if (module.tools && Array.isArray(module.tools)) {
|
|
17803
|
+
return module.tools;
|
|
17804
|
+
}
|
|
17805
|
+
return [];
|
|
18109
17806
|
} catch {
|
|
18110
|
-
return
|
|
17807
|
+
return [];
|
|
18111
17808
|
}
|
|
18112
17809
|
}
|
|
18113
|
-
|
|
18114
|
-
// src/cli/utils/agent.ts
|
|
18115
|
-
import { join as join6 } from "path";
|
|
18116
|
-
async function loadAgent(cwd) {
|
|
18117
|
-
const agentPath = join6(cwd, "src/agent.ts");
|
|
17810
|
+
async function loadFromIndex(indexPath) {
|
|
18118
17811
|
try {
|
|
18119
|
-
const module = await import(
|
|
18120
|
-
|
|
18121
|
-
|
|
18122
|
-
throw new Error("Agent must have a name");
|
|
18123
|
-
}
|
|
18124
|
-
if (!agent.version) {
|
|
18125
|
-
throw new Error("Agent must have a version");
|
|
17812
|
+
const module = await import(indexPath);
|
|
17813
|
+
if (Array.isArray(module.default)) {
|
|
17814
|
+
return module.default;
|
|
18126
17815
|
}
|
|
18127
|
-
|
|
18128
|
-
|
|
17816
|
+
const items = [];
|
|
17817
|
+
for (const key of Object.keys(module)) {
|
|
17818
|
+
if (key === "default")
|
|
17819
|
+
continue;
|
|
17820
|
+
const value = module[key];
|
|
17821
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
17822
|
+
items.push(value);
|
|
17823
|
+
}
|
|
18129
17824
|
}
|
|
18130
|
-
return
|
|
17825
|
+
return items;
|
|
18131
17826
|
} catch (error) {
|
|
18132
|
-
|
|
18133
|
-
|
|
17827
|
+
throw new Error(`Failed to load index at ${indexPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
17828
|
+
}
|
|
17829
|
+
}
|
|
17830
|
+
async function loadFromDirectory(dir) {
|
|
17831
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".ts") && f !== "index.ts" && !f.endsWith(".d.ts"));
|
|
17832
|
+
const items = [];
|
|
17833
|
+
for (const file of files) {
|
|
17834
|
+
const filePath = join4(dir, file);
|
|
17835
|
+
try {
|
|
17836
|
+
const module = await import(filePath);
|
|
17837
|
+
if (module.default) {
|
|
17838
|
+
items.push(module.default);
|
|
17839
|
+
}
|
|
17840
|
+
} catch (error) {
|
|
17841
|
+
throw new Error(`Failed to load ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
18134
17842
|
}
|
|
18135
|
-
throw error;
|
|
18136
17843
|
}
|
|
17844
|
+
return items;
|
|
17845
|
+
}
|
|
17846
|
+
function getResourceDirectories(cwd) {
|
|
17847
|
+
return {
|
|
17848
|
+
agents: join4(cwd, "agents"),
|
|
17849
|
+
entityTypes: join4(cwd, "entity-types"),
|
|
17850
|
+
roles: join4(cwd, "roles"),
|
|
17851
|
+
tools: join4(cwd, "tools")
|
|
17852
|
+
};
|
|
17853
|
+
}
|
|
17854
|
+
|
|
17855
|
+
// src/cli/utils/extractor.ts
|
|
17856
|
+
var BUILTIN_TOOLS = [
|
|
17857
|
+
"entity.create",
|
|
17858
|
+
"entity.get",
|
|
17859
|
+
"entity.query",
|
|
17860
|
+
"entity.update",
|
|
17861
|
+
"entity.delete",
|
|
17862
|
+
"entity.link",
|
|
17863
|
+
"entity.unlink",
|
|
17864
|
+
"event.emit",
|
|
17865
|
+
"event.query",
|
|
17866
|
+
"job.enqueue",
|
|
17867
|
+
"job.status"
|
|
17868
|
+
];
|
|
17869
|
+
function extractSyncPayload(resources) {
|
|
17870
|
+
const customToolsMap = new Map;
|
|
17871
|
+
for (const tool of resources.customTools) {
|
|
17872
|
+
customToolsMap.set(tool.name, tool);
|
|
17873
|
+
}
|
|
17874
|
+
const agents = resources.agents.map((agent) => extractAgentPayload(agent, customToolsMap));
|
|
17875
|
+
const entityTypes = resources.entityTypes.map((et) => ({
|
|
17876
|
+
name: et.name,
|
|
17877
|
+
slug: et.slug,
|
|
17878
|
+
schema: et.schema,
|
|
17879
|
+
searchFields: et.searchFields,
|
|
17880
|
+
displayConfig: et.displayConfig
|
|
17881
|
+
}));
|
|
17882
|
+
const roles = resources.roles.map((role) => ({
|
|
17883
|
+
name: role.name,
|
|
17884
|
+
description: role.description,
|
|
17885
|
+
policies: role.policies.map((p) => ({
|
|
17886
|
+
resource: p.resource,
|
|
17887
|
+
actions: p.actions,
|
|
17888
|
+
effect: p.effect,
|
|
17889
|
+
priority: p.priority
|
|
17890
|
+
})),
|
|
17891
|
+
scopeRules: role.scopeRules?.map((sr) => ({
|
|
17892
|
+
entityType: sr.entityType,
|
|
17893
|
+
field: sr.field,
|
|
17894
|
+
operator: sr.operator,
|
|
17895
|
+
value: sr.value
|
|
17896
|
+
})),
|
|
17897
|
+
fieldMasks: role.fieldMasks?.map((fm) => ({
|
|
17898
|
+
entityType: fm.entityType,
|
|
17899
|
+
fieldPath: fm.fieldPath,
|
|
17900
|
+
maskType: fm.maskType,
|
|
17901
|
+
maskConfig: fm.maskConfig
|
|
17902
|
+
}))
|
|
17903
|
+
}));
|
|
17904
|
+
return { agents, entityTypes, roles };
|
|
17905
|
+
}
|
|
17906
|
+
function extractAgentPayload(agent, customToolsMap) {
|
|
17907
|
+
let systemPrompt;
|
|
17908
|
+
if (typeof agent.systemPrompt === "function") {
|
|
17909
|
+
const result = agent.systemPrompt();
|
|
17910
|
+
if (result instanceof Promise) {
|
|
17911
|
+
throw new Error("Async system prompts must be resolved before syncing");
|
|
17912
|
+
}
|
|
17913
|
+
systemPrompt = result;
|
|
17914
|
+
} else {
|
|
17915
|
+
systemPrompt = agent.systemPrompt;
|
|
17916
|
+
}
|
|
17917
|
+
const tools = (agent.tools || []).map((toolName) => {
|
|
17918
|
+
const isBuiltin = BUILTIN_TOOLS.includes(toolName);
|
|
17919
|
+
if (isBuiltin) {
|
|
17920
|
+
return {
|
|
17921
|
+
name: toolName,
|
|
17922
|
+
description: getBuiltinToolDescription(toolName),
|
|
17923
|
+
parameters: { type: "object", properties: {} },
|
|
17924
|
+
isBuiltin: true
|
|
17925
|
+
};
|
|
17926
|
+
}
|
|
17927
|
+
const customTool = customToolsMap.get(toolName);
|
|
17928
|
+
if (!customTool) {
|
|
17929
|
+
throw new Error(`Tool "${toolName}" not found in custom tools`);
|
|
17930
|
+
}
|
|
17931
|
+
return {
|
|
17932
|
+
name: customTool.name,
|
|
17933
|
+
description: customTool.description,
|
|
17934
|
+
parameters: customTool.parameters || { type: "object", properties: {} },
|
|
17935
|
+
handlerCode: extractHandlerCode(customTool.handler),
|
|
17936
|
+
isBuiltin: false
|
|
17937
|
+
};
|
|
17938
|
+
});
|
|
17939
|
+
return {
|
|
17940
|
+
name: agent.name,
|
|
17941
|
+
slug: agent.slug,
|
|
17942
|
+
version: agent.version,
|
|
17943
|
+
description: agent.description,
|
|
17944
|
+
systemPrompt,
|
|
17945
|
+
model: {
|
|
17946
|
+
provider: agent.model?.provider || "anthropic",
|
|
17947
|
+
name: agent.model?.name || "claude-sonnet-4-20250514",
|
|
17948
|
+
temperature: agent.model?.temperature,
|
|
17949
|
+
maxTokens: agent.model?.maxTokens
|
|
17950
|
+
},
|
|
17951
|
+
tools
|
|
17952
|
+
};
|
|
17953
|
+
}
|
|
17954
|
+
function getBuiltinToolDescription(name) {
|
|
17955
|
+
const descriptions = {
|
|
17956
|
+
"entity.create": "Create a new entity",
|
|
17957
|
+
"entity.get": "Get an entity by ID",
|
|
17958
|
+
"entity.query": "Query entities by type and filters",
|
|
17959
|
+
"entity.update": "Update an entity",
|
|
17960
|
+
"entity.delete": "Delete an entity",
|
|
17961
|
+
"entity.link": "Link two entities",
|
|
17962
|
+
"entity.unlink": "Unlink two entities",
|
|
17963
|
+
"event.emit": "Emit an event",
|
|
17964
|
+
"event.query": "Query events",
|
|
17965
|
+
"job.enqueue": "Schedule a background job",
|
|
17966
|
+
"job.status": "Get job status"
|
|
17967
|
+
};
|
|
17968
|
+
return descriptions[name] || name;
|
|
17969
|
+
}
|
|
17970
|
+
function extractHandlerCode(handler) {
|
|
17971
|
+
const code = handler.toString();
|
|
17972
|
+
const arrowMatch = code.match(/(?:async\s*)?\([^)]*\)\s*=>\s*\{?([\s\S]*)\}?$/);
|
|
17973
|
+
if (arrowMatch) {
|
|
17974
|
+
let body = arrowMatch[1].trim();
|
|
17975
|
+
if (body.startsWith("{") && body.endsWith("}")) {
|
|
17976
|
+
body = body.slice(1, -1).trim();
|
|
17977
|
+
}
|
|
17978
|
+
return body;
|
|
17979
|
+
}
|
|
17980
|
+
const funcMatch = code.match(/(?:async\s*)?function[^(]*\([^)]*\)\s*\{([\s\S]*)\}$/);
|
|
17981
|
+
if (funcMatch) {
|
|
17982
|
+
return funcMatch[1].trim();
|
|
17983
|
+
}
|
|
17984
|
+
return code;
|
|
18137
17985
|
}
|
|
18138
17986
|
|
|
18139
17987
|
// src/cli/commands/dev.ts
|
|
18140
|
-
var devCommand = new Command("dev").description("Sync
|
|
17988
|
+
var devCommand = new Command("dev").description("Sync all resources to development environment").action(async () => {
|
|
18141
17989
|
const spinner = ora();
|
|
18142
17990
|
const cwd = process.cwd();
|
|
18143
17991
|
console.log();
|
|
18144
17992
|
console.log(source_default.bold("Struere Dev"));
|
|
18145
17993
|
console.log();
|
|
18146
|
-
let project = loadProject(cwd);
|
|
18147
17994
|
if (!hasProject(cwd)) {
|
|
18148
|
-
console.log(source_default.yellow("No struere.json found"));
|
|
17995
|
+
console.log(source_default.yellow("No struere.json found - initializing project..."));
|
|
18149
17996
|
console.log();
|
|
18150
|
-
|
|
18151
|
-
|
|
18152
|
-
|
|
18153
|
-
|
|
18154
|
-
|
|
17997
|
+
await runInit(cwd);
|
|
17998
|
+
console.log();
|
|
17999
|
+
}
|
|
18000
|
+
const version = getProjectVersion(cwd);
|
|
18001
|
+
if (version === "1.0") {
|
|
18002
|
+
console.log(source_default.yellow("This is a v1 agent-centric project."));
|
|
18003
|
+
console.log(source_default.yellow("Please migrate to v2 structure or use an older CLI version."));
|
|
18004
|
+
console.log();
|
|
18005
|
+
process.exit(1);
|
|
18155
18006
|
}
|
|
18156
|
-
project =
|
|
18007
|
+
const project = loadProjectV2(cwd);
|
|
18157
18008
|
if (!project) {
|
|
18158
18009
|
console.log(source_default.red("Failed to load struere.json"));
|
|
18159
18010
|
process.exit(1);
|
|
18160
18011
|
}
|
|
18161
|
-
console.log(source_default.gray("
|
|
18012
|
+
console.log(source_default.gray("Organization:"), source_default.cyan(project.organization.name));
|
|
18162
18013
|
console.log();
|
|
18163
|
-
spinner.start("Loading configuration");
|
|
18164
|
-
await loadConfig(cwd);
|
|
18165
|
-
spinner.succeed("Configuration loaded");
|
|
18166
|
-
spinner.start("Loading agent");
|
|
18167
|
-
let agent = await loadAgent(cwd);
|
|
18168
|
-
spinner.succeed(`Agent "${agent.name}" loaded`);
|
|
18169
|
-
const claudeMdPath = join7(cwd, "CLAUDE.md");
|
|
18170
|
-
if (!existsSync5(claudeMdPath)) {
|
|
18171
|
-
writeFileSync4(claudeMdPath, getClaudeMD(project.agent.slug));
|
|
18172
|
-
console.log(source_default.green("\u2713"), "Created CLAUDE.md");
|
|
18173
|
-
}
|
|
18174
18014
|
let credentials = loadCredentials();
|
|
18175
18015
|
const apiKey = getApiKey();
|
|
18176
18016
|
if (!credentials && !apiKey) {
|
|
18177
|
-
console.log(source_default.
|
|
18017
|
+
console.log(source_default.yellow("Not logged in - authenticating..."));
|
|
18178
18018
|
console.log();
|
|
18179
18019
|
credentials = await performLogin();
|
|
18180
18020
|
if (!credentials) {
|
|
18181
18021
|
console.log(source_default.red("Authentication failed"));
|
|
18182
18022
|
process.exit(1);
|
|
18183
18023
|
}
|
|
18024
|
+
console.log();
|
|
18025
|
+
}
|
|
18026
|
+
const claudeMdPath = join5(cwd, "CLAUDE.md");
|
|
18027
|
+
if (!existsSync5(claudeMdPath)) {
|
|
18028
|
+
writeFileSync4(claudeMdPath, getClaudeMDV2(project.organization.name));
|
|
18029
|
+
console.log(source_default.green("\u2713"), "Created CLAUDE.md");
|
|
18184
18030
|
}
|
|
18185
|
-
spinner.start("Syncing to Convex");
|
|
18186
|
-
const performSync = async () => {
|
|
18187
|
-
try {
|
|
18188
|
-
const config = extractConfig(agent);
|
|
18189
|
-
const result = await syncToConvex(project.agentId, config);
|
|
18190
|
-
if (!result.success) {
|
|
18191
|
-
throw new Error(result.error || "Sync failed");
|
|
18192
|
-
}
|
|
18193
|
-
return true;
|
|
18194
|
-
} catch (error) {
|
|
18195
|
-
throw error;
|
|
18196
|
-
}
|
|
18197
|
-
};
|
|
18198
18031
|
const isAuthError = (error) => {
|
|
18199
18032
|
const message = error instanceof Error ? error.message : String(error);
|
|
18200
18033
|
return message.includes("Unauthenticated") || message.includes("OIDC") || message.includes("token") || message.includes("expired");
|
|
18201
18034
|
};
|
|
18035
|
+
const performSync = async () => {
|
|
18036
|
+
const resources = await loadAllResources(cwd);
|
|
18037
|
+
const payload = extractSyncPayload(resources);
|
|
18038
|
+
const result = await syncOrganization(payload);
|
|
18039
|
+
if (!result.success) {
|
|
18040
|
+
throw new Error(result.error || "Sync failed");
|
|
18041
|
+
}
|
|
18042
|
+
return true;
|
|
18043
|
+
};
|
|
18044
|
+
spinner.start("Loading resources");
|
|
18045
|
+
try {
|
|
18046
|
+
const resources = await loadAllResources(cwd);
|
|
18047
|
+
spinner.succeed(`Loaded ${resources.agents.length} agents, ${resources.entityTypes.length} entity types, ${resources.roles.length} roles`);
|
|
18048
|
+
} catch (error) {
|
|
18049
|
+
spinner.fail("Failed to load resources");
|
|
18050
|
+
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
18051
|
+
process.exit(1);
|
|
18052
|
+
}
|
|
18053
|
+
spinner.start("Syncing to Convex");
|
|
18202
18054
|
try {
|
|
18203
18055
|
await performSync();
|
|
18204
18056
|
spinner.succeed("Synced to development");
|
|
18205
18057
|
} catch (error) {
|
|
18206
18058
|
if (isAuthError(error)) {
|
|
18207
|
-
spinner.fail("Session expired");
|
|
18208
|
-
console.log();
|
|
18209
|
-
console.log(source_default.gray("Re-authenticating..."));
|
|
18059
|
+
spinner.fail("Session expired - re-authenticating...");
|
|
18210
18060
|
clearCredentials();
|
|
18211
18061
|
credentials = await performLogin();
|
|
18212
18062
|
if (!credentials) {
|
|
@@ -18228,13 +18078,18 @@ var devCommand = new Command("dev").description("Sync agent to development envir
|
|
|
18228
18078
|
process.exit(1);
|
|
18229
18079
|
}
|
|
18230
18080
|
}
|
|
18231
|
-
const devUrl = `https://${project.agent.slug}-dev.struere.dev`;
|
|
18232
|
-
console.log();
|
|
18233
|
-
console.log(source_default.green("Development URL:"), source_default.cyan(devUrl));
|
|
18234
18081
|
console.log();
|
|
18235
18082
|
console.log(source_default.gray("Watching for changes... Press Ctrl+C to stop"));
|
|
18236
18083
|
console.log();
|
|
18237
|
-
const
|
|
18084
|
+
const dirs = getResourceDirectories(cwd);
|
|
18085
|
+
const watchPaths = [
|
|
18086
|
+
dirs.agents,
|
|
18087
|
+
dirs.entityTypes,
|
|
18088
|
+
dirs.roles,
|
|
18089
|
+
dirs.tools,
|
|
18090
|
+
join5(cwd, "struere.config.ts")
|
|
18091
|
+
].filter((p) => existsSync5(p));
|
|
18092
|
+
const watcher = import_chokidar.default.watch(watchPaths, {
|
|
18238
18093
|
ignoreInitial: true,
|
|
18239
18094
|
ignored: /node_modules/
|
|
18240
18095
|
});
|
|
@@ -18243,14 +18098,11 @@ var devCommand = new Command("dev").description("Sync agent to development envir
|
|
|
18243
18098
|
console.log(source_default.gray(`Changed: ${relativePath}`));
|
|
18244
18099
|
const syncSpinner = ora("Syncing...").start();
|
|
18245
18100
|
try {
|
|
18246
|
-
agent = await loadAgent(cwd);
|
|
18247
18101
|
await performSync();
|
|
18248
18102
|
syncSpinner.succeed("Synced");
|
|
18249
18103
|
} catch (error) {
|
|
18250
18104
|
if (isAuthError(error)) {
|
|
18251
|
-
syncSpinner.fail("Session expired");
|
|
18252
|
-
console.log();
|
|
18253
|
-
console.log(source_default.gray("Re-authenticating..."));
|
|
18105
|
+
syncSpinner.fail("Session expired - re-authenticating...");
|
|
18254
18106
|
clearCredentials();
|
|
18255
18107
|
const newCredentials = await performLogin();
|
|
18256
18108
|
if (!newCredentials) {
|
|
@@ -18271,248 +18123,109 @@ var devCommand = new Command("dev").description("Sync agent to development envir
|
|
|
18271
18123
|
}
|
|
18272
18124
|
}
|
|
18273
18125
|
});
|
|
18274
|
-
|
|
18275
|
-
|
|
18276
|
-
|
|
18277
|
-
|
|
18278
|
-
|
|
18279
|
-
|
|
18280
|
-
|
|
18281
|
-
|
|
18282
|
-
|
|
18283
|
-
|
|
18284
|
-
if (!credentials) {
|
|
18285
|
-
console.log(source_default.gray("Authentication required"));
|
|
18286
|
-
console.log();
|
|
18287
|
-
credentials = await performLogin();
|
|
18288
|
-
if (!credentials) {
|
|
18289
|
-
console.log(source_default.red("Authentication failed"));
|
|
18290
|
-
return null;
|
|
18291
|
-
}
|
|
18292
|
-
} else {
|
|
18293
|
-
console.log(source_default.green("\u2713"), "Logged in as", source_default.cyan(credentials.user.name));
|
|
18294
|
-
console.log();
|
|
18295
|
-
}
|
|
18296
|
-
spinner.start("Fetching agents");
|
|
18297
|
-
let { agents: existingAgents, error: listError } = await listAgents();
|
|
18298
|
-
if (listError) {
|
|
18299
|
-
const isAuthError = listError.includes("Unauthenticated") || listError.includes("OIDC") || listError.includes("token") || listError.includes("expired");
|
|
18300
|
-
if (isAuthError) {
|
|
18301
|
-
spinner.fail("Session expired");
|
|
18302
|
-
console.log();
|
|
18303
|
-
console.log(source_default.gray("Re-authenticating..."));
|
|
18304
|
-
clearCredentials();
|
|
18305
|
-
credentials = await performLogin();
|
|
18306
|
-
if (!credentials) {
|
|
18307
|
-
console.log(source_default.red("Authentication failed"));
|
|
18308
|
-
return null;
|
|
18309
|
-
}
|
|
18310
|
-
spinner.start("Fetching agents");
|
|
18311
|
-
const retryResult = await listAgents();
|
|
18312
|
-
existingAgents = retryResult.agents;
|
|
18313
|
-
listError = retryResult.error;
|
|
18314
|
-
}
|
|
18315
|
-
if (listError) {
|
|
18316
|
-
spinner.fail("Failed to fetch agents");
|
|
18317
|
-
console.log();
|
|
18318
|
-
console.log(source_default.red("Error:"), listError);
|
|
18319
|
-
return null;
|
|
18320
|
-
}
|
|
18321
|
-
}
|
|
18322
|
-
const agents = existingAgents.map((a) => ({ id: a._id, name: a.name, slug: a.slug }));
|
|
18323
|
-
spinner.succeed(`Found ${agents.length} existing agent(s)`);
|
|
18324
|
-
let selectedAgent = null;
|
|
18325
|
-
if (agents.length === 0) {
|
|
18326
|
-
console.log(source_default.gray("No existing agents found. Creating a new one..."));
|
|
18327
|
-
} else {
|
|
18328
|
-
console.log();
|
|
18329
|
-
const choices = [
|
|
18330
|
-
{ value: "link", label: "Link to an existing agent" },
|
|
18331
|
-
{ value: "create", label: "Create a new agent" },
|
|
18332
|
-
{ value: "cancel", label: "Cancel" }
|
|
18333
|
-
];
|
|
18334
|
-
const action = await promptChoiceArrows("No agent configured. Would you like to:", choices);
|
|
18335
|
-
if (action === "cancel") {
|
|
18336
|
-
console.log();
|
|
18337
|
-
console.log(source_default.gray("Run"), source_default.cyan("struere init"), source_default.gray("when ready to set up"));
|
|
18338
|
-
return null;
|
|
18339
|
-
}
|
|
18340
|
-
if (action === "link") {
|
|
18341
|
-
console.log();
|
|
18342
|
-
const agentChoices = agents.map((a) => ({ value: a.id, label: `${a.name} (${a.slug})` }));
|
|
18343
|
-
const agentId = await promptChoiceArrows("Select an agent:", agentChoices);
|
|
18344
|
-
selectedAgent = agents.find((a) => a.id === agentId) || null;
|
|
18345
|
-
}
|
|
18346
|
-
}
|
|
18347
|
-
if (!selectedAgent) {
|
|
18348
|
-
console.log();
|
|
18349
|
-
const projectName = slugify2(basename2(cwd));
|
|
18350
|
-
const name = await promptText2("Agent name:", projectName);
|
|
18351
|
-
const displayName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
18352
|
-
spinner.start("Creating agent");
|
|
18353
|
-
let { agentId, error: createError } = await createAgent({
|
|
18354
|
-
name: displayName,
|
|
18355
|
-
slug: name,
|
|
18356
|
-
description: `${displayName} Agent`
|
|
18357
|
-
});
|
|
18358
|
-
if (createError) {
|
|
18359
|
-
const isAuthError = createError.includes("Unauthenticated") || createError.includes("OIDC") || createError.includes("token") || createError.includes("expired");
|
|
18360
|
-
if (isAuthError) {
|
|
18361
|
-
spinner.fail("Session expired");
|
|
18362
|
-
console.log();
|
|
18363
|
-
console.log(source_default.gray("Re-authenticating..."));
|
|
18364
|
-
clearCredentials();
|
|
18365
|
-
credentials = await performLogin();
|
|
18366
|
-
if (!credentials) {
|
|
18367
|
-
console.log(source_default.red("Authentication failed"));
|
|
18368
|
-
return null;
|
|
18369
|
-
}
|
|
18370
|
-
spinner.start("Creating agent");
|
|
18371
|
-
const retryResult = await createAgent({
|
|
18372
|
-
name: displayName,
|
|
18373
|
-
slug: name,
|
|
18374
|
-
description: `${displayName} Agent`
|
|
18375
|
-
});
|
|
18376
|
-
agentId = retryResult.agentId;
|
|
18377
|
-
createError = retryResult.error;
|
|
18378
|
-
}
|
|
18379
|
-
}
|
|
18380
|
-
if (createError || !agentId) {
|
|
18381
|
-
spinner.fail("Failed to create agent");
|
|
18382
|
-
console.log();
|
|
18383
|
-
console.log(source_default.red("Error:"), createError || "Unknown error");
|
|
18384
|
-
return null;
|
|
18385
|
-
}
|
|
18386
|
-
selectedAgent = { id: agentId, name: displayName, slug: name };
|
|
18387
|
-
spinner.succeed(`Created agent "${name}"`);
|
|
18388
|
-
}
|
|
18389
|
-
if (!selectedAgent) {
|
|
18390
|
-
return null;
|
|
18391
|
-
}
|
|
18392
|
-
const projectData = {
|
|
18393
|
-
agentId: selectedAgent.id,
|
|
18394
|
-
team: credentials.organization.slug,
|
|
18395
|
-
agent: {
|
|
18396
|
-
slug: selectedAgent.slug,
|
|
18397
|
-
name: selectedAgent.name
|
|
18398
|
-
}
|
|
18399
|
-
};
|
|
18400
|
-
saveProject(cwd, projectData);
|
|
18401
|
-
console.log(source_default.green("\u2713"), "Created struere.json");
|
|
18402
|
-
if (!hasAgentFiles(cwd)) {
|
|
18403
|
-
const scaffoldResult = scaffoldAgentFiles(cwd, selectedAgent.slug);
|
|
18404
|
-
for (const file of scaffoldResult.createdFiles) {
|
|
18405
|
-
console.log(source_default.green("\u2713"), `Created ${file}`);
|
|
18126
|
+
watcher.on("add", async (path) => {
|
|
18127
|
+
const relativePath = path.replace(cwd, ".");
|
|
18128
|
+
console.log(source_default.gray(`Added: ${relativePath}`));
|
|
18129
|
+
const syncSpinner = ora("Syncing...").start();
|
|
18130
|
+
try {
|
|
18131
|
+
await performSync();
|
|
18132
|
+
syncSpinner.succeed("Synced");
|
|
18133
|
+
} catch (error) {
|
|
18134
|
+
syncSpinner.fail("Sync failed");
|
|
18135
|
+
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
18406
18136
|
}
|
|
18407
|
-
|
|
18408
|
-
|
|
18137
|
+
});
|
|
18138
|
+
watcher.on("unlink", async (path) => {
|
|
18139
|
+
const relativePath = path.replace(cwd, ".");
|
|
18140
|
+
console.log(source_default.gray(`Removed: ${relativePath}`));
|
|
18141
|
+
const syncSpinner = ora("Syncing...").start();
|
|
18409
18142
|
try {
|
|
18410
|
-
|
|
18411
|
-
|
|
18412
|
-
|
|
18413
|
-
|
|
18414
|
-
|
|
18415
|
-
await proc.exited;
|
|
18416
|
-
if (proc.exitCode === 0) {
|
|
18417
|
-
spinner.succeed("Dependencies installed");
|
|
18418
|
-
} else {
|
|
18419
|
-
spinner.fail("Failed to install dependencies");
|
|
18420
|
-
console.log(source_default.yellow("Run"), source_default.cyan("bun install"), source_default.yellow("manually"));
|
|
18421
|
-
}
|
|
18422
|
-
} catch {
|
|
18423
|
-
spinner.fail("Failed to install dependencies");
|
|
18424
|
-
console.log(source_default.yellow("Run"), source_default.cyan("bun install"), source_default.yellow("manually"));
|
|
18143
|
+
await performSync();
|
|
18144
|
+
syncSpinner.succeed("Synced");
|
|
18145
|
+
} catch (error) {
|
|
18146
|
+
syncSpinner.fail("Sync failed");
|
|
18147
|
+
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
18425
18148
|
}
|
|
18149
|
+
});
|
|
18150
|
+
process.on("SIGINT", () => {
|
|
18151
|
+
console.log();
|
|
18152
|
+
watcher.close();
|
|
18153
|
+
console.log(source_default.gray("Stopped"));
|
|
18154
|
+
process.exit(0);
|
|
18155
|
+
});
|
|
18156
|
+
});
|
|
18157
|
+
|
|
18158
|
+
// src/cli/commands/build.ts
|
|
18159
|
+
import { join as join8 } from "path";
|
|
18160
|
+
|
|
18161
|
+
// src/cli/utils/config.ts
|
|
18162
|
+
import { join as join6 } from "path";
|
|
18163
|
+
var defaultConfig = {
|
|
18164
|
+
port: 3000,
|
|
18165
|
+
host: "localhost",
|
|
18166
|
+
cors: {
|
|
18167
|
+
origins: ["http://localhost:3000"],
|
|
18168
|
+
credentials: true
|
|
18169
|
+
},
|
|
18170
|
+
logging: {
|
|
18171
|
+
level: "info",
|
|
18172
|
+
format: "pretty"
|
|
18173
|
+
},
|
|
18174
|
+
auth: {
|
|
18175
|
+
type: "none"
|
|
18426
18176
|
}
|
|
18427
|
-
|
|
18428
|
-
|
|
18429
|
-
|
|
18430
|
-
|
|
18431
|
-
|
|
18432
|
-
|
|
18433
|
-
|
|
18434
|
-
|
|
18435
|
-
|
|
18436
|
-
|
|
18437
|
-
|
|
18438
|
-
|
|
18439
|
-
|
|
18440
|
-
|
|
18441
|
-
|
|
18442
|
-
|
|
18443
|
-
|
|
18444
|
-
|
|
18177
|
+
};
|
|
18178
|
+
async function loadConfig(cwd) {
|
|
18179
|
+
const configPath = join6(cwd, "struere.config.ts");
|
|
18180
|
+
try {
|
|
18181
|
+
const module = await import(configPath);
|
|
18182
|
+
const config = module.default || module;
|
|
18183
|
+
return {
|
|
18184
|
+
...defaultConfig,
|
|
18185
|
+
...config,
|
|
18186
|
+
cors: {
|
|
18187
|
+
...defaultConfig.cors,
|
|
18188
|
+
...config.cors
|
|
18189
|
+
},
|
|
18190
|
+
logging: {
|
|
18191
|
+
...defaultConfig.logging,
|
|
18192
|
+
...config.logging
|
|
18193
|
+
},
|
|
18194
|
+
auth: {
|
|
18195
|
+
...defaultConfig.auth,
|
|
18196
|
+
...config.auth
|
|
18445
18197
|
}
|
|
18446
18198
|
};
|
|
18447
|
-
|
|
18448
|
-
|
|
18449
|
-
|
|
18450
|
-
|
|
18451
|
-
|
|
18452
|
-
|
|
18199
|
+
} catch {
|
|
18200
|
+
return defaultConfig;
|
|
18201
|
+
}
|
|
18202
|
+
}
|
|
18203
|
+
|
|
18204
|
+
// src/cli/utils/agent.ts
|
|
18205
|
+
import { join as join7 } from "path";
|
|
18206
|
+
async function loadAgent(cwd) {
|
|
18207
|
+
const agentPath = join7(cwd, "src/agent.ts");
|
|
18208
|
+
try {
|
|
18209
|
+
const module = await import(`${agentPath}?t=${Date.now()}`);
|
|
18210
|
+
const agent = module.default || module;
|
|
18211
|
+
if (!agent.name) {
|
|
18212
|
+
throw new Error("Agent must have a name");
|
|
18453
18213
|
}
|
|
18454
|
-
if (!
|
|
18455
|
-
|
|
18456
|
-
return;
|
|
18214
|
+
if (!agent.version) {
|
|
18215
|
+
throw new Error("Agent must have a version");
|
|
18457
18216
|
}
|
|
18458
|
-
|
|
18459
|
-
|
|
18460
|
-
const onKeypress = (key) => {
|
|
18461
|
-
const char = key.toString();
|
|
18462
|
-
if (char === "\x1B[A" || char === "k") {
|
|
18463
|
-
selectedIndex = (selectedIndex - 1 + choices.length) % choices.length;
|
|
18464
|
-
render();
|
|
18465
|
-
} else if (char === "\x1B[B" || char === "j") {
|
|
18466
|
-
selectedIndex = (selectedIndex + 1) % choices.length;
|
|
18467
|
-
render();
|
|
18468
|
-
} else if (char === "\r" || char === `
|
|
18469
|
-
`) {
|
|
18470
|
-
process.stdin.removeListener("data", onKeypress);
|
|
18471
|
-
process.stdin.setRawMode?.(false);
|
|
18472
|
-
process.stdin.pause();
|
|
18473
|
-
process.stdout.write("\x1B[?25h");
|
|
18474
|
-
resolve(choices[selectedIndex].value);
|
|
18475
|
-
} else if (char === "\x03") {
|
|
18476
|
-
process.stdin.removeListener("data", onKeypress);
|
|
18477
|
-
process.stdin.setRawMode?.(false);
|
|
18478
|
-
process.stdout.write("\x1B[?25h");
|
|
18479
|
-
process.exit(0);
|
|
18480
|
-
}
|
|
18481
|
-
};
|
|
18482
|
-
process.stdin.on("data", onKeypress);
|
|
18483
|
-
});
|
|
18484
|
-
}
|
|
18485
|
-
async function promptText2(message, defaultValue) {
|
|
18486
|
-
process.stdout.write(source_default.gray(`${message} `));
|
|
18487
|
-
process.stdout.write(source_default.cyan(`(${defaultValue}) `));
|
|
18488
|
-
const answer = await readLine2();
|
|
18489
|
-
return answer || defaultValue;
|
|
18490
|
-
}
|
|
18491
|
-
function readLine2() {
|
|
18492
|
-
return new Promise((resolve) => {
|
|
18493
|
-
let buffer = "";
|
|
18494
|
-
const onData = (chunk) => {
|
|
18495
|
-
const str = chunk.toString();
|
|
18496
|
-
buffer += str;
|
|
18497
|
-
if (str.includes(`
|
|
18498
|
-
`) || str.includes("\r")) {
|
|
18499
|
-
process.stdin.removeListener("data", onData);
|
|
18500
|
-
process.stdin.pause();
|
|
18501
|
-
process.stdin.setRawMode?.(false);
|
|
18502
|
-
resolve(buffer.replace(/[\r\n]/g, "").trim());
|
|
18503
|
-
}
|
|
18504
|
-
};
|
|
18505
|
-
if (process.stdin.isTTY) {
|
|
18506
|
-
process.stdin.setRawMode?.(false);
|
|
18217
|
+
if (!agent.systemPrompt) {
|
|
18218
|
+
throw new Error("Agent must have a systemPrompt");
|
|
18507
18219
|
}
|
|
18508
|
-
|
|
18509
|
-
|
|
18510
|
-
|
|
18220
|
+
return agent;
|
|
18221
|
+
} catch (error) {
|
|
18222
|
+
if (error instanceof Error && error.message.includes("Cannot find module")) {
|
|
18223
|
+
throw new Error(`Agent not found at ${agentPath}`);
|
|
18224
|
+
}
|
|
18225
|
+
throw error;
|
|
18226
|
+
}
|
|
18511
18227
|
}
|
|
18512
18228
|
|
|
18513
|
-
// src/cli/commands/build.ts
|
|
18514
|
-
import { join as join8 } from "path";
|
|
18515
|
-
|
|
18516
18229
|
// src/cli/utils/validate.ts
|
|
18517
18230
|
function validateAgent(agent) {
|
|
18518
18231
|
const errors = [];
|
|
@@ -18827,73 +18540,90 @@ function formatAssertionError(assertion, context) {
|
|
|
18827
18540
|
}
|
|
18828
18541
|
|
|
18829
18542
|
// src/cli/commands/deploy.ts
|
|
18830
|
-
var deployCommand = new Command("deploy").description("Deploy
|
|
18831
|
-
const environment = "production";
|
|
18543
|
+
var deployCommand = new Command("deploy").description("Deploy all agents to production").option("--dry-run", "Show what would be deployed without deploying").action(async (options) => {
|
|
18832
18544
|
const spinner = ora();
|
|
18833
18545
|
const cwd = process.cwd();
|
|
18834
18546
|
console.log();
|
|
18835
|
-
console.log(source_default.bold("Deploying
|
|
18547
|
+
console.log(source_default.bold("Deploying Agents"));
|
|
18836
18548
|
console.log();
|
|
18837
18549
|
if (!hasProject(cwd)) {
|
|
18838
|
-
console.log(source_default.yellow("No struere.json found"));
|
|
18550
|
+
console.log(source_default.yellow("No struere.json found - initializing project..."));
|
|
18839
18551
|
console.log();
|
|
18840
|
-
|
|
18552
|
+
const success = await runInit(cwd);
|
|
18553
|
+
if (!success) {
|
|
18554
|
+
process.exit(1);
|
|
18555
|
+
}
|
|
18556
|
+
console.log();
|
|
18557
|
+
}
|
|
18558
|
+
const version = getProjectVersion(cwd);
|
|
18559
|
+
if (version === "1.0") {
|
|
18560
|
+
console.log(source_default.yellow("This is a v1 agent-centric project."));
|
|
18561
|
+
console.log(source_default.yellow("Please migrate to v2 structure or use an older CLI version."));
|
|
18841
18562
|
console.log();
|
|
18842
18563
|
process.exit(1);
|
|
18843
18564
|
}
|
|
18844
|
-
const project =
|
|
18565
|
+
const project = loadProjectV2(cwd);
|
|
18845
18566
|
if (!project) {
|
|
18846
18567
|
console.log(source_default.red("Failed to load struere.json"));
|
|
18847
18568
|
process.exit(1);
|
|
18848
18569
|
}
|
|
18849
|
-
console.log(source_default.gray("
|
|
18570
|
+
console.log(source_default.gray("Organization:"), source_default.cyan(project.organization.name));
|
|
18850
18571
|
console.log();
|
|
18851
|
-
|
|
18852
|
-
|
|
18853
|
-
|
|
18854
|
-
|
|
18855
|
-
const agent = await loadAgent(cwd);
|
|
18856
|
-
spinner.succeed(`Agent "${agent.name}" loaded`);
|
|
18857
|
-
spinner.start("Validating agent");
|
|
18858
|
-
const errors = validateAgent(agent);
|
|
18859
|
-
if (errors.length > 0) {
|
|
18860
|
-
spinner.fail("Validation failed");
|
|
18572
|
+
let credentials = loadCredentials();
|
|
18573
|
+
const apiKey = getApiKey();
|
|
18574
|
+
if (!credentials && !apiKey) {
|
|
18575
|
+
console.log(source_default.yellow("Not logged in - authenticating..."));
|
|
18861
18576
|
console.log();
|
|
18862
|
-
|
|
18863
|
-
|
|
18577
|
+
credentials = await performLogin();
|
|
18578
|
+
if (!credentials) {
|
|
18579
|
+
console.log(source_default.red("Authentication failed"));
|
|
18580
|
+
process.exit(1);
|
|
18864
18581
|
}
|
|
18865
18582
|
console.log();
|
|
18583
|
+
}
|
|
18584
|
+
spinner.start("Loading resources");
|
|
18585
|
+
let resources;
|
|
18586
|
+
try {
|
|
18587
|
+
resources = await loadAllResources(cwd);
|
|
18588
|
+
spinner.succeed(`Loaded ${resources.agents.length} agents, ${resources.entityTypes.length} entity types, ${resources.roles.length} roles`);
|
|
18589
|
+
} catch (error) {
|
|
18590
|
+
spinner.fail("Failed to load resources");
|
|
18591
|
+
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
18866
18592
|
process.exit(1);
|
|
18867
18593
|
}
|
|
18868
|
-
|
|
18594
|
+
if (resources.agents.length === 0) {
|
|
18595
|
+
console.log();
|
|
18596
|
+
console.log(source_default.yellow("No agents found to deploy"));
|
|
18597
|
+
console.log();
|
|
18598
|
+
console.log(source_default.gray("Run"), source_default.cyan("struere add agent my-agent"), source_default.gray("to create an agent"));
|
|
18599
|
+
console.log();
|
|
18600
|
+
return;
|
|
18601
|
+
}
|
|
18869
18602
|
if (options.dryRun) {
|
|
18870
18603
|
console.log();
|
|
18871
18604
|
console.log(source_default.yellow("Dry run mode - no changes will be made"));
|
|
18872
18605
|
console.log();
|
|
18873
18606
|
console.log("Would deploy:");
|
|
18874
|
-
|
|
18875
|
-
|
|
18876
|
-
|
|
18877
|
-
console.log(source_default.gray(" -"), `Agent ID: ${source_default.cyan(project.agentId)}`);
|
|
18607
|
+
for (const agent of resources.agents) {
|
|
18608
|
+
console.log(source_default.gray(" -"), `${source_default.cyan(agent.name)} (${agent.slug}) v${agent.version}`);
|
|
18609
|
+
}
|
|
18878
18610
|
console.log();
|
|
18879
|
-
|
|
18880
|
-
|
|
18881
|
-
|
|
18882
|
-
|
|
18883
|
-
if (!credentials && !apiKey) {
|
|
18884
|
-
spinner.fail("Not authenticated");
|
|
18611
|
+
console.log("Entity types:");
|
|
18612
|
+
for (const et of resources.entityTypes) {
|
|
18613
|
+
console.log(source_default.gray(" -"), source_default.cyan(et.name), `(${et.slug})`);
|
|
18614
|
+
}
|
|
18885
18615
|
console.log();
|
|
18886
|
-
console.log(
|
|
18887
|
-
|
|
18616
|
+
console.log("Roles:");
|
|
18617
|
+
for (const role of resources.roles) {
|
|
18618
|
+
console.log(source_default.gray(" -"), source_default.cyan(role.name));
|
|
18619
|
+
}
|
|
18888
18620
|
console.log();
|
|
18889
|
-
|
|
18621
|
+
return;
|
|
18890
18622
|
}
|
|
18891
|
-
spinner.start("Extracting agent configuration");
|
|
18892
|
-
const config = extractConfig(agent);
|
|
18893
|
-
spinner.succeed("Configuration extracted");
|
|
18894
18623
|
spinner.start("Syncing to development");
|
|
18895
18624
|
try {
|
|
18896
|
-
const
|
|
18625
|
+
const payload = extractSyncPayload(resources);
|
|
18626
|
+
const syncResult = await syncOrganization(payload);
|
|
18897
18627
|
if (!syncResult.success) {
|
|
18898
18628
|
throw new Error(syncResult.error || "Sync failed");
|
|
18899
18629
|
}
|
|
@@ -18903,32 +18633,40 @@ var deployCommand = new Command("deploy").description("Deploy agent to productio
|
|
|
18903
18633
|
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
18904
18634
|
process.exit(1);
|
|
18905
18635
|
}
|
|
18906
|
-
spinner.start(
|
|
18636
|
+
spinner.start("Deploying to production");
|
|
18907
18637
|
try {
|
|
18908
|
-
const deployResult = await
|
|
18638
|
+
const deployResult = await deployAllAgents();
|
|
18909
18639
|
if (!deployResult.success) {
|
|
18910
18640
|
throw new Error(deployResult.error || "Deployment failed");
|
|
18911
18641
|
}
|
|
18912
|
-
spinner.succeed(
|
|
18913
|
-
const prodUrl = `https://${project.agent.slug}.struere.dev`;
|
|
18642
|
+
spinner.succeed("Deployed to production");
|
|
18914
18643
|
console.log();
|
|
18915
|
-
console.log(source_default.green("Success!"), "
|
|
18644
|
+
console.log(source_default.green("Success!"), "All agents deployed");
|
|
18916
18645
|
console.log();
|
|
18917
|
-
|
|
18918
|
-
|
|
18919
|
-
|
|
18920
|
-
|
|
18646
|
+
if (deployResult.deployed && deployResult.deployed.length > 0) {
|
|
18647
|
+
console.log("Deployed agents:");
|
|
18648
|
+
for (const slug of deployResult.deployed) {
|
|
18649
|
+
const agent = resources.agents.find((a) => a.slug === slug);
|
|
18650
|
+
const prodUrl = `https://${slug}.struere.dev`;
|
|
18651
|
+
console.log(source_default.gray(" -"), source_default.cyan(agent?.name || slug), source_default.gray(`\u2192 ${prodUrl}`));
|
|
18652
|
+
}
|
|
18653
|
+
}
|
|
18654
|
+
if (deployResult.skipped && deployResult.skipped.length > 0) {
|
|
18655
|
+
console.log();
|
|
18656
|
+
console.log(source_default.yellow("Skipped (no development config):"));
|
|
18657
|
+
for (const slug of deployResult.skipped) {
|
|
18658
|
+
console.log(source_default.gray(" -"), slug);
|
|
18659
|
+
}
|
|
18660
|
+
}
|
|
18921
18661
|
console.log();
|
|
18922
|
-
console.log(source_default.gray("Test your
|
|
18923
|
-
console.log(source_default.gray(" $"), source_default.cyan(`curl -X POST
|
|
18662
|
+
console.log(source_default.gray("Test your agents:"));
|
|
18663
|
+
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"}'`));
|
|
18924
18664
|
console.log();
|
|
18925
18665
|
} catch (error) {
|
|
18926
18666
|
spinner.fail("Deployment failed");
|
|
18927
18667
|
console.log();
|
|
18928
18668
|
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
18929
18669
|
console.log();
|
|
18930
|
-
console.log(source_default.gray("Try running"), source_default.cyan("struere login"), source_default.gray("to re-authenticate"));
|
|
18931
|
-
console.log();
|
|
18932
18670
|
process.exit(1);
|
|
18933
18671
|
}
|
|
18934
18672
|
});
|
|
@@ -19144,10 +18882,225 @@ var whoamiCommand = new Command("whoami").description("Show current logged in us
|
|
|
19144
18882
|
console.log();
|
|
19145
18883
|
}
|
|
19146
18884
|
});
|
|
18885
|
+
|
|
18886
|
+
// src/cli/commands/add.ts
|
|
18887
|
+
var addCommand = new Command("add").description("Scaffold a new resource").argument("<type>", "Resource type: agent, entity-type, or role").argument("<name>", "Resource name").action(async (type, name) => {
|
|
18888
|
+
const cwd = process.cwd();
|
|
18889
|
+
console.log();
|
|
18890
|
+
if (!hasProject(cwd)) {
|
|
18891
|
+
console.log(source_default.yellow("No struere.json found - initializing project..."));
|
|
18892
|
+
console.log();
|
|
18893
|
+
const success = await runInit(cwd);
|
|
18894
|
+
if (!success) {
|
|
18895
|
+
process.exit(1);
|
|
18896
|
+
}
|
|
18897
|
+
console.log();
|
|
18898
|
+
}
|
|
18899
|
+
const version = getProjectVersion(cwd);
|
|
18900
|
+
if (version === "1.0") {
|
|
18901
|
+
console.log(source_default.yellow("This is a v1 agent-centric project."));
|
|
18902
|
+
console.log(source_default.yellow("The add command requires v2 structure."));
|
|
18903
|
+
console.log();
|
|
18904
|
+
process.exit(1);
|
|
18905
|
+
}
|
|
18906
|
+
const slug = slugify2(name);
|
|
18907
|
+
const displayName = name.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
18908
|
+
let result;
|
|
18909
|
+
switch (type.toLowerCase()) {
|
|
18910
|
+
case "agent":
|
|
18911
|
+
result = scaffoldAgent(cwd, displayName, slug);
|
|
18912
|
+
if (result.createdFiles.length > 0) {
|
|
18913
|
+
console.log(source_default.green("\u2713"), `Created agent "${displayName}"`);
|
|
18914
|
+
for (const file of result.createdFiles) {
|
|
18915
|
+
console.log(source_default.gray(" \u2192"), file);
|
|
18916
|
+
}
|
|
18917
|
+
} else {
|
|
18918
|
+
console.log(source_default.yellow("Agent already exists:"), `agents/${slug}.ts`);
|
|
18919
|
+
}
|
|
18920
|
+
break;
|
|
18921
|
+
case "entity-type":
|
|
18922
|
+
case "entitytype":
|
|
18923
|
+
case "type":
|
|
18924
|
+
result = scaffoldEntityType(cwd, displayName, slug);
|
|
18925
|
+
if (result.createdFiles.length > 0) {
|
|
18926
|
+
console.log(source_default.green("\u2713"), `Created entity type "${displayName}"`);
|
|
18927
|
+
for (const file of result.createdFiles) {
|
|
18928
|
+
console.log(source_default.gray(" \u2192"), file);
|
|
18929
|
+
}
|
|
18930
|
+
} else {
|
|
18931
|
+
console.log(source_default.yellow("Entity type already exists:"), `entity-types/${slug}.ts`);
|
|
18932
|
+
}
|
|
18933
|
+
break;
|
|
18934
|
+
case "role":
|
|
18935
|
+
result = scaffoldRole(cwd, slug);
|
|
18936
|
+
if (result.createdFiles.length > 0) {
|
|
18937
|
+
console.log(source_default.green("\u2713"), `Created role "${slug}"`);
|
|
18938
|
+
for (const file of result.createdFiles) {
|
|
18939
|
+
console.log(source_default.gray(" \u2192"), file);
|
|
18940
|
+
}
|
|
18941
|
+
} else {
|
|
18942
|
+
console.log(source_default.yellow("Role already exists:"), `roles/${slug}.ts`);
|
|
18943
|
+
}
|
|
18944
|
+
break;
|
|
18945
|
+
default:
|
|
18946
|
+
console.log(source_default.red("Unknown resource type:"), type);
|
|
18947
|
+
console.log();
|
|
18948
|
+
console.log("Available types:");
|
|
18949
|
+
console.log(source_default.gray(" -"), source_default.cyan("agent"), "- Create an AI agent");
|
|
18950
|
+
console.log(source_default.gray(" -"), source_default.cyan("entity-type"), "- Create an entity type schema");
|
|
18951
|
+
console.log(source_default.gray(" -"), source_default.cyan("role"), "- Create a role with permissions");
|
|
18952
|
+
console.log();
|
|
18953
|
+
process.exit(1);
|
|
18954
|
+
}
|
|
18955
|
+
console.log();
|
|
18956
|
+
console.log(source_default.gray("Run"), source_default.cyan("struere dev"), source_default.gray("to sync changes"));
|
|
18957
|
+
console.log();
|
|
18958
|
+
});
|
|
18959
|
+
function slugify2(name) {
|
|
18960
|
+
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
18961
|
+
}
|
|
18962
|
+
|
|
18963
|
+
// src/cli/commands/status.ts
|
|
18964
|
+
var statusCommand = new Command("status").description("Compare local vs remote state").action(async () => {
|
|
18965
|
+
const spinner = ora();
|
|
18966
|
+
const cwd = process.cwd();
|
|
18967
|
+
console.log();
|
|
18968
|
+
console.log(source_default.bold("Struere Status"));
|
|
18969
|
+
console.log();
|
|
18970
|
+
if (!hasProject(cwd)) {
|
|
18971
|
+
console.log(source_default.yellow("No struere.json found - initializing project..."));
|
|
18972
|
+
console.log();
|
|
18973
|
+
const success = await runInit(cwd);
|
|
18974
|
+
if (!success) {
|
|
18975
|
+
process.exit(1);
|
|
18976
|
+
}
|
|
18977
|
+
console.log();
|
|
18978
|
+
}
|
|
18979
|
+
const version = getProjectVersion(cwd);
|
|
18980
|
+
if (version === "1.0") {
|
|
18981
|
+
console.log(source_default.yellow("This is a v1 agent-centric project."));
|
|
18982
|
+
console.log(source_default.yellow("The status command requires v2 structure."));
|
|
18983
|
+
console.log();
|
|
18984
|
+
process.exit(1);
|
|
18985
|
+
}
|
|
18986
|
+
const project = loadProjectV2(cwd);
|
|
18987
|
+
if (!project) {
|
|
18988
|
+
console.log(source_default.red("Failed to load struere.json"));
|
|
18989
|
+
process.exit(1);
|
|
18990
|
+
}
|
|
18991
|
+
console.log(source_default.gray("Organization:"), source_default.cyan(project.organization.name));
|
|
18992
|
+
console.log();
|
|
18993
|
+
let credentials = loadCredentials();
|
|
18994
|
+
const apiKey = getApiKey();
|
|
18995
|
+
if (!credentials && !apiKey) {
|
|
18996
|
+
console.log(source_default.yellow("Not logged in - authenticating..."));
|
|
18997
|
+
console.log();
|
|
18998
|
+
credentials = await performLogin();
|
|
18999
|
+
if (!credentials) {
|
|
19000
|
+
console.log(source_default.red("Authentication failed"));
|
|
19001
|
+
process.exit(1);
|
|
19002
|
+
}
|
|
19003
|
+
console.log();
|
|
19004
|
+
}
|
|
19005
|
+
spinner.start("Loading local resources");
|
|
19006
|
+
let localResources;
|
|
19007
|
+
try {
|
|
19008
|
+
localResources = await loadAllResources(cwd);
|
|
19009
|
+
spinner.succeed("Local resources loaded");
|
|
19010
|
+
} catch (error) {
|
|
19011
|
+
spinner.fail("Failed to load local resources");
|
|
19012
|
+
console.log(source_default.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
19013
|
+
process.exit(1);
|
|
19014
|
+
}
|
|
19015
|
+
spinner.start("Fetching remote state");
|
|
19016
|
+
const { state: remoteState, error: fetchError } = await getSyncState();
|
|
19017
|
+
if (fetchError || !remoteState) {
|
|
19018
|
+
spinner.fail("Failed to fetch remote state");
|
|
19019
|
+
console.log(source_default.red("Error:"), fetchError || "Unknown error");
|
|
19020
|
+
process.exit(1);
|
|
19021
|
+
}
|
|
19022
|
+
spinner.succeed("Remote state fetched");
|
|
19023
|
+
console.log();
|
|
19024
|
+
const localAgentSlugs = new Set(localResources.agents.map((a) => a.slug));
|
|
19025
|
+
const remoteAgentSlugs = new Set(remoteState.agents.map((a) => a.slug));
|
|
19026
|
+
const localEntityTypeSlugs = new Set(localResources.entityTypes.map((et) => et.slug));
|
|
19027
|
+
const remoteEntityTypeSlugs = new Set(remoteState.entityTypes.map((et) => et.slug));
|
|
19028
|
+
const localRoleNames = new Set(localResources.roles.map((r) => r.name));
|
|
19029
|
+
const remoteRoleNames = new Set(remoteState.roles.map((r) => r.name));
|
|
19030
|
+
console.log(source_default.bold("Agents"));
|
|
19031
|
+
console.log(source_default.gray("\u2500".repeat(60)));
|
|
19032
|
+
if (localResources.agents.length === 0 && remoteState.agents.length === 0) {
|
|
19033
|
+
console.log(source_default.gray(" No agents"));
|
|
19034
|
+
} else {
|
|
19035
|
+
for (const agent of localResources.agents) {
|
|
19036
|
+
const remote = remoteState.agents.find((a) => a.slug === agent.slug);
|
|
19037
|
+
if (remote) {
|
|
19038
|
+
const statusIcon = remote.hasProdConfig ? source_default.green("\u25CF") : source_default.yellow("\u25CB");
|
|
19039
|
+
console.log(` ${statusIcon} ${source_default.cyan(agent.name)} (${agent.slug}) - v${agent.version}`);
|
|
19040
|
+
if (!remote.hasProdConfig) {
|
|
19041
|
+
console.log(source_default.gray(" Not deployed to production"));
|
|
19042
|
+
}
|
|
19043
|
+
} else {
|
|
19044
|
+
console.log(` ${source_default.blue("+")} ${source_default.cyan(agent.name)} (${agent.slug}) - ${source_default.blue("new")}`);
|
|
19045
|
+
}
|
|
19046
|
+
}
|
|
19047
|
+
for (const remote of remoteState.agents) {
|
|
19048
|
+
if (!localAgentSlugs.has(remote.slug)) {
|
|
19049
|
+
console.log(` ${source_default.red("-")} ${remote.name} (${remote.slug}) - ${source_default.red("will be deleted")}`);
|
|
19050
|
+
}
|
|
19051
|
+
}
|
|
19052
|
+
}
|
|
19053
|
+
console.log();
|
|
19054
|
+
console.log(source_default.bold("Entity Types"));
|
|
19055
|
+
console.log(source_default.gray("\u2500".repeat(60)));
|
|
19056
|
+
if (localResources.entityTypes.length === 0 && remoteState.entityTypes.length === 0) {
|
|
19057
|
+
console.log(source_default.gray(" No entity types"));
|
|
19058
|
+
} else {
|
|
19059
|
+
for (const et of localResources.entityTypes) {
|
|
19060
|
+
const remote = remoteState.entityTypes.find((r) => r.slug === et.slug);
|
|
19061
|
+
if (remote) {
|
|
19062
|
+
console.log(` ${source_default.green("\u25CF")} ${source_default.cyan(et.name)} (${et.slug})`);
|
|
19063
|
+
} else {
|
|
19064
|
+
console.log(` ${source_default.blue("+")} ${source_default.cyan(et.name)} (${et.slug}) - ${source_default.blue("new")}`);
|
|
19065
|
+
}
|
|
19066
|
+
}
|
|
19067
|
+
for (const remote of remoteState.entityTypes) {
|
|
19068
|
+
if (!localEntityTypeSlugs.has(remote.slug)) {
|
|
19069
|
+
console.log(` ${source_default.red("-")} ${remote.name} (${remote.slug}) - ${source_default.red("will be deleted")}`);
|
|
19070
|
+
}
|
|
19071
|
+
}
|
|
19072
|
+
}
|
|
19073
|
+
console.log();
|
|
19074
|
+
console.log(source_default.bold("Roles"));
|
|
19075
|
+
console.log(source_default.gray("\u2500".repeat(60)));
|
|
19076
|
+
if (localResources.roles.length === 0 && remoteState.roles.length === 0) {
|
|
19077
|
+
console.log(source_default.gray(" No roles"));
|
|
19078
|
+
} else {
|
|
19079
|
+
for (const role of localResources.roles) {
|
|
19080
|
+
const remote = remoteState.roles.find((r) => r.name === role.name);
|
|
19081
|
+
if (remote) {
|
|
19082
|
+
console.log(` ${source_default.green("\u25CF")} ${source_default.cyan(role.name)} (${role.policies.length} policies)`);
|
|
19083
|
+
} else {
|
|
19084
|
+
console.log(` ${source_default.blue("+")} ${source_default.cyan(role.name)} - ${source_default.blue("new")}`);
|
|
19085
|
+
}
|
|
19086
|
+
}
|
|
19087
|
+
for (const remote of remoteState.roles) {
|
|
19088
|
+
if (!localRoleNames.has(remote.name)) {
|
|
19089
|
+
console.log(` ${source_default.red("-")} ${remote.name} - ${source_default.red("will be deleted")}`);
|
|
19090
|
+
}
|
|
19091
|
+
}
|
|
19092
|
+
}
|
|
19093
|
+
console.log();
|
|
19094
|
+
console.log(source_default.gray("Legend:"));
|
|
19095
|
+
console.log(source_default.gray(" "), source_default.green("\u25CF"), "Synced", source_default.yellow("\u25CB"), "Not deployed", source_default.blue("+"), "New", source_default.red("-"), "Will be deleted");
|
|
19096
|
+
console.log();
|
|
19097
|
+
console.log(source_default.gray("Run"), source_default.cyan("struere dev"), source_default.gray("to sync changes"));
|
|
19098
|
+
console.log();
|
|
19099
|
+
});
|
|
19147
19100
|
// package.json
|
|
19148
19101
|
var package_default = {
|
|
19149
19102
|
name: "struere",
|
|
19150
|
-
version: "0.
|
|
19103
|
+
version: "0.4.1",
|
|
19151
19104
|
description: "Build, test, and deploy AI agents",
|
|
19152
19105
|
keywords: [
|
|
19153
19106
|
"ai",
|
|
@@ -19252,4 +19205,6 @@ program.addCommand(deployCommand);
|
|
|
19252
19205
|
program.addCommand(validateCommand);
|
|
19253
19206
|
program.addCommand(logsCommand);
|
|
19254
19207
|
program.addCommand(stateCommand);
|
|
19208
|
+
program.addCommand(addCommand);
|
|
19209
|
+
program.addCommand(statusCommand);
|
|
19255
19210
|
program.parse();
|