terse-cli 0.1.16 → 0.1.17
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/assertProjectRoot.d.ts +3 -2
- package/dist/assertProjectRoot.d.ts.map +1 -1
- package/dist/assertProjectRoot.js +5 -8
- package/dist/assertProjectRoot.js.map +1 -1
- package/dist/deploy.d.ts +2 -1
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +11 -12
- package/dist/deploy.js.map +1 -1
- package/dist/generate.d.ts +2 -1
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +12 -11
- package/dist/generate.js.map +1 -1
- package/dist/index.js +10 -7
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +2 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +29 -57
- package/dist/init.js.map +1 -1
- package/dist/loadJob.d.ts +4 -3
- package/dist/loadJob.d.ts.map +1 -1
- package/dist/loadJob.js +6 -54
- package/dist/loadJob.js.map +1 -1
- package/dist/providers/LanguageProvider.d.ts +31 -0
- package/dist/providers/LanguageProvider.d.ts.map +1 -0
- package/dist/providers/LanguageProvider.js +2 -0
- package/dist/providers/LanguageProvider.js.map +1 -0
- package/dist/providers/codegenTypes.d.ts +130 -0
- package/dist/providers/codegenTypes.d.ts.map +1 -0
- package/dist/providers/codegenTypes.js +2 -0
- package/dist/providers/codegenTypes.js.map +1 -0
- package/dist/providers/python/PythonProvider.d.ts +33 -0
- package/dist/providers/python/PythonProvider.d.ts.map +1 -0
- package/dist/providers/python/PythonProvider.js +369 -0
- package/dist/providers/python/PythonProvider.js.map +1 -0
- package/dist/providers/python/preparePythonCodegenData.d.ts +73 -0
- package/dist/providers/python/preparePythonCodegenData.d.ts.map +1 -0
- package/dist/providers/python/preparePythonCodegenData.js +338 -0
- package/dist/providers/python/preparePythonCodegenData.js.map +1 -0
- package/dist/providers/python/pythonTemplateEngine.d.ts +3 -0
- package/dist/providers/python/pythonTemplateEngine.d.ts.map +1 -0
- package/dist/providers/python/pythonTemplateEngine.js +30 -0
- package/dist/providers/python/pythonTemplateEngine.js.map +1 -0
- package/dist/providers/resolveProvider.d.ts +8 -0
- package/dist/providers/resolveProvider.d.ts.map +1 -0
- package/dist/providers/resolveProvider.js +47 -0
- package/dist/providers/resolveProvider.js.map +1 -0
- package/dist/providers/shared/sessionStream.d.ts +24 -0
- package/dist/providers/shared/sessionStream.d.ts.map +1 -0
- package/dist/providers/shared/sessionStream.js +159 -0
- package/dist/providers/shared/sessionStream.js.map +1 -0
- package/dist/providers/shared/shellUtils.d.ts +13 -0
- package/dist/providers/shared/shellUtils.d.ts.map +1 -0
- package/dist/providers/shared/shellUtils.js +70 -0
- package/dist/providers/shared/shellUtils.js.map +1 -0
- package/dist/providers/templateUtils.d.ts +7 -0
- package/dist/providers/templateUtils.d.ts.map +1 -0
- package/dist/providers/templateUtils.js +30 -0
- package/dist/providers/templateUtils.js.map +1 -0
- package/dist/providers/typescript/TypeScriptProvider.d.ts +33 -0
- package/dist/providers/typescript/TypeScriptProvider.d.ts.map +1 -0
- package/dist/providers/typescript/TypeScriptProvider.js +162 -0
- package/dist/providers/typescript/TypeScriptProvider.js.map +1 -0
- package/dist/providers/typescript/prepareCodegenData.d.ts +145 -0
- package/dist/providers/typescript/prepareCodegenData.d.ts.map +1 -0
- package/dist/providers/typescript/prepareCodegenData.js +834 -0
- package/dist/providers/typescript/prepareCodegenData.js.map +1 -0
- package/dist/providers/typescript/templateEngine.d.ts +3 -0
- package/dist/providers/typescript/templateEngine.d.ts.map +1 -0
- package/dist/providers/typescript/templateEngine.js +37 -0
- package/dist/providers/typescript/templateEngine.js.map +1 -0
- package/dist/run.d.ts +2 -11
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +5 -192
- package/dist/run.js.map +1 -1
- package/dist/shared/types.d.ts +899 -107
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/test.d.ts +2 -1
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +5 -5
- package/dist/test.js.map +1 -1
- package/package.json +2 -2
- package/templates/python/codegen/generated.hbs +6 -0
- package/templates/python/codegen/partials/attio-list-objects-method.hbs +8 -0
- package/templates/python/codegen/partials/attio-object-types.hbs +23 -0
- package/templates/python/codegen/partials/attio-query-records-method.hbs +45 -0
- package/templates/python/codegen/partials/attio-section.hbs +111 -0
- package/templates/python/codegen/partials/attio-upsert-record-method.hbs +51 -0
- package/templates/python/codegen/partials/deterministic-tools-section.hbs +194 -0
- package/templates/python/codegen/partials/file-header.hbs +32 -0
- package/templates/python/codegen/partials/schedule-section.hbs +9 -0
- package/templates/python/codegen/partials/snowflake-section.hbs +26 -0
- package/templates/python/codegen/partials/tool-name-types.hbs +15 -0
- package/templates/python/init/.python-version.hbs +1 -0
- package/templates/python/init/README.md.hbs +38 -0
- package/templates/python/init/gitignore.hbs +3 -0
- package/templates/python/init/pyproject.toml.hbs +16 -0
- package/templates/python/init/src/main.py.hbs +21 -0
- package/templates/typescript/codegen/generated.hbs +17 -0
- package/templates/typescript/codegen/partials/atlassian-section.hbs +32 -0
- package/templates/typescript/codegen/partials/attio-section.hbs +38 -0
- package/templates/typescript/codegen/partials/datadog-section.hbs +11 -0
- package/templates/typescript/codegen/partials/figma-section.hbs +13 -0
- package/templates/typescript/codegen/partials/github-section.hbs +62 -0
- package/templates/typescript/codegen/partials/gmail-section.hbs +21 -0
- package/templates/typescript/codegen/partials/header.hbs +12 -0
- package/templates/typescript/codegen/partials/launchdarkly-section.hbs +11 -0
- package/templates/typescript/codegen/partials/linear-section.hbs +27 -0
- package/templates/typescript/codegen/partials/notion-section.hbs +15 -0
- package/templates/typescript/codegen/partials/posthog-section.hbs +11 -0
- package/templates/typescript/codegen/partials/resource-class.hbs +9 -0
- package/templates/typescript/codegen/partials/slack-section.hbs +23 -0
- package/templates/typescript/codegen/partials/snowflake-section.hbs +9 -0
- package/templates/typescript/codegen/partials/system-section.hbs +18 -0
- package/templates/typescript/codegen/partials/tools-section.hbs +68 -0
- package/templates/typescript/codegen/partials/workos-section.hbs +53 -0
- package/templates/typescript/init/env.example.hbs +1 -0
- package/templates/{package.json.tmpl → typescript/init/package.json.hbs} +1 -1
- package/templates/{src/index.ts.tmpl → typescript/init/src/index.ts.hbs} +1 -3
- /package/templates/{env.example.tmpl → python/init/env.example.hbs} +0 -0
- /package/templates/{.claude/settings.json.tmpl → typescript/init/.claude/settings.json.hbs} +0 -0
- /package/templates/{gitignore.tmpl → typescript/init/gitignore.hbs} +0 -0
- /package/templates/{tsconfig.json.tmpl → typescript/init/tsconfig.json.hbs} +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
# === Deterministic tools ===
|
|
4
|
+
|
|
5
|
+
{{#if hasAttioTools}}
|
|
6
|
+
_ATTIO_FILTER_ATTRS_BY_OBJECT: dict[str, frozenset[str]] = {
|
|
7
|
+
{{#each attio.objects}}
|
|
8
|
+
{{#if hasAttributes}}
|
|
9
|
+
{{{apiSlugRepr}}}: {{{attrSlugsFrozensetRepr}}},
|
|
10
|
+
{{/if}}
|
|
11
|
+
{{/each}}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _attio_invalid_filter_error(obj_slug: str, attribute: str | None = None) -> ValueError:
|
|
16
|
+
message = f"Invalid Attio filter for object {obj_slug!r}"
|
|
17
|
+
if attribute is not None:
|
|
18
|
+
message += f" on attribute {attribute!r}"
|
|
19
|
+
message += ": list shorthand is not supported."
|
|
20
|
+
if attribute == 'domains':
|
|
21
|
+
message += ' Use scalar shorthand "useterse.ai" or explicit Attio syntax {"domain": {"$eq": "useterse.ai"}}.'
|
|
22
|
+
else:
|
|
23
|
+
message += " Use a scalar shorthand or explicit Attio operator/object syntax instead."
|
|
24
|
+
return ValueError(message)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _validate_attio_filter_node(
|
|
28
|
+
obj_slug: str,
|
|
29
|
+
value: object,
|
|
30
|
+
*,
|
|
31
|
+
attr_slugs: frozenset[str],
|
|
32
|
+
current_attribute: str | None = None,
|
|
33
|
+
) -> None:
|
|
34
|
+
if isinstance(value, dict):
|
|
35
|
+
for raw_key, child in value.items():
|
|
36
|
+
if not isinstance(raw_key, str):
|
|
37
|
+
raise ValueError(f"Invalid Attio filter for object {obj_slug!r}: filter keys must be strings.")
|
|
38
|
+
key = raw_key
|
|
39
|
+
if key in ('$and', '$or'):
|
|
40
|
+
if not isinstance(child, list):
|
|
41
|
+
raise ValueError(f"Invalid Attio filter for object {obj_slug!r}: {key} must be a list.")
|
|
42
|
+
for item in child:
|
|
43
|
+
if not isinstance(item, dict):
|
|
44
|
+
raise ValueError(f"Invalid Attio filter for object {obj_slug!r}: {key} entries must be objects.")
|
|
45
|
+
_validate_attio_filter_node(obj_slug, item, attr_slugs=attr_slugs)
|
|
46
|
+
continue
|
|
47
|
+
if key == '$not':
|
|
48
|
+
if not isinstance(child, dict):
|
|
49
|
+
raise ValueError(f"Invalid Attio filter for object {obj_slug!r}: $not must be an object.")
|
|
50
|
+
_validate_attio_filter_node(obj_slug, child, attr_slugs=attr_slugs)
|
|
51
|
+
continue
|
|
52
|
+
if key == '$in':
|
|
53
|
+
if not isinstance(child, list):
|
|
54
|
+
raise ValueError(f"Invalid Attio filter for object {obj_slug!r}: $in must be a list.")
|
|
55
|
+
continue
|
|
56
|
+
if key == 'path':
|
|
57
|
+
if not isinstance(child, list):
|
|
58
|
+
raise ValueError(f"Invalid Attio filter for object {obj_slug!r}: path must be a list.")
|
|
59
|
+
continue
|
|
60
|
+
if key == 'constraints':
|
|
61
|
+
if not isinstance(child, dict):
|
|
62
|
+
raise ValueError(f"Invalid Attio filter for object {obj_slug!r}: constraints must be an object.")
|
|
63
|
+
_validate_attio_filter_node(
|
|
64
|
+
obj_slug,
|
|
65
|
+
child,
|
|
66
|
+
attr_slugs=attr_slugs,
|
|
67
|
+
current_attribute=current_attribute,
|
|
68
|
+
)
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
attribute: str | None = key if key in attr_slugs else current_attribute
|
|
72
|
+
if isinstance(child, list):
|
|
73
|
+
raise _attio_invalid_filter_error(obj_slug, attribute)
|
|
74
|
+
_validate_attio_filter_node(
|
|
75
|
+
obj_slug,
|
|
76
|
+
child,
|
|
77
|
+
attr_slugs=attr_slugs,
|
|
78
|
+
current_attribute=attribute,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _serialize_attio_filter(obj_slug: str, value: object | None) -> str | None:
|
|
83
|
+
if value is None:
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
parsed = value
|
|
87
|
+
if isinstance(value, str):
|
|
88
|
+
try:
|
|
89
|
+
parsed = json.loads(value)
|
|
90
|
+
except json.JSONDecodeError as exc:
|
|
91
|
+
raise ValueError(f"Attio filters for object {obj_slug!r} must be valid JSON objects.") from exc
|
|
92
|
+
|
|
93
|
+
if not isinstance(parsed, dict):
|
|
94
|
+
raise ValueError(f"Attio filters for object {obj_slug!r} must be objects.")
|
|
95
|
+
|
|
96
|
+
_validate_attio_filter_node(
|
|
97
|
+
obj_slug,
|
|
98
|
+
parsed,
|
|
99
|
+
attr_slugs=_ATTIO_FILTER_ATTRS_BY_OBJECT.get(obj_slug, frozenset()),
|
|
100
|
+
)
|
|
101
|
+
return json.dumps(parsed, separators=(',', ':'), sort_keys=True)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _serialize_attio_records(records: Sequence[object]) -> str:
|
|
105
|
+
return json.dumps(records, separators=(',', ':'), sort_keys=True)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class _AttioTools:
|
|
109
|
+
def __init__(self, agent: _SdkTerseAgent) -> None:
|
|
110
|
+
self._agent = agent
|
|
111
|
+
{{#each attio.tools}}
|
|
112
|
+
{{#if (eq name "attio_list_objects")}}{{> attio-list-objects-method tool=this attio=../attio}}{{/if}}
|
|
113
|
+
{{#if (eq name "attio_query_records")}}{{> attio-query-records-method tool=this attio=../attio}}{{/if}}
|
|
114
|
+
{{#if (eq name "attio_upsert_record")}}{{> attio-upsert-record-method tool=this attio=../attio}}{{/if}}
|
|
115
|
+
{{/each}}
|
|
116
|
+
{{/if}}
|
|
117
|
+
{{#if hasSnowflakeTools}}
|
|
118
|
+
|
|
119
|
+
class _SnowflakeTools:
|
|
120
|
+
def __init__(self, agent: _SdkTerseAgent) -> None:
|
|
121
|
+
self._agent = agent
|
|
122
|
+
{{#each snowflake.tools}}
|
|
123
|
+
|
|
124
|
+
def {{methodName}}(self, query: str) -> {{outputModel}}:
|
|
125
|
+
return {{outputModel}}.model_validate(
|
|
126
|
+
self._agent.execute_tool(
|
|
127
|
+
{{{nameRepr}}},
|
|
128
|
+
{'integrationId': {{{../snowflake.instanceIdRepr}}}, 'query': query},
|
|
129
|
+
)
|
|
130
|
+
)
|
|
131
|
+
{{/each}}
|
|
132
|
+
{{/if}}
|
|
133
|
+
{{#if hasAnyDeterministicTools}}
|
|
134
|
+
|
|
135
|
+
class GeneratedTools:
|
|
136
|
+
{{#if hasAttioTools}}
|
|
137
|
+
attio: _AttioTools
|
|
138
|
+
{{/if}}
|
|
139
|
+
{{#if hasSnowflakeTools}}
|
|
140
|
+
snowflake: _SnowflakeTools
|
|
141
|
+
{{/if}}
|
|
142
|
+
|
|
143
|
+
def __init__(
|
|
144
|
+
self,
|
|
145
|
+
{{#if hasAttioTools}}
|
|
146
|
+
attio: _AttioTools | None = None,
|
|
147
|
+
{{/if}}
|
|
148
|
+
{{#if hasSnowflakeTools}}
|
|
149
|
+
snowflake: _SnowflakeTools | None = None,
|
|
150
|
+
{{/if}}
|
|
151
|
+
) -> None:
|
|
152
|
+
{{#if hasAttioTools}}
|
|
153
|
+
if attio is not None:
|
|
154
|
+
self.attio = attio
|
|
155
|
+
{{/if}}
|
|
156
|
+
{{#if hasSnowflakeTools}}
|
|
157
|
+
if snowflake is not None:
|
|
158
|
+
self.snowflake = snowflake
|
|
159
|
+
{{/if}}
|
|
160
|
+
{{else}}
|
|
161
|
+
|
|
162
|
+
class GeneratedTools:
|
|
163
|
+
pass
|
|
164
|
+
{{/if}}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class TerseAgent(_SdkTerseAgent):
|
|
168
|
+
tools: GeneratedTools
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def create_tools(agent: _SdkTerseAgent) -> GeneratedTools:
|
|
172
|
+
allowed = {skill.integration_type for skill in agent.skills}
|
|
173
|
+
{{#if hasAnyDeterministicTools}}
|
|
174
|
+
return GeneratedTools(
|
|
175
|
+
{{#if hasAttioTools}}
|
|
176
|
+
attio=_AttioTools(agent) if 'attio' in allowed else None,
|
|
177
|
+
{{/if}}
|
|
178
|
+
{{#if hasSnowflakeTools}}
|
|
179
|
+
snowflake=_SnowflakeTools(agent) if 'snowflake' in allowed else None,
|
|
180
|
+
{{/if}}
|
|
181
|
+
)
|
|
182
|
+
{{else}}
|
|
183
|
+
_ = allowed
|
|
184
|
+
return GeneratedTools()
|
|
185
|
+
{{/if}}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def attach_tools(agent: _SdkTerseAgent) -> GeneratedTools:
|
|
189
|
+
tools = create_tools(agent)
|
|
190
|
+
agent.attach_tools(tools)
|
|
191
|
+
return tools
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
__all__ = {{{exportedNamesRepr}}}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generated by `terse generate`. Do not edit manually.
|
|
3
|
+
|
|
4
|
+
Type checking:
|
|
5
|
+
uv run ty check src/
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
{{#if typingImportsLine}}
|
|
10
|
+
|
|
11
|
+
{{typingImportsLine}}
|
|
12
|
+
{{/if}}
|
|
13
|
+
{{#if pydanticImportsLine}}
|
|
14
|
+
|
|
15
|
+
{{pydanticImportsLine}}
|
|
16
|
+
{{/if}}
|
|
17
|
+
{{#if needsJsonImport}}
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
{{/if}}
|
|
21
|
+
|
|
22
|
+
from terse_sdk import (
|
|
23
|
+
{{#each sdkImports}}
|
|
24
|
+
{{this}},
|
|
25
|
+
{{/each}}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _compact_dict(**values: object) -> dict[str, object]:
|
|
30
|
+
"""Drop None values while preserving falsey config values like False and []."""
|
|
31
|
+
|
|
32
|
+
return {key: value for key, value in values.items() if value is not None}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{{#if snowflake}}
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# === Snowflake ===
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Snowflake:
|
|
8
|
+
@staticmethod
|
|
9
|
+
def skill() -> SkillConfig[{{snowflake.skillToolType}}]:
|
|
10
|
+
return SkillConfig(integration_id={{{snowflake.instanceIdRepr}}}, integration_type='snowflake', config_type='snowflake_output', config=_compact_dict())
|
|
11
|
+
{{else}}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# === Snowflake ===
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Snowflake:
|
|
18
|
+
@staticmethod
|
|
19
|
+
def skill() -> SkillConfig[str]:
|
|
20
|
+
return SkillConfig(
|
|
21
|
+
integration_id='snowflake_placeholder',
|
|
22
|
+
integration_type='snowflake',
|
|
23
|
+
config_type='snowflake_output',
|
|
24
|
+
config=_compact_dict(),
|
|
25
|
+
)
|
|
26
|
+
{{/if}}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{{#if hasAnyToolNameTypes}}
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# === Tool name types ===
|
|
5
|
+
|
|
6
|
+
{{#if attioToolNamesLiteral}}
|
|
7
|
+
AttioToolNames = Literal[{{{attioToolNamesLiteral}}}]
|
|
8
|
+
{{/if}}
|
|
9
|
+
{{#if snowflakeToolNamesLiteral}}
|
|
10
|
+
SnowflakeToolNames = Literal[{{{snowflakeToolNamesLiteral}}}]
|
|
11
|
+
{{/if}}
|
|
12
|
+
{{#if allToolNamesLiteral}}
|
|
13
|
+
AllToolNames = Literal[{{{allToolNamesLiteral}}}]
|
|
14
|
+
{{/if}}
|
|
15
|
+
{{/if}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
Project scaffolded by `terse-cli`.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Python `>=3.11`
|
|
8
|
+
- [`uv`](https://docs.astral.sh/uv/getting-started/installation/) for dependency management and local commands
|
|
9
|
+
|
|
10
|
+
If `terse init` did not install dependencies automatically, install `uv` and run:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
uv sync
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv sync
|
|
20
|
+
terse test
|
|
21
|
+
terse deploy
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Edit `src/main.py` to register jobs with `@app.job(...)`.
|
|
25
|
+
|
|
26
|
+
## Generated Helpers
|
|
27
|
+
|
|
28
|
+
The starter scaffold registers `Attio.skill()` and `Snowflake.skill()` by default.
|
|
29
|
+
|
|
30
|
+
After you connect Attio or Snowflake in Terse, rerun `terse generate` to refresh `src/terse_generated.py` with the real integration IDs and deterministic tool wrappers.
|
|
31
|
+
|
|
32
|
+
Import helpers like `Attio`, `AttioObject`, `Schedule`, `Snowflake`, and `TerseAgent` from `terse_generated`.
|
|
33
|
+
|
|
34
|
+
If a job registers `skills=[Snowflake.skill()]`, it can call deterministic wrappers like:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
agent.tools.snowflake.execute_query(query="select 1")
|
|
38
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "{{projectName}}"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
requires-python = ">=3.11"
|
|
5
|
+
dependencies = [
|
|
6
|
+
"{{sdkDependency}}",
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
[tool.uv]
|
|
10
|
+
package = false
|
|
11
|
+
|
|
12
|
+
[dependency-groups]
|
|
13
|
+
dev = ["ty"]
|
|
14
|
+
|
|
15
|
+
[tool.ty.environment]
|
|
16
|
+
python = ".venv"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from terse_sdk import CronJobInputEvent, SdkAgentStreamEventFinalOutput, Terse
|
|
2
|
+
from terse_generated import Attio, Schedule, Snowflake, TerseAgent
|
|
3
|
+
|
|
4
|
+
app = Terse()
|
|
5
|
+
|
|
6
|
+
@app.job(
|
|
7
|
+
name="{{projectName}}",
|
|
8
|
+
triggers=[Schedule.cron("0 9 * * 1")],
|
|
9
|
+
skills=[
|
|
10
|
+
Attio.skill(),
|
|
11
|
+
Snowflake.skill(),
|
|
12
|
+
],
|
|
13
|
+
)
|
|
14
|
+
def run_job(event: CronJobInputEvent, agent: TerseAgent) -> None:
|
|
15
|
+
prompt = (
|
|
16
|
+
"Tell a joke"
|
|
17
|
+
f"Context: {event.formatted_content}"
|
|
18
|
+
)
|
|
19
|
+
for stream_event in agent.run(prompt, event):
|
|
20
|
+
if isinstance(stream_event, SdkAgentStreamEventFinalOutput):
|
|
21
|
+
print(stream_event.final_output)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{{> header}}
|
|
2
|
+
|
|
3
|
+
{{#if github}}{{> github-section github}}{{/if~}}
|
|
4
|
+
{{#if gmail}}{{> gmail-section gmail}}{{/if~}}
|
|
5
|
+
{{#if slack}}{{> slack-section slack}}{{/if~}}
|
|
6
|
+
{{#if figma}}{{> figma-section figma}}{{/if~}}
|
|
7
|
+
{{#if linear}}{{> linear-section linear}}{{/if~}}
|
|
8
|
+
{{#if atlassian}}{{> atlassian-section atlassian}}{{/if~}}
|
|
9
|
+
{{#if notion}}{{> notion-section notion}}{{/if~}}
|
|
10
|
+
{{#if posthog}}{{> posthog-section posthog}}{{/if~}}
|
|
11
|
+
{{#if datadog}}{{> datadog-section datadog}}{{/if~}}
|
|
12
|
+
{{#if launchdarkly}}{{> launchdarkly-section launchdarkly}}{{/if~}}
|
|
13
|
+
{{#if workos}}{{> workos-section workos}}{{/if~}}
|
|
14
|
+
{{#if attio}}{{> attio-section attio}}{{/if~}}
|
|
15
|
+
{{#if snowflake}}{{> snowflake-section snowflake}}{{/if~}}
|
|
16
|
+
{{#if tools}}{{> tools-section tools}}{{/if}}
|
|
17
|
+
{{> system-section system}}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{{sectionHeader "Jira & Confluence"}}
|
|
2
|
+
|
|
3
|
+
{{> resource-class jiraProjectClass}}
|
|
4
|
+
|
|
5
|
+
{{> resource-class confluencePageClass}}
|
|
6
|
+
|
|
7
|
+
export const Jira = {
|
|
8
|
+
/** Trigger when a Jira issue is created */
|
|
9
|
+
onIssueCreated(opts?: { project?: JiraProject }): JiraConfig {
|
|
10
|
+
return new JiraConfig("{{escape id}}", opts?.project?.projectKey, opts?.project?.projectId, [JiraEventType.ISSUE_CREATED])
|
|
11
|
+
},
|
|
12
|
+
/** Trigger when a Jira issue is updated */
|
|
13
|
+
onIssueUpdated(opts?: { project?: JiraProject }): JiraConfig {
|
|
14
|
+
return new JiraConfig("{{escape id}}", opts?.project?.projectKey, opts?.project?.projectId, [JiraEventType.ISSUE_UPDATED])
|
|
15
|
+
},
|
|
16
|
+
/** Trigger on all Jira events */
|
|
17
|
+
trigger(opts?: { project?: JiraProject; eventTypes?: JiraEventType[] }): JiraConfig {
|
|
18
|
+
return new JiraConfig("{{escape id}}", opts?.project?.projectKey, opts?.project?.projectId, opts?.eventTypes)
|
|
19
|
+
},
|
|
20
|
+
/** Use in `skills[]` */
|
|
21
|
+
skill(opts?: { project?: JiraProject }): TypedSkill<{{{skillToolType}}}> {
|
|
22
|
+
return new JiraConfig("{{escape id}}", opts?.project?.projectKey, opts?.project?.projectId) as TypedSkill<{{{skillToolType}}}>
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const Confluence = {
|
|
27
|
+
/** Use in `skills[]` */
|
|
28
|
+
skill(opts: { page: ConfluencePage }): TypedSkill<{{{skillToolType}}}> {
|
|
29
|
+
return new ConfluenceConfig("{{escape id}}", opts.page.spaceName, opts.page.spaceId, opts.page.pageId, opts.page.title) as TypedSkill<{{{skillToolType}}}>
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{{sectionHeader "Attio"}}
|
|
2
|
+
|
|
3
|
+
export type AttioAttributeDefinition<TSlug extends string = string, TType extends string = string> = {
|
|
4
|
+
apiSlug: TSlug
|
|
5
|
+
title?: string
|
|
6
|
+
type?: TType
|
|
7
|
+
isRequired?: boolean
|
|
8
|
+
isUnique?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class AttioObject<
|
|
12
|
+
TSlug extends string = string,
|
|
13
|
+
TRecordValues extends Record<string, unknown> = Record<string, unknown>,
|
|
14
|
+
TInputValues extends Record<string, unknown> = TRecordValues
|
|
15
|
+
> {
|
|
16
|
+
constructor(
|
|
17
|
+
public readonly apiSlug: TSlug,
|
|
18
|
+
public readonly name: string,
|
|
19
|
+
public readonly attributes: readonly AttioAttributeDefinition[] = []
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
declare readonly __recordValues: TRecordValues
|
|
23
|
+
declare readonly __inputValues: TInputValues
|
|
24
|
+
{{#if objects.length}}
|
|
25
|
+
|
|
26
|
+
{{#each objects}}
|
|
27
|
+
static {{staticName}} = new AttioObject<"{{escape apiSlug}}", {{{recordValuesType}}}, {{{inputValuesType}}}>("{{escape apiSlug}}", "{{escape singularNoun}}", {{{attributeSource}}})
|
|
28
|
+
{{/each}}
|
|
29
|
+
{{/if}}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const Attio = {
|
|
33
|
+
/** Use in `skills[]` */
|
|
34
|
+
skill(opts: { object: AttioObject<any> }): TypedSkill<{{{skillToolType}}}> {
|
|
35
|
+
return new AttioOutputConfig("{{escape id}}", opts.object.apiSlug) as TypedSkill<{{{skillToolType}}}>
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{{sectionHeader "Datadog"}}
|
|
2
|
+
|
|
3
|
+
{{> resource-class indexClass}}
|
|
4
|
+
|
|
5
|
+
export const Datadog = {
|
|
6
|
+
/** Use in `skills[]` */
|
|
7
|
+
skill(opts?: { indexes?: DatadogIndex[] }): TypedSkill<{{{skillToolType}}}> {
|
|
8
|
+
return new DatadogConfig("{{escape id}}", opts?.indexes?.map(i => i.name)) as TypedSkill<{{{skillToolType}}}>
|
|
9
|
+
},
|
|
10
|
+
}
|
|
11
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{{sectionHeader "Figma"}}
|
|
2
|
+
|
|
3
|
+
export const Figma = {
|
|
4
|
+
/** Trigger when a comment is added to a Figma file */
|
|
5
|
+
onComment(opts: { fileKey: string; fileName: string; teamId: string }): FigmaConfig {
|
|
6
|
+
return new FigmaConfig("{{escape id}}", opts.fileKey, opts.fileName, opts.teamId, [FigmaEventType.FILE_COMMENT])
|
|
7
|
+
},
|
|
8
|
+
/** Trigger on all Figma events */
|
|
9
|
+
trigger(opts: { fileKey: string; fileName: string; teamId: string; eventTypes?: FigmaEventType[] }): FigmaConfig {
|
|
10
|
+
return new FigmaConfig("{{escape id}}", opts.fileKey, opts.fileName, opts.teamId, opts.eventTypes)
|
|
11
|
+
},
|
|
12
|
+
}
|
|
13
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{{sectionHeader "GitHub"}}
|
|
2
|
+
|
|
3
|
+
export class GithubOwner {
|
|
4
|
+
constructor(public readonly name: string) {}
|
|
5
|
+
{{#if owners.length}}
|
|
6
|
+
|
|
7
|
+
{{#each owners}}
|
|
8
|
+
static {{staticName}} = new GithubOwner("{{escape name}}")
|
|
9
|
+
{{/each}}
|
|
10
|
+
{{/if}}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class Repos {
|
|
14
|
+
constructor(
|
|
15
|
+
public readonly repositoryId: number,
|
|
16
|
+
public readonly name: string,
|
|
17
|
+
public readonly owner: GithubOwner,
|
|
18
|
+
public readonly fullName: string
|
|
19
|
+
) {}
|
|
20
|
+
{{#if repoGroups.length}}
|
|
21
|
+
|
|
22
|
+
{{#each repoGroups}}
|
|
23
|
+
static {{ownerStaticName}} = {
|
|
24
|
+
{{#each repos}}
|
|
25
|
+
{{staticName}}: new Repos({{id}}, "{{escape name}}", GithubOwner.{{../ownerStaticName}}, "{{escape fullName}}"),
|
|
26
|
+
{{/each}}
|
|
27
|
+
} as const
|
|
28
|
+
{{/each}}
|
|
29
|
+
{{/if}}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const GitHub = {
|
|
33
|
+
/** Trigger on push to a repository */
|
|
34
|
+
onPush(opts: { repo: Repos }): TypedTrigger<GithubPushInputEvent> {
|
|
35
|
+
return new GitHubConfig("{{escape id}}", [opts.repo.repositoryId], [GitHubEventType.PUSH]) as TypedTrigger<GithubPushInputEvent>
|
|
36
|
+
},
|
|
37
|
+
/** Trigger when a pull request is opened */
|
|
38
|
+
onPROpened(opts: { repo: Repos }): TypedTrigger<GithubPRInputEvent> {
|
|
39
|
+
return new GitHubConfig("{{escape id}}", [opts.repo.repositoryId], [GitHubEventType.PR_OPENED]) as TypedTrigger<GithubPRInputEvent>
|
|
40
|
+
},
|
|
41
|
+
/** Trigger when a pull request is merged */
|
|
42
|
+
onPRMerged(opts: { repo: Repos }): TypedTrigger<GithubPRInputEvent> {
|
|
43
|
+
return new GitHubConfig("{{escape id}}", [opts.repo.repositoryId], [GitHubEventType.PR_MERGED]) as TypedTrigger<GithubPRInputEvent>
|
|
44
|
+
},
|
|
45
|
+
/** Trigger when a pull request is closed */
|
|
46
|
+
onPRClosed(opts: { repo: Repos }): TypedTrigger<GithubPRInputEvent> {
|
|
47
|
+
return new GitHubConfig("{{escape id}}", [opts.repo.repositoryId], [GitHubEventType.PR_CLOSED]) as TypedTrigger<GithubPRInputEvent>
|
|
48
|
+
},
|
|
49
|
+
/** Trigger on any pull request event */
|
|
50
|
+
onPR(opts: { repo: Repos }): TypedTrigger<GithubPRInputEvent> {
|
|
51
|
+
return new GitHubConfig("{{escape id}}", [opts.repo.repositoryId], [GitHubEventType.PR_OPENED, GitHubEventType.PR_MERGED, GitHubEventType.PR_CLOSED, GitHubEventType.PR_SYNCHRONIZE]) as TypedTrigger<GithubPRInputEvent>
|
|
52
|
+
},
|
|
53
|
+
/** Trigger on specific GitHub events for the given repositories */
|
|
54
|
+
trigger(opts: { repos: Repos[]; eventTypes?: GitHubEventType[] }): TypedTrigger<GithubInputEvent> {
|
|
55
|
+
return new GitHubConfig("{{escape id}}", opts.repos.map(r => r.repositoryId), opts.eventTypes) as TypedTrigger<GithubInputEvent>
|
|
56
|
+
},
|
|
57
|
+
/** Use in `skills[]` */
|
|
58
|
+
skill(opts: { repos: Repos[] }): TypedSkill<{{{skillToolType}}}> {
|
|
59
|
+
return new GitHubConfig("{{escape id}}", opts.repos.map(r => r.repositoryId)) as TypedSkill<{{{skillToolType}}}>
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{{sectionHeader "Gmail"}}
|
|
2
|
+
|
|
3
|
+
export const Gmail = {
|
|
4
|
+
/** Trigger when a new email is received */
|
|
5
|
+
onEmail(): GmailConfig {
|
|
6
|
+
return new GmailConfig("{{escape id}}", [GmailEventType.EMAIL_RECEIVED])
|
|
7
|
+
},
|
|
8
|
+
/** Trigger on all Gmail events */
|
|
9
|
+
trigger(opts?: { eventTypes?: GmailEventType[] }): GmailConfig {
|
|
10
|
+
return new GmailConfig("{{escape id}}", opts?.eventTypes)
|
|
11
|
+
},
|
|
12
|
+
/** Use in `skills[]` */
|
|
13
|
+
skill(): TypedSkill<{{{skillToolType}}}> {
|
|
14
|
+
return new GmailOutputConfig("{{escape id}}") as TypedSkill<{{{skillToolType}}}>
|
|
15
|
+
},
|
|
16
|
+
/** Use in `skills[]` — creates draft emails */
|
|
17
|
+
draftSkill(): TypedSkill<{{{skillToolType}}}> {
|
|
18
|
+
return new GmailDraftOutputConfig("{{escape id}}") as TypedSkill<{{{skillToolType}}}>
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Auto-generated by `terse generate` — do not edit manually
|
|
2
|
+
// Re-run `terse generate` to update.
|
|
3
|
+
|
|
4
|
+
{{#if useMultilineImports}}
|
|
5
|
+
import {
|
|
6
|
+
{{#each imports}}
|
|
7
|
+
{{this}},
|
|
8
|
+
{{/each}}
|
|
9
|
+
} from "terse-sdk"
|
|
10
|
+
{{else}}
|
|
11
|
+
import { {{#each imports}}{{this}}{{#unless @last}}, {{/unless}}{{/each}} } from "terse-sdk"
|
|
12
|
+
{{/if}}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{{sectionHeader "LaunchDarkly"}}
|
|
2
|
+
|
|
3
|
+
{{> resource-class projectClass}}
|
|
4
|
+
|
|
5
|
+
export const LaunchDarkly = {
|
|
6
|
+
/** Use in `skills[]` */
|
|
7
|
+
skill(opts: { project: LaunchDarklyProject; environmentKeys: string[] }): TypedSkill<{{{skillToolType}}}> {
|
|
8
|
+
return new LaunchDarklyConfig("{{escape id}}", opts.project.projectKey, opts.environmentKeys) as TypedSkill<{{{skillToolType}}}>
|
|
9
|
+
},
|
|
10
|
+
}
|
|
11
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{{sectionHeader "Linear"}}
|
|
2
|
+
|
|
3
|
+
{{> resource-class teamClass}}
|
|
4
|
+
|
|
5
|
+
export const Linear = {
|
|
6
|
+
/** Trigger when a new issue is created */
|
|
7
|
+
onIssueCreated(opts?: { projectId?: string; projectName?: string }): LinearInputConfig {
|
|
8
|
+
return new LinearInputConfig("{{escape id}}", opts?.projectId, opts?.projectName, [LinearEventType.ISSUE_CREATED])
|
|
9
|
+
},
|
|
10
|
+
/** Trigger when an issue is updated */
|
|
11
|
+
onIssueUpdated(opts?: { projectId?: string; projectName?: string }): LinearInputConfig {
|
|
12
|
+
return new LinearInputConfig("{{escape id}}", opts?.projectId, opts?.projectName, [LinearEventType.ISSUE_UPDATED])
|
|
13
|
+
},
|
|
14
|
+
/** Trigger when a comment is added to an issue */
|
|
15
|
+
onComment(opts?: { projectId?: string; projectName?: string }): LinearInputConfig {
|
|
16
|
+
return new LinearInputConfig("{{escape id}}", opts?.projectId, opts?.projectName, [LinearEventType.COMMENT_CREATED])
|
|
17
|
+
},
|
|
18
|
+
/** Trigger on all Linear events */
|
|
19
|
+
trigger(opts?: { projectId?: string; projectName?: string; eventTypes?: LinearEventType[] }): LinearInputConfig {
|
|
20
|
+
return new LinearInputConfig("{{escape id}}", opts?.projectId, opts?.projectName, opts?.eventTypes)
|
|
21
|
+
},
|
|
22
|
+
/** Use in `skills[]` */
|
|
23
|
+
skill(opts?: { team?: LinearTeam; projectId?: string; projectName?: string }): TypedSkill<{{{skillToolType}}}> {
|
|
24
|
+
return new LinearOutputConfig("{{escape id}}", opts?.team?.teamId, opts?.team?.name, opts?.projectId, opts?.projectName) as TypedSkill<{{{skillToolType}}}>
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{{sectionHeader "Notion"}}
|
|
2
|
+
|
|
3
|
+
{{> resource-class databaseClass}}
|
|
4
|
+
|
|
5
|
+
{{> resource-class pageClass}}
|
|
6
|
+
|
|
7
|
+
export const Notion = {
|
|
8
|
+
/** Use in `skills[]` */
|
|
9
|
+
skill(opts?: { databases?: NotionDatabase[]; pages?: NotionPage[] }): TypedSkill<{{{skillToolType}}}> {
|
|
10
|
+
return new NotionConfig("{{escape id}}",
|
|
11
|
+
opts?.databases?.map(d => d.databaseId), opts?.databases?.map(d => d.title),
|
|
12
|
+
opts?.pages?.map(p => p.pageId), opts?.pages?.map(p => p.title)) as TypedSkill<{{{skillToolType}}}>
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{{sectionHeader "PostHog"}}
|
|
2
|
+
|
|
3
|
+
{{> resource-class projectClass}}
|
|
4
|
+
|
|
5
|
+
export const Posthog = {
|
|
6
|
+
/** Use in `skills[]` */
|
|
7
|
+
skill(opts: { project: PosthogProject }): TypedSkill<{{{skillToolType}}}> {
|
|
8
|
+
return new PosthogConfig("{{escape id}}", opts.project.projectId, opts.project.name) as TypedSkill<{{{skillToolType}}}>
|
|
9
|
+
},
|
|
10
|
+
}
|
|
11
|
+
|