@notis_ai/cli 0.2.0-beta.177.1 → 0.2.0-beta.178.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -39
- package/dist/agent-hooks/notis-agent-hook.mjs +187 -142
- package/dist/sdk/src/config.ts +4 -0
- package/dist/sdk/src/hooks/useNotis.ts +3 -1
- package/dist/sdk/src/index.ts +1 -0
- package/dist/sdk/src/runtime.ts +13 -0
- package/package.json +1 -1
- package/skills/notis-apps/cli.md +23 -37
- package/src/command-specs/apps.js +18 -14
- package/src/command-specs/reports.js +61 -50
- package/src/runtime/agent-browser.js +10 -5
- package/src/runtime/app-platform.js +25 -1
- package/src/runtime/app-test-server.js +1 -0
- package/template/.harness/index.html.tmpl +12 -4
|
@@ -4797,7 +4797,7 @@ var init_skill_sync = __esm({
|
|
|
4797
4797
|
|
|
4798
4798
|
// src/cli.js
|
|
4799
4799
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
4800
|
-
import { dirname as dirname16, join as
|
|
4800
|
+
import { dirname as dirname16, join as join17 } from "node:path";
|
|
4801
4801
|
import { fileURLToPath as fileURLToPath11 } from "node:url";
|
|
4802
4802
|
|
|
4803
4803
|
// node_modules/commander/lib/error.js
|
|
@@ -8169,8 +8169,8 @@ function useColor() {
|
|
|
8169
8169
|
var program = new Command();
|
|
8170
8170
|
|
|
8171
8171
|
// src/command-specs/reports.js
|
|
8172
|
-
import { readFileSync as readFileSync9 } from "node:fs";
|
|
8173
|
-
import { resolve as resolve6 } from "node:path";
|
|
8172
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, rmSync as rmSync6 } from "node:fs";
|
|
8173
|
+
import { resolve as resolve6, join as join9 } from "node:path";
|
|
8174
8174
|
|
|
8175
8175
|
// src/command-specs/apps.js
|
|
8176
8176
|
init_errors();
|
|
@@ -9223,10 +9223,10 @@ function detectProjectWarnings(projectDir, appConfig = null) {
|
|
|
9223
9223
|
warnings.push("Missing Tailwind config.");
|
|
9224
9224
|
}
|
|
9225
9225
|
const readsWorkspaceDatabases = appConfig?.capabilities?.workspaceDatabases === "read";
|
|
9226
|
-
if (appConfig && !readsWorkspaceDatabases && (!Array.isArray(appConfig.databases) || appConfig.databases.length === 0)) {
|
|
9226
|
+
if (appConfig && appConfig.kind !== "report" && !readsWorkspaceDatabases && (!Array.isArray(appConfig.databases) || appConfig.databases.length === 0)) {
|
|
9227
9227
|
warnings.push("No database references declared in notis.config.ts.");
|
|
9228
9228
|
}
|
|
9229
|
-
if (appConfig) {
|
|
9229
|
+
if (appConfig && appConfig.kind !== "report") {
|
|
9230
9230
|
try {
|
|
9231
9231
|
const listing = inspectListingReadiness(projectDir, appConfig);
|
|
9232
9232
|
warnings.push(...listing.errors, ...listing.warnings);
|
|
@@ -9657,6 +9657,33 @@ function generateManifest(appConfig, projectDir) {
|
|
|
9657
9657
|
}
|
|
9658
9658
|
return entry;
|
|
9659
9659
|
});
|
|
9660
|
+
if (appConfig.kind === "report") {
|
|
9661
|
+
if (routes.length !== 1 || routes[0].collection || routes[0].resourceDeepLinks) {
|
|
9662
|
+
throw usageError("Reports require one standalone route, without app collections or resource deep links.");
|
|
9663
|
+
}
|
|
9664
|
+
if (["databases", "skills", "automations", "onboarding", "tagline", "categories", "author", "screenshots"].some((key) => {
|
|
9665
|
+
const value = appConfig[key];
|
|
9666
|
+
return Array.isArray(value) ? value.length > 0 : Boolean(value);
|
|
9667
|
+
})) throw usageError("Reports cannot own databases, skills, automations or Store metadata.");
|
|
9668
|
+
if (Object.values(appConfig.capabilities || {}).some((value) => value !== "read")) {
|
|
9669
|
+
throw usageError("Reports support read capabilities only; shell grants require an installed app.");
|
|
9670
|
+
}
|
|
9671
|
+
const databaseAccess = appConfig.databaseAccess || [];
|
|
9672
|
+
if (!Array.isArray(databaseAccess) || databaseAccess.some(
|
|
9673
|
+
(entry) => !entry || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(entry.id || "") || !["read", "write"].includes(entry.access)
|
|
9674
|
+
)) throw usageError("databaseAccess entries require an existing database UUID and read/write access.");
|
|
9675
|
+
return {
|
|
9676
|
+
kind: "report",
|
|
9677
|
+
version: 1,
|
|
9678
|
+
spec_version: 4,
|
|
9679
|
+
routes: routes.map((route) => ({ ...route, default: true, tool_access: { base: { tools: appConfig.tools || [] } } })),
|
|
9680
|
+
bundle: { js: "bundle/app.js", css: "bundle/app.css" },
|
|
9681
|
+
tools: appConfig.tools || [],
|
|
9682
|
+
tool_bindings: normalizeAppToolBindings(appConfig.toolBindings),
|
|
9683
|
+
capabilities: normalizeAppCapabilities(appConfig.capabilities),
|
|
9684
|
+
database_access: databaseAccess
|
|
9685
|
+
};
|
|
9686
|
+
}
|
|
9660
9687
|
const databases = (appConfig.databases || []).map((entry) => {
|
|
9661
9688
|
if (typeof entry === "string") return entry;
|
|
9662
9689
|
if (!entry || typeof entry !== "object" || !entry.slug) return entry;
|
|
@@ -11600,6 +11627,7 @@ function buildHarnessDescriptor({ state, manifest, appConfig, route, scenario =
|
|
|
11600
11627
|
Array.isArray(appConfig?.tools) && appConfig.tools.length ? appConfig.tools : manifest.tools
|
|
11601
11628
|
);
|
|
11602
11629
|
return {
|
|
11630
|
+
...state.resource ? { resource: state.resource } : {},
|
|
11603
11631
|
app: {
|
|
11604
11632
|
id: state.appId || "harness-app",
|
|
11605
11633
|
slug: state.slug,
|
|
@@ -11902,7 +11930,8 @@ async function runHarnessRoute({
|
|
|
11902
11930
|
sessionName,
|
|
11903
11931
|
timeoutMs = 1e4,
|
|
11904
11932
|
snapshotPath = null,
|
|
11905
|
-
designViewports = DEFAULT_DESIGN_VIEWPORTS
|
|
11933
|
+
designViewports = DEFAULT_DESIGN_VIEWPORTS,
|
|
11934
|
+
waitForRuntime = false
|
|
11906
11935
|
}) {
|
|
11907
11936
|
const opened = await runAgentBrowser(["--session", sessionName, "open", url], {
|
|
11908
11937
|
timeoutMs: Math.min(Math.max(timeoutMs, 5e3), 3e4)
|
|
@@ -11918,17 +11947,21 @@ async function runHarnessRoute({
|
|
|
11918
11947
|
};
|
|
11919
11948
|
}
|
|
11920
11949
|
const deadline = Date.now() + timeoutMs;
|
|
11950
|
+
let readySince = null;
|
|
11921
11951
|
while (Date.now() < deadline) {
|
|
11922
11952
|
const lastRead = await readHarness(sessionName, 5e3);
|
|
11923
11953
|
if (!lastRead.ok) {
|
|
11954
|
+
readySince = null;
|
|
11924
11955
|
await delay(250);
|
|
11925
11956
|
continue;
|
|
11926
11957
|
}
|
|
11927
11958
|
const harness2 = lastRead.harness;
|
|
11928
|
-
if (
|
|
11929
|
-
|
|
11930
|
-
|
|
11931
|
-
|
|
11959
|
+
if (Array.isArray(harness2?.errors) && harness2.errors.length > 0) break;
|
|
11960
|
+
const pending = (harness2?.runtimeCalls || []).some((call) => call?.ok == null);
|
|
11961
|
+
if (harness2?.mounted && (!waitForRuntime || !pending)) {
|
|
11962
|
+
readySince ??= Date.now();
|
|
11963
|
+
if (Date.now() - readySince >= 250) break;
|
|
11964
|
+
} else readySince = null;
|
|
11932
11965
|
await delay(250);
|
|
11933
11966
|
}
|
|
11934
11967
|
const finalRead = await readHarness(sessionName, 5e3);
|
|
@@ -11954,8 +11987,8 @@ async function runHarnessRoute({
|
|
|
11954
11987
|
savedSnapshotPath = snapshotPath;
|
|
11955
11988
|
}
|
|
11956
11989
|
}
|
|
11957
|
-
const timedOut = !harness.mounted && Date.now() >= deadline;
|
|
11958
11990
|
const runtimeCalls = Array.isArray(harness.runtimeCalls) ? harness.runtimeCalls : [];
|
|
11991
|
+
const timedOut = (!harness.mounted || waitForRuntime && runtimeCalls.some((call) => call?.ok == null)) && Date.now() >= deadline;
|
|
11959
11992
|
let design = [];
|
|
11960
11993
|
let designToolError = null;
|
|
11961
11994
|
if (harness.mounted && Array.isArray(designViewports) && designViewports.length > 0) {
|
|
@@ -13176,6 +13209,9 @@ function assertHarnessResult(result, route, databaseSlugs, mode = "stub", capabi
|
|
|
13176
13209
|
});
|
|
13177
13210
|
}
|
|
13178
13211
|
if (mode === "live") {
|
|
13212
|
+
if (runtimeCalls.some((call) => call?.ok == null)) {
|
|
13213
|
+
assertions.push({ ok: false, code: "runtime_calls_pending", message: "Live verification ended before every started runtime call settled." });
|
|
13214
|
+
}
|
|
13179
13215
|
if (runtimeCalls.length > 0 && runtimeCalls.every((call) => call?.ok === false)) {
|
|
13180
13216
|
assertions.push({
|
|
13181
13217
|
ok: false,
|
|
@@ -13414,6 +13450,8 @@ async function appsVerifyHandler(ctx) {
|
|
|
13414
13450
|
throw usageError(`Project has problems:
|
|
13415
13451
|
${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
13416
13452
|
}
|
|
13453
|
+
const appConfig = await loadAppConfig(projectDir);
|
|
13454
|
+
const isReport = appConfig.kind === "report";
|
|
13417
13455
|
const mode = ctx.options.mode || "stub";
|
|
13418
13456
|
if (!["stub", "live"].includes(mode)) {
|
|
13419
13457
|
throw usageError('--mode must be either "stub" or "live".');
|
|
@@ -13426,8 +13464,10 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
13426
13464
|
if (!ctx.runtime.jwt) {
|
|
13427
13465
|
throw usageError("Live verify mode requires CLI auth. Run notis login and retry.");
|
|
13428
13466
|
}
|
|
13429
|
-
|
|
13430
|
-
|
|
13467
|
+
if (isReport) {
|
|
13468
|
+
if (!ctx.options.documentId || !/^\d+$/.test(String(ctx.options.expectedRevision ?? ""))) throw usageError("Live report verification requires --document-id and --expected-revision of the saved report.");
|
|
13469
|
+
} else linkedState = readLinkedState(projectDir, linkedStateProfileKey(ctx.runtime));
|
|
13470
|
+
if (!isReport && !linkedState?.app_id) {
|
|
13431
13471
|
throw usageError("Live verify mode requires a linked app. Run `notis apps link <app-id> .` first.");
|
|
13432
13472
|
}
|
|
13433
13473
|
}
|
|
@@ -13437,15 +13477,14 @@ ${problems.map((p) => ` - ${p}`).join("\n")}`);
|
|
|
13437
13477
|
});
|
|
13438
13478
|
}
|
|
13439
13479
|
const manifest = readManifest(projectDir);
|
|
13440
|
-
const
|
|
13441
|
-
const listing = inspectListingReadiness(projectDir, appConfig);
|
|
13480
|
+
const listing = isReport ? { ready: true, errors: [], warnings: [] } : inspectListingReadiness(projectDir, appConfig);
|
|
13442
13481
|
if (listing.errors.length && ctx.options.listing === true) {
|
|
13443
13482
|
throw usageError(`Listing metadata has problems:
|
|
13444
13483
|
${listing.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
13445
13484
|
}
|
|
13446
13485
|
const listingWarnings = [
|
|
13447
13486
|
...[...listing.errors, ...listing.warnings].map((message) => `Store readiness: ${message}`),
|
|
13448
|
-
...findUnknownScreenshotScenarios(projectDir, resolveListingScreenshots(projectDir, appConfig))
|
|
13487
|
+
...isReport ? [] : findUnknownScreenshotScenarios(projectDir, resolveListingScreenshots(projectDir, appConfig))
|
|
13449
13488
|
];
|
|
13450
13489
|
const routes = routeSelection(manifest, parseRouteSlugs(ctx.options.routes));
|
|
13451
13490
|
const port = parsePort(ctx.options.port) || await getAvailablePort();
|
|
@@ -13467,7 +13506,8 @@ ${listing.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
|
13467
13506
|
apps: [{
|
|
13468
13507
|
slug: appSlug,
|
|
13469
13508
|
projectDir,
|
|
13470
|
-
appId: linkedState?.app_id || "harness-app"
|
|
13509
|
+
appId: linkedState?.app_id || "harness-app",
|
|
13510
|
+
resource: isReport ? { kind: "report", id: ctx.options.documentId || "preview", revision: Number(ctx.options.expectedRevision || 0) } : void 0
|
|
13471
13511
|
}],
|
|
13472
13512
|
port,
|
|
13473
13513
|
harness: {
|
|
@@ -13541,7 +13581,8 @@ ${listing.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
|
13541
13581
|
const result = await runHarnessRoute({
|
|
13542
13582
|
url,
|
|
13543
13583
|
sessionName: browserSessionName,
|
|
13544
|
-
timeoutMs: Number.parseInt(ctx.globalOptions.timeoutMs || "", 10) || 1e4,
|
|
13584
|
+
timeoutMs: Number.parseInt(ctx.globalOptions.timeoutMs || "", 10) || (mode === "live" ? 9e4 : 1e4),
|
|
13585
|
+
waitForRuntime: mode === "live",
|
|
13545
13586
|
snapshotPath
|
|
13546
13587
|
});
|
|
13547
13588
|
const assertions = assertHarnessResult(
|
|
@@ -13596,11 +13637,7 @@ ${listing.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
|
13596
13637
|
},
|
|
13597
13638
|
summary,
|
|
13598
13639
|
results,
|
|
13599
|
-
listing: {
|
|
13600
|
-
ready: listing.ready,
|
|
13601
|
-
gated: ctx.options.listing === true,
|
|
13602
|
-
problems: listing.errors
|
|
13603
|
-
}
|
|
13640
|
+
...!isReport ? { listing: { ready: listing.ready, gated: ctx.options.listing === true, problems: listing.errors } } : {}
|
|
13604
13641
|
};
|
|
13605
13642
|
if (!keepOpen) await cleanup();
|
|
13606
13643
|
ctx.output.emitSuccess({
|
|
@@ -14513,76 +14550,86 @@ var appsCommandSpecs = [
|
|
|
14513
14550
|
|
|
14514
14551
|
// src/command-specs/reports.js
|
|
14515
14552
|
init_errors();
|
|
14516
|
-
|
|
14517
|
-
const
|
|
14553
|
+
async function assertReportProject(ctx) {
|
|
14554
|
+
const dir = resolveProjectDir(ctx.args.dir || ".");
|
|
14555
|
+
if ((await loadAppConfig(dir)).kind !== "report") throw usageError('Set kind: "report" in notis.config.ts. Reports have no app installation or owned resources.');
|
|
14556
|
+
return dir;
|
|
14557
|
+
}
|
|
14558
|
+
async function initReport(ctx) {
|
|
14559
|
+
const dir = resolveProjectDir(ctx.args.dir || `./${ctx.args.name.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`);
|
|
14560
|
+
const result = await appsInitHandler({ ...ctx, args: { ...ctx.args, dir }, options: {}, output: { emitSuccess: (value) => value } });
|
|
14561
|
+
writeFileSync6(join9(dir, "notis.config.ts"), `import { defineNotisApp } from '@notis/sdk/config';
|
|
14562
|
+
export default defineNotisApp({
|
|
14563
|
+
kind: 'report', name: ${JSON.stringify(ctx.args.name)},
|
|
14564
|
+
routes: [{ path: '/', slug: 'home', name: 'Report', default: true }],
|
|
14565
|
+
tools: [],
|
|
14566
|
+
});
|
|
14567
|
+
`);
|
|
14568
|
+
writeFileSync6(join9(dir, "app/page.tsx"), `'use client';
|
|
14569
|
+
import { useNotis, NotisCommentBoundary } from '@notis/sdk';
|
|
14570
|
+
export default function Report() {
|
|
14571
|
+
const { app } = useNotis();
|
|
14572
|
+
return <NotisCommentBoundary><main className="notis-app-shell space-y-6">
|
|
14573
|
+
<h1 className="text-2xl font-semibold">{app?.name || 'Report'}</h1>
|
|
14574
|
+
<p className="text-muted-foreground">Your report is ready to build.</p>
|
|
14575
|
+
</main></NotisCommentBoundary>;
|
|
14576
|
+
}
|
|
14577
|
+
`);
|
|
14578
|
+
rmSync6(join9(dir, "CHANGELOG.md"), { force: true });
|
|
14579
|
+
return ctx.output.emitSuccess({ ...result, command: "reports init", hints: [
|
|
14580
|
+
{ command: `cd ${dir} && npm install`, reason: "Install dependencies" },
|
|
14581
|
+
{ command: `notis reports build ${dir}`, reason: "Build the standalone report" }
|
|
14582
|
+
] });
|
|
14583
|
+
}
|
|
14584
|
+
var localReportCommand = (command, name = command) => {
|
|
14585
|
+
const app = appsCommandSpecs.find((item) => item.command_path.join(" ") === `apps ${command}`);
|
|
14518
14586
|
return {
|
|
14519
|
-
...
|
|
14520
|
-
handler: async (ctx) => {
|
|
14521
|
-
const output = new Proxy(ctx.output, {
|
|
14522
|
-
get(target, key) {
|
|
14523
|
-
if (key === "emitSuccess") return (result) => {
|
|
14524
|
-
const value2 = { ...result, warnings: (result.warnings || []).filter((warning) => !warning.startsWith("Store readiness:")) };
|
|
14525
|
-
if (value2.data?.listing) {
|
|
14526
|
-
value2.data = { ...value2.data };
|
|
14527
|
-
delete value2.data.listing;
|
|
14528
|
-
}
|
|
14529
|
-
return target.emitSuccess(value2);
|
|
14530
|
-
};
|
|
14531
|
-
const value = Reflect.get(target, key);
|
|
14532
|
-
return typeof value === "function" ? value.bind(target) : value;
|
|
14533
|
-
}
|
|
14534
|
-
});
|
|
14535
|
-
return spec.handler({ ...ctx, options: { ...ctx.options, listing: false, ...name === "preview" ? { keepOpen: true } : {} }, output });
|
|
14536
|
-
},
|
|
14587
|
+
...app,
|
|
14537
14588
|
command_path: ["reports", name],
|
|
14538
|
-
summary: `${name[0].toUpperCase() + name.slice(1)} a
|
|
14539
|
-
|
|
14540
|
-
|
|
14541
|
-
|
|
14542
|
-
|
|
14543
|
-
|
|
14544
|
-
|
|
14589
|
+
summary: `${name[0].toUpperCase() + name.slice(1)} a standalone live SDK report.`,
|
|
14590
|
+
when_to_use: "Author a private standalone document using the shared view runtime; no app or database is required.",
|
|
14591
|
+
args_schema: { ...app.args_schema, options: [...(app.args_schema.options || []).filter((option) => !["--listing", "--from <slug>"].includes(option.flags)), ...command === "verify" ? [{ flags: "--document-id <id>", description: "Saved report for live verification." }, { flags: "--expected-revision <revision>", description: "Saved report revision for live verification." }] : []] },
|
|
14592
|
+
examples: [`notis reports ${name} ${command === "init" ? '"Weekly report" ./weekly-report' : "./weekly-report"}`],
|
|
14593
|
+
handler: async (ctx) => {
|
|
14594
|
+
if (command === "init") return initReport(ctx);
|
|
14595
|
+
await assertReportProject(ctx);
|
|
14596
|
+
return (command === "build" ? appsBuildHandler : appsVerifyHandler)({ ...ctx, options: { ...ctx.options, ...name === "preview" ? { keepOpen: true } : {} } });
|
|
14597
|
+
}
|
|
14545
14598
|
};
|
|
14546
14599
|
};
|
|
14547
14600
|
var reportsCommandSpecs = [
|
|
14548
|
-
|
|
14549
|
-
|
|
14550
|
-
|
|
14551
|
-
|
|
14601
|
+
localReportCommand("init"),
|
|
14602
|
+
localReportCommand("build"),
|
|
14603
|
+
localReportCommand("verify"),
|
|
14604
|
+
localReportCommand("verify", "preview"),
|
|
14552
14605
|
{
|
|
14553
14606
|
command_path: ["reports", "save"],
|
|
14554
|
-
summary: "Build, verify and save a
|
|
14555
|
-
when_to_use: "Persist an independently
|
|
14607
|
+
summary: "Build, verify and save a standalone live report document.",
|
|
14608
|
+
when_to_use: "Persist an independently owned report, not an app release.",
|
|
14556
14609
|
args_schema: {
|
|
14557
14610
|
arguments: [{ token: "[dir]", key: "dir", description: "Report source directory." }],
|
|
14558
14611
|
options: [
|
|
14559
|
-
{ flags: "--
|
|
14560
|
-
{ flags: "--
|
|
14561
|
-
{ flags: "--
|
|
14562
|
-
{ flags: "--
|
|
14563
|
-
{ flags: "--title <title>", description: "Required, including updates. Record title." },
|
|
14564
|
-
{ flags: "--context-file <file>", description: "Required. UTF-8 readable report content and structure." },
|
|
14565
|
-
{ flags: "--properties-file <file>", description: "JSON database property values keyed by name." }
|
|
14612
|
+
{ flags: "--document-id <id>", description: "Existing report document to update." },
|
|
14613
|
+
{ flags: "--expected-revision <revision>", description: "Current saved report revision; required for updates." },
|
|
14614
|
+
{ flags: "--title <title>", description: "Required, including updates. Document title." },
|
|
14615
|
+
{ flags: "--context-file <file>", description: "Required. UTF-8 readable report content and structure." }
|
|
14566
14616
|
]
|
|
14567
14617
|
},
|
|
14568
|
-
examples: ['notis reports save ./weekly-report --
|
|
14618
|
+
examples: ['notis reports save ./weekly-report --title "Weekly review" --context-file ./context.md'],
|
|
14569
14619
|
mutates: true,
|
|
14570
14620
|
idempotent: true,
|
|
14571
14621
|
backend_call: { type: "tool", name: "LOCAL_NOTIS_SAVE_REPORT" },
|
|
14572
14622
|
async handler(ctx) {
|
|
14573
|
-
const dir =
|
|
14574
|
-
if (!ctx.options.
|
|
14575
|
-
if (ctx.options.documentId && (!/^\d+$/.test(String(ctx.options.expectedRevision ?? "")) || !Number.isSafeInteger(Number(ctx.options.expectedRevision)))) throw usageError("--expected-revision is required for update
|
|
14576
|
-
if (ctx.options.attach && !ctx.options.documentId) throw usageError("--attach requires --document-id.");
|
|
14623
|
+
const dir = await assertReportProject(ctx);
|
|
14624
|
+
if (!ctx.options.title || !ctx.options.contextFile) throw usageError("--title and --context-file are required.");
|
|
14625
|
+
if (ctx.options.documentId && (!/^\d+$/.test(String(ctx.options.expectedRevision ?? "")) || !Number.isSafeInteger(Number(ctx.options.expectedRevision)))) throw usageError("--expected-revision is required for update.");
|
|
14577
14626
|
const context = readFileSync9(resolve6(ctx.options.contextFile), "utf8");
|
|
14578
|
-
const properties = ctx.options.propertiesFile ? JSON.parse(readFileSync9(resolve6(ctx.options.propertiesFile), "utf8")) : {};
|
|
14579
14627
|
await buildArtifact(dir, { stdio: ctx.output.isMachineMode() ? "pipe" : "inherit" });
|
|
14580
14628
|
const release = prepareAppRelease(dir);
|
|
14581
14629
|
try {
|
|
14582
14630
|
if (release.manifest.routes?.length !== 1) throw usageError("Reports require exactly one SDK route.");
|
|
14583
14631
|
let verification;
|
|
14584
|
-
const
|
|
14585
|
-
const exit = await verify.handler({ ...ctx, args: { dir: release.projectDir }, options: { skipBuild: true, mode: "stub" }, output: { ...ctx.output, isMachineMode: () => true, emitSuccess: (value) => {
|
|
14632
|
+
const exit = await appsVerifyHandler({ ...ctx, args: { dir: release.projectDir }, options: { skipBuild: true, mode: "stub" }, output: { ...ctx.output, isMachineMode: () => true, emitSuccess: (value) => {
|
|
14586
14633
|
verification = value;
|
|
14587
14634
|
} } });
|
|
14588
14635
|
if (exit !== EXIT_CODES.ok || verification?.data?.status !== "passed") throw usageError("Report verification failed; nothing saved.");
|
|
@@ -14593,12 +14640,10 @@ var reportsCommandSpecs = [
|
|
|
14593
14640
|
mutating: true,
|
|
14594
14641
|
idempotencyKey: nextIdempotencyKey(ctx.globalOptions),
|
|
14595
14642
|
arguments_: {
|
|
14596
|
-
operation: ctx.options.
|
|
14597
|
-
database_id: ctx.options.databaseId,
|
|
14643
|
+
operation: ctx.options.documentId ? "update" : "create",
|
|
14598
14644
|
title: ctx.options.title,
|
|
14599
|
-
properties,
|
|
14600
14645
|
...ctx.options.documentId ? { document_id: ctx.options.documentId, expected_revision: Number(ctx.options.expectedRevision) } : {},
|
|
14601
|
-
report: { schema: "notis-report/
|
|
14646
|
+
report: { schema: "notis-report/v3", context, artifact: { manifest: release.manifest, files, encoding: "base64" } }
|
|
14602
14647
|
}
|
|
14603
14648
|
});
|
|
14604
14649
|
if (!result?.payload?.document?.id || !result.payload.document.view_revision) throw usageError("Save returned no record identity. Read back before retrying; the outcome may be unknown.");
|
|
@@ -15518,7 +15563,7 @@ init_auth_recovery();
|
|
|
15518
15563
|
init_profiles();
|
|
15519
15564
|
init_oauth();
|
|
15520
15565
|
import { readFileSync as readFileSync13 } from "node:fs";
|
|
15521
|
-
import { dirname as dirname12, join as
|
|
15566
|
+
import { dirname as dirname12, join as join12 } from "node:path";
|
|
15522
15567
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
15523
15568
|
|
|
15524
15569
|
// src/command-specs/auth.js
|
|
@@ -15536,15 +15581,15 @@ import {
|
|
|
15536
15581
|
readFileSync as readFileSync11,
|
|
15537
15582
|
readdirSync as readdirSync6,
|
|
15538
15583
|
renameSync as renameSync4,
|
|
15539
|
-
writeFileSync as
|
|
15584
|
+
writeFileSync as writeFileSync7
|
|
15540
15585
|
} from "node:fs";
|
|
15541
15586
|
import { createHash as createHash4 } from "node:crypto";
|
|
15542
15587
|
import { homedir as homedir4 } from "node:os";
|
|
15543
|
-
import { dirname as dirname10, join as
|
|
15588
|
+
import { dirname as dirname10, join as join10 } from "node:path";
|
|
15544
15589
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
15545
15590
|
var HERE = dirname10(fileURLToPath7(import.meta.url));
|
|
15546
|
-
var INSTRUCTIONS_PATH =
|
|
15547
|
-
var HOOK_BUNDLE_PATH =
|
|
15591
|
+
var INSTRUCTIONS_PATH = join10(HERE, "..", "..", "skills", "notis-cli", "AGENT_INSTRUCTIONS.md");
|
|
15592
|
+
var HOOK_BUNDLE_PATH = join10(HERE, "..", "..", "dist", "agent-hooks", "notis-agent-hook.mjs");
|
|
15548
15593
|
var START_MARKER = "<!-- notis-cli:instructions:start -->";
|
|
15549
15594
|
var END_MARKER = "<!-- notis-cli:instructions:end -->";
|
|
15550
15595
|
var LEGACY_SENTINEL = "Use the Notis CLI (`npx --package @notis_ai/cli@latest -- notis ...`)";
|
|
@@ -15554,18 +15599,18 @@ var AGENT_IDS = Object.freeze(["codex", "claude-code"]);
|
|
|
15554
15599
|
function atomicWrite(filePath, contents, mode = 384) {
|
|
15555
15600
|
mkdirSync7(dirname10(filePath), { recursive: true, mode: 448 });
|
|
15556
15601
|
const temporaryPath = `${filePath}.notis-${process.pid}-${Date.now()}.tmp`;
|
|
15557
|
-
|
|
15602
|
+
writeFileSync7(temporaryPath, contents, { mode });
|
|
15558
15603
|
renameSync4(temporaryPath, filePath);
|
|
15559
15604
|
}
|
|
15560
15605
|
function readText(filePath) {
|
|
15561
15606
|
return existsSync9(filePath) ? readFileSync11(filePath, "utf-8") : "";
|
|
15562
15607
|
}
|
|
15563
15608
|
function activeCodexInstructionsPath(home) {
|
|
15564
|
-
const overridePath =
|
|
15609
|
+
const overridePath = join10(home, ".codex", "AGENTS.override.md");
|
|
15565
15610
|
if (existsSync9(overridePath) && readText(overridePath).trim()) {
|
|
15566
15611
|
return overridePath;
|
|
15567
15612
|
}
|
|
15568
|
-
return
|
|
15613
|
+
return join10(home, ".codex", "AGENTS.md");
|
|
15569
15614
|
}
|
|
15570
15615
|
function upsertInstructionBlock(filePath, block) {
|
|
15571
15616
|
const existing = readText(filePath);
|
|
@@ -15655,7 +15700,7 @@ function installManagedHookRuntime({
|
|
|
15655
15700
|
} = {}) {
|
|
15656
15701
|
const bundle = readFileSync11(bundlePath2);
|
|
15657
15702
|
const digest = createHash4("sha256").update(bundle).digest("hex");
|
|
15658
|
-
const runtimePath =
|
|
15703
|
+
const runtimePath = join10(
|
|
15659
15704
|
home,
|
|
15660
15705
|
".notis",
|
|
15661
15706
|
"agent-hooks",
|
|
@@ -15666,7 +15711,7 @@ function installManagedHookRuntime({
|
|
|
15666
15711
|
const existingDigest = existsSync9(runtimePath) ? createHash4("sha256").update(readFileSync11(runtimePath)).digest("hex") : null;
|
|
15667
15712
|
if (existingDigest !== digest) atomicWrite(runtimePath, bundle, 320);
|
|
15668
15713
|
chmodSync(runtimePath, 320);
|
|
15669
|
-
const launcherPath =
|
|
15714
|
+
const launcherPath = join10(
|
|
15670
15715
|
home,
|
|
15671
15716
|
".notis",
|
|
15672
15717
|
"agent-hooks",
|
|
@@ -15750,16 +15795,16 @@ function upsertPromptHooks(filePath, agentId, profileName, enabled, launcherPath
|
|
|
15750
15795
|
function agentPaths(agentId, home) {
|
|
15751
15796
|
if (agentId === "codex") {
|
|
15752
15797
|
return {
|
|
15753
|
-
root:
|
|
15798
|
+
root: join10(home, ".codex"),
|
|
15754
15799
|
instructions: activeCodexInstructionsPath(home),
|
|
15755
|
-
hooks:
|
|
15800
|
+
hooks: join10(home, ".codex", "hooks.json")
|
|
15756
15801
|
};
|
|
15757
15802
|
}
|
|
15758
15803
|
if (agentId === "claude-code") {
|
|
15759
15804
|
return {
|
|
15760
|
-
root:
|
|
15761
|
-
instructions:
|
|
15762
|
-
hooks:
|
|
15805
|
+
root: join10(home, ".claude"),
|
|
15806
|
+
instructions: join10(home, ".claude", "CLAUDE.md"),
|
|
15807
|
+
hooks: join10(home, ".claude", "settings.json")
|
|
15763
15808
|
};
|
|
15764
15809
|
}
|
|
15765
15810
|
throw new Error(`Unsupported agent: ${agentId}`);
|
|
@@ -15830,10 +15875,10 @@ import {
|
|
|
15830
15875
|
mkdirSync as mkdirSync8,
|
|
15831
15876
|
readFileSync as readFileSync12,
|
|
15832
15877
|
renameSync as renameSync5,
|
|
15833
|
-
writeFileSync as
|
|
15878
|
+
writeFileSync as writeFileSync8
|
|
15834
15879
|
} from "node:fs";
|
|
15835
15880
|
import { homedir as homedir5 } from "node:os";
|
|
15836
|
-
import { dirname as dirname11, join as
|
|
15881
|
+
import { dirname as dirname11, join as join11 } from "node:path";
|
|
15837
15882
|
var MAX_SEEN_HASHES = 500;
|
|
15838
15883
|
var MAX_PROMPT_CHARS = 12e3;
|
|
15839
15884
|
function hash(value) {
|
|
@@ -15841,7 +15886,7 @@ function hash(value) {
|
|
|
15841
15886
|
}
|
|
15842
15887
|
function statePath(sessionId, home = homedir5()) {
|
|
15843
15888
|
if (!sessionId) return null;
|
|
15844
|
-
return
|
|
15889
|
+
return join11(home, ".notis", "agent-memory-hooks", `${hash(sessionId)}.json`);
|
|
15845
15890
|
}
|
|
15846
15891
|
function readState(sessionId, home) {
|
|
15847
15892
|
const filePath = statePath(sessionId, home);
|
|
@@ -15879,7 +15924,7 @@ function writeState(sessionId, state, home) {
|
|
|
15879
15924
|
if (!filePath) return;
|
|
15880
15925
|
mkdirSync8(dirname11(filePath), { recursive: true, mode: 448 });
|
|
15881
15926
|
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
15882
|
-
|
|
15927
|
+
writeFileSync8(temporaryPath, `${JSON.stringify(state)}
|
|
15883
15928
|
`, { mode: 384 });
|
|
15884
15929
|
renameSync5(temporaryPath, filePath);
|
|
15885
15930
|
}
|
|
@@ -16383,7 +16428,7 @@ var authCommandSpecs = [
|
|
|
16383
16428
|
|
|
16384
16429
|
// src/command-specs/onboarding.js
|
|
16385
16430
|
var HERE2 = dirname12(fileURLToPath8(import.meta.url));
|
|
16386
|
-
var BUNDLED_BRIEF_PATH =
|
|
16431
|
+
var BUNDLED_BRIEF_PATH = join12(HERE2, "..", "..", "skills", "notis-onboarding", "BRIEF.md");
|
|
16387
16432
|
async function fetchOnboardingState(runtime) {
|
|
16388
16433
|
try {
|
|
16389
16434
|
const { payload } = await runToolCommand({
|
|
@@ -17239,11 +17284,11 @@ import { createHash as createHash8, randomUUID as randomUUID6 } from "node:crypt
|
|
|
17239
17284
|
import {
|
|
17240
17285
|
mkdtempSync as mkdtempSync4,
|
|
17241
17286
|
readFileSync as readFileSync15,
|
|
17242
|
-
rmSync as
|
|
17243
|
-
writeFileSync as
|
|
17287
|
+
rmSync as rmSync7,
|
|
17288
|
+
writeFileSync as writeFileSync9
|
|
17244
17289
|
} from "node:fs";
|
|
17245
17290
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
17246
|
-
import { basename as basename6, join as
|
|
17291
|
+
import { basename as basename6, join as join13 } from "node:path";
|
|
17247
17292
|
import { setTimeout as delay2 } from "node:timers/promises";
|
|
17248
17293
|
function discoveryNames(payload) {
|
|
17249
17294
|
return [...new Set(
|
|
@@ -17324,12 +17369,12 @@ function remotePath(folder, name) {
|
|
|
17324
17369
|
return `${normalizedFolder}/${name}`;
|
|
17325
17370
|
}
|
|
17326
17371
|
async function smokeFileUploadHandler(ctx) {
|
|
17327
|
-
const tempDir = mkdtempSync4(
|
|
17372
|
+
const tempDir = mkdtempSync4(join13(tmpdir3(), "notis-smoke-file-upload-"));
|
|
17328
17373
|
const marker = `${(/* @__PURE__ */ new Date()).toISOString().replaceAll(/[-:.]/g, "")}-${randomUUID6().slice(0, 8)}`;
|
|
17329
17374
|
const generatedFixture = !ctx.options.sourceFile;
|
|
17330
|
-
const fixturePath = ctx.options.sourceFile ||
|
|
17375
|
+
const fixturePath = ctx.options.sourceFile || join13(tempDir, `notis-file-upload-${marker}.txt`);
|
|
17331
17376
|
if (generatedFixture) {
|
|
17332
|
-
|
|
17377
|
+
writeFileSync9(
|
|
17333
17378
|
fixturePath,
|
|
17334
17379
|
`Notis Dropbox file-upload smoke
|
|
17335
17380
|
marker=${marker}
|
|
@@ -17552,7 +17597,7 @@ unicode=\xE9t\xE9 Z\xFCrich \u6771\u4EAC
|
|
|
17552
17597
|
result.cleanup.deleted = false;
|
|
17553
17598
|
}
|
|
17554
17599
|
}
|
|
17555
|
-
|
|
17600
|
+
rmSync7(tempDir, { recursive: true, force: true });
|
|
17556
17601
|
}
|
|
17557
17602
|
}
|
|
17558
17603
|
var smokeCommandSpecs = [
|
|
@@ -18318,7 +18363,7 @@ init_profiles();
|
|
|
18318
18363
|
import { createHash as createHash9, randomUUID as randomUUID7 } from "node:crypto";
|
|
18319
18364
|
import { mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
18320
18365
|
import { homedir as homedir7 } from "node:os";
|
|
18321
|
-
import { dirname as dirname14, join as
|
|
18366
|
+
import { dirname as dirname14, join as join15 } from "node:path";
|
|
18322
18367
|
|
|
18323
18368
|
// src/runtime/base-skills.js
|
|
18324
18369
|
import {
|
|
@@ -18328,11 +18373,11 @@ import {
|
|
|
18328
18373
|
mkdirSync as mkdirSync9,
|
|
18329
18374
|
readlinkSync,
|
|
18330
18375
|
renameSync as renameSync6,
|
|
18331
|
-
rmSync as
|
|
18376
|
+
rmSync as rmSync8,
|
|
18332
18377
|
symlinkSync
|
|
18333
18378
|
} from "node:fs";
|
|
18334
18379
|
import { homedir as homedir6 } from "node:os";
|
|
18335
|
-
import { dirname as dirname13, join as
|
|
18380
|
+
import { dirname as dirname13, join as join14, relative as relative5, resolve as resolve8 } from "node:path";
|
|
18336
18381
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
18337
18382
|
var BASE_SKILL_NAMES = Object.freeze([
|
|
18338
18383
|
"notis-apps",
|
|
@@ -18343,29 +18388,29 @@ var HERE3 = dirname13(fileURLToPath9(import.meta.url));
|
|
|
18343
18388
|
function resolveBundledBaseSkillsRoot({ resourcesPath = process.resourcesPath } = {}) {
|
|
18344
18389
|
const sourceCheckout = resolve8(HERE3, "../../../../server/skills");
|
|
18345
18390
|
const packaged = resolve8(HERE3, "../../dist/base-skills");
|
|
18346
|
-
const desktopResource = resourcesPath ?
|
|
18391
|
+
const desktopResource = resourcesPath ? join14(resourcesPath, "base-skills") : null;
|
|
18347
18392
|
const desktopPackagerRoot = resourcesPath || null;
|
|
18348
18393
|
for (const candidate of [sourceCheckout, packaged, desktopResource, desktopPackagerRoot]) {
|
|
18349
|
-
if (candidate && BASE_SKILL_NAMES.every((name) => existsSync12(
|
|
18394
|
+
if (candidate && BASE_SKILL_NAMES.every((name) => existsSync12(join14(candidate, name, "SKILL.md")))) {
|
|
18350
18395
|
return candidate;
|
|
18351
18396
|
}
|
|
18352
18397
|
}
|
|
18353
18398
|
return packaged;
|
|
18354
18399
|
}
|
|
18355
18400
|
function getBaseSkillPaths({ home = homedir6(), userId = null } = {}) {
|
|
18356
|
-
const notisRoot =
|
|
18401
|
+
const notisRoot = join14(home, ".notis", "skills");
|
|
18357
18402
|
const targetRoots = [
|
|
18358
|
-
|
|
18359
|
-
|
|
18360
|
-
|
|
18361
|
-
|
|
18403
|
+
join14(home, ".agents", "skills"),
|
|
18404
|
+
join14(home, ".codex", "skills"),
|
|
18405
|
+
join14(home, ".cursor", "skills"),
|
|
18406
|
+
join14(home, ".claude", "skills")
|
|
18362
18407
|
];
|
|
18363
18408
|
if (userId) {
|
|
18364
|
-
targetRoots.push(
|
|
18409
|
+
targetRoots.push(join14(notisRoot, "users", userId, "skills"));
|
|
18365
18410
|
}
|
|
18366
18411
|
return {
|
|
18367
|
-
baseRoot:
|
|
18368
|
-
backupRoot:
|
|
18412
|
+
baseRoot: join14(notisRoot, "base"),
|
|
18413
|
+
backupRoot: join14(notisRoot, "backups"),
|
|
18369
18414
|
targetRoots
|
|
18370
18415
|
};
|
|
18371
18416
|
}
|
|
@@ -18378,7 +18423,7 @@ function sameLinkTarget(linkPath, expectedTarget) {
|
|
|
18378
18423
|
}
|
|
18379
18424
|
}
|
|
18380
18425
|
function backupConflict(linkPath, backupRoot, targetLabel, skillName, now) {
|
|
18381
|
-
const backupPath =
|
|
18426
|
+
const backupPath = join14(backupRoot, String(now), targetLabel, skillName);
|
|
18382
18427
|
mkdirSync9(dirname13(backupPath), { recursive: true, mode: 448 });
|
|
18383
18428
|
renameSync6(linkPath, backupPath);
|
|
18384
18429
|
return backupPath;
|
|
@@ -18387,7 +18432,7 @@ function replaceDirectory(source, destination) {
|
|
|
18387
18432
|
mkdirSync9(dirname13(destination), { recursive: true, mode: 448 });
|
|
18388
18433
|
const temporary = `${destination}.tmp-${process.pid}-${Date.now()}`;
|
|
18389
18434
|
const previous = `${destination}.previous-${process.pid}-${Date.now()}`;
|
|
18390
|
-
|
|
18435
|
+
rmSync8(temporary, { recursive: true, force: true });
|
|
18391
18436
|
cpSync2(source, temporary, { recursive: true });
|
|
18392
18437
|
let movedPrevious = false;
|
|
18393
18438
|
try {
|
|
@@ -18396,9 +18441,9 @@ function replaceDirectory(source, destination) {
|
|
|
18396
18441
|
movedPrevious = true;
|
|
18397
18442
|
}
|
|
18398
18443
|
renameSync6(temporary, destination);
|
|
18399
|
-
if (movedPrevious)
|
|
18444
|
+
if (movedPrevious) rmSync8(previous, { recursive: true, force: true });
|
|
18400
18445
|
} catch (error) {
|
|
18401
|
-
|
|
18446
|
+
rmSync8(temporary, { recursive: true, force: true });
|
|
18402
18447
|
if (movedPrevious && !existsSync12(destination) && existsSync12(previous)) {
|
|
18403
18448
|
renameSync6(previous, destination);
|
|
18404
18449
|
}
|
|
@@ -18420,19 +18465,19 @@ function reconcileBaseSkills({
|
|
|
18420
18465
|
skills: [...BASE_SKILL_NAMES]
|
|
18421
18466
|
};
|
|
18422
18467
|
for (const name of BASE_SKILL_NAMES) {
|
|
18423
|
-
const source =
|
|
18424
|
-
if (!existsSync12(
|
|
18468
|
+
const source = join14(sourceRoot, name);
|
|
18469
|
+
if (!existsSync12(join14(source, "SKILL.md"))) {
|
|
18425
18470
|
throw new Error(`Bundled base skill is missing: ${name}`);
|
|
18426
18471
|
}
|
|
18427
|
-
replaceDirectory(source,
|
|
18472
|
+
replaceDirectory(source, join14(paths.baseRoot, name));
|
|
18428
18473
|
result.installed += 1;
|
|
18429
18474
|
}
|
|
18430
18475
|
for (const targetRoot of paths.targetRoots) {
|
|
18431
18476
|
mkdirSync9(targetRoot, { recursive: true, mode: 448 });
|
|
18432
18477
|
const targetLabel = relative5(home, targetRoot).replaceAll("/", "_") || "home";
|
|
18433
18478
|
for (const name of BASE_SKILL_NAMES) {
|
|
18434
|
-
const skillTarget =
|
|
18435
|
-
const linkPath =
|
|
18479
|
+
const skillTarget = join14(paths.baseRoot, name);
|
|
18480
|
+
const linkPath = join14(targetRoot, name);
|
|
18436
18481
|
if (sameLinkTarget(linkPath, skillTarget)) {
|
|
18437
18482
|
result.unchanged += 1;
|
|
18438
18483
|
continue;
|
|
@@ -18466,7 +18511,7 @@ var DEFAULT_LOCK_POLL_MS = 50;
|
|
|
18466
18511
|
async function lockSnapshot(lockDirectory) {
|
|
18467
18512
|
try {
|
|
18468
18513
|
const [raw, metadata] = await Promise.all([
|
|
18469
|
-
readFile(
|
|
18514
|
+
readFile(join15(lockDirectory, "owner"), "utf8").catch(() => ""),
|
|
18470
18515
|
stat(lockDirectory)
|
|
18471
18516
|
]);
|
|
18472
18517
|
let owner = {};
|
|
@@ -18502,11 +18547,11 @@ function delay3(milliseconds) {
|
|
|
18502
18547
|
return new Promise((resolve9) => setTimeout(resolve9, milliseconds));
|
|
18503
18548
|
}
|
|
18504
18549
|
async function quarantineStaleLock(lockDirectory, snapshot) {
|
|
18505
|
-
const quarantineRoot =
|
|
18550
|
+
const quarantineRoot = join15(dirname14(lockDirectory), ".stale-operation-locks");
|
|
18506
18551
|
const snapshotToken = createHash9("sha256").update(JSON.stringify(snapshot)).digest("hex");
|
|
18507
18552
|
await mkdir(quarantineRoot, { recursive: true, mode: 448 });
|
|
18508
18553
|
try {
|
|
18509
|
-
await rename(lockDirectory,
|
|
18554
|
+
await rename(lockDirectory, join15(quarantineRoot, snapshotToken));
|
|
18510
18555
|
return true;
|
|
18511
18556
|
} catch (error) {
|
|
18512
18557
|
if (["EEXIST", "ENOTEMPTY", "ENOENT"].includes(error?.code)) return false;
|
|
@@ -18514,16 +18559,16 @@ async function quarantineStaleLock(lockDirectory, snapshot) {
|
|
|
18514
18559
|
}
|
|
18515
18560
|
}
|
|
18516
18561
|
async function writeLockOwnerAtomically(lockDirectory, owner) {
|
|
18517
|
-
const temporaryOwnerPath =
|
|
18518
|
-
const ownerPath =
|
|
18562
|
+
const temporaryOwnerPath = join15(lockDirectory, `.owner.${owner.id}.tmp`);
|
|
18563
|
+
const ownerPath = join15(lockDirectory, "owner");
|
|
18519
18564
|
await writeFile(temporaryOwnerPath, JSON.stringify(owner), { mode: 384 });
|
|
18520
18565
|
await rename(temporaryOwnerPath, ownerPath);
|
|
18521
18566
|
}
|
|
18522
18567
|
async function releaseOwnedLock(lockDirectory, ownerId) {
|
|
18523
18568
|
const owner = await lockSnapshot(lockDirectory);
|
|
18524
18569
|
if (owner?.id !== ownerId) return;
|
|
18525
|
-
const quarantineRoot =
|
|
18526
|
-
const releasedDirectory =
|
|
18570
|
+
const quarantineRoot = join15(dirname14(lockDirectory), ".stale-operation-locks");
|
|
18571
|
+
const releasedDirectory = join15(quarantineRoot, `released.${ownerId}`);
|
|
18527
18572
|
await mkdir(quarantineRoot, { recursive: true, mode: 448 });
|
|
18528
18573
|
try {
|
|
18529
18574
|
await rename(lockDirectory, releasedDirectory);
|
|
@@ -18540,7 +18585,7 @@ async function withSkillSyncLock(callback, {
|
|
|
18540
18585
|
pollMs = DEFAULT_LOCK_POLL_MS,
|
|
18541
18586
|
now = () => Date.now()
|
|
18542
18587
|
} = {}) {
|
|
18543
|
-
const lockDirectory =
|
|
18588
|
+
const lockDirectory = join15(home, ".notis", "skills", ".operation-lock");
|
|
18544
18589
|
const ownerId = `${process.pid}.${randomUUID7()}`;
|
|
18545
18590
|
const deadline = now() + timeoutMs;
|
|
18546
18591
|
await mkdir(dirname14(lockDirectory), { recursive: true, mode: 448 });
|
|
@@ -18579,7 +18624,7 @@ async function withSkillSyncLock(callback, {
|
|
|
18579
18624
|
let heartbeatInFlight = Promise.resolve();
|
|
18580
18625
|
const refreshHeartbeat = async () => {
|
|
18581
18626
|
if (heartbeatStopped) return;
|
|
18582
|
-
const temporaryOwnerPath =
|
|
18627
|
+
const temporaryOwnerPath = join15(lockDirectory, `.owner.${ownerId}.tmp`);
|
|
18583
18628
|
await writeFile(
|
|
18584
18629
|
temporaryOwnerPath,
|
|
18585
18630
|
JSON.stringify({ id: ownerId, pid: process.pid, at: now() }),
|
|
@@ -18590,7 +18635,7 @@ async function withSkillSyncLock(callback, {
|
|
|
18590
18635
|
await rm(temporaryOwnerPath, { force: true });
|
|
18591
18636
|
return;
|
|
18592
18637
|
}
|
|
18593
|
-
await rename(temporaryOwnerPath,
|
|
18638
|
+
await rename(temporaryOwnerPath, join15(lockDirectory, "owner"));
|
|
18594
18639
|
};
|
|
18595
18640
|
const heartbeat = setInterval(() => {
|
|
18596
18641
|
heartbeatInFlight = heartbeatInFlight.then(refreshHeartbeat).catch(() => void 0);
|
|
@@ -18640,16 +18685,16 @@ init_oauth();
|
|
|
18640
18685
|
init_profiles();
|
|
18641
18686
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
18642
18687
|
import { createHash as createHash11 } from "node:crypto";
|
|
18643
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync17, renameSync as renameSync7, writeFileSync as
|
|
18688
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync17, renameSync as renameSync7, writeFileSync as writeFileSync10 } from "node:fs";
|
|
18644
18689
|
import { homedir as homedir8 } from "node:os";
|
|
18645
|
-
import { dirname as dirname15, join as
|
|
18690
|
+
import { dirname as dirname15, join as join16 } from "node:path";
|
|
18646
18691
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
18647
18692
|
var SKILL_SYNC_SERVICE_LABEL = "ai.notis.skills-sync";
|
|
18648
18693
|
var bundlePath = fileURLToPath10(new URL("../../dist/skill-sync-worker.mjs", import.meta.url));
|
|
18649
18694
|
function atomicWrite2(target, value, mode = 384) {
|
|
18650
18695
|
mkdirSync10(dirname15(target), { recursive: true, mode: 448 });
|
|
18651
18696
|
const temporary = `${target}.${process.pid}.tmp`;
|
|
18652
|
-
|
|
18697
|
+
writeFileSync10(temporary, value, { mode });
|
|
18653
18698
|
renameSync7(temporary, target);
|
|
18654
18699
|
}
|
|
18655
18700
|
function xml(value) {
|
|
@@ -18662,7 +18707,7 @@ async function maybeInstallSkillSyncService(runtime, {
|
|
|
18662
18707
|
refresh
|
|
18663
18708
|
} = {}) {
|
|
18664
18709
|
if (process.platform !== "darwin" || runtime.credentialKind !== "oauth" || config.current_profile !== runtime.profileName || !["https://api.notis.ai", "https://api-beta.notis.ai"].includes(runtime.apiBase)) return;
|
|
18665
|
-
const plist =
|
|
18710
|
+
const plist = join16(homedir8(), "Library", "LaunchAgents", `${SKILL_SYNC_SERVICE_LABEL}.plist`);
|
|
18666
18711
|
if (existsSync13(plist)) {
|
|
18667
18712
|
const saved = readFileSync17(plist, "utf8");
|
|
18668
18713
|
if (runtime.oauthUserId && [runtime.profileName, runtime.apiBase, runtime.oauthUserId].every((value) => saved.includes(`<string>${xml(value)}</string>`))) return install(runtime);
|
|
@@ -18696,10 +18741,10 @@ async function installSkillSyncServiceLocked(runtime, {
|
|
|
18696
18741
|
if (runtime.credentialKind !== "oauth" || runtime.envCredentialOverride || !isValidProfileName(runtime.profileName) || !["https://api.notis.ai", "https://api-beta.notis.ai"].includes(runtime.apiBase)) {
|
|
18697
18742
|
return { status: "skipped_non_personal_profile" };
|
|
18698
18743
|
}
|
|
18699
|
-
const root =
|
|
18744
|
+
const root = join16(home, ".notis", "skills", "service");
|
|
18700
18745
|
const bundle = readFileSync17(source);
|
|
18701
18746
|
const digest = createHash11("sha256").update(bundle).digest("hex");
|
|
18702
|
-
const installedBundle =
|
|
18747
|
+
const installedBundle = join16(root, "runtime", digest, "worker.mjs");
|
|
18703
18748
|
if (!existsSync13(installedBundle) || !readFileSync17(installedBundle).equals(bundle)) atomicWrite2(installedBundle, bundle, 320);
|
|
18704
18749
|
const args = [nodePath, installedBundle, runtime.profileName, runtime.apiBase, runtime.oauthUserId];
|
|
18705
18750
|
if (!runtime.oauthUserId) return { status: "skipped_missing_account" };
|
|
@@ -18713,7 +18758,7 @@ async function installSkillSyncServiceLocked(runtime, {
|
|
|
18713
18758
|
<key>ProcessType</key><string>Background</string>
|
|
18714
18759
|
</dict></plist>
|
|
18715
18760
|
`;
|
|
18716
|
-
const plistPath =
|
|
18761
|
+
const plistPath = join16(home, "Library", "LaunchAgents", `${SKILL_SYNC_SERVICE_LABEL}.plist`);
|
|
18717
18762
|
const domain = `gui/${uid}`;
|
|
18718
18763
|
const target = `${domain}/${SKILL_SYNC_SERVICE_LABEL}`;
|
|
18719
18764
|
const unchanged = existsSync13(plistPath) && readFileSync17(plistPath, "utf8") === plist;
|
|
@@ -18916,7 +18961,7 @@ init_channel();
|
|
|
18916
18961
|
init_profiles();
|
|
18917
18962
|
function readCliVersion() {
|
|
18918
18963
|
try {
|
|
18919
|
-
const manifestPath =
|
|
18964
|
+
const manifestPath = join17(dirname16(fileURLToPath11(import.meta.url)), "..", "package.json");
|
|
18920
18965
|
const version = JSON.parse(readFileSync18(manifestPath, "utf-8")).version;
|
|
18921
18966
|
if (typeof version === "string" && version.trim()) {
|
|
18922
18967
|
return version.trim();
|