loopat 0.1.54 → 0.1.55

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 (33) hide show
  1. package/package.json +1 -1
  2. package/server/src/git-host.ts +5 -1
  3. package/server/src/github.ts +21 -0
  4. package/server/src/loops.ts +14 -0
  5. package/web/dist/assets/{Editor-BN7Q5g4N.js → Editor-onKcF7Hk.js} +1 -1
  6. package/web/dist/assets/{Markdown-DvFvJx3d.js → Markdown-TRpyMaSJ.js} +1 -1
  7. package/web/dist/assets/{MilkdownEditor-DsqsnlSK.js → MilkdownEditor-CPzvRlCr.js} +1 -1
  8. package/web/dist/assets/{architectureDiagram-3BPJPVTR-BiL4yrd-.js → architectureDiagram-3BPJPVTR-BqWvl6_Z.js} +1 -1
  9. package/web/dist/assets/{chunk-727SXJPM-DB5VAPjb.js → chunk-727SXJPM-DgAepYue.js} +1 -1
  10. package/web/dist/assets/{chunk-AQP2D5EJ-zKFdEFYM.js → chunk-AQP2D5EJ-DSPZ2zOm.js} +1 -1
  11. package/web/dist/assets/{classDiagram-4FO5ZUOK-DGIJJBY5.js → classDiagram-4FO5ZUOK-CRIzZW-i.js} +1 -1
  12. package/web/dist/assets/{classDiagram-v2-Q7XG4LA2-BdsZBmZ_.js → classDiagram-v2-Q7XG4LA2-CWOJ0KP_.js} +1 -1
  13. package/web/dist/assets/{diagram-2AECGRRQ-DE80i5fc.js → diagram-2AECGRRQ-aYV3991Q.js} +1 -1
  14. package/web/dist/assets/{diagram-5GNKFQAL-Egr34zuZ.js → diagram-5GNKFQAL-430D0ULK.js} +1 -1
  15. package/web/dist/assets/{diagram-LMA3HP47-f9ekzEBK.js → diagram-LMA3HP47-B_1CjDrM.js} +1 -1
  16. package/web/dist/assets/{diagram-OG6HWLK6-CJhcoTKU.js → diagram-OG6HWLK6-DS7uDEpu.js} +1 -1
  17. package/web/dist/assets/{erDiagram-TEJ5UH35-CYNgf9or.js → erDiagram-TEJ5UH35-KifhsjrY.js} +1 -1
  18. package/web/dist/assets/{flowDiagram-I6XJVG4X-BXTQ7QRg.js → flowDiagram-I6XJVG4X-CUF2AkfX.js} +1 -1
  19. package/web/dist/assets/{index-BbAx7IhR.js → index-B60_8DVa.js} +6 -6
  20. package/web/dist/assets/{infoDiagram-5YYISTIA-Be0OcURm.js → infoDiagram-5YYISTIA-BWBMucTX.js} +1 -1
  21. package/web/dist/assets/{ishikawaDiagram-YF4QCWOH-BFyPbPqD.js → ishikawaDiagram-YF4QCWOH-B08GNYsG.js} +1 -1
  22. package/web/dist/assets/{kanban-definition-UN3LZRKU-C5BAKIQX.js → kanban-definition-UN3LZRKU-B5M0uLn9.js} +1 -1
  23. package/web/dist/assets/{mindmap-definition-RKZ34NQL-DqgCcW-r.js → mindmap-definition-RKZ34NQL-BcWz14bu.js} +1 -1
  24. package/web/dist/assets/{pieDiagram-4H26LBE5-BP9tC1HT.js → pieDiagram-4H26LBE5-BeDVs-my.js} +1 -1
  25. package/web/dist/assets/{requirementDiagram-4Y6WPE33-B_O_cEpd.js → requirementDiagram-4Y6WPE33-AQfIOy53.js} +1 -1
  26. package/web/dist/assets/{sequenceDiagram-3UESZ5HK-DBBZS2WD.js → sequenceDiagram-3UESZ5HK--egU0Cm3.js} +1 -1
  27. package/web/dist/assets/{stateDiagram-AJRCARHV-BaH2s_og.js → stateDiagram-AJRCARHV-CbyXuf3R.js} +1 -1
  28. package/web/dist/assets/{stateDiagram-v2-BHNVJYJU-Ryor5k_i.js → stateDiagram-v2-BHNVJYJU-CeJTs4x0.js} +1 -1
  29. package/web/dist/assets/{timeline-definition-PNZ67QCA-BNvy_0DN.js → timeline-definition-PNZ67QCA-DvSJjt5p.js} +1 -1
  30. package/web/dist/assets/{vennDiagram-CIIHVFJN-D5RSWRYm.js → vennDiagram-CIIHVFJN-BFBGrdGc.js} +1 -1
  31. package/web/dist/assets/{wardleyDiagram-YWT4CUSO-5PntQ1A_.js → wardleyDiagram-YWT4CUSO-9QmHh5AB.js} +1 -1
  32. package/web/dist/assets/{xychartDiagram-2RQKCTM6-B_1m_ITe.js → xychartDiagram-2RQKCTM6-DwH6-8zP.js} +1 -1
  33. package/web/dist/index.html +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopat",
3
- "version": "0.1.54",
3
+ "version": "0.1.55",
4
4
  "description": "Self-hosted AI workspace built around context management — works solo, scales to teams",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/simpx/loopat",
@@ -25,12 +25,16 @@ export type OnboardingField = {
25
25
  type?: "password" | "text"
26
26
  help?: string
27
27
  placeholder?: string
28
+ /** Prefill value (e.g. the seeded baseUrl/model) — user can edit. */
29
+ value?: string
28
30
  /**
29
31
  * - "vault-env": store the submitted value in the vault under `name`.
30
32
  * - "personal-repo-token": use the submitted value as the git token to
31
33
  * provision + import the user's personal repo.
34
+ * - "provider-field": write to the default provider's `name` field
35
+ * (e.g. baseUrl, model) in personal config.json.
32
36
  */
33
- action: "vault-env" | "personal-repo-token"
37
+ action: "vault-env" | "personal-repo-token" | "provider-field"
34
38
  }
35
39
 
36
40
  export type OnboardingForm = {
@@ -193,6 +193,27 @@ export const githubProvider: GitHostProvider = {
193
193
  },
194
194
  }
195
195
  }
196
+ // Step 2: need at least one usable AI key. Judge by resolved providers (key
197
+ // already expanded from vault) — repo seeded anthropic with a baseUrl/model.
198
+ const providers = (ctx.config?.providers ?? {}) as Record<string, any>
199
+ const hasKey = Object.values(providers).some((p) => p && typeof p.apiKey === "string" && p.apiKey.trim().length > 0)
200
+ const anthropic = providers.anthropic ?? {}
201
+ if (!hasKey) {
202
+ return {
203
+ done: false,
204
+ show: {
205
+ kind: "form",
206
+ title: "配置 AI Provider",
207
+ description: "填一个 API key 才能开始。URL 和 model 可改(默认 Anthropic)。key 存在你自己的加密 vault 里,不上服务器。",
208
+ submitLabel: "保存并开始",
209
+ fields: [
210
+ { name: "ANTHROPIC_API_KEY", label: "API Key", type: "password", action: "vault-env" },
211
+ { name: "baseUrl", label: "API URL", type: "text", action: "provider-field", value: anthropic.baseUrl ?? "https://api.anthropic.com" },
212
+ { name: "model", label: "Model", type: "text", action: "provider-field", value: anthropic.model ?? "claude-opus-4-7" },
213
+ ],
214
+ },
215
+ }
216
+ }
196
217
  return { done: true }
197
218
  },
198
219
  async authenticate(cred) {
@@ -772,6 +772,7 @@ export async function submitOnboarding(
772
772
  // user acting on the real page, then onboarding() re-checks.
773
773
  if (cur.show.kind !== "form") return { ok: true, next: cur }
774
774
  let wroteVaultEnv = false
775
+ const providerFields: Record<string, string> = {}
775
776
  for (const field of cur.show.fields) {
776
777
  const raw = values[field.name]
777
778
  const value = typeof raw === "string" ? raw.trim() : ""
@@ -780,6 +781,8 @@ export async function submitOnboarding(
780
781
  const r = await writeVaultEnv(userId, vault, field.name, value)
781
782
  if (!r.ok) return { ok: false, error: `couldn't save ${field.label}: ${r.error}` }
782
783
  wroteVaultEnv = true
784
+ } else if (field.action === "provider-field") {
785
+ providerFields[field.name] = value
783
786
  } else if (field.action === "personal-repo-token") {
784
787
  const r = await setupPersonalViaProvider({
785
788
  userId,
@@ -791,6 +794,17 @@ export async function submitOnboarding(
791
794
  if (!r.ok) return { ok: false, error: r.error }
792
795
  }
793
796
  }
797
+ // Write provider-field edits (baseUrl / model) into the default provider.
798
+ if (Object.keys(providerFields).length > 0) {
799
+ const { readPersonalDiskRaw, savePersonalDisk } = await import("./config")
800
+ const disk = await readPersonalDiskRaw(userId)
801
+ const def = (disk.providers?.default as string) || "anthropic"
802
+ const p = { ...(disk.providers?.[def] as any) }
803
+ if (providerFields.baseUrl) p.baseUrl = providerFields.baseUrl
804
+ if (providerFields.model) { p.model = providerFields.model; p.models = [{ id: providerFields.model, enabled: true }] }
805
+ await savePersonalDisk(userId, { providers: { ...disk.providers, [def]: p } as any })
806
+ wroteVaultEnv = true // ensure the config change is pushed below
807
+ }
794
808
  // Persist vault edits (e.g. an api key) to the personal remote — committed but
795
809
  // unpushed secrets are lost on re-import / another machine.
796
810
  if (wroteVaultEnv) {
@@ -1 +1 @@
1
- import{i as e}from"./chunk-62oNxeRG.js";import{n as t,t as n}from"./jsx-runtime-ZP0XCtgh.js";import{d as r,f as i,u as a}from"./index-BbAx7IhR.js";import{CodeEditor as o}from"./CodeEditor-ROIg2ylA.js";var s=e(t(),1),c=n();function l(){try{if(localStorage.getItem(`loopat:editor:wordWrap`)===`0`)return!1}catch{}return!0}function u({loopId:e,path:t,onSelectionChange:n}){let[u,d]=(0,s.useState)(``),[f,p]=(0,s.useState)(``),[m,h]=(0,s.useState)(!1),[g,_]=(0,s.useState)(!1),[v,y]=(0,s.useState)(l);(0,s.useEffect)(()=>{if(!t){d(``),p(``);return}h(!0),a(e,t).then(e=>{let t=e?.content??``;d(t),p(t)}).finally(()=>h(!1))},[e,t]);let b=t&&f!==u,x=async()=>{if(!(!t||g)){_(!0);try{await r(e,t,f)&&d(f)}finally{_(!1)}}};return t?(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(`div`,{className:`flex-1 min-h-0 relative`,onKeyDown:e=>{(e.metaKey||e.ctrlKey)&&e.key===`s`&&(e.preventDefault(),x())},children:m?(0,c.jsx)(`div`,{className:`h-full w-full flex items-center justify-center text-[12px] text-gray-400`,children:`loading…`}):(0,c.jsx)(o,{path:t,value:f,onChange:p,wordWrap:v,onSelectionChange:n})}),(0,c.jsxs)(`div`,{className:`border-t border-gray-200 px-3 py-1.5 text-[11px] text-gray-500 flex items-center gap-3`,children:[(0,c.jsx)(`span`,{className:`truncate`,children:t}),b&&(0,c.jsx)(`button`,{onClick:x,className:`text-orange-600 hover:underline`,title:`ctrl/⌘+S`,children:g?`saving…`:`unsaved · save`}),(0,c.jsx)(`span`,{className:`flex-1`}),(0,c.jsx)(`button`,{onClick:()=>{let e=!v;y(e);try{localStorage.setItem(`loopat:editor:wordWrap`,e?`1`:`0`)}catch{}},className:`flex items-center gap-1 hover:text-gray-700 transition-colors ${v?`text-gray-500`:`text-gray-300`}`,title:v?`word wrap: on`:`word wrap: off`,children:(0,c.jsx)(i,{size:13})}),(0,c.jsx)(`span`,{children:`utf-8 · LF`})]})]}):(0,c.jsx)(`div`,{className:`flex-1 min-h-0 flex items-center justify-center text-[13px] text-gray-500 px-8 text-center`,children:`没打开文件 · 在 ▤ workdir 里点一个`})}export{u as Editor};
1
+ import{i as e}from"./chunk-62oNxeRG.js";import{n as t,t as n}from"./jsx-runtime-ZP0XCtgh.js";import{d as r,f as i,u as a}from"./index-B60_8DVa.js";import{CodeEditor as o}from"./CodeEditor-ROIg2ylA.js";var s=e(t(),1),c=n();function l(){try{if(localStorage.getItem(`loopat:editor:wordWrap`)===`0`)return!1}catch{}return!0}function u({loopId:e,path:t,onSelectionChange:n}){let[u,d]=(0,s.useState)(``),[f,p]=(0,s.useState)(``),[m,h]=(0,s.useState)(!1),[g,_]=(0,s.useState)(!1),[v,y]=(0,s.useState)(l);(0,s.useEffect)(()=>{if(!t){d(``),p(``);return}h(!0),a(e,t).then(e=>{let t=e?.content??``;d(t),p(t)}).finally(()=>h(!1))},[e,t]);let b=t&&f!==u,x=async()=>{if(!(!t||g)){_(!0);try{await r(e,t,f)&&d(f)}finally{_(!1)}}};return t?(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(`div`,{className:`flex-1 min-h-0 relative`,onKeyDown:e=>{(e.metaKey||e.ctrlKey)&&e.key===`s`&&(e.preventDefault(),x())},children:m?(0,c.jsx)(`div`,{className:`h-full w-full flex items-center justify-center text-[12px] text-gray-400`,children:`loading…`}):(0,c.jsx)(o,{path:t,value:f,onChange:p,wordWrap:v,onSelectionChange:n})}),(0,c.jsxs)(`div`,{className:`border-t border-gray-200 px-3 py-1.5 text-[11px] text-gray-500 flex items-center gap-3`,children:[(0,c.jsx)(`span`,{className:`truncate`,children:t}),b&&(0,c.jsx)(`button`,{onClick:x,className:`text-orange-600 hover:underline`,title:`ctrl/⌘+S`,children:g?`saving…`:`unsaved · save`}),(0,c.jsx)(`span`,{className:`flex-1`}),(0,c.jsx)(`button`,{onClick:()=>{let e=!v;y(e);try{localStorage.setItem(`loopat:editor:wordWrap`,e?`1`:`0`)}catch{}},className:`flex items-center gap-1 hover:text-gray-700 transition-colors ${v?`text-gray-500`:`text-gray-300`}`,title:v?`word wrap: on`:`word wrap: off`,children:(0,c.jsx)(i,{size:13})}),(0,c.jsx)(`span`,{children:`utf-8 · LF`})]})]}):(0,c.jsx)(`div`,{className:`flex-1 min-h-0 flex items-center justify-center text-[13px] text-gray-500 px-8 text-center`,children:`没打开文件 · 在 ▤ workdir 里点一个`})}export{u as Editor};