@treeseed/cli 0.6.25 → 0.6.27

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.
@@ -1,25 +1,19 @@
1
- import { RemoteTreeseedAuthClient, RemoteTreeseedClient } from "@treeseed/sdk/remote";
2
1
  import {
3
- resolveTreeseedRemoteConfig,
4
- setTreeseedRemoteSession,
5
- TreeseedKeyAgentError
6
- } from "@treeseed/sdk/workflow-support";
2
+ setMarketSession
3
+ } from "@treeseed/sdk/market-client";
4
+ import { TreeseedKeyAgentError } from "@treeseed/sdk/workflow-support";
7
5
  import { guidedResult } from "./utils.js";
6
+ import { createMarketClientForInvocation, marketAuthRoot } from "./market-utils.js";
8
7
  function sleep(ms) {
9
8
  return new Promise((resolve) => setTimeout(resolve, ms));
10
9
  }
11
10
  const handleAuthLogin = async (invocation, context) => {
12
11
  try {
13
- const tenantRoot = context.cwd;
14
- const remoteConfig = resolveTreeseedRemoteConfig(tenantRoot, context.env);
15
- const hostId = typeof invocation.args.host === "string" ? invocation.args.host : remoteConfig.activeHostId;
16
- const client = new RemoteTreeseedAuthClient(new RemoteTreeseedClient({
17
- ...remoteConfig,
18
- activeHostId: hostId
19
- }));
20
- const started = await client.startDeviceFlow({
12
+ const tenantRoot = marketAuthRoot(context);
13
+ const { profile, client } = createMarketClientForInvocation(invocation, context);
14
+ const started = await client.startDeviceLogin({
21
15
  clientName: "treeseed-cli",
22
- scopes: ["auth:me", "sdk", "operations"]
16
+ scopes: ["auth:me", "market"]
23
17
  });
24
18
  if (context.outputFormat !== "json") {
25
19
  context.write(`Open ${started.verificationUriComplete}`, "stdout");
@@ -28,10 +22,10 @@ const handleAuthLogin = async (invocation, context) => {
28
22
  }
29
23
  const deadline = Date.parse(started.expiresAt);
30
24
  while (Date.now() < deadline) {
31
- const response = await client.pollDeviceFlow({ deviceCode: started.deviceCode });
25
+ const response = await client.pollDeviceLogin({ deviceCode: started.deviceCode });
32
26
  if (response.ok && response.status === "approved") {
33
- setTreeseedRemoteSession(tenantRoot, {
34
- hostId,
27
+ setMarketSession(tenantRoot, {
28
+ marketId: profile.id,
35
29
  accessToken: response.accessToken,
36
30
  refreshToken: response.refreshToken,
37
31
  expiresAt: response.expiresAt,
@@ -41,12 +35,14 @@ const handleAuthLogin = async (invocation, context) => {
41
35
  command: "auth:login",
42
36
  summary: "Treeseed API login completed successfully.",
43
37
  facts: [
44
- { label: "Host", value: hostId },
38
+ { label: "Market", value: profile.id },
39
+ { label: "URL", value: profile.baseUrl },
45
40
  { label: "Principal", value: response.principal.displayName ?? response.principal.id },
46
41
  { label: "Scopes", value: response.principal.scopes.join(", ") }
47
42
  ],
48
43
  report: {
49
- hostId,
44
+ marketId: profile.id,
45
+ baseUrl: profile.baseUrl,
50
46
  principal: response.principal
51
47
  }
52
48
  });
@@ -1,20 +1,25 @@
1
1
  import {
2
- clearTreeseedRemoteSession,
3
- resolveTreeseedRemoteConfig,
4
2
  TreeseedKeyAgentError
5
3
  } from "@treeseed/sdk/workflow-support";
4
+ import { clearMarketSession } from "@treeseed/sdk/market-client";
6
5
  import { guidedResult } from "./utils.js";
6
+ import { createMarketClientForInvocation, marketAuthRoot } from "./market-utils.js";
7
7
  const handleAuthLogout = async (invocation, context) => {
8
8
  try {
9
- const tenantRoot = context.cwd;
10
- const remoteConfig = resolveTreeseedRemoteConfig(tenantRoot, context.env);
11
- const hostId = typeof invocation.args.host === "string" ? invocation.args.host : remoteConfig.activeHostId;
12
- clearTreeseedRemoteSession(tenantRoot, hostId);
9
+ const tenantRoot = marketAuthRoot(context);
10
+ const { profile, client, session } = createMarketClientForInvocation(invocation, context);
11
+ if (session?.accessToken) {
12
+ await client.logout().catch(() => null);
13
+ }
14
+ clearMarketSession(tenantRoot, profile.id);
13
15
  return guidedResult({
14
16
  command: "auth:logout",
15
17
  summary: "Cleared the local Treeseed API session.",
16
- facts: [{ label: "Host", value: hostId }],
17
- report: { hostId }
18
+ facts: [
19
+ { label: "Market", value: profile.id },
20
+ { label: "URL", value: profile.baseUrl }
21
+ ],
22
+ report: { marketId: profile.id, baseUrl: profile.baseUrl }
18
23
  });
19
24
  } catch (error) {
20
25
  if (error instanceof TreeseedKeyAgentError) {
@@ -1,22 +1,48 @@
1
- import { RemoteTreeseedAuthClient, RemoteTreeseedClient } from "@treeseed/sdk/remote";
2
- import { resolveTreeseedRemoteConfig, TreeseedKeyAgentError } from "@treeseed/sdk/workflow-support";
1
+ import { loadMarketRegistryState, resolveMarketSession } from "@treeseed/sdk/market-client";
2
+ import { TreeseedKeyAgentError } from "@treeseed/sdk/workflow-support";
3
3
  import { guidedResult } from "./utils.js";
4
- const handleAuthWhoAmI = async (_invocation, context) => {
4
+ import { createMarketClientForInvocation, marketAuthRoot } from "./market-utils.js";
5
+ const handleAuthWhoAmI = async (invocation, context) => {
5
6
  try {
6
- const remoteConfig = resolveTreeseedRemoteConfig(context.cwd, context.env);
7
- const client = new RemoteTreeseedAuthClient(new RemoteTreeseedClient(remoteConfig));
8
- const response = await client.whoAmI();
7
+ if (invocation.args.allMarkets === true) {
8
+ const tenantRoot = marketAuthRoot(context);
9
+ const state = loadMarketRegistryState();
10
+ const sessions = state.profiles.map((profile2) => ({
11
+ profile: profile2,
12
+ session: resolveMarketSession(tenantRoot, profile2.id)
13
+ }));
14
+ return guidedResult({
15
+ command: "auth:whoami",
16
+ summary: "Treeseed market identities",
17
+ sections: [{
18
+ title: "Markets",
19
+ lines: sessions.map(({ profile: profile2, session }) => `${profile2.id}: ${session?.principal?.displayName ?? session?.principal?.id ?? "(not logged in)"}`)
20
+ }],
21
+ report: {
22
+ markets: sessions.map(({ profile: profile2, session }) => ({
23
+ marketId: profile2.id,
24
+ baseUrl: profile2.baseUrl,
25
+ principal: session?.principal ?? null
26
+ }))
27
+ }
28
+ });
29
+ }
30
+ const { profile, client } = createMarketClientForInvocation(invocation, context, { requireAuth: true });
31
+ const response = await client.me();
9
32
  return guidedResult({
10
33
  command: "auth:whoami",
11
34
  summary: "Treeseed API identity",
12
35
  facts: [
13
- { label: "Host", value: remoteConfig.activeHostId },
14
- { label: "Principal", value: response.payload.displayName ?? response.payload.id },
15
- { label: "Scopes", value: response.payload.scopes.join(", ") }
36
+ { label: "Market", value: profile.id },
37
+ { label: "URL", value: profile.baseUrl },
38
+ { label: "Principal", value: response.payload.principal.displayName ?? response.payload.principal.id },
39
+ { label: "Scopes", value: response.payload.principal.scopes.join(", ") }
16
40
  ],
17
41
  report: {
18
- hostId: remoteConfig.activeHostId,
19
- principal: response.payload
42
+ marketId: profile.id,
43
+ baseUrl: profile.baseUrl,
44
+ principal: response.payload.principal,
45
+ teams: response.payload.teams
20
46
  }
21
47
  });
22
48
  } catch (error) {
@@ -0,0 +1,12 @@
1
+ import { MarketClient, type MarketProfile } from '@treeseed/sdk/market-client';
2
+ import type { TreeseedCommandContext, TreeseedParsedInvocation } from '../types.js';
3
+ export declare function marketAuthRoot(context: TreeseedCommandContext): string;
4
+ export declare function marketSelector(invocation: TreeseedParsedInvocation): string | null;
5
+ export declare function createMarketClientForInvocation(invocation: TreeseedParsedInvocation, context: TreeseedCommandContext, options?: {
6
+ requireAuth?: boolean;
7
+ }): {
8
+ profile: MarketProfile;
9
+ session: import("@treeseed/sdk/market-client").MarketSession | null;
10
+ client: MarketClient;
11
+ };
12
+ export declare function formatMarketProfile(profile: MarketProfile): string;
@@ -0,0 +1,39 @@
1
+ import { homedir } from "node:os";
2
+ import { resolve } from "node:path";
3
+ import {
4
+ MarketClient,
5
+ resolveMarketProfile,
6
+ resolveMarketSession
7
+ } from "@treeseed/sdk/market-client";
8
+ import { findNearestTreeseedRoot } from "@treeseed/sdk/workflow-support";
9
+ function marketAuthRoot(context) {
10
+ return findNearestTreeseedRoot(context.cwd) ?? resolve(context.env.HOME || homedir());
11
+ }
12
+ function marketSelector(invocation) {
13
+ return typeof invocation.args.market === "string" ? invocation.args.market : typeof invocation.args.host === "string" ? invocation.args.host : null;
14
+ }
15
+ function createMarketClientForInvocation(invocation, context, options = {}) {
16
+ const profile = resolveMarketProfile(marketSelector(invocation));
17
+ const session = resolveMarketSession(marketAuthRoot(context), profile.id);
18
+ if (options.requireAuth && !session?.accessToken) {
19
+ throw new Error(`Not logged in to market "${profile.id}". Run treeseed auth:login --market ${profile.id}.`);
20
+ }
21
+ return {
22
+ profile,
23
+ session,
24
+ client: new MarketClient({
25
+ profile,
26
+ accessToken: session?.accessToken ?? null,
27
+ userAgent: "treeseed-cli"
28
+ })
29
+ };
30
+ }
31
+ function formatMarketProfile(profile) {
32
+ return `${profile.id} ${profile.baseUrl} ${profile.kind}${profile.teamId ? ` team=${profile.teamId}` : ""}`;
33
+ }
34
+ export {
35
+ createMarketClientForInvocation,
36
+ formatMarketProfile,
37
+ marketAuthRoot,
38
+ marketSelector
39
+ };
@@ -0,0 +1,2 @@
1
+ import type { TreeseedCommandHandler } from '../types.js';
2
+ export declare const handleMarket: TreeseedCommandHandler;
@@ -0,0 +1,88 @@
1
+ import {
2
+ addMarketProfile,
3
+ loadMarketRegistryState,
4
+ removeMarketProfile,
5
+ setActiveMarketProfile
6
+ } from "@treeseed/sdk/market-client";
7
+ import { guidedResult } from "./utils.js";
8
+ import { createMarketClientForInvocation, formatMarketProfile } from "./market-utils.js";
9
+ const handleMarket = async (invocation, context) => {
10
+ const action = invocation.positionals[0] ?? "list";
11
+ if (action === "list") {
12
+ const state = loadMarketRegistryState();
13
+ return guidedResult({
14
+ command: "market",
15
+ summary: "Configured Treeseed markets",
16
+ sections: [{
17
+ title: "Markets",
18
+ lines: state.profiles.map((profile) => `${profile.id === state.activeMarketId ? "*" : " "} ${formatMarketProfile(profile)}`)
19
+ }],
20
+ report: state
21
+ });
22
+ }
23
+ if (action === "add") {
24
+ const id = invocation.positionals[1];
25
+ const baseUrl = invocation.positionals[2];
26
+ if (!id || !baseUrl) {
27
+ return { exitCode: 1, stderr: ["Usage: treeseed market add <id> <url>"] };
28
+ }
29
+ const state = addMarketProfile({
30
+ id,
31
+ label: typeof invocation.args.label === "string" ? invocation.args.label : id,
32
+ baseUrl,
33
+ kind: invocation.args.kind === "central" ? "central" : "specialized",
34
+ teamId: typeof invocation.args.team === "string" ? invocation.args.team : null,
35
+ alwaysAvailable: invocation.args.kind === "central"
36
+ });
37
+ return guidedResult({
38
+ command: "market",
39
+ summary: `Added market "${id}".`,
40
+ report: state
41
+ });
42
+ }
43
+ if (action === "remove") {
44
+ const id = invocation.positionals[1];
45
+ if (!id) return { exitCode: 1, stderr: ["Usage: treeseed market remove <id>"] };
46
+ const state = removeMarketProfile(id);
47
+ return guidedResult({
48
+ command: "market",
49
+ summary: `Removed market "${id}".`,
50
+ report: state
51
+ });
52
+ }
53
+ if (action === "use") {
54
+ const id = invocation.positionals[1];
55
+ if (!id) return { exitCode: 1, stderr: ["Usage: treeseed market use <id>"] };
56
+ const state = setActiveMarketProfile(id);
57
+ return guidedResult({
58
+ command: "market",
59
+ summary: `Active market is now "${id}".`,
60
+ report: state
61
+ });
62
+ }
63
+ if (action === "status") {
64
+ const { profile, client, session } = createMarketClientForInvocation(invocation, context);
65
+ const current = await client.currentMarket().catch(() => null);
66
+ return guidedResult({
67
+ command: "market",
68
+ summary: "Treeseed market status",
69
+ facts: [
70
+ { label: "Market", value: profile.id },
71
+ { label: "URL", value: profile.baseUrl },
72
+ { label: "Kind", value: current?.payload.kind ?? profile.kind },
73
+ { label: "Authenticated", value: Boolean(session?.accessToken) }
74
+ ],
75
+ report: {
76
+ market: current?.payload ?? profile,
77
+ authenticated: Boolean(session?.accessToken)
78
+ }
79
+ });
80
+ }
81
+ return {
82
+ exitCode: 1,
83
+ stderr: [`Unknown market action: ${action}`]
84
+ };
85
+ };
86
+ export {
87
+ handleMarket
88
+ };
@@ -0,0 +1,2 @@
1
+ import type { TreeseedCommandHandler } from '../types.js';
2
+ export declare const handlePacks: TreeseedCommandHandler;
@@ -0,0 +1,70 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import {
4
+ listIntegratedMarketCatalog,
5
+ resolveIntegratedCatalogArtifactDownload,
6
+ verifyArtifactBytes
7
+ } from "@treeseed/sdk/market-client";
8
+ import { guidedResult } from "./utils.js";
9
+ import { marketAuthRoot, marketSelector } from "./market-utils.js";
10
+ function artifactFileName(kind, slug, version) {
11
+ return `${kind}-${slug}-${version}.tar`.replace(/[^A-Za-z0-9._-]+/g, "-");
12
+ }
13
+ const handlePacks = async (invocation, context) => {
14
+ const action = invocation.positionals[0] ?? "search";
15
+ const selector = marketSelector(invocation);
16
+ const authRoot = marketAuthRoot(context);
17
+ if (action === "search" || action === "list") {
18
+ const response = await listIntegratedMarketCatalog({
19
+ kind: "knowledge_pack",
20
+ selector,
21
+ authRoot,
22
+ userAgent: "treeseed-cli"
23
+ });
24
+ return guidedResult({
25
+ command: "packs",
26
+ summary: selector ? "Treeseed knowledge packs" : "Treeseed integrated knowledge packs",
27
+ sections: [{
28
+ title: "Packs",
29
+ lines: response.payload.map((pack) => `${pack.id} ${pack.title ?? pack.name ?? pack.slug} market=${pack.sourceMarket.label ?? pack.sourceMarket.id}`)
30
+ }],
31
+ report: { selector, packs: response.payload, errors: response.errors }
32
+ });
33
+ }
34
+ if (action === "install") {
35
+ const itemId = invocation.positionals[1];
36
+ const version = typeof invocation.args.version === "string" ? invocation.args.version : "1.0.0";
37
+ if (!itemId) return { exitCode: 1, stderr: ["Usage: treeseed packs install <item-id> [--version <version>]"] };
38
+ const response = await resolveIntegratedCatalogArtifactDownload({
39
+ itemId,
40
+ version,
41
+ selector,
42
+ authRoot,
43
+ userAgent: "treeseed-cli"
44
+ });
45
+ const download = await fetch(response.payload.downloadUrl);
46
+ if (!download.ok) {
47
+ return { exitCode: 1, stderr: [`Artifact download failed with ${download.status}.`] };
48
+ }
49
+ const bytes = await verifyArtifactBytes(download, response.payload.sha256);
50
+ const outputDir = resolve(context.cwd, ".treeseed", "downloads");
51
+ mkdirSync(outputDir, { recursive: true });
52
+ const outputPath = resolve(outputDir, artifactFileName(response.payload.kind, response.payload.slug ?? itemId, response.payload.version));
53
+ writeFileSync(outputPath, bytes);
54
+ return guidedResult({
55
+ command: "packs",
56
+ summary: "Downloaded knowledge pack artifact.",
57
+ facts: [
58
+ { label: "Market", value: response.payload.sourceMarket.label ?? response.payload.sourceMarket.id },
59
+ { label: "Pack", value: response.payload.slug ?? itemId },
60
+ { label: "Version", value: response.payload.version },
61
+ { label: "Path", value: outputPath }
62
+ ],
63
+ report: { marketId: response.payload.sourceMarket.id, artifact: response.payload, outputPath }
64
+ });
65
+ }
66
+ return { exitCode: 1, stderr: [`Unknown packs action: ${action}`] };
67
+ };
68
+ export {
69
+ handlePacks
70
+ };
@@ -0,0 +1,2 @@
1
+ import type { TreeseedCommandHandler } from '../types.js';
2
+ export declare const handleProjects: TreeseedCommandHandler;
@@ -0,0 +1,48 @@
1
+ import { guidedResult } from "./utils.js";
2
+ import { createMarketClientForInvocation } from "./market-utils.js";
3
+ const handleProjects = async (invocation, context) => {
4
+ const action = invocation.positionals[0] ?? "list";
5
+ const { profile, client } = createMarketClientForInvocation(invocation, context, { requireAuth: true });
6
+ if (action === "list") {
7
+ const teamId = typeof invocation.args.team === "string" ? invocation.args.team : null;
8
+ const response = await client.projects(teamId);
9
+ return guidedResult({
10
+ command: "projects",
11
+ summary: "Treeseed market projects",
12
+ sections: [{
13
+ title: "Projects",
14
+ lines: response.payload.map((project) => `${project.id} ${project.name ?? project.slug} team=${project.teamId}`)
15
+ }],
16
+ report: { marketId: profile.id, teamId, projects: response.payload }
17
+ });
18
+ }
19
+ if (action === "access") {
20
+ const projectId = invocation.positionals[1];
21
+ if (!projectId) return { exitCode: 1, stderr: ["Usage: treeseed projects access <project-id>"] };
22
+ const response = await client.projectAccess(projectId);
23
+ return guidedResult({
24
+ command: "projects",
25
+ summary: "Treeseed market project access",
26
+ facts: [
27
+ { label: "Project", value: response.payload.projectId },
28
+ { label: "Staging admin", value: response.payload.team.summary.canAdminStaging },
29
+ { label: "Production admin", value: response.payload.team.summary.canAdminProduction }
30
+ ],
31
+ sections: [{
32
+ title: "Environments",
33
+ lines: response.payload.environments.map((entry) => `${entry.environment}: ${entry.role}`)
34
+ }],
35
+ report: { marketId: profile.id, access: response.payload }
36
+ });
37
+ }
38
+ if (action === "connect") {
39
+ return {
40
+ exitCode: 1,
41
+ stderr: ["Use treeseed config --connect-market --market-project-id <project-id> for project pairing."]
42
+ };
43
+ }
44
+ return { exitCode: 1, stderr: [`Unknown projects action: ${action}`] };
45
+ };
46
+ export {
47
+ handleProjects
48
+ };
@@ -0,0 +1,2 @@
1
+ import type { TreeseedCommandHandler } from '../types.js';
2
+ export declare const handleTeams: TreeseedCommandHandler;
@@ -0,0 +1,47 @@
1
+ import { setActiveMarketProfile } from "@treeseed/sdk/market-client";
2
+ import { guidedResult } from "./utils.js";
3
+ import { createMarketClientForInvocation } from "./market-utils.js";
4
+ const handleTeams = async (invocation, context) => {
5
+ const action = invocation.positionals[0] ?? "list";
6
+ const { profile, client } = createMarketClientForInvocation(invocation, context, { requireAuth: true });
7
+ if (action === "list") {
8
+ const response = await client.teams();
9
+ return guidedResult({
10
+ command: "teams",
11
+ summary: "Treeseed market teams",
12
+ sections: [{
13
+ title: "Teams",
14
+ lines: response.payload.map((team) => `${team.id} ${team.displayName ?? team.name ?? team.slug}`)
15
+ }],
16
+ report: { marketId: profile.id, teams: response.payload }
17
+ });
18
+ }
19
+ if (action === "use") {
20
+ const teamId = invocation.positionals[1];
21
+ if (!teamId) return { exitCode: 1, stderr: ["Usage: treeseed teams use <team-id>"] };
22
+ const state = setActiveMarketProfile(profile.id);
23
+ return guidedResult({
24
+ command: "teams",
25
+ summary: `Selected team "${teamId}" for market "${profile.id}".`,
26
+ report: { marketId: profile.id, teamId, registry: state }
27
+ });
28
+ }
29
+ if (action === "members") {
30
+ const teamId = typeof invocation.args.team === "string" ? invocation.args.team : invocation.positionals[1];
31
+ if (!teamId) return { exitCode: 1, stderr: ["Usage: treeseed teams members <team-id>"] };
32
+ const response = await client.teamMembers(teamId);
33
+ return guidedResult({
34
+ command: "teams",
35
+ summary: "Treeseed market team members",
36
+ sections: [{
37
+ title: "Members",
38
+ lines: response.payload.map((member) => `${member.userId} ${member.displayName ?? member.email ?? member.id} ${(member.roles ?? []).join(", ")}`)
39
+ }],
40
+ report: { marketId: profile.id, teamId, members: response.payload }
41
+ });
42
+ }
43
+ return { exitCode: 1, stderr: [`Unknown teams action: ${action}`] };
44
+ };
45
+ export {
46
+ handleTeams
47
+ };
@@ -1,6 +1,69 @@
1
1
  import { TreeseedOperationsSdk } from "@treeseed/sdk/operations";
2
+ import { mkdirSync, writeFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import {
5
+ listIntegratedMarketCatalog,
6
+ resolveIntegratedCatalogArtifactDownload,
7
+ verifyArtifactBytes
8
+ } from "@treeseed/sdk/market-client";
9
+ import { guidedResult } from "./utils.js";
10
+ import { marketAuthRoot, marketSelector } from "./market-utils.js";
2
11
  const operations = new TreeseedOperationsSdk();
3
12
  const handleTemplate = async (invocation, context) => {
13
+ if (invocation.positionals[0] === "search" || invocation.positionals[0] === "install" || typeof invocation.args.market === "string") {
14
+ const action = invocation.positionals[0] ?? "search";
15
+ const selector = marketSelector(invocation);
16
+ const authRoot = marketAuthRoot(context);
17
+ if (action === "search" || action === "list") {
18
+ const response = await listIntegratedMarketCatalog({
19
+ kind: "template",
20
+ selector,
21
+ authRoot,
22
+ userAgent: "treeseed-cli"
23
+ });
24
+ return guidedResult({
25
+ command: "template",
26
+ summary: selector ? "Treeseed market templates" : "Treeseed integrated market templates",
27
+ sections: [{
28
+ title: "Templates",
29
+ lines: response.payload.map((template) => `${template.id} ${template.title ?? template.displayName ?? template.slug} market=${template.sourceMarket.label ?? template.sourceMarket.id}`)
30
+ }],
31
+ report: { selector, templates: response.payload, errors: response.errors }
32
+ });
33
+ }
34
+ if (action === "install") {
35
+ const itemId = invocation.positionals[1];
36
+ const version = typeof invocation.args.version === "string" ? invocation.args.version : "1.0.0";
37
+ if (!itemId) return { exitCode: 1, stderr: ["Usage: treeseed template install <item-id> [--version <version>]"] };
38
+ const response = await resolveIntegratedCatalogArtifactDownload({
39
+ itemId,
40
+ version,
41
+ selector,
42
+ authRoot,
43
+ userAgent: "treeseed-cli"
44
+ });
45
+ const download = await fetch(response.payload.downloadUrl);
46
+ if (!download.ok) {
47
+ return { exitCode: 1, stderr: [`Artifact download failed with ${download.status}.`] };
48
+ }
49
+ const bytes = await verifyArtifactBytes(download, response.payload.sha256);
50
+ const outputDir = resolve(context.cwd, ".treeseed", "downloads");
51
+ mkdirSync(outputDir, { recursive: true });
52
+ const outputPath = resolve(outputDir, `template-${response.payload.slug ?? itemId}-${response.payload.version}.tar`.replace(/[^A-Za-z0-9._-]+/g, "-"));
53
+ writeFileSync(outputPath, bytes);
54
+ return guidedResult({
55
+ command: "template",
56
+ summary: "Downloaded template artifact.",
57
+ facts: [
58
+ { label: "Market", value: response.payload.sourceMarket.label ?? response.payload.sourceMarket.id },
59
+ { label: "Template", value: response.payload.slug ?? itemId },
60
+ { label: "Version", value: response.payload.version },
61
+ { label: "Path", value: outputPath }
62
+ ],
63
+ report: { marketId: response.payload.sourceMarket.id, artifact: response.payload, outputPath }
64
+ });
65
+ }
66
+ }
4
67
  const result = await operations.execute({
5
68
  operationName: "template",
6
69
  input: {
@@ -662,6 +662,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
662
662
  ["auth:login", command({
663
663
  options: [
664
664
  { name: "host", flags: "--host <id>", description: "Override the configured remote host id for this login.", kind: "string" },
665
+ { name: "market", flags: "--market <id-or-url>", description: "Limit catalog lookup to one configured market id or direct market API URL. Without this, search/install uses the integrated catalog from all configured catalog markets.", kind: "string" },
665
666
  { name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
666
667
  ],
667
668
  examples: ["treeseed auth:login"],
@@ -679,6 +680,7 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
679
680
  ["auth:logout", command({
680
681
  options: [
681
682
  { name: "host", flags: "--host <id>", description: "Override the configured remote host id to clear.", kind: "string" },
683
+ { name: "market", flags: "--market <id-or-url>", description: "Select a configured market id or direct market API URL.", kind: "string" },
682
684
  { name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
683
685
  ],
684
686
  examples: ["treeseed auth:logout"],
@@ -694,7 +696,11 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
694
696
  handlerName: "auth:logout"
695
697
  })],
696
698
  ["auth:whoami", command({
697
- options: [{ name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }],
699
+ options: [
700
+ { name: "market", flags: "--market <id-or-url>", description: "Select a configured market id or direct market API URL.", kind: "string" },
701
+ { name: "allMarkets", flags: "--all-markets", description: "Show locally stored identities for all configured markets.", kind: "boolean" },
702
+ { name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
703
+ ],
698
704
  examples: ["treeseed auth:whoami"],
699
705
  help: {
700
706
  longSummary: ["Auth:whoami shows the currently active Treeseed API identity so you can verify which account and host context the CLI is using."],
@@ -790,10 +796,15 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
790
796
  { name: "action", description: "Template action: list, show, or validate.", required: false },
791
797
  { name: "id", description: "Template id for show or validate.", required: false }
792
798
  ],
799
+ options: [
800
+ { name: "market", flags: "--market <id-or-url>", description: "Select a configured market id or direct market API URL.", kind: "string" },
801
+ { name: "version", flags: "--version <version>", description: "Artifact version for market template install.", kind: "string" },
802
+ { name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
803
+ ],
793
804
  examples: ["treeseed template", "treeseed template list", "treeseed template show starter-basic", "treeseed template validate"],
794
805
  help: {
795
806
  longSummary: [
796
- "Template exposes the Treeseed starter catalog so you can list, inspect, and validate starter definitions before using them for initialization or distribution work."
807
+ "Template exposes local starter catalog actions and market-backed search/install actions. Market search/install uses an integrated catalog from central and configured specialized markets, with every result labeled by source market."
797
808
  ],
798
809
  examples: [
799
810
  example("treeseed template", "Default to the catalog list", "Show the available starters without specifying an action."),
@@ -1201,6 +1212,120 @@ const CLI_COMMAND_OVERLAYS = /* @__PURE__ */ new Map([
1201
1212
  ["starlight:patch", command({ examples: ["treeseed starlight:patch"], executionMode: "adapter" })]
1202
1213
  ]);
1203
1214
  const CLI_ONLY_OPERATION_SPECS = [
1215
+ {
1216
+ id: "market.registry",
1217
+ name: "market",
1218
+ aliases: [],
1219
+ group: "Utilities",
1220
+ summary: "Manage configured Treeseed market API endpoints.",
1221
+ description: "List, add, select, remove, and inspect market API profiles stored in local machine configuration.",
1222
+ provider: "default",
1223
+ related: ["auth:login", "teams", "projects"],
1224
+ usage: "treeseed market [list|add|use|remove|status]",
1225
+ arguments: [{ name: "action", description: "Market action.", required: false }],
1226
+ options: [
1227
+ { name: "market", flags: "--market <id-or-url>", description: "Select a configured market id or direct market API URL.", kind: "string" },
1228
+ { name: "label", flags: "--label <label>", description: "Display label for market add.", kind: "string" },
1229
+ { name: "kind", flags: "--kind <kind>", description: "Market kind for market add.", kind: "enum", values: ["central", "specialized"] },
1230
+ { name: "team", flags: "--team <team-id>", description: "Team id for a specialized market profile.", kind: "string" },
1231
+ { name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
1232
+ ],
1233
+ examples: ["treeseed market list", "treeseed market add enterprise https://market.example.com", "treeseed market use central"],
1234
+ help: {
1235
+ longSummary: ["Market manages local Treeseed market API profiles. It never serves market behavior itself; it only selects endpoints the SDK market client should call."],
1236
+ whenToUse: ["Use this when you need to add an enterprise market, switch back to central, or inspect which market endpoint CLI commands will target."],
1237
+ beforeYouRun: ["Decide whether you are managing the always-available central profile or a team-specific specialized market profile."],
1238
+ automationNotes: ["Use `--json` when scripts need the active market id, URL, and configured profiles."]
1239
+ },
1240
+ helpVisible: true,
1241
+ helpFeatured: false,
1242
+ executionMode: "handler",
1243
+ handlerName: "market"
1244
+ },
1245
+ {
1246
+ id: "market.teams",
1247
+ name: "teams",
1248
+ aliases: [],
1249
+ group: "Utilities",
1250
+ summary: "Inspect teams from the selected market.",
1251
+ description: "List teams, select a team context, and inspect team membership through the market API client.",
1252
+ provider: "default",
1253
+ related: ["market", "auth:login", "projects"],
1254
+ usage: "treeseed teams [list|use|members]",
1255
+ arguments: [{ name: "action", description: "Teams action.", required: false }],
1256
+ options: [
1257
+ { name: "market", flags: "--market <id-or-url>", description: "Select a configured market id or direct market API URL.", kind: "string" },
1258
+ { name: "team", flags: "--team <team-id>", description: "Team id for member lookup.", kind: "string" },
1259
+ { name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
1260
+ ],
1261
+ examples: ["treeseed teams list", "treeseed teams members team_123"],
1262
+ help: {
1263
+ longSummary: ["Teams reads team membership data from the selected market API using the SDK market client."],
1264
+ whenToUse: ["Use this after login to confirm team membership or inspect who belongs to a market-owned team."],
1265
+ beforeYouRun: ["Authenticate to the selected market with `treeseed auth:login --market <id>` before reading private team data."],
1266
+ automationNotes: ["Use `--json` for scripts that need stable team or member arrays."]
1267
+ },
1268
+ helpVisible: true,
1269
+ helpFeatured: false,
1270
+ executionMode: "handler",
1271
+ handlerName: "teams"
1272
+ },
1273
+ {
1274
+ id: "market.projects",
1275
+ name: "projects",
1276
+ aliases: [],
1277
+ group: "Utilities",
1278
+ summary: "Inspect projects and access controls from the selected market.",
1279
+ description: "List market projects and inspect staging/production access through the market API client.",
1280
+ provider: "default",
1281
+ related: ["market", "teams", "config"],
1282
+ usage: "treeseed projects [list|access|connect]",
1283
+ arguments: [{ name: "action", description: "Projects action.", required: false }],
1284
+ options: [
1285
+ { name: "market", flags: "--market <id-or-url>", description: "Select a configured market id or direct market API URL.", kind: "string" },
1286
+ { name: "team", flags: "--team <team-id>", description: "Limit project list to a team.", kind: "string" },
1287
+ { name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
1288
+ ],
1289
+ examples: ["treeseed projects list", "treeseed projects access project_123"],
1290
+ help: {
1291
+ longSummary: ["Projects reads project and environment access data from the selected market API using the SDK market client."],
1292
+ whenToUse: ["Use this to confirm which market projects you can see and whether your account has staging or production admin access."],
1293
+ beforeYouRun: ["Authenticate to the market and know the project id when inspecting a single project access summary."],
1294
+ automationNotes: ["Use `--json` to capture project lists and access summaries for automation."]
1295
+ },
1296
+ helpVisible: true,
1297
+ helpFeatured: false,
1298
+ executionMode: "handler",
1299
+ handlerName: "projects"
1300
+ },
1301
+ {
1302
+ id: "market.packs",
1303
+ name: "packs",
1304
+ aliases: [],
1305
+ group: "Utilities",
1306
+ summary: "Search and download market knowledge packs.",
1307
+ description: "Search knowledge packs and download artifact versions through the integrated market catalog or a selected market API.",
1308
+ provider: "default",
1309
+ related: ["market", "template"],
1310
+ usage: "treeseed packs [search|install] [id]",
1311
+ arguments: [{ name: "action", description: "Packs action.", required: false }],
1312
+ options: [
1313
+ { name: "market", flags: "--market <id-or-url>", description: "Limit catalog lookup to one configured market id or direct market API URL. Without this, search/install uses the integrated catalog from all configured catalog markets.", kind: "string" },
1314
+ { name: "version", flags: "--version <version>", description: "Artifact version for install.", kind: "string" },
1315
+ { name: "json", flags: "--json", description: "Emit machine-readable JSON instead of human-readable text.", kind: "boolean" }
1316
+ ],
1317
+ examples: ["treeseed packs search", "treeseed packs install pack_123 --version 1.0.0"],
1318
+ help: {
1319
+ longSummary: ["Packs searches and downloads knowledge pack artifacts from the integrated catalog formed by central and configured specialized markets."],
1320
+ whenToUse: ["Use this when a project should install a market-published knowledge bundle rather than a local fixture."],
1321
+ beforeYouRun: ["Choose the market and artifact version; private packs require an authenticated market session."],
1322
+ automationNotes: ["Use `--json` to capture artifact metadata and the downloaded file path."]
1323
+ },
1324
+ helpVisible: true,
1325
+ helpFeatured: false,
1326
+ executionMode: "handler",
1327
+ handlerName: "packs"
1328
+ },
1204
1329
  {
1205
1330
  id: "agents.run",
1206
1331
  name: "agents",
@@ -24,6 +24,10 @@ export declare const COMMAND_HANDLERS: {
24
24
  readonly 'auth:login': import("./operations-types.js").TreeseedCommandHandler;
25
25
  readonly 'auth:logout': import("./operations-types.js").TreeseedCommandHandler;
26
26
  readonly 'auth:whoami': import("./operations-types.js").TreeseedCommandHandler;
27
+ readonly market: import("./operations-types.js").TreeseedCommandHandler;
28
+ readonly teams: import("./operations-types.js").TreeseedCommandHandler;
29
+ readonly projects: import("./operations-types.js").TreeseedCommandHandler;
30
+ readonly packs: import("./operations-types.js").TreeseedCommandHandler;
27
31
  readonly 'secrets:status': import("./operations-types.js").TreeseedCommandHandler;
28
32
  readonly 'secrets:unlock': import("./operations-types.js").TreeseedCommandHandler;
29
33
  readonly 'secrets:lock': import("./operations-types.js").TreeseedCommandHandler;
@@ -19,6 +19,10 @@ import { handleSync } from "./handlers/sync.js";
19
19
  import { handleAuthLogin } from "./handlers/auth-login.js";
20
20
  import { handleAuthLogout } from "./handlers/auth-logout.js";
21
21
  import { handleAuthWhoAmI } from "./handlers/auth-whoami.js";
22
+ import { handleMarket } from "./handlers/market.js";
23
+ import { handleTeams } from "./handlers/teams.js";
24
+ import { handleProjects } from "./handlers/projects.js";
25
+ import { handlePacks } from "./handlers/packs.js";
22
26
  import {
23
27
  handleSecretsLock,
24
28
  handleSecretsMigrateKey,
@@ -62,6 +66,10 @@ const COMMAND_HANDLERS = {
62
66
  "auth:login": handleAuthLogin,
63
67
  "auth:logout": handleAuthLogout,
64
68
  "auth:whoami": handleAuthWhoAmI,
69
+ market: handleMarket,
70
+ teams: handleTeams,
71
+ projects: handleProjects,
72
+ packs: handlePacks,
65
73
  "secrets:status": handleSecretsStatus,
66
74
  "secrets:unlock": handleSecretsUnlock,
67
75
  "secrets:lock": handleSecretsLock,
@@ -333,7 +333,19 @@ function formatProjectError(spec) {
333
333
  ].join("\n");
334
334
  }
335
335
  function commandNeedsProjectRoot(spec) {
336
- return spec.name !== "init" && spec.name !== "export" && spec.name !== "install";
336
+ return !(/* @__PURE__ */ new Set([
337
+ "init",
338
+ "export",
339
+ "install",
340
+ "auth:login",
341
+ "auth:logout",
342
+ "auth:whoami",
343
+ "market",
344
+ "teams",
345
+ "projects",
346
+ "packs",
347
+ "template"
348
+ ])).has(spec.name);
337
349
  }
338
350
  function resolveTreeseedCommandCwd(spec, cwd) {
339
351
  if (!commandNeedsProjectRoot(spec)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/cli",
3
- "version": "0.6.25",
3
+ "version": "0.6.27",
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.6.27",
48
+ "@treeseed/sdk": "0.6.29",
49
49
  "ink": "^7.0.0",
50
50
  "react": "^19.2.5"
51
51
  },