@loicngr/kobo 1.11.2 → 1.11.4
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/CHANGELOG.md +8 -0
- package/README.md +5 -5
- package/dist/server/db/migrations.js +20 -0
- package/dist/server/db/schema.js +1 -0
- package/dist/server/routes/workspaces.js +71 -0
- package/dist/server/services/agent/engines/claude-code/engine.js +1 -0
- package/dist/server/services/agent/engines/claude-code/options-builder.js +2 -0
- package/dist/server/services/agent/engines/codex/capabilities.js +2 -1
- package/dist/server/services/agent/engines/codex/engine.js +1 -1
- package/dist/server/services/agent/engines/codex/options-builder.js +4 -2
- package/dist/server/services/agent/orchestrator.js +45 -8
- package/dist/server/services/engine-handoff-service.js +41 -0
- package/dist/server/services/forge/bitbucket/provider.js +291 -0
- package/dist/server/services/forge/registry.js +3 -1
- package/dist/server/services/forge/resolve.js +4 -1
- package/dist/server/services/settings-defaults.js +1 -1
- package/dist/server/services/settings-service.js +15 -1
- package/dist/server/services/workspace-service.js +50 -2
- package/package.json +1 -1
- package/src/client/dist/pwa/assets/{ActivityFeed-BNhy8oDx.js → ActivityFeed-CoE7FTe_.js} +1 -1
- package/src/client/dist/pwa/assets/{ChangelogPage-eRWLuQLR.js → ChangelogPage-Cr_15zCy.js} +1 -1
- package/src/client/dist/pwa/assets/{ClosePopup-Cc2YwliF.js → ClosePopup-DQgUw5Fk.js} +1 -1
- package/src/client/dist/pwa/assets/{CreatePage-B9Mxi7zD.js → CreatePage-BgDusUKR.js} +1 -1
- package/src/client/dist/pwa/assets/{DiffViewer-DglPAzaZ.js → DiffViewer-CfOhZMXv.js} +3 -3
- package/src/client/dist/pwa/assets/{HealthPage-sS-N-_9T.js → HealthPage-3GHu-I59.js} +1 -1
- package/src/client/dist/pwa/assets/{MainLayout-BoZsUoGd.css → MainLayout-BY-xOjDK.css} +1 -1
- package/src/client/dist/pwa/assets/{MainLayout-BelFb6SW.js → MainLayout-DPLjufY9.js} +2 -2
- package/src/client/dist/pwa/assets/{QBtnToggle-eC2_l1xJ.js → QBtnToggle-CC3TCOK8.js} +1 -1
- package/src/client/dist/pwa/assets/{QChip-BT8qyt_p.js → QChip-CLKcMuqJ.js} +1 -1
- package/src/client/dist/pwa/assets/{QSelect-Bj-wqU82.js → QSelect-DUCHcz_p.js} +1 -1
- package/src/client/dist/pwa/assets/{QTooltip-CCNfQ3sa.js → QTooltip-CUM1JiWx.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-6CSIDDcM.js → SearchPage-f9Bjw2s3.js} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-BmTlGyff.js +16 -0
- package/src/client/dist/pwa/assets/{SettingsPage-CENUP7ua.css → SettingsPage-Os-6b-6H.css} +1 -1
- package/src/client/dist/pwa/assets/{ThinkingItem-kP-lU251.js → ThinkingItem-D9OZZN0H.js} +1 -1
- package/src/client/dist/pwa/assets/{TouchPan-DVggdTB9.js → TouchPan-DvWjWFLZ.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspacePage-D-1HoeQg.css → WorkspacePage-BbJM8PyI.css} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-CtiULAL9.js +4 -0
- package/src/client/dist/pwa/assets/{build-path-tree-FNTYv-td.js → build-path-tree-nLp-H-pQ.js} +1 -1
- package/src/client/dist/pwa/assets/{cssMode-DHVn7kI3.js → cssMode-7vYc6lG_.js} +1 -1
- package/src/client/dist/pwa/assets/{documents-DoEDCa8X.js → documents-CSi0kXxo.js} +1 -1
- package/src/client/dist/pwa/assets/{editor.api2-DO15WOFV.js → editor.api2-BzgKTMyb.js} +1 -1
- package/src/client/dist/pwa/assets/{editor.main-Ce_TPxw1.js → editor.main-DOAM9U2f.js} +2 -2
- package/src/client/dist/pwa/assets/{expand-template-DnNNlpvQ.js → expand-template-CT5wSSEr.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-BBT0Ye5P.js → formatters-66ZLwD6M.js} +1 -1
- package/src/client/dist/pwa/assets/{freemarker2-B5mFQ9MW.js → freemarker2-CgDldzFl.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-TGXWWwrU.js → handlebars-Dt0cB0x6.js} +1 -1
- package/src/client/dist/pwa/assets/{html-BcoyXTgh.js → html-Blz0fA0T.js} +1 -1
- package/src/client/dist/pwa/assets/{htmlMode-Dhuwig6w.js → htmlMode-CgzmQ96i.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-BjRW3rME.js +1 -0
- package/src/client/dist/pwa/assets/index-C0ylRsyw.js +84 -0
- package/src/client/dist/pwa/assets/{javascript-v_uXBoLN.js → javascript-DVKtVeCf.js} +1 -1
- package/src/client/dist/pwa/assets/{jsonMode-CErvRpqG.js → jsonMode-Cfa435aJ.js} +1 -1
- package/src/client/dist/pwa/assets/{kobo-commands-DXJ85QAa.js → kobo-commands-C-rMOa-X.js} +1 -1
- package/src/client/dist/pwa/assets/{layout-Di93C8KP.js → layout-BUYxQPjS.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-ZappZiY7.js → liquid-DU4LoIen.js} +1 -1
- package/src/client/dist/pwa/assets/{lspLanguageFeatures-ur5VVJZY.js → lspLanguageFeatures-DDmy9kyx.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-BKL2jNxT.js → mdx-CVPu9G6b.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-WfNiFCn_.js → monaco.contribution-BCKXe1LM.js} +2 -2
- package/src/client/dist/pwa/assets/{network-auth-O2hz04zH.js → network-auth-BP0-h4VQ.js} +1 -1
- package/src/client/dist/pwa/assets/{pending-diff-open-CysqNMDL.js → pending-diff-open-BAyLIcQ6.js} +1 -1
- package/src/client/dist/pwa/assets/{permissionModes-Crg3_WdF.js → permissionModes-CNIg8DwA.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-DVafgHFe.js → project-color-B38mkxVB.js} +1 -1
- package/src/client/dist/pwa/assets/{python-DXpnDgWC.js → python-WDhDBOmq.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-2pTtVGVn.js → razor-Dv_KM7xr.js} +1 -1
- package/src/client/dist/pwa/assets/{render-chat-markdown-BbmsqP4r.js → render-chat-markdown-DahMoI2g.js} +1 -1
- package/src/client/dist/pwa/assets/{tsMode-t5L5YTTB.js → tsMode-B3UNfmkB.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-C2FUrxc2.js → typescript-CNkrDBQS.js} +1 -1
- package/src/client/dist/pwa/assets/{use-onboarding-CxQRTetV.js → use-onboarding-Bb-Hawzx.js} +1 -1
- package/src/client/dist/pwa/assets/{use-quasar-CUBX9V4F.js → use-quasar-DCRxdLu-.js} +1 -1
- package/src/client/dist/pwa/assets/{workers-ri7So4GA.js → workers-UUdizQoG.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-CvWFj3be.js → xml-zRWWyHRk.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-DsSH3Hc4.js → yaml-Uma2sS3z.js} +1 -1
- package/src/client/dist/pwa/index.html +1 -1
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-jGIZfB7N.js +0 -16
- package/src/client/dist/pwa/assets/WorkspacePage-BWRpD_9V.js +0 -4
- package/src/client/dist/pwa/assets/i18n-BHXcyfLT.js +0 -1
- package/src/client/dist/pwa/assets/index-CGnQPjwo.js +0 -84
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { getGlobalSettings } from '../../settings-service.js';
|
|
4
|
+
import { deriveReadyToMerge } from '../types.js';
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
function record(value) {
|
|
7
|
+
return value !== null && typeof value === 'object' ? value : {};
|
|
8
|
+
}
|
|
9
|
+
function string(value) {
|
|
10
|
+
return typeof value === 'string' ? value : '';
|
|
11
|
+
}
|
|
12
|
+
function number(value) {
|
|
13
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
14
|
+
}
|
|
15
|
+
function branch(raw, cloudKey, dcKey) {
|
|
16
|
+
const cloud = record(record(raw[cloudKey]).branch);
|
|
17
|
+
return string(cloud.name) || string(record(raw[dcKey]).displayId);
|
|
18
|
+
}
|
|
19
|
+
function login(raw) {
|
|
20
|
+
const user = record(raw);
|
|
21
|
+
return string(user.nickname) || string(user.username) || string(user.name) || string(user.display_name);
|
|
22
|
+
}
|
|
23
|
+
function url(raw) {
|
|
24
|
+
const links = record(raw.links);
|
|
25
|
+
const html = record(links.html);
|
|
26
|
+
if (string(html.href))
|
|
27
|
+
return string(html.href);
|
|
28
|
+
const self = links.self;
|
|
29
|
+
if (Array.isArray(self))
|
|
30
|
+
return string(record(self[0]).href);
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
function mapState(value) {
|
|
34
|
+
const state = value.toUpperCase();
|
|
35
|
+
if (state === 'MERGED')
|
|
36
|
+
return 'MERGED';
|
|
37
|
+
if (state === 'DECLINED' || state === 'CLOSED' || state === 'SUPERSEDED')
|
|
38
|
+
return 'CLOSED';
|
|
39
|
+
return 'OPEN';
|
|
40
|
+
}
|
|
41
|
+
function mapReviewers(raw) {
|
|
42
|
+
const result = new Map();
|
|
43
|
+
const add = (candidate, fallback) => {
|
|
44
|
+
const entry = record(candidate);
|
|
45
|
+
const reviewerLogin = login(entry.user) || login(entry);
|
|
46
|
+
if (!reviewerLogin)
|
|
47
|
+
return;
|
|
48
|
+
const rawState = string(entry.status || entry.state).toUpperCase();
|
|
49
|
+
const state = entry.approved === true || rawState === 'APPROVED'
|
|
50
|
+
? 'APPROVED'
|
|
51
|
+
: rawState === 'NEEDS_WORK' || rawState === 'CHANGES_REQUESTED'
|
|
52
|
+
? 'CHANGES_REQUESTED'
|
|
53
|
+
: fallback;
|
|
54
|
+
result.set(reviewerLogin, state);
|
|
55
|
+
};
|
|
56
|
+
for (const reviewer of Array.isArray(raw.reviewers) ? raw.reviewers : [])
|
|
57
|
+
add(reviewer, 'PENDING');
|
|
58
|
+
for (const participant of Array.isArray(raw.participants) ? raw.participants : [])
|
|
59
|
+
add(participant, 'COMMENTED');
|
|
60
|
+
return [...result].map(([reviewerLogin, state]) => ({ login: reviewerLogin, state }));
|
|
61
|
+
}
|
|
62
|
+
function mapCheck(raw) {
|
|
63
|
+
const check = record(raw);
|
|
64
|
+
const state = string(check.state || check.status).toUpperCase();
|
|
65
|
+
const completed = ['SUCCESSFUL', 'SUCCESS', 'FAILED', 'FAILURE', 'CANCELLED', 'CANCELED'].includes(state);
|
|
66
|
+
const conclusion = ['SUCCESSFUL', 'SUCCESS'].includes(state)
|
|
67
|
+
? 'SUCCESS'
|
|
68
|
+
: ['FAILED', 'FAILURE'].includes(state)
|
|
69
|
+
? 'FAILURE'
|
|
70
|
+
: ['CANCELLED', 'CANCELED'].includes(state)
|
|
71
|
+
? 'CANCELLED'
|
|
72
|
+
: null;
|
|
73
|
+
return {
|
|
74
|
+
name: string(check.name) || string(check.key),
|
|
75
|
+
status: completed ? 'COMPLETED' : 'IN_PROGRESS',
|
|
76
|
+
conclusion,
|
|
77
|
+
detailsUrl: string(check.url) || null,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function mapBktPr(raw, ci = { rollup: null, checks: [] }) {
|
|
81
|
+
const state = mapState(string(raw.state));
|
|
82
|
+
const reviewers = mapReviewers(raw);
|
|
83
|
+
const reviewDecision = reviewers.some((reviewer) => reviewer.state === 'CHANGES_REQUESTED')
|
|
84
|
+
? 'CHANGES_REQUESTED'
|
|
85
|
+
: null;
|
|
86
|
+
const mergeable = raw.open === false ? 'CONFLICTING' : null;
|
|
87
|
+
return {
|
|
88
|
+
number: number(raw.id),
|
|
89
|
+
title: string(raw.title),
|
|
90
|
+
url: url(raw),
|
|
91
|
+
state,
|
|
92
|
+
base: branch(raw, 'destination', 'toRef'),
|
|
93
|
+
reviewDecision,
|
|
94
|
+
author: { login: login(raw.author) },
|
|
95
|
+
assignees: [],
|
|
96
|
+
reviewers,
|
|
97
|
+
labels: [],
|
|
98
|
+
ci,
|
|
99
|
+
updatedAt: string(raw.updated_on) || string(raw.updatedAt) || String(raw.updatedDate ?? ''),
|
|
100
|
+
unresolvedReviewThreadsCount: 0,
|
|
101
|
+
mergeable,
|
|
102
|
+
readyToMerge: deriveReadyToMerge({ state, ci, reviewDecision, reviewers, mergeable }),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function availabilityFromError(err) {
|
|
106
|
+
const code = err.code;
|
|
107
|
+
if (code === 'ENOENT')
|
|
108
|
+
return { available: false, reason: 'cli_missing' };
|
|
109
|
+
const message = err.message?.toLowerCase() ?? '';
|
|
110
|
+
if (message.includes('no active context') ||
|
|
111
|
+
message.includes('not authenticated') ||
|
|
112
|
+
message.includes('auth login')) {
|
|
113
|
+
return { available: false, reason: 'not_authenticated' };
|
|
114
|
+
}
|
|
115
|
+
return { available: false };
|
|
116
|
+
}
|
|
117
|
+
function parseBitbucketRemote(remote) {
|
|
118
|
+
const match = remote.trim().match(/bitbucket(?:\.org)?[/:]([^/\s:]+)\/([^/\s]+?)(?:\.git)?\/?$/i);
|
|
119
|
+
if (!match)
|
|
120
|
+
return null;
|
|
121
|
+
return { workspace: match[1], repo: match[2] };
|
|
122
|
+
}
|
|
123
|
+
function contextName(workspace, repo) {
|
|
124
|
+
return `kobo-${workspace}-${repo}`.toLowerCase().replace(/[^a-z0-9._-]/g, '-');
|
|
125
|
+
}
|
|
126
|
+
function bktArgs(target, args) {
|
|
127
|
+
return target.context ? ['--context', target.context, ...args] : args;
|
|
128
|
+
}
|
|
129
|
+
function bktEnv() {
|
|
130
|
+
try {
|
|
131
|
+
const settings = getGlobalSettings();
|
|
132
|
+
if (settings.bitbucketToken) {
|
|
133
|
+
return {
|
|
134
|
+
...process.env,
|
|
135
|
+
BKT_HOST: 'https://bitbucket.org',
|
|
136
|
+
BKT_TOKEN: settings.bitbucketToken,
|
|
137
|
+
BKT_USERNAME: settings.bitbucketUsername || process.env.BKT_USERNAME,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Settings are deliberately uninitialized in isolated forge tests.
|
|
143
|
+
}
|
|
144
|
+
return process.env;
|
|
145
|
+
}
|
|
146
|
+
function bktOptions(repoPath) {
|
|
147
|
+
return { cwd: repoPath, encoding: 'utf-8', env: bktEnv() };
|
|
148
|
+
}
|
|
149
|
+
async function resolveTarget(repoPath) {
|
|
150
|
+
const { stdout } = await execFileAsync('git', ['remote', 'get-url', 'origin'], {
|
|
151
|
+
cwd: repoPath,
|
|
152
|
+
encoding: 'utf-8',
|
|
153
|
+
});
|
|
154
|
+
const target = parseBitbucketRemote(stdout);
|
|
155
|
+
if (!target)
|
|
156
|
+
throw new Error('Could not determine Bitbucket workspace and repository from the origin remote');
|
|
157
|
+
// BKT_TOKEN is the CLI's documented headless mode. It deliberately avoids
|
|
158
|
+
// keyring and context access, which cannot prompt from the Kobo server.
|
|
159
|
+
if (bktEnv().BKT_TOKEN)
|
|
160
|
+
return { ...target, context: null };
|
|
161
|
+
const { stdout: contextOutput } = await execFileAsync('bkt', ['context', 'list', '--json'], {
|
|
162
|
+
cwd: repoPath,
|
|
163
|
+
encoding: 'utf-8',
|
|
164
|
+
});
|
|
165
|
+
const contexts = record(JSON.parse(contextOutput)).contexts;
|
|
166
|
+
const existing = (Array.isArray(contexts) ? contexts : [])
|
|
167
|
+
.map(record)
|
|
168
|
+
.find((context) => string(context.workspace) === target.workspace && string(context.default_repo) === target.repo);
|
|
169
|
+
if (existing && string(existing.name))
|
|
170
|
+
return { ...target, context: string(existing.name) };
|
|
171
|
+
const { stdout: authOutput } = await execFileAsync('bkt', ['auth', 'status', '--json'], {
|
|
172
|
+
cwd: repoPath,
|
|
173
|
+
encoding: 'utf-8',
|
|
174
|
+
});
|
|
175
|
+
const hosts = record(JSON.parse(authOutput)).hosts;
|
|
176
|
+
const host = (Array.isArray(hosts) ? hosts : []).map(record).find((item) => string(item.kind) === 'cloud');
|
|
177
|
+
const hostKey = string(host?.key);
|
|
178
|
+
if (!hostKey)
|
|
179
|
+
throw new Error('No authenticated Bitbucket Cloud host found; run bkt auth login first');
|
|
180
|
+
const context = contextName(target.workspace, target.repo);
|
|
181
|
+
await execFileAsync('bkt', ['context', 'create', context, '--host', hostKey, '--workspace', target.workspace, '--repo', target.repo], { cwd: repoPath, encoding: 'utf-8' });
|
|
182
|
+
return { ...target, context };
|
|
183
|
+
}
|
|
184
|
+
function targetArgs(target) {
|
|
185
|
+
return ['--workspace', target.workspace, '--repo', target.repo];
|
|
186
|
+
}
|
|
187
|
+
async function fetchCi(repoPath, number, target) {
|
|
188
|
+
try {
|
|
189
|
+
const { stdout } = await execFileAsync('bkt', bktArgs(target, ['pr', 'checks', String(number), ...targetArgs(target), '--json']), bktOptions(repoPath));
|
|
190
|
+
const statuses = record(JSON.parse(stdout)).statuses;
|
|
191
|
+
const checks = (Array.isArray(statuses) ? statuses : []).map(mapCheck);
|
|
192
|
+
const rollup = checks.length === 0
|
|
193
|
+
? null
|
|
194
|
+
: checks.some((check) => check.conclusion === 'FAILURE')
|
|
195
|
+
? 'FAILURE'
|
|
196
|
+
: checks.some((check) => check.conclusion === 'CANCELLED')
|
|
197
|
+
? 'CANCELLED'
|
|
198
|
+
: checks.some((check) => check.status !== 'COMPLETED')
|
|
199
|
+
? 'PENDING'
|
|
200
|
+
: 'SUCCESS';
|
|
201
|
+
return { rollup, checks };
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return { rollup: null, checks: [] };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
export const bitbucketProvider = {
|
|
208
|
+
id: 'bitbucket-community',
|
|
209
|
+
capabilities: {
|
|
210
|
+
canCreatePr: true,
|
|
211
|
+
// bkt 0.30 does not expose a target-branch edit command.
|
|
212
|
+
canChangePrBase: false,
|
|
213
|
+
canMergeRequest: true,
|
|
214
|
+
canDeleteRemoteBranch: true,
|
|
215
|
+
requestTermShort: 'PR',
|
|
216
|
+
},
|
|
217
|
+
async isAvailable(repoPath) {
|
|
218
|
+
try {
|
|
219
|
+
const { stdout } = await execFileAsync('bkt', ['auth', 'status', '--json'], bktOptions(repoPath));
|
|
220
|
+
const status = record(JSON.parse(stdout));
|
|
221
|
+
const hosts = status.hosts;
|
|
222
|
+
if ((Array.isArray(hosts) && hosts.length > 0) || bktEnv().BKT_TOKEN)
|
|
223
|
+
return { available: true };
|
|
224
|
+
return { available: false, reason: 'not_authenticated' };
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
return availabilityFromError(err);
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
async getPrStatus(repoPath, branchName) {
|
|
231
|
+
try {
|
|
232
|
+
const target = await resolveTarget(repoPath);
|
|
233
|
+
let match;
|
|
234
|
+
// The post-merge workflow asks for the final state before offering remote
|
|
235
|
+
// branch deletion, so do not stop at OPEN requests.
|
|
236
|
+
for (const state of ['OPEN', 'MERGED', 'DECLINED']) {
|
|
237
|
+
const { stdout: listOutput } = await execFileAsync('bkt', bktArgs(target, ['pr', 'list', '--state', state, '--limit', '50', ...targetArgs(target), '--json']), bktOptions(repoPath));
|
|
238
|
+
const pullRequests = record(JSON.parse(listOutput)).pull_requests;
|
|
239
|
+
match = (Array.isArray(pullRequests) ? pullRequests : [])
|
|
240
|
+
.map(record)
|
|
241
|
+
.find((pullRequest) => branch(pullRequest, 'source', 'fromRef') === branchName);
|
|
242
|
+
if (match)
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
if (!match)
|
|
246
|
+
return null;
|
|
247
|
+
const { stdout: viewOutput } = await execFileAsync('bkt', bktArgs(target, ['pr', 'view', String(number(match.id)), ...targetArgs(target), '--json']), bktOptions(repoPath));
|
|
248
|
+
const raw = record(record(JSON.parse(viewOutput)).pull_request);
|
|
249
|
+
if (!number(raw.id))
|
|
250
|
+
return null;
|
|
251
|
+
const ci = await fetchCi(repoPath, number(raw.id), target);
|
|
252
|
+
return mapBktPr(raw, ci);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
async createPr(repoPath, opts) {
|
|
259
|
+
const target = await resolveTarget(repoPath);
|
|
260
|
+
const { stdout } = await execFileAsync('bkt', bktArgs(target, [
|
|
261
|
+
'pr',
|
|
262
|
+
'create',
|
|
263
|
+
'--source',
|
|
264
|
+
opts.head,
|
|
265
|
+
'--target',
|
|
266
|
+
opts.base,
|
|
267
|
+
'--title',
|
|
268
|
+
opts.title,
|
|
269
|
+
'--description',
|
|
270
|
+
opts.body,
|
|
271
|
+
...targetArgs(target),
|
|
272
|
+
'--json',
|
|
273
|
+
]), bktOptions(repoPath));
|
|
274
|
+
const raw = record(JSON.parse(stdout));
|
|
275
|
+
const prNumber = number(raw.id);
|
|
276
|
+
const prUrl = string(raw.url);
|
|
277
|
+
if (!prNumber || !prUrl)
|
|
278
|
+
throw new Error('Could not parse PR URL from bkt output');
|
|
279
|
+
return { url: prUrl, number: prNumber };
|
|
280
|
+
},
|
|
281
|
+
async changePrBase() {
|
|
282
|
+
throw new Error('Changing a Bitbucket PR base branch is not supported by the installed bkt CLI');
|
|
283
|
+
},
|
|
284
|
+
async mergeRequest(repoPath, number) {
|
|
285
|
+
const target = await resolveTarget(repoPath);
|
|
286
|
+
await execFileAsync('bkt', bktArgs(target, ['pr', 'merge', String(number), '--close-source=false', ...targetArgs(target)]), bktOptions(repoPath));
|
|
287
|
+
},
|
|
288
|
+
async deleteRemoteBranch(repoPath, branchName) {
|
|
289
|
+
await execFileAsync('git', ['push', 'origin', '--delete', branchName], { cwd: repoPath, encoding: 'utf-8' });
|
|
290
|
+
},
|
|
291
|
+
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// src/server/services/forge/registry.ts
|
|
2
|
+
import { bitbucketProvider } from './bitbucket/provider.js';
|
|
2
3
|
import { githubProvider } from './github/provider.js';
|
|
3
4
|
import { gitlabProvider } from './gitlab/provider.js';
|
|
4
5
|
import { noneProvider } from './none.js';
|
|
5
6
|
const PROVIDERS = {
|
|
6
7
|
github: githubProvider,
|
|
7
8
|
gitlab: gitlabProvider,
|
|
9
|
+
'bitbucket-community': bitbucketProvider,
|
|
8
10
|
none: noneProvider,
|
|
9
11
|
};
|
|
10
12
|
/** Resolve a provider by id. Unknown ids fall back to the none provider. */
|
|
@@ -13,5 +15,5 @@ export function getForgeProvider(id) {
|
|
|
13
15
|
}
|
|
14
16
|
/** The selectable forge ids, in display order. */
|
|
15
17
|
export function listForges() {
|
|
16
|
-
return ['github', 'gitlab', 'none'];
|
|
18
|
+
return ['github', 'gitlab', 'bitbucket-community', 'none'];
|
|
17
19
|
}
|
|
@@ -8,6 +8,8 @@ export function forgeFromRemoteUrl(url) {
|
|
|
8
8
|
return 'github';
|
|
9
9
|
if (lower.includes('gitlab'))
|
|
10
10
|
return 'gitlab';
|
|
11
|
+
if (lower.includes('bitbucket'))
|
|
12
|
+
return 'bitbucket-community';
|
|
11
13
|
return 'none';
|
|
12
14
|
}
|
|
13
15
|
/** Read the `origin` remote URL, or '' when there is no remote. */
|
|
@@ -28,7 +30,8 @@ function readRemoteUrl(projectPath) {
|
|
|
28
30
|
*/
|
|
29
31
|
export function resolveForge(projectPath) {
|
|
30
32
|
const setting = getProjectSettings(projectPath)?.forge ?? 'auto';
|
|
31
|
-
if (setting === 'github' || setting === 'gitlab' || setting === 'none')
|
|
33
|
+
if (setting === 'github' || setting === 'gitlab' || setting === 'bitbucket-community' || setting === 'none') {
|
|
32
34
|
return setting;
|
|
35
|
+
}
|
|
33
36
|
return forgeFromRemoteUrl(readRemoteUrl(projectPath));
|
|
34
37
|
}
|
|
@@ -18,7 +18,7 @@ export const DEFAULT_CHANGE_SOURCE_BRANCH_SCRIPT = `#!/usr/bin/env bash
|
|
|
18
18
|
# KOBO_PROJECT_NAME project directory name (basename of KOBO_PROJECT_PATH)
|
|
19
19
|
# KOBO_WORKSPACE_ID Kōbō workspace id (stable across renames)
|
|
20
20
|
# KOBO_WORKSPACE_NAME workspace display name
|
|
21
|
-
# KOBO_FORGE resolved forge: github / gitlab / none
|
|
21
|
+
# KOBO_FORGE resolved forge: github / gitlab / bitbucket-community / none
|
|
22
22
|
# KOBO_PR_NUMBER PR/MR number on the resolved forge (empty if none open)
|
|
23
23
|
set -euo pipefail
|
|
24
24
|
|
|
@@ -793,6 +793,16 @@ const settingsMigrations = [
|
|
|
793
793
|
}
|
|
794
794
|
},
|
|
795
795
|
},
|
|
796
|
+
{
|
|
797
|
+
version: 50,
|
|
798
|
+
name: 'add-bitbucket-credentials',
|
|
799
|
+
migrate: ({ global }) => {
|
|
800
|
+
if (typeof global.bitbucketToken !== 'string')
|
|
801
|
+
global.bitbucketToken = '';
|
|
802
|
+
if (typeof global.bitbucketUsername !== 'string')
|
|
803
|
+
global.bitbucketUsername = '';
|
|
804
|
+
},
|
|
805
|
+
},
|
|
796
806
|
];
|
|
797
807
|
/** Current settings schema version — always equals the highest migration version. */
|
|
798
808
|
export const SETTINGS_SCHEMA_VERSION = settingsMigrations.length > 0 ? settingsMigrations[settingsMigrations.length - 1].version : 0;
|
|
@@ -843,6 +853,8 @@ function defaultSettings() {
|
|
|
843
853
|
networkAccessEnabled: false,
|
|
844
854
|
networkAccessToken: '',
|
|
845
855
|
networkAccessBehindProxy: false,
|
|
856
|
+
bitbucketToken: '',
|
|
857
|
+
bitbucketUsername: '',
|
|
846
858
|
browserNotifications: true,
|
|
847
859
|
audioNotifications: true,
|
|
848
860
|
audioQuestionNotifications: false,
|
|
@@ -1068,7 +1080,7 @@ export function getSettings() {
|
|
|
1068
1080
|
return readSettings();
|
|
1069
1081
|
}
|
|
1070
1082
|
/** Keys stripped from exports — secrets that should stay on the machine. */
|
|
1071
|
-
const SECRET_GLOBAL_KEYS = ['notionMcpKey', 'sentryMcpKey', 'networkAccessToken'];
|
|
1083
|
+
const SECRET_GLOBAL_KEYS = ['notionMcpKey', 'sentryMcpKey', 'networkAccessToken', 'bitbucketToken'];
|
|
1072
1084
|
/** Build an export bundle with settings + templates. MCP keys are stripped. */
|
|
1073
1085
|
export function exportConfigBundle(templates) {
|
|
1074
1086
|
const settings = readSettings();
|
|
@@ -1282,6 +1294,8 @@ export function updateGlobalSettings(data) {
|
|
|
1282
1294
|
'defaultPermissionModeByEngine',
|
|
1283
1295
|
'notionMcpKey',
|
|
1284
1296
|
'sentryMcpKey',
|
|
1297
|
+
'bitbucketToken',
|
|
1298
|
+
'bitbucketUsername',
|
|
1285
1299
|
'notionEnabled',
|
|
1286
1300
|
'sentryEnabled',
|
|
1287
1301
|
'showThinkingBlocks',
|
|
@@ -209,6 +209,22 @@ export function updateWorkspaceReasoningEffort(id, reasoningEffort) {
|
|
|
209
209
|
}
|
|
210
210
|
return getWorkspace(id);
|
|
211
211
|
}
|
|
212
|
+
/** Atomically change the engine configuration used by future workspace turns. */
|
|
213
|
+
export function updateWorkspaceEngineConfiguration(id, engine, model, reasoningEffort, agentPermissionMode) {
|
|
214
|
+
const db = getDb();
|
|
215
|
+
const now = new Date().toISOString();
|
|
216
|
+
const legacyMode = agentPermissionMode === 'plan' ? 'plan' : 'auto-accept';
|
|
217
|
+
const legacyProfile = agentPermissionMode === 'plan' ? 'bypass' : agentPermissionMode;
|
|
218
|
+
const result = db
|
|
219
|
+
.prepare(`UPDATE workspaces
|
|
220
|
+
SET engine = ?, model = ?, reasoning_effort = ?, agent_permission_mode = ?,
|
|
221
|
+
permission_mode = ?, permission_profile = ?, updated_at = ?
|
|
222
|
+
WHERE id = ?`)
|
|
223
|
+
.run(engine, model, reasoningEffort, agentPermissionMode, legacyMode, legacyProfile, now, id);
|
|
224
|
+
if (result.changes === 0)
|
|
225
|
+
throw new Error(`Workspace '${id}' not found`);
|
|
226
|
+
return getWorkspace(id);
|
|
227
|
+
}
|
|
212
228
|
/**
|
|
213
229
|
* Update the working branch for a workspace. Used both after ticket-ID
|
|
214
230
|
* injection at creation time and after the rename-branch / resync-branch
|
|
@@ -649,6 +665,7 @@ function mapSession(row) {
|
|
|
649
665
|
workspaceId: row.workspace_id,
|
|
650
666
|
pid: row.pid,
|
|
651
667
|
engineSessionId: row.engine_session_id,
|
|
668
|
+
engine: row.engine,
|
|
652
669
|
status: row.status,
|
|
653
670
|
model: row.model,
|
|
654
671
|
startedAt: row.started_at,
|
|
@@ -695,16 +712,20 @@ export function getActiveSession(workspaceId) {
|
|
|
695
712
|
/** Create an idle agent session (no Claude process yet) for a workspace. */
|
|
696
713
|
export function createIdleSession(workspaceId) {
|
|
697
714
|
const db = getDb();
|
|
715
|
+
const workspace = getWorkspace(workspaceId);
|
|
716
|
+
if (!workspace)
|
|
717
|
+
throw new Error(`Workspace '${workspaceId}' not found`);
|
|
698
718
|
const id = nanoid();
|
|
699
719
|
const now = new Date().toISOString();
|
|
700
|
-
db.prepare('INSERT INTO agent_sessions (id, workspace_id, pid, status, started_at) VALUES (?, ?, NULL, ?, ?)').run(id, workspaceId, 'idle', now);
|
|
720
|
+
db.prepare('INSERT INTO agent_sessions (id, workspace_id, pid, engine, status, model, started_at) VALUES (?, ?, NULL, ?, ?, ?, ?)').run(id, workspaceId, workspace.engine, 'idle', workspace.model, now);
|
|
701
721
|
return {
|
|
702
722
|
id,
|
|
703
723
|
workspaceId,
|
|
704
724
|
pid: null,
|
|
705
725
|
engineSessionId: null,
|
|
726
|
+
engine: workspace.engine,
|
|
706
727
|
status: 'idle',
|
|
707
|
-
model:
|
|
728
|
+
model: workspace.model,
|
|
708
729
|
startedAt: now,
|
|
709
730
|
endedAt: null,
|
|
710
731
|
name: null,
|
|
@@ -721,3 +742,30 @@ export function renameSession(sessionId, workspaceId, name) {
|
|
|
721
742
|
const row = db.prepare('SELECT * FROM agent_sessions WHERE id = ?').get(sessionId);
|
|
722
743
|
return row ? mapSession(row) : null;
|
|
723
744
|
}
|
|
745
|
+
/** Delete a stopped session and only the persisted conversation attached to it. */
|
|
746
|
+
export function deleteSession(sessionId, workspaceId) {
|
|
747
|
+
const db = getDb();
|
|
748
|
+
const session = db
|
|
749
|
+
.prepare('SELECT status, engine FROM agent_sessions WHERE id = ? AND workspace_id = ?')
|
|
750
|
+
.get(sessionId, workspaceId);
|
|
751
|
+
if (!session)
|
|
752
|
+
return false;
|
|
753
|
+
if (session.status === 'running') {
|
|
754
|
+
throw new Error(`Cannot delete active session '${sessionId}'`);
|
|
755
|
+
}
|
|
756
|
+
const transaction = db.transaction(() => {
|
|
757
|
+
db.prepare('DELETE FROM ws_events WHERE workspace_id = ? AND session_id = ?').run(workspaceId, sessionId);
|
|
758
|
+
db.prepare('DELETE FROM agent_sessions WHERE id = ? AND workspace_id = ?').run(sessionId, workspaceId);
|
|
759
|
+
const workspace = getWorkspace(workspaceId);
|
|
760
|
+
if (workspace?.engine === session.engine) {
|
|
761
|
+
const fallback = db
|
|
762
|
+
.prepare('SELECT engine, model FROM agent_sessions WHERE workspace_id = ? AND engine IS NOT NULL ORDER BY started_at DESC LIMIT 1')
|
|
763
|
+
.get(workspaceId);
|
|
764
|
+
if (fallback) {
|
|
765
|
+
db.prepare('UPDATE workspaces SET engine = ?, model = COALESCE(?, model), updated_at = ? WHERE id = ?').run(fallback.engine, fallback.model, new Date().toISOString(), workspaceId);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
transaction();
|
|
770
|
+
return true;
|
|
771
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loicngr/kobo",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.4",
|
|
4
4
|
"description": "Kōbō — multi-workspace agent manager for Claude Code. Orchestrates isolated git worktrees with dev servers, Notion integration, and MCP tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{$ as e,B as t,F as n,I as r,R as i,S as a,U as o,W as s,_ as c,at as l,b as u,c as d,ct as f,g as p,h as m,it as h,k as g,m as _,n as v,p as y,s as b,st as x,y as S}from"./vue.runtime.esm-bundler-JZnIeD9D.js";import{l as C,t as w}from"./QIcon-C6E-oEjI.js";import{G as T,K as E,d as D,k as O,p as k,v as A,y as j,z as ee}from"./index-
|
|
1
|
+
import{$ as e,B as t,F as n,I as r,R as i,S as a,U as o,W as s,_ as c,at as l,b as u,c as d,ct as f,g as p,h as m,it as h,k as g,m as _,n as v,p as y,s as b,st as x,y as S}from"./vue.runtime.esm-bundler-JZnIeD9D.js";import{l as C,t as w}from"./QIcon-C6E-oEjI.js";import{G as T,K as E,d as D,k as O,p as k,v as A,y as j,z as ee}from"./index-C0ylRsyw.js";import{t as M}from"./QSpinnerDots-DIV68-cV.js";import{t as N}from"./QTooltip-CUM1JiWx.js";import{a as te}from"./permissionModes-CNIg8DwA.js";import{r as P,t as ne}from"./pending-diff-open-BAyLIcQ6.js";import{t as F}from"./documents-CSi0kXxo.js";import{i as I,n as re,t as L}from"./render-chat-markdown-DahMoI2g.js";import{t as R}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as z}from"./ThinkingItem-D9OZZN0H.js";function ie(e,t,n=!0,r){let i=[],a=new Map,o=new Map;for(let n=0;n<e.length;n++){let s=e[n],c=t?.[n],l=r?.[n]??void 0;switch(s.kind){case`message:text`:{let e=a.get(s.messageId);if(e)e.text+=s.text,e.streaming=s.streaming,l&&e.eventIds?.push(l);else{let e={type:`text`,messageId:s.messageId,text:s.text,streaming:s.streaming,ts:c,eventIds:l?[l]:void 0};a.set(s.messageId,e),i.push(e)}break}case`message:end`:{let e=a.get(s.messageId);e&&(e.streaming=!1);break}case`message:thinking`:i.push({type:`thinking`,messageId:s.messageId,text:s.text,ts:c,eventIds:l?[l]:void 0});break;case`tool:call`:{let e={type:`tool`,toolCallId:s.toolCallId,name:s.name,input:s.input,ts:c,eventIds:l?[l]:void 0};o.set(s.toolCallId,e),i.push(e);break}case`tool:result`:{let e=o.get(s.toolCallId);e&&(e.result={output:s.output,isError:s.isError},l&&e.eventIds?.push(l));break}case`session:started`:i.push({type:`session`,kind:`started`,detail:{engineSessionId:s.engineSessionId,model:s.model},ts:c,eventIds:l?[l]:void 0});break;case`session:ended`:i.push({type:`session`,kind:`ended`,detail:{reason:s.reason,exitCode:s.exitCode},ts:c,eventIds:l?[l]:void 0});break;case`session:compacted`:i.push({type:`session`,kind:`compacted`,ts:c,eventIds:l?[l]:void 0});break;case`session:compacting`:case`session:brainstorm-complete`:case`session:user-input-requested`:case`mcp:status`:case`message:raw`:case`skills:discovered`:case`usage`:case`rate_limit`:case`subagent:progress`:case`error`:break;default:}}let s=null;for(let e of i)e.type===`text`&&e.streaming&&(s&&(s.streaming=!1),s=e);return s&&!n&&(s.streaming=!1),i}function ae(e,t){if(t.length===0)return e;let n=t.map(e=>({type:`user`,content:e.content,sender:e.sender,ts:e.ts,eventIds:e.eventIds})),r=[...e,...n];r.sort((e,t)=>{let n=e.ts??``,r=t.ts??``;return n===r?0:n?r?n<r?-1:1:-1:1});let i;for(let e of r)e.type===`user`&&e.sender!==`system-prompt`&&e.ts&&(!i||e.ts>i)&&(i=e.ts);if(i)for(let e of r)e.type===`text`&&e.streaming&&(!e.ts||e.ts<i)&&(e.streaming=!1);return r}var B=new Set([`setup`,`cleanup`,`archive`]);function V(e){switch(e.type){case`user`:return e.sender===`system-prompt`?`system-prompt`:B.has(e.sender)?`script`:`user`;case`session`:return`session`;default:return`agent`}}function oe(e){let t=[],n=null,r=null;for(let i of e){let e=V(i),a=e===`session`||e===`system-prompt`,o=e===`script`&&i.type===`user`?`script:${i.sender}`:e;!n||r!==o||a?(n={speaker:e,ts:i.ts,items:[i]},r=o,t.push(n),a&&(n=null)):n.items.push(i)}return t}var H={class:`text-caption text-grey-6`},se=a({__name:`SessionEventItem`,props:{item:{}},setup(e){let t=e,n=y(()=>{switch(t.item.kind){case`started`:return`session.started`;case`ended`:return`session.ended`;case`compacted`:return`session.compacted`;default:return`session.started`}});return(e,t)=>(i(),c(`span`,H,f(e.$t(n.value)),1))}});function ce(e,t){if(t.length===0||e.length===0)return e;let n=[...t].sort((e,t)=>t.length-e.length),r=new DOMParser().parseFromString(`<div>${e}</div>`,`text/html`),i=r.body.firstChild;if(!i)return e;function a(e){if(e.nodeType===Node.TEXT_NODE){le(e,n,r);return}if(e.nodeName===`A`)return;let t=Array.from(e.childNodes);for(let e of t)a(e)}return a(i),i.innerHTML}function le(e,t,n){let r=e.textContent??``;if(!t.some(e=>r.includes(e)))return;let i=n.createDocumentFragment(),a=0;for(;a<r.length;){let e=ue(r,a,t);if(!e){i.appendChild(n.createTextNode(r.slice(a)));break}e.index>a&&i.appendChild(n.createTextNode(r.slice(a,e.index)));let o=n.createElement(`a`);o.className=`document-link`,o.setAttribute(`data-document-path`,e.path),o.setAttribute(`href`,`#`),o.textContent=e.path,i.appendChild(o),a=e.index+e.path.length}e.parentNode?.replaceChild(i,e)}function ue(e,t,n){let r=null;for(let i of n){let n=e.indexOf(i,t);n<0||(!r||n<r.index||n===r.index&&i.length>r.path.length)&&(r={index:n,path:i})}return r}var U=[`innerHTML`],de=R(a({__name:`TextMessageItem`,props:{item:{}},setup(e){let t=e,n=F(),r=D(),a=y(()=>{let e=r.selectedWorkspaceId;return e?n.documentsFor(e).map(e=>e.path):[]}),o=y(()=>re(ce(I.parse(t.item.text,{async:!1,breaks:!0,gfm:!0}),a.value),{addAttr:[`data-document-path`]}));function s(e){let t=e.target?.closest(`.document-link`);if(!t)return;e.preventDefault();let i=t.getAttribute(`data-document-path`),a=r.selectedWorkspaceId;!i||!a||n.openDocumentByPath(a,i)}return(t,n)=>(i(),c(`div`,{class:`markdown-message`,onClick:s},[_(`div`,{innerHTML:o.value},null,8,U),e.item.streaming?(i(),m(C,{key:0,size:`xs`,class:`q-ml-xs`})):p(``,!0)]))}}),[[`__scopeId`,`data-v-1b7bd8ca`]]);function fe(e,t){let n=e.split(`
|
|
2
2
|
`),r=t.split(`
|
|
3
3
|
`),i=n.length,a=r.length,o=Array.from({length:i+1},()=>Array(a+1).fill(0));for(let e=i-1;e>=0;e--)for(let t=a-1;t>=0;t--)n[e]===r[t]?o[e][t]=o[e+1][t+1]+1:o[e][t]=Math.max(o[e+1][t],o[e][t+1]);let s=[],c=0,l=0;for(;c<i&&l<a;)n[c]===r[l]?(s.push({type:`context`,content:n[c]}),c++,l++):o[c+1][l]>=o[c][l+1]?(s.push({type:`del`,content:n[c]}),c++):(s.push({type:`add`,content:r[l]}),l++);for(;c<i;)s.push({type:`del`,content:n[c++]});for(;l<a;)s.push({type:`add`,content:r[l++]});return s}function W(e){let t=e.split(`
|
|
4
4
|
`),n=[];for(let e of t)e.startsWith(`@@`)||e.startsWith(`+++`)||e.startsWith(`---`)||(e.startsWith(`+`)?n.push({type:`add`,content:e.slice(1)}):e.startsWith(`-`)?n.push({type:`del`,content:e.slice(1)}):e.startsWith(` `)?n.push({type:`context`,content:e.slice(1)}):e.length>0&&n.push({type:`context`,content:e}));return n}function G(e,t){if(!t||typeof t!=`object`)return null;let n=t;if(e===`Edit`){let e=n.file_path;if(!e)return null;let t=n.old_string??``,r=n.new_string??``,i=typeof n.diff==`string`?n.diff:``;if(!t&&!r&&i.length>0){let t=W(i);return{toolName:`Edit`,filePath:e,additions:t.filter(e=>e.type===`add`).length,deletions:t.filter(e=>e.type===`del`).length,diffLines:t}}return{toolName:`Edit`,filePath:e,oldString:t,newString:r,replaceAll:n.replace_all??!1,additions:r?r.split(`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{$ as e,B as t,F as n,R as r,S as i,W as a,_ as o,b as s,c,ct as l,g as u,h as d,it as f,m as p}from"./vue.runtime.esm-bundler-JZnIeD9D.js";import{St as m,bt as h,s as g,z as _}from"./index-
|
|
1
|
+
import{$ as e,B as t,F as n,R as r,S as i,W as a,_ as o,b as s,c,ct as l,g as u,h as d,it as f,m as p}from"./vue.runtime.esm-bundler-JZnIeD9D.js";import{St as m,bt as h,s as g,z as _}from"./index-C0ylRsyw.js";import{t as v}from"./render-chat-markdown-DahMoI2g.js";import{t as y}from"./_plugin-vue_export-helper-BDNMzG2s.js";import{t as b}from"./QSpace-ezNrZkuj.js";import{t as x}from"./QChip-CLKcMuqJ.js";import{t as S}from"./QPage-D3rjwA1l.js";var C={class:`row items-center q-mb-md`},w={class:`text-h6 q-ml-sm`},T={key:0,class:`text-grey-6 text-center q-pa-lg`},E={key:1,class:`text-negative text-center q-pa-lg`},D={key:2,class:`text-grey-6 text-center q-pa-lg`},O={key:3,class:`column q-gutter-md`},k={key:0,class:`text-caption text-grey-6`},A={class:`row items-center q-mb-sm`},j={class:`text-subtitle1 text-indigo-3`,style:{"font-family":`var(--kobo-font-mono, monospace)`}},M=[`innerHTML`],N=y(i({__name:`ChangelogPage`,setup(i){let y=g(),N=e([]),P=e(``),F=e(!1),I=e(null);function L(e){return v(e)}async function R(){F.value=!0,I.value=null;try{let e=await fetch(`/api/changelog`);if(!e.ok)throw Error(`HTTP ${e.status}`);let t=await e.json();P.value=t.currentVersion??``,N.value=t.versions??[]}catch(e){I.value=e instanceof Error?e.message:String(e)}finally{F.value=!1}}return n(R),(e,n)=>(r(),d(S,{class:`q-pa-md`,style:{"max-width":`900px`,margin:`0 auto`}},{default:a(()=>[p(`div`,C,[s(_,{flat:``,dense:``,round:``,icon:`arrow_back`,onClick:n[0]||=e=>f(y).back()}),p(`div`,w,l(e.$t(`changelog.title`)),1),s(b),s(_,{flat:``,dense:``,icon:`refresh`,loading:F.value,label:e.$t(`common.refresh`),onClick:R},null,8,[`loading`,`label`])]),F.value&&N.value.length===0?(r(),o(`div`,T,l(e.$t(`common.loading`)),1)):I.value?(r(),o(`div`,E,l(I.value),1)):N.value.length===0?(r(),o(`div`,D,l(e.$t(`changelog.empty`)),1)):(r(),o(`div`,O,[P.value?(r(),o(`div`,k,l(e.$t(`changelog.currentVersion`,{version:P.value})),1)):u(``,!0),(r(!0),o(c,null,t(N.value,t=>(r(),d(h,{key:t.version,dark:``,flat:``,bordered:``},{default:a(()=>[s(m,null,{default:a(()=>[p(`div`,A,[p(`div`,j,` v`+l(t.version),1),t.version===P.value?(r(),d(x,{key:0,dense:``,size:`sm`,color:`indigo-7`,"text-color":`grey-2`,label:e.$t(`changelog.current`),class:`q-ml-sm`},null,8,[`label`])):u(``,!0)]),p(`div`,{class:`changelog-notes`,innerHTML:L(t.notes)},null,8,M)]),_:2},1024)]),_:2},1024))),128))]))]),_:1}))}}),[[`__scopeId`,`data-v-ed73d661`]]);export{N as default};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{P as e,b as t}from"./QIcon-C6E-oEjI.js";import{ct as n,lt as r}from"./index-
|
|
1
|
+
import{P as e,b as t}from"./QIcon-C6E-oEjI.js";import{ct as n,lt as r}from"./index-C0ylRsyw.js";var i={left:!0,right:!0,up:!0,down:!0,horizontal:!0,vertical:!0},a=Object.keys(i);i.all=!0;function o(e){let t={};for(let n of a)e[n]&&(t[n]=!0);return Object.keys(t).length===0?i:(t.horizontal?t.left=t.right=!0:t.left&&t.right&&(t.horizontal=!0),t.vertical?t.up=t.down=!0:t.up&&t.down&&(t.vertical=!0),t.horizontal&&t.vertical&&(t.all=!0),t)}var s=[`INPUT`,`TEXTAREA`];function c(e,t){return t.event===void 0&&e.target!==void 0&&!e.target.draggable&&typeof t.handler==`function`&&!s.includes(e.target.nodeName.toUpperCase())&&(e.qClonedBy===void 0||!e.qClonedBy.includes(t.uid))}function l(e){return e===!1?0:e===!0||e===void 0?1:Number.parseInt(e,10)||0}var u=e({name:`close-popup`,beforeMount(e,{value:i}){let a={depth:l(i),handler(t){a.depth!==0&&setTimeout(()=>{let i=r(e);i!==void 0&&n(i,t,a.depth)},0)},handlerKey(e){t(e,13)&&a.handler(e)}};e.__qclosepopup=a,e.addEventListener(`click`,a.handler),e.addEventListener(`keyup`,a.handlerKey)},updated(e,{value:t,oldValue:n}){t!==n&&(e.__qclosepopup.depth=l(t))},beforeUnmount(e){let t=e.__qclosepopup;e.removeEventListener(`click`,t.handler),e.removeEventListener(`keyup`,t.handlerKey),delete e.__qclosepopup}});export{o as n,c as r,u as t};
|