@eve-horizon/cli 0.2.42 → 0.2.44

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.
@@ -45,6 +45,8 @@ spec:
45
45
  value: http://eve-api:4701
46
46
  - name: EVE_PUBLIC_API_URL
47
47
  value: http://api.eve.lvh.me
48
+ - name: EVE_SSO_URL
49
+ value: http://sso.eve.lvh.me
48
50
  - name: DATABASE_URL
49
51
  value: postgres://eve:eve@postgres.eve.svc.cluster.local:5432/eve
50
52
  - name: WORKER_PORT
package/dist/index.js CHANGED
@@ -50320,6 +50320,24 @@ for cloud deployments. Credentials are stored globally per API URL.`,
50320
50320
  "eve skills install"
50321
50321
  ]
50322
50322
  },
50323
+ user: {
50324
+ description: "Look up user profiles and memberships.",
50325
+ usage: "eve user <subcommand> [options]",
50326
+ subcommands: {
50327
+ show: {
50328
+ description: "Show user profile with org and project memberships",
50329
+ usage: "eve user show [user_id|me]",
50330
+ options: [
50331
+ "--json Output as JSON"
50332
+ ],
50333
+ examples: [
50334
+ "eve user show me",
50335
+ "eve user show usr_abc123",
50336
+ "eve user show me --json"
50337
+ ]
50338
+ }
50339
+ }
50340
+ },
50323
50341
  admin: {
50324
50342
  description: "Administrative commands for user, identity, and platform operations.",
50325
50343
  usage: "eve admin <subcommand> [options]",
@@ -50899,21 +50917,27 @@ Requires Docker Desktop; k3d and kubectl are auto-managed by the CLI.`,
50899
50917
  usage: "eve chat <subcommand> [options]",
50900
50918
  subcommands: {
50901
50919
  simulate: {
50902
- description: "Simulate an inbound chat message",
50903
- usage: "eve chat simulate --project <id> --team-id <team> --text <message>",
50920
+ description: "Simulate an inbound chat message via the gateway",
50921
+ usage: "eve chat simulate --team-id <team> --text <message>",
50904
50922
  options: [
50905
- "--project <id> Project ID (uses profile default)",
50906
- "--provider <name> Provider name (default: slack)",
50907
- "--team-id <id> Slack team ID",
50908
- "--channel-id <id> Channel ID",
50909
- "--user-id <id> User ID",
50910
- "--thread-key <key> Thread key override",
50911
- "--metadata <json> Extra metadata JSON"
50923
+ "--team-id <id> Slack team ID (required)",
50924
+ "--text <msg> Message text (required)",
50925
+ "--provider <name> Provider name (default: slack)",
50926
+ "--channel-id <id> Channel ID",
50927
+ "--user-id <id> User ID",
50928
+ "--external-email <e> Email hint for Tier 1 identity auto-match",
50929
+ "--dedupe-key <key> Deduplication key",
50930
+ "--event-type <type> Event type (default: app_mention)",
50931
+ "--thread-id <id> Thread ID override",
50932
+ "--metadata <json> Extra metadata JSON",
50933
+ "--project <id> [deprecated] Legacy API simulate path"
50912
50934
  ]
50913
50935
  }
50914
50936
  },
50915
50937
  examples: [
50916
- 'eve chat simulate --project proj_xxx --team-id T123 --text "hello"'
50938
+ 'eve chat simulate --team-id T123 --text "hello"',
50939
+ 'eve chat simulate --team-id T123 --text "@eve deploy" --external-email alice@example.com',
50940
+ 'eve chat simulate --team-id T123 --text "hello" --dedupe-key test-dedup-1'
50917
50941
  ]
50918
50942
  },
50919
50943
  docs: {
@@ -51460,6 +51484,7 @@ function showMainHelp() {
51460
51484
  console.log(" analytics Org analytics (jobs, pipelines, env health)");
51461
51485
  console.log(" ollama Manage inference targets, aliases, and model routes");
51462
51486
  console.log(" access Access control: permissions, roles, bindings, policy-as-code sync");
51487
+ console.log(" user Look up user profiles and memberships");
51463
51488
  console.log(" admin User and platform admin operations");
51464
51489
  console.log(" skills Install skills from skills.txt (skills CLI)");
51465
51490
  console.log(" migrate Migration helpers for upgrading config formats");
@@ -51863,8 +51888,52 @@ async function handleOrg(subcommand, positionals, flags, context2) {
51863
51888
  }
51864
51889
  }
51865
51890
  }
51891
+ case "membership-requests": {
51892
+ const action = positionals[0];
51893
+ const orgId = typeof flags.org === "string" ? flags.org : context2.orgId;
51894
+ if (!orgId) {
51895
+ throw new Error("Missing org id. Provide --org or set a profile default.");
51896
+ }
51897
+ switch (action) {
51898
+ case "approve": {
51899
+ const requestId = positionals[1];
51900
+ if (!requestId) {
51901
+ throw new Error("Usage: eve org membership-requests approve <request_id> [--role member|admin]");
51902
+ }
51903
+ const role = getStringFlag(flags, ["role"]) ?? "member";
51904
+ const email = getStringFlag(flags, ["email"]);
51905
+ const body = { role };
51906
+ if (email) body.email = email;
51907
+ const response = await requestJson(context2, `/orgs/${orgId}/membership-requests/${requestId}/approve`, {
51908
+ method: "POST",
51909
+ body
51910
+ });
51911
+ outputJson(response, json, `\u2713 Membership request ${requestId} approved`);
51912
+ return;
51913
+ }
51914
+ case "deny": {
51915
+ const requestId = positionals[1];
51916
+ if (!requestId) {
51917
+ throw new Error("Usage: eve org membership-requests deny <request_id>");
51918
+ }
51919
+ const response = await requestJson(context2, `/orgs/${orgId}/membership-requests/${requestId}/deny`, {
51920
+ method: "POST"
51921
+ });
51922
+ outputJson(response, json, `\u2713 Membership request ${requestId} denied`);
51923
+ return;
51924
+ }
51925
+ case "list":
51926
+ default: {
51927
+ const status = getStringFlag(flags, ["status"]);
51928
+ const query = status ? `?status=${status}` : "";
51929
+ const response = await requestJson(context2, `/orgs/${orgId}/membership-requests${query}`);
51930
+ outputJson(response, json);
51931
+ return;
51932
+ }
51933
+ }
51934
+ }
51866
51935
  default:
51867
- throw new Error("Usage: eve org <ensure|list|get|spend|update|delete|members>");
51936
+ throw new Error("Usage: eve org <ensure|list|get|spend|update|delete|members|membership-requests>");
51868
51937
  }
51869
51938
  }
51870
51939
  function parseSinceValue(since) {
@@ -56602,6 +56671,8 @@ var configSchema = external_exports.object({
56602
56671
  EVE_GITHUB_WEBHOOK_SECRET: external_exports.string().optional(),
56603
56672
  EVE_GITHUB_TOKEN: external_exports.string().optional(),
56604
56673
  EVE_SLACK_SIGNING_SECRET: external_exports.string().optional(),
56674
+ EVE_SLACK_CLIENT_ID: external_exports.string().optional(),
56675
+ EVE_SLACK_CLIENT_SECRET: external_exports.string().optional(),
56605
56676
  EVE_INTERNAL_API_KEY: external_exports.string().optional(),
56606
56677
  EVE_ORG_FS_LINK_TOKEN_SECRET: external_exports.string().optional(),
56607
56678
  EVE_ORG_FS_LINK_TOKEN_TTL_SECONDS: external_exports.coerce.number().int().min(60).default(900),
@@ -57178,6 +57249,8 @@ var OrgMemberRequestSchema = external_exports.object({
57178
57249
  var OrgMemberResponseSchema = external_exports.object({
57179
57250
  org_id: OrgIdSchema,
57180
57251
  user_id: external_exports.string(),
57252
+ email: external_exports.string(),
57253
+ display_name: external_exports.string().nullable(),
57181
57254
  role: OrgMemberRoleSchema,
57182
57255
  created_at: external_exports.string(),
57183
57256
  updated_at: external_exports.string()
@@ -57403,6 +57476,8 @@ var ProjectMemberRequestSchema = external_exports.object({
57403
57476
  var ProjectMemberResponseSchema = external_exports.object({
57404
57477
  project_id: external_exports.string(),
57405
57478
  user_id: external_exports.string(),
57479
+ email: external_exports.string(),
57480
+ display_name: external_exports.string().nullable(),
57406
57481
  role: ProjectMemberRoleSchema,
57407
57482
  created_at: external_exports.string(),
57408
57483
  updated_at: external_exports.string()
@@ -59222,7 +59297,8 @@ var SlackConnectRequestSchema = external_exports.object({
59222
59297
  status: external_exports.string().optional()
59223
59298
  });
59224
59299
  var IntegrationTestResponseSchema = external_exports.object({
59225
- ok: external_exports.boolean()
59300
+ ok: external_exports.boolean(),
59301
+ detail: external_exports.string().optional()
59226
59302
  });
59227
59303
  var IntegrationResolveRequestSchema = external_exports.object({
59228
59304
  provider: external_exports.string().min(1),
@@ -59236,13 +59312,54 @@ var ExternalIdentityResolveRequestSchema = external_exports.object({
59236
59312
  provider: external_exports.string().min(1),
59237
59313
  account_id: external_exports.string().min(1),
59238
59314
  external_user_id: external_exports.string().min(1),
59239
- org_id: external_exports.string().min(1)
59315
+ org_id: external_exports.string().min(1),
59316
+ external_email: external_exports.string().email().optional()
59240
59317
  });
59241
59318
  var ExternalIdentityResolveResponseSchema = external_exports.object({
59242
59319
  external_identity_id: external_exports.string(),
59243
59320
  eve_user_id: external_exports.string().nullable(),
59244
59321
  membership_request_id: external_exports.string().nullable()
59245
59322
  });
59323
+ var MembershipRequestResponseSchema = external_exports.object({
59324
+ id: external_exports.string(),
59325
+ org_id: external_exports.string(),
59326
+ external_identity_id: external_exports.string(),
59327
+ status: external_exports.string(),
59328
+ approved_by: external_exports.string().nullable(),
59329
+ approved_at: external_exports.string().nullable(),
59330
+ created_at: external_exports.string(),
59331
+ updated_at: external_exports.string()
59332
+ });
59333
+ var MembershipRequestListResponseSchema = external_exports.object({
59334
+ requests: external_exports.array(MembershipRequestResponseSchema)
59335
+ });
59336
+ var MembershipRequestApproveRequestSchema = external_exports.object({
59337
+ role: external_exports.string().default("member"),
59338
+ email: external_exports.string().email().optional()
59339
+ });
59340
+ var IdentityLinkTokenRequestSchema = external_exports.object({
59341
+ provider: external_exports.string().min(1),
59342
+ org_id: external_exports.string().min(1)
59343
+ });
59344
+ var IdentityLinkTokenResponseSchema = external_exports.object({
59345
+ token: external_exports.string(),
59346
+ expires_in: external_exports.number(),
59347
+ instructions: external_exports.string()
59348
+ });
59349
+ var IdentityLinkRedeemRequestSchema = external_exports.object({
59350
+ token: external_exports.string().min(1),
59351
+ provider: external_exports.string().min(1),
59352
+ account_id: external_exports.string().min(1),
59353
+ external_user_id: external_exports.string().min(1)
59354
+ });
59355
+ var IdentityLinkRedeemResponseSchema = external_exports.object({
59356
+ ok: external_exports.boolean(),
59357
+ external_identity_id: external_exports.string().optional(),
59358
+ error: external_exports.string().optional()
59359
+ });
59360
+ var IntegrationSettingsUpdateRequestSchema = external_exports.object({
59361
+ settings: external_exports.record(external_exports.unknown())
59362
+ });
59246
59363
 
59247
59364
  // ../shared/dist/schemas/builds.js
59248
59365
  var CreateBuildSpecRequestSchema = external_exports.object({
@@ -67052,17 +67169,18 @@ async function handleStatus2(context2, json) {
67052
67169
  }
67053
67170
  async function handleHealth(context2, json) {
67054
67171
  try {
67055
- const health = await requestJson(context2, "/health");
67172
+ const response = await requestRaw(context2, "/health", { allowError: true });
67173
+ const health = response.data ?? {};
67056
67174
  if (json) {
67057
67175
  outputJson(health, json);
67058
67176
  } else {
67059
- const isHealthy = health.status === "ok" || health.status === "healthy";
67177
+ const isHealthy = response.ok && (health.status === "ok" || health.status === "healthy");
67060
67178
  const icon = isHealthy ? "\u2713" : "\u2717";
67061
- const status = isHealthy ? "Healthy" : "Unhealthy";
67179
+ const statusLabel = isHealthy ? "Healthy" : "Degraded";
67062
67180
  console.log("System Health Check");
67063
67181
  console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
67064
67182
  console.log("");
67065
- console.log(` ${icon} API: ${status}`);
67183
+ console.log(` ${icon} API: ${statusLabel}`);
67066
67184
  if (health.database) {
67067
67185
  console.log(` Database: ${health.database}`);
67068
67186
  }
@@ -67073,7 +67191,7 @@ async function handleHealth(context2, json) {
67073
67191
  if (isHealthy) {
67074
67192
  console.log("API is operational.");
67075
67193
  } else {
67076
- console.log("API is not healthy. Check server logs.");
67194
+ console.log("API is degraded. Check server logs and database connectivity.");
67077
67195
  }
67078
67196
  }
67079
67197
  } catch (error) {
@@ -73475,6 +73593,7 @@ function parseSkillsManifest(manifestPath) {
73475
73593
  }
73476
73594
  const content = fs4.readFileSync(manifestPath, "utf-8");
73477
73595
  const sources = [];
73596
+ const manifestDir = path6.dirname(manifestPath);
73478
73597
  for (const rawLine of content.split("\n")) {
73479
73598
  const line = rawLine.split("#")[0].trim();
73480
73599
  if (!line) continue;
@@ -73483,11 +73602,46 @@ function parseSkillsManifest(manifestPath) {
73483
73602
  const expanded = expandGlobPattern(line, manifestPath, explicitPrivateTarget);
73484
73603
  sources.push(...expanded);
73485
73604
  } else {
73486
- sources.push(parseSkillSource(line));
73605
+ const parsed = parseSkillSource(line);
73606
+ if (parsed.type === "local") {
73607
+ const expanded = tryExpandLocalDirectory(parsed, manifestDir);
73608
+ if (expanded) {
73609
+ sources.push(...expanded);
73610
+ continue;
73611
+ }
73612
+ }
73613
+ sources.push(parsed);
73487
73614
  }
73488
73615
  }
73489
73616
  return sources;
73490
73617
  }
73618
+ function tryExpandLocalDirectory(skill, manifestDir) {
73619
+ let source = skill.source;
73620
+ if (source.startsWith("~")) {
73621
+ source = source.replace(/^~/, process.env.HOME || "~");
73622
+ }
73623
+ const abs = path6.isAbsolute(source) ? source : path6.resolve(manifestDir, source);
73624
+ try {
73625
+ if (!fs4.existsSync(abs) || !fs4.statSync(abs).isDirectory()) return null;
73626
+ } catch {
73627
+ return null;
73628
+ }
73629
+ if (fs4.existsSync(path6.join(abs, "SKILL.md"))) return null;
73630
+ const explicitPrivate = sourcePathExplicitlyTargetsPrivate(skill.source);
73631
+ const skillDirs = findSkillDirs(abs, { fullDepth: true, excludePrivate: !explicitPrivate });
73632
+ if (skillDirs.length === 0) return null;
73633
+ return skillDirs.map((dir) => {
73634
+ const name = path6.basename(dir);
73635
+ const relativePath = path6.relative(manifestDir, dir);
73636
+ const resolvedSource = relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
73637
+ return {
73638
+ raw: skill.raw,
73639
+ source: resolvedSource,
73640
+ type: "local",
73641
+ name
73642
+ };
73643
+ });
73644
+ }
73491
73645
  function parseSkillSource(line) {
73492
73646
  if (line.startsWith("https://") || line.startsWith("http://")) {
73493
73647
  const name2 = extractNameFromUrl(line);
@@ -74150,17 +74304,21 @@ async function handleUsers(flags, context2, json) {
74150
74304
  const rows = [];
74151
74305
  for (const user of users) {
74152
74306
  const base = {
74153
- id: user.id,
74154
74307
  email: user.email,
74155
74308
  name: user.display_name ?? "-",
74156
74309
  admin: user.is_admin ? "yes" : "",
74157
74310
  created: user.created_at?.split("T")[0] ?? ""
74158
74311
  };
74159
- if (user.memberships.length === 0) {
74160
- rows.push({ ...base, org: "-", role: "-" });
74312
+ const hasOrg = user.memberships.length > 0;
74313
+ const hasProj = (user.project_memberships ?? []).length > 0;
74314
+ if (!hasOrg && !hasProj) {
74315
+ rows.push({ ...base, scope: "-", target: "-", role: "-" });
74161
74316
  } else {
74162
74317
  for (const m of user.memberships) {
74163
- rows.push({ ...base, org: m.org_slug || m.org_name, role: m.role });
74318
+ rows.push({ ...base, scope: "org", target: m.org_slug || m.org_name, role: m.role });
74319
+ }
74320
+ for (const pm of user.project_memberships ?? []) {
74321
+ rows.push({ ...base, scope: "project", target: `${pm.org_slug}/${pm.project_slug}`, role: pm.role });
74164
74322
  }
74165
74323
  }
74166
74324
  }
@@ -74169,7 +74327,8 @@ async function handleUsers(flags, context2, json) {
74169
74327
  email: col("email", "Email"),
74170
74328
  name: col("name", "Name"),
74171
74329
  admin: col("admin", "Admin"),
74172
- org: col("org", "Org"),
74330
+ scope: col("scope", "Scope"),
74331
+ target: col("target", "Target"),
74173
74332
  role: col("role", "Role"),
74174
74333
  created: col("created", "Created")
74175
74334
  };
@@ -74178,7 +74337,8 @@ async function handleUsers(flags, context2, json) {
74178
74337
  pad3("Email", w.email),
74179
74338
  pad3("Name", w.name),
74180
74339
  pad3("Admin", w.admin),
74181
- pad3("Org", w.org),
74340
+ pad3("Scope", w.scope),
74341
+ pad3("Target", w.target),
74182
74342
  pad3("Role", w.role),
74183
74343
  pad3("Created", w.created)
74184
74344
  ].join(" ");
@@ -74189,7 +74349,8 @@ async function handleUsers(flags, context2, json) {
74189
74349
  pad3(row.email, w.email),
74190
74350
  pad3(row.name, w.name),
74191
74351
  pad3(row.admin, w.admin),
74192
- pad3(row.org, w.org),
74352
+ pad3(row.scope, w.scope),
74353
+ pad3(row.target, w.target),
74193
74354
  pad3(row.role, w.role),
74194
74355
  pad3(row.created, w.created)
74195
74356
  ].join(" "));
@@ -75627,8 +75788,24 @@ async function handleIntegrations(subcommand, positionals, flags, context2) {
75627
75788
  }
75628
75789
  case "slack": {
75629
75790
  const action = positionals[0];
75791
+ if (action === "install-url") {
75792
+ if (!orgId) {
75793
+ throw new Error("Missing org id. Provide --org or set a profile default.");
75794
+ }
75795
+ const url = `${context2.apiUrl}/orgs/${orgId}/integrations/slack/authorize`;
75796
+ if (json) {
75797
+ outputJson({ url, org_id: orgId }, json);
75798
+ } else {
75799
+ console.log(`Slack install URL:
75800
+
75801
+ ${url}
75802
+
75803
+ Share this URL with your Slack workspace admin.`);
75804
+ }
75805
+ return;
75806
+ }
75630
75807
  if (action !== "connect") {
75631
- throw new Error("Usage: eve integrations slack connect --org <org_id> --team-id <team_id> [--token <token>]");
75808
+ throw new Error("Usage: eve integrations slack <connect|install-url> --org <org_id>");
75632
75809
  }
75633
75810
  if (!orgId) {
75634
75811
  throw new Error("Missing org id. Provide --org or set a profile default.");
@@ -75681,8 +75858,35 @@ async function handleIntegrations(subcommand, positionals, flags, context2) {
75681
75858
  outputJson(response, json, response.ok ? "\u2713 Integration test ok" : "Integration test failed");
75682
75859
  return;
75683
75860
  }
75861
+ case "update": {
75862
+ const integrationId = positionals[0];
75863
+ if (!integrationId) {
75864
+ throw new Error("Usage: eve integrations update <integration_id> --setting key=value");
75865
+ }
75866
+ if (!orgId) {
75867
+ throw new Error("Missing org id. Provide --org or set a profile default.");
75868
+ }
75869
+ const settingFlag = getStringFlag(flags, ["setting"]);
75870
+ if (!settingFlag) {
75871
+ throw new Error("Missing --setting flag. Usage: --setting admin_channel_id=C12345");
75872
+ }
75873
+ const eqIdx = settingFlag.indexOf("=");
75874
+ if (eqIdx < 1) {
75875
+ throw new Error("Invalid --setting format. Use key=value (e.g., admin_channel_id=C12345)");
75876
+ }
75877
+ const key = settingFlag.slice(0, eqIdx);
75878
+ const value = settingFlag.slice(eqIdx + 1);
75879
+ const settings = { [key]: value };
75880
+ const response = await requestJson(
75881
+ context2,
75882
+ `/orgs/${orgId}/integrations/${integrationId}/settings`,
75883
+ { method: "PATCH", body: { settings } }
75884
+ );
75885
+ outputJson(response, json, `\u2713 Integration ${integrationId} settings updated`);
75886
+ return;
75887
+ }
75684
75888
  default:
75685
- throw new Error("Usage: eve integrations <list|slack|test>");
75889
+ throw new Error("Usage: eve integrations <list|slack|test|update>");
75686
75890
  }
75687
75891
  }
75688
75892
 
@@ -75691,26 +75895,66 @@ async function handleChat(subcommand, _positionals, flags, context2) {
75691
75895
  const json = Boolean(flags.json);
75692
75896
  switch (subcommand) {
75693
75897
  case "simulate": {
75694
- const projectId = getStringFlag(flags, ["project"]) ?? context2.projectId;
75695
- if (!projectId) {
75696
- throw new Error("Missing project id. Provide --project or set a profile default.");
75697
- }
75698
- const provider = getStringFlag(flags, ["provider"]) ?? "slack";
75898
+ const projectId = getStringFlag(flags, ["project"]);
75699
75899
  const teamId = getStringFlag(flags, ["team-id"]);
75700
75900
  const text = getStringFlag(flags, ["text"]);
75901
+ if (projectId) {
75902
+ if (!teamId || !text) {
75903
+ throw new Error("Usage: eve chat simulate --project <id> --team-id <team> --text <message>");
75904
+ }
75905
+ process.stderr.write("\u26A0 --project routes through the legacy API simulate path. Use --team-id without --project for gateway routing.\n");
75906
+ const provider2 = getStringFlag(flags, ["provider"]) ?? "slack";
75907
+ const channelId2 = getStringFlag(flags, ["channel-id"]);
75908
+ const userId2 = getStringFlag(flags, ["user-id"]);
75909
+ const threadKey = getStringFlag(flags, ["thread-key"]);
75910
+ const metadataFlag2 = getStringFlag(flags, ["metadata"]);
75911
+ let metadata;
75912
+ if (metadataFlag2) {
75913
+ try {
75914
+ const parsed = JSON.parse(metadataFlag2);
75915
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
75916
+ metadata = parsed;
75917
+ }
75918
+ } catch {
75919
+ throw new Error("Invalid --metadata (must be JSON object)");
75920
+ }
75921
+ }
75922
+ const response2 = await requestJson(
75923
+ context2,
75924
+ `/projects/${projectId}/chat/simulate`,
75925
+ {
75926
+ method: "POST",
75927
+ body: {
75928
+ provider: provider2,
75929
+ team_id: teamId,
75930
+ channel_id: channelId2,
75931
+ user_id: userId2,
75932
+ text,
75933
+ thread_key: threadKey,
75934
+ metadata
75935
+ }
75936
+ }
75937
+ );
75938
+ outputJson(response2, json, `\u2713 Chat simulated (thread: ${response2.thread_id})`);
75939
+ return;
75940
+ }
75701
75941
  if (!teamId || !text) {
75702
- throw new Error("Usage: eve chat simulate --project <id> --team-id <team> --text <message>");
75942
+ throw new Error("Usage: eve chat simulate --team-id <team> --text <message>");
75703
75943
  }
75944
+ const provider = getStringFlag(flags, ["provider"]) ?? "slack";
75704
75945
  const channelId = getStringFlag(flags, ["channel-id"]);
75705
75946
  const userId = getStringFlag(flags, ["user-id"]);
75706
- const threadKey = getStringFlag(flags, ["thread-key"]);
75947
+ const threadId = getStringFlag(flags, ["thread-id", "thread-key"]);
75948
+ const externalEmail = getStringFlag(flags, ["external-email"]);
75949
+ const dedupeKey = getStringFlag(flags, ["dedupe-key"]);
75950
+ const eventType = getStringFlag(flags, ["event-type"]);
75707
75951
  const metadataFlag = getStringFlag(flags, ["metadata"]);
75708
- let metadata;
75952
+ let metadataEmail;
75709
75953
  if (metadataFlag) {
75710
75954
  try {
75711
75955
  const parsed = JSON.parse(metadataFlag);
75712
75956
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
75713
- metadata = parsed;
75957
+ metadataEmail = typeof parsed.external_email === "string" ? parsed.external_email : void 0;
75714
75958
  }
75715
75959
  } catch {
75716
75960
  throw new Error("Invalid --metadata (must be JSON object)");
@@ -75718,21 +75962,33 @@ async function handleChat(subcommand, _positionals, flags, context2) {
75718
75962
  }
75719
75963
  const response = await requestJson(
75720
75964
  context2,
75721
- `/projects/${projectId}/chat/simulate`,
75965
+ "/gateway/providers/simulate",
75722
75966
  {
75723
75967
  method: "POST",
75724
75968
  body: {
75725
75969
  provider,
75726
- team_id: teamId,
75727
- channel_id: channelId,
75728
- user_id: userId,
75970
+ account_id: teamId,
75971
+ channel_id: channelId || void 0,
75972
+ user_id: userId || void 0,
75729
75973
  text,
75730
- thread_key: threadKey,
75731
- metadata
75974
+ external_email: externalEmail ?? metadataEmail,
75975
+ event_type: eventType || void 0,
75976
+ thread_id: threadId || void 0,
75977
+ dedupe_key: dedupeKey || void 0
75732
75978
  }
75733
75979
  }
75734
75980
  );
75735
- outputJson(response, json, `\u2713 Chat simulated (thread: ${response.thread_id})`);
75981
+ const normalized = {
75982
+ thread_id: response.route?.thread_id ?? null,
75983
+ route_id: response.route?.route_id ?? null,
75984
+ target: response.route?.target ?? null,
75985
+ job_ids: response.route?.job_ids ?? [],
75986
+ event_id: response.route?.event_id ?? null,
75987
+ immediate_reply: response.immediate_reply,
75988
+ duplicate: response.duplicate
75989
+ };
75990
+ const summary = response.duplicate ? "\u2713 Duplicate (deduplicated)" : response.route?.job_ids?.length ? `\u2713 Chat routed via gateway (thread: ${response.route.thread_id})` : response.immediate_reply ? `\u2713 Gateway reply: ${response.immediate_reply.text.slice(0, 80)}` : "\u2713 Chat simulated via gateway";
75991
+ outputJson(normalized, json, summary);
75736
75992
  return;
75737
75993
  }
75738
75994
  default:
@@ -81119,6 +81375,77 @@ function printManualInstructions(result) {
81119
81375
  console.log(' 6. Click "Add webhook"');
81120
81376
  }
81121
81377
 
81378
+ // src/commands/identity.ts
81379
+ async function handleIdentity(subcommand, positionals, flags, context2) {
81380
+ const json = Boolean(flags.json);
81381
+ switch (subcommand) {
81382
+ case "link": {
81383
+ const provider = positionals[0];
81384
+ if (!provider) {
81385
+ throw new Error("Usage: eve identity link <provider> --org <org_id>\n\nSupported providers: slack");
81386
+ }
81387
+ const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
81388
+ if (!orgId) {
81389
+ throw new Error("Missing org id. Provide --org or set a profile default.");
81390
+ }
81391
+ const response = await requestJson(
81392
+ context2,
81393
+ "/users/me/identity-link-tokens",
81394
+ {
81395
+ method: "POST",
81396
+ body: { provider, org_id: orgId }
81397
+ }
81398
+ );
81399
+ if (json) {
81400
+ outputJson(response, json);
81401
+ } else {
81402
+ console.log(response.instructions);
81403
+ }
81404
+ return;
81405
+ }
81406
+ default:
81407
+ throw new Error("Usage: eve identity <link>");
81408
+ }
81409
+ }
81410
+
81411
+ // src/commands/user.ts
81412
+ async function handleUser(subcommand, positionals, flags, context2) {
81413
+ const json = Boolean(flags.json);
81414
+ switch (subcommand) {
81415
+ case "show": {
81416
+ const userId = positionals[0] ?? "me";
81417
+ const user = await requestJson(context2, `/users/${userId}`);
81418
+ if (json) {
81419
+ outputJson(user, true);
81420
+ return;
81421
+ }
81422
+ console.log(`User: ${user.email}`);
81423
+ if (user.display_name) console.log(`Name: ${user.display_name}`);
81424
+ console.log(`ID: ${user.id}`);
81425
+ if (user.is_admin) console.log(`Role: system_admin`);
81426
+ console.log(`Since: ${user.created_at?.split("T")[0] ?? ""}`);
81427
+ if (user.memberships.length > 0) {
81428
+ console.log("\nOrg memberships:");
81429
+ for (const m of user.memberships) {
81430
+ console.log(` ${m.org_slug || m.org_name} ${m.role}`);
81431
+ }
81432
+ }
81433
+ if ((user.project_memberships ?? []).length > 0) {
81434
+ console.log("\nProject memberships:");
81435
+ for (const pm of user.project_memberships) {
81436
+ console.log(` ${pm.org_slug}/${pm.project_slug} ${pm.role}`);
81437
+ }
81438
+ }
81439
+ if (user.memberships.length === 0 && (user.project_memberships ?? []).length === 0) {
81440
+ console.log("\nNo memberships.");
81441
+ }
81442
+ return;
81443
+ }
81444
+ default:
81445
+ throw new Error("Usage: eve user <show> [user_id|me]");
81446
+ }
81447
+ }
81448
+
81122
81449
  // src/index.ts
81123
81450
  function getCliVersion() {
81124
81451
  try {
@@ -81314,6 +81641,12 @@ async function main2() {
81314
81641
  case "github":
81315
81642
  await handleGithub(subcommand, rest, flags, context2);
81316
81643
  return;
81644
+ case "identity":
81645
+ await handleIdentity(subcommand, rest, flags, context2);
81646
+ return;
81647
+ case "user":
81648
+ await handleUser(subcommand, rest, flags, context2);
81649
+ return;
81317
81650
  default:
81318
81651
  showMainHelp();
81319
81652
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eve-horizon/cli",
3
- "version": "0.2.42",
3
+ "version": "0.2.44",
4
4
  "description": "Eve Horizon CLI",
5
5
  "license": "MIT",
6
6
  "repository": {