@c4a/context-cli 0.7.29 → 0.7.30

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.
package/README.md CHANGED
@@ -283,6 +283,18 @@ MIT.
283
283
 
284
284
  ## Updating an existing workspace
285
285
 
286
+ ### Lark read identity
287
+
288
+ `CONTEXT_LARK_IDENTITY=user|bot` selects the identity for Lark acquisition
289
+ (default: `user`). Export it in each shell executing Context commands. In Bot
290
+ mode, a credential/scope/access failure retries once as the current user; the
291
+ rest of that capture keeps the fallback identity for pagination and resources.
292
+ Network, rate-limit and parsing errors do not trigger a user authorization
293
+ attempt. This does not change source-read consent or the audience of published
294
+ knowledge. Host-managed OAuth must use the host's authorization flow.
295
+
296
+ ### Workspace updates
297
+
286
298
  Start with `context entry --format json` and read the returned update guide when
287
299
  adding material or changing published knowledge. Entry and status only observe;
288
300
  they do not resume an earlier managed task before the new request is understood.
package/README.zh-CN.md CHANGED
@@ -47,6 +47,14 @@ Provider Skill 投影到 Claude、Codex 和 Cursor 的用户技能目录。
47
47
  方法并停止。它不会为每次调用增加安装预检,也不会把普通工作流中的 `not found`
48
48
  诊断误判成可执行文件缺失。
49
49
 
50
+ ## 飞书读取身份
51
+
52
+ `CONTEXT_LARK_IDENTITY=user|bot` 控制飞书来源采集,默认 `user`。每次执行 Context
53
+ 命令的 shell 都需传入该环境变量;仅在提示词中写配置并不会自动导出环境变量。
54
+ `bot` 模式先使用应用身份,遇到凭证、scope 或访问权限错误时回退当前用户一次,
55
+ 本次采集后续分页、引用与资源下载沿用回退身份。网络、限流和解析错误不切换身份。
56
+ 该配置不扩大来源授权或知识产物可见范围;托管运行时的用户授权遵循宿主流程。
57
+
50
58
  ## 一个 Agent 入口如何驱动整套工作流
51
59
 
52
60
  ```text
package/cli.js CHANGED
@@ -98654,6 +98654,72 @@ var init_larkMarkdownImages = __esm(() => {
98654
98654
  init_markdownLinks();
98655
98655
  });
98656
98656
 
98657
+ // src/lib/larkReadIdentity.ts
98658
+ function resolveLarkReadIdentity() {
98659
+ const value = process.env.CONTEXT_LARK_IDENTITY?.trim() || "user";
98660
+ if (value === "user" || value === "bot")
98661
+ return value;
98662
+ throw new ContextError(ExitCode.UserError, "CONTEXT_LARK_IDENTITY must be user or bot", {
98663
+ reason_code: "lark.invalid-read-identity",
98664
+ valid_values: ["user", "bot"],
98665
+ next: "Set CONTEXT_LARK_IDENTITY to user or bot and rerun the same command"
98666
+ });
98667
+ }
98668
+ function credentialFailure(result) {
98669
+ for (const output of [result.stdout, result.stderr]) {
98670
+ const start2 = output.indexOf("{");
98671
+ if (start2 < 0)
98672
+ continue;
98673
+ try {
98674
+ const parsed = JSON.parse(output.slice(start2));
98675
+ if (!parsed || typeof parsed !== "object" || !("error" in parsed))
98676
+ continue;
98677
+ if (result.exitCode === 0 && (!("ok" in parsed) || parsed.ok !== false))
98678
+ continue;
98679
+ const error = parsed.error;
98680
+ if (!error || typeof error !== "object")
98681
+ continue;
98682
+ const type = "type" in error ? error.type : undefined;
98683
+ const subtype = "subtype" in error ? error.subtype : undefined;
98684
+ if (type === "authorization" || type === "authentication" || ["missing_scope", "permission_denied", "access_denied", "need_user_authorization"].includes(String(subtype))) {
98685
+ return true;
98686
+ }
98687
+ } catch {}
98688
+ }
98689
+ return result.exitCode !== 0 && /reason=(?:bot_unavailable|not_authorized)\b|\b(?:missing_scope|permission_denied|access_denied)\b/iu.test(result.stderr);
98690
+ }
98691
+ function createLarkReadSession(runner2, initial, fallback = initial === "bot") {
98692
+ let identity2 = initial;
98693
+ let usedFallback = false;
98694
+ const run = async (args, options) => {
98695
+ const explicit = args.indexOf("--as");
98696
+ const requested = explicit >= 0 ? args[explicit + 1] : identity2;
98697
+ const selected = usedFallback ? "user" : requested === "bot" ? "bot" : "user";
98698
+ const withIdentity = [...args];
98699
+ if (explicit >= 0)
98700
+ withIdentity[explicit + 1] = selected;
98701
+ else
98702
+ withIdentity.push("--as", selected);
98703
+ const result = await runner2(withIdentity, options);
98704
+ identity2 = selected;
98705
+ if (identity2 !== "bot" || !fallback || usedFallback || args.includes("--help") || !credentialFailure(result))
98706
+ return result;
98707
+ usedFallback = true;
98708
+ identity2 = "user";
98709
+ withIdentity[withIdentity.indexOf("--as") + 1] = "user";
98710
+ return runner2(withIdentity, options);
98711
+ };
98712
+ return { run, get identity() {
98713
+ return identity2;
98714
+ }, get usedFallback() {
98715
+ return usedFallback;
98716
+ } };
98717
+ }
98718
+ var init_larkReadIdentity = __esm(() => {
98719
+ init_errors3();
98720
+ init_exitCode();
98721
+ });
98722
+
98657
98723
  // src/lib/feishu.ts
98658
98724
  import { spawn as spawn4 } from "node:child_process";
98659
98725
  async function detectDocsFetchCapabilities(runner2) {
@@ -98670,14 +98736,19 @@ ${result.stderr}`;
98670
98736
  supportsDocFormat: result.exitCode === 0 && /\s--doc-format\b/u.test(help)
98671
98737
  };
98672
98738
  }
98673
- function resolveDocsFetchPlan(requested, runner2) {
98739
+ function resolveDocsFetchPlan(requested, runner2, cacheKey = runner2, identity2 = "user") {
98674
98740
  if (requested === "v1" || requested === "v2") {
98675
98741
  return Promise.resolve({ apiVersion: requested, docFormat: "xml" });
98676
98742
  }
98677
- let capabilitiesPromise = docsFetchCapabilitiesCache.get(runner2);
98743
+ let cached = docsFetchCapabilitiesCache.get(cacheKey);
98744
+ if (!cached) {
98745
+ cached = new Map;
98746
+ docsFetchCapabilitiesCache.set(cacheKey, cached);
98747
+ }
98748
+ let capabilitiesPromise = cached.get(identity2);
98678
98749
  if (capabilitiesPromise === undefined) {
98679
98750
  capabilitiesPromise = detectDocsFetchCapabilities(runner2);
98680
- docsFetchCapabilitiesCache.set(runner2, capabilitiesPromise);
98751
+ cached.set(identity2, capabilitiesPromise);
98681
98752
  }
98682
98753
  return capabilitiesPromise.then((capabilities) => {
98683
98754
  if (!capabilities.supportsDocFormat) {
@@ -98974,20 +99045,25 @@ function hasEmptySubPageList(projection) {
98974
99045
  return projection.fidelity.issues.some((issue) => issue.code === LARK_EMPTY_SUB_PAGE_LIST_CODE);
98975
99046
  }
98976
99047
  async function fetchFeishuDocSnapshot(input, runner2 = defaultRunner) {
98977
- const docsFetchPlan = input.prefetched ? { apiVersion: "v2", docFormat: "xml" } : await resolveDocsFetchPlan(input.docsApiVersion ?? "auto", runner2);
99048
+ const preference = input.prefetched?.identity ?? input.identity ?? resolveLarkReadIdentity();
99049
+ const cacheKey = runner2;
99050
+ const session = createLarkReadSession(runner2, input.prefetched?.identity ?? (preference === "bot" ? "bot" : "user"), !input.prefetched && preference === "bot");
99051
+ runner2 = session.run;
99052
+ const docsFetchPlan = input.prefetched ? { apiVersion: "v2", docFormat: "xml" } : await resolveDocsFetchPlan(input.docsApiVersion ?? "auto", runner2, cacheKey, preference);
98978
99053
  const identityFetch = input.prefetched ? {
98979
99054
  fetched: await fetchDocsResponse(input, docsFetchPlan, runner2, input.prefetched.identity),
98980
99055
  identity: input.prefetched.identity,
98981
99056
  fallback: false
98982
- } : await fetchWithIdentity(input, docsFetchPlan, runner2);
99057
+ } : await fetchWithIdentity({ ...input, identity: preference }, docsFetchPlan, runner2);
98983
99058
  let fetched = identityFetch.fetched;
98984
- const accessIdentity = identityFetch.identity;
99059
+ let accessIdentity = input.prefetched?.identity ?? session.identity;
98985
99060
  let projection;
98986
99061
  for (let attempt = 0;attempt < MAX_STRUCTURAL_FETCH_ATTEMPTS && fetched.contentFormat === "xml"; attempt++) {
98987
99062
  projection = projectLarkDocxXml({ xml: fetched.body, sourceUrl: input.url });
98988
99063
  if (input.prefetched || !hasEmptySubPageList(projection) || attempt === MAX_STRUCTURAL_FETCH_ATTEMPTS - 1)
98989
99064
  break;
98990
99065
  fetched = await fetchDocsResponse(input, docsFetchPlan, runner2, accessIdentity);
99066
+ accessIdentity = session.identity;
98991
99067
  }
98992
99068
  let body2 = fetched.body;
98993
99069
  let title2 = fetched.title;
@@ -99073,7 +99149,7 @@ ${body2}`,
99073
99149
  fidelity,
99074
99150
  resourceMaterialization,
99075
99151
  accessIdentity,
99076
- identityFallback: identityFetch.fallback
99152
+ identityFallback: identityFetch.fallback || session.usedFallback
99077
99153
  };
99078
99154
  }
99079
99155
  return {
@@ -99084,7 +99160,7 @@ ${body2}`,
99084
99160
  fidelity,
99085
99161
  resourceMaterialization,
99086
99162
  accessIdentity,
99087
- identityFallback: identityFetch.fallback
99163
+ identityFallback: identityFetch.fallback || session.usedFallback
99088
99164
  };
99089
99165
  }
99090
99166
  var LARK_BIN = "lark-cli", MAX_FETCH_PAGES = 50, MAX_STRUCTURAL_FETCH_ATTEMPTS = 2, LarkCliNotInstalledError, LarkCliError, defaultRunner = (args, options) => new Promise((resolve8, reject) => {
@@ -99116,6 +99192,7 @@ var init_feishu = __esm(() => {
99116
99192
  init_larkResourceMaterialization();
99117
99193
  init_larkCaptureReport();
99118
99194
  init_larkMarkdownImages();
99195
+ init_larkReadIdentity();
99119
99196
  LarkCliNotInstalledError = class LarkCliNotInstalledError extends Error {
99120
99197
  constructor() {
99121
99198
  super(`${LARK_BIN} not installed — see https://github.com/larksuite/cli for install instructions`);
@@ -99276,7 +99353,7 @@ function larkErrorRecovery(error, sourceName) {
99276
99353
  if (/auth|login|permission|forbidden|unauthori[sz]ed|scope/iu.test(message)) {
99277
99354
  return {
99278
99355
  reasonCode: "external.authorization-required",
99279
- next: "Run lark-cli auth login with an account that can read the document, then rerun capture"
99356
+ next: "Check the selected Lark identity and document access. For host-managed credentials use the host authorization flow; use lark-cli auth login only for a locally managed user login, then rerun capture"
99280
99357
  };
99281
99358
  }
99282
99359
  return {
@@ -99421,6 +99498,8 @@ async function runCaptureLarkPhaseUnlocked(input) {
99421
99498
  ...input.prefetched === undefined ? {} : { prefetched: input.prefetched }
99422
99499
  }, input.larkRunner);
99423
99500
  } catch (error) {
99501
+ if (error instanceof ContextError)
99502
+ throw error;
99424
99503
  const failure2 = normalizeLarkError(error, resolved.sourceName);
99425
99504
  if (failure2.detail?.reason_code === "external.authorization-required" || failure2.detail?.reason_code === "external.tool-failed" && error instanceof LarkCliError && error.exitCode !== 0) {
99426
99505
  throw new ContextError(failure2.code, failure2.message, { ...failure2.detail, reason_code: DOCUMENT_ACQUISITION_UNAVAILABLE, acquisition_cause: failure2.detail?.reason_code });
@@ -99594,7 +99673,7 @@ async function runCaptureLarkPhaseUnlocked(input) {
99594
99673
  resource_materialization: fetched.resourceMaterialization,
99595
99674
  diagnostics: [
99596
99675
  ...recovery === undefined ? [] : [recovery.diagnostic],
99597
- ...fetched.identityFallback ? [`info: lark.capture.identity-fallback: user credentials unavailable; captured with ${fetched.accessIdentity} identity`] : [],
99676
+ ...fetched.identityFallback ? [`info: lark.capture.identity-fallback: capture used an identity fallback; document identity: ${fetched.accessIdentity}; embedded reads may use the fallback identity`] : [],
99598
99677
  ...fetched.fidelity.issues.map((issue) => `${issue.severity}: ${issue.code}: ${issue.block_type} × ${issue.count}: ${issue.reason}`),
99599
99678
  ...fetched.resourceMaterialization.items.filter((item) => item.status === "failed" || item.kind === "poll" && item.reason?.includes("absent") === true).map((item) => `${item.status}: lark.capture.resource-${item.status}: ${item.kind}: ${item.reason ?? item.locator}`)
99600
99679
  ],