@symplek/cli 0.0.0-stage → 0.2.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/LICENSE +21 -0
- package/README.md +74 -2
- package/bin/symplek.js +5 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +44 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/add.d.ts +36 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +467 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/generate.d.ts +26 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +491 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/init.d.ts +46 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +350 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/migrate.d.ts +41 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +518 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/validate.d.ts +252 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +515 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/models.d.ts +24 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +39 -0
- package/dist/models.js.map +1 -0
- package/dist/naming.d.ts +20 -0
- package/dist/naming.d.ts.map +1 -0
- package/dist/naming.js +48 -0
- package/dist/naming.js.map +1 -0
- package/dist/templates/agent/agent.ts.template +41 -0
- package/dist/templates/agent/general.yaml.template +34 -0
- package/dist/templates/agent/researcher.yaml.template +42 -0
- package/dist/templates/agent/reviewer.yaml.template +51 -0
- package/dist/templates/project/full/.github/workflows/ci.yml.template +34 -0
- package/dist/templates/project/full/.gitignore.template +6 -0
- package/dist/templates/project/full/Dockerfile.template +43 -0
- package/dist/templates/project/full/README.md.template +77 -0
- package/dist/templates/project/full/agents/example.yaml.template +16 -0
- package/dist/templates/project/full/package.json.template +30 -0
- package/dist/templates/project/full/src/index.ts.template +43 -0
- package/dist/templates/project/full/src/symplek.config.ts.template +20 -0
- package/dist/templates/project/full/src/tools/index.ts.template +73 -0
- package/dist/templates/project/full/tests/example.test.ts.template +49 -0
- package/dist/templates/project/full/tsconfig.json.template +18 -0
- package/dist/templates/project/minimal/.gitignore.template +6 -0
- package/dist/templates/project/minimal/agents/example.yaml.template +16 -0
- package/dist/templates/project/minimal/package.json.template +27 -0
- package/dist/templates/project/minimal/src/index.ts.template +43 -0
- package/dist/templates/project/minimal/src/symplek.config.ts.template +20 -0
- package/dist/templates/project/minimal/tsconfig.json.template +18 -0
- package/dist/templates/project/standard/.gitignore.template +6 -0
- package/dist/templates/project/standard/README.md.template +77 -0
- package/dist/templates/project/standard/agents/example.yaml.template +16 -0
- package/dist/templates/project/standard/package.json.template +30 -0
- package/dist/templates/project/standard/src/index.ts.template +43 -0
- package/dist/templates/project/standard/src/symplek.config.ts.template +20 -0
- package/dist/templates/project/standard/src/tools/index.ts.template +73 -0
- package/dist/templates/project/standard/tests/example.test.ts.template +49 -0
- package/dist/templates/project/standard/tsconfig.json.template +18 -0
- package/dist/utils/templates.d.ts +41 -0
- package/dist/utils/templates.d.ts.map +1 -0
- package/dist/utils/templates.js +133 -0
- package/dist/utils/templates.js.map +1 -0
- package/package.json +60 -3
package/dist/models.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The one place the CLI knows about model ids.
|
|
4
|
+
*
|
|
5
|
+
* Providers add and retire models far more often than this CLI is released,
|
|
6
|
+
* so the CLI does not decide which models exist. Any well-formed id passes
|
|
7
|
+
* `validate` and `generate`; the provider is the authority on whether it is
|
|
8
|
+
* live. The list below is only what `symplek add agent` offers in its picker.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.MODEL_ID_HINT = exports.MODEL_ID_PATTERN = exports.DEFAULT_MODEL = exports.SUGGESTED_MODELS = void 0;
|
|
12
|
+
exports.isModelId = isModelId;
|
|
13
|
+
/** Offered by the interactive picker. Mirrors @symplek/core's default model table. */
|
|
14
|
+
exports.SUGGESTED_MODELS = [
|
|
15
|
+
"gpt-4o",
|
|
16
|
+
"gpt-4o-mini",
|
|
17
|
+
"claude-opus-5-5",
|
|
18
|
+
"claude-sonnet-5-5",
|
|
19
|
+
"claude-haiku-5-5",
|
|
20
|
+
"gemini-3.8-flash",
|
|
21
|
+
"gemini-3.5-flash-lite",
|
|
22
|
+
];
|
|
23
|
+
/** Used when the caller names no model. */
|
|
24
|
+
exports.DEFAULT_MODEL = exports.SUGGESTED_MODELS[0];
|
|
25
|
+
/**
|
|
26
|
+
* The shape of a model id: letters, digits, and `. _ : / -`, starting with a
|
|
27
|
+
* letter or digit. Wide enough for dated snapshots, fine-tune ids and
|
|
28
|
+
* `vendor/model` routes; narrow enough that an id can be written into YAML or
|
|
29
|
+
* TypeScript without quoting hazards (no whitespace, quotes or newlines).
|
|
30
|
+
*/
|
|
31
|
+
exports.MODEL_ID_PATTERN = "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$";
|
|
32
|
+
const MODEL_ID_REGEX = new RegExp(exports.MODEL_ID_PATTERN);
|
|
33
|
+
/** True when `value` is a well-formed model id. */
|
|
34
|
+
function isModelId(value) {
|
|
35
|
+
return typeof value === "string" && MODEL_ID_REGEX.test(value);
|
|
36
|
+
}
|
|
37
|
+
/** One-line hint shown when a model id is missing or malformed. */
|
|
38
|
+
exports.MODEL_ID_HINT = `Use a provider model id, for example: ${exports.SUGGESTED_MODELS.join(", ")}`;
|
|
39
|
+
//# sourceMappingURL=models.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models.js","sourceRoot":"","sources":["../src/models.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AA2BH,8BAEC;AA3BD,sFAAsF;AACzE,QAAA,gBAAgB,GAAG;IAC9B,QAAQ;IACR,aAAa;IACb,iBAAiB;IACjB,mBAAmB;IACnB,kBAAkB;IAClB,kBAAkB;IAClB,uBAAuB;CACf,CAAC;AAEX,2CAA2C;AAC9B,QAAA,aAAa,GAAW,wBAAgB,CAAC,CAAC,CAAC,CAAC;AAEzD;;;;;GAKG;AACU,QAAA,gBAAgB,GAAG,sCAAsC,CAAC;AAEvE,MAAM,cAAc,GAAG,IAAI,MAAM,CAAC,wBAAgB,CAAC,CAAC;AAEpD,mDAAmD;AACnD,SAAgB,SAAS,CAAC,KAAc;IACtC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACjE,CAAC;AAED,mEAAmE;AACtD,QAAA,aAAa,GAAG,yCAAyC,wBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC"}
|
package/dist/naming.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place the CLI turns an agent's name into a JavaScript identifier.
|
|
3
|
+
*
|
|
4
|
+
* An agent is named in kebab-case (`code-reviewer`) and exported from its
|
|
5
|
+
* generated file in camelCase (`codeReviewer`). `add`, `validate` and
|
|
6
|
+
* `generate` all use the rules here, so a name one of them accepts is a name
|
|
7
|
+
* the others can turn into a file that parses.
|
|
8
|
+
*/
|
|
9
|
+
/** Lowercase letters and digits in hyphen-separated segments, starting with a letter. */
|
|
10
|
+
export declare const KEBAB_CASE_REGEX: RegExp;
|
|
11
|
+
/**
|
|
12
|
+
* `code-reviewer` to `codeReviewer`. A segment may start with a digit
|
|
13
|
+
* (`agent-2` to `agent2`), so the hyphen is dropped before digits too.
|
|
14
|
+
*/
|
|
15
|
+
export declare function kebabToCamel(name: string): string;
|
|
16
|
+
/** True when `word` cannot be used as an exported function's name. */
|
|
17
|
+
export declare function isReservedWord(word: string): boolean;
|
|
18
|
+
/** Why `name` cannot be an agent name, or undefined when it can. */
|
|
19
|
+
export declare function agentNameProblem(name: string): string | undefined;
|
|
20
|
+
//# sourceMappingURL=naming.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../src/naming.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,yFAAyF;AACzF,eAAO,MAAM,gBAAgB,QAAkC,CAAC;AAahE;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,oEAAoE;AACpE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQjE"}
|
package/dist/naming.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The one place the CLI turns an agent's name into a JavaScript identifier.
|
|
4
|
+
*
|
|
5
|
+
* An agent is named in kebab-case (`code-reviewer`) and exported from its
|
|
6
|
+
* generated file in camelCase (`codeReviewer`). `add`, `validate` and
|
|
7
|
+
* `generate` all use the rules here, so a name one of them accepts is a name
|
|
8
|
+
* the others can turn into a file that parses.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.KEBAB_CASE_REGEX = void 0;
|
|
12
|
+
exports.kebabToCamel = kebabToCamel;
|
|
13
|
+
exports.isReservedWord = isReservedWord;
|
|
14
|
+
exports.agentNameProblem = agentNameProblem;
|
|
15
|
+
/** Lowercase letters and digits in hyphen-separated segments, starting with a letter. */
|
|
16
|
+
exports.KEBAB_CASE_REGEX = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
17
|
+
/** Words that cannot be the name of an exported function. */
|
|
18
|
+
const RESERVED_WORDS = new Set([
|
|
19
|
+
"arguments", "await", "break", "case", "catch", "class", "const", "continue",
|
|
20
|
+
"debugger", "default", "delete", "do", "else", "enum", "eval", "export",
|
|
21
|
+
"extends", "false", "finally", "for", "function", "if", "implements",
|
|
22
|
+
"import", "in", "instanceof", "interface", "let", "new", "null", "package",
|
|
23
|
+
"private", "protected", "public", "return", "static", "super", "switch",
|
|
24
|
+
"this", "throw", "true", "try", "typeof", "var", "void", "while", "with",
|
|
25
|
+
"yield",
|
|
26
|
+
]);
|
|
27
|
+
/**
|
|
28
|
+
* `code-reviewer` to `codeReviewer`. A segment may start with a digit
|
|
29
|
+
* (`agent-2` to `agent2`), so the hyphen is dropped before digits too.
|
|
30
|
+
*/
|
|
31
|
+
function kebabToCamel(name) {
|
|
32
|
+
return name.replace(/-([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
33
|
+
}
|
|
34
|
+
/** True when `word` cannot be used as an exported function's name. */
|
|
35
|
+
function isReservedWord(word) {
|
|
36
|
+
return RESERVED_WORDS.has(word);
|
|
37
|
+
}
|
|
38
|
+
/** Why `name` cannot be an agent name, or undefined when it can. */
|
|
39
|
+
function agentNameProblem(name) {
|
|
40
|
+
if (!exports.KEBAB_CASE_REGEX.test(name)) {
|
|
41
|
+
return "Agent name must be kebab-case: lowercase letters, numbers and hyphens, starting with a letter (e.g. my-agent)";
|
|
42
|
+
}
|
|
43
|
+
if (isReservedWord(kebabToCamel(name))) {
|
|
44
|
+
return `"${name}" is a JavaScript reserved word, so it cannot name the exported agent function. Pick another name (e.g. ${name}-agent)`;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=naming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"naming.js","sourceRoot":"","sources":["../src/naming.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAoBH,oCAEC;AAGD,wCAEC;AAGD,4CAQC;AApCD,yFAAyF;AAC5E,QAAA,gBAAgB,GAAG,+BAA+B,CAAC;AAEhE,6DAA6D;AAC7D,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU;IAC5E,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ;IACvE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY;IACpE,QAAQ,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS;IAC1E,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ;IACvE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IACxE,OAAO;CACR,CAAC,CAAC;AAEH;;;GAGG;AACH,SAAgB,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;AAC/E,CAAC;AAED,sEAAsE;AACtE,SAAgB,cAAc,CAAC,IAAY;IACzC,OAAO,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC;AAED,oEAAoE;AACpE,SAAgB,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC,wBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,OAAO,+GAA+G,CAAC;IACzH,CAAC;IACD,IAAI,cAAc,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACvC,OAAO,IAAI,IAAI,2GAA2G,IAAI,SAAS,CAAC;IAC1I,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{name}} Agent
|
|
3
|
+
* TypeScript agent definition
|
|
4
|
+
* Created with: symplek add agent {{name}}
|
|
5
|
+
*
|
|
6
|
+
* Before using this agent, set a provider key in the environment (for
|
|
7
|
+
* example OPENAI_API_KEY), or call
|
|
8
|
+
* Symplek.configure({ provider, apiKey }) from @symplek/core/server.
|
|
9
|
+
*/
|
|
10
|
+
import { AgentUnit, createContext } from "@symplek/core";
|
|
11
|
+
import { ai } from "@symplek/core/server";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Run the {{name}} agent with a prompt.
|
|
15
|
+
* Returns an AgentUnit<string> — chain operators (.retry, .timeout, …) before calling .run().
|
|
16
|
+
*/
|
|
17
|
+
export function {{agentName}}(prompt: string): AgentUnit<string> {
|
|
18
|
+
return AgentUnit.from((_ctx) =>
|
|
19
|
+
ai(prompt, {
|
|
20
|
+
model: "{{model}}",
|
|
21
|
+
system: `You are {{name}}, a helpful AI assistant.
|
|
22
|
+
|
|
23
|
+
Your responsibilities:
|
|
24
|
+
- Answer questions clearly and accurately
|
|
25
|
+
- Provide helpful explanations
|
|
26
|
+
|
|
27
|
+
Be concise, accurate, and friendly.`,
|
|
28
|
+
}),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type AgentResult = Awaited<ReturnType<ReturnType<typeof {{agentName}}>["run"]>>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Convenience helper: run the agent and return its string value.
|
|
36
|
+
*/
|
|
37
|
+
export async function run(prompt: string): Promise<string> {
|
|
38
|
+
const ctx = createContext();
|
|
39
|
+
const result = await {{agentName}}(prompt).run(ctx);
|
|
40
|
+
return result.value as string;
|
|
41
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# General conversational agent
|
|
2
|
+
# Created with: symplek add agent {{name}} --type general
|
|
3
|
+
|
|
4
|
+
name: {{name}}
|
|
5
|
+
version: "1.0.0"
|
|
6
|
+
|
|
7
|
+
model: "{{model}}"
|
|
8
|
+
|
|
9
|
+
systemPrompt: |
|
|
10
|
+
You are {{name}}, a helpful AI assistant.
|
|
11
|
+
|
|
12
|
+
Your responsibilities:
|
|
13
|
+
- Answer questions clearly and accurately
|
|
14
|
+
- Provide helpful explanations
|
|
15
|
+
- Admit when you don't know something
|
|
16
|
+
- Be friendly and professional
|
|
17
|
+
|
|
18
|
+
Guidelines:
|
|
19
|
+
- Be concise but thorough
|
|
20
|
+
- Ask clarifying questions when needed
|
|
21
|
+
- Provide examples when helpful
|
|
22
|
+
|
|
23
|
+
temperature: 0.7
|
|
24
|
+
maxTokens: 2048
|
|
25
|
+
|
|
26
|
+
# Optional: enable tools when you have defined them in your project.
|
|
27
|
+
# tools:
|
|
28
|
+
# - search
|
|
29
|
+
# - calculator
|
|
30
|
+
|
|
31
|
+
# Optional: episodic memory — store recent turns for context.
|
|
32
|
+
# memory:
|
|
33
|
+
# episodic: true
|
|
34
|
+
# maxEpisodes: 10
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Research-focused agent
|
|
2
|
+
# Created with: symplek add agent {{name}} --type researcher
|
|
3
|
+
|
|
4
|
+
name: {{name}}
|
|
5
|
+
version: "1.0.0"
|
|
6
|
+
|
|
7
|
+
model: "{{model}}"
|
|
8
|
+
|
|
9
|
+
systemPrompt: |
|
|
10
|
+
You are {{name}}, an expert research assistant.
|
|
11
|
+
|
|
12
|
+
Your responsibilities:
|
|
13
|
+
- Conduct thorough research on given topics
|
|
14
|
+
- Synthesize information from multiple sources
|
|
15
|
+
- Provide well-structured summaries
|
|
16
|
+
- Cite sources when available
|
|
17
|
+
- Identify knowledge gaps
|
|
18
|
+
|
|
19
|
+
Research methodology:
|
|
20
|
+
1. Understand the research question
|
|
21
|
+
2. Break down into sub-questions
|
|
22
|
+
3. Gather relevant information
|
|
23
|
+
4. Analyze and synthesize findings
|
|
24
|
+
5. Present conclusions with evidence
|
|
25
|
+
|
|
26
|
+
Output format:
|
|
27
|
+
- Start with key findings
|
|
28
|
+
- Provide supporting details
|
|
29
|
+
- Note limitations and uncertainties
|
|
30
|
+
- Suggest areas for further research
|
|
31
|
+
|
|
32
|
+
temperature: 0.5
|
|
33
|
+
maxTokens: 4096
|
|
34
|
+
|
|
35
|
+
tools:
|
|
36
|
+
- search
|
|
37
|
+
- fetch_url
|
|
38
|
+
|
|
39
|
+
# Optional: episodic memory to track research threads across turns.
|
|
40
|
+
# memory:
|
|
41
|
+
# episodic: true
|
|
42
|
+
# maxEpisodes: 20
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Code review agent
|
|
2
|
+
# Created with: symplek add agent {{name}} --type reviewer
|
|
3
|
+
|
|
4
|
+
name: {{name}}
|
|
5
|
+
version: "1.0.0"
|
|
6
|
+
|
|
7
|
+
model: "{{model}}"
|
|
8
|
+
|
|
9
|
+
systemPrompt: |
|
|
10
|
+
You are {{name}}, an expert code reviewer.
|
|
11
|
+
|
|
12
|
+
Your responsibilities:
|
|
13
|
+
- Review code for correctness, style, and best practices
|
|
14
|
+
- Identify bugs, security issues, and performance problems
|
|
15
|
+
- Suggest improvements and alternatives
|
|
16
|
+
- Explain reasoning behind suggestions
|
|
17
|
+
- Be constructive and educational
|
|
18
|
+
|
|
19
|
+
Review priorities (in order):
|
|
20
|
+
1. Security vulnerabilities
|
|
21
|
+
2. Correctness bugs
|
|
22
|
+
3. Performance issues
|
|
23
|
+
4. Code style and readability
|
|
24
|
+
5. Best practices and patterns
|
|
25
|
+
6. Documentation
|
|
26
|
+
|
|
27
|
+
Guidelines:
|
|
28
|
+
- Be specific with line numbers and code examples
|
|
29
|
+
- Explain WHY something is an issue
|
|
30
|
+
- Provide concrete fix suggestions
|
|
31
|
+
- Acknowledge good code when you see it
|
|
32
|
+
- Consider context and trade-offs
|
|
33
|
+
|
|
34
|
+
Severity levels:
|
|
35
|
+
- CRITICAL: Security vulnerabilities, data loss risks
|
|
36
|
+
- HIGH: Bugs that will cause failures
|
|
37
|
+
- MEDIUM: Performance issues, poor patterns
|
|
38
|
+
- LOW: Style issues, minor improvements
|
|
39
|
+
- SUGGESTION: Nice-to-have improvements
|
|
40
|
+
|
|
41
|
+
temperature: 0.3
|
|
42
|
+
maxTokens: 4096
|
|
43
|
+
|
|
44
|
+
tools:
|
|
45
|
+
- read_file
|
|
46
|
+
- search_code
|
|
47
|
+
|
|
48
|
+
# Optional: episodic memory to track ongoing review context.
|
|
49
|
+
# memory:
|
|
50
|
+
# episodic: true
|
|
51
|
+
# maxEpisodes: 15
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- uses: pnpm/action-setup@v4
|
|
16
|
+
with:
|
|
17
|
+
version: 9
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-node@v4
|
|
20
|
+
with:
|
|
21
|
+
node-version: "20"
|
|
22
|
+
cache: pnpm
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: pnpm install --frozen-lockfile
|
|
26
|
+
|
|
27
|
+
- name: Typecheck
|
|
28
|
+
run: pnpm typecheck
|
|
29
|
+
|
|
30
|
+
- name: Test
|
|
31
|
+
run: pnpm test
|
|
32
|
+
|
|
33
|
+
- name: Build
|
|
34
|
+
run: pnpm build
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# {{name}} — Symplek agent runtime image.
|
|
2
|
+
#
|
|
3
|
+
# Multi-stage build:
|
|
4
|
+
# 1. Install all deps + compile TypeScript to dist/.
|
|
5
|
+
# 2. Ship a slim runtime image with only production deps + dist/.
|
|
6
|
+
#
|
|
7
|
+
# Build: docker build -t {{name}} .
|
|
8
|
+
# Run: docker run --rm -e OPENAI_API_KEY=$OPENAI_API_KEY {{name}}
|
|
9
|
+
|
|
10
|
+
# ─── Stage 1: build ──────────────────────────────────────────────────────────
|
|
11
|
+
FROM node:20-alpine AS build
|
|
12
|
+
WORKDIR /app
|
|
13
|
+
RUN corepack enable
|
|
14
|
+
|
|
15
|
+
# Copy lockfile if present (won't fail if it's missing).
|
|
16
|
+
COPY package.json ./
|
|
17
|
+
COPY pnpm-lock.yaml* ./
|
|
18
|
+
|
|
19
|
+
RUN pnpm install
|
|
20
|
+
|
|
21
|
+
COPY tsconfig.json ./
|
|
22
|
+
COPY src ./src
|
|
23
|
+
COPY agents ./agents
|
|
24
|
+
|
|
25
|
+
RUN pnpm build
|
|
26
|
+
|
|
27
|
+
# ─── Stage 2: runtime ────────────────────────────────────────────────────────
|
|
28
|
+
FROM node:20-alpine
|
|
29
|
+
WORKDIR /app
|
|
30
|
+
RUN corepack enable && adduser -D -u 1001 symplek
|
|
31
|
+
|
|
32
|
+
COPY package.json ./
|
|
33
|
+
COPY pnpm-lock.yaml* ./
|
|
34
|
+
|
|
35
|
+
RUN pnpm install --prod
|
|
36
|
+
|
|
37
|
+
COPY --from=build /app/dist ./dist
|
|
38
|
+
COPY --from=build /app/agents ./agents
|
|
39
|
+
|
|
40
|
+
USER symplek
|
|
41
|
+
ENV NODE_ENV=production
|
|
42
|
+
|
|
43
|
+
CMD ["node", "dist/src/index.js"]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
{{description}}
|
|
4
|
+
|
|
5
|
+
Built with [Symplek](https://github.com/symplek/symplek) — composable AI agents for TypeScript.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Setup
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm install # or npm install / yarn / bun
|
|
13
|
+
export OPENAI_API_KEY=sk-... # provider env var from src/symplek.config.ts
|
|
14
|
+
pnpm dev # watch mode
|
|
15
|
+
pnpm start # one-shot
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
To switch providers, edit `src/symplek.config.ts` and install the matching SDK
|
|
19
|
+
(`pnpm add @anthropic-ai/sdk` for Anthropic, etc.). Symplek auto-detects which
|
|
20
|
+
provider is configured at runtime.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Project layout
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
{{name}}/
|
|
28
|
+
├── src/
|
|
29
|
+
│ ├── index.ts # entry — runs the example agent
|
|
30
|
+
│ ├── symplek.config.ts # Symplek runtime configuration (provider, API key)
|
|
31
|
+
│ └── tools/index.ts # composable helpers (resilient, withProvenance)
|
|
32
|
+
├── agents/
|
|
33
|
+
│ └── {{agentName}}.yaml # declarative agent — `symplek generate` compiles it
|
|
34
|
+
└── tests/
|
|
35
|
+
└── example.test.ts # vitest unit tests
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## What's where
|
|
41
|
+
|
|
42
|
+
- **`src/symplek.config.ts`** — registers your AI provider. Edit here to swap
|
|
43
|
+
OpenAI for Anthropic, change the default model, set a base URL, etc.
|
|
44
|
+
- **`src/tools/index.ts`** — your project's local helpers. Compose `AgentUnit`
|
|
45
|
+
operators into project-specific patterns here so the rest of your code stays
|
|
46
|
+
readable.
|
|
47
|
+
- **`agents/`** — YAML agent definitions. Run `symplek generate` to compile them
|
|
48
|
+
into typed TypeScript wrappers next to them, as `agents/<name>.ts`.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Scripts
|
|
53
|
+
|
|
54
|
+
| Command | What it does |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `pnpm dev` | Run the entry in watch mode via `tsx` |
|
|
57
|
+
| `pnpm start` | Run once |
|
|
58
|
+
| `pnpm build` | Compile TypeScript to `dist/` |
|
|
59
|
+
| `pnpm typecheck` | TypeScript check without emit |
|
|
60
|
+
| `pnpm test` | Run vitest once |
|
|
61
|
+
| `pnpm test:watch` | Run vitest in watch mode |
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Next steps
|
|
66
|
+
|
|
67
|
+
- Replace the example in `src/index.ts` with your real agent.
|
|
68
|
+
- Edit `agents/{{agentName}}.yaml`, then run `symplek generate` to add typed wrappers.
|
|
69
|
+
- Add more agents in `agents/`; the CLI handles wiring.
|
|
70
|
+
- See [github.com/symplek/symplek](https://github.com/symplek/symplek) for the operator
|
|
71
|
+
reference (consensus, debate, dialectic, rehearse, replay, supervised, etc.).
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
ISC
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Symplek agent definition — declarative form.
|
|
2
|
+
#
|
|
3
|
+
# Compile to TypeScript with:
|
|
4
|
+
# pnpm exec symplek generate
|
|
5
|
+
#
|
|
6
|
+
# This produces `agents/{{agentName}}.ts` that you can import and call
|
|
7
|
+
# from your application code. YAML lets non-engineers tune prompts without
|
|
8
|
+
# touching the source.
|
|
9
|
+
|
|
10
|
+
name: {{agentName}}
|
|
11
|
+
description: Summarises text in one clear sentence.
|
|
12
|
+
model: "{{model}}"
|
|
13
|
+
|
|
14
|
+
systemPrompt: |
|
|
15
|
+
You summarise the user's input in exactly one clear sentence.
|
|
16
|
+
Never embellish. Never editorialise. Faithful to the input only.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "{{version}}",
|
|
4
|
+
"description": "{{description}}",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"private": true,
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "tsx watch src/index.ts",
|
|
9
|
+
"start": "tsx src/index.ts",
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:watch": "vitest"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@symplek/core": "^1.0.0-beta.1",
|
|
17
|
+
"zod": "^3.25.76",
|
|
18
|
+
"openai": "^4.104.0"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@symplek/cli": "^{{cliVersion}}",
|
|
22
|
+
"@types/node": "^22.14.0",
|
|
23
|
+
"tsx": "^4.19.0",
|
|
24
|
+
"typescript": "^5.9.0",
|
|
25
|
+
"vitest": "^2.1.0"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20.19"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import "./symplek.config.js";
|
|
2
|
+
import { createContext } from "@symplek/core";
|
|
3
|
+
import { Symplek } from "@symplek/core/server";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* {{name}} — a minimal Symplek agent.
|
|
7
|
+
*
|
|
8
|
+
* Demonstrates the core pattern:
|
|
9
|
+
* 1. `Symplek(prompt)` builds an AI call.
|
|
10
|
+
* 2. `.coordinate()` lifts it into the AgentUnit<T> algebra.
|
|
11
|
+
* 3. Layer safety operators on top (retry, timeout, fallback, …).
|
|
12
|
+
* 4. `.run(ctx)` executes and returns a TrackedResult.
|
|
13
|
+
*
|
|
14
|
+
* Scripts:
|
|
15
|
+
* pnpm dev — watch mode (re-runs on file change)
|
|
16
|
+
* pnpm start — one-shot
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
if (!process.env.OPENAI_API_KEY) {
|
|
20
|
+
console.error("[{{name}}] OPENAI_API_KEY is not set.");
|
|
21
|
+
console.error("Set it and re-run: export OPENAI_API_KEY=sk-...");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function main(): Promise<void> {
|
|
26
|
+
const ctx = createContext();
|
|
27
|
+
const input = "The quick brown fox jumps over the lazy dog.";
|
|
28
|
+
|
|
29
|
+
const result = await Symplek(`Summarise in one sentence: ${input}`)
|
|
30
|
+
.coordinate()
|
|
31
|
+
.retry(2)
|
|
32
|
+
.timeout(15_000)
|
|
33
|
+
.run(ctx);
|
|
34
|
+
|
|
35
|
+
console.log("Input: ", input);
|
|
36
|
+
console.log("Summary: ", result.value);
|
|
37
|
+
console.log("Confidence: ", result.confidence);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
main().catch((err: unknown) => {
|
|
41
|
+
console.error("[{{name}}] agent failed:", err);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symplek runtime configuration for {{name}}.
|
|
3
|
+
*
|
|
4
|
+
* Imported for side effect by `src/index.ts` before any agent runs.
|
|
5
|
+
* Set OPENAI_API_KEY in your environment:
|
|
6
|
+
*
|
|
7
|
+
* export OPENAI_API_KEY=sk-...
|
|
8
|
+
*
|
|
9
|
+
* To swap providers, change `provider` and the matching env var. Symplek
|
|
10
|
+
* supports `openai`, `anthropic`, and `google` out of the box — install
|
|
11
|
+
* the corresponding SDK (`pnpm add @anthropic-ai/sdk` etc.) and update
|
|
12
|
+
* this file.
|
|
13
|
+
*/
|
|
14
|
+
import { Symplek } from "@symplek/core/server";
|
|
15
|
+
|
|
16
|
+
Symplek.configure({
|
|
17
|
+
provider: "openai",
|
|
18
|
+
apiKey: process.env.OPENAI_API_KEY ?? "",
|
|
19
|
+
model: "{{model}}",
|
|
20
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project-local helpers for {{name}}.
|
|
3
|
+
*
|
|
4
|
+
* Compose Symplek operators into patterns specific to your project here so
|
|
5
|
+
* your business code stays declarative. Each helper is one operator chain.
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
type AgentUnit,
|
|
9
|
+
isTracked,
|
|
10
|
+
type ResultSource,
|
|
11
|
+
type TrackedResult,
|
|
12
|
+
} from "@symplek/core";
|
|
13
|
+
|
|
14
|
+
export interface ProvenanceOptions {
|
|
15
|
+
/** Source category — one of Symplek's `ResultSource` literals. */
|
|
16
|
+
source: ResultSource;
|
|
17
|
+
/** Caller's confidence in this value, in [0, 1]. */
|
|
18
|
+
confidence: number;
|
|
19
|
+
/** Optional identifier of the verifier (audit trail). */
|
|
20
|
+
verifiedBy?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Wrap an `AgentUnit<T>` so its output carries provenance metadata.
|
|
25
|
+
*
|
|
26
|
+
* Downstream callers can introspect `result.value`, `result.source`,
|
|
27
|
+
* `result.confidence` — Symplek's TrackedResult shape — without any extra
|
|
28
|
+
* plumbing.
|
|
29
|
+
*/
|
|
30
|
+
export function withProvenance<T>(
|
|
31
|
+
unit: AgentUnit<T>,
|
|
32
|
+
opts: ProvenanceOptions,
|
|
33
|
+
): AgentUnit<TrackedResult<T>> {
|
|
34
|
+
return unit.tracked({
|
|
35
|
+
source: opts.source,
|
|
36
|
+
confidence: opts.confidence,
|
|
37
|
+
verifiedBy: opts.verifiedBy,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ResilienceOptions<T> {
|
|
42
|
+
/** Max attempts including the first. Default 3. */
|
|
43
|
+
maxAttempts?: number;
|
|
44
|
+
/** Hard timeout in milliseconds. Default 15s. */
|
|
45
|
+
timeoutMs?: number;
|
|
46
|
+
/** Static fallback value if all attempts fail. */
|
|
47
|
+
fallback?: T;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Apply the standard resilience triad: retry on transient failure, hard
|
|
52
|
+
* timeout, fallback to a known-safe value if all else fails.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* const safe = resilient(
|
|
56
|
+
* Symplek(prompt).coordinate(),
|
|
57
|
+
* { maxAttempts: 3, timeoutMs: 10_000, fallback: "" },
|
|
58
|
+
* );
|
|
59
|
+
*/
|
|
60
|
+
export function resilient<T>(
|
|
61
|
+
unit: AgentUnit<T>,
|
|
62
|
+
options: ResilienceOptions<T> = {},
|
|
63
|
+
): AgentUnit<T> {
|
|
64
|
+
const { maxAttempts = 3, timeoutMs = 15_000, fallback } = options;
|
|
65
|
+
let chain = unit.retry(maxAttempts).timeout(timeoutMs);
|
|
66
|
+
if (fallback !== undefined) {
|
|
67
|
+
chain = chain.orElse(fallback);
|
|
68
|
+
}
|
|
69
|
+
return chain;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Re-export Symplek's tracked-result type guard for convenience. */
|
|
73
|
+
export { isTracked };
|