@notis_ai/cli 0.2.0-beta.159.1 → 0.2.0-beta.160.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 +94 -0
- package/dist/agent-hooks/notis-agent-hook.mjs +180 -56
- package/dist/base-skills/notis-apps/SKILL.md +21 -2
- package/dist/base-skills/notis-apps/references/context.md +81 -0
- package/dist/base-skills/notis-apps/references/reading.md +89 -0
- package/dist/base-skills/notis-apps/references/sdk.md +1 -0
- package/dist/skill-sync/index.js +25 -6
- package/dist/skill-sync/index.js.map +2 -2
- package/dist/skill-sync-worker.mjs +2 -1
- package/package.json +1 -1
- package/skills/notis-apps/cli.md +89 -0
- package/skills/notis-onboarding/BRIEF.md +6 -5
- package/src/command-specs/apps.js +1 -1
- package/src/command-specs/index.js +3 -0
- package/src/command-specs/onboarding.js +1 -1
- package/src/command-specs/reports.js +86 -0
- package/src/runtime/skill-sync/index.ts +31 -4
- package/template/packages/sdk/src/agentContext.ts +36 -0
- package/template/packages/sdk/src/components/NotisCommentBoundary.tsx +172 -0
- package/template/packages/sdk/src/components/NotisSelectionBoundary.tsx +9 -5
- package/template/packages/sdk/src/hooks/useAgentContext.ts +23 -0
- package/template/packages/sdk/src/index.ts +5 -0
- package/template/packages/sdk/src/runtime.ts +9 -2
- package/template/packages/sdk/src/tailwind.ts +56 -0
- package/template/packages/sdk/src/vite.ts +2 -0
- package/template/tailwind.config.ts +1 -0
|
@@ -4452,13 +4452,13 @@ async function createLoopbackReceiver({
|
|
|
4452
4452
|
let pendingResponse = null;
|
|
4453
4453
|
const sockets = /* @__PURE__ */ new Set();
|
|
4454
4454
|
let responseFlushed = Promise.resolve();
|
|
4455
|
-
const result = new Promise((
|
|
4456
|
-
resolveCode =
|
|
4455
|
+
const result = new Promise((resolve9, reject) => {
|
|
4456
|
+
resolveCode = resolve9;
|
|
4457
4457
|
rejectCode = reject;
|
|
4458
4458
|
});
|
|
4459
4459
|
const endResponse = (response, body) => {
|
|
4460
|
-
responseFlushed = new Promise((
|
|
4461
|
-
response.end(body,
|
|
4460
|
+
responseFlushed = new Promise((resolve9) => {
|
|
4461
|
+
response.end(body, resolve9);
|
|
4462
4462
|
});
|
|
4463
4463
|
};
|
|
4464
4464
|
const server = createServer3((request, response) => {
|
|
@@ -4507,11 +4507,11 @@ async function createLoopbackReceiver({
|
|
|
4507
4507
|
sockets.add(socket);
|
|
4508
4508
|
socket.on("close", () => sockets.delete(socket));
|
|
4509
4509
|
});
|
|
4510
|
-
await new Promise((
|
|
4510
|
+
await new Promise((resolve9, reject) => {
|
|
4511
4511
|
server.once("error", reject);
|
|
4512
4512
|
server.listen(0, "127.0.0.1", () => {
|
|
4513
4513
|
server.off("error", reject);
|
|
4514
|
-
|
|
4514
|
+
resolve9();
|
|
4515
4515
|
});
|
|
4516
4516
|
});
|
|
4517
4517
|
const address = server.address();
|
|
@@ -4535,14 +4535,14 @@ async function createLoopbackReceiver({
|
|
|
4535
4535
|
}
|
|
4536
4536
|
await Promise.race([
|
|
4537
4537
|
responseFlushed,
|
|
4538
|
-
new Promise((
|
|
4539
|
-
setTimeout(
|
|
4538
|
+
new Promise((resolve9) => {
|
|
4539
|
+
setTimeout(resolve9, RESPONSE_FLUSH_GRACE_MS).unref?.();
|
|
4540
4540
|
})
|
|
4541
4541
|
]);
|
|
4542
4542
|
for (const socket of sockets) socket.destroy();
|
|
4543
4543
|
sockets.clear();
|
|
4544
4544
|
if (!server.listening) return;
|
|
4545
|
-
await new Promise((
|
|
4545
|
+
await new Promise((resolve9) => server.close(resolve9));
|
|
4546
4546
|
};
|
|
4547
4547
|
return {
|
|
4548
4548
|
port,
|
|
@@ -4842,7 +4842,7 @@ async function acquireListenerStartLock(runtime) {
|
|
|
4842
4842
|
"Timed out waiting for another CLI process to start browser authorization."
|
|
4843
4843
|
);
|
|
4844
4844
|
}
|
|
4845
|
-
await new Promise((
|
|
4845
|
+
await new Promise((resolve9) => setTimeout(resolve9, 50));
|
|
4846
4846
|
}
|
|
4847
4847
|
}
|
|
4848
4848
|
}
|
|
@@ -4889,7 +4889,7 @@ async function acquireListenerGlobalLock({
|
|
|
4889
4889
|
"Timed out waiting for another CLI process to finish OAuth account changes."
|
|
4890
4890
|
);
|
|
4891
4891
|
}
|
|
4892
|
-
await new Promise((
|
|
4892
|
+
await new Promise((resolve9) => setTimeout(resolve9, 50));
|
|
4893
4893
|
}
|
|
4894
4894
|
}
|
|
4895
4895
|
}
|
|
@@ -5066,7 +5066,7 @@ async function startDetachedLoopbackListener(runtime, {
|
|
|
5066
5066
|
}
|
|
5067
5067
|
return null;
|
|
5068
5068
|
}
|
|
5069
|
-
const port = await new Promise((
|
|
5069
|
+
const port = await new Promise((resolve9) => {
|
|
5070
5070
|
let settled = false;
|
|
5071
5071
|
const finish = (value) => {
|
|
5072
5072
|
if (settled) return;
|
|
@@ -5075,7 +5075,7 @@ async function startDetachedLoopbackListener(runtime, {
|
|
|
5075
5075
|
child.off("message", onMessage);
|
|
5076
5076
|
child.off("error", onFailure);
|
|
5077
5077
|
child.off("exit", onFailure);
|
|
5078
|
-
|
|
5078
|
+
resolve9(value);
|
|
5079
5079
|
};
|
|
5080
5080
|
const onMessage = (message) => finish(Number(message?.port) || null);
|
|
5081
5081
|
const onFailure = () => finish(null);
|
|
@@ -5902,7 +5902,7 @@ async function acquireRefreshLock(runtime, waitMs = 6e4) {
|
|
|
5902
5902
|
if (Date.now() >= deadline) {
|
|
5903
5903
|
throw oauthError("oauth_refresh_lock_timeout", "Timed out waiting for another CLI process to refresh OAuth.");
|
|
5904
5904
|
}
|
|
5905
|
-
await new Promise((
|
|
5905
|
+
await new Promise((resolve9) => setTimeout(resolve9, 100));
|
|
5906
5906
|
}
|
|
5907
5907
|
}
|
|
5908
5908
|
}
|
|
@@ -7339,7 +7339,7 @@ function applyLegacyFirstRunState(localSkills, scopedState, legacyState) {
|
|
|
7339
7339
|
skills: migratedSkills
|
|
7340
7340
|
};
|
|
7341
7341
|
}
|
|
7342
|
-
async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previousState, syncPaths, deps, failures = []) {
|
|
7342
|
+
async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previousState, syncPaths, deps, failures = [], writtenSkillNames = /* @__PURE__ */ new Set()) {
|
|
7343
7343
|
const localSkillMap = toSkillMap(localSkills);
|
|
7344
7344
|
const warnSkillSync = (message, error) => {
|
|
7345
7345
|
console.warn(`[Notis] ${message}`, error);
|
|
@@ -7355,6 +7355,7 @@ async function writePulledSkillsToScopedMirror(pullResponse, localSkills, previo
|
|
|
7355
7355
|
onWarning: warnSkillSync
|
|
7356
7356
|
})) {
|
|
7357
7357
|
downloaded += 1;
|
|
7358
|
+
writtenSkillNames.add(cloudSkill.name);
|
|
7358
7359
|
} else {
|
|
7359
7360
|
failures.push({ name: cloudSkill.name, error: "Skill content could not be downloaded or written; sync will retry" });
|
|
7360
7361
|
}
|
|
@@ -7379,19 +7380,21 @@ async function materializeCloudSkillsForLocalShell(serverUrl, jwt, dependencies
|
|
|
7379
7380
|
"Cannot materialize skills without a valid authenticated desktop session."
|
|
7380
7381
|
);
|
|
7381
7382
|
}
|
|
7382
|
-
const syncPaths = getSkillSyncPathsForUser(authUserId);
|
|
7383
|
+
const syncPaths = getSkillSyncPathsForUser(options.canonicalUserId?.trim() || authUserId);
|
|
7383
7384
|
const pullResponse = await deps.pullSkills(serverUrl, jwt);
|
|
7384
7385
|
assertSkillsPullAuthorized(pullResponse);
|
|
7385
7386
|
const previousState = await deps.readSyncState(syncPaths);
|
|
7386
7387
|
const localSkills = await deps.scanLocalSkills(syncPaths);
|
|
7387
7388
|
const failedDownloads = [];
|
|
7389
|
+
const writtenSkillNames = /* @__PURE__ */ new Set();
|
|
7388
7390
|
const downloaded = await writePulledSkillsToScopedMirror(
|
|
7389
7391
|
pullResponse,
|
|
7390
7392
|
localSkills,
|
|
7391
7393
|
previousState,
|
|
7392
7394
|
syncPaths,
|
|
7393
7395
|
deps,
|
|
7394
|
-
failedDownloads
|
|
7396
|
+
failedDownloads,
|
|
7397
|
+
writtenSkillNames
|
|
7395
7398
|
);
|
|
7396
7399
|
const finalLocalSkills = await deps.scanLocalSkills(syncPaths);
|
|
7397
7400
|
const lastSyncedAt = pullResponse.last_synced_at || (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -7418,11 +7421,27 @@ async function materializeCloudSkillsForLocalShell(serverUrl, jwt, dependencies
|
|
|
7418
7421
|
}
|
|
7419
7422
|
}
|
|
7420
7423
|
for (const failure of failedDownloads) delete verifiedLinks[failure.name];
|
|
7421
|
-
|
|
7422
|
-
|
|
7423
|
-
|
|
7424
|
+
const materializedState = buildSyncState(
|
|
7425
|
+
pullResponse,
|
|
7426
|
+
finalLocalSkills,
|
|
7427
|
+
lastSyncedAt,
|
|
7428
|
+
verifiedLinks,
|
|
7429
|
+
new Set(failedDownloads.map((item) => item.name))
|
|
7424
7430
|
);
|
|
7431
|
+
for (const [name, entry] of Object.entries(materializedState.skills)) {
|
|
7432
|
+
if (writtenSkillNames.has(name)) continue;
|
|
7433
|
+
const previous = previousState.skills[name];
|
|
7434
|
+
if (previous) {
|
|
7435
|
+
entry.folderHash = previous.folderHash;
|
|
7436
|
+
entry.cloudContentHash = previous.cloudContentHash;
|
|
7437
|
+
} else {
|
|
7438
|
+
delete materializedState.skills[name];
|
|
7439
|
+
}
|
|
7440
|
+
}
|
|
7441
|
+
materializedState.skills = { ...previousState.skills, ...materializedState.skills };
|
|
7442
|
+
await deps.writeSyncState(materializedState, syncPaths);
|
|
7425
7443
|
return {
|
|
7444
|
+
materializedSkillNames: pullResponse.skills.filter((skill) => finalLocalSkills.some((local) => local.name === skill.name) && !failedDownloads.some((failure) => failure.name === skill.name)).map((skill) => skill.name),
|
|
7426
7445
|
pulled: pullResponse.skills.length,
|
|
7427
7446
|
downloaded,
|
|
7428
7447
|
deleted: 0,
|
|
@@ -7711,7 +7730,7 @@ var init_skill_sync = __esm({
|
|
|
7711
7730
|
});
|
|
7712
7731
|
|
|
7713
7732
|
// src/cli.js
|
|
7714
|
-
import { readFileSync as
|
|
7733
|
+
import { readFileSync as readFileSync18 } from "node:fs";
|
|
7715
7734
|
import { dirname as dirname16, join as join16 } from "node:path";
|
|
7716
7735
|
import { fileURLToPath as fileURLToPath11 } from "node:url";
|
|
7717
7736
|
|
|
@@ -7732,6 +7751,10 @@ var {
|
|
|
7732
7751
|
Help
|
|
7733
7752
|
} = import_index.default;
|
|
7734
7753
|
|
|
7754
|
+
// src/command-specs/reports.js
|
|
7755
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
7756
|
+
import { resolve as resolve6 } from "node:path";
|
|
7757
|
+
|
|
7735
7758
|
// src/command-specs/apps.js
|
|
7736
7759
|
init_errors();
|
|
7737
7760
|
import { mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync3, readdirSync as readdirSync5, rmSync as rmSync5 } from "node:fs";
|
|
@@ -13172,7 +13195,7 @@ ${listing.errors.map((error) => ` - ${error}`).join("\n")}`);
|
|
|
13172
13195
|
renderHuman: () => renderVerifyReport({ summary, results, noBrowser })
|
|
13173
13196
|
});
|
|
13174
13197
|
if (keepOpen) {
|
|
13175
|
-
process.stderr.write(`[notis apps verify] harness open at ${baseUrl}. Press Ctrl-C to stop.
|
|
13198
|
+
process.stderr.write(`[notis apps verify] harness open at ${urls[0]?.url || baseUrl}. Press Ctrl-C to stop.
|
|
13176
13199
|
`);
|
|
13177
13200
|
await new Promise(() => {
|
|
13178
13201
|
});
|
|
@@ -14071,9 +14094,108 @@ var appsCommandSpecs = [
|
|
|
14071
14094
|
}
|
|
14072
14095
|
];
|
|
14073
14096
|
|
|
14097
|
+
// src/command-specs/reports.js
|
|
14098
|
+
init_errors();
|
|
14099
|
+
var reuse = (command, name = command) => {
|
|
14100
|
+
const spec = appsCommandSpecs.find((item) => item.command_path.join(" ") === `apps ${command}`);
|
|
14101
|
+
return {
|
|
14102
|
+
...spec,
|
|
14103
|
+
handler: async (ctx) => {
|
|
14104
|
+
const output = new Proxy(ctx.output, {
|
|
14105
|
+
get(target, key) {
|
|
14106
|
+
if (key === "emitSuccess") return (result) => {
|
|
14107
|
+
const value2 = { ...result, warnings: (result.warnings || []).filter((warning) => !warning.startsWith("Store readiness:")) };
|
|
14108
|
+
if (value2.data?.listing) {
|
|
14109
|
+
value2.data = { ...value2.data };
|
|
14110
|
+
delete value2.data.listing;
|
|
14111
|
+
}
|
|
14112
|
+
return target.emitSuccess(value2);
|
|
14113
|
+
};
|
|
14114
|
+
const value = Reflect.get(target, key);
|
|
14115
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
14116
|
+
}
|
|
14117
|
+
});
|
|
14118
|
+
return spec.handler({ ...ctx, options: { ...ctx.options, listing: false, ...name === "preview" ? { keepOpen: true } : {} }, output });
|
|
14119
|
+
},
|
|
14120
|
+
command_path: ["reports", name],
|
|
14121
|
+
summary: `${name[0].toUpperCase() + name.slice(1)} a record-owned SDK report locally.${name === "preview" ? " Keeps the preview server and browser session open." : ""}`,
|
|
14122
|
+
args_schema: {
|
|
14123
|
+
...spec.args_schema,
|
|
14124
|
+
options: (spec.args_schema?.options || []).map((option) => option.flags === "--listing" ? { ...option, description: "Ignored for reports; saving a report does not publish a Store listing." } : option)
|
|
14125
|
+
},
|
|
14126
|
+
examples: (spec.examples || []).filter((example) => !example.includes("--listing")).map((example) => example.replace(`apps ${command}`, `reports ${name}`)),
|
|
14127
|
+
when_to_use: "Author an independent report without deploying its owning app."
|
|
14128
|
+
};
|
|
14129
|
+
};
|
|
14130
|
+
var reportsCommandSpecs = [
|
|
14131
|
+
reuse("init"),
|
|
14132
|
+
reuse("build"),
|
|
14133
|
+
reuse("verify"),
|
|
14134
|
+
reuse("verify", "preview"),
|
|
14135
|
+
{
|
|
14136
|
+
command_path: ["reports", "save"],
|
|
14137
|
+
summary: "Build, verify and save a report into an app-owned database record.",
|
|
14138
|
+
when_to_use: "Persist an independently authored report, not an app release.",
|
|
14139
|
+
args_schema: {
|
|
14140
|
+
arguments: [{ token: "[dir]", key: "dir", description: "Report source directory." }],
|
|
14141
|
+
options: [
|
|
14142
|
+
{ flags: "--database-id <id>", description: "Required. Owning app database." },
|
|
14143
|
+
{ flags: "--document-id <id>", description: "Existing record to update or attach to." },
|
|
14144
|
+
{ flags: "--attach", description: "Attach to an existing non-view record." },
|
|
14145
|
+
{ flags: "--expected-revision <revision>", description: "Fresh view revision (0 for a record without a view)." },
|
|
14146
|
+
{ flags: "--title <title>", description: "Required, including updates. Record title." },
|
|
14147
|
+
{ flags: "--context-file <file>", description: "Required. UTF-8 readable report content and structure." },
|
|
14148
|
+
{ flags: "--properties-file <file>", description: "JSON database property values keyed by name." }
|
|
14149
|
+
]
|
|
14150
|
+
},
|
|
14151
|
+
examples: ['notis reports save ./weekly-report --database-id <id> --title "Weekly review" --context-file ./context.md'],
|
|
14152
|
+
mutates: true,
|
|
14153
|
+
idempotent: true,
|
|
14154
|
+
backend_call: { type: "tool", name: "LOCAL_NOTIS_SAVE_REPORT" },
|
|
14155
|
+
async handler(ctx) {
|
|
14156
|
+
const dir = resolveProjectDir(ctx.args.dir || ".");
|
|
14157
|
+
if (!ctx.options.databaseId || !ctx.options.title || !ctx.options.contextFile) throw usageError("--database-id, --title and --context-file are required.");
|
|
14158
|
+
if (ctx.options.documentId && (!/^\d+$/.test(String(ctx.options.expectedRevision ?? "")) || !Number.isSafeInteger(Number(ctx.options.expectedRevision)))) throw usageError("--expected-revision is required for update/attach.");
|
|
14159
|
+
if (ctx.options.attach && !ctx.options.documentId) throw usageError("--attach requires --document-id.");
|
|
14160
|
+
const context = readFileSync9(resolve6(ctx.options.contextFile), "utf8");
|
|
14161
|
+
const properties = ctx.options.propertiesFile ? JSON.parse(readFileSync9(resolve6(ctx.options.propertiesFile), "utf8")) : {};
|
|
14162
|
+
await buildArtifact(dir, { stdio: ctx.output.isMachineMode() ? "pipe" : "inherit" });
|
|
14163
|
+
const release = prepareAppRelease(dir);
|
|
14164
|
+
try {
|
|
14165
|
+
if (release.manifest.routes?.length !== 1) throw usageError("Reports require exactly one SDK route.");
|
|
14166
|
+
let verification;
|
|
14167
|
+
const verify = appsCommandSpecs.find((item) => item.command_path.join(" ") === "apps verify");
|
|
14168
|
+
const exit = await verify.handler({ ...ctx, args: { dir: release.projectDir }, options: { skipBuild: true, mode: "stub" }, output: { ...ctx.output, isMachineMode: () => true, emitSuccess: (value) => {
|
|
14169
|
+
verification = value;
|
|
14170
|
+
} } });
|
|
14171
|
+
if (exit !== EXIT_CODES.ok || verification?.data?.status !== "passed") throw usageError("Report verification failed; nothing saved.");
|
|
14172
|
+
const files = { ...release.files, ...Object.fromEntries(Object.entries(release.sourceFiles).map(([path3, data]) => [`source/${path3}`, data])) };
|
|
14173
|
+
const result = await runToolCommand({
|
|
14174
|
+
runtime: { ...ctx.runtime, timeoutMs: Math.max(ctx.runtime.timeoutMs || 0, 9e4) },
|
|
14175
|
+
toolName: "LOCAL_NOTIS_SAVE_REPORT",
|
|
14176
|
+
mutating: true,
|
|
14177
|
+
idempotencyKey: nextIdempotencyKey(ctx.globalOptions),
|
|
14178
|
+
arguments_: {
|
|
14179
|
+
operation: ctx.options.attach ? "attach" : ctx.options.documentId ? "update" : "create",
|
|
14180
|
+
database_id: ctx.options.databaseId,
|
|
14181
|
+
title: ctx.options.title,
|
|
14182
|
+
properties,
|
|
14183
|
+
...ctx.options.documentId ? { document_id: ctx.options.documentId, expected_revision: Number(ctx.options.expectedRevision) } : {},
|
|
14184
|
+
report: { schema: "notis-report/v2", context, artifact: { manifest: release.manifest, files, encoding: "base64" } }
|
|
14185
|
+
}
|
|
14186
|
+
});
|
|
14187
|
+
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.");
|
|
14188
|
+
return ctx.output.emitSuccess({ command: "reports save", data: result.payload });
|
|
14189
|
+
} finally {
|
|
14190
|
+
release.close();
|
|
14191
|
+
}
|
|
14192
|
+
}
|
|
14193
|
+
}
|
|
14194
|
+
];
|
|
14195
|
+
|
|
14074
14196
|
// src/command-specs/tools.js
|
|
14075
14197
|
init_errors();
|
|
14076
|
-
import { accessSync, constants as fsConstants2, createReadStream as createReadStream2, existsSync as existsSync8, readFileSync as
|
|
14198
|
+
import { accessSync, constants as fsConstants2, createReadStream as createReadStream2, existsSync as existsSync8, readFileSync as readFileSync10, statSync as statSync5 } from "node:fs";
|
|
14077
14199
|
import { createHash as createHash3 } from "node:crypto";
|
|
14078
14200
|
import { basename as basename4 } from "node:path";
|
|
14079
14201
|
async function resolveJsonInput(value, label) {
|
|
@@ -14090,7 +14212,7 @@ async function resolveJsonInput(value, label) {
|
|
|
14090
14212
|
if (!existsSync8(filePath)) {
|
|
14091
14213
|
throw usageError(`File not found: ${filePath}`);
|
|
14092
14214
|
}
|
|
14093
|
-
return parseJson(
|
|
14215
|
+
return parseJson(readFileSync10(filePath, "utf-8"), label);
|
|
14094
14216
|
}
|
|
14095
14217
|
return parseJson(value, label);
|
|
14096
14218
|
}
|
|
@@ -14978,7 +15100,7 @@ init_errors();
|
|
|
14978
15100
|
init_auth_recovery();
|
|
14979
15101
|
init_profiles();
|
|
14980
15102
|
init_oauth();
|
|
14981
|
-
import { readFileSync as
|
|
15103
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
14982
15104
|
import { dirname as dirname12, join as join11 } from "node:path";
|
|
14983
15105
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
14984
15106
|
|
|
@@ -14994,7 +15116,7 @@ import {
|
|
|
14994
15116
|
chmodSync,
|
|
14995
15117
|
existsSync as existsSync9,
|
|
14996
15118
|
mkdirSync as mkdirSync7,
|
|
14997
|
-
readFileSync as
|
|
15119
|
+
readFileSync as readFileSync11,
|
|
14998
15120
|
readdirSync as readdirSync6,
|
|
14999
15121
|
renameSync as renameSync4,
|
|
15000
15122
|
writeFileSync as writeFileSync6
|
|
@@ -15019,7 +15141,7 @@ function atomicWrite(filePath, contents, mode = 384) {
|
|
|
15019
15141
|
renameSync4(temporaryPath, filePath);
|
|
15020
15142
|
}
|
|
15021
15143
|
function readText(filePath) {
|
|
15022
|
-
return existsSync9(filePath) ?
|
|
15144
|
+
return existsSync9(filePath) ? readFileSync11(filePath, "utf-8") : "";
|
|
15023
15145
|
}
|
|
15024
15146
|
function activeCodexInstructionsPath(home) {
|
|
15025
15147
|
const overridePath = join9(home, ".codex", "AGENTS.override.md");
|
|
@@ -15062,7 +15184,7 @@ function upsertInstructionBlock(filePath, block) {
|
|
|
15062
15184
|
}
|
|
15063
15185
|
function readJsonObject(filePath) {
|
|
15064
15186
|
if (!existsSync9(filePath)) return {};
|
|
15065
|
-
const raw =
|
|
15187
|
+
const raw = readFileSync11(filePath, "utf-8");
|
|
15066
15188
|
try {
|
|
15067
15189
|
const parsed = JSON.parse(raw);
|
|
15068
15190
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
@@ -15114,7 +15236,7 @@ function installManagedHookRuntime({
|
|
|
15114
15236
|
nodePath = process.execPath,
|
|
15115
15237
|
platform = process.platform
|
|
15116
15238
|
} = {}) {
|
|
15117
|
-
const bundle =
|
|
15239
|
+
const bundle = readFileSync11(bundlePath2);
|
|
15118
15240
|
const digest = createHash4("sha256").update(bundle).digest("hex");
|
|
15119
15241
|
const runtimePath = join9(
|
|
15120
15242
|
home,
|
|
@@ -15124,7 +15246,7 @@ function installManagedHookRuntime({
|
|
|
15124
15246
|
digest,
|
|
15125
15247
|
"notis-agent-hook.mjs"
|
|
15126
15248
|
);
|
|
15127
|
-
const existingDigest = existsSync9(runtimePath) ? createHash4("sha256").update(
|
|
15249
|
+
const existingDigest = existsSync9(runtimePath) ? createHash4("sha256").update(readFileSync11(runtimePath)).digest("hex") : null;
|
|
15128
15250
|
if (existingDigest !== digest) atomicWrite(runtimePath, bundle, 320);
|
|
15129
15251
|
chmodSync(runtimePath, 320);
|
|
15130
15252
|
const launcherPath = join9(
|
|
@@ -15243,7 +15365,7 @@ function installAgentSetup({
|
|
|
15243
15365
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(String(profileName || ""))) {
|
|
15244
15366
|
throw new Error("A valid authenticated Notis profile is required for agent setup");
|
|
15245
15367
|
}
|
|
15246
|
-
const instructions =
|
|
15368
|
+
const instructions = readFileSync11(INSTRUCTIONS_PATH, "utf-8").trim();
|
|
15247
15369
|
const results = [];
|
|
15248
15370
|
const shouldRepairManagedHooks = memoryHooks === true || memoryHooks === null && agents.some((agentId) => fileHasManagedNotisHook(agentPaths(agentId, home).hooks));
|
|
15249
15371
|
const hookRuntime = shouldRepairManagedHooks ? installManagedHookRuntime({ home, platform }) : null;
|
|
@@ -15289,7 +15411,7 @@ import { createHash as createHash5 } from "node:crypto";
|
|
|
15289
15411
|
import {
|
|
15290
15412
|
existsSync as existsSync10,
|
|
15291
15413
|
mkdirSync as mkdirSync8,
|
|
15292
|
-
readFileSync as
|
|
15414
|
+
readFileSync as readFileSync12,
|
|
15293
15415
|
renameSync as renameSync5,
|
|
15294
15416
|
writeFileSync as writeFileSync7
|
|
15295
15417
|
} from "node:fs";
|
|
@@ -15308,7 +15430,7 @@ function readState(sessionId, home) {
|
|
|
15308
15430
|
const filePath = statePath(sessionId, home);
|
|
15309
15431
|
if (!filePath || !existsSync10(filePath)) return { seen: [], pending: null };
|
|
15310
15432
|
try {
|
|
15311
|
-
const parsed = JSON.parse(
|
|
15433
|
+
const parsed = JSON.parse(readFileSync12(filePath, "utf-8"));
|
|
15312
15434
|
return parsed && typeof parsed === "object" ? {
|
|
15313
15435
|
seen: Array.isArray(parsed.seen) ? parsed.seen.filter((item) => typeof item === "string") : [],
|
|
15314
15436
|
pending: parsed.pending && typeof parsed.pending === "object" ? parsed.pending : null,
|
|
@@ -15885,7 +16007,7 @@ async function fetchBrief(apiBase, timeoutMs) {
|
|
|
15885
16007
|
} catch {
|
|
15886
16008
|
}
|
|
15887
16009
|
try {
|
|
15888
|
-
return { markdown:
|
|
16010
|
+
return { markdown: readFileSync13(BUNDLED_BRIEF_PATH, "utf-8"), source: "bundled" };
|
|
15889
16011
|
} catch {
|
|
15890
16012
|
return { markdown: null, source: null };
|
|
15891
16013
|
}
|
|
@@ -15941,7 +16063,7 @@ async function authenticatedResult(ctx) {
|
|
|
15941
16063
|
base.agent_setup = [];
|
|
15942
16064
|
}
|
|
15943
16065
|
if (onboardingComplete) {
|
|
15944
|
-
const name = state?.settings?.
|
|
16066
|
+
const name = state?.settings?.first_name;
|
|
15945
16067
|
const setupSummary2 = renderAgentSetup(base.agent_setup);
|
|
15946
16068
|
return ctx.output.emitSuccess({
|
|
15947
16069
|
command: "start",
|
|
@@ -16054,7 +16176,7 @@ var onboardingCommandSpecs = [
|
|
|
16054
16176
|
// src/command-specs/diagnostics.js
|
|
16055
16177
|
init_errors();
|
|
16056
16178
|
import { createHash as createHash7, randomUUID as randomUUID5 } from "node:crypto";
|
|
16057
|
-
import { existsSync as existsSync11, readFileSync as
|
|
16179
|
+
import { existsSync as existsSync11, readFileSync as readFileSync14 } from "node:fs";
|
|
16058
16180
|
var ENTITLEMENT_FIELDS = [
|
|
16059
16181
|
"created_at",
|
|
16060
16182
|
"included_credit_seat_count",
|
|
@@ -16460,7 +16582,7 @@ ORDER BY i.created_at ASC;`;
|
|
|
16460
16582
|
function traceFileDiagnostics(path3) {
|
|
16461
16583
|
if (!path3) return null;
|
|
16462
16584
|
if (!existsSync11(path3)) throw usageError(`Trace file not found: ${path3}`);
|
|
16463
|
-
const trace = JSON.parse(
|
|
16585
|
+
const trace = JSON.parse(readFileSync14(path3, "utf-8"));
|
|
16464
16586
|
const text = JSON.stringify(trace).toLowerCase();
|
|
16465
16587
|
const modelCounts = {};
|
|
16466
16588
|
const generationIds = /* @__PURE__ */ new Set();
|
|
@@ -16691,7 +16813,7 @@ init_errors();
|
|
|
16691
16813
|
import { createHash as createHash8, randomUUID as randomUUID6 } from "node:crypto";
|
|
16692
16814
|
import {
|
|
16693
16815
|
mkdtempSync as mkdtempSync4,
|
|
16694
|
-
readFileSync as
|
|
16816
|
+
readFileSync as readFileSync15,
|
|
16695
16817
|
rmSync as rmSync6,
|
|
16696
16818
|
writeFileSync as writeFileSync8
|
|
16697
16819
|
} from "node:fs";
|
|
@@ -16792,7 +16914,7 @@ unicode=\xE9t\xE9 Z\xFCrich \u6771\u4EAC
|
|
|
16792
16914
|
);
|
|
16793
16915
|
}
|
|
16794
16916
|
const localHash = await hashFileSha256(fixturePath);
|
|
16795
|
-
const localBytes =
|
|
16917
|
+
const localBytes = readFileSync15(fixturePath);
|
|
16796
16918
|
const destination = remotePath(
|
|
16797
16919
|
ctx.options.remoteFolder,
|
|
16798
16920
|
`notis-file-upload-${marker}-${basename6(fixturePath)}`
|
|
@@ -17320,8 +17442,8 @@ init_errors();
|
|
|
17320
17442
|
// src/runtime/git.js
|
|
17321
17443
|
init_errors();
|
|
17322
17444
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
17323
|
-
import { lstatSync as lstatSync2, readFileSync as
|
|
17324
|
-
import { basename as basename7, relative as relative4, resolve as
|
|
17445
|
+
import { lstatSync as lstatSync2, readFileSync as readFileSync16 } from "node:fs";
|
|
17446
|
+
import { basename as basename7, relative as relative4, resolve as resolve7, sep as sep2 } from "node:path";
|
|
17325
17447
|
var GIT_TIMEOUT_MS = 12e4;
|
|
17326
17448
|
var MAX_SECRET_SCAN_BYTES = 1e6;
|
|
17327
17449
|
var SAFE_ENV_TEMPLATES = /^\.env\.(?:example|sample|template)$/i;
|
|
@@ -17375,10 +17497,10 @@ function filesToAutoCommit(repository) {
|
|
|
17375
17497
|
])];
|
|
17376
17498
|
}
|
|
17377
17499
|
function sensitiveAutoCommitFiles(repository) {
|
|
17378
|
-
const root =
|
|
17500
|
+
const root = resolve7(repository.toplevel);
|
|
17379
17501
|
const sensitive = [];
|
|
17380
17502
|
for (const path3 of filesToAutoCommit(repository)) {
|
|
17381
|
-
const absolute =
|
|
17503
|
+
const absolute = resolve7(root, path3);
|
|
17382
17504
|
const insideRoot = relative4(root, absolute);
|
|
17383
17505
|
if (insideRoot === ".." || insideRoot.startsWith(`..${sep2}`) || insideRoot === "") continue;
|
|
17384
17506
|
const name = basename7(path3);
|
|
@@ -17389,7 +17511,7 @@ function sensitiveAutoCommitFiles(repository) {
|
|
|
17389
17511
|
try {
|
|
17390
17512
|
const stat2 = lstatSync2(absolute);
|
|
17391
17513
|
if (!stat2.isFile() || stat2.size > MAX_SECRET_SCAN_BYTES) continue;
|
|
17392
|
-
const content =
|
|
17514
|
+
const content = readFileSync16(absolute, "utf8");
|
|
17393
17515
|
if (SENSITIVE_CONTENT.some((pattern) => pattern.test(content))) sensitive.push(path3);
|
|
17394
17516
|
} catch {
|
|
17395
17517
|
}
|
|
@@ -17785,7 +17907,7 @@ import {
|
|
|
17785
17907
|
symlinkSync
|
|
17786
17908
|
} from "node:fs";
|
|
17787
17909
|
import { homedir as homedir6 } from "node:os";
|
|
17788
|
-
import { dirname as dirname13, join as join13, relative as relative5, resolve as
|
|
17910
|
+
import { dirname as dirname13, join as join13, relative as relative5, resolve as resolve8 } from "node:path";
|
|
17789
17911
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
17790
17912
|
var BASE_SKILL_NAMES = Object.freeze([
|
|
17791
17913
|
"notis-apps",
|
|
@@ -17794,8 +17916,8 @@ var BASE_SKILL_NAMES = Object.freeze([
|
|
|
17794
17916
|
]);
|
|
17795
17917
|
var HERE3 = dirname13(fileURLToPath9(import.meta.url));
|
|
17796
17918
|
function resolveBundledBaseSkillsRoot({ resourcesPath = process.resourcesPath } = {}) {
|
|
17797
|
-
const sourceCheckout =
|
|
17798
|
-
const packaged =
|
|
17919
|
+
const sourceCheckout = resolve8(HERE3, "../../../../server/skills");
|
|
17920
|
+
const packaged = resolve8(HERE3, "../../dist/base-skills");
|
|
17799
17921
|
const desktopResource = resourcesPath ? join13(resourcesPath, "base-skills") : null;
|
|
17800
17922
|
const desktopPackagerRoot = resourcesPath || null;
|
|
17801
17923
|
for (const candidate of [sourceCheckout, packaged, desktopResource, desktopPackagerRoot]) {
|
|
@@ -17825,7 +17947,7 @@ function getBaseSkillPaths({ home = homedir6(), userId = null } = {}) {
|
|
|
17825
17947
|
function sameLinkTarget(linkPath, expectedTarget) {
|
|
17826
17948
|
try {
|
|
17827
17949
|
if (!lstatSync3(linkPath).isSymbolicLink()) return false;
|
|
17828
|
-
return
|
|
17950
|
+
return resolve8(dirname13(linkPath), readlinkSync(linkPath)) === resolve8(expectedTarget);
|
|
17829
17951
|
} catch {
|
|
17830
17952
|
return false;
|
|
17831
17953
|
}
|
|
@@ -17952,7 +18074,7 @@ function processIsAlive2(pid) {
|
|
|
17952
18074
|
}
|
|
17953
18075
|
}
|
|
17954
18076
|
function delay3(milliseconds) {
|
|
17955
|
-
return new Promise((
|
|
18077
|
+
return new Promise((resolve9) => setTimeout(resolve9, milliseconds));
|
|
17956
18078
|
}
|
|
17957
18079
|
async function quarantineStaleLock(lockDirectory, snapshot) {
|
|
17958
18080
|
const quarantineRoot = join14(dirname14(lockDirectory), ".stale-operation-locks");
|
|
@@ -18093,7 +18215,7 @@ init_oauth();
|
|
|
18093
18215
|
init_profiles();
|
|
18094
18216
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
18095
18217
|
import { createHash as createHash11 } from "node:crypto";
|
|
18096
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as
|
|
18218
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync17, renameSync as renameSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
18097
18219
|
import { homedir as homedir8 } from "node:os";
|
|
18098
18220
|
import { dirname as dirname15, join as join15 } from "node:path";
|
|
18099
18221
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
@@ -18117,7 +18239,7 @@ async function maybeInstallSkillSyncService(runtime, {
|
|
|
18117
18239
|
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;
|
|
18118
18240
|
const plist = join15(homedir8(), "Library", "LaunchAgents", `${SKILL_SYNC_SERVICE_LABEL}.plist`);
|
|
18119
18241
|
if (existsSync13(plist)) {
|
|
18120
|
-
const saved =
|
|
18242
|
+
const saved = readFileSync17(plist, "utf8");
|
|
18121
18243
|
if (runtime.oauthUserId && [runtime.profileName, runtime.apiBase, runtime.oauthUserId].every((value) => saved.includes(`<string>${xml(value)}</string>`))) return install(runtime);
|
|
18122
18244
|
return;
|
|
18123
18245
|
}
|
|
@@ -18150,10 +18272,10 @@ async function installSkillSyncServiceLocked(runtime, {
|
|
|
18150
18272
|
return { status: "skipped_non_personal_profile" };
|
|
18151
18273
|
}
|
|
18152
18274
|
const root = join15(home, ".notis", "skills", "service");
|
|
18153
|
-
const bundle =
|
|
18275
|
+
const bundle = readFileSync17(source);
|
|
18154
18276
|
const digest = createHash11("sha256").update(bundle).digest("hex");
|
|
18155
18277
|
const installedBundle = join15(root, "runtime", digest, "worker.mjs");
|
|
18156
|
-
if (!existsSync13(installedBundle) || !
|
|
18278
|
+
if (!existsSync13(installedBundle) || !readFileSync17(installedBundle).equals(bundle)) atomicWrite2(installedBundle, bundle, 320);
|
|
18157
18279
|
const args = [nodePath, installedBundle, runtime.profileName, runtime.apiBase, runtime.oauthUserId];
|
|
18158
18280
|
if (!runtime.oauthUserId) return { status: "skipped_missing_account" };
|
|
18159
18281
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -18169,7 +18291,7 @@ async function installSkillSyncServiceLocked(runtime, {
|
|
|
18169
18291
|
const plistPath = join15(home, "Library", "LaunchAgents", `${SKILL_SYNC_SERVICE_LABEL}.plist`);
|
|
18170
18292
|
const domain = `gui/${uid}`;
|
|
18171
18293
|
const target = `${domain}/${SKILL_SYNC_SERVICE_LABEL}`;
|
|
18172
|
-
const unchanged = existsSync13(plistPath) &&
|
|
18294
|
+
const unchanged = existsSync13(plistPath) && readFileSync17(plistPath, "utf8") === plist;
|
|
18173
18295
|
const loaded = run("/bin/launchctl", ["print", target], { encoding: "utf8", timeout: 5e3 });
|
|
18174
18296
|
if (unchanged && loaded.status === 0) return { status: "installed", intervalSeconds: 60, profile: runtime.profileName };
|
|
18175
18297
|
if (loaded.status === 0) {
|
|
@@ -18181,7 +18303,7 @@ async function installSkillSyncServiceLocked(runtime, {
|
|
|
18181
18303
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
18182
18304
|
started = run("/bin/launchctl", ["bootstrap", domain, plistPath], { encoding: "utf8", timeout: 5e3 });
|
|
18183
18305
|
if (started.status === 0) break;
|
|
18184
|
-
await new Promise((
|
|
18306
|
+
await new Promise((resolve9) => setTimeout(resolve9, 100));
|
|
18185
18307
|
}
|
|
18186
18308
|
if (started?.status !== 0) throw new Error("Could not register automatic skill sync with macOS");
|
|
18187
18309
|
return { status: "installed", intervalSeconds: 60, profile: runtime.profileName };
|
|
@@ -18256,6 +18378,7 @@ var skillsCommandSpecs = [
|
|
|
18256
18378
|
|
|
18257
18379
|
// src/command-specs/index.js
|
|
18258
18380
|
var GROUP_SUMMARIES = {
|
|
18381
|
+
reports: "Build and save independent SDK reports into app database records.",
|
|
18259
18382
|
apps: "Develop, deploy, and submit Notis Apps.",
|
|
18260
18383
|
agents: "Install Notis context into local coding agents.",
|
|
18261
18384
|
handover: "Hand the branch you are on to a Notis agent, hosted or your own Codex/Claude.",
|
|
@@ -18272,6 +18395,7 @@ var COMMAND_SPECS = [
|
|
|
18272
18395
|
...agentsCommandSpecs,
|
|
18273
18396
|
...skillsCommandSpecs,
|
|
18274
18397
|
...appsCommandSpecs,
|
|
18398
|
+
...reportsCommandSpecs,
|
|
18275
18399
|
...handoverCommandSpecs,
|
|
18276
18400
|
...toolsCommandSpecs,
|
|
18277
18401
|
...diagnosticCommandSpecs,
|
|
@@ -18368,7 +18492,7 @@ init_profiles();
|
|
|
18368
18492
|
function readCliVersion() {
|
|
18369
18493
|
try {
|
|
18370
18494
|
const manifestPath = join16(dirname16(fileURLToPath11(import.meta.url)), "..", "package.json");
|
|
18371
|
-
const version = JSON.parse(
|
|
18495
|
+
const version = JSON.parse(readFileSync18(manifestPath, "utf-8")).version;
|
|
18372
18496
|
if (typeof version === "string" && version.trim()) {
|
|
18373
18497
|
return version.trim();
|
|
18374
18498
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: notis-apps
|
|
3
|
-
description: Design and package Notis apps. Use when users want an app
|
|
3
|
+
description: Design and package Notis apps, or inspect their live views and resources. Use when users want an installable Notis app or need rendered content, charts, or filters that ordinary data tools do not provide.
|
|
4
4
|
feature_flag: store
|
|
5
5
|
mcp_resource: true
|
|
6
6
|
mcp_tool_patterns: ["LOCAL_NOTIS_INSTALL_APP"]
|
|
7
|
-
mcp_references: ["references/release.md", "references/architecture.md", "references/design.md", "references/sdk.md", "references/troubleshooting.md"]
|
|
7
|
+
mcp_references: ["references/release.md", "references/architecture.md", "references/design.md", "references/sdk.md", "references/troubleshooting.md", "references/reading.md", "references/context.md"]
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Notis Apps
|
|
@@ -13,6 +13,14 @@ Build apps that feel native to Notis: compact, readable, responsive, and useful.
|
|
|
13
13
|
Use Vite + React, `@notis/sdk`, and the existing scaffold components. Use the Notis
|
|
14
14
|
CLI for app operations: `npx --package @notis_ai/cli@latest -- notis ...`.
|
|
15
15
|
|
|
16
|
+
## Read existing apps and resources
|
|
17
|
+
|
|
18
|
+
Use ordinary data tools for straightforward reads. When the task needs a live
|
|
19
|
+
render, chart, filter, or visual inspection, follow [Read Notis web content](references/reading.md).
|
|
20
|
+
It covers apps, views, reports, HTML and file documents without requiring the
|
|
21
|
+
user to open Portal or Desktop. Use the agent's available browser capability;
|
|
22
|
+
this is not an app build, deployment, or Portal editing-context workflow.
|
|
23
|
+
|
|
16
24
|
## Build → inspect → fix → deliver
|
|
17
25
|
|
|
18
26
|
For every app UI create/edit task, read both [Design](references/design.md) and
|
|
@@ -49,3 +57,14 @@ For every app UI create/edit task, read both [Design](references/design.md) and
|
|
|
49
57
|
|
|
50
58
|
The CLI distributes this skill and its references from the canonical product
|
|
51
59
|
source. Do not maintain competing copies.
|
|
60
|
+
|
|
61
|
+
## Shared views, independent reports and feedback
|
|
62
|
+
|
|
63
|
+
Use a shared app view when many records should share one implementation. Use
|
|
64
|
+
`notis-reports` when an app-owned record needs its own independently authored
|
|
65
|
+
SDK presentation. A report is not a new app, and changing it must not deploy or
|
|
66
|
+
replace shared app routes. The agent chooses live data versus captured results.
|
|
67
|
+
|
|
68
|
+
Share selected text, comments, loaded resources or app-defined annotations through
|
|
69
|
+
[generic context pills](references/context.md). Apps own annotation storage and
|
|
70
|
+
presentation; the chat owns unsent context drafts. Passive context is not execution approval.
|