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

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devosurf/tesser",
3
- "version": "0.1.0-alpha.1",
3
+ "version": "0.1.0-alpha.3",
4
4
  "description": "Tesser CLI — the machine-first interface for agents and humans.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -19,8 +19,8 @@
19
19
  "dependencies": {
20
20
  "commander": "^15.0.0",
21
21
  "esbuild": "^0.28.1",
22
- "@devosurf/tesser-sdk": "0.1.0-alpha.1",
23
- "@devosurf/tesser-testing": "0.1.0-alpha.1"
22
+ "@devosurf/tesser-sdk": "0.1.0-alpha.3",
23
+ "@devosurf/tesser-testing": "0.1.0-alpha.3"
24
24
  },
25
25
  "main": "./dist/index.js",
26
26
  "types": "./src/index.ts",
@@ -143,7 +143,7 @@ function docsReadmeMd(projectName: string, version: string): string {
143
143
  }
144
144
 
145
145
  function cliReferenceMd(version: string): string {
146
- return `# Tesser CLI reference\n\nGenerated 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.\n\n## Install and invoke\n\n\`\`\`bash\nnpm install -g @devosurf/tesser@${version}\ntesser --version\n\n# Or run an exact version without global install:\nnpx @devosurf/tesser@${version} --version\n\`\`\`\n\n## Common flow\n\n\`\`\`bash\ntesser init my-project --instance https://tesser.example.com\ncd my-project\npnpm install # creates pnpm-lock.yaml; commit it\ngit init && git add -A && git commit -m init\ntesser login --instance https://tesser.example.com --token "$TESSER_TOKEN"\ntesser link --json # registers this Project on the Instance\ntesser test --json\ntesser build --json\ntesser deploy --json\n\`\`\`\n\n## Commands agents commonly use\n\n- \`tesser init <name> [--dir DIR] [--instance URL]\` — scaffold a Project.\n- \`tesser upgrade\` — pin Tesser packages to this CLI version and refresh \`.tesser/docs/\`. To target a version: \`npx @devosurf/tesser@<version> upgrade\`.\n- \`tesser login --instance URL --token TOKEN\` — verify and store a profile. Prefer \`TESSER_TOKEN\` over pasting tokens into chat.\n- \`tesser link [--repo URL] --json\` — register the Project and print deploy-key/webhook setup data. CLI output must not include private keys or webhook secrets.\n- \`tesser status --json\` — instance health and deploy state.\n- \`tesser test [--smoke-only] [--automation ID] --json\` — fast local tests plus generated smoke tests.\n- \`tesser build --json\` — bundle and statically extract manifests. Use before deploy when changing requirements.\n- \`tesser dev [--port N] [--no-watch]\` — local Instance with embedded Postgres under ignored \`.tesser/*\` state.\n- \`tesser deploy [--ref REF] [--local] [--no-wait] --json\` — server-side build/test/promotion. Exit code 4 means halted on missing credentials; run \`tesser connect\`.\n- \`tesser connect [--wait] [--status TOKEN] --json\` — mint or poll the human connect link. The human supplies credentials in the browser; the agent only sees readiness.\n- \`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.\n- \`tesser runs list|show|trigger|signal|cancel --json\`; \`tesser logs <runId> [--follow]\`; \`tesser replay <runId>\`.\n- \`tesser rollback <automation> --to <version> --json\` — alias re-point; no rebuild.\n\n## Credential safety\n\n- 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.\n- Never pass raw secret values as CLI arguments. Use environment variables, profiles, connect links, OAuth, or \`--value-stdin\`.\n- Treat connect links and status tokens as sensitive operational material.\n\n## Deploy hygiene\n\n- Run \`pnpm install\` after init/upgrade and commit \`pnpm-lock.yaml\`; the Instance installs from lockfiles for reproducible builds.\n- Do not commit \`node_modules/\`, \`.env\`, or local runtime state in \`.tesser/*\` outside \`.tesser/docs/\`.\n- Git is the source of truth. Commit and push before expecting a normal Instance deploy to see changes.\n`;
146
+ return `# Tesser CLI reference\n\nGenerated 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.\n\n## Install and invoke\n\n\`\`\`bash\nnpm install -g @devosurf/tesser@${version}\ntesser --version\n\n# Or run an exact version without global install:\nnpx @devosurf/tesser@${version} --version\n\`\`\`\n\n## Common flow\n\n\`\`\`bash\ntesser init my-project --instance https://tesser.example.com\ncd my-project\npnpm install # creates pnpm-lock.yaml; commit it\ngit init && git add -A && git commit -m init\ntesser login --url https://tesser.example.com --token "$TESSER_TOKEN"\ntesser link --json # registers this Project on the Instance\ntesser test --json\ntesser build --json\ntesser deploy --json\n\`\`\`\n\n## Commands agents commonly use\n\n- \`tesser init <name> [--dir DIR] [--instance URL]\` — scaffold a Project.\n- \`tesser upgrade\` — pin Tesser packages to this CLI version and refresh \`.tesser/docs/\`. To target a version: \`npx @devosurf/tesser@<version> upgrade\`.\n- \`tesser login --url URL --token TOKEN\` — verify and store a profile. Prefer \`TESSER_TOKEN\` over pasting tokens into chat.\n- \`tesser link [--repo URL] --json\` — register the Project and print deploy-key/webhook setup data. CLI output must not include private keys or webhook secrets.\n- \`tesser status --json\` — instance health and deploy state.\n- \`tesser test [--smoke-only] [--automation ID] --json\` — fast local tests plus generated smoke tests.\n- \`tesser build --json\` — bundle and statically extract manifests. Use before deploy when changing requirements.\n- \`tesser dev [--port N] [--no-watch]\` — local Instance with embedded Postgres under ignored \`.tesser/*\` state.\n- \`tesser deploy [--ref REF] [--local] [--no-wait] --json\` — server-side build/test/promotion. Exit code 4 means halted on missing credentials; run \`tesser connect\`.\n- \`tesser connect [--wait] [--status TOKEN] --json\` — mint or poll the human connect link. The human supplies credentials in the browser; the agent only sees readiness.\n- \`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.\n- \`tesser runs list|show|trigger|signal|cancel --json\`; \`tesser logs <runId> [--follow]\`; \`tesser replay <runId>\`.\n- \`tesser rollback <automation> --to <version> --json\` — alias re-point; no rebuild.\n\n## Credential safety\n\n- 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.\n- Never pass raw secret values as CLI arguments. Use environment variables, profiles, connect links, OAuth, or \`--value-stdin\`.\n- Treat connect links and status tokens as sensitive operational material.\n\n## Deploy hygiene\n\n- Run \`pnpm install\` after init/upgrade and commit \`pnpm-lock.yaml\`; the Instance installs from lockfiles for reproducible builds.\n- Do not commit \`node_modules/\`, \`.env\`, or local runtime state in \`.tesser/*\` outside \`.tesser/docs/\`.\n- Git is the source of truth. Commit and push before expecting a normal Instance deploy to see changes.\n`;
147
147
  }
148
148
 
149
149
  function sdkReferenceMd(version: string): string {
@@ -151,5 +151,5 @@ function sdkReferenceMd(version: string): string {
151
151
  }
152
152
 
153
153
  function connectorsReferenceMd(version: string): string {
154
- return `# Tesser Connector reference\n\nGenerated for \`@devosurf/tesser-connectors@${version}\`. A Connector is a typed integration; a Connection is an authed runtime instance injected by the Credential broker.\n\n## Available connector imports\n\n\`\`\`ts\nimport {\n anthropic,\n claudeCode,\n github,\n gmail,\n googleCalendar,\n googleDocs,\n googleDrive,\n googleSheets,\n http,\n pi,\n resend,\n slack,\n} from "@devosurf/tesser-connectors";\n\`\`\`\n\nOnly import Connectors that the Automation declares in \`connections: { ... }\`.\n\n## Pattern\n\n\`\`\`ts\nimport { defineAutomation, onSchedule } from "@devosurf/tesser-sdk";\nimport { github, slack } from "@devosurf/tesser-connectors";\nimport { z } from "zod";\n\nexport default defineAutomation({\n id: "digest",\n trigger: onSchedule({ cron: "0 9 * * *", tz: "UTC" }),\n connections: { github, slack },\n output: z.object({ posted: z.boolean(), count: z.number() }),\n\n run: async (_input, ctx) => {\n const issues = await ctx.step("fetch-open-issues", () =>\n ctx.connections.github.issues.list({ state: "open", labels: ["bug"] }),\n );\n\n if (issues.length === 0) return { posted: false, count: 0 };\n\n await ctx.step("post-to-slack", () =>\n ctx.connections.slack.chat.postMessage({ channel: "#ops", text: \`\${issues.length} bugs\` }),\n );\n\n return { posted: true, count: issues.length };\n },\n});\n\`\`\`\n\n## Common Actions\n\n- \`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.\n- \`ctx.connections.github.issues.list({ repo?, state?, labels?, limit? })\`, \`issues.create({ repo, title, body?, labels? })\`, \`issues.comment({ repo, number, body })\`, \`repos.get({ repo })\`.\n- \`ctx.connections.slack.chat.postMessage({ channel, text, threadTs? })\` and related Slack chat/conversation/user Actions.\n- \`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.\n\n## Connector triggers\n\nSome 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.\n\n\`\`\`ts\ntrigger: github.triggers.issueOpened({ repo: "owner/repo" })\n\`\`\`\n\n## Safety rules\n\n- Connector Actions are not automatically durable. The Automation author must wrap every Action call in \`ctx.step\`.\n- The imported Connector is not a token-bearing client. The authed client exists only at \`ctx.connections.<name>\` inside \`run\`.\n- For bespoke APIs, combine \`http\` with declared \`secrets: { ... }\`; do not put API keys in source, tests, logs, CLI args, or committed env files.\n- 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.\n`;
154
+ return `# Tesser Connector reference\n\nGenerated for \`@devosurf/tesser-connectors@${version}\`. A Connector is a typed integration; a Connection is an authed runtime instance injected by the Credential broker.\n\n## Available connector imports\n\n\`\`\`ts\nimport {\n anthropic,\n claudeCode,\n github,\n gmail,\n googleCalendar,\n googleDocs,\n googleDrive,\n googleSheets,\n http,\n outlookMail,\n pi,\n resend,\n slack,\n} from "@devosurf/tesser-connectors";\n\`\`\`\n\nOnly import Connectors that the Automation declares in \`connections: { ... }\`.\n\n## Pattern\n\n\`\`\`ts\nimport { defineAutomation, onSchedule } from "@devosurf/tesser-sdk";\nimport { github, slack } from "@devosurf/tesser-connectors";\nimport { z } from "zod";\n\nexport default defineAutomation({\n id: "digest",\n trigger: onSchedule({ cron: "0 9 * * *", tz: "UTC" }),\n connections: { github, slack },\n output: z.object({ posted: z.boolean(), count: z.number() }),\n\n run: async (_input, ctx) => {\n const issues = await ctx.step("fetch-open-issues", () =>\n ctx.connections.github.issues.list({ state: "open", labels: ["bug"] }),\n );\n\n if (issues.length === 0) return { posted: false, count: 0 };\n\n await ctx.step("post-to-slack", () =>\n ctx.connections.slack.chat.postMessage({ channel: "#ops", text: \`\${issues.length} bugs\` }),\n );\n\n return { posted: true, count: issues.length };\n },\n});\n\`\`\`\n\n## Common Actions\n\n- \`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.\n- \`ctx.connections.github.issues.list({ repo?, state?, labels?, limit? })\`, \`issues.create({ repo, title, body?, labels? })\`, \`issues.comment({ repo, number, body })\`, \`repos.get({ repo })\`.\n- \`ctx.connections.slack.chat.postMessage({ channel, text, threadTs? })\` and related Slack chat/conversation/user Actions.\n- \`ctx.connections.resend.emails.send(...)\`, Gmail and Outlook Mail Actions (Outlook accepts optional \`mailbox\` for shared mailbox \`/users/{userPrincipalName}\` access), Google Calendar/Docs/Drive/Sheets Actions, Anthropic model Actions, Claude Code/Pi Harness-related Connectors: inspect package types or examples before using.\n\n## Connector triggers\n\nSome Connectors expose typed triggers, e.g. GitHub issue triggers, Slack event triggers, Gmail/Outlook mailbox poll triggers. Use the Connector's \`triggers\` constructors; do not hand-roll webhook delivery unless no Connector exists.\n\n\`\`\`ts\ntrigger: github.triggers.issueOpened({ repo: "owner/repo" })\n\`\`\`\n\n## Safety rules\n\n- Connector Actions are not automatically durable. The Automation author must wrap every Action call in \`ctx.step\`.\n- The imported Connector is not a token-bearing client. The authed client exists only at \`ctx.connections.<name>\` inside \`run\`.\n- For bespoke APIs, combine \`http\` with declared \`secrets: { ... }\`; do not put API keys in source, tests, logs, CLI args, or committed env files.\n- 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.\n`;
155
155
  }
package/src/config.ts CHANGED
@@ -20,6 +20,8 @@ export interface LinkManifest {
20
20
  instance?: string;
21
21
  }
22
22
 
23
+ export const DEFAULT_INSTANCE_URL = "http://localhost:8377";
24
+
23
25
  const CONFIG_PATH = join(
24
26
  process.env["TESSER_CONFIG_DIR"] ?? join(homedir(), ".config", "tesser"),
25
27
  "config.json",
@@ -79,7 +81,7 @@ export function resolveTarget(opts: { url?: string; token?: string; profile?: st
79
81
  manifest?.instance ??
80
82
  process.env["TESSER_URL"] ??
81
83
  profile.url ??
82
- "http://localhost:8377",
84
+ DEFAULT_INSTANCE_URL,
83
85
  token: opts.token ?? process.env["TESSER_TOKEN"] ?? profile.token,
84
86
  project: manifest?.project,
85
87
  projectRoot,
package/src/index.ts CHANGED
@@ -10,6 +10,7 @@ import { extractAutomationManifest } from "@devosurf/tesser-sdk/internal";
10
10
  import { ApiClient } from "./client.js";
11
11
  import { readConfig, readLinkManifest, resolveTarget, writeConfig } from "./config.js";
12
12
  import { EXIT } from "./exit-codes.js";
13
+ import { DEFAULT_INSTANCE_URL, resolveLoginInstance, type LoginCommandOptions } from "./login.js";
13
14
  import { CliError, Output, toExit } from "./output.js";
14
15
  import { discoverLocalAutomations, loadAutomationDef } from "./project.js";
15
16
  import { authClaudeCode, authPi } from "./commands/auth.js";
@@ -96,21 +97,23 @@ program
96
97
  program
97
98
  .command("login")
98
99
  .option("--token <token>", "API token from the instance (printed at first boot)")
99
- .option("--instance <url>", "instance URL", "http://localhost:8377")
100
+ .option("--url <url>", `instance URL (defaults to ${DEFAULT_INSTANCE_URL})`)
101
+ .option("--instance <url>", "deprecated alias for --url")
100
102
  .option("--save-profile <name>", "profile name", "default")
101
103
  .description("store instance credentials in ~/.config/tesser (or use env TESSER_TOKEN)")
102
- .action(async (cmdOpts: { token?: string; instance: string; saveProfile: string }) => {
104
+ .action(async (cmdOpts: LoginCommandOptions) => {
103
105
  const { out, opts } = setup();
104
106
  try {
105
107
  const token = cmdOpts.token ?? opts.token;
106
108
  if (!token) throw new CliError(EXIT.USAGE, "missing required option '--token <token>'");
107
- const client = new ApiClient(cmdOpts.instance, token);
109
+ const instance = resolveLoginInstance(cmdOpts, opts);
110
+ const client = new ApiClient(instance, token);
108
111
  await client.get("/health"); // verify before storing
109
112
  const config = readConfig();
110
- config.profiles = { ...config.profiles, [cmdOpts.saveProfile]: { url: cmdOpts.instance, token } };
113
+ config.profiles = { ...config.profiles, [cmdOpts.saveProfile]: { url: instance, token } };
111
114
  config.current = cmdOpts.saveProfile;
112
115
  writeConfig(config);
113
- out.data({ profile: cmdOpts.saveProfile, url: cmdOpts.instance }, () => `logged in to ${cmdOpts.instance} (profile "${cmdOpts.saveProfile}")`);
116
+ out.data({ profile: cmdOpts.saveProfile, url: instance }, () => `logged in to ${instance} (profile "${cmdOpts.saveProfile}")`);
114
117
  } catch (err) {
115
118
  toExit(err, out);
116
119
  }
@@ -0,0 +1,20 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { DEFAULT_INSTANCE_URL, resolveLoginInstance } from "./login.js";
3
+
4
+ describe("tesser login target resolution", () => {
5
+ test("accepts --url on the login command", () => {
6
+ expect(resolveLoginInstance({ url: "https://inst.example" }, {})).toBe("https://inst.example");
7
+ });
8
+
9
+ test("accepts the global --url before login", () => {
10
+ expect(resolveLoginInstance({}, { url: "https://global.example" })).toBe("https://global.example");
11
+ });
12
+
13
+ test("keeps --instance as a backwards-compatible alias", () => {
14
+ expect(resolveLoginInstance({ instance: "https://old.example" }, { url: "https://global.example" })).toBe("https://old.example");
15
+ });
16
+
17
+ test("defaults to the local dev instance", () => {
18
+ expect(resolveLoginInstance({}, {})).toBe(DEFAULT_INSTANCE_URL);
19
+ });
20
+ });
package/src/login.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { DEFAULT_INSTANCE_URL } from "./config.js";
2
+
3
+ export { DEFAULT_INSTANCE_URL };
4
+
5
+ export interface LoginCommandOptions {
6
+ token?: string;
7
+ url?: string;
8
+ instance?: string;
9
+ saveProfile: string;
10
+ }
11
+
12
+ export interface LoginGlobalOptions {
13
+ url?: string;
14
+ }
15
+
16
+ export function resolveLoginInstance(cmdOpts: Pick<LoginCommandOptions, "url" | "instance">, globalOpts: LoginGlobalOptions): string {
17
+ return cmdOpts.url ?? cmdOpts.instance ?? globalOpts.url ?? DEFAULT_INSTANCE_URL;
18
+ }