terse-cli 0.1.16 → 0.1.18
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/README.md +4 -4
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +6 -9
- package/dist/api.js.map +1 -1
- package/dist/assertProjectRoot.d.ts +3 -2
- package/dist/assertProjectRoot.d.ts.map +1 -1
- package/dist/assertProjectRoot.js +6 -9
- package/dist/assertProjectRoot.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +4 -4
- package/dist/auth.js.map +1 -1
- package/dist/deploy.d.ts +2 -1
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +19 -23
- 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 +59 -33
- package/dist/generate.js.map +1 -1
- package/dist/index.js +12 -12
- 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 +30 -58
- package/dist/init.js.map +1 -1
- package/dist/integrate.d.ts.map +1 -1
- package/dist/integrate.js +8 -12
- package/dist/integrate.js.map +1 -1
- package/dist/integrationApi.d.ts +2 -2
- package/dist/integrationApi.d.ts.map +1 -1
- package/dist/integrationApi.js.map +1 -1
- package/dist/loadJob.d.ts +4 -3
- package/dist/loadJob.d.ts.map +1 -1
- package/dist/loadJob.js +8 -56
- 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 +119 -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 +362 -0
- package/dist/providers/python/PythonProvider.js.map +1 -0
- package/dist/providers/python/preparePythonCodegenData.d.ts +89 -0
- package/dist/providers/python/preparePythonCodegenData.d.ts.map +1 -0
- package/dist/providers/python/preparePythonCodegenData.js +359 -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 +157 -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 +69 -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 +160 -0
- package/dist/providers/typescript/TypeScriptProvider.js.map +1 -0
- package/dist/providers/typescript/prepareCodegenData.d.ts +144 -0
- package/dist/providers/typescript/prepareCodegenData.d.ts.map +1 -0
- package/dist/providers/typescript/prepareCodegenData.js +751 -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 +7 -194
- package/dist/run.js.map +1 -1
- package/dist/shared/ToolDisplayUtils.d.ts.map +1 -1
- package/dist/shared/ToolDisplayUtils.js +13 -1
- package/dist/shared/ToolDisplayUtils.js.map +1 -1
- package/dist/shared/types.d.ts +912 -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 +12 -12
- package/dist/test.js.map +1 -1
- package/dist/util.d.ts +1 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js.map +1 -1
- package/package.json +13 -9
- package/templates/python/codegen/generated.hbs +7 -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 +110 -0
- package/templates/python/codegen/partials/attio-upsert-record-method.hbs +51 -0
- package/templates/python/codegen/partials/deterministic-tools-section.hbs +308 -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/slack-section.hbs +178 -0
- package/templates/python/codegen/partials/snowflake-section.hbs +26 -0
- package/templates/python/codegen/partials/tool-name-types.hbs +18 -0
- package/templates/python/init/.python-version.hbs +1 -0
- package/templates/python/init/README.md.hbs +40 -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 +22 -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 +69 -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,308 @@
|
|
|
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 hasSlackTools}}
|
|
118
|
+
|
|
119
|
+
def _normalize_slack_channel_id(channel: object) -> str:
|
|
120
|
+
if hasattr(channel, 'channel_id'):
|
|
121
|
+
return str(getattr(channel, 'channel_id'))
|
|
122
|
+
return str(channel)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _serialize_slack_blocks(blocks: object | None) -> str | None:
|
|
126
|
+
if blocks is None:
|
|
127
|
+
return None
|
|
128
|
+
if isinstance(blocks, str):
|
|
129
|
+
return blocks
|
|
130
|
+
return json.dumps(blocks, separators=(',', ':'))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class _SlackTools:
|
|
134
|
+
def __init__(self, agent: _SdkTerseAgent) -> None:
|
|
135
|
+
self._agent = agent
|
|
136
|
+
{{#each slack.tools}}
|
|
137
|
+
{{#if (eq name "slack_send_message")}}
|
|
138
|
+
|
|
139
|
+
def send_message(
|
|
140
|
+
self,
|
|
141
|
+
*,
|
|
142
|
+
channel: SlackChannel | str,
|
|
143
|
+
message: str,
|
|
144
|
+
thread_ts: str | None = None,
|
|
145
|
+
blocks: object | None = None,
|
|
146
|
+
) -> SlackSendMessageToolOutput:
|
|
147
|
+
return SlackSendMessageToolOutput.model_validate(
|
|
148
|
+
self._agent.execute_tool(
|
|
149
|
+
{{{nameRepr}}},
|
|
150
|
+
_compact_dict(
|
|
151
|
+
integrationId={{{../slack.instanceIdRepr}}},
|
|
152
|
+
channelId=_normalize_slack_channel_id(channel),
|
|
153
|
+
message=message,
|
|
154
|
+
thread_ts=thread_ts,
|
|
155
|
+
blocks=_serialize_slack_blocks(blocks),
|
|
156
|
+
),
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
{{/if}}
|
|
160
|
+
{{#if (eq name "slack_list_channels")}}
|
|
161
|
+
|
|
162
|
+
def list_channels(
|
|
163
|
+
self,
|
|
164
|
+
*,
|
|
165
|
+
types: str | None = None,
|
|
166
|
+
limit: int | None = None,
|
|
167
|
+
cursor: str | None = None,
|
|
168
|
+
) -> SlackListChannelsToolOutput:
|
|
169
|
+
return SlackListChannelsToolOutput.model_validate(
|
|
170
|
+
self._agent.execute_tool(
|
|
171
|
+
{{{nameRepr}}},
|
|
172
|
+
_compact_dict(
|
|
173
|
+
integrationId={{{../slack.instanceIdRepr}}},
|
|
174
|
+
types=types,
|
|
175
|
+
limit=limit,
|
|
176
|
+
cursor=cursor,
|
|
177
|
+
),
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
{{/if}}
|
|
181
|
+
{{#if (eq name "slack_list_users")}}
|
|
182
|
+
|
|
183
|
+
def list_users(self, *, query: str | None = None) -> SlackListUsersToolOutput:
|
|
184
|
+
return SlackListUsersToolOutput.model_validate(
|
|
185
|
+
self._agent.execute_tool(
|
|
186
|
+
{{{nameRepr}}},
|
|
187
|
+
_compact_dict(
|
|
188
|
+
integrationId={{{../slack.instanceIdRepr}}},
|
|
189
|
+
query=query,
|
|
190
|
+
),
|
|
191
|
+
)
|
|
192
|
+
)
|
|
193
|
+
{{/if}}
|
|
194
|
+
{{#if (eq name "slack_read_conversation")}}
|
|
195
|
+
|
|
196
|
+
def read_conversation(
|
|
197
|
+
self,
|
|
198
|
+
*,
|
|
199
|
+
channel: SlackChannel | str,
|
|
200
|
+
limit: int | None = None,
|
|
201
|
+
cursor: str | None = None,
|
|
202
|
+
) -> SlackReadConversationToolOutput:
|
|
203
|
+
return SlackReadConversationToolOutput.model_validate(
|
|
204
|
+
self._agent.execute_tool(
|
|
205
|
+
{{{nameRepr}}},
|
|
206
|
+
_compact_dict(
|
|
207
|
+
integrationId={{{../slack.instanceIdRepr}}},
|
|
208
|
+
channelId=_normalize_slack_channel_id(channel),
|
|
209
|
+
limit=limit,
|
|
210
|
+
cursor=cursor,
|
|
211
|
+
),
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
{{/if}}
|
|
215
|
+
{{/each}}
|
|
216
|
+
{{/if}}
|
|
217
|
+
{{#if hasSnowflakeTools}}
|
|
218
|
+
|
|
219
|
+
class _SnowflakeTools:
|
|
220
|
+
def __init__(self, agent: _SdkTerseAgent) -> None:
|
|
221
|
+
self._agent = agent
|
|
222
|
+
{{#each snowflake.tools}}
|
|
223
|
+
|
|
224
|
+
def {{methodName}}(self, query: str) -> {{outputModel}}:
|
|
225
|
+
return {{outputModel}}.model_validate(
|
|
226
|
+
self._agent.execute_tool(
|
|
227
|
+
{{{nameRepr}}},
|
|
228
|
+
{'integrationId': {{{../snowflake.instanceIdRepr}}}, 'query': query},
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
{{/each}}
|
|
232
|
+
{{/if}}
|
|
233
|
+
{{#if hasAnyDeterministicTools}}
|
|
234
|
+
|
|
235
|
+
class GeneratedTools:
|
|
236
|
+
{{#if hasAttioTools}}
|
|
237
|
+
attio: _AttioTools
|
|
238
|
+
{{/if}}
|
|
239
|
+
{{#if hasSlackTools}}
|
|
240
|
+
slack: _SlackTools
|
|
241
|
+
{{/if}}
|
|
242
|
+
{{#if hasSnowflakeTools}}
|
|
243
|
+
snowflake: _SnowflakeTools
|
|
244
|
+
{{/if}}
|
|
245
|
+
|
|
246
|
+
def __init__(
|
|
247
|
+
self,
|
|
248
|
+
{{#if hasAttioTools}}
|
|
249
|
+
attio: _AttioTools | None = None,
|
|
250
|
+
{{/if}}
|
|
251
|
+
{{#if hasSlackTools}}
|
|
252
|
+
slack: _SlackTools | None = None,
|
|
253
|
+
{{/if}}
|
|
254
|
+
{{#if hasSnowflakeTools}}
|
|
255
|
+
snowflake: _SnowflakeTools | None = None,
|
|
256
|
+
{{/if}}
|
|
257
|
+
) -> None:
|
|
258
|
+
{{#if hasAttioTools}}
|
|
259
|
+
if attio is not None:
|
|
260
|
+
self.attio = attio
|
|
261
|
+
{{/if}}
|
|
262
|
+
{{#if hasSlackTools}}
|
|
263
|
+
if slack is not None:
|
|
264
|
+
self.slack = slack
|
|
265
|
+
{{/if}}
|
|
266
|
+
{{#if hasSnowflakeTools}}
|
|
267
|
+
if snowflake is not None:
|
|
268
|
+
self.snowflake = snowflake
|
|
269
|
+
{{/if}}
|
|
270
|
+
{{else}}
|
|
271
|
+
|
|
272
|
+
class GeneratedTools:
|
|
273
|
+
pass
|
|
274
|
+
{{/if}}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class TerseAgent(_SdkTerseAgent):
|
|
278
|
+
tools: GeneratedTools
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def create_tools(agent: _SdkTerseAgent) -> GeneratedTools:
|
|
282
|
+
manual_tool_configs = getattr(agent, "manual_tool_configs", None) or agent.skills
|
|
283
|
+
allowed = {config.integration_type for config in manual_tool_configs}
|
|
284
|
+
{{#if hasAnyDeterministicTools}}
|
|
285
|
+
return GeneratedTools(
|
|
286
|
+
{{#if hasAttioTools}}
|
|
287
|
+
attio=_AttioTools(agent) if 'attio' in allowed else None,
|
|
288
|
+
{{/if}}
|
|
289
|
+
{{#if hasSlackTools}}
|
|
290
|
+
slack=_SlackTools(agent) if 'slack' in allowed else None,
|
|
291
|
+
{{/if}}
|
|
292
|
+
{{#if hasSnowflakeTools}}
|
|
293
|
+
snowflake=_SnowflakeTools(agent) if 'snowflake' in allowed else None,
|
|
294
|
+
{{/if}}
|
|
295
|
+
)
|
|
296
|
+
{{else}}
|
|
297
|
+
_ = allowed
|
|
298
|
+
return GeneratedTools()
|
|
299
|
+
{{/if}}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def attach_tools(agent: _SdkTerseAgent) -> GeneratedTools:
|
|
303
|
+
tools = create_tools(agent)
|
|
304
|
+
agent.attach_tools(tools)
|
|
305
|
+
return tools
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
__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,178 @@
|
|
|
1
|
+
{{#if slack}}
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# === Slack ===
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SlackChannel(BaseModel):
|
|
8
|
+
model_config = ConfigDict(frozen=True)
|
|
9
|
+
channel_id: str
|
|
10
|
+
name: str
|
|
11
|
+
{{#if slack.channels.length}}
|
|
12
|
+
{{#each slack.channels}}
|
|
13
|
+
{{staticName}}: ClassVar["SlackChannel"]
|
|
14
|
+
{{/each}}
|
|
15
|
+
|
|
16
|
+
{{#each slack.channels}}
|
|
17
|
+
SlackChannel.{{staticName}} = SlackChannel(channel_id={{{channelIdRepr}}}, name={{{nameRepr}}})
|
|
18
|
+
{{/each}}
|
|
19
|
+
{{/if}}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Slack:
|
|
23
|
+
@staticmethod
|
|
24
|
+
def on_message(channel: SlackChannel, *, user_ids: Sequence[str] | None = None) -> TriggerConfig:
|
|
25
|
+
return TriggerConfig(
|
|
26
|
+
integration_id={{{slack.instanceIdRepr}}},
|
|
27
|
+
integration_type='slack',
|
|
28
|
+
event_type='message',
|
|
29
|
+
config_type='slack',
|
|
30
|
+
config=_compact_dict(
|
|
31
|
+
channelId=channel.channel_id,
|
|
32
|
+
channelName=channel.name,
|
|
33
|
+
listenToUserDms=False,
|
|
34
|
+
userIds=list(user_ids) if user_ids is not None else None,
|
|
35
|
+
eventTypes=['message'],
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def on_dm(*, user_ids: Sequence[str] | None = None) -> TriggerConfig:
|
|
41
|
+
return TriggerConfig(
|
|
42
|
+
integration_id={{{slack.instanceIdRepr}}},
|
|
43
|
+
integration_type='slack',
|
|
44
|
+
event_type='message',
|
|
45
|
+
config_type='slack',
|
|
46
|
+
config=_compact_dict(
|
|
47
|
+
listenToUserDms=True,
|
|
48
|
+
userIds=list(user_ids) if user_ids is not None else None,
|
|
49
|
+
eventTypes=['message'],
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def trigger(
|
|
55
|
+
*,
|
|
56
|
+
channel: SlackChannel | None = None,
|
|
57
|
+
listen_to_user_dms: bool | None = None,
|
|
58
|
+
user_ids: Sequence[str] | None = None,
|
|
59
|
+
event_types: Sequence[SlackEventType] | None = None,
|
|
60
|
+
) -> TriggerConfig:
|
|
61
|
+
return TriggerConfig(
|
|
62
|
+
integration_id={{{slack.instanceIdRepr}}},
|
|
63
|
+
integration_type='slack',
|
|
64
|
+
config_type='slack',
|
|
65
|
+
config=_compact_dict(
|
|
66
|
+
channelId=channel.channel_id if channel is not None else None,
|
|
67
|
+
channelName=channel.name if channel is not None else None,
|
|
68
|
+
listenToUserDms=listen_to_user_dms,
|
|
69
|
+
userIds=list(user_ids) if user_ids is not None else None,
|
|
70
|
+
eventTypes=list(event_types) if event_types is not None else None,
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def skill(
|
|
76
|
+
*,
|
|
77
|
+
channel: SlackChannel | None = None,
|
|
78
|
+
user_ids: Sequence[str] | None = None,
|
|
79
|
+
user_names: Sequence[str] | None = None,
|
|
80
|
+
listen_to_user_dms: bool | None = None,
|
|
81
|
+
) -> SkillConfig[{{slack.skillToolType}}]:
|
|
82
|
+
return SkillConfig(
|
|
83
|
+
integration_id={{{slack.instanceIdRepr}}},
|
|
84
|
+
integration_type='slack',
|
|
85
|
+
config_type='slack_output',
|
|
86
|
+
config=_compact_dict(
|
|
87
|
+
channelId=channel.channel_id if channel is not None else None,
|
|
88
|
+
channelName=channel.name if channel is not None else None,
|
|
89
|
+
userIds=list(user_ids) if user_ids is not None else None,
|
|
90
|
+
userNames=list(user_names) if user_names is not None else None,
|
|
91
|
+
listenToUserDms=listen_to_user_dms,
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
{{else}}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# === Slack ===
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class SlackChannel:
|
|
101
|
+
def __init__(self, channel_id: str, name: str) -> None:
|
|
102
|
+
self.channel_id = channel_id
|
|
103
|
+
self.name = name
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class Slack:
|
|
107
|
+
@staticmethod
|
|
108
|
+
def on_message(channel: SlackChannel, *, user_ids: list[str] | None = None) -> TriggerConfig:
|
|
109
|
+
return TriggerConfig(
|
|
110
|
+
integration_id='slack_placeholder',
|
|
111
|
+
integration_type='slack',
|
|
112
|
+
event_type='message',
|
|
113
|
+
config_type='slack',
|
|
114
|
+
config=_compact_dict(
|
|
115
|
+
channelId=channel.channel_id,
|
|
116
|
+
channelName=channel.name,
|
|
117
|
+
listenToUserDms=False,
|
|
118
|
+
userIds=list(user_ids) if user_ids is not None else None,
|
|
119
|
+
eventTypes=['message'],
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def on_dm(*, user_ids: list[str] | None = None) -> TriggerConfig:
|
|
125
|
+
return TriggerConfig(
|
|
126
|
+
integration_id='slack_placeholder',
|
|
127
|
+
integration_type='slack',
|
|
128
|
+
event_type='message',
|
|
129
|
+
config_type='slack',
|
|
130
|
+
config=_compact_dict(
|
|
131
|
+
listenToUserDms=True,
|
|
132
|
+
userIds=list(user_ids) if user_ids is not None else None,
|
|
133
|
+
eventTypes=['message'],
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def trigger(
|
|
139
|
+
*,
|
|
140
|
+
channel: SlackChannel | None = None,
|
|
141
|
+
listen_to_user_dms: bool | None = None,
|
|
142
|
+
user_ids: list[str] | None = None,
|
|
143
|
+
event_types: list[str] | None = None,
|
|
144
|
+
) -> TriggerConfig:
|
|
145
|
+
return TriggerConfig(
|
|
146
|
+
integration_id='slack_placeholder',
|
|
147
|
+
integration_type='slack',
|
|
148
|
+
config_type='slack',
|
|
149
|
+
config=_compact_dict(
|
|
150
|
+
channelId=channel.channel_id if channel is not None else None,
|
|
151
|
+
channelName=channel.name if channel is not None else None,
|
|
152
|
+
listenToUserDms=listen_to_user_dms,
|
|
153
|
+
userIds=list(user_ids) if user_ids is not None else None,
|
|
154
|
+
eventTypes=list(event_types) if event_types is not None else None,
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def skill(
|
|
160
|
+
*,
|
|
161
|
+
channel: SlackChannel | None = None,
|
|
162
|
+
user_ids: list[str] | None = None,
|
|
163
|
+
user_names: list[str] | None = None,
|
|
164
|
+
listen_to_user_dms: bool | None = None,
|
|
165
|
+
) -> SkillConfig[str]:
|
|
166
|
+
return SkillConfig(
|
|
167
|
+
integration_id='slack_placeholder',
|
|
168
|
+
integration_type='slack',
|
|
169
|
+
config_type='slack_output',
|
|
170
|
+
config=_compact_dict(
|
|
171
|
+
channelId=channel.channel_id if channel is not None else None,
|
|
172
|
+
channelName=channel.name if channel is not None else None,
|
|
173
|
+
userIds=list(user_ids) if user_ids is not None else None,
|
|
174
|
+
userNames=list(user_names) if user_names is not None else None,
|
|
175
|
+
listenToUserDms=listen_to_user_dms,
|
|
176
|
+
),
|
|
177
|
+
)
|
|
178
|
+
{{/if}}
|
|
@@ -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,18 @@
|
|
|
1
|
+
{{#if hasAnyToolNameTypes}}
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# === Tool name types ===
|
|
5
|
+
|
|
6
|
+
{{#if attioToolNamesLiteral}}
|
|
7
|
+
AttioToolNames = Literal[{{{attioToolNamesLiteral}}}]
|
|
8
|
+
{{/if}}
|
|
9
|
+
{{#if slackToolNamesLiteral}}
|
|
10
|
+
SlackToolNames = Literal[{{{slackToolNamesLiteral}}}]
|
|
11
|
+
{{/if}}
|
|
12
|
+
{{#if snowflakeToolNamesLiteral}}
|
|
13
|
+
SnowflakeToolNames = Literal[{{{snowflakeToolNamesLiteral}}}]
|
|
14
|
+
{{/if}}
|
|
15
|
+
{{#if allToolNamesLiteral}}
|
|
16
|
+
AllToolNames = Literal[{{{allToolNamesLiteral}}}]
|
|
17
|
+
{{/if}}
|
|
18
|
+
{{/if}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,40 @@
|
|
|
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, Slack, or Snowflake in Terse, rerun `terse generate` to refresh `src/terse_generated.py` with the real integration IDs, generated resources, and deterministic tool wrappers.
|
|
31
|
+
|
|
32
|
+
Import helpers like `Attio`, `AttioObject`, `Schedule`, `Slack`, `SlackChannel`, `Snowflake`, and `TerseAgent` from `terse_generated`.
|
|
33
|
+
|
|
34
|
+
If a job includes the integration in `triggers=` or `skills=`, it can call deterministic wrappers like:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
agent.tools.snowflake.execute_query(query="select 1")
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Use `agent.tools.slack.*` for direct deterministic Slack calls. Use `skills=[Slack.skill(...)]` when you want Slack to be visible to the model during `run()` or `run_and_wait()`.
|
|
@@ -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}}
|