@lobehub/cli 0.0.31 → 0.0.33

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/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { createRequire } from "node:module";
3
3
  import { EventEmitter, once } from "node:events";
4
4
  import { execFile, execFileSync, spawn } from "node:child_process";
5
5
  import path, { basename, extname } from "node:path";
6
- import fs, { constants, createReadStream, createWriteStream, existsSync, readFileSync, writeFileSync } from "node:fs";
6
+ import fs, { constants, createWriteStream, existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
7
7
  import crypto$1, { createHash, randomUUID } from "node:crypto";
8
8
  import os, { platform, tmpdir } from "node:os";
9
9
  import { createInterface } from "node:readline";
@@ -5679,13 +5679,48 @@ function isProcessAlive(pid) {
5679
5679
  }
5680
5680
  }
5681
5681
  /**
5682
+ * Verify a live PID actually belongs to a LobeHub connect daemon.
5683
+ *
5684
+ * A bare `isProcessAlive` check is not enough: if a daemon dies without cleaning
5685
+ * up `daemon.pid` (crash, `kill -9`, reboot), the OS can later reuse that PID
5686
+ * for an unrelated process. Acting on the stale PID would let `lh logout` /
5687
+ * `connect stop` SIGTERM a stranger. The daemon is always spawned as
5688
+ * `<node> … connect … --daemon-child`, so we confirm that signature in the
5689
+ * process command line before trusting the PID.
5690
+ *
5691
+ * Best-effort and deliberately conservative: if the command line can't be read
5692
+ * (e.g. `ps` is unavailable), we return `false` so callers never kill a process
5693
+ * we can't positively identify.
5694
+ */
5695
+ function isDaemonProcess(pid) {
5696
+ try {
5697
+ const command = execFileSync("ps", [
5698
+ "-ww",
5699
+ "-p",
5700
+ String(pid),
5701
+ "-o",
5702
+ "command="
5703
+ ], {
5704
+ encoding: "utf8",
5705
+ stdio: [
5706
+ "ignore",
5707
+ "pipe",
5708
+ "ignore"
5709
+ ]
5710
+ }).trim();
5711
+ return command.includes("--daemon-child") && command.includes("connect");
5712
+ } catch {
5713
+ return false;
5714
+ }
5715
+ }
5716
+ /**
5682
5717
  * Get the PID of a running daemon, cleaning up stale PID files.
5683
5718
  * Returns null if no daemon is running.
5684
5719
  */
5685
5720
  function getRunningDaemonPid() {
5686
5721
  const pid = readPid();
5687
5722
  if (pid === null) return null;
5688
- if (isProcessAlive(pid)) return pid;
5723
+ if (isProcessAlive(pid) && isDaemonProcess(pid)) return pid;
5689
5724
  removePid();
5690
5725
  removeStatus();
5691
5726
  return null;
@@ -7614,8 +7649,7 @@ const HotkeyEnum = {
7614
7649
  ShowApp: "showApp",
7615
7650
  SwitchAgent: "switchAgent",
7616
7651
  ToggleLeftPanel: "toggleLeftPanel",
7617
- ToggleRightPanel: "toggleRightPanel",
7618
- ToggleZenMode: "toggleZenMode"
7652
+ ToggleRightPanel: "toggleRightPanel"
7619
7653
  };
7620
7654
  const HotkeyGroupEnum = {
7621
7655
  Conversation: "conversation",
@@ -7675,12 +7709,6 @@ const HOTKEYS_REGISTRATION = [
7675
7709
  ]),
7676
7710
  scopes: [HotkeyScopeEnum.Global]
7677
7711
  },
7678
- {
7679
- group: HotkeyGroupEnum.Essential,
7680
- id: HotkeyEnum.ToggleZenMode,
7681
- keys: combineKeys([KeyEnum.Mod, KeyEnum.Backslash]),
7682
- scopes: [HotkeyScopeEnum.Chat]
7683
- },
7684
7712
  {
7685
7713
  group: HotkeyGroupEnum.Conversation,
7686
7714
  id: HotkeyEnum.OpenChatSettings,
@@ -8674,669 +8702,6 @@ const DEFAULT_INBOX_SESSION = merge(DEFAULT_AGENT_LOBE_SESSION, {
8674
8702
  meta: { avatar: DEFAULT_INBOX_AVATAR }
8675
8703
  });
8676
8704
 
8677
- //#endregion
8678
- //#region ../../packages/const/src/taskTemplate.ts
8679
- const taskTemplates = [
8680
- {
8681
- id: "daily-topic-pick",
8682
- category: "content-creation",
8683
- cronPattern: "0 9 * * *",
8684
- interests: ["writing"]
8685
- },
8686
- {
8687
- id: "hot-topic-radar",
8688
- category: "content-creation",
8689
- cronPattern: "0 10 * * *",
8690
- interests: ["writing"]
8691
- },
8692
- {
8693
- id: "headline-inspiration",
8694
- category: "content-creation",
8695
- cronPattern: "0 10 * * *",
8696
- interests: ["writing"]
8697
- },
8698
- {
8699
- id: "viral-content-breakdown",
8700
- category: "content-creation",
8701
- cronPattern: "0 10 * * *",
8702
- interests: ["writing"]
8703
- },
8704
- {
8705
- id: "twitter-weekly-recap",
8706
- category: "content-creation",
8707
- cronPattern: "0 10 * * 1",
8708
- interests: ["writing", "creator"],
8709
- requiresSkills: [{
8710
- provider: "twitter",
8711
- source: "lobehub"
8712
- }]
8713
- },
8714
- {
8715
- id: "youtube-weekly-recap",
8716
- category: "content-creation",
8717
- cronPattern: "0 9 * * 1",
8718
- interests: ["writing", "creator"],
8719
- requiresSkills: [{
8720
- provider: "youtube",
8721
- source: "composio"
8722
- }]
8723
- },
8724
- {
8725
- id: "competitor-creator-tracking",
8726
- category: "content-creation",
8727
- cronPattern: "0 9 * * *",
8728
- interests: ["writing", "creator"]
8729
- },
8730
- {
8731
- id: "content-calendar-weekly",
8732
- category: "content-creation",
8733
- cronPattern: "0 20 * * 0",
8734
- interests: ["writing", "creator"],
8735
- optionalSkills: [{
8736
- provider: "notion",
8737
- source: "lobehub"
8738
- }]
8739
- },
8740
- {
8741
- id: "oss-intel-daily",
8742
- category: "engineering",
8743
- cronPattern: "0 9 * * *",
8744
- icon: "github",
8745
- interests: ["coding"]
8746
- },
8747
- {
8748
- id: "repo-health-weekly",
8749
- category: "engineering",
8750
- cronPattern: "0 9 * * 1",
8751
- interests: ["coding"],
8752
- requiresSkills: [{
8753
- provider: "github",
8754
- source: "lobehub"
8755
- }]
8756
- },
8757
- {
8758
- id: "dependency-security-weekly",
8759
- category: "engineering",
8760
- cronPattern: "0 10 * * 1",
8761
- interests: ["coding"],
8762
- requiresSkills: [{
8763
- provider: "github",
8764
- source: "lobehub"
8765
- }]
8766
- },
8767
- {
8768
- id: "vercel-health-weekly",
8769
- category: "engineering",
8770
- cronPattern: "0 10 * * 1",
8771
- interests: ["coding"],
8772
- requiresSkills: [{
8773
- provider: "vercel",
8774
- source: "lobehub"
8775
- }]
8776
- },
8777
- {
8778
- id: "linear-sprint-daily",
8779
- category: "engineering",
8780
- cronPattern: "30 8 * * *",
8781
- interests: ["coding", "product"],
8782
- requiresSkills: [{
8783
- provider: "linear",
8784
- source: "lobehub"
8785
- }]
8786
- },
8787
- {
8788
- id: "tech-trend-weekly",
8789
- category: "engineering",
8790
- cronPattern: "0 8 * * 1",
8791
- interests: ["coding"]
8792
- },
8793
- {
8794
- id: "keyword-tech-feed",
8795
- category: "engineering",
8796
- cronPattern: "0 10 * * *",
8797
- interests: ["coding"]
8798
- },
8799
- {
8800
- id: "daily-design-inspiration",
8801
- category: "design",
8802
- cronPattern: "0 9 * * *",
8803
- interests: ["design"]
8804
- },
8805
- {
8806
- id: "design-trend-weekly",
8807
- category: "design",
8808
- cronPattern: "0 9 * * 1",
8809
- interests: ["design"]
8810
- },
8811
- {
8812
- id: "figma-files-cleanup",
8813
- category: "design",
8814
- cronPattern: "0 17 * * 5",
8815
- interests: ["design"],
8816
- requiresSkills: [{
8817
- provider: "figma",
8818
- source: "composio"
8819
- }]
8820
- },
8821
- {
8822
- id: "aigc-prompt-inspiration",
8823
- category: "design",
8824
- cronPattern: "0 10 * * *",
8825
- interests: ["design"]
8826
- },
8827
- {
8828
- id: "brand-watch-weekly",
8829
- category: "design",
8830
- cronPattern: "0 10 * * 1",
8831
- interests: ["design"]
8832
- },
8833
- {
8834
- id: "font-color-weekly",
8835
- category: "design",
8836
- cronPattern: "0 10 * * 3",
8837
- interests: ["design"]
8838
- },
8839
- {
8840
- id: "arxiv-curated-daily",
8841
- category: "learning-research",
8842
- cronPattern: "0 9 * * *",
8843
- interests: ["education"]
8844
- },
8845
- {
8846
- id: "must-read-papers-weekly",
8847
- category: "learning-research",
8848
- cronPattern: "0 20 * * 0",
8849
- interests: ["education"]
8850
- },
8851
- {
8852
- id: "language-morning-bite",
8853
- category: "learning-research",
8854
- cronPattern: "30 7 * * *",
8855
- interests: ["education"]
8856
- },
8857
- {
8858
- id: "industry-research-weekly",
8859
- category: "learning-research",
8860
- cronPattern: "0 9 * * 1",
8861
- interests: ["education", "business"]
8862
- },
8863
- {
8864
- id: "industry-morning-brief",
8865
- category: "business",
8866
- cronPattern: "0 8 * * *",
8867
- interests: ["business"]
8868
- },
8869
- {
8870
- id: "competitor-radar-daily",
8871
- category: "business",
8872
- cronPattern: "0 9 * * *",
8873
- interests: ["business"]
8874
- },
8875
- {
8876
- id: "funding-intel-daily",
8877
- category: "business",
8878
- cronPattern: "0 10 * * *",
8879
- interests: ["business"]
8880
- },
8881
- {
8882
- id: "macro-economy-weekly",
8883
- category: "business",
8884
- cronPattern: "0 8 * * 1",
8885
- interests: ["business", "investing"]
8886
- },
8887
- {
8888
- id: "weekly-meeting-brief",
8889
- category: "business",
8890
- cronPattern: "30 8 * * 1",
8891
- interests: ["business"]
8892
- },
8893
- {
8894
- id: "marketing-hot-radar",
8895
- category: "marketing",
8896
- cronPattern: "0 10 * * *",
8897
- interests: ["marketing"]
8898
- },
8899
- {
8900
- id: "ad-creative-inspiration",
8901
- category: "marketing",
8902
- cronPattern: "0 10 * * *",
8903
- interests: ["marketing"]
8904
- },
8905
- {
8906
- id: "brand-mention-daily",
8907
- category: "marketing",
8908
- cronPattern: "0 18 * * *",
8909
- interests: ["marketing"],
8910
- requiresSkills: [{
8911
- provider: "twitter",
8912
- source: "lobehub"
8913
- }]
8914
- },
8915
- {
8916
- id: "seo-weekly-report",
8917
- category: "marketing",
8918
- cronPattern: "0 9 * * 1",
8919
- interests: ["marketing"]
8920
- },
8921
- {
8922
- id: "newsletter-perf-weekly",
8923
- category: "marketing",
8924
- cronPattern: "0 10 * * 1",
8925
- interests: ["marketing"],
8926
- requiresSkills: [{
8927
- provider: "gmail",
8928
- source: "composio"
8929
- }]
8930
- },
8931
- {
8932
- id: "kol-collab-calendar",
8933
- category: "marketing",
8934
- cronPattern: "0 9 * * 1",
8935
- interests: ["marketing"],
8936
- requiresSkills: [{
8937
- provider: "airtable",
8938
- source: "composio"
8939
- }]
8940
- },
8941
- {
8942
- id: "hubspot-funnel-daily",
8943
- category: "marketing",
8944
- cronPattern: "0 9 * * *",
8945
- interests: ["marketing", "sales"],
8946
- requiresSkills: [{
8947
- provider: "hubspot",
8948
- source: "composio"
8949
- }]
8950
- },
8951
- {
8952
- id: "user-feedback-daily",
8953
- category: "product",
8954
- cronPattern: "0 9 * * *",
8955
- interests: ["product"]
8956
- },
8957
- {
8958
- id: "competitor-update-daily",
8959
- category: "product",
8960
- cronPattern: "0 10 * * *",
8961
- interests: ["product"]
8962
- },
8963
- {
8964
- id: "standup-brief",
8965
- category: "product",
8966
- cronPattern: "30 8 * * *",
8967
- interests: ["product"],
8968
- requiresSkills: [{
8969
- provider: "linear",
8970
- source: "lobehub"
8971
- }]
8972
- },
8973
- {
8974
- id: "iteration-recap-weekly",
8975
- category: "product",
8976
- cronPattern: "0 17 * * 5",
8977
- interests: ["product"],
8978
- requiresSkills: [{
8979
- provider: "linear",
8980
- source: "lobehub"
8981
- }]
8982
- },
8983
- {
8984
- id: "core-metric-daily",
8985
- category: "product",
8986
- cronPattern: "0 9 * * *",
8987
- interests: ["product"]
8988
- },
8989
- {
8990
- id: "user-interview-schedule",
8991
- category: "product",
8992
- cronPattern: "0 9 * * 1",
8993
- interests: ["product"],
8994
- requiresSkills: [{
8995
- provider: "google-calendar",
8996
- source: "composio"
8997
- }]
8998
- },
8999
- {
9000
- id: "prd-review-reminder",
9001
- category: "product",
9002
- cronPattern: "0 15 * * 5",
9003
- interests: ["product"],
9004
- requiresSkills: [{
9005
- provider: "notion",
9006
- source: "lobehub"
9007
- }]
9008
- },
9009
- {
9010
- id: "daily-followup-list",
9011
- category: "sales-customer",
9012
- cronPattern: "0 9 * * *",
9013
- interests: ["sales"],
9014
- requiresSkills: [{
9015
- provider: "hubspot",
9016
- source: "composio"
9017
- }]
9018
- },
9019
- {
9020
- id: "renewal-risk-weekly",
9021
- category: "sales-customer",
9022
- cronPattern: "0 9 * * 1",
9023
- interests: ["sales"],
9024
- requiresSkills: [{
9025
- provider: "hubspot",
9026
- source: "composio"
9027
- }]
9028
- },
9029
- {
9030
- id: "deal-pipeline-weekly",
9031
- category: "sales-customer",
9032
- cronPattern: "0 16 * * 5",
9033
- interests: ["sales"],
9034
- requiresSkills: [{
9035
- provider: "hubspot",
9036
- source: "composio"
9037
- }]
9038
- },
9039
- {
9040
- id: "key-account-radar",
9041
- category: "sales-customer",
9042
- cronPattern: "0 9 * * *",
9043
- interests: ["sales"]
9044
- },
9045
- {
9046
- id: "zendesk-ticket-daily",
9047
- category: "sales-customer",
9048
- cronPattern: "0 9 * * *",
9049
- interests: ["sales"],
9050
- requiresSkills: [{
9051
- provider: "zendesk",
9052
- source: "composio"
9053
- }]
9054
- },
9055
- {
9056
- id: "morning-brief",
9057
- category: "operations",
9058
- cronPattern: "0 8 * * *",
9059
- interests: ["operations"],
9060
- requiresSkills: [{
9061
- provider: "google-calendar",
9062
- source: "composio"
9063
- }]
9064
- },
9065
- {
9066
- id: "meeting-brief",
9067
- category: "operations",
9068
- cronPattern: "30 8 * * *",
9069
- interests: ["operations"],
9070
- requiresSkills: [{
9071
- provider: "google-calendar",
9072
- source: "composio"
9073
- }]
9074
- },
9075
- {
9076
- id: "calendar-conflict-check",
9077
- category: "operations",
9078
- cronPattern: "30 7 * * *",
9079
- interests: ["operations"],
9080
- requiresSkills: [{
9081
- provider: "google-calendar",
9082
- source: "composio"
9083
- }]
9084
- },
9085
- {
9086
- id: "friday-wrap-list",
9087
- category: "operations",
9088
- cronPattern: "0 16 * * 5",
9089
- interests: ["operations"],
9090
- requiresSkills: [{
9091
- provider: "linear",
9092
- source: "lobehub"
9093
- }]
9094
- },
9095
- {
9096
- id: "recruit-funnel-daily",
9097
- category: "hr",
9098
- cronPattern: "0 9 * * *",
9099
- interests: ["hr"],
9100
- requiresSkills: [{
9101
- provider: "airtable",
9102
- source: "composio"
9103
- }]
9104
- },
9105
- {
9106
- id: "onboarding-buddy-weekly",
9107
- category: "hr",
9108
- cronPattern: "0 9 * * 1",
9109
- interests: ["hr"],
9110
- requiresSkills: [{
9111
- provider: "notion",
9112
- source: "lobehub"
9113
- }]
9114
- },
9115
- {
9116
- id: "team-status-weekly",
9117
- category: "hr",
9118
- cronPattern: "0 9 * * 1",
9119
- interests: ["hr"],
9120
- requiresSkills: [{
9121
- provider: "google-calendar",
9122
- source: "composio"
9123
- }]
9124
- },
9125
- {
9126
- id: "precious-metals-daily",
9127
- category: "finance-legal",
9128
- cronPattern: "0 16 * * *",
9129
- interests: ["finance-legal", "investing"]
9130
- },
9131
- {
9132
- id: "pre-market-brief",
9133
- category: "finance-legal",
9134
- cronPattern: "0 9 * * *",
9135
- interests: ["finance-legal", "investing"]
9136
- },
9137
- {
9138
- id: "cashflow-weekly",
9139
- category: "finance-legal",
9140
- cronPattern: "0 9 * * 1",
9141
- interests: ["finance-legal"],
9142
- requiresSkills: [{
9143
- provider: "airtable",
9144
- source: "composio"
9145
- }]
9146
- },
9147
- {
9148
- id: "contract-expiry-weekly",
9149
- category: "finance-legal",
9150
- cronPattern: "0 9 * * 1",
9151
- interests: ["finance-legal"],
9152
- requiresSkills: [{
9153
- provider: "notion",
9154
- source: "lobehub"
9155
- }]
9156
- },
9157
- {
9158
- id: "regulation-watch-weekly",
9159
- category: "finance-legal",
9160
- cronPattern: "0 10 * * 1",
9161
- interests: ["finance-legal"]
9162
- },
9163
- {
9164
- id: "invoice-collection-daily",
9165
- category: "finance-legal",
9166
- cronPattern: "0 10 * * *",
9167
- interests: ["finance-legal"],
9168
- requiresSkills: [{
9169
- provider: "gmail",
9170
- source: "composio"
9171
- }]
9172
- },
9173
- {
9174
- id: "cross-platform-engagement-daily",
9175
- category: "creator",
9176
- cronPattern: "0 9 * * *",
9177
- interests: ["creator"],
9178
- requiresSkills: [{
9179
- provider: "twitter",
9180
- source: "lobehub"
9181
- }]
9182
- },
9183
- {
9184
- id: "brand-collab-weekly",
9185
- category: "creator",
9186
- cronPattern: "0 10 * * 1",
9187
- interests: ["creator"]
9188
- },
9189
- {
9190
- id: "follower-growth-weekly",
9191
- category: "creator",
9192
- cronPattern: "0 10 * * 1",
9193
- interests: ["creator"],
9194
- requiresSkills: [{
9195
- provider: "twitter",
9196
- source: "lobehub"
9197
- }]
9198
- },
9199
- {
9200
- id: "youtube-channel-weekly",
9201
- category: "creator",
9202
- cronPattern: "0 9 * * 1",
9203
- interests: ["creator"],
9204
- requiresSkills: [{
9205
- provider: "youtube",
9206
- source: "composio"
9207
- }]
9208
- },
9209
- {
9210
- id: "monetization-opportunity-weekly",
9211
- category: "creator",
9212
- cronPattern: "0 10 * * 3",
9213
- interests: ["creator"]
9214
- },
9215
- {
9216
- id: "portfolio-daily",
9217
- category: "investing",
9218
- cronPattern: "0 16 * * *",
9219
- interests: ["investing"]
9220
- },
9221
- {
9222
- id: "crypto-market-daily",
9223
- category: "investing",
9224
- cronPattern: "0 9 * * *",
9225
- interests: ["investing"]
9226
- },
9227
- {
9228
- id: "child-growth-weekly",
9229
- category: "parenting",
9230
- cronPattern: "0 9 * * 1",
9231
- interests: ["parenting"]
9232
- },
9233
- {
9234
- id: "child-study-weekly",
9235
- category: "parenting",
9236
- cronPattern: "0 20 * * 0",
9237
- interests: ["parenting", "education"]
9238
- },
9239
- {
9240
- id: "family-finance-weekly",
9241
- category: "parenting",
9242
- cronPattern: "0 20 * * 0",
9243
- interests: ["parenting", "finance-legal"],
9244
- requiresSkills: [{
9245
- provider: "google-sheets",
9246
- source: "composio"
9247
- }]
9248
- },
9249
- {
9250
- id: "family-task-schedule",
9251
- category: "parenting",
9252
- cronPattern: "0 8 * * 1",
9253
- interests: ["parenting"],
9254
- optionalSkills: [{
9255
- provider: "google-calendar",
9256
- source: "composio"
9257
- }]
9258
- },
9259
- {
9260
- id: "diet-log-companion",
9261
- category: "health",
9262
- cronPattern: "0 21 * * *",
9263
- interests: ["health"]
9264
- },
9265
- {
9266
- id: "podcast-new-episodes",
9267
- category: "hobbies",
9268
- cronPattern: "0 9 * * 1",
9269
- interests: ["hobbies"]
9270
- },
9271
- {
9272
- id: "newsletter-aggregator",
9273
- category: "hobbies",
9274
- cronPattern: "0 20 * * 0",
9275
- interests: ["hobbies"],
9276
- requiresSkills: [{
9277
- provider: "gmail",
9278
- source: "composio"
9279
- }]
9280
- },
9281
- {
9282
- id: "series-update-weekly",
9283
- category: "hobbies",
9284
- cronPattern: "0 9 * * 1",
9285
- interests: ["hobbies"]
9286
- },
9287
- {
9288
- id: "travel-inspiration-weekly",
9289
- category: "hobbies",
9290
- cronPattern: "0 10 * * 3",
9291
- interests: ["hobbies"]
9292
- },
9293
- {
9294
- id: "watchlist-friday",
9295
- category: "hobbies",
9296
- cronPattern: "0 18 * * 5",
9297
- interests: ["hobbies"]
9298
- },
9299
- {
9300
- id: "exhibition-event-weekly",
9301
- category: "hobbies",
9302
- cronPattern: "0 10 * * 1",
9303
- interests: ["hobbies"]
9304
- },
9305
- {
9306
- id: "daily-learning-bite",
9307
- category: "personal-life",
9308
- cronPattern: "30 7 * * *",
9309
- interests: ["education", "personal"]
9310
- },
9311
- {
9312
- id: "sunday-reflection",
9313
- category: "personal-life",
9314
- cronPattern: "0 21 * * 0",
9315
- interests: ["personal"]
9316
- },
9317
- {
9318
- id: "morning-ritual",
9319
- category: "personal-life",
9320
- cronPattern: "0 7 * * *",
9321
- interests: ["personal"],
9322
- optionalSkills: [{
9323
- provider: "google-calendar",
9324
- source: "composio"
9325
- }]
9326
- },
9327
- {
9328
- id: "bedtime-gratitude",
9329
- category: "personal-life",
9330
- cronPattern: "0 22 * * *",
9331
- interests: ["personal"],
9332
- optionalSkills: [{
9333
- provider: "notion",
9334
- source: "lobehub"
9335
- }]
9336
- }
9337
- ];
9338
- const KNOWN_TASK_TEMPLATE_IDS = new Set(taskTemplates.map((t) => t.id));
9339
-
9340
8705
  //#endregion
9341
8706
  //#region node_modules/.pnpm/url-join@5.0.0/node_modules/url-join/lib/url-join.js
9342
8707
  function normalize(strArray) {
@@ -9419,7 +8784,7 @@ const MEMORY_SEARCH_TOP_K_LIMITS = {
9419
8784
 
9420
8785
  //#endregion
9421
8786
  //#region ../../package.json
9422
- var version$3 = "2.2.4";
8787
+ var version$3 = "2.2.6";
9423
8788
 
9424
8789
  //#endregion
9425
8790
  //#region ../../packages/const/src/version.ts
@@ -18080,11 +17445,27 @@ const expandTilde = (input) => {
18080
17445
  if (input.startsWith("~/") || input.startsWith("~\\")) return path.join(os.homedir(), input.slice(2));
18081
17446
  return input;
18082
17447
  };
17448
+ /**
17449
+ * Resolve a filesystem path for Node fs APIs: first expand a leading `~`, then
17450
+ * anchor a still-relative path to `cwd` — the device-bound working directory.
17451
+ *
17452
+ * Absolute paths pass through untouched. When `cwd` is absent the behavior is
17453
+ * identical to {@link expandTilde}, so callers that don't carry a working
17454
+ * directory (e.g. desktop client-mode today) keep resolving relative paths
17455
+ * against the process cwd and nothing regresses. Without this, a relative path
17456
+ * supplied by the model resolves against the daemon's `process.cwd()` (= `/`
17457
+ * for a Finder/Dock-launched app) instead of the user's bound directory.
17458
+ */
17459
+ const resolveAgainstCwd = (input, cwd) => {
17460
+ const expanded = expandTilde(input);
17461
+ if (!expanded || !cwd) return expanded;
17462
+ return path.isAbsolute(expanded) ? expanded : path.join(cwd, expanded);
17463
+ };
18083
17464
 
18084
17465
  //#endregion
18085
17466
  //#region ../../packages/local-file-shell/src/file/edit.ts
18086
- async function editLocalFile({ file_path: rawPath, old_string, new_string, replace_all = false }) {
18087
- const filePath = expandTilde(rawPath) ?? rawPath;
17467
+ async function editLocalFile({ file_path: rawPath, old_string, new_string, replace_all = false, cwd }) {
17468
+ const filePath = resolveAgainstCwd(rawPath, cwd) ?? rawPath;
18088
17469
  try {
18089
17470
  const content = await readFile(filePath, "utf8");
18090
17471
  if (!content.includes(old_string)) return {
@@ -198620,9 +198001,9 @@ const sniffBinaryFile = async (filePath) => {
198620
198001
 
198621
198002
  //#endregion
198622
198003
  //#region ../../packages/local-file-shell/src/file/list.ts
198623
- async function listLocalFiles({ path: rawPath, sortBy = "modifiedTime", sortOrder = "desc", limit = 100 }, options) {
198004
+ async function listLocalFiles({ path: rawPath, sortBy = "modifiedTime", sortOrder = "desc", limit = 100, cwd }, options) {
198624
198005
  const { ignoreSystemFiles = true } = options || {};
198625
- const dirPath = expandTilde(rawPath) ?? rawPath;
198006
+ const dirPath = resolveAgainstCwd(rawPath, cwd) ?? rawPath;
198626
198007
  try {
198627
198008
  const entries = await readdir(dirPath);
198628
198009
  const results = [];
@@ -198678,12 +198059,12 @@ async function listLocalFiles({ path: rawPath, sortBy = "modifiedTime", sortOrde
198678
198059
 
198679
198060
  //#endregion
198680
198061
  //#region ../../packages/local-file-shell/src/file/move.ts
198681
- async function moveLocalFiles({ items }) {
198062
+ async function moveLocalFiles({ items, cwd }) {
198682
198063
  const results = [];
198683
198064
  if (!items || items.length === 0) return [];
198684
198065
  for (const item of items) {
198685
- const sourcePath = expandTilde(item.oldPath) ?? item.oldPath;
198686
- const newPath = expandTilde(item.newPath) ?? item.newPath;
198066
+ const sourcePath = resolveAgainstCwd(item.oldPath, cwd) ?? item.oldPath;
198067
+ const newPath = resolveAgainstCwd(item.newPath, cwd) ?? item.newPath;
198687
198068
  const resultItem = {
198688
198069
  newPath: void 0,
198689
198070
  sourcePath,
@@ -198759,8 +198140,8 @@ const truncateLine = (line) => {
198759
198140
  truncated: true
198760
198141
  };
198761
198142
  };
198762
- async function readLocalFile({ path: rawPath, loc, fullContent }) {
198763
- const filePath = expandTilde(rawPath) ?? rawPath;
198143
+ async function readLocalFile({ path: rawPath, loc, fullContent, cwd }) {
198144
+ const filePath = resolveAgainstCwd(rawPath, cwd) ?? rawPath;
198764
198145
  const effectiveLoc = fullContent ? void 0 : loc ?? [0, 200];
198765
198146
  let stats;
198766
198147
  try {
@@ -198921,7 +198302,7 @@ async function searchLocalFiles({ keywords, directory, onlyIn, contentContains,
198921
198302
 
198922
198303
  //#endregion
198923
198304
  //#region ../../packages/local-file-shell/src/file/write.ts
198924
- async function writeLocalFile({ path: rawPath, content }) {
198305
+ async function writeLocalFile({ path: rawPath, content, cwd }) {
198925
198306
  if (!rawPath) return {
198926
198307
  error: "Path cannot be empty",
198927
198308
  success: false
@@ -198930,7 +198311,7 @@ async function writeLocalFile({ path: rawPath, content }) {
198930
198311
  error: "Content cannot be empty",
198931
198312
  success: false
198932
198313
  };
198933
- const filePath = expandTilde(rawPath) ?? rawPath;
198314
+ const filePath = resolveAgainstCwd(rawPath, cwd) ?? rawPath;
198934
198315
  try {
198935
198316
  await mkdir(path.dirname(filePath), { recursive: true });
198936
198317
  await writeFile(filePath, content, "utf8");
@@ -207326,10 +206707,7 @@ function registerConnectCommand(program) {
207326
206707
  if (options.daemon) return handleDaemonStart(options);
207327
206708
  await runConnect(options, options.daemonChild || process.env.LOBEHUB_DAEMON === "1");
207328
206709
  });
207329
- connectCmd.command("stop").description("Stop the background daemon process").action(() => {
207330
- if (stopDaemon()) log$10.info("Daemon stopped.");
207331
- else log$10.warn("No daemon is running.");
207332
- });
206710
+ connectCmd.command("stop").description("Stop the background daemon process").action(handleStop);
207333
206711
  connectCmd.command("status").description("Show background daemon status").action(() => {
207334
206712
  const pid = getRunningDaemonPid();
207335
206713
  if (pid === null) {
@@ -207368,6 +206746,11 @@ function registerConnectCommand(program) {
207368
206746
  daemon: true
207369
206747
  });
207370
206748
  });
206749
+ program.command("disconnect").description("Disconnect from the device gateway (alias for `connect stop`)").action(handleStop);
206750
+ }
206751
+ function handleStop() {
206752
+ if (stopDaemon()) log$10.info("Daemon stopped.");
206753
+ else log$10.warn("No daemon is running.");
207371
206754
  }
207372
206755
  function handleDaemonStart(options) {
207373
206756
  const existingPid = getRunningDaemonPid();
@@ -208245,21 +207628,27 @@ function registerEvalCommand(program) {
208245
207628
  * Unknown extensions fall back to `application/octet-stream`.
208246
207629
  */
208247
207630
  const MIME_MAP = {
207631
+ aac: "audio/aac",
208248
207632
  csv: "text/csv",
208249
207633
  doc: "application/msword",
208250
207634
  docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
207635
+ flac: "audio/flac",
208251
207636
  gif: "image/gif",
208252
207637
  jpeg: "image/jpeg",
208253
207638
  jpg: "image/jpeg",
208254
207639
  json: "application/json",
207640
+ m4a: "audio/mp4",
208255
207641
  md: "text/markdown",
208256
207642
  mp3: "audio/mpeg",
208257
207643
  mp4: "video/mp4",
207644
+ ogg: "audio/ogg",
208258
207645
  pdf: "application/pdf",
208259
207646
  png: "image/png",
208260
207647
  pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
208261
207648
  svg: "image/svg+xml",
208262
207649
  txt: "text/plain",
207650
+ wav: "audio/wav",
207651
+ webm: "audio/webm",
208263
207652
  webp: "image/webp",
208264
207653
  xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
208265
207654
  };
@@ -208505,46 +207894,128 @@ function registerFileCommand(program) {
208505
207894
 
208506
207895
  //#endregion
208507
207896
  //#region src/commands/generate/asr.ts
207897
+ const MAX_INLINE_AUDIO_BYTES = 3 * 1024 * 1024;
208508
207898
  function registerAsrCommand(parent) {
208509
- parent.command("asr <audio-file>").description("Convert speech to text (automatic speech recognition)").option("--model <model>", "STT model", "whisper-1").option("--language <lang>", "Language code (e.g. en, zh)").option("--json", "Output raw JSON").action(async (audioFile, options) => {
208510
- if (!existsSync(audioFile)) {
207899
+ parent.command("asr <audio-file>").description("Convert speech to text (automatic speech recognition). Accepts a local path or a URL").option("--model <model>", "STT model", "whisper-1").option("--provider <provider>", "AI provider", "openai").option("--language <lang>", "Language code (e.g. en, zh)").option("--json", "Output raw JSON").action(async (audioFile, options) => {
207900
+ const isUrl = audioFile.startsWith("http://") || audioFile.startsWith("https://");
207901
+ if (!isUrl && !existsSync(audioFile)) {
208511
207902
  log$10.error(`File not found: ${audioFile}`);
208512
207903
  process.exit(1);
208513
207904
  return;
208514
207905
  }
208515
- const { serverUrl, headers } = await getAuthInfo();
208516
- const sttOptions = { model: options.model };
208517
- if (options.language) sttOptions.language = options.language;
208518
- const formData = new FormData();
208519
- const fileBuffer = await readFileAsBlob(audioFile);
208520
- formData.append("speech", fileBuffer, path.basename(audioFile));
208521
- formData.append("options", JSON.stringify(sttOptions));
208522
- const { "Content-Type": _, ...formHeaders } = headers;
208523
- const res = await fetch(`${serverUrl}/webapi/stt/openai`, {
208524
- body: formData,
208525
- headers: formHeaders,
208526
- method: "POST"
208527
- });
208528
- if (!res.ok) {
208529
- const errText = await res.text();
208530
- log$10.error(`ASR failed: ${res.status} ${errText}`);
207906
+ let localPath;
207907
+ let fileName;
207908
+ let mimeType;
207909
+ let size;
207910
+ let tempPath;
207911
+ try {
207912
+ if (isUrl) {
207913
+ const downloaded = await fetchAudioFromUrl(audioFile);
207914
+ fileName = downloaded.name;
207915
+ mimeType = downloaded.mimeType;
207916
+ size = downloaded.bytes.byteLength;
207917
+ tempPath = path.join(os.tmpdir(), `lh-asr-${process.pid}-${Date.now()}-${fileName}`);
207918
+ await writeFile(tempPath, downloaded.bytes);
207919
+ localPath = tempPath;
207920
+ } else {
207921
+ localPath = audioFile;
207922
+ fileName = path.basename(audioFile);
207923
+ size = statSync(audioFile).size;
207924
+ }
207925
+ } catch (error) {
207926
+ log$10.error(error instanceof Error ? error.message : String(error));
208531
207927
  process.exit(1);
208532
207928
  return;
208533
207929
  }
208534
- const result = await res.json();
208535
- if (options.json) console.log(JSON.stringify(result, null, 2));
208536
- else {
208537
- const text = result.text || JSON.stringify(result);
208538
- process.stdout.write(text);
208539
- process.stdout.write("\n");
208540
- }
208541
- });
207930
+ try {
207931
+ const client = await getTrpcClient();
207932
+ let result;
207933
+ if (size > MAX_INLINE_AUDIO_BYTES) {
207934
+ process.stderr.write(`Audio is ${(size / 1024 / 1024).toFixed(1)}MB — uploading before transcription…\n`);
207935
+ const record = await uploadLocalFile(client, localPath);
207936
+ result = await client.asr.transcribe.mutate({
207937
+ fileId: record.id,
207938
+ language: options.language,
207939
+ model: options.model,
207940
+ provider: options.provider
207941
+ });
207942
+ } else {
207943
+ const bytes = await readFile(localPath);
207944
+ result = await client.asr.transcribe.mutate({
207945
+ audioBase64: Buffer.from(bytes).toString("base64"),
207946
+ fileName,
207947
+ language: options.language,
207948
+ mimeType,
207949
+ model: options.model,
207950
+ provider: options.provider
207951
+ });
207952
+ }
207953
+ if (options.json) console.log(JSON.stringify(result, null, 2));
207954
+ else {
207955
+ process.stdout.write(result.text);
207956
+ process.stdout.write("\n");
207957
+ }
207958
+ } catch (error) {
207959
+ log$10.error(`ASR failed: ${error instanceof Error ? error.message : String(error)}`);
207960
+ process.exit(1);
207961
+ } finally {
207962
+ if (tempPath) await rm(tempPath, { force: true }).catch(() => {});
207963
+ }
207964
+ });
207965
+ }
207966
+ const AUDIO_MIME_TO_EXT = {
207967
+ "audio/aac": "aac",
207968
+ "audio/flac": "flac",
207969
+ "audio/m4a": "m4a",
207970
+ "audio/mp3": "mp3",
207971
+ "audio/mp4": "m4a",
207972
+ "audio/mpeg": "mp3",
207973
+ "audio/mpga": "mp3",
207974
+ "audio/ogg": "ogg",
207975
+ "audio/opus": "ogg",
207976
+ "audio/wav": "wav",
207977
+ "audio/wave": "wav",
207978
+ "audio/webm": "webm",
207979
+ "audio/x-m4a": "m4a",
207980
+ "audio/x-wav": "wav"
207981
+ };
207982
+ async function fetchAudioFromUrl(url) {
207983
+ const res = await fetch(url);
207984
+ if (!res.ok) throw new Error(`Failed to download audio: ${res.status} ${res.statusText}`);
207985
+ const bytes = new Uint8Array(await res.arrayBuffer());
207986
+ const contentType = res.headers.get("content-type")?.split(";")[0]?.trim().toLowerCase();
207987
+ const mimeType = contentType?.startsWith("audio/") ? contentType : void 0;
207988
+ const name = fileNameFromContentDisposition(res.headers.get("content-disposition")) || basenameFromUrl(url) || "audio";
207989
+ const ext = contentType ? AUDIO_MIME_TO_EXT[contentType] : void 0;
207990
+ return {
207991
+ bytes,
207992
+ mimeType,
207993
+ name: path.extname(name) || !ext ? name : `${name}.${ext}`
207994
+ };
208542
207995
  }
208543
- async function readFileAsBlob(filePath) {
208544
- const chunks = [];
208545
- const stream = createReadStream(filePath);
208546
- for await (const chunk of stream) chunks.push(chunk);
208547
- return new Blob(chunks);
207996
+ function fileNameFromContentDisposition(header) {
207997
+ if (!header) return void 0;
207998
+ const extended = /filename\*=\s*(?:UTF-8|ISO-8859-1)?''([^;]+)/i.exec(header);
207999
+ if (extended?.[1]) try {
208000
+ return path.basename(decodeURIComponent(extended[1].trim()));
208001
+ } catch {}
208002
+ const value = /filename=\s*"?([^";]+)"?/i.exec(header)?.[1]?.trim();
208003
+ return value ? path.basename(value) : void 0;
208004
+ }
208005
+ function basenameFromUrl(url) {
208006
+ let pathname;
208007
+ try {
208008
+ pathname = new URL(url).pathname;
208009
+ } catch {
208010
+ return;
208011
+ }
208012
+ const base = path.basename(pathname);
208013
+ if (!base) return void 0;
208014
+ try {
208015
+ return decodeURIComponent(base);
208016
+ } catch {
208017
+ return base;
208018
+ }
208548
208019
  }
208549
208020
 
208550
208021
  //#endregion
@@ -209020,17 +208491,26 @@ const CLI_OVERLOADED_PATTERNS = [
209020
208491
  ...CLI_SERVER_THROTTLE_PATTERNS
209021
208492
  ];
209022
208493
  /**
209023
- * The one reliable discriminator between a user-side plan/quota limit and a
209024
- * transient server throttle: only the genuine user limit carries a concrete
209025
- * reset window in the structured `rate_limit_event` — `resetsAt` (epoch
209026
- * seconds) and/or a named `rateLimitType` (e.g. `seven_day`). Anthropic's
209027
- * transient throttle emits a rate_limit_event too, but with just
209028
- * `status: 'rejected'` and no reset info. Status codes (429 / 529) alone are
209029
- * ambiguous, so this structured signal — not the HTTP status, not the message
209030
- * text — is what decides whether we show the "usage limit reached, resets at
209031
- * X" guide vs the "temporarily overloaded, retry" guide.
208494
+ * Discriminates a user-side plan/quota limit from everything else.
208495
+ *
208496
+ * Two signals must BOTH hold:
208497
+ * 1. The request was actually `status: 'rejected'`. Anthropic stamps a
208498
+ * `rate_limit_info` onto its events even when the request goes through
208499
+ * (`status: 'allowed'`) — that block is just the rolling-window metadata
208500
+ * (`resetsAt`, `rateLimitType`) for an *allowed* call, NOT evidence the
208501
+ * limit was hit. Leaning on the presence of a reset window alone made a
208502
+ * later unrelated terminal failure (e.g. an `ECONNRESET` network drop)
208503
+ * inherit the last allowed event's window and render a bogus "usage limit
208504
+ * reached, resets at X" guide. The `status` is the gate.
208505
+ * 2. A concrete reset window (`resetsAt` epoch seconds and/or a named
208506
+ * `rateLimitType` such as `seven_day`). A bare `rejected` with no window is
208507
+ * Anthropic's transient server throttle — left to the overloaded (retry)
208508
+ * classifier, not the usage-limit guide.
208509
+ *
208510
+ * Status codes (429 / 529) and message text are deliberately not consulted
208511
+ * here — only this structured signal decides the "usage limit reached" guide.
209032
208512
  */
209033
- const isUserQuotaRateLimit = (info) => !!info && (info.resetsAt != null || info.rateLimitType != null);
208513
+ const isUserQuotaRateLimit = (info) => !!info && info.status === "rejected" && (info.resetsAt != null || info.rateLimitType != null);
209034
208514
  const getCliResultMessage = (result) => {
209035
208515
  if (typeof result === "string") return result;
209036
208516
  if (result && typeof result === "object" && "message" in result && typeof result.message === "string") return result.message;
@@ -212466,6 +211946,7 @@ async function openBrowser(url) {
212466
211946
  //#region src/commands/logout.ts
212467
211947
  function registerLogoutCommand(program) {
212468
211948
  program.command("logout").description("Log out and remove stored credentials").action(() => {
211949
+ if (stopDaemon()) log$10.info("Disconnected device daemon.");
212469
211950
  if (clearCredentials()) log$10.info("Logged out. Credentials removed.");
212470
211951
  else log$10.info("No credentials found. Already logged out.");
212471
211952
  });
@@ -213544,17 +213025,19 @@ function registerMigrateCommand(program) {
213544
213025
  //#endregion
213545
213026
  //#region src/commands/model.ts
213546
213027
  const isVisibleModel = (model) => model.visible !== false;
213028
+ const normalizeModelType = (type) => type === "stt" ? "asr" : type;
213547
213029
  function registerModelCommand(program) {
213548
213030
  const model = program.command("model").description("Manage AI models");
213549
- model.command("list <providerId>").description("List models for a provider").option("-L, --limit <n>", "Maximum number of items", "50").option("--enabled", "Only show enabled models").option("--type <type>", "Filter by model type (chat|embedding|tts|stt|image|video|text2music|realtime)").option("--json [fields]", "Output JSON, optionally specify fields (comma-separated)").action(async (providerId, options) => {
213031
+ model.command("list <providerId>").description("List models for a provider").option("-L, --limit <n>", "Maximum number of items", "50").option("--enabled", "Only show enabled models").option("--type <type>", "Filter by model type (chat|embedding|tts|asr|image|video|text2music|realtime)").option("--json [fields]", "Output JSON, optionally specify fields (comma-separated)").action(async (providerId, options) => {
213550
213032
  const client = await getTrpcClient();
213033
+ const typeFilter = options.type ? normalizeModelType(options.type) : void 0;
213551
213034
  const input = { id: providerId };
213552
213035
  if (options.limit) input.limit = Number.parseInt(options.limit, 10);
213553
213036
  if (options.enabled) input.enabled = true;
213554
- if (options.type) input.type = options.type;
213037
+ if (typeFilter) input.type = typeFilter;
213555
213038
  const result = await client.aiModel.getAiProviderModelList.query(input);
213556
213039
  let items = (Array.isArray(result) ? result : result.items ?? []).filter(isVisibleModel);
213557
- if (options.type) items = items.filter((m) => m.type === options.type);
213040
+ if (typeFilter) items = items.filter((m) => m.type === typeFilter);
213558
213041
  if (options.json !== void 0) {
213559
213042
  const fields = typeof options.json === "string" ? options.json : void 0;
213560
213043
  outputJson(items, fields);
@@ -213595,18 +213078,18 @@ function registerModelCommand(program) {
213595
213078
  if (r.enabled !== void 0) meta.push(r.enabled ? "Enabled" : "Disabled");
213596
213079
  if (meta.length > 0) console.log(import_picocolors.default.dim(meta.join(" · ")));
213597
213080
  });
213598
- model.command("create").description("Create a new model").requiredOption("--id <id>", "Model ID").requiredOption("--provider <providerId>", "Provider ID").option("--display-name <name>", "Display name").option("--type <type>", "Model type (chat|embedding|tts|stt|image|video|text2music|realtime)", "chat").action(async (options) => {
213081
+ model.command("create").description("Create a new model").requiredOption("--id <id>", "Model ID").requiredOption("--provider <providerId>", "Provider ID").option("--display-name <name>", "Display name").option("--type <type>", "Model type (chat|embedding|tts|asr|image|video|text2music|realtime)", "chat").action(async (options) => {
213599
213082
  const client = await getTrpcClient();
213600
213083
  const input = {
213601
213084
  id: options.id,
213602
213085
  providerId: options.provider,
213603
- type: options.type || "chat"
213086
+ type: normalizeModelType(options.type || "chat")
213604
213087
  };
213605
213088
  if (options.displayName) input.displayName = options.displayName;
213606
213089
  const resultId = await client.aiModel.createAiModel.mutate(input);
213607
213090
  console.log(`${import_picocolors.default.green("✓")} Created model ${import_picocolors.default.bold(resultId || options.id)}`);
213608
213091
  });
213609
- model.command("edit <id>").description("Update model info").requiredOption("--provider <providerId>", "Provider ID").option("--display-name <name>", "Display name").option("--type <type>", "Model type (chat|embedding|tts|stt|image|video|text2music|realtime)").action(async (id, options) => {
213092
+ model.command("edit <id>").description("Update model info").requiredOption("--provider <providerId>", "Provider ID").option("--display-name <name>", "Display name").option("--type <type>", "Model type (chat|embedding|tts|asr|image|video|text2music|realtime)").action(async (id, options) => {
213610
213093
  if (!options.displayName && !options.type) {
213611
213094
  log$10.error("No changes specified. Use --display-name or --type.");
213612
213095
  process.exit(1);
@@ -213614,7 +213097,7 @@ function registerModelCommand(program) {
213614
213097
  const client = await getTrpcClient();
213615
213098
  const value = {};
213616
213099
  if (options.displayName) value.displayName = options.displayName;
213617
- if (options.type) value.type = options.type;
213100
+ if (options.type) value.type = normalizeModelType(options.type);
213618
213101
  await client.aiModel.updateAiModel.mutate({
213619
213102
  id,
213620
213103
  providerId: options.provider,
package/man/man1/lh.1 CHANGED
@@ -1,6 +1,6 @@
1
1
  .\" Code generated by `npm run man:generate`; DO NOT EDIT.
2
2
  .\" Manual command details come from the Commander command tree.
3
- .TH LH 1 "" "@lobehub/cli 0.0.31" "User Commands"
3
+ .TH LH 1 "" "@lobehub/cli 0.0.33" "User Commands"
4
4
  .SH NAME
5
5
  lh \- LobeHub CLI \- manage and connect to LobeHub services
6
6
  .SH SYNOPSIS
@@ -41,6 +41,9 @@ Show a manual page for the CLI or a subcommand
41
41
  .B connect
42
42
  Connect to the device gateway and listen for tool calls
43
43
  .TP
44
+ .B disconnect
45
+ Disconnect from the device gateway (alias for `connect stop`)
46
+ .TP
44
47
  .B device
45
48
  Manage connected devices
46
49
  .TP
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/cli",
3
- "version": "0.0.31",
3
+ "version": "0.0.33",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "lh": "./dist/index.js",
@@ -31,13 +31,13 @@
31
31
  "tsdown": "^0.21.4",
32
32
  "typescript": "^6.0.3",
33
33
  "ws": "^8.18.1",
34
- "@lobechat/agent-gateway-client": "1.0.0",
34
+ "@lobechat/device-control": "1.0.0",
35
35
  "@lobechat/device-gateway-client": "1.0.0",
36
+ "@lobechat/agent-gateway-client": "1.0.0",
36
37
  "@lobechat/heterogeneous-agents": "1.0.0",
37
- "@lobechat/device-control": "1.0.0",
38
- "@lobechat/device-identity": "1.0.0",
38
+ "@lobechat/tool-runtime": "1.0.0",
39
39
  "@lobechat/local-file-shell": "1.0.0",
40
- "@lobechat/tool-runtime": "1.0.0"
40
+ "@lobechat/device-identity": "1.0.0"
41
41
  },
42
42
  "publishConfig": {
43
43
  "access": "public",