@treeseed/cli 0.8.14 → 0.8.16

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.
@@ -7,6 +7,38 @@ import { createMarketClientForInvocation, marketAuthRoot } from "./market-utils.
7
7
  function sleep(ms) {
8
8
  return new Promise((resolve) => setTimeout(resolve, ms));
9
9
  }
10
+ function localWebApprovalUrlFromApiUrl(value, profileId) {
11
+ const explicit = process.env.TREESEED_SITE_URL?.trim() || process.env.BETTER_AUTH_URL?.trim();
12
+ if (explicit && profileId === "local") {
13
+ const url2 = new URL(value);
14
+ return new URL(`${url2.pathname}${url2.search}${url2.hash}`, explicit.replace(/\/+$/u, "")).toString();
15
+ }
16
+ const url = new URL(value);
17
+ if (profileId === "local" && (url.hostname === "127.0.0.1" || url.hostname === "localhost") && url.port === "3000") {
18
+ url.port = "4321";
19
+ return url.toString();
20
+ }
21
+ return value;
22
+ }
23
+ function centralWebApprovalUrlFromApiUrl(value, profileId) {
24
+ if (profileId !== "central") {
25
+ return value;
26
+ }
27
+ const explicit = process.env.TREESEED_CENTRAL_MARKET_WEB_URL?.trim();
28
+ const fallback = explicit || "https://treeseed.ai";
29
+ const url = new URL(value);
30
+ if (url.protocol === "http:" && (url.hostname === "127.0.0.1" || url.hostname === "localhost")) {
31
+ return new URL(`${url.pathname}${url.search}${url.hash}`, fallback.replace(/\/+$/u, "")).toString();
32
+ }
33
+ return value;
34
+ }
35
+ function approvalUrlForDisplay(value, profileId) {
36
+ try {
37
+ return centralWebApprovalUrlFromApiUrl(localWebApprovalUrlFromApiUrl(value, profileId), profileId);
38
+ } catch {
39
+ return value;
40
+ }
41
+ }
10
42
  const handleAuthLogin = async (invocation, context) => {
11
43
  try {
12
44
  const tenantRoot = marketAuthRoot(context);
@@ -15,8 +47,9 @@ const handleAuthLogin = async (invocation, context) => {
15
47
  clientName: "treeseed-cli",
16
48
  scopes: ["auth:me", "market"]
17
49
  });
50
+ const approvalUrl = approvalUrlForDisplay(started.verificationUriComplete, profile.id);
18
51
  if (context.outputFormat !== "json") {
19
- context.write(`Open ${started.verificationUriComplete}`, "stdout");
52
+ context.write(`Open ${approvalUrl}`, "stdout");
20
53
  context.write(`User code: ${started.userCode}`, "stdout");
21
54
  context.write("Waiting for approval...", "stdout");
22
55
  }
@@ -1,13 +1,24 @@
1
1
  import { pathToFileURL } from "node:url";
2
2
  import { dirname, resolve } from "node:path";
3
- import { mkdirSync, writeFileSync } from "node:fs";
3
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
4
4
  import { formatSeedDiagnostics, formatSeedPlan, loadAndPlanSeed } from "@treeseed/sdk/seeds";
5
5
  import { MarketApiError } from "@treeseed/sdk/market-client";
6
- import { createMarketClientForInvocation } from "./market-utils.js";
6
+ import { createMarketClientForInvocation, marketAuthRoot, marketSelector } from "./market-utils.js";
7
+ import { resolveMarketProfile, resolveMarketSession } from "@treeseed/sdk/market-client";
7
8
  async function loadLocalSeedModule(projectRoot) {
8
- const moduleUrl = pathToFileURL(resolve(projectRoot, "src", "lib", "market", "seeds", "apply.js")).href;
9
+ const apiModulePath = resolve(projectRoot, "src", "lib", "market", "seeds", "local-api.js");
10
+ const moduleUrl = pathToFileURL(existsSync(apiModulePath) ? apiModulePath : resolve(projectRoot, "src", "lib", "market", "seeds", "apply.js")).href;
9
11
  return await import(moduleUrl);
10
12
  }
13
+ function requireLocalSeedSession(invocation, context) {
14
+ const selector = marketSelector(invocation) ?? "local";
15
+ const profile = resolveMarketProfile(selector);
16
+ const session = resolveMarketSession(marketAuthRoot(context), profile.id);
17
+ if (!session?.accessToken) {
18
+ throw new Error(`Not logged in to market "${profile.id}". Run treeseed auth:login --market ${profile.id}.`);
19
+ }
20
+ return { profile, session };
21
+ }
11
22
  function seedRequestBody(invocation) {
12
23
  return {
13
24
  ...typeof invocation.args.environments === "string" ? { environments: invocation.args.environments.split(",").map((entry) => entry.trim()).filter(Boolean) } : {},
@@ -116,17 +127,20 @@ async function handleSeedExport(invocation, context) {
116
127
  const { client } = createMarketClientForInvocation(invocation, context, { requireAuth: true });
117
128
  payload = await client.exportSeed(team, body);
118
129
  } else {
130
+ const { session } = requireLocalSeedSession(invocation, context);
119
131
  const localModule = await loadLocalSeedModule(context.cwd);
120
- if (typeof localModule.exportSeedFromCli !== "function") {
132
+ const exporter = localModule.exportLocalSeedViaApiFromCli ?? localModule.exportSeedFromCli;
133
+ if (typeof exporter !== "function") {
121
134
  throw new Error("Local seed export service is not available in this market project.");
122
135
  }
123
- payload = await localModule.exportSeedFromCli({
136
+ payload = await exporter({
124
137
  projectRoot: context.cwd,
125
138
  seedName,
126
139
  team,
127
140
  environments: typeof invocation.args.environments === "string" ? invocation.args.environments : void 0,
128
141
  includePrivate: invocation.args.includePrivate === true,
129
142
  includeArtifacts: invocation.args.includeArtifacts === true,
143
+ accessToken: session.accessToken,
130
144
  env: context.env
131
145
  });
132
146
  }
@@ -187,22 +201,37 @@ const handleSeed = async (invocation, context) => {
187
201
  }
188
202
  };
189
203
  }
204
+ const localAuth = planned.plan.environments.every((environment) => environment === "local") && !wantsValidate ? (() => {
205
+ try {
206
+ return requireLocalSeedSession(invocation, context);
207
+ } catch (error) {
208
+ return { error };
209
+ }
210
+ })() : null;
211
+ if (localAuth && "error" in localAuth) {
212
+ return remoteSeedError(localAuth.error, "seed");
213
+ }
190
214
  if (!wantsApply && !wantsValidate && planned.plan.environments.length === 1 && planned.plan.environments[0] === "local") {
191
215
  try {
192
216
  const localModule = await loadLocalSeedModule(context.cwd);
193
- if (typeof localModule.planLocalSeedFromCli === "function") {
194
- const localPlanned = await localModule.planLocalSeedFromCli({
217
+ const localPlanner = localModule.planLocalSeedViaApiFromCli ?? localModule.planLocalSeedFromCli;
218
+ if (typeof localPlanner === "function") {
219
+ const localPlanned = await localPlanner({
195
220
  projectRoot: context.cwd,
196
221
  seedName,
197
222
  environments: typeof invocation.args.environments === "string" ? invocation.args.environments : void 0,
198
223
  mode,
224
+ accessToken: localAuth?.session.accessToken,
199
225
  env: context.env
200
226
  });
201
227
  if (localPlanned.plan) {
202
228
  planned.plan = localPlanned.plan;
203
229
  }
204
230
  }
205
- } catch {
231
+ } catch (error) {
232
+ if (error instanceof Error && /not logged in|authentication|permission denied/iu.test(error.message)) {
233
+ return remoteSeedError(error, "seed");
234
+ }
206
235
  planned.plan.diagnostics.push({
207
236
  severity: "warning",
208
237
  code: "seed.local_state_unavailable",
@@ -222,15 +251,16 @@ const handleSeed = async (invocation, context) => {
222
251
  }
223
252
  }
224
253
  const localModule = await loadLocalSeedModule(context.cwd);
225
- if (typeof localModule.applyLocalSeedFromCli !== "function") {
254
+ const runner = localModule.applyLocalSeedViaApiFromCli ?? localModule.applyLocalSeedFromCli;
255
+ if (typeof runner !== "function") {
226
256
  throw new Error("Local seed apply service is not available in this market project.");
227
257
  }
228
- const runner = localModule.applyLocalSeedFromCli;
229
258
  const applied = await runner({
230
259
  projectRoot: context.cwd,
231
260
  seedName,
232
261
  environments: typeof invocation.args.environments === "string" ? invocation.args.environments : void 0,
233
262
  plan: planned.plan,
263
+ accessToken: localAuth?.session.accessToken,
234
264
  env: context.env
235
265
  });
236
266
  const message = "Local seed apply completed.";
@@ -30,7 +30,7 @@ const DEV_RUNTIME_OPTIONS = [
30
30
  { name: "managerPort", flags: "--manager-port <port>", description: "Port used for the local manager service URL.", kind: "string" },
31
31
  { name: "setup", flags: "--setup <mode>", description: "Control automatic local runtime setup.", kind: "enum", values: ["auto", "check", "off"] },
32
32
  { name: "feedback", flags: "--feedback <mode>", description: "Control live feedback, service restarts, and browser reload stamps.", kind: "enum", values: ["live", "restart", "off"] },
33
- { name: "open", flags: "--open <mode>", description: "Control whether dev opens the browser after readiness.", kind: "enum", values: ["auto", "on", "off"] },
33
+ { name: "open", flags: "--open <mode>", description: "Control whether dev opens the browser after readiness. Defaults to off; use --open on to launch it.", kind: "enum", values: ["auto", "on", "off"] },
34
34
  { name: "plan", flags: "--plan", description: "Print the dev runtime plan and exit without starting services.", kind: "boolean" },
35
35
  { name: "reset", flags: "--reset", description: "Clear local dev runtime state before setup, migrations, and service startup.", kind: "boolean" },
36
36
  { name: "json", flags: "--json", description: "Emit structured JSON or newline-delimited dev events.", kind: "boolean" },
@@ -726,6 +726,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
726
726
  longSummary: ["Auth:login authenticates the CLI against the configured Treeseed API so later provider-aware and remote-aware workflows can run without missing-credential failures."],
727
727
  examples: [
728
728
  example("treeseed auth:login", "Log in with the default host", "Authenticate the CLI against the configured default Treeseed API host."),
729
+ example("treeseed auth:login --market local", "Log in to local dev", "Authenticate against the local Treeseed API at TREESEED_MARKET_API_BASE_URL or http://127.0.0.1:3000."),
729
730
  example("treeseed auth:login --host production", "Target a specific host id", "Override the configured default host for this login session."),
730
731
  example("treeseed auth:login --json", "Automate auth workflows", "Emit structured auth results where supported for scripts and agents.")
731
732
  ]
@@ -1171,7 +1172,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
1171
1172
  })],
1172
1173
  ["dev", command({
1173
1174
  options: DEV_RUNTIME_OPTIONS,
1174
- examples: ["treeseed dev", "treeseed dev --reset", "treeseed dev --reset --plan --json", "treeseed dev --surfaces web,api --plan --json", "treeseed dev --surface web --port 4322 --open off"],
1175
+ examples: ["treeseed dev", "treeseed dev --reset", "treeseed dev --reset --plan --json", "treeseed dev --surfaces web,api --plan --json", "treeseed dev --surface web --port 4322"],
1175
1176
  help: {
1176
1177
  longSummary: [
1177
1178
  "Dev starts the unified local Treeseed runtime as a foreground supervisor so you can work against the integrated web, API, manager, worker, and supporting local surfaces.",
@@ -1181,6 +1182,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
1181
1182
  "Run from the tenant or workspace root you want to develop.",
1182
1183
  "Use `--plan --json` when you want to inspect selected surfaces, commands, setup steps, readiness checks, watched paths, and restart policy without starting services.",
1183
1184
  "Use `--reset` when you want a fresh local D1 database, Mailpit inbox, generated worker bundle, and Wrangler temp output without deleting configuration.",
1185
+ "Dev prints the local web URL after readiness; it does not open a browser unless you pass `--open on` or `--open auto`.",
1184
1186
  "Keep the foreground process running while you test. Press Ctrl+C to stop the supervised stack and free the local ports."
1185
1187
  ],
1186
1188
  examples: [
@@ -1191,12 +1193,13 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
1191
1193
  example("treeseed dev --surfaces web,api,worker --plan --json", "Inspect selected surfaces", "Plan a comma-separated set of local surfaces without starting the supervisor."),
1192
1194
  example("treeseed dev --surface api --plan --json", "Inspect the API surface", "Plan the local API runtime without the web UI."),
1193
1195
  example("treeseed dev --surfaces integrated,agents", "Opt into agents diagnostics", "Start the integrated local platform plus the agents diagnostic loop."),
1194
- example("treeseed dev --surface web --port 4322 --open off", "Run only the web surface", "Start the Astro UI on a specific port without opening a browser."),
1196
+ example("treeseed dev --surface web --port 4322", "Run only the web surface", "Start the Astro UI on a specific port and print the URL when ready."),
1197
+ example("treeseed dev --open on", "Open the browser explicitly", "Start the integrated runtime and launch the local web URL after readiness."),
1195
1198
  example("trsd dev", "Use the short alias", "Start the same local runtime through the shorter entrypoint."),
1196
1199
  example("treeseed dev --json", "Stream dev events", "Emit newline-delimited events while the long-running dev process supervises local services.")
1197
1200
  ],
1198
1201
  outcomes: [
1199
- "Starts the selected local surfaces, waits for readiness, and then remains attached as the live supervisor.",
1202
+ "Starts the selected local surfaces, waits for readiness, prints the local URL, and then remains attached as the live supervisor.",
1200
1203
  "Restarts required crashed surfaces with capped exponential backoff and keeps setup/readiness failures alive for retry.",
1201
1204
  "Stops watchers first and then terminates service process groups when the foreground command exits."
1202
1205
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/cli",
3
- "version": "0.8.14",
3
+ "version": "0.8.16",
4
4
  "description": "Operator-facing Treeseed CLI package.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -45,7 +45,7 @@
45
45
  "release:publish": "node ./scripts/run-ts.mjs ./scripts/publish-package.ts"
46
46
  },
47
47
  "dependencies": {
48
- "@treeseed/sdk": "0.8.14",
48
+ "@treeseed/sdk": "0.8.16",
49
49
  "ink": "^7.0.0",
50
50
  "react": "^19.2.5"
51
51
  },