@blotout/qinto-cli 0.1.11 → 0.1.12
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 +1228 -1207
- package/dist/index.js.map +1 -1
- package/dist/worker-artifact/main/index.js +170 -53
- package/dist/worker-artifact/version.json +4 -4
- package/dist/worker-artifact/wfp/index.js +17 -2
- package/package.json +3 -3
|
@@ -570,6 +570,92 @@ var appendSpan = /* @__PURE__ */ __name((span) => {
|
|
|
570
570
|
scope.buffer.spans.push(span);
|
|
571
571
|
}, "appendSpan");
|
|
572
572
|
|
|
573
|
+
// ../../node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/http-exception.js
|
|
574
|
+
var HTTPException = class extends Error {
|
|
575
|
+
static {
|
|
576
|
+
__name(this, "HTTPException");
|
|
577
|
+
}
|
|
578
|
+
res;
|
|
579
|
+
status;
|
|
580
|
+
/**
|
|
581
|
+
* Creates an instance of `HTTPException`.
|
|
582
|
+
* @param status - HTTP status code for the exception. Defaults to 500.
|
|
583
|
+
* @param options - Additional options for the exception.
|
|
584
|
+
*/
|
|
585
|
+
constructor(status = 500, options) {
|
|
586
|
+
super(options?.message, { cause: options?.cause });
|
|
587
|
+
this.res = options?.res;
|
|
588
|
+
this.status = status;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Returns the response object associated with the exception.
|
|
592
|
+
* If a response object is not provided, a new response is created with the error message and status code.
|
|
593
|
+
* @returns The response object.
|
|
594
|
+
*/
|
|
595
|
+
getResponse() {
|
|
596
|
+
if (this.res) {
|
|
597
|
+
const newResponse = new Response(this.res.body, {
|
|
598
|
+
status: this.status,
|
|
599
|
+
headers: this.res.headers
|
|
600
|
+
});
|
|
601
|
+
return newResponse;
|
|
602
|
+
}
|
|
603
|
+
return new Response(this.message, {
|
|
604
|
+
status: this.status
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// ../../node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/middleware/body-limit/index.js
|
|
610
|
+
var ERROR_MESSAGE = "Payload Too Large";
|
|
611
|
+
var bodyLimit = /* @__PURE__ */ __name((options) => {
|
|
612
|
+
const onError = options.onError || (() => {
|
|
613
|
+
const res = new Response(ERROR_MESSAGE, {
|
|
614
|
+
status: 413
|
|
615
|
+
});
|
|
616
|
+
throw new HTTPException(413, { res });
|
|
617
|
+
});
|
|
618
|
+
const maxSize = options.maxSize;
|
|
619
|
+
return /* @__PURE__ */ __name(async function bodyLimit2(c, next) {
|
|
620
|
+
if (!c.req.raw.body) {
|
|
621
|
+
return next();
|
|
622
|
+
}
|
|
623
|
+
const hasTransferEncoding = c.req.raw.headers.has("transfer-encoding");
|
|
624
|
+
const hasContentLength = c.req.raw.headers.has("content-length");
|
|
625
|
+
if (hasContentLength && !hasTransferEncoding) {
|
|
626
|
+
const contentLength = parseInt(c.req.raw.headers.get("content-length") || "0", 10);
|
|
627
|
+
return contentLength > maxSize ? onError(c) : next();
|
|
628
|
+
}
|
|
629
|
+
let size = 0;
|
|
630
|
+
const chunks = [];
|
|
631
|
+
const rawReader = c.req.raw.body.getReader();
|
|
632
|
+
for (; ; ) {
|
|
633
|
+
const { done, value } = await rawReader.read();
|
|
634
|
+
if (done) {
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
size += value.length;
|
|
638
|
+
if (size > maxSize) {
|
|
639
|
+
return onError(c);
|
|
640
|
+
}
|
|
641
|
+
chunks.push(value);
|
|
642
|
+
}
|
|
643
|
+
const requestInit = {
|
|
644
|
+
body: new ReadableStream({
|
|
645
|
+
start(controller) {
|
|
646
|
+
for (const chunk of chunks) {
|
|
647
|
+
controller.enqueue(chunk);
|
|
648
|
+
}
|
|
649
|
+
controller.close();
|
|
650
|
+
}
|
|
651
|
+
}),
|
|
652
|
+
duplex: "half"
|
|
653
|
+
};
|
|
654
|
+
c.req.raw = new Request(c.req.raw, requestInit);
|
|
655
|
+
return next();
|
|
656
|
+
}, "bodyLimit2");
|
|
657
|
+
}, "bodyLimit");
|
|
658
|
+
|
|
573
659
|
// ../../node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/middleware/cors/index.js
|
|
574
660
|
var cors = /* @__PURE__ */ __name((options) => {
|
|
575
661
|
const opts = {
|
|
@@ -653,42 +739,6 @@ var cors = /* @__PURE__ */ __name((options) => {
|
|
|
653
739
|
}, "cors2");
|
|
654
740
|
}, "cors");
|
|
655
741
|
|
|
656
|
-
// ../../node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/http-exception.js
|
|
657
|
-
var HTTPException = class extends Error {
|
|
658
|
-
static {
|
|
659
|
-
__name(this, "HTTPException");
|
|
660
|
-
}
|
|
661
|
-
res;
|
|
662
|
-
status;
|
|
663
|
-
/**
|
|
664
|
-
* Creates an instance of `HTTPException`.
|
|
665
|
-
* @param status - HTTP status code for the exception. Defaults to 500.
|
|
666
|
-
* @param options - Additional options for the exception.
|
|
667
|
-
*/
|
|
668
|
-
constructor(status = 500, options) {
|
|
669
|
-
super(options?.message, { cause: options?.cause });
|
|
670
|
-
this.res = options?.res;
|
|
671
|
-
this.status = status;
|
|
672
|
-
}
|
|
673
|
-
/**
|
|
674
|
-
* Returns the response object associated with the exception.
|
|
675
|
-
* If a response object is not provided, a new response is created with the error message and status code.
|
|
676
|
-
* @returns The response object.
|
|
677
|
-
*/
|
|
678
|
-
getResponse() {
|
|
679
|
-
if (this.res) {
|
|
680
|
-
const newResponse = new Response(this.res.body, {
|
|
681
|
-
status: this.status,
|
|
682
|
-
headers: this.res.headers
|
|
683
|
-
});
|
|
684
|
-
return newResponse;
|
|
685
|
-
}
|
|
686
|
-
return new Response(this.message, {
|
|
687
|
-
status: this.status
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
|
-
};
|
|
691
|
-
|
|
692
742
|
// src/apps/broker.ts
|
|
693
743
|
import { timingSafeEqual } from "node:crypto";
|
|
694
744
|
import { WorkerEntrypoint } from "cloudflare:workers";
|
|
@@ -25971,9 +26021,29 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_facts_conversion ON facts (key) WHERE kind
|
|
|
25971
26021
|
"ALTER TABLE facts ADD COLUMN expires_at INTEGER;"
|
|
25972
26022
|
];
|
|
25973
26023
|
|
|
26024
|
+
// src/id-graph/anon/token-bucket.ts
|
|
26025
|
+
var createTokenBucket = /* @__PURE__ */ __name((capacity, refillPerSecond) => {
|
|
26026
|
+
let tokens = capacity;
|
|
26027
|
+
let updatedAt = null;
|
|
26028
|
+
return {
|
|
26029
|
+
take: /* @__PURE__ */ __name((now) => {
|
|
26030
|
+
if (updatedAt !== null) {
|
|
26031
|
+
tokens = Math.min(capacity, tokens + Math.max(0, now - updatedAt) * (refillPerSecond / 1e3));
|
|
26032
|
+
}
|
|
26033
|
+
updatedAt = now;
|
|
26034
|
+
if (tokens < 1) {
|
|
26035
|
+
return false;
|
|
26036
|
+
}
|
|
26037
|
+
tokens -= 1;
|
|
26038
|
+
return true;
|
|
26039
|
+
}, "take")
|
|
26040
|
+
};
|
|
26041
|
+
}, "createTokenBucket");
|
|
26042
|
+
|
|
25974
26043
|
// src/id-graph/anon/websocket.ts
|
|
25975
26044
|
var MAX_BROWSER_RESULT_BYTES = 1024;
|
|
25976
26045
|
var MAX_BROWSER_RESULTS_BYTES = 32 * 1024;
|
|
26046
|
+
var MAX_FRAME_BYTES = 64 * 1024;
|
|
25977
26047
|
var browserContextFrame = external_exports.object({
|
|
25978
26048
|
occurredAt: external_exports.number().int().nonnegative().safe(),
|
|
25979
26049
|
page: external_exports.object({ url: external_exports.string().min(1).max(4096), referrer: external_exports.string().min(1).max(4096).optional() }).strict(),
|
|
@@ -26046,6 +26116,10 @@ var pluckRef = /* @__PURE__ */ __name((parsed) => {
|
|
|
26046
26116
|
return typeof ref === "string" && ref.length > 0 ? ref : void 0;
|
|
26047
26117
|
}, "pluckRef");
|
|
26048
26118
|
var parseFrame = /* @__PURE__ */ __name((message) => {
|
|
26119
|
+
const size = typeof message === "string" ? message.length : message.byteLength;
|
|
26120
|
+
if (size > MAX_FRAME_BYTES) {
|
|
26121
|
+
return { ok: false, ref: void 0, error: new Error(`frame exceeds ${MAX_FRAME_BYTES} bytes`) };
|
|
26122
|
+
}
|
|
26049
26123
|
const text = typeof message === "string" ? message : new TextDecoder().decode(message);
|
|
26050
26124
|
let parsed;
|
|
26051
26125
|
try {
|
|
@@ -26144,12 +26218,19 @@ var siteAudience = /* @__PURE__ */ __name(({ registry: registry3, app: app2, top
|
|
|
26144
26218
|
const allowed = app2.events.publishes.includes(topic) && app2.data.includes(SHARED_CONTEXT_CAPABILITY);
|
|
26145
26219
|
return allowed ? siteEventReceivers(registry3, consent2, enforced) : null;
|
|
26146
26220
|
}, "siteAudience");
|
|
26221
|
+
var FRAME_BURST = 60;
|
|
26222
|
+
var FRAMES_PER_SECOND = 10;
|
|
26223
|
+
var EMIT_BURST = 120;
|
|
26224
|
+
var EMITS_PER_SECOND = 5;
|
|
26147
26225
|
var AnonDO = class extends DurableObject2 {
|
|
26148
26226
|
static {
|
|
26149
26227
|
__name(this, "AnonDO");
|
|
26150
26228
|
}
|
|
26151
26229
|
// Serialize link decisions so concurrent first identifiers cannot split the graph.
|
|
26152
26230
|
ingestQueue = Promise.resolve();
|
|
26231
|
+
// In memory on purpose: a hibernated socket sent nothing, so a fresh bucket on wake is the right one.
|
|
26232
|
+
frameBuckets = /* @__PURE__ */ new WeakMap();
|
|
26233
|
+
emitBudget = createTokenBucket(EMIT_BURST, EMITS_PER_SECOND);
|
|
26153
26234
|
// deleteAll removes the live schema; only a later write recreates it.
|
|
26154
26235
|
wiped = false;
|
|
26155
26236
|
// Warm write-through mirrors reset together after expiry.
|
|
@@ -26208,9 +26289,50 @@ var AnonDO = class extends DurableObject2 {
|
|
|
26208
26289
|
...action.type === "track" ? { "action.event": action.payload.event } : {}
|
|
26209
26290
|
};
|
|
26210
26291
|
return runInvocation({ name: "do.anon.ingest", trace, attrs }, () => {
|
|
26292
|
+
if (action.origin !== void 0 && !this.emitBudget.take(Date.now())) {
|
|
26293
|
+
addSpanEvent("emit rejected: the visitor\u2019s emit budget is spent", {
|
|
26294
|
+
level: "warn",
|
|
26295
|
+
attrs: { app: action.origin.app, depth: action.origin.depth }
|
|
26296
|
+
});
|
|
26297
|
+
return Promise.resolve();
|
|
26298
|
+
}
|
|
26211
26299
|
return this.ingestLocal(action);
|
|
26212
26300
|
});
|
|
26213
26301
|
}
|
|
26302
|
+
// The frames that ask instead of act are answered in place; an action frame comes back to be queued.
|
|
26303
|
+
async answerQuery(ws, parsed, userId, attachment) {
|
|
26304
|
+
const { frame, ref } = parsed;
|
|
26305
|
+
if (frame.t === "getSession") {
|
|
26306
|
+
this.respond(ws, {
|
|
26307
|
+
t: "session",
|
|
26308
|
+
ref,
|
|
26309
|
+
anonId: userId,
|
|
26310
|
+
globalId: linkedGlobalId(this.kv),
|
|
26311
|
+
ip: attachment?.host?.ip ?? null,
|
|
26312
|
+
geo: attachment?.browserGeo ?? null
|
|
26313
|
+
});
|
|
26314
|
+
return null;
|
|
26315
|
+
}
|
|
26316
|
+
if (frame.t === "capture") {
|
|
26317
|
+
const { accepted } = await this.capture({ apps: frame.apps });
|
|
26318
|
+
this.respond(ws, { t: "captureAck", ref, anonId: userId, accepted });
|
|
26319
|
+
return null;
|
|
26320
|
+
}
|
|
26321
|
+
return frame;
|
|
26322
|
+
}
|
|
26323
|
+
admitFrame(ws) {
|
|
26324
|
+
let bucket = this.frameBuckets.get(ws);
|
|
26325
|
+
if (bucket === void 0) {
|
|
26326
|
+
bucket = createTokenBucket(FRAME_BURST, FRAMES_PER_SECOND);
|
|
26327
|
+
this.frameBuckets.set(ws, bucket);
|
|
26328
|
+
}
|
|
26329
|
+
if (bucket.take(Date.now())) {
|
|
26330
|
+
return true;
|
|
26331
|
+
}
|
|
26332
|
+
addSpanEvent("websocket frame rate limited", { level: "warn" });
|
|
26333
|
+
this.respond(ws, { t: "error", ref: void 0, message: "rate limited" });
|
|
26334
|
+
return false;
|
|
26335
|
+
}
|
|
26214
26336
|
ingestLocal(action) {
|
|
26215
26337
|
return this.enqueue(() => this.processAction(action));
|
|
26216
26338
|
}
|
|
@@ -26233,6 +26355,9 @@ var AnonDO = class extends DurableObject2 {
|
|
|
26233
26355
|
async webSocketMessage(ws, message) {
|
|
26234
26356
|
const attachment = ws.deserializeAttachment() ?? void 0;
|
|
26235
26357
|
return runInvocation({ name: "ws.message", attrs: attachment?.attrs }, async () => {
|
|
26358
|
+
if (!this.admitFrame(ws)) {
|
|
26359
|
+
return;
|
|
26360
|
+
}
|
|
26236
26361
|
const parsed = parseFrame(message);
|
|
26237
26362
|
if (!parsed.ok) {
|
|
26238
26363
|
addSpanEvent("invalid websocket frame", { level: "warn", attrs: { error: String(parsed.error) } });
|
|
@@ -26250,23 +26375,11 @@ var AnonDO = class extends DurableObject2 {
|
|
|
26250
26375
|
this.respond(ws, { t: "error", ref: parsed.ref, message: "session not initialized" });
|
|
26251
26376
|
return;
|
|
26252
26377
|
}
|
|
26253
|
-
|
|
26254
|
-
|
|
26255
|
-
t: "session",
|
|
26256
|
-
ref: parsed.ref,
|
|
26257
|
-
anonId: userId,
|
|
26258
|
-
globalId: linkedGlobalId(this.kv),
|
|
26259
|
-
ip: attachment?.host?.ip ?? null,
|
|
26260
|
-
geo: attachment?.browserGeo ?? null
|
|
26261
|
-
});
|
|
26262
|
-
return;
|
|
26263
|
-
}
|
|
26264
|
-
if (parsed.frame.t === "capture") {
|
|
26265
|
-
const { accepted } = await this.capture({ apps: parsed.frame.apps });
|
|
26266
|
-
this.respond(ws, { t: "captureAck", ref: parsed.ref, anonId: userId, accepted });
|
|
26378
|
+
const frame = await this.answerQuery(ws, { frame: parsed.frame, ref: parsed.ref }, userId, attachment);
|
|
26379
|
+
if (frame === null) {
|
|
26267
26380
|
return;
|
|
26268
26381
|
}
|
|
26269
|
-
const action = actionFromFrame(
|
|
26382
|
+
const action = actionFromFrame(frame, userId, attachment?.cf, attachment?.host);
|
|
26270
26383
|
const ref = parsed.ref ?? action.actionId;
|
|
26271
26384
|
setSpanAttributes({ "action.id": action.actionId });
|
|
26272
26385
|
try {
|
|
@@ -27192,6 +27305,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_facts_key ON facts (kind, scope, key);
|
|
|
27192
27305
|
];
|
|
27193
27306
|
|
|
27194
27307
|
// src/id-graph/global/do.ts
|
|
27308
|
+
var SIBLING_FAN_OUT_CONCURRENCY = 6;
|
|
27195
27309
|
var GlobalDO = class extends DurableObject3 {
|
|
27196
27310
|
static {
|
|
27197
27311
|
__name(this, "GlobalDO");
|
|
@@ -27293,8 +27407,9 @@ var GlobalDO = class extends DurableObject3 {
|
|
|
27293
27407
|
}
|
|
27294
27408
|
await runInvocation({ name: "do.global.fan_out", trace, attrs: { "global.id": globalId } }, async () => {
|
|
27295
27409
|
const snapshot = this.snapshot(globalId);
|
|
27296
|
-
await
|
|
27297
|
-
|
|
27410
|
+
await runPool(
|
|
27411
|
+
SIBLING_FAN_OUT_CONCURRENCY,
|
|
27412
|
+
recipients.map((anonId) => async () => {
|
|
27298
27413
|
try {
|
|
27299
27414
|
await traced("rpc anon.sync_from_global", { attrs: { "anon.id": anonId } }, () => {
|
|
27300
27415
|
return anonStub(this.env, anonId).syncFromGlobal(snapshot, currentTraceContext());
|
|
@@ -27317,8 +27432,10 @@ app.use("/q.js", cors());
|
|
|
27317
27432
|
app.get("/q.js", qJs);
|
|
27318
27433
|
app.use("/health", cors());
|
|
27319
27434
|
app.route("/", health);
|
|
27435
|
+
var MAX_V1_BODY_BYTES = 1024 * 1024;
|
|
27320
27436
|
var allowCors = cors({ origin: /* @__PURE__ */ __name((origin) => origin, "origin"), credentials: true });
|
|
27321
27437
|
app.use("/v1/*", allowCors);
|
|
27438
|
+
app.use("/v1/*", bodyLimit({ maxSize: MAX_V1_BODY_BYTES, onError: /* @__PURE__ */ __name((c) => c.json({ error: "too_large" }, 413), "onError") }));
|
|
27322
27439
|
app.route("/", alias);
|
|
27323
27440
|
app.route("/", identify);
|
|
27324
27441
|
app.route("/", track);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"main": {
|
|
3
3
|
"version": "0.1.0",
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-08-
|
|
4
|
+
"gitSha": "539e97d61a67ca9c5c6580b94350354e917fcf99",
|
|
5
|
+
"builtAt": "2026-08-31T10:11:02.141Z",
|
|
6
6
|
"compatibilityDate": "2026-06-23",
|
|
7
7
|
"compatibilityFlags": [
|
|
8
8
|
"nodejs_compat"
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
},
|
|
24
24
|
"wfp": {
|
|
25
25
|
"version": "0.1.0",
|
|
26
|
-
"gitSha": "
|
|
27
|
-
"builtAt": "2026-08-
|
|
26
|
+
"gitSha": "6064f0fb074965ec6add05255c92ece93bb31321",
|
|
27
|
+
"builtAt": "2026-08-31T12:30:46.702Z",
|
|
28
28
|
"compatibilityDate": "2026-06-23",
|
|
29
29
|
"compatibilityFlags": [
|
|
30
30
|
"nodejs_compat"
|
|
@@ -2158,10 +2158,9 @@ var EDGETAG_PREFIXES = [
|
|
|
2158
2158
|
|
|
2159
2159
|
// src/allowed-paths.ts
|
|
2160
2160
|
var QINTO_PREFIXES = ["/open", "/q.js", "/health", "/v1/", "/openapi.json", "/apps/", "/app/"];
|
|
2161
|
-
var QINTO_EXACT = /* @__PURE__ */ new Set(["/__qinto/control"]);
|
|
2162
2161
|
var ALLOWED_PREFIXES = [...QINTO_PREFIXES, ...EDGETAG_PREFIXES];
|
|
2163
2162
|
var allowedPaths = /* @__PURE__ */ __name((path) => {
|
|
2164
|
-
return path === "/" ||
|
|
2163
|
+
return path === "/" || ALLOWED_PREFIXES.some((prefix) => path.startsWith(prefix));
|
|
2165
2164
|
}, "allowedPaths");
|
|
2166
2165
|
|
|
2167
2166
|
// ../../libs/wfp-control-token/src/control-token.ts
|
|
@@ -2244,6 +2243,21 @@ var requireControlToken = /* @__PURE__ */ __name(async (c, next) => {
|
|
|
2244
2243
|
return next();
|
|
2245
2244
|
}, "requireControlToken");
|
|
2246
2245
|
|
|
2246
|
+
// src/control/app.ts
|
|
2247
|
+
var forwardAppControl = /* @__PURE__ */ __name(async (c) => {
|
|
2248
|
+
const workerName = c.req.query("workerName");
|
|
2249
|
+
if (workerName === void 0 || workerName === "") {
|
|
2250
|
+
return c.text("workerName required", 400);
|
|
2251
|
+
}
|
|
2252
|
+
const url = new URL(c.req.url);
|
|
2253
|
+
url.pathname = "/__qinto/control";
|
|
2254
|
+
url.search = "";
|
|
2255
|
+
const headers = new Headers(c.req.raw.headers);
|
|
2256
|
+
headers.delete("authorization");
|
|
2257
|
+
const body = await c.req.raw.text();
|
|
2258
|
+
return c.env.DISPATCHER.get(workerName).fetch(new Request(url, { method: "POST", headers, body }));
|
|
2259
|
+
}, "forwardAppControl");
|
|
2260
|
+
|
|
2247
2261
|
// src/control/id-graph.ts
|
|
2248
2262
|
var INSPECT_AUTHORIZATION_HEADER = "x-qinto-inspect-authorization";
|
|
2249
2263
|
var ACTIONS = /* @__PURE__ */ new Set(["resolve", "inspect", "rows"]);
|
|
@@ -2683,6 +2697,7 @@ var Tail = class extends DurableObject {
|
|
|
2683
2697
|
// src/index.ts
|
|
2684
2698
|
var app = new Hono2();
|
|
2685
2699
|
app.get("/tail", requireControlToken, openTailSocket);
|
|
2700
|
+
app.post("/control/app", requireControlToken, forwardAppControl);
|
|
2686
2701
|
app.post("/control/id-graph/:action", requireControlToken, forwardIdGraph);
|
|
2687
2702
|
app.all("*", (c) => {
|
|
2688
2703
|
if (!allowedPaths(c.req.path)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blotout/qinto-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Qinto CLI: authenticate against the Qinto platform and operate it from the terminal or from an agent.",
|
|
6
6
|
"repository": {
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"typescript": "6.0.3",
|
|
41
41
|
"vite": "^8.0.0",
|
|
42
42
|
"vitest": "4.1.10",
|
|
43
|
-
"@qinto/app-build": "0.0.0",
|
|
44
|
-
"@qinto/app-dev": "0.0.0",
|
|
45
43
|
"@qinto/app-manifest": "0.0.0",
|
|
44
|
+
"@qinto/app-dev": "0.0.0",
|
|
45
|
+
"@qinto/app-build": "0.0.0",
|
|
46
46
|
"@qinto/oauth-grammar": "0.0.0"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|