@lazehq/cli 0.1.25 → 0.1.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.
Files changed (2) hide show
  1. package/dist/laze.mjs +294 -87
  2. package/package.json +1 -1
package/dist/laze.mjs CHANGED
@@ -30997,6 +30997,41 @@ function isBasicAuthPayload(value) {
30997
30997
  }
30998
30998
  var CREDENTIAL_QUALIFIERS = "master|encryption|signing|private|access|api|auth|secret|refresh|session|bearer|verification|webhook|client|service|personal|root|admin";
30999
30999
  var CREDENTIAL_KEYWORD = `(?:x[_.\\- ]?)?(?:(?:${CREDENTIAL_QUALIFIERS})[_.\\- ]?(?:api[_.\\- ]?)?key|(?:${CREDENTIAL_QUALIFIERS})?[_.\\- ]?(?:api[_.\\- ]?)?(?:token|secret|password|passwd|pwd|credentials?|authorization|cookie))`;
31000
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
31001
+ var GIT_SHA = /^[0-9a-f]{40}$/;
31002
+ var DOMAIN_PATH = /^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}\//;
31003
+ var NAMED_KEY_MIN = 24;
31004
+ var NAMED_KEY_MAX = 256;
31005
+ function isSlug(value) {
31006
+ const parts = value.split(/[-_.]/);
31007
+ if (parts.length < 3) return false;
31008
+ const short = parts.every((part) => /^(?:[a-z0-9]{1,10}|[A-Z0-9]{1,10})$/.test(part));
31009
+ return short && parts.some((part) => /^[A-Za-z]{3,}$/.test(part));
31010
+ }
31011
+ var NOT_A_NAMED_KEY = [
31012
+ /^\/|\/\//,
31013
+ GIT_SHA,
31014
+ DOMAIN_PATH,
31015
+ PATH_LIKE,
31016
+ CODE_EXPRESSION,
31017
+ ENV_REFERENCE,
31018
+ VERSION_LIKE
31019
+ ];
31020
+ function isNamedKeyValue(value) {
31021
+ if (value.length < NAMED_KEY_MIN || value.length > NAMED_KEY_MAX) return false;
31022
+ if (UUID.test(value)) return true;
31023
+ if (isSlug(value)) return false;
31024
+ if (NOT_A_NAMED_KEY.some((shape) => shape.test(value))) return false;
31025
+ const prefix = /^([A-Za-z][A-Za-z0-9]{1,11})[_-]/.exec(value)?.[1];
31026
+ if (prefix && isNonCredentialPrefix(prefix)) return false;
31027
+ if (HEX.test(value)) return true;
31028
+ if (shannonEntropyBits(value) < 3.5) return false;
31029
+ const c = charClasses(value);
31030
+ return c.digit >= 2 && c.lower + c.upper >= 8 && (c.lower >= 2 || c.upper >= 2);
31031
+ }
31032
+ var KEY_WORD = String.raw`(?:api[_.\- ]?key|apikey|access[_.\- ]?key|key|token|secret|password|passwd|pwd)`;
31033
+ var QUOTE = "[\"'`]";
31034
+ var KEY_WORD_GAP = String.raw`(?:[ \t]{0,3}(?:is|was|here|it's|its|below|:|=|->|=>|-{1,2}))`;
31000
31035
  var TOKEN_START = "(?<![A-Za-z0-9_-])";
31001
31036
  var TOKEN_END = "(?![A-Za-z0-9_-])";
31002
31037
  var VENDOR_KEY_PATTERNS = [
@@ -31125,11 +31160,21 @@ var RULES = [
31125
31160
  "i"
31126
31161
  ),
31127
31162
  value: { kind: "group", group: 2 },
31163
+ dropsFullStop: true,
31128
31164
  accept: (match) => {
31129
31165
  const introduction = groupOf(match, 1);
31130
31166
  const value = groupOf(match, 2);
31131
31167
  return STRICT_INTRODUCTION.test(introduction) ? isCredentialValue(value) : isKeyMaterial(value);
31132
31168
  }
31169
+ },
31170
+ {
31171
+ id: "named_key",
31172
+ regex: rx(
31173
+ String.raw`\b(${KEY_WORD})\b(${KEY_WORD_GAP}{0,3}[ \t]*(?:\r?\n[ \t]*)?${QUOTE}?)` + String.raw`([A-Za-z0-9+/=_.-]{${NAMED_KEY_MIN - 1},${NAMED_KEY_MAX - 1}}[A-Za-z0-9+/=_-])` + String.raw`(?![A-Za-z0-9+/=_-]|\.[A-Za-z0-9])`,
31174
+ "i"
31175
+ ),
31176
+ value: { kind: "group", group: 3 },
31177
+ accept: (match) => /[\s:="'`]/.test(groupOf(match, 2)) && isNamedKeyValue(groupOf(match, 3))
31133
31178
  }
31134
31179
  ];
31135
31180
  var SECRET_RULE_IDS = RULES.map((rule) => rule.id);
@@ -31157,12 +31202,21 @@ function spanStartOf({ rule, text: text2, fullStart }) {
31157
31202
  const before = rule.precededBy.exec(text2.slice(from, fullStart));
31158
31203
  return before ? fullStart - before[0].length : fullStart;
31159
31204
  }
31205
+ function withoutFullStop({
31206
+ text: text2,
31207
+ span
31208
+ }) {
31209
+ let end = span.end;
31210
+ while (end > span.start && text2[end - 1] === "." && !/\S/.test(text2[end] ?? "")) end--;
31211
+ return end === span.end ? span : { start: span.start, end, spanEnd: Math.min(span.spanEnd, end) };
31212
+ }
31160
31213
  function rawMatches(rule, text2) {
31161
31214
  const out = [];
31162
31215
  rule.regex.lastIndex = 0;
31163
31216
  for (const match of text2.matchAll(rule.regex)) {
31164
31217
  if (rule.accept && !rule.accept(match)) continue;
31165
- const span = valueSpan(rule, match);
31218
+ const found = valueSpan(rule, match);
31219
+ const span = found && rule.dropsFullStop ? withoutFullStop({ text: text2, span: found }) : found;
31166
31220
  if (!span || span.end <= span.start) continue;
31167
31221
  const spanStart = spanStartOf({ rule, text: text2, fullStart: match.index });
31168
31222
  out.push({ ruleId: rule.id, spanStart, ...span });
@@ -31322,7 +31376,33 @@ function suggestedName({
31322
31376
  const line = lineBreak === -1 ? before : before.slice(lineBreak + 1);
31323
31377
  const assigned = ASSIGNMENT.exec(line)?.[1]?.toUpperCase();
31324
31378
  if (assigned && isValidSecretName(assigned)) return assigned;
31325
- return defaultName({ kind, value, before });
31379
+ const lead = before.replace(/\s+$/, "");
31380
+ const lastLine = lead.slice(lead.search(/[^\r\n]*$/));
31381
+ const named = KEY_WORD_KINDS.has(kind) ? keyWordName({ line: lastLine }) : null;
31382
+ return named ?? defaultName({ kind, value, before });
31383
+ }
31384
+ var KEY_WORD_KINDS = /* @__PURE__ */ new Set(["named_key", "sensitive_assignment"]);
31385
+ var VENDOR_KEY_WORD = /([A-Za-z][A-Za-z0-9]{1,30})[ _.-]+(?:(?:api|access)[ _.-]?)?(key|token|secret|password|passwd|pwd)\b/gi;
31386
+ var NOT_VENDOR_WORDS = new Set(
31387
+ "a an the my your our their his her its this that these new old here is of for with and or to my api access secret private public personal prod production live test dev staging master admin root bearer auth client service session refresh read write full main default".split(
31388
+ " "
31389
+ )
31390
+ );
31391
+ var KEY_WORD_SUFFIXES = {
31392
+ key: "_API_KEY",
31393
+ token: "_TOKEN",
31394
+ secret: "_SECRET",
31395
+ password: "_PASSWORD",
31396
+ passwd: "_PASSWORD",
31397
+ pwd: "_PASSWORD"
31398
+ };
31399
+ function keyWordName({ line }) {
31400
+ const last = [...line.slice(-80).matchAll(VENDOR_KEY_WORD)].at(-1);
31401
+ const vendor = last?.[1]?.toLowerCase();
31402
+ const word3 = last?.[2]?.toLowerCase();
31403
+ if (!vendor || !word3 || NOT_VENDOR_WORDS.has(vendor)) return null;
31404
+ const name2 = `${vendor.toUpperCase()}${KEY_WORD_SUFFIXES[word3] ?? "_KEY"}`;
31405
+ return isValidSecretName(name2) ? name2 : null;
31326
31406
  }
31327
31407
  var SAFE_CUT_LOOKAHEAD = 4096;
31328
31408
  var PEM_BEGIN = "-----BEGIN";
@@ -35437,7 +35517,7 @@ function cliEnvironment({
35437
35517
 
35438
35518
  // src/services/config-store.service.ts
35439
35519
  import { chmod, mkdir, readFile, rename, writeFile } from "node:fs/promises";
35440
- import { dirname, resolve } from "node:path";
35520
+ import { basename, dirname, resolve } from "node:path";
35441
35521
  var EMPTY = { profiles: {}, bindings: {} };
35442
35522
  var ConfigStore = class _ConfigStore {
35443
35523
  constructor(path) {
@@ -35540,6 +35620,11 @@ function chooseProfile({
35540
35620
  const profile = name2 ? config2.profiles[name2] : void 0;
35541
35621
  return name2 && profile ? { name: name2, profile } : { name: void 0, profile: void 0 };
35542
35622
  }
35623
+ function isWorktreeRoot({ directory }) {
35624
+ const parent = dirname(directory);
35625
+ if (basename(parent) === ".worktrees") return true;
35626
+ return basename(parent) === "worktrees" && basename(dirname(parent)) === ".claude";
35627
+ }
35543
35628
  function boundProfileOf({
35544
35629
  bindings,
35545
35630
  cwd
@@ -35549,7 +35634,7 @@ function boundProfileOf({
35549
35634
  const bound = bindings[directory];
35550
35635
  if (bound) return bound;
35551
35636
  const parent = dirname(directory);
35552
- if (parent === directory) return void 0;
35637
+ if (parent === directory || isWorktreeRoot({ directory })) return void 0;
35553
35638
  directory = parent;
35554
35639
  }
35555
35640
  return void 0;
@@ -35620,7 +35705,7 @@ function flagText({
35620
35705
  }
35621
35706
 
35622
35707
  // src/rules/cli-version.rules.ts
35623
- var STAMPED = "0.1.25";
35708
+ var STAMPED = "0.1.27";
35624
35709
  function cliVersionOf({ stamped = STAMPED } = {}) {
35625
35710
  return stamped.startsWith("__") ? "dev" : stamped;
35626
35711
  }
@@ -57843,6 +57928,17 @@ var chatScrubReportSchemaDefinition = external_exports.object({
57843
57928
  channels: external_exports.array(chatScrubChannelSchemaDefinition)
57844
57929
  });
57845
57930
  var chatScrubReportSchema = chatScrubReportSchemaDefinition;
57931
+ var saveMessageSecretBodySchemaDefinition = external_exports.object({
57932
+ /** One token, as keys are: a value with spaces is text, not a key. */
57933
+ value: external_exports.string().min(8).max(4096).regex(/^\S+$/),
57934
+ name: external_exports.string().regex(/^[a-z0-9][a-z0-9-]{0,62}$/),
57935
+ description: external_exports.string().trim().min(1).max(120)
57936
+ });
57937
+ var messageSecretSavedSchemaDefinition = external_exports.object({
57938
+ outcome: external_exports.enum(["named", "already_named"]),
57939
+ name: external_exports.string(),
57940
+ connectionId: external_exports.string()
57941
+ });
57846
57942
 
57847
57943
  // ../../modules/chat/contract/src/chat.sent.ts
57848
57944
  var SENT_PAGE_MAX = 50;
@@ -62598,6 +62694,16 @@ var savePendingSecretBodySchemaDefinition = external_exports.object({
62598
62694
  name: connectionName2,
62599
62695
  description: external_exports.string().trim().min(1).max(120)
62600
62696
  });
62697
+ var saveMessageSecretInputSchemaDefinition = external_exports.object({
62698
+ workspaceId: external_exports.string().min(1),
62699
+ channelId: external_exports.string().min(1),
62700
+ messageId: external_exports.string().min(1),
62701
+ /** The message's author, a person: the keychain the value goes to. */
62702
+ ownerUserId: external_exports.string().min(1),
62703
+ value: external_exports.string().min(8).max(4096).regex(/^\S+$/),
62704
+ name: connectionName2,
62705
+ description: external_exports.string().trim().min(1).max(120)
62706
+ });
62601
62707
  var vaultPendingSecretSavedSchemaDefinition = external_exports.object({
62602
62708
  outcome: external_exports.enum(["named", "already_named"]),
62603
62709
  name: connectionName2,
@@ -65627,6 +65733,29 @@ function selfClauseOf({ self: self2 }) {
65627
65733
  return self2 ? `, where you are ${self2}` : "";
65628
65734
  }
65629
65735
 
65736
+ // src/rules/session-headers.rules.ts
65737
+ import { relative, sep } from "node:path";
65738
+ function sessionKeyOf({ session }) {
65739
+ return `${session.harness}:${session.sessionId}`;
65740
+ }
65741
+ function sessionHeadersOf({
65742
+ session
65743
+ }) {
65744
+ if (!session) return {};
65745
+ return {
65746
+ [AGENT_SESSION_HEADER]: sessionKeyOf({ session }),
65747
+ [AGENT_SESSION_FOLDER_HEADER]: encodeURIComponent(session.folder),
65748
+ [AGENT_SESSION_HOST_HEADER]: encodeURIComponent(session.host)
65749
+ };
65750
+ }
65751
+ function displayFolderOf({ folder, home }) {
65752
+ if (!home) return folder;
65753
+ const below = relative(home, folder);
65754
+ if (below === "") return "~";
65755
+ if (below.startsWith("..") || below.startsWith(sep)) return folder;
65756
+ return `~/${below.split(sep).join("/")}`;
65757
+ }
65758
+
65630
65759
  // src/rules/transcript.rules.ts
65631
65760
  function record2(value) {
65632
65761
  return typeof value === "object" && value !== null ? value : {};
@@ -66545,33 +66674,32 @@ import { mkdir as mkdir3, readdir, readFile as readFile3, writeFile as writeFile
66545
66674
  import { hostname as hostname3 } from "node:os";
66546
66675
  import { dirname as dirname3, join as join3 } from "node:path";
66547
66676
 
66548
- // src/rules/session-headers.rules.ts
66549
- import { relative, sep } from "node:path";
66550
- function sessionKeyOf({ session }) {
66551
- return `${session.harness}:${session.sessionId}`;
66552
- }
66553
- function sessionHeadersOf({
66554
- session
66677
+ // src/rules/rush-host.rules.ts
66678
+ function rushSessionOf({
66679
+ parentArgs
66555
66680
  }) {
66556
- if (!session) return {};
66557
- return {
66558
- [AGENT_SESSION_HEADER]: sessionKeyOf({ session }),
66559
- [AGENT_SESSION_FOLDER_HEADER]: encodeURIComponent(session.folder),
66560
- [AGENT_SESSION_HOST_HEADER]: encodeURIComponent(session.host)
66561
- };
66681
+ if (!parentArgs) return void 0;
66682
+ const [program = "", verb, action, id] = parentArgs;
66683
+ const isRush = program === "rush" || program.endsWith("/rush");
66684
+ if (!isRush || verb !== "host" || action !== "run" || !id) return void 0;
66685
+ return { id, program };
66562
66686
  }
66563
- function displayFolderOf({ folder, home }) {
66564
- if (!home) return folder;
66565
- const below = relative(home, folder);
66566
- if (below === "") return "~";
66567
- if (below.startsWith("..") || below.startsWith(sep)) return folder;
66568
- return `~/${below.split(sep).join("/")}`;
66687
+ function rushSendCommand({ program, id }) {
66688
+ return ["sh", "-c", 'printf "%s\\n" "$LAZE_WAKE_TEXT" | "$0" session send "$1"', program, id];
66689
+ }
66690
+ function rushListsLive({ listed, sessionId }) {
66691
+ try {
66692
+ const sessions = JSON.parse(listed);
66693
+ return sessions.some((one) => one.sessionId === sessionId && one.state !== "stopped");
66694
+ } catch {
66695
+ return false;
66696
+ }
66569
66697
  }
66570
66698
 
66571
66699
  // src/services/claude-project.service.ts
66572
- import { basename, isAbsolute, relative as relative2, resolve as resolve2 } from "node:path";
66700
+ import { basename as basename2, isAbsolute, relative as relative2, resolve as resolve2 } from "node:path";
66573
66701
  function harnessIn({ words }) {
66574
- const names = words.slice(0, 2).map((word3) => basename(word3).replace(/\.(js|mjs|cjs)$/, ""));
66702
+ const names = words.slice(0, 2).map((word3) => basename2(word3).replace(/\.(js|mjs|cjs)$/, ""));
66575
66703
  if (names.includes("codex")) return "codex";
66576
66704
  if (words.some((word3) => word3.includes("@openai/codex"))) return "codex";
66577
66705
  const claude = words.some((word3) => word3.includes("@anthropic-ai/claude-code"));
@@ -66744,7 +66872,8 @@ async function currentSessionOf({
66744
66872
  async function sessionProfileArgs({
66745
66873
  context,
66746
66874
  args,
66747
- session
66875
+ session,
66876
+ binds = true
66748
66877
  }) {
66749
66878
  if (!session) return args;
66750
66879
  const key = sessionKeyOf({ session });
@@ -66754,16 +66883,29 @@ async function sessionProfileArgs({
66754
66883
  return { ...args, flags: { ...args.flags, profile: bound } };
66755
66884
  }
66756
66885
  const own2 = flagText({ args, name: "profile" });
66757
- if (own2 && config2.profiles[own2]?.kind === "agent") {
66886
+ if (binds && !args.flags.plugin && own2 && config2.profiles[own2]?.kind === "agent") {
66758
66887
  await context.store.bindSession({ session: key, profile: own2 });
66759
66888
  }
66760
66889
  return args;
66761
66890
  }
66891
+ async function rushKeeps({ context, key }) {
66892
+ const sessionId = key.slice(key.indexOf(":") + 1);
66893
+ let listed = "";
66894
+ const code = await context.runCommand({
66895
+ command: ["sh", "-c", "command -v rush >/dev/null && rush session list --json"],
66896
+ env: {},
66897
+ stdout: { write: (chunk) => listed += chunk },
66898
+ stderr: { write: () => true }
66899
+ }).catch(() => 1);
66900
+ if (code !== 0) return false;
66901
+ return rushListsLive({ listed, sessionId });
66902
+ }
66762
66903
  async function liveElsewhere({
66763
66904
  context,
66764
66905
  key,
66765
66906
  claudePid
66766
66907
  }) {
66908
+ if (await rushKeeps({ context, key })) return true;
66767
66909
  const folder = dirname3(statePathOf({ context, kind: "sessions", profile: "x" }));
66768
66910
  for (const file3 of await readdir(folder).catch(() => [])) {
66769
66911
  const pid = Number(file3.replace(/\.json$/, ""));
@@ -66776,14 +66918,20 @@ async function liveElsewhere({
66776
66918
  }
66777
66919
  return false;
66778
66920
  }
66779
- function notBoundText({ meta: meta3 }) {
66780
- return `Laze: this agent listens in another session${meta3.bound ? `: ${meta3.bound}` : ""}. Create a new Laze agent with this session: laze agent request. Or move this agent here: laze agent bind.`;
66921
+ function notBoundText({
66922
+ meta: meta3,
66923
+ agent: agent2,
66924
+ configPath
66925
+ }) {
66926
+ const who = `${agent2 ? `"${agent2}"` : "this agent"} (config ${configPath})`;
66927
+ return `Laze: ${who} listens in another session${meta3.bound ? `: ${meta3.bound}` : ""}. Create a new Laze agent with this session: laze agent request. Or move this agent here: laze agent bind.`;
66781
66928
  }
66782
66929
  async function notBoundNotice({
66783
66930
  context,
66784
66931
  client,
66785
66932
  session,
66786
- error: error62
66933
+ error: error62,
66934
+ profile
66787
66935
  }) {
66788
66936
  if (!(error62 instanceof LazeApiError) || error62.code !== "session_not_bound") return void 0;
66789
66937
  const meta3 = error62.meta ?? {};
@@ -66803,7 +66951,10 @@ async function notBoundNotice({
66803
66951
  if (told) return { notice: void 0 };
66804
66952
  await mkdir3(dirname3(path), { recursive: true, mode: 448 });
66805
66953
  await writeFile3(path, String(context.nowMs()), { mode: 384 });
66806
- return { notice: notBoundText({ meta: meta3 }) };
66954
+ const agent2 = (await context.store.read()).profiles[profile]?.name;
66955
+ return {
66956
+ notice: notBoundText({ meta: meta3, agent: agent2, configPath: context.environment.configPath })
66957
+ };
66807
66958
  }
66808
66959
 
66809
66960
  // src/services/plugin-hook.service.ts
@@ -66821,7 +66972,7 @@ async function agentProfileHere({
66821
66972
  if (!folder && !session) return void 0;
66822
66973
  const asked = { ...args, flags: { ...args.flags, ...folder ? { profile: folder } : {} } };
66823
66974
  const name2 = flagText({
66824
- args: await sessionProfileArgs({ context, args: asked, session }),
66975
+ args: await sessionProfileArgs({ context, args: asked, session, binds: false }),
66825
66976
  name: "profile"
66826
66977
  });
66827
66978
  return name2 && config2.profiles[name2]?.kind === "agent" ? name2 : void 0;
@@ -67482,20 +67633,6 @@ function channelLabelIn(channel) {
67482
67633
  import { readFile as readFile7, rm as rm2, writeFile as writeFile6, mkdir as mkdir6 } from "node:fs/promises";
67483
67634
  import { dirname as dirname6 } from "node:path";
67484
67635
 
67485
- // src/rules/rush-host.rules.ts
67486
- function rushSessionOf({
67487
- parentArgs
67488
- }) {
67489
- if (!parentArgs) return void 0;
67490
- const [program = "", verb, action, id] = parentArgs;
67491
- const isRush = program === "rush" || program.endsWith("/rush");
67492
- if (!isRush || verb !== "host" || action !== "run" || !id) return void 0;
67493
- return { id, program };
67494
- }
67495
- function rushSendCommand({ program, id }) {
67496
- return ["sh", "-c", 'printf "%s\\n" "$LAZE_WAKE_TEXT" | "$0" session send "$1"', program, id];
67497
- }
67498
-
67499
67636
  // src/services/rush-host.service.ts
67500
67637
  import { mkdir as mkdir5, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
67501
67638
  import { dirname as dirname5, join as join6 } from "node:path";
@@ -67649,7 +67786,8 @@ async function settleRound(round) {
67649
67786
  context,
67650
67787
  client: forSession({ client, session }),
67651
67788
  session,
67652
- error: error62
67789
+ error: error62,
67790
+ profile: name2
67653
67791
  });
67654
67792
  if (!refused) throw error62;
67655
67793
  return refused;
@@ -69907,10 +70045,10 @@ async function nextStepsFor({
69907
70045
  }) {
69908
70046
  if (harness === "codex") return linkCodexSession({ context, args, profile, laze, directory });
69909
70047
  if (!hooked) return waiterStepsOf({ profile, laze });
70048
+ const session = await currentSessionOf({ context, args });
70049
+ if (session) await context.store.bindSession({ session: sessionKeyOf({ session }), profile });
69910
70050
  const plugin2 = args.flags["no-plugin"] ? void 0 : await installLazePlugin({ context });
69911
- if (plugin2?.registered) {
69912
- await markReloadTipTold({ context, session: await currentSessionOf({ context, args }) });
69913
- }
70051
+ if (plugin2?.registered) await markReloadTipTold({ context, session });
69914
70052
  const mcp2 = args.flags.mcp ? await registerMcp({ context, directory, profile }) : void 0;
69915
70053
  const mcpAdd2 = mcpAddCommandOf({ context, profile });
69916
70054
  const lines = pluginLinesOf({ context, registered: plugin2?.registered });
@@ -69962,7 +70100,13 @@ async function handOver({ context, args }) {
69962
70100
  try {
69963
70101
  fresh = await receiveMessages({ context, client: claim3, waitSeconds: 0 });
69964
70102
  } catch (error62) {
69965
- const refused = await notBoundNotice({ context, client: claim3, session, error: error62 });
70103
+ const refused = await notBoundNotice({
70104
+ context,
70105
+ client: claim3,
70106
+ session,
70107
+ error: error62,
70108
+ profile: agent2
70109
+ });
69966
70110
  if (!refused) throw error62;
69967
70111
  if (refused.notice) stopContext({ context, text: refused.notice });
69968
70112
  return;
@@ -70696,6 +70840,7 @@ async function setAgentPool({
70696
70840
 
70697
70841
  // src/services/agent-session-commands.service.ts
70698
70842
  var NO_SESSION = "Run this inside the Claude Code or Codex session the agent should listen in, or pass --session <id>.";
70843
+ var NO_AGENT = "This session and its folder have no agent: name one with `laze agent bind --profile <name>`.";
70699
70844
  var DEFAULT_WAIT_SECONDS = 300;
70700
70845
  async function bindAgentSession({
70701
70846
  context,
@@ -70703,10 +70848,21 @@ async function bindAgentSession({
70703
70848
  }) {
70704
70849
  const session = await currentSessionOf({ context, args });
70705
70850
  if (!session) throw new CliUsageError(NO_SESSION);
70706
- const { client, name: name2 } = await signedIn({ context, args });
70851
+ const key = sessionKeyOf({ session });
70852
+ const config2 = await context.store.read();
70853
+ const held = config2.sessions?.[key];
70854
+ const own2 = held && config2.profiles[held] ? held : void 0;
70855
+ const explicit = flagText({ args, name: "profile" }) || void 0;
70856
+ const target = explicit ?? own2 ?? boundProfileOf({ bindings: config2.bindings, cwd: context.cwd });
70857
+ if (!target) throw new CliUsageError(NO_AGENT);
70858
+ const named = { ...args, flags: { ...args.flags, profile: target } };
70859
+ const { client, name: name2, profile } = await signedIn({ context, args: named });
70707
70860
  await forSession({ client, session }).post("/api/agents/me/bind", {});
70708
- await context.store.bindSession({ session: sessionKeyOf({ session }), profile: name2 });
70709
- context.out("This agent now listens in this session.\n");
70861
+ await context.store.bindSession({ session: key, profile: name2 });
70862
+ const where = `profile ${name2}, config ${context.environment.configPath}`;
70863
+ const from = own2 && own2 !== name2 ? ` It moved from "${config2.profiles[own2]?.name}".` : "";
70864
+ context.out(`"${profile.name}" now listens in this session (${where}).${from}
70865
+ `);
70710
70866
  }
70711
70867
  async function mintedRequestProfile({
70712
70868
  context,
@@ -75813,7 +75969,7 @@ async function channelSettings({ context, args }) {
75813
75969
 
75814
75970
  // src/services/chat-actions-commands.service.ts
75815
75971
  import { readFile as readFile17 } from "node:fs/promises";
75816
- import { basename as basename2, isAbsolute as isAbsolute2, relative as relative4, resolve as resolve7 } from "node:path";
75972
+ import { basename as basename3, isAbsolute as isAbsolute2, relative as relative4, resolve as resolve7 } from "node:path";
75817
75973
 
75818
75974
  // src/services/file-upload.service.ts
75819
75975
  async function sendParts({
@@ -76113,7 +76269,7 @@ var files = async ({ context, args, client }) => {
76113
76269
  };
76114
76270
  function uploadNameOf({ cwd, path }) {
76115
76271
  const inside = relative4(cwd, path);
76116
- return inside && !inside.startsWith("..") && !isAbsolute2(inside) ? inside : basename2(path);
76272
+ return inside && !inside.startsWith("..") && !isAbsolute2(inside) ? inside : basename3(path);
76117
76273
  }
76118
76274
  var fileUpload = async ({ context, args, client }) => {
76119
76275
  const path = resolve7(context.cwd, wordAt(args, 3));
@@ -76320,7 +76476,13 @@ async function monitorRound(turn) {
76320
76476
  const named = { ...turn.args, flags: { ...turn.args.flags, profile: turn.profile } };
76321
76477
  const { client } = await signedIn({ context, args: named });
76322
76478
  const claim3 = forSession({ client, session });
76323
- const refused = await notBoundNotice({ context, client: claim3, session, error: error62 });
76479
+ const refused = await notBoundNotice({
76480
+ context,
76481
+ client: claim3,
76482
+ session,
76483
+ error: error62,
76484
+ profile: turn.profile
76485
+ });
76324
76486
  if (refused?.rebound) return true;
76325
76487
  if (refused?.notice) context.out(`${refused.notice}
76326
76488
  `);
@@ -79110,7 +79272,7 @@ function feedFlagsOf({ args }) {
79110
79272
 
79111
79273
  // src/services/feed-files.service.ts
79112
79274
  import { readFile as readFile23 } from "node:fs/promises";
79113
- import { basename as basename3, resolve as resolve9 } from "node:path";
79275
+ import { basename as basename4, resolve as resolve9 } from "node:path";
79114
79276
 
79115
79277
  // ../../modules/feed/contract/src/feed.ts
79116
79278
  var feedAuthorSchemaDefinition = external_exports.object({
@@ -79689,7 +79851,7 @@ async function bytesOf2({
79689
79851
  async function downloaded({ context, url: url2 }) {
79690
79852
  const response = await context.fetcher(url2);
79691
79853
  if (!response.ok) throw new CliUsageError(`${url2} answered ${response.status}`);
79692
- const fileName = basename3(new URL(url2).pathname) || "file";
79854
+ const fileName = basename4(new URL(url2).pathname) || "file";
79693
79855
  const header = (response.headers.get("content-type") ?? "").split(";")[0]?.trim();
79694
79856
  return {
79695
79857
  fileName,
@@ -79699,7 +79861,7 @@ async function downloaded({ context, url: url2 }) {
79699
79861
  }
79700
79862
  async function local({ context, path }) {
79701
79863
  const full = resolve9(context.cwd, path);
79702
- const fileName = basename3(full);
79864
+ const fileName = basename4(full);
79703
79865
  return {
79704
79866
  fileName,
79705
79867
  contentType: contentTypeOf(fileName),
@@ -81404,6 +81566,7 @@ var MCP_REFUSED_COMMANDS = /* @__PURE__ */ new Set([
81404
81566
  "vault add",
81405
81567
  "vault replace",
81406
81568
  "vault compare",
81569
+ "vault save-from-message",
81407
81570
  "connect mcp-key",
81408
81571
  "connect card-key"
81409
81572
  ]);
@@ -83475,15 +83638,20 @@ async function update2({
83475
83638
  return 0;
83476
83639
  }
83477
83640
 
83478
- // src/services/vault-save-commands.service.ts
83479
- var USAGE18 = `Usage: laze vault save <pending> --name <name> --description "<what it is for>"
83480
- Names a key Laze held from a message you received, in the sender's vault.`;
83641
+ // src/services/vault-save-message-commands.service.ts
83642
+ var SAVE_USAGE = `Usage: laze vault save <pending> --name <name> --description "<what it is for>"
83643
+ Names a key Laze held from a message you received, in the sender's vault.
83644
+ or: laze vault save --from-message <message-id> --name <name> --description "<what>" <<'EOF'
83645
+ Saves a key a person sent in that message, which Laze did not hold, to their own vault: the key
83646
+ on stdin, exactly as it is in the message. The message then shows {{vault:<name>}}. Run it in
83647
+ your turn in the message's conversation; it needs no vault access. MCP cannot send stdin.`;
83481
83648
  var KEPT_IN_LAZE = "The key never left Laze: the chat, its history and your session hold only a reference, so there is nothing to rotate. Tell the person that.";
83482
- async function vaultSave({ context, args }) {
83483
- const pending = args.words[2];
83484
- const name2 = flagText({ args, name: "name" });
83485
- const description = flagText({ args, name: "description" });
83486
- if (!pending || !name2 || !description) throw new CliUsageError(USAGE18);
83649
+ async function postInTurn({
83650
+ context,
83651
+ args,
83652
+ path,
83653
+ body
83654
+ }) {
83487
83655
  const env = context.environment.inherited;
83488
83656
  const turn = await turnTokenOf4({
83489
83657
  env,
@@ -83491,11 +83659,13 @@ async function vaultSave({ context, args }) {
83491
83659
  command: "laze vault save"
83492
83660
  });
83493
83661
  const { client } = await signedIn({ context, args });
83494
- const saved3 = await client.withHeaders({ "x-laze-turn": turn }).post("/api/vault/pending-secrets/save", {
83495
- pending,
83496
- name: name2,
83497
- description
83498
- });
83662
+ return client.withHeaders({ "x-laze-turn": turn }).post(path, body);
83663
+ }
83664
+ function printSaved({
83665
+ context,
83666
+ args,
83667
+ saved: saved3
83668
+ }) {
83499
83669
  if (args.flags.json) return context.out(`${JSON.stringify(saved3, null, 2)}
83500
83670
  `);
83501
83671
  const done = saved3.outcome === "named" ? "Saved as" : "Already named";
@@ -83505,6 +83675,40 @@ ${KEPT_IN_LAZE}
83505
83675
  `
83506
83676
  );
83507
83677
  }
83678
+ async function vaultSaveFromMessage({
83679
+ context,
83680
+ args
83681
+ }) {
83682
+ const messageId = flagText({ args, name: "from-message" });
83683
+ const name2 = flagText({ args, name: "name" });
83684
+ const description = flagText({ args, name: "description" });
83685
+ if (!messageId || !name2 || !description) throw new CliUsageError(SAVE_USAGE);
83686
+ const value = (await context.readStdin()).trim();
83687
+ if (!value) throw new CliUsageError(SAVE_USAGE);
83688
+ const saved3 = await postInTurn({
83689
+ context,
83690
+ args,
83691
+ path: `/api/chat/messages/${encodeURIComponent(messageId)}/save-secret`,
83692
+ body: { value, name: name2, description }
83693
+ });
83694
+ printSaved({ context, args, saved: saved3 });
83695
+ }
83696
+
83697
+ // src/services/vault-save-commands.service.ts
83698
+ async function vaultSave({ context, args }) {
83699
+ if (flagText({ args, name: "from-message" })) return vaultSaveFromMessage({ context, args });
83700
+ const pending = args.words[2];
83701
+ const name2 = flagText({ args, name: "name" });
83702
+ const description = flagText({ args, name: "description" });
83703
+ if (!pending || !name2 || !description) throw new CliUsageError(SAVE_USAGE);
83704
+ const saved3 = await postInTurn({
83705
+ context,
83706
+ args,
83707
+ path: "/api/vault/pending-secrets/save",
83708
+ body: { pending, name: name2, description }
83709
+ });
83710
+ printSaved({ context, args, saved: saved3 });
83711
+ }
83508
83712
 
83509
83713
  // src/services/vault-scrub-commands.service.ts
83510
83714
  function lineOf3(channel) {
@@ -84238,7 +84442,7 @@ var PATHS3 = {
84238
84442
  var STATUS_WAIT_SECONDS2 = 50;
84239
84443
  var STATUS_CALLS2 = 2;
84240
84444
  var SETTLED = /* @__PURE__ */ new Set(["sent", "denied", "expired", "failed"]);
84241
- var USAGE19 = [
84445
+ var USAGE18 = [
84242
84446
  "Usage: laze whatsapp chats <machine> [--limit <n>] the chats on the machine, newest first",
84243
84447
  " laze whatsapp read <machine> <chat> [--limit <n>] a chat's last messages, oldest first",
84244
84448
  ' laze whatsapp send <machine> <chat> "<text>" send as the owner; an agent waits for their approval',
@@ -84248,14 +84452,14 @@ var USAGE19 = [
84248
84452
  ].join("\n");
84249
84453
  function word2({ args, at }) {
84250
84454
  const value = args.words[at];
84251
- if (!value) throw new CliUsageError(USAGE19);
84455
+ if (!value) throw new CliUsageError(USAGE18);
84252
84456
  return value;
84253
84457
  }
84254
84458
  function limitOf5({ args }) {
84255
84459
  const text2 = flagText({ args, name: "limit" });
84256
84460
  if (!text2) return {};
84257
84461
  const limit = Number(text2);
84258
- if (!Number.isInteger(limit) || limit < 1) throw new CliUsageError(USAGE19);
84462
+ if (!Number.isInteger(limit) || limit < 1) throw new CliUsageError(USAGE18);
84259
84463
  return { limit };
84260
84464
  }
84261
84465
  async function chats({ context, args }) {
@@ -84317,7 +84521,7 @@ Still waiting. Run \`laze whatsapp status ${send4.id}\` later to see the answer.
84317
84521
  }
84318
84522
  async function send3({ context, args }) {
84319
84523
  const text2 = args.words.slice(4).join(" ") || args.rest.join(" ");
84320
- if (!text2) throw new CliUsageError(USAGE19);
84524
+ if (!text2) throw new CliUsageError(USAGE18);
84321
84525
  const client = await computeClient({ context, args });
84322
84526
  const asked = await client.post(PATHS3.send, {
84323
84527
  machine: word2({ args, at: 2 }),
@@ -84347,11 +84551,11 @@ async function whatsapp({
84347
84551
  if (command === "status") return status7({ context, args });
84348
84552
  const linking = whatsappPairingCommand(command);
84349
84553
  if (linking) return linking({ context, args });
84350
- throw new CliUsageError(USAGE19);
84554
+ throw new CliUsageError(USAGE18);
84351
84555
  }
84352
84556
 
84353
84557
  // src/services/wiki-commands.service.ts
84354
- var USAGE20 = `Usage:
84558
+ var USAGE19 = `Usage:
84355
84559
  laze wiki list
84356
84560
  laze wiki get <path>
84357
84561
  laze wiki put <path> <markdown|@file|-> [--title <title>] [--channel <channel>] [--base <version>]
@@ -84371,7 +84575,7 @@ async function wikiList({ context, args }) {
84371
84575
  }
84372
84576
  async function wikiGet({ context, args }) {
84373
84577
  const path = args.words[2];
84374
- if (!path) throw new CliUsageError(USAGE20);
84578
+ if (!path) throw new CliUsageError(USAGE19);
84375
84579
  const { client } = await signedIn({ context, args });
84376
84580
  const page = await client.post("/api/wiki/get", { path });
84377
84581
  if (args.flags.json) return context.out(`${JSON.stringify(page, null, 2)}
@@ -84388,7 +84592,7 @@ Linked from: ${page.backlinks.map((link) => link.path).join(", ")}
84388
84592
  async function wikiPut({ context, args }) {
84389
84593
  const path = args.words[2];
84390
84594
  const value = args.words.slice(3).join(" ");
84391
- if (!path || !value) throw new CliUsageError(USAGE20);
84595
+ if (!path || !value) throw new CliUsageError(USAGE19);
84392
84596
  const body = await textArgument({ context, value });
84393
84597
  const title = flagText({ args, name: "title" });
84394
84598
  const channel = flagText({ args, name: "channel" });
@@ -84408,7 +84612,7 @@ async function wikiPut({ context, args }) {
84408
84612
  }
84409
84613
  async function wikiSearch({ context, args }) {
84410
84614
  const query = args.words.slice(2).join(" ").trim();
84411
- if (!query) throw new CliUsageError(USAGE20);
84615
+ if (!query) throw new CliUsageError(USAGE19);
84412
84616
  const { client } = await signedIn({ context, args });
84413
84617
  const hits = await client.post("/api/wiki/search", { query });
84414
84618
  if (args.flags.json) return context.out(`${JSON.stringify(hits, null, 2)}
@@ -84418,7 +84622,7 @@ async function wikiSearch({ context, args }) {
84418
84622
  }
84419
84623
  async function wikiHistory({ context, args }) {
84420
84624
  const path = args.words[2];
84421
- if (!path) throw new CliUsageError(USAGE20);
84625
+ if (!path) throw new CliUsageError(USAGE19);
84422
84626
  const { client } = await signedIn({ context, args });
84423
84627
  const revisions = await client.post("/api/wiki/history", { path });
84424
84628
  if (args.flags.json) return context.out(`${JSON.stringify(revisions, null, 2)}
@@ -84432,7 +84636,7 @@ async function wikiHistory({ context, args }) {
84432
84636
  }
84433
84637
  async function wikiMove({ context, args }) {
84434
84638
  const [, , path, to] = args.words;
84435
- if (!path || !to) throw new CliUsageError(USAGE20);
84639
+ if (!path || !to) throw new CliUsageError(USAGE19);
84436
84640
  const { client } = await signedIn({ context, args });
84437
84641
  const page = await client.post("/api/wiki/move", { path, to });
84438
84642
  if (args.flags.json) return context.out(`${JSON.stringify(page, null, 2)}
@@ -84442,7 +84646,7 @@ async function wikiMove({ context, args }) {
84442
84646
  }
84443
84647
  async function wikiRemove({ context, args }) {
84444
84648
  const path = args.words[2];
84445
- if (!path) throw new CliUsageError(USAGE20);
84649
+ if (!path) throw new CliUsageError(USAGE19);
84446
84650
  const { client } = await signedIn({ context, args });
84447
84651
  const removed = await client.post("/api/wiki/remove", { path });
84448
84652
  if (args.flags.json) return context.out(`${JSON.stringify(removed)}
@@ -84462,7 +84666,7 @@ var WIKI_COMMANDS = {
84462
84666
  async function wiki({ context, args }) {
84463
84667
  const name2 = args.words[1] ?? "";
84464
84668
  const command = Object.hasOwn(WIKI_COMMANDS, name2) ? WIKI_COMMANDS[name2] : void 0;
84465
- if (!command) throw new CliUsageError(USAGE20);
84669
+ if (!command) throw new CliUsageError(USAGE19);
84466
84670
  await command({ context, args });
84467
84671
  }
84468
84672
 
@@ -85080,6 +85284,8 @@ var HELP = `laze: work in a Laze workspace from your terminal or your agent.
85080
85284
  laze adopt codex --status | --stop the background adopt that laze agent link started
85081
85285
  laze vault run <name> --reason "<why>" -- <cmd> run a command with a vault connection (agents)
85082
85286
  laze vault save <pending> --name <name> --description "<text>" name a key Laze held from a message (agents)
85287
+ laze vault save --from-message <id> --name <name> --description "<text>" save a key a person sent,
85288
+ read from stdin, to their own vault; the message keeps {{vault:<name>}} (agents, no vault access)
85083
85289
  laze vault scrub [--dry-run] move secrets found in messages to the vault now (admins)
85084
85290
  laze vault list|add|update|replace|compare|rm|revoke|uses your keychains with your own key
85085
85291
  a value comes from stdin or --from-file, never a flag; no command prints a value
@@ -85235,6 +85441,7 @@ var COMMANDS3 = {
85235
85441
  app,
85236
85442
  "vault run": vaultRun,
85237
85443
  "vault save": vaultSave,
85444
+ "vault save-from-message": vaultSaveFromMessage,
85238
85445
  connect,
85239
85446
  slack,
85240
85447
  "connect providers": connectAsPerson,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazehq/cli",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "The Laze command line: sign in, read and post in chat, and connect your own Claude Code or Codex session as an agent.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",