@stigmer/cli 3.14.1 → 3.15.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.
Files changed (138) hide show
  1. package/client/client.d.ts +20 -1
  2. package/client/client.d.ts.map +1 -1
  3. package/client/client.js +32 -5
  4. package/client/client.js.map +1 -1
  5. package/commands/auth/index.d.ts.map +1 -1
  6. package/commands/auth/index.js +7 -36
  7. package/commands/auth/index.js.map +1 -1
  8. package/commands/auth/login.d.ts +13 -0
  9. package/commands/auth/login.d.ts.map +1 -0
  10. package/commands/auth/login.js +58 -0
  11. package/commands/auth/login.js.map +1 -0
  12. package/commands/auth/whoami.d.ts +18 -0
  13. package/commands/auth/whoami.d.ts.map +1 -0
  14. package/commands/auth/whoami.js +58 -0
  15. package/commands/auth/whoami.js.map +1 -0
  16. package/commands/draft.d.ts.map +1 -1
  17. package/commands/draft.js +24 -8
  18. package/commands/draft.js.map +1 -1
  19. package/commands/internal-daemon.d.ts.map +1 -1
  20. package/commands/internal-daemon.js +2 -9
  21. package/commands/internal-daemon.js.map +1 -1
  22. package/commands/run.d.ts.map +1 -1
  23. package/commands/run.js +15 -6
  24. package/commands/run.js.map +1 -1
  25. package/commands/up.d.ts.map +1 -1
  26. package/commands/up.js +37 -8
  27. package/commands/up.js.map +1 -1
  28. package/local/artifact.d.ts +13 -3
  29. package/local/artifact.d.ts.map +1 -1
  30. package/local/artifact.js +31 -10
  31. package/local/artifact.js.map +1 -1
  32. package/local/daemon/components.d.ts.map +1 -1
  33. package/local/daemon/components.js +2 -0
  34. package/local/daemon/components.js.map +1 -1
  35. package/local/daemon/host.d.ts +8 -0
  36. package/local/daemon/host.d.ts.map +1 -1
  37. package/local/daemon/host.js +42 -1
  38. package/local/daemon/host.js.map +1 -1
  39. package/local/daemon/index.d.ts +3 -3
  40. package/local/daemon/index.d.ts.map +1 -1
  41. package/local/daemon/index.js +2 -2
  42. package/local/daemon/index.js.map +1 -1
  43. package/local/daemon/launch.d.ts +29 -1
  44. package/local/daemon/launch.d.ts.map +1 -1
  45. package/local/daemon/launch.js +73 -23
  46. package/local/daemon/launch.js.map +1 -1
  47. package/local/daemon/process.d.ts +16 -2
  48. package/local/daemon/process.d.ts.map +1 -1
  49. package/local/daemon/process.js +16 -2
  50. package/local/daemon/process.js.map +1 -1
  51. package/local/daemon/types.d.ts +2 -0
  52. package/local/daemon/types.d.ts.map +1 -1
  53. package/local/paths.d.ts +14 -4
  54. package/local/paths.d.ts.map +1 -1
  55. package/local/paths.js +17 -4
  56. package/local/paths.js.map +1 -1
  57. package/local/runtime/runner.d.ts +4 -0
  58. package/local/runtime/runner.d.ts.map +1 -1
  59. package/local/runtime/runner.js +10 -6
  60. package/local/runtime/runner.js.map +1 -1
  61. package/local/runtime/server.d.ts +5 -0
  62. package/local/runtime/server.d.ts.map +1 -1
  63. package/local/runtime/server.js +13 -8
  64. package/local/runtime/server.js.map +1 -1
  65. package/local/seedpack/content.d.ts +4 -0
  66. package/local/seedpack/content.d.ts.map +1 -1
  67. package/local/seedpack/content.js +11 -11
  68. package/local/seedpack/content.js.map +1 -1
  69. package/local/state/lock.d.ts +3 -2
  70. package/local/state/lock.d.ts.map +1 -1
  71. package/local/state/lock.js +23 -9
  72. package/local/state/lock.js.map +1 -1
  73. package/local/state/proc.d.ts +13 -0
  74. package/local/state/proc.d.ts.map +1 -1
  75. package/local/state/proc.js +15 -0
  76. package/local/state/proc.js.map +1 -1
  77. package/local/temporal/download.d.ts +7 -1
  78. package/local/temporal/download.d.ts.map +1 -1
  79. package/local/temporal/download.js +21 -11
  80. package/local/temporal/download.js.map +1 -1
  81. package/local/temporal/index.d.ts +3 -3
  82. package/local/temporal/index.d.ts.map +1 -1
  83. package/local/temporal/index.js +2 -2
  84. package/local/temporal/index.js.map +1 -1
  85. package/local/temporal/inspect.d.ts +10 -3
  86. package/local/temporal/inspect.d.ts.map +1 -1
  87. package/local/temporal/inspect.js +52 -13
  88. package/local/temporal/inspect.js.map +1 -1
  89. package/local/temporal/manager.d.ts +16 -4
  90. package/local/temporal/manager.d.ts.map +1 -1
  91. package/local/temporal/manager.js +19 -6
  92. package/local/temporal/manager.js.map +1 -1
  93. package/package.json +5 -5
  94. package/resources/run/prepare.d.ts +10 -8
  95. package/resources/run/prepare.d.ts.map +1 -1
  96. package/resources/run/prepare.js +18 -4
  97. package/resources/run/prepare.js.map +1 -1
  98. package/src/client/client.test.ts +89 -1
  99. package/src/client/client.ts +82 -9
  100. package/src/commands/auth/index.ts +7 -56
  101. package/src/commands/auth/login.test.ts +52 -0
  102. package/src/commands/auth/login.ts +70 -0
  103. package/src/commands/auth/whoami.test.ts +96 -0
  104. package/src/commands/auth/whoami.ts +87 -0
  105. package/src/commands/draft.ts +85 -26
  106. package/src/commands/internal-daemon.ts +2 -10
  107. package/src/commands/run.ts +77 -35
  108. package/src/commands/up.ts +52 -8
  109. package/src/local/artifact.test.ts +27 -1
  110. package/src/local/artifact.ts +35 -13
  111. package/src/local/daemon/components.ts +2 -0
  112. package/src/local/daemon/daemon.integration.test.ts +33 -0
  113. package/src/local/daemon/host.ts +50 -1
  114. package/src/local/daemon/index.ts +3 -3
  115. package/src/local/daemon/launch.test.ts +136 -0
  116. package/src/local/daemon/launch.ts +108 -27
  117. package/src/local/daemon/process.ts +31 -4
  118. package/src/local/daemon/supervisor.test.ts +1 -1
  119. package/src/local/daemon/types.ts +2 -0
  120. package/src/local/paths.ts +17 -4
  121. package/src/local/runtime/runner.ts +10 -6
  122. package/src/local/runtime/runtime.test.ts +15 -0
  123. package/src/local/runtime/server.test.ts +37 -0
  124. package/src/local/runtime/server.ts +17 -12
  125. package/src/local/seedpack/content.test.ts +21 -0
  126. package/src/local/seedpack/content.ts +15 -16
  127. package/src/local/state/lock.ts +24 -9
  128. package/src/local/state/proc.ts +16 -0
  129. package/src/local/state/state.test.ts +25 -1
  130. package/src/local/temporal/download.test.ts +79 -11
  131. package/src/local/temporal/download.ts +24 -11
  132. package/src/local/temporal/index.ts +11 -3
  133. package/src/local/temporal/inspect.test.ts +83 -0
  134. package/src/local/temporal/inspect.ts +60 -12
  135. package/src/local/temporal/manager.test.ts +34 -1
  136. package/src/local/temporal/manager.ts +22 -8
  137. package/src/resources/run/prepare.test.ts +28 -26
  138. package/src/resources/run/prepare.ts +48 -18
@@ -0,0 +1,96 @@
1
+ // Unit tests for `stigmer auth whoami`'s result (commands/auth/whoami.ts;
2
+ // 20260911.11 A3): the command runs the SDK's ensureMyIdentityAccount — the
3
+ // same first-sign-in flow the console runs — and RENDERS what it learned.
4
+ // whoamiResult is the pure half: given the account and whether this call
5
+ // created it, the CommandResult a person reads. A first sign-in says so
6
+ // (visibility of system status); a missing organization is a hint, not a
7
+ // failure; an empty profile (the unconfigured laptop's operator) renders
8
+ // only the fields it has. Accounts are built with the generated schema
9
+ // (DD-007: the generated type is the contract; refinement 12, slice 4).
10
+
11
+ import { create } from "@bufbuild/protobuf";
12
+ import { IdentityAccountSchema } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
13
+ import { describe, expect, it } from "vitest";
14
+
15
+ import { whoamiResult } from "./whoami.js";
16
+
17
+ const ALICE = {
18
+ metadata: { id: "ida_wtr3jcf281yfk9xx61kj59fsme", name: "alice@example.com" },
19
+ spec: {
20
+ idpId: "auth0|alice",
21
+ email: "alice@example.com",
22
+ firstName: "Alice",
23
+ lastName: "Liddell",
24
+ isMachineAccount: false,
25
+ },
26
+ };
27
+ const ACCOUNT = create(IdentityAccountSchema, ALICE);
28
+
29
+ function fields(
30
+ result: ReturnType<typeof whoamiResult>,
31
+ ): Record<string, string> {
32
+ return Object.fromEntries(
33
+ result.sections.flatMap((s) => s.fields.map((f) => [f.key, f.value])),
34
+ );
35
+ }
36
+
37
+ describe("whoamiResult", () => {
38
+ it("renders an existing account as 'Authenticated' with its identity fields", () => {
39
+ const result = whoamiResult(ACCOUNT, { created: false, org: "acme" });
40
+ expect(result.status).toBe("success");
41
+ expect(result.message).toBe("Authenticated");
42
+ expect(fields(result)).toEqual({
43
+ "Account ID": "ida_wtr3jcf281yfk9xx61kj59fsme",
44
+ Name: "alice@example.com",
45
+ Email: "alice@example.com",
46
+ "Full Name": "Alice Liddell",
47
+ "Account Type": "User Account",
48
+ Organization: "acme",
49
+ });
50
+ expect(result.hints).toEqual([]);
51
+ });
52
+
53
+ it("says so when this call created the account — the first sign-in is visible, never silent", () => {
54
+ const result = whoamiResult(ACCOUNT, { created: true, org: "acme" });
55
+ expect(result.status).toBe("success");
56
+ expect(result.message).toBe(
57
+ "Authenticated — your account was created on this first sign-in",
58
+ );
59
+ expect(fields(result)["Account ID"]).toBe("ida_wtr3jcf281yfk9xx61kj59fsme");
60
+ });
61
+
62
+ it("a missing organization is a hint with the command that sets it", () => {
63
+ const result = whoamiResult(ACCOUNT, { created: false, org: "" });
64
+ expect(fields(result).Organization).toBeUndefined();
65
+ expect(result.hints).toEqual([
66
+ "No organization set. Use: stigmer config context set --org <slug>",
67
+ ]);
68
+ });
69
+
70
+ it("renders only the fields an empty profile has (the unconfigured laptop's operator)", () => {
71
+ const result = whoamiResult(
72
+ create(IdentityAccountSchema, {
73
+ metadata: { id: "ida_fn0zdvkkkhhrb4wry43zba8gnn", name: "system" },
74
+ spec: { idpId: "local|system" },
75
+ }),
76
+ { created: false, org: "local" },
77
+ );
78
+ expect(fields(result)).toEqual({
79
+ "Account ID": "ida_fn0zdvkkkhhrb4wry43zba8gnn",
80
+ Name: "system",
81
+ "Account Type": "User Account",
82
+ Organization: "local",
83
+ });
84
+ });
85
+
86
+ it("names a machine account", () => {
87
+ const result = whoamiResult(
88
+ create(IdentityAccountSchema, {
89
+ ...ALICE,
90
+ spec: { ...ALICE.spec, isMachineAccount: true },
91
+ }),
92
+ { created: false, org: "acme" },
93
+ );
94
+ expect(fields(result)["Account Type"]).toBe("Machine Account");
95
+ });
96
+ });
@@ -0,0 +1,87 @@
1
+ // `stigmer auth whoami` — who the connected server says you are.
2
+ //
3
+ // Runs the SDK's ensureMyIdentityAccount, the same first-sign-in flow the
4
+ // console's gate runs (20260911.11 A3): an authenticated caller with no
5
+ // account yet is provisioned here rather than told "not found", so a person
6
+ // who only ever uses the CLI is never left without an account. The result
7
+ // says when THIS call created it — a first sign-in is visible, never silent.
8
+ //
9
+ // whoamiResult is the pure half (given the account and what the call learned,
10
+ // the CommandResult a person reads); runWhoami is the I/O half.
11
+
12
+ import type { IdentityAccount } from "@stigmer/protos/ai/stigmer/iam/identityaccount/v1/api_pb";
13
+ import { ensureMyIdentityAccount } from "@stigmer/sdk";
14
+ import {
15
+ ensureAuthenticated,
16
+ resolveContextOrganization,
17
+ } from "../../config/index.js";
18
+ import { CommandResult } from "../../output/index.js";
19
+
20
+ /** What the call learned beyond the account itself. */
21
+ export interface WhoamiContext {
22
+ /** `true` when this call created the account (a first sign-in). */
23
+ readonly created: boolean;
24
+ /** The organization the CLI context resolves to; "" when none is set. */
25
+ readonly org: string;
26
+ }
27
+
28
+ /**
29
+ * Render the caller's account. Every field is optional on the wire and an
30
+ * empty profile (the unconfigured laptop's operator) renders only what it
31
+ * has; a missing organization is a hint with the command that sets it, not a
32
+ * failure.
33
+ */
34
+ export function whoamiResult(
35
+ account: IdentityAccount,
36
+ context: WhoamiContext,
37
+ ): CommandResult {
38
+ const result = CommandResult.success(
39
+ context.created
40
+ ? "Authenticated — your account was created on this first sign-in"
41
+ : "Authenticated",
42
+ );
43
+ const section = result.addSection("");
44
+
45
+ if (account.metadata !== undefined) {
46
+ section.field("Account ID", account.metadata.id);
47
+ if (account.metadata.name !== "")
48
+ section.field("Name", account.metadata.name);
49
+ }
50
+ if (account.spec !== undefined) {
51
+ if (account.spec.email !== "") section.field("Email", account.spec.email);
52
+ if (account.spec.firstName !== "" || account.spec.lastName !== "") {
53
+ section.field(
54
+ "Full Name",
55
+ `${account.spec.firstName} ${account.spec.lastName}`.trim(),
56
+ );
57
+ }
58
+ section.field(
59
+ "Account Type",
60
+ account.spec.isMachineAccount ? "Machine Account" : "User Account",
61
+ );
62
+ }
63
+
64
+ if (context.org !== "") {
65
+ section.field("Organization", context.org);
66
+ } else {
67
+ result.hint(
68
+ "No organization set. Use: stigmer config context set --org <slug>",
69
+ );
70
+ }
71
+
72
+ return result;
73
+ }
74
+
75
+ export async function runWhoami(): Promise<CommandResult> {
76
+ const { connectBackend } = await import("../../backend.js");
77
+ const client = connectBackend();
78
+ ensureAuthenticated(client.config);
79
+
80
+ const { account, created } = await ensureMyIdentityAccount(client.stigmer, {
81
+ onProvisioning: () => process.stderr.write("Setting up your account...\n"),
82
+ });
83
+ return whoamiResult(account, {
84
+ created,
85
+ org: resolveContextOrganization(client.config),
86
+ });
87
+ }
@@ -8,9 +8,14 @@
8
8
  // unless detached or a local workspace is in play.
9
9
 
10
10
  import type { Command } from "commander";
11
- import { ensureAuthenticated, isCloudMode, resolveOrganization } from "../config/index.js";
11
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
12
+ import { ensureAuthenticated, resolveOrganization } from "../config/index.js";
12
13
  import { UsageError } from "../errors/index.js";
13
- import { addAgentExecFlags, type AgentExecOptions, toAgentExecFlags } from "./agent-exec-flags.js";
14
+ import {
15
+ addAgentExecFlags,
16
+ type AgentExecOptions,
17
+ toAgentExecFlags,
18
+ } from "./agent-exec-flags.js";
14
19
  import { globalOrg } from "./shared.js";
15
20
 
16
21
  // The org that owns the system agent blueprints (installed by the seedpack),
@@ -31,9 +36,21 @@ interface DraftConfig {
31
36
  }
32
37
 
33
38
  const CONFIGS: readonly DraftConfig[] = [
34
- { name: "skill", resourceType: "Skill", summary: "create a skill with AI assistance" },
35
- { name: "agent", resourceType: "Agent", summary: "create an agent with AI assistance" },
36
- { name: "mcp-server", resourceType: "McpServer", summary: "create an MCP server config with AI assistance" },
39
+ {
40
+ name: "skill",
41
+ resourceType: "Skill",
42
+ summary: "create a skill with AI assistance",
43
+ },
44
+ {
45
+ name: "agent",
46
+ resourceType: "Agent",
47
+ summary: "create an agent with AI assistance",
48
+ },
49
+ {
50
+ name: "mcp-server",
51
+ resourceType: "McpServer",
52
+ summary: "create an MCP server config with AI assistance",
53
+ },
37
54
  ];
38
55
 
39
56
  // System agent slug per subcommand (Go's draftConfig.AgentName: "<type>-creator").
@@ -42,17 +59,28 @@ function agentSlug(name: string): string {
42
59
  }
43
60
 
44
61
  export function registerDraft(program: Command): void {
45
- const draft = program.command("draft").description("create resource configurations with AI assistance");
62
+ const draft = program
63
+ .command("draft")
64
+ .description("create resource configurations with AI assistance");
46
65
  for (const config of CONFIGS) {
47
66
  const sub = draft.command(config.name).description(config.summary);
48
67
  addAgentExecFlags(sub, true)
49
- .option("-o, --output <dir>", "directory to save generated artifacts (default: current directory)")
68
+ .option(
69
+ "-o, --output <dir>",
70
+ "directory to save generated artifacts (default: current directory)",
71
+ )
50
72
  .option("--json", "stream events as newline-delimited JSON")
51
- .action((options: DraftFlags, command: Command) => runDraft(config, options, command));
73
+ .action((options: DraftFlags, command: Command) =>
74
+ runDraft(config, options, command),
75
+ );
52
76
  }
53
77
  }
54
78
 
55
- async function runDraft(config: DraftConfig, options: DraftFlags, command: Command): Promise<void> {
79
+ async function runDraft(
80
+ config: DraftConfig,
81
+ options: DraftFlags,
82
+ command: Command,
83
+ ): Promise<void> {
56
84
  const { connectBackend } = await import("../backend.js");
57
85
  const client = connectBackend();
58
86
  ensureAuthenticated(client.config);
@@ -63,29 +91,52 @@ async function runDraft(config: DraftConfig, options: DraftFlags, command: Comma
63
91
  );
64
92
  }
65
93
 
66
- const [{ prepareAgentExec }, { executeResolvedAgent }, { resolveAgentRef }, { localWorkspaceRoots }] =
67
- await Promise.all([
68
- import("../resources/run/prepare.js"),
69
- import("../resources/run/agent-exec.js"),
70
- import("../resources/run/resolve.js"),
71
- import("../resources/run/workspace.js"),
72
- ]);
73
-
74
- const agent = await resolveDraftAgent(resolveAgentRef, client.stigmer, config);
75
-
76
- const prepared = await prepareAgentExec(toAgentExecFlags(options), client.stigmer, org, stderrProgress(), {
77
- cloudBackend: isCloudMode(client.config),
78
- });
94
+ const [
95
+ { prepareAgentExec },
96
+ { executeResolvedAgent },
97
+ { resolveAgentRef },
98
+ { localWorkspaceRoots },
99
+ ] = await Promise.all([
100
+ import("../resources/run/prepare.js"),
101
+ import("../resources/run/agent-exec.js"),
102
+ import("../resources/run/resolve.js"),
103
+ import("../resources/run/workspace.js"),
104
+ ]);
105
+
106
+ const agent = await resolveDraftAgent(
107
+ resolveAgentRef,
108
+ client.stigmer,
109
+ config,
110
+ );
111
+
112
+ const prepared = await prepareAgentExec(
113
+ toAgentExecFlags(options),
114
+ client.stigmer,
115
+ org,
116
+ stderrProgress(),
117
+ {
118
+ // The kind's tier against the server's reported edition, as `run` asks it.
119
+ accountPreferencesAvailable: await client.isResourceAvailable(
120
+ ApiResourceKind.identity_account,
121
+ ),
122
+ },
123
+ );
79
124
 
80
125
  if (prepared.attachments.length + prepared.workspaceFileRefs.length > 0) {
81
- process.stderr.write(`Attached ${prepared.attachments.length + prepared.workspaceFileRefs.length} file(s) as context\n`);
126
+ process.stderr.write(
127
+ `Attached ${prepared.attachments.length + prepared.workspaceFileRefs.length} file(s) as context\n`,
128
+ );
82
129
  }
83
130
 
84
131
  await executeResolvedAgent({
85
132
  agent,
86
133
  prepared,
87
134
  org,
88
- downloadDir: resolveDownloadDir(options.output ?? "", prepared.detach, localWorkspaceRoots(prepared.workspaceEntries)),
135
+ downloadDir: resolveDownloadDir(
136
+ options.output ?? "",
137
+ prepared.detach,
138
+ localWorkspaceRoots(prepared.workspaceEntries),
139
+ ),
89
140
  outputMode: options.json === true ? "json" : "inline",
90
141
  client,
91
142
  });
@@ -100,7 +151,11 @@ async function resolveDraftAgent(
100
151
  ): Promise<import("@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb").Agent> {
101
152
  const slug = agentSlug(config.name);
102
153
  try {
103
- return await resolveAgentRef(client, `${SYSTEM_AGENT_ORG}/${slug}`, SYSTEM_AGENT_ORG);
154
+ return await resolveAgentRef(
155
+ client,
156
+ `${SYSTEM_AGENT_ORG}/${slug}`,
157
+ SYSTEM_AGENT_ORG,
158
+ );
104
159
  } catch {
105
160
  throw new UsageError(
106
161
  `${slug} agent not found in organization "${SYSTEM_AGENT_ORG}"\n\n` +
@@ -117,7 +172,11 @@ async function resolveDraftAgent(
117
172
  // explicit -o → that directory
118
173
  // local workspace → skip (the agent writes directly to disk)
119
174
  // otherwise → current directory
120
- function resolveDownloadDir(output: string, detach: boolean, localRoots: readonly string[]): string {
175
+ function resolveDownloadDir(
176
+ output: string,
177
+ detach: boolean,
178
+ localRoots: readonly string[],
179
+ ): string {
121
180
  if (detach) return "";
122
181
  if (output !== "") return output;
123
182
  if (localRoots.length > 0) return "";
@@ -1,6 +1,7 @@
1
1
  // `stigmer internal-daemon` (hidden) — the long-lived supervised daemon process
2
2
  // that `up` re-execs. It is not meant to be invoked directly; it runs the local
3
3
  // stack until it receives SIGTERM/SIGINT, then exits with the daemon's code.
4
+ // (`stigmer up --foreground` runs the same body in the launching process.)
4
5
 
5
6
  import type { Command } from "commander";
6
7
 
@@ -9,17 +10,8 @@ export function registerInternalDaemon(program: Command): void {
9
10
  .command("internal-daemon", { hidden: true })
10
11
  .description("internal: run the supervised local-stack daemon (do not invoke directly)")
11
12
  .action(async () => {
12
- const { runInternalDaemon } = await import("../local/daemon/process.js");
13
+ const { runInternalDaemon, waitForShutdownSignal } = await import("../local/daemon/process.js");
13
14
  const code = await runInternalDaemon({ waitForShutdown: waitForShutdownSignal });
14
15
  process.exit(code);
15
16
  });
16
17
  }
17
-
18
- // Resolve when the first SIGTERM/SIGINT arrives — the daemon's shutdown trigger.
19
- function waitForShutdownSignal(): Promise<void> {
20
- return new Promise((resolve) => {
21
- const onSignal = (): void => resolve();
22
- process.once("SIGTERM", onSignal);
23
- process.once("SIGINT", onSignal);
24
- });
25
- }
@@ -5,10 +5,14 @@
5
5
  // the differ) load lazily inside the action so `--help` stays fast (DD-001).
6
6
 
7
7
  import type { Command } from "commander";
8
- import { ensureAuthenticated, isCloudMode, resolveOrganization } from "../config/index.js";
8
+ import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
9
+ import { ensureAuthenticated, resolveOrganization } from "../config/index.js";
9
10
  import { UsageError } from "../errors/index.js";
10
11
  import { interactiveBrowseEnabled } from "../resources/picker/tty.js";
11
- import { addAgentExecFlags, type AgentExecOptions } from "./agent-exec-flags.js";
12
+ import {
13
+ addAgentExecFlags,
14
+ type AgentExecOptions,
15
+ } from "./agent-exec-flags.js";
12
16
  import { globalOrg } from "./shared.js";
13
17
 
14
18
  interface RunFlags extends AgentExecOptions {
@@ -23,8 +27,13 @@ export function registerRun(program: Command): void {
23
27
  addAgentExecFlags(run)
24
28
  .option("--json", "stream events as newline-delimited JSON")
25
29
  .option("--download <dir>", "download artifacts to directory when complete")
26
- .action((type: string | undefined, reference: string | undefined, options: RunFlags, command: Command) =>
27
- runRun(type, reference, options, command),
30
+ .action(
31
+ (
32
+ type: string | undefined,
33
+ reference: string | undefined,
34
+ options: RunFlags,
35
+ command: Command,
36
+ ) => runRun(type, reference, options, command),
28
37
  );
29
38
  }
30
39
 
@@ -76,7 +85,8 @@ async function runSmart(
76
85
  outputMode: "inline" | "json",
77
86
  client: import("../client/index.js").BackendClient,
78
87
  ): Promise<void> {
79
- const { isSessionId, hasResourceIdPrefix, isAgentId, validateResourceId } = await import("../resources/reference.js");
88
+ const { isSessionId, hasResourceIdPrefix, isAgentId, validateResourceId } =
89
+ await import("../resources/reference.js");
80
90
 
81
91
  if (isSessionId(value)) {
82
92
  throw new UsageError(
@@ -126,13 +136,14 @@ async function runExplicit(
126
136
  client: import("../client/index.js").BackendClient,
127
137
  ): Promise<void> {
128
138
  const { defaultRegistry, Verb } = await import("../registry/index.js");
129
- const { ApiResourceKind } = await import(
130
- "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb"
131
- );
139
+ const { ApiResourceKind } =
140
+ await import("@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb");
132
141
 
133
142
  const info = defaultRegistry().getByAlias(type);
134
143
  if (info === undefined) {
135
- throw new UsageError(`unknown resource type: ${type}\n\nAvailable types: agent, workflow`);
144
+ throw new UsageError(
145
+ `unknown resource type: ${type}\n\nAvailable types: agent, workflow`,
146
+ );
136
147
  }
137
148
  if (!info.supportedVerbs.has(Verb.Run)) {
138
149
  throw new UsageError(`${info.displayName} does not support 'run'`);
@@ -168,15 +179,26 @@ async function runResolvedAgent(
168
179
  client: import("../client/index.js").BackendClient,
169
180
  ): Promise<void> {
170
181
  const { prepareAgentExec } = await import("../resources/run/prepare.js");
171
- const { executeResolvedAgent } = await import("../resources/run/agent-exec.js");
182
+ const { executeResolvedAgent } =
183
+ await import("../resources/run/agent-exec.js");
172
184
  const { toAgentExecFlags } = await import("./agent-exec-flags.js");
173
185
 
174
- const prepared = await prepareAgentExec(toAgentExecFlags(options), client.stigmer, org, stderrProgress(), {
175
- cloudBackend: isCloudMode(client.config),
176
- // run opts into the account default_harness fill; draft deliberately
177
- // does not (see the option's doc comment for the D5 rationale).
178
- applyAccountHarnessDefault: true,
179
- });
186
+ const prepared = await prepareAgentExec(
187
+ toAgentExecFlags(options),
188
+ client.stigmer,
189
+ org,
190
+ stderrProgress(),
191
+ {
192
+ // The kind's tier against the server's reported edition — the same
193
+ // question the console asks — never the config's backend type.
194
+ accountPreferencesAvailable: await client.isResourceAvailable(
195
+ ApiResourceKind.identity_account,
196
+ ),
197
+ // run opts into the account default_harness fill; draft deliberately
198
+ // does not (see the option's doc comment for the D5 rationale).
199
+ applyAccountHarnessDefault: true,
200
+ },
201
+ );
180
202
  await executeResolvedAgent({
181
203
  agent,
182
204
  prepared,
@@ -199,15 +221,21 @@ async function runWorkflow(
199
221
  client: import("../client/index.js").BackendClient,
200
222
  ): Promise<void> {
201
223
  if (options.workspace.length > 0) {
202
- throw new UsageError("--workspace is not supported for workflows (workspace is an agent-level concept)");
224
+ throw new UsageError(
225
+ "--workspace is not supported for workflows (workspace is an agent-level concept)",
226
+ );
203
227
  }
204
- const [{ resolveWorkflowRef }, { createWorkflowExecution }, { loadAndMergeEnv }, { parseApprovalAction }] =
205
- await Promise.all([
206
- import("../resources/run/resolve.js"),
207
- import("../resources/run/create.js"),
208
- import("../resources/run/env.js"),
209
- import("../resources/run/prepare.js"),
210
- ]);
228
+ const [
229
+ { resolveWorkflowRef },
230
+ { createWorkflowExecution },
231
+ { loadAndMergeEnv },
232
+ { parseApprovalAction },
233
+ ] = await Promise.all([
234
+ import("../resources/run/resolve.js"),
235
+ import("../resources/run/create.js"),
236
+ import("../resources/run/env.js"),
237
+ import("../resources/run/prepare.js"),
238
+ ]);
211
239
 
212
240
  const workflow = await resolveWorkflowRef(client.stigmer, reference, org);
213
241
  const runtimeEnv = loadAndMergeEnv({
@@ -220,27 +248,37 @@ async function runWorkflow(
220
248
  runtimeEnv.STIGMER_ORG_ID = { value: org, isSecret: false };
221
249
  }
222
250
 
223
- const execution = await createWorkflowExecution(client.controller.bind(client), {
224
- workflowId: workflow.metadata?.id ?? "",
225
- orgId: org,
226
- message: options.message ?? "",
227
- runtimeEnv,
228
- });
251
+ const execution = await createWorkflowExecution(
252
+ client.controller.bind(client),
253
+ {
254
+ workflowId: workflow.metadata?.id ?? "",
255
+ orgId: org,
256
+ message: options.message ?? "",
257
+ runtimeEnv,
258
+ },
259
+ );
229
260
  const id = execution.metadata?.id ?? "";
230
261
 
231
262
  if (options.detach === true) {
232
263
  process.stdout.write(`Workflow execution created: ${id}\n`);
233
- process.stdout.write(`Track it with: stigmer execution logs ${id} --follow\n`);
264
+ process.stdout.write(
265
+ `Track it with: stigmer execution logs ${id} --follow\n`,
266
+ );
234
267
  return;
235
268
  }
236
269
 
237
- const { ApprovalAction } = await import("@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb");
238
- const { streamWorkflowExecution } = await import("../resources/run/workflow-stream.js");
270
+ const { ApprovalAction } =
271
+ await import("@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb");
272
+ const { streamWorkflowExecution } =
273
+ await import("../resources/run/workflow-stream.js");
239
274
  await streamWorkflowExecution({
240
275
  client: client.stigmer,
241
276
  executionId: id,
242
277
  outputMode,
243
- defaultAction: options.autoApprove === true ? ApprovalAction.APPROVE_ALL : parseApprovalAction(options.approveDefault ?? ""),
278
+ defaultAction:
279
+ options.autoApprove === true
280
+ ? ApprovalAction.APPROVE_ALL
281
+ : parseApprovalAction(options.approveDefault ?? ""),
244
282
  });
245
283
  }
246
284
 
@@ -260,7 +298,11 @@ async function browseAndRunAgent(
260
298
  client: import("../client/index.js").BackendClient,
261
299
  ): Promise<void> {
262
300
  const { pickAgent } = await import("../resources/picker/ink.js");
263
- const selected = await pickAgent({ client: client.stigmer, org, initialQuery });
301
+ const selected = await pickAgent({
302
+ client: client.stigmer,
303
+ org,
304
+ initialQuery,
305
+ });
264
306
  if (selected === undefined) return;
265
307
  await runAgent(selected.id, options, org, outputMode, client);
266
308
  }
@@ -1,6 +1,7 @@
1
1
  // `stigmer up [server]` — start the local Stigmer stack (managed Temporal, the
2
2
  // control-plane server, and the unified runner) as a supervised background
3
- // daemon. `up server` brings up only the control plane.
3
+ // daemon, or with `--foreground` as this very process. `up server` brings up
4
+ // only the control plane.
4
5
  //
5
6
  // Thin handler: parse flags, delegate to the local daemon launcher, render the
6
7
  // outcome. The launcher (and the heavy resolvers it pulls in) load lazily so
@@ -9,6 +10,8 @@
9
10
  import { homedir } from "node:os";
10
11
  import { join } from "node:path";
11
12
  import type { Command } from "commander";
13
+ import { CliExitError } from "../errors/cli-exit-error.js";
14
+ import { ExitCode } from "../errors/exit-codes.js";
12
15
  import { CommandResult, type OutputFlags, renderResult } from "../output/index.js";
13
16
  import { HEALTH_STATE_FILE, SERVER_PORT } from "../local/constants.js";
14
17
  import { addResultFlags, resultFormat } from "./shared.js";
@@ -16,6 +19,13 @@ import { addResultFlags, resultFormat } from "./shared.js";
16
19
  interface UpFlags extends OutputFlags {
17
20
  serverOnly?: boolean;
18
21
  web?: boolean; // false when --no-web is passed
22
+ foreground?: boolean;
23
+ }
24
+
25
+ interface UpRun {
26
+ serverOnly: boolean;
27
+ noWeb: boolean;
28
+ foreground: boolean;
19
29
  }
20
30
 
21
31
  // The server serves the web console from its unified port (DD-012); the
@@ -23,14 +33,23 @@ interface UpFlags extends OutputFlags {
23
33
  // process, one origin — there is no separate console to not-start).
24
34
  const NO_WEB_HELP = "don't report the web console URL";
25
35
 
36
+ // Foreground is the shape a supervisor wants — a container entrypoint, a
37
+ // systemd unit, a tmux pane: one process to watch, signals delivered to it,
38
+ // the stack's output on its stdio — instead of a detached daemon to poll.
39
+ const FOREGROUND_HELP = "run the stack in this process until Ctrl-C or SIGTERM (for containers and service managers)";
40
+
26
41
  export function registerUp(program: Command): void {
27
42
  const up = program
28
43
  .command("up")
29
44
  .description("start the local Stigmer stack (server, runner, Temporal)")
30
45
  .option("--server-only", "start only the control plane (no runners)")
31
46
  .option("--no-web", NO_WEB_HELP)
47
+ .option("--foreground", FOREGROUND_HELP)
32
48
  .action((options: UpFlags) =>
33
- runUp({ serverOnly: options.serverOnly === true, noWeb: options.web === false }, options),
49
+ runUp(
50
+ { serverOnly: options.serverOnly === true, noWeb: options.web === false, foreground: options.foreground === true },
51
+ options,
52
+ ),
34
53
  );
35
54
  addResultFlags(up);
36
55
 
@@ -38,16 +57,40 @@ export function registerUp(program: Command): void {
38
57
  .command("server")
39
58
  .description("start only the control plane (no runners)")
40
59
  .option("--no-web", NO_WEB_HELP)
41
- .action((options: UpFlags) => runUp({ serverOnly: true, noWeb: options.web === false }, options));
60
+ .option("--foreground", FOREGROUND_HELP)
61
+ .action((options: UpFlags) =>
62
+ runUp({ serverOnly: true, noWeb: options.web === false, foreground: options.foreground === true }, options),
63
+ );
42
64
  addResultFlags(server);
43
65
  }
44
66
 
45
- async function runUp(opts: { serverOnly: boolean; noWeb: boolean }, flags: OutputFlags): Promise<void> {
67
+ async function runUp(run: UpRun, flags: OutputFlags): Promise<void> {
46
68
  process.stderr.write("Starting Stigmer local stack… (first run may take a moment)\n");
47
- const { up } = await import("../local/daemon/launch.js");
48
- await up(opts);
69
+ const launch = await import("../local/daemon/launch.js");
70
+
71
+ if (!run.foreground) {
72
+ await launch.up(run);
73
+ await renderUpCard(run, flags);
74
+ return;
75
+ }
76
+
77
+ // The card renders when the stack is serving and seeded — the moment the
78
+ // detached shape would have returned — and the process then stays up,
79
+ // mirroring the server's and runner's output, until a shutdown signal.
80
+ const code = await launch.upForeground(run, homedir(), {
81
+ onReady: () => renderUpCard(run, flags),
82
+ });
83
+ if (code !== 0) {
84
+ // The daemon body has already logged which component failed and why.
85
+ const { logDir } = await import("../local/paths.js");
86
+ throw new CliExitError("the local stack did not start", ExitCode.General, [
87
+ `Check the component logs under ${logDir(homedir())}.`,
88
+ ]);
89
+ }
90
+ }
49
91
 
50
- const result = CommandResult.success(opts.serverOnly ? "Stigmer control plane is up" : "Stigmer local stack is up");
92
+ async function renderUpCard(run: UpRun, flags: OutputFlags): Promise<void> {
93
+ const result = CommandResult.success(run.serverOnly ? "Stigmer control plane is up" : "Stigmer local stack is up");
51
94
  const section = result.addSection("Endpoints");
52
95
  section.field("server", `http://localhost:${SERVER_PORT}`);
53
96
  if (await consoleReported()) {
@@ -56,7 +99,8 @@ async function runUp(opts: { serverOnly: boolean; noWeb: boolean }, flags: Outpu
56
99
  // server without one must not advertise a dead URL.
57
100
  section.field("console", `http://localhost:${SERVER_PORT}`);
58
101
  }
59
- result.hint("Check status with: stigmer status").hint("Stop it with: stigmer down");
102
+ result.hint("Check status with: stigmer status");
103
+ result.hint(run.foreground ? "Stop it with: Ctrl-C (or stigmer down from another shell)" : "Stop it with: stigmer down");
60
104
  renderResult(result, resultFormat(flags));
61
105
  }
62
106