@devosurf/tesser 0.1.0-alpha.0 → 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,7 @@ The interface both your agent and you drive everything through. Machine-first (A
10
10
  # auth & link (agent lane: set TESSER_TOKEN and skip all prompts)
11
11
  tesser login --instance URL --token tsk_… # verify + store a profile
12
12
  tesser init <name> # scaffold a Project (one repo of automations)
13
+ tesser upgrade # pin Tesser packages to this CLI version + refresh .tesser/docs
13
14
  tesser link [--repo URL] # register the Project on the instance
14
15
  tesser status # instance health + deploy state
15
16
 
@@ -35,6 +36,8 @@ tesser logs <runId> [--follow]
35
36
  Auth resolution: `--token` > `TESSER_TOKEN` > profile (`~/.config/tesser/config.json`).
36
37
  Instance resolution: `--url` > `tesser.json` > `TESSER_URL` > profile > `http://localhost:8377`.
37
38
 
39
+ `init` pins all Tesser packages to the CLI's exact version and writes committed agent reference docs under `.tesser/docs/` plus a user-editable `AGENTS.md`. During alpha/beta, upgrade with the target CLI version so package pins and docs move together: `npx @devosurf/tesser@<version> upgrade`.
40
+
38
41
  `tesser dev` spawns the separately-installed `tesser-server` binary (process boundary —
39
42
  this Apache package never links the AGPL server). `tesser test` runs the project's own
40
43
  test runner behind the scenes plus auto-smoke for untested automations; no third-party
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // packages/cli/src/index.ts
2
2
  import { execFile as execFile2 } from "node:child_process";
3
- import { readFileSync as readFileSync3 } from "node:fs";
3
+ import { readFileSync as readFileSync4 } from "node:fs";
4
4
  import { promisify as promisify2 } from "node:util";
5
5
  import { Command } from "commander";
6
6
 
@@ -1171,8 +1171,428 @@ async function dev(out, projectRoot, project, opts) {
1171
1171
  }
1172
1172
 
1173
1173
  // packages/cli/src/commands/init.ts
1174
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
1174
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
1175
+ import { join as join5 } from "node:path";
1176
+
1177
+ // packages/cli/src/commands/project-docs.ts
1178
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
1175
1179
  import { join as join4 } from "node:path";
1180
+ var TESSER_DEPENDENCIES = ["@devosurf/tesser-sdk", "@devosurf/tesser-connectors"];
1181
+ var TESSER_DEV_DEPENDENCIES = ["@devosurf/tesser", "@devosurf/tesser-server", "@devosurf/tesser-testing"];
1182
+ var GENERATED_DOC_PATHS = [
1183
+ ".tesser/docs/manifest.json",
1184
+ ".tesser/docs/README.md",
1185
+ ".tesser/docs/cli.md",
1186
+ ".tesser/docs/sdk.md",
1187
+ ".tesser/docs/connectors.md"
1188
+ ];
1189
+ function projectPackageJson(name, version) {
1190
+ return {
1191
+ name,
1192
+ private: true,
1193
+ type: "module",
1194
+ packageManager: "pnpm@9.12.0",
1195
+ scripts: { test: "tesser test", deploy: "tesser deploy", dev: "tesser dev" },
1196
+ dependencies: {
1197
+ "@devosurf/tesser-sdk": version,
1198
+ "@devosurf/tesser-connectors": version,
1199
+ zod: "^4"
1200
+ },
1201
+ devDependencies: {
1202
+ "@devosurf/tesser": version,
1203
+ "@devosurf/tesser-server": version,
1204
+ "@devosurf/tesser-testing": version,
1205
+ vitest: "^4"
1206
+ }
1207
+ };
1208
+ }
1209
+ function writeProjectAgentInstructions(root, projectName, version, opts) {
1210
+ const path = join4(root, "AGENTS.md");
1211
+ if (!opts.overwrite && existsSync4(path)) return false;
1212
+ writeFileSync2(path, projectAgentsMd(projectName, version));
1213
+ return true;
1214
+ }
1215
+ function writeTesserGeneratedDocs(root, projectName, version) {
1216
+ const docsDir = join4(root, ".tesser", "docs");
1217
+ mkdirSync2(docsDir, { recursive: true });
1218
+ const docs = {
1219
+ "manifest.json": JSON.stringify(
1220
+ {
1221
+ kind: "tesser-agent-docs",
1222
+ schemaVersion: 1,
1223
+ generatedBy: "@devosurf/tesser",
1224
+ tesserVersion: version,
1225
+ project: projectName,
1226
+ generatedFiles: GENERATED_DOC_PATHS.filter((path) => path !== ".tesser/docs/manifest.json")
1227
+ },
1228
+ null,
1229
+ 2
1230
+ ) + "\n",
1231
+ "README.md": docsReadmeMd(projectName, version),
1232
+ "cli.md": cliReferenceMd(version),
1233
+ "sdk.md": sdkReferenceMd(version),
1234
+ "connectors.md": connectorsReferenceMd(version)
1235
+ };
1236
+ for (const [file, content] of Object.entries(docs)) {
1237
+ writeFileSync2(join4(docsDir, file), content);
1238
+ }
1239
+ return [...GENERATED_DOC_PATHS];
1240
+ }
1241
+ function tesserGitignore() {
1242
+ return `node_modules/
1243
+ .env
1244
+
1245
+ # Tesser local runtime state from \`tesser dev\`; keep generated agent docs committed.
1246
+ .tesser/*
1247
+ !.tesser/
1248
+ !.tesser/docs/
1249
+ !.tesser/docs/**
1250
+ `;
1251
+ }
1252
+ function ensureTesserDocsGitignore(root) {
1253
+ const path = join4(root, ".gitignore");
1254
+ const block = `
1255
+ # Tesser local runtime state from \`tesser dev\`; keep generated agent docs committed.
1256
+ .tesser/*
1257
+ !.tesser/
1258
+ !.tesser/docs/
1259
+ !.tesser/docs/**
1260
+ `;
1261
+ if (!existsSync4(path)) {
1262
+ writeFileSync2(path, tesserGitignore());
1263
+ return true;
1264
+ }
1265
+ const existing = readFileSync2(path, "utf8");
1266
+ if (existing.includes("!.tesser/docs/**")) return false;
1267
+ writeFileSync2(path, existing.replace(/\s*$/, "\n") + block);
1268
+ return true;
1269
+ }
1270
+ function upgradeProject(out, project, version) {
1271
+ const packagePath = join4(project.root, "package.json");
1272
+ if (!existsSync4(packagePath)) {
1273
+ throw new CliError(EXIT.USAGE, "not inside a package-backed Tesser project (missing package.json)");
1274
+ }
1275
+ const pkg = JSON.parse(readFileSync2(packagePath, "utf8"));
1276
+ pinTesserPackages(pkg, version);
1277
+ writeFileSync2(packagePath, JSON.stringify(pkg, null, 2) + "\n");
1278
+ const docs = writeTesserGeneratedDocs(project.root, project.name, version);
1279
+ const gitignoreUpdated = ensureTesserDocsGitignore(project.root);
1280
+ const agentInstructionsCreated = writeProjectAgentInstructions(project.root, project.name, version, { overwrite: false });
1281
+ const next = [
1282
+ "pnpm install",
1283
+ "tesser test --json",
1284
+ 'git add package.json pnpm-lock.yaml .gitignore AGENTS.md .tesser/docs && git commit -m "upgrade tesser"'
1285
+ ];
1286
+ out.data(
1287
+ { upgraded: project.root, tesserVersion: version, docs, packageJson: "package.json", gitignoreUpdated, agentInstructionsCreated, next },
1288
+ () => `upgraded ${project.root} to Tesser ${version}
1289
+ next:
1290
+ pnpm install
1291
+ tesser test --json
1292
+ git add package.json pnpm-lock.yaml .gitignore AGENTS.md .tesser/docs && git commit -m "upgrade tesser"`
1293
+ );
1294
+ }
1295
+ function pinTesserPackages(pkg, version) {
1296
+ const dependencies = objectField(pkg, "dependencies");
1297
+ for (const name of TESSER_DEPENDENCIES) dependencies[name] = version;
1298
+ const devDependencies = objectField(pkg, "devDependencies");
1299
+ for (const name of TESSER_DEV_DEPENDENCIES) devDependencies[name] = version;
1300
+ }
1301
+ function objectField(pkg, key) {
1302
+ const existing = pkg[key];
1303
+ if (existing && typeof existing === "object" && !Array.isArray(existing)) return existing;
1304
+ const next = {};
1305
+ pkg[key] = next;
1306
+ return next;
1307
+ }
1308
+ function projectAgentsMd(projectName, version) {
1309
+ return `# Working in this Tesser Project
1310
+
1311
+ This repository is a Tesser **Project**: one git repo of automations deployed to a Tesser **Instance**.
1312
+
1313
+ ## Read first
1314
+ - [Generated Tesser reference](./.tesser/docs/README.md) \u2014 generated for Tesser ${version}.
1315
+ - [CLI reference](./.tesser/docs/cli.md) \u2014 use \`--json\` for machine-readable output.
1316
+ - [SDK reference](./.tesser/docs/sdk.md) \u2014 authoring pattern, \`ctx.step\`, tests.
1317
+ - [Connector reference](./.tesser/docs/connectors.md) \u2014 safe Connection and Secret usage.
1318
+
1319
+ ## Project rules
1320
+ - Use Tesser terms: **Automation**, **Trigger**, **Step**, **Project**, **Instance**, **Connector**, **Connection**, **Secret**, **Credential broker**, **Replay**.
1321
+ - One Automation per directory under \`automations/\`; colocate \`*.test.ts\` with each Automation.
1322
+ - Every side effect and external call must be inside \`ctx.step("stable-name", async () => ...)\`.
1323
+ - Declare credentials statically with \`connections: { ... }\` and \`secrets: { ... }\`; never read or print secret values.
1324
+ - Run \`pnpm install\` after init/upgrade, commit \`pnpm-lock.yaml\`, and never commit \`node_modules/\`.
1325
+ - Before deploy, run \`tesser test --json\` (and usually \`tesser build --json\`).
1326
+ - Deploy from git. Commit generated Tesser docs under \`.tesser/docs/\`; local runtime state in other \`.tesser/*\` paths stays ignored.
1327
+
1328
+ ## Upgrade rule
1329
+ To update this Project's pinned Tesser packages and generated references, run the target CLI version explicitly:
1330
+
1331
+ \`\`\`bash
1332
+ npx @devosurf/tesser@<version> upgrade
1333
+ pnpm install
1334
+ tesser test --json
1335
+ \`\`\`
1336
+
1337
+ Project name: \`${projectName}\`.
1338
+ `;
1339
+ }
1340
+ function docsReadmeMd(projectName, version) {
1341
+ return `# Tesser agent reference
1342
+
1343
+ Generated for Project \`${projectName}\` by \`@devosurf/tesser@${version}\`. Treat this directory as generated/vendor reference: do not hand-edit files under \`.tesser/docs/\`; customize local policy in \`AGENTS.md\`.
1344
+
1345
+ ## Start here
1346
+ 1. Read \`AGENTS.md\` for project-specific policy.
1347
+ 2. Use [cli.md](./cli.md) for command sequences and safe secret handling.
1348
+ 3. Use [sdk.md](./sdk.md) before editing an Automation.
1349
+ 4. Use [connectors.md](./connectors.md) before adding a Connection or Secret.
1350
+
1351
+ ## Version contract
1352
+ Tesser packages in \`package.json\` should be pinned exactly to \`${version}\`:
1353
+
1354
+ - \`@devosurf/tesser\`
1355
+ - \`@devosurf/tesser-sdk\`
1356
+ - \`@devosurf/tesser-connectors\`
1357
+ - \`@devosurf/tesser-testing\`
1358
+ - \`@devosurf/tesser-server\`
1359
+
1360
+ To upgrade, run the target CLI version so docs and packages move together:
1361
+
1362
+ \`\`\`bash
1363
+ npx @devosurf/tesser@<version> upgrade
1364
+ pnpm install
1365
+ tesser test --json
1366
+ \`\`\`
1367
+
1368
+ Commit \`package.json\`, \`pnpm-lock.yaml\`, \`.tesser/docs/\`, and any Automation/test changes together. Do not commit \`node_modules/\` or local runtime files such as \`.tesser/master.key\`.
1369
+ `;
1370
+ }
1371
+ function cliReferenceMd(version) {
1372
+ return `# Tesser CLI reference
1373
+
1374
+ Generated for \`@devosurf/tesser@${version}\`. The CLI is the agent interface: prefer \`--json\` when output will drive follow-up actions. stdout is data; stderr is logs/progress.
1375
+
1376
+ ## Install and invoke
1377
+
1378
+ \`\`\`bash
1379
+ npm install -g @devosurf/tesser@${version}
1380
+ tesser --version
1381
+
1382
+ # Or run an exact version without global install:
1383
+ npx @devosurf/tesser@${version} --version
1384
+ \`\`\`
1385
+
1386
+ ## Common flow
1387
+
1388
+ \`\`\`bash
1389
+ tesser init my-project --instance https://tesser.example.com
1390
+ cd my-project
1391
+ pnpm install # creates pnpm-lock.yaml; commit it
1392
+ git init && git add -A && git commit -m init
1393
+ tesser login --instance https://tesser.example.com --token "$TESSER_TOKEN"
1394
+ tesser link --json # registers this Project on the Instance
1395
+ tesser test --json
1396
+ tesser build --json
1397
+ tesser deploy --json
1398
+ \`\`\`
1399
+
1400
+ ## Commands agents commonly use
1401
+
1402
+ - \`tesser init <name> [--dir DIR] [--instance URL]\` \u2014 scaffold a Project.
1403
+ - \`tesser upgrade\` \u2014 pin Tesser packages to this CLI version and refresh \`.tesser/docs/\`. To target a version: \`npx @devosurf/tesser@<version> upgrade\`.
1404
+ - \`tesser login --instance URL --token TOKEN\` \u2014 verify and store a profile. Prefer \`TESSER_TOKEN\` over pasting tokens into chat.
1405
+ - \`tesser link [--repo URL] --json\` \u2014 register the Project and print deploy-key/webhook setup data. CLI output must not include private keys or webhook secrets.
1406
+ - \`tesser status --json\` \u2014 instance health and deploy state.
1407
+ - \`tesser test [--smoke-only] [--automation ID] --json\` \u2014 fast local tests plus generated smoke tests.
1408
+ - \`tesser build --json\` \u2014 bundle and statically extract manifests. Use before deploy when changing requirements.
1409
+ - \`tesser dev [--port N] [--no-watch]\` \u2014 local Instance with embedded Postgres under ignored \`.tesser/*\` state.
1410
+ - \`tesser deploy [--ref REF] [--local] [--no-wait] --json\` \u2014 server-side build/test/promotion. Exit code 4 means halted on missing credentials; run \`tesser connect\`.
1411
+ - \`tesser connect [--wait] [--status TOKEN] --json\` \u2014 mint or poll the human connect link. The human supplies credentials in the browser; the agent only sees readiness.
1412
+ - \`tesser secrets list --json\`, \`tesser secrets rm <name> --json\`, \`printenv MY_SECRET | tesser secrets set <name> --value-stdin --json\`. Never put secret values in argv.
1413
+ - \`tesser runs list|show|trigger|signal|cancel --json\`; \`tesser logs <runId> [--follow]\`; \`tesser replay <runId>\`.
1414
+ - \`tesser rollback <automation> --to <version> --json\` \u2014 alias re-point; no rebuild.
1415
+
1416
+ ## Credential safety
1417
+
1418
+ - Do not ask the user to paste secrets into chat. Use masked secret prompts where your harness supports them, or ask the human to run a command locally.
1419
+ - Never pass raw secret values as CLI arguments. Use environment variables, profiles, connect links, OAuth, or \`--value-stdin\`.
1420
+ - Treat connect links and status tokens as sensitive operational material.
1421
+
1422
+ ## Deploy hygiene
1423
+
1424
+ - Run \`pnpm install\` after init/upgrade and commit \`pnpm-lock.yaml\`; the Instance installs from lockfiles for reproducible builds.
1425
+ - Do not commit \`node_modules/\`, \`.env\`, or local runtime state in \`.tesser/*\` outside \`.tesser/docs/\`.
1426
+ - Git is the source of truth. Commit and push before expecting a normal Instance deploy to see changes.
1427
+ `;
1428
+ }
1429
+ function sdkReferenceMd(version) {
1430
+ return `# Tesser SDK reference
1431
+
1432
+ Generated for \`@devosurf/tesser-sdk@${version}\`. Author ordinary async TypeScript; Tesser durability comes only from Steps.
1433
+
1434
+ ## Minimal Automation
1435
+
1436
+ \`\`\`ts
1437
+ import { defineAutomation, onWebhook } from "@devosurf/tesser-sdk";
1438
+ import { z } from "zod";
1439
+
1440
+ export default defineAutomation({
1441
+ id: "hello",
1442
+ trigger: onWebhook({ input: z.object({ name: z.string().default("world") }) }),
1443
+ output: z.object({ greeting: z.string() }),
1444
+
1445
+ run: async (input, ctx) => {
1446
+ const greeting = await ctx.step("compose", async () => \`hello, \${input.name}!\`);
1447
+ return { greeting };
1448
+ },
1449
+ });
1450
+ \`\`\`
1451
+
1452
+ ## Non-negotiable rules
1453
+
1454
+ - Use **Step**, not node/task/action, for durable checkpoints.
1455
+ - Every side effect, external I/O call, Connector Action, bespoke HTTP request, file/network operation, or secret-dependent operation must happen inside \`ctx.step(name, fn)\`.
1456
+ - Step names are stable API for the journal. Use short, descriptive names such as \`fetch-customer\`, \`post-to-slack\`, \`charge-card\`.
1457
+ - Step outputs are journaled and must be serializable. Return plain data, not functions, class instances, streams, sockets, or live handles.
1458
+ - An Automation declares credentials statically at the top level. Do not hide requirements inside \`run\`.
1459
+ - Do not access provider tokens directly. Runtime-injected \`ctx.connections.*\` and \`ctx.secrets.*\` are the only runtime credential surfaces.
1460
+
1461
+ ## Triggers
1462
+
1463
+ \`\`\`ts
1464
+ import { onWebhook, onSchedule, onEvent } from "@devosurf/tesser-sdk";
1465
+
1466
+ const webhook = onWebhook({ input: schema, respond: "sync" });
1467
+ const schedule = onSchedule({ cron: "0 9 * * *", tz: "UTC" });
1468
+ const event = onEvent(myEvent);
1469
+ \`\`\`
1470
+
1471
+ Connector triggers are reached from the Connector import, for example \`github.triggers.issueOpened({ repo: "owner/repo" })\` when supported.
1472
+
1473
+ ## Connections and Secrets
1474
+
1475
+ \`\`\`ts
1476
+ import { defineAutomation, onWebhook, secret } from "@devosurf/tesser-sdk";
1477
+ import { slack } from "@devosurf/tesser-connectors";
1478
+ import { z } from "zod";
1479
+
1480
+ export default defineAutomation({
1481
+ id: "notify",
1482
+ trigger: onWebhook({ input: z.object({ text: z.string() }) }),
1483
+ connections: { slack },
1484
+ secrets: { signingKey: secret({ describe: "Shared HMAC signing key" }) },
1485
+ output: z.object({ ok: z.boolean() }),
1486
+
1487
+ run: async (input, ctx) => {
1488
+ await ctx.step("post-message", () =>
1489
+ ctx.connections.slack.chat.postMessage({ channel: "#ops", text: input.text }),
1490
+ );
1491
+ return { ok: true };
1492
+ },
1493
+ });
1494
+ \`\`\`
1495
+
1496
+ Deploy halts if required Connections or Secrets are missing. The agent should surface the connect link to a human and poll status; it must not collect the secret value itself.
1497
+
1498
+ ## Tests
1499
+
1500
+ Colocate tests beside each Automation.
1501
+
1502
+ \`\`\`ts
1503
+ import { createTest } from "@devosurf/tesser-testing";
1504
+ import automation from "./index";
1505
+
1506
+ test("greets by name", async () => {
1507
+ const t = createTest({ automation });
1508
+ const { result } = await t.run({ input: { name: "tesser" } });
1509
+ expect(result).toEqual({ greeting: "hello, tesser!" });
1510
+ });
1511
+ \`\`\`
1512
+
1513
+ For Connector calls, mock the Step result by Step name. Replay fixtures produced by \`tesser replay <runId>\` should become permanent regression tests.
1514
+ `;
1515
+ }
1516
+ function connectorsReferenceMd(version) {
1517
+ return `# Tesser Connector reference
1518
+
1519
+ Generated for \`@devosurf/tesser-connectors@${version}\`. A Connector is a typed integration; a Connection is an authed runtime instance injected by the Credential broker.
1520
+
1521
+ ## Available connector imports
1522
+
1523
+ \`\`\`ts
1524
+ import {
1525
+ anthropic,
1526
+ claudeCode,
1527
+ github,
1528
+ gmail,
1529
+ googleCalendar,
1530
+ googleDocs,
1531
+ googleDrive,
1532
+ googleSheets,
1533
+ http,
1534
+ pi,
1535
+ resend,
1536
+ slack,
1537
+ } from "@devosurf/tesser-connectors";
1538
+ \`\`\`
1539
+
1540
+ Only import Connectors that the Automation declares in \`connections: { ... }\`.
1541
+
1542
+ ## Pattern
1543
+
1544
+ \`\`\`ts
1545
+ import { defineAutomation, onSchedule } from "@devosurf/tesser-sdk";
1546
+ import { github, slack } from "@devosurf/tesser-connectors";
1547
+ import { z } from "zod";
1548
+
1549
+ export default defineAutomation({
1550
+ id: "digest",
1551
+ trigger: onSchedule({ cron: "0 9 * * *", tz: "UTC" }),
1552
+ connections: { github, slack },
1553
+ output: z.object({ posted: z.boolean(), count: z.number() }),
1554
+
1555
+ run: async (_input, ctx) => {
1556
+ const issues = await ctx.step("fetch-open-issues", () =>
1557
+ ctx.connections.github.issues.list({ state: "open", labels: ["bug"] }),
1558
+ );
1559
+
1560
+ if (issues.length === 0) return { posted: false, count: 0 };
1561
+
1562
+ await ctx.step("post-to-slack", () =>
1563
+ ctx.connections.slack.chat.postMessage({ channel: "#ops", text: \`\${issues.length} bugs\` }),
1564
+ );
1565
+
1566
+ return { posted: true, count: issues.length };
1567
+ },
1568
+ });
1569
+ \`\`\`
1570
+
1571
+ ## Common Actions
1572
+
1573
+ - \`ctx.connections.http.get({ url, headers?, query? })\` and \`ctx.connections.http.request({ method, url, headers?, query?, body?, bodyText? })\`. Generic writes are not retry-safe; still wrap them in a Step.
1574
+ - \`ctx.connections.github.issues.list({ repo?, state?, labels?, limit? })\`, \`issues.create({ repo, title, body?, labels? })\`, \`issues.comment({ repo, number, body })\`, \`repos.get({ repo })\`.
1575
+ - \`ctx.connections.slack.chat.postMessage({ channel, text, threadTs? })\` and related Slack chat/conversation/user Actions.
1576
+ - \`ctx.connections.resend.emails.send(...)\`, Gmail/Google Calendar/Docs/Drive/Sheets Actions, Anthropic model Actions, Claude Code/Pi Harness-related Connectors: inspect package types or examples before using.
1577
+
1578
+ ## Connector triggers
1579
+
1580
+ Some Connectors expose typed triggers, e.g. GitHub issue triggers and Slack event triggers. Use the Connector's \`triggers\` constructors; do not hand-roll webhook delivery unless no Connector exists.
1581
+
1582
+ \`\`\`ts
1583
+ trigger: github.triggers.issueOpened({ repo: "owner/repo" })
1584
+ \`\`\`
1585
+
1586
+ ## Safety rules
1587
+
1588
+ - Connector Actions are not automatically durable. The Automation author must wrap every Action call in \`ctx.step\`.
1589
+ - The imported Connector is not a token-bearing client. The authed client exists only at \`ctx.connections.<name>\` inside \`run\`.
1590
+ - For bespoke APIs, combine \`http\` with declared \`secrets: { ... }\`; do not put API keys in source, tests, logs, CLI args, or committed env files.
1591
+ - If deploy halts on a missing Connection, surface \`tesser connect\` output to the human. The agent must not complete OAuth or paste raw secrets itself.
1592
+ `;
1593
+ }
1594
+
1595
+ // packages/cli/src/commands/init.ts
1176
1596
  var EXAMPLE_AUTOMATION = `import { defineAutomation, onWebhook } from "@devosurf/tesser-sdk";
1177
1597
  import { z } from "zod";
1178
1598
 
@@ -1197,42 +1617,29 @@ test("greets by name", async () => {
1197
1617
  expect(result).toEqual({ greeting: "hello, tesser!" });
1198
1618
  });
1199
1619
  `;
1200
- function init(out, name, opts) {
1620
+ function init(out, name, opts, version) {
1201
1621
  if (!/^[a-z][a-z0-9-]{0,63}$/.test(name)) {
1202
1622
  throw new CliError(EXIT.USAGE, "project name must be kebab-case");
1203
1623
  }
1204
- const root = join4(opts.dir ?? process.cwd(), name);
1205
- if (existsSync4(join4(root, "tesser.json"))) {
1624
+ const root = join5(opts.dir ?? process.cwd(), name);
1625
+ if (existsSync5(join5(root, "tesser.json"))) {
1206
1626
  throw new CliError(EXIT.CONFLICT, `${root} is already a Tesser project`);
1207
1627
  }
1208
- mkdirSync2(join4(root, "automations", "hello"), { recursive: true });
1209
- writeFileSync2(
1210
- join4(root, "tesser.json"),
1628
+ mkdirSync3(join5(root, "automations", "hello"), { recursive: true });
1629
+ writeFileSync3(
1630
+ join5(root, "tesser.json"),
1211
1631
  JSON.stringify({ project: name, ...opts.instance !== void 0 ? { instance: opts.instance } : {} }, null, 2) + "\n"
1212
1632
  );
1213
- writeFileSync2(
1214
- join4(root, "package.json"),
1633
+ writeFileSync3(
1634
+ join5(root, "package.json"),
1215
1635
  JSON.stringify(
1216
- {
1217
- name,
1218
- private: true,
1219
- type: "module",
1220
- packageManager: "pnpm@9.12.0",
1221
- scripts: { test: "tesser test", deploy: "tesser deploy", dev: "tesser dev" },
1222
- dependencies: { "@devosurf/tesser-sdk": "latest", "@devosurf/tesser-connectors": "latest", zod: "^4" },
1223
- devDependencies: {
1224
- "@devosurf/tesser": "latest",
1225
- "@devosurf/tesser-server": "latest",
1226
- "@devosurf/tesser-testing": "latest",
1227
- vitest: "^4"
1228
- }
1229
- },
1636
+ projectPackageJson(name, version),
1230
1637
  null,
1231
1638
  2
1232
1639
  ) + "\n"
1233
1640
  );
1234
- writeFileSync2(
1235
- join4(root, "tsconfig.json"),
1641
+ writeFileSync3(
1642
+ join5(root, "tsconfig.json"),
1236
1643
  JSON.stringify(
1237
1644
  {
1238
1645
  compilerOptions: {
@@ -1249,41 +1656,50 @@ function init(out, name, opts) {
1249
1656
  2
1250
1657
  ) + "\n"
1251
1658
  );
1252
- writeFileSync2(
1253
- join4(root, "vitest.config.ts"),
1659
+ writeFileSync3(
1660
+ join5(root, "vitest.config.ts"),
1254
1661
  `import { defineConfig } from "vitest/config";
1255
1662
  export default defineConfig({ test: { globals: true, include: ["automations/**/*.test.ts"] } });
1256
1663
  `
1257
1664
  );
1258
- writeFileSync2(join4(root, ".gitignore"), "node_modules/\n.tesser/\n.env\n");
1259
- writeFileSync2(join4(root, "automations", "hello", "index.ts"), EXAMPLE_AUTOMATION);
1260
- writeFileSync2(join4(root, "automations", "hello", "index.test.ts"), EXAMPLE_TEST);
1665
+ writeFileSync3(join5(root, ".gitignore"), tesserGitignore());
1666
+ writeProjectAgentInstructions(root, name, version, { overwrite: true });
1667
+ const docs = writeTesserGeneratedDocs(root, name, version);
1668
+ writeFileSync3(join5(root, "automations", "hello", "index.ts"), EXAMPLE_AUTOMATION);
1669
+ writeFileSync3(join5(root, "automations", "hello", "index.test.ts"), EXAMPLE_TEST);
1670
+ const next = [
1671
+ "cd " + name,
1672
+ "pnpm install",
1673
+ "git init && git add -A && git commit -m init",
1674
+ "tesser link",
1675
+ "tesser test"
1676
+ ];
1261
1677
  out.data(
1262
- { created: root, next: ["cd " + name, "git init && git add -A && git commit -m init", "npm install (or pnpm)", "tesser link", "tesser test"] },
1678
+ { created: root, tesserVersion: version, docs, next },
1263
1679
  () => `created ${root}
1264
1680
  next:
1265
1681
  cd ${name}
1682
+ pnpm install # creates pnpm-lock.yaml; commit it
1266
1683
  git init && git add -A && git commit -m init
1267
- pnpm install
1268
- tesser link # register on your instance
1269
- tesser test # green in milliseconds`
1684
+ tesser link # register on your instance
1685
+ tesser test # green in milliseconds`
1270
1686
  );
1271
1687
  }
1272
1688
 
1273
1689
  // packages/cli/src/commands/replay.ts
1274
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, existsSync as existsSync5 } from "node:fs";
1275
- import { join as join5 } from "node:path";
1690
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync4, existsSync as existsSync6 } from "node:fs";
1691
+ import { join as join6 } from "node:path";
1276
1692
  async function replay(out, api2, projectRoot, runId) {
1277
1693
  const { replay: run } = await api2.get(`/runs/${runId}/replay`);
1278
- const dir = join5(projectRoot, "automations", run.automation_id);
1279
- if (!existsSync5(dir)) {
1694
+ const dir = join6(projectRoot, "automations", run.automation_id);
1695
+ if (!existsSync6(dir)) {
1280
1696
  throw new CliError(EXIT.NOT_FOUND, `automation directory not found locally: automations/${run.automation_id}`);
1281
1697
  }
1282
1698
  const shortId = run.id.slice(0, 8);
1283
- const fixtureDir = join5(dir, "__replays__");
1284
- mkdirSync3(fixtureDir, { recursive: true });
1285
- const fixturePath = join5(fixtureDir, `${shortId}.replay.json`);
1286
- writeFileSync3(
1699
+ const fixtureDir = join6(dir, "__replays__");
1700
+ mkdirSync4(fixtureDir, { recursive: true });
1701
+ const fixturePath = join6(fixtureDir, `${shortId}.replay.json`);
1702
+ writeFileSync4(
1287
1703
  fixturePath,
1288
1704
  JSON.stringify(
1289
1705
  {
@@ -1300,8 +1716,8 @@ async function replay(out, api2, projectRoot, runId) {
1300
1716
  2
1301
1717
  ) + "\n"
1302
1718
  );
1303
- const testPath = join5(dir, `replay-${shortId}.test.ts`);
1304
- writeFileSync3(
1719
+ const testPath = join6(dir, `replay-${shortId}.test.ts`);
1720
+ writeFileSync4(
1305
1721
  testPath,
1306
1722
  `// Regression frozen from run ${run.id} (recorded status: ${run.status}).
1307
1723
  // Generated by \`tesser replay\` \u2014 adjust the final assertion once the bug is fixed.
@@ -1334,8 +1750,8 @@ run \`tesser test\` to execute it`
1334
1750
 
1335
1751
  // packages/cli/src/commands/test.ts
1336
1752
  import { execFile } from "node:child_process";
1337
- import { existsSync as existsSync6 } from "node:fs";
1338
- import { join as join6 } from "node:path";
1753
+ import { existsSync as existsSync7 } from "node:fs";
1754
+ import { join as join7 } from "node:path";
1339
1755
  import { promisify } from "node:util";
1340
1756
 
1341
1757
  // packages/testing/src/engine.ts
@@ -1911,7 +2327,7 @@ async function smokeModelScripts(def) {
1911
2327
  }
1912
2328
 
1913
2329
  // packages/testing/src/cassette.ts
1914
- import { mkdirSync as mkdirSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync4 } from "node:fs";
2330
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync5 } from "node:fs";
1915
2331
  import { dirname as dirname3 } from "node:path";
1916
2332
  import { createHash } from "node:crypto";
1917
2333
 
@@ -1920,9 +2336,9 @@ var exec = promisify(execFile);
1920
2336
  function findVitest(projectRoot) {
1921
2337
  let dir = projectRoot;
1922
2338
  for (; ; ) {
1923
- const bin = join6(dir, "node_modules", ".bin", process.platform === "win32" ? "vitest.cmd" : "vitest");
1924
- if (existsSync6(bin)) return bin;
1925
- const parent = join6(dir, "..");
2339
+ const bin = join7(dir, "node_modules", ".bin", process.platform === "win32" ? "vitest.cmd" : "vitest");
2340
+ if (existsSync7(bin)) return bin;
2341
+ const parent = join7(dir, "..");
1926
2342
  if (parent === dir) return null;
1927
2343
  dir = parent;
1928
2344
  }
@@ -1932,7 +2348,7 @@ async function runTests(out, projectRoot, opts) {
1932
2348
  (a) => opts.filter === void 0 || a.automationId === opts.filter
1933
2349
  );
1934
2350
  if (automations.length === 0) {
1935
- throw new CliError(EXIT.USAGE, `no automations found under ${join6(projectRoot, "automations")}`);
2351
+ throw new CliError(EXIT.USAGE, `no automations found under ${join7(projectRoot, "automations")}`);
1936
2352
  }
1937
2353
  const report = {
1938
2354
  passed: true,
@@ -2022,7 +2438,7 @@ async function runTests(out, projectRoot, opts) {
2022
2438
  // packages/cli/src/index.ts
2023
2439
  var exec2 = promisify2(execFile2);
2024
2440
  var program = new Command();
2025
- var VERSION = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8")).version;
2441
+ var VERSION = JSON.parse(readFileSync4(new URL("../package.json", import.meta.url), "utf8")).version;
2026
2442
  function setup() {
2027
2443
  const opts = program.opts();
2028
2444
  return { out: new Output(opts.json ?? false), opts };
@@ -2049,7 +2465,15 @@ program.name("tesser").version(VERSION).description("Code-first, agent-native au
2049
2465
  program.command("init").argument("<name>", "project name (kebab-case)").option("--dir <dir>", "parent directory").option("--instance <url>", "instance URL to write into tesser.json").description("scaffold a new Project (one repo of automations)").action((name, cmdOpts) => {
2050
2466
  const { out } = setup();
2051
2467
  try {
2052
- init(out, name, cmdOpts);
2468
+ init(out, name, cmdOpts, VERSION);
2469
+ } catch (err) {
2470
+ toExit(err, out);
2471
+ }
2472
+ });
2473
+ program.command("upgrade").description("pin Tesser packages to this CLI version and refresh generated Project docs").action(() => {
2474
+ const { out, opts } = setup();
2475
+ try {
2476
+ upgradeProject(out, requireProject(opts), VERSION);
2053
2477
  } catch (err) {
2054
2478
  toExit(err, out);
2055
2479
  }