@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.
@@ -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((resolve8, reject) => {
4456
- resolveCode = resolve8;
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((resolve8) => {
4461
- response.end(body, resolve8);
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((resolve8, reject) => {
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
- resolve8();
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((resolve8) => {
4539
- setTimeout(resolve8, RESPONSE_FLUSH_GRACE_MS).unref?.();
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((resolve8) => server.close(resolve8));
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((resolve8) => setTimeout(resolve8, 50));
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((resolve8) => setTimeout(resolve8, 50));
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((resolve8) => {
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
- resolve8(value);
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((resolve8) => setTimeout(resolve8, 100));
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
- await deps.writeSyncState(
7422
- buildSyncState(pullResponse, finalLocalSkills, lastSyncedAt, verifiedLinks, new Set(failedDownloads.map((item) => item.name))),
7423
- syncPaths
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 readFileSync17 } from "node:fs";
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 readFileSync9, statSync as statSync5 } from "node:fs";
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(readFileSync9(filePath, "utf-8"), label);
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 readFileSync12 } from "node:fs";
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 readFileSync10,
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) ? readFileSync10(filePath, "utf-8") : "";
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 = readFileSync10(filePath, "utf-8");
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 = readFileSync10(bundlePath2);
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(readFileSync10(runtimePath)).digest("hex") : null;
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 = readFileSync10(INSTRUCTIONS_PATH, "utf-8").trim();
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 readFileSync11,
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(readFileSync11(filePath, "utf-8"));
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: readFileSync12(BUNDLED_BRIEF_PATH, "utf-8"), source: "bundled" };
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?.full_name;
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 readFileSync13 } from "node:fs";
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(readFileSync13(path3, "utf-8"));
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 readFileSync14,
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 = readFileSync14(fixturePath);
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 readFileSync15 } from "node:fs";
17324
- import { basename as basename7, relative as relative4, resolve as resolve6, sep as sep2 } from "node:path";
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 = resolve6(repository.toplevel);
17500
+ const root = resolve7(repository.toplevel);
17379
17501
  const sensitive = [];
17380
17502
  for (const path3 of filesToAutoCommit(repository)) {
17381
- const absolute = resolve6(root, path3);
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 = readFileSync15(absolute, "utf8");
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 resolve7 } from "node:path";
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 = resolve7(HERE3, "../../../../server/skills");
17798
- const packaged = resolve7(HERE3, "../../dist/base-skills");
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 resolve7(dirname13(linkPath), readlinkSync(linkPath)) === resolve7(expectedTarget);
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((resolve8) => setTimeout(resolve8, milliseconds));
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 readFileSync16, renameSync as renameSync7, writeFileSync as writeFileSync9 } from "node:fs";
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 = readFileSync16(plist, "utf8");
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 = readFileSync16(source);
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) || !readFileSync16(installedBundle).equals(bundle)) atomicWrite2(installedBundle, bundle, 320);
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) && readFileSync16(plistPath, "utf8") === plist;
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((resolve8) => setTimeout(resolve8, 100));
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(readFileSync17(manifestPath, "utf-8")).version;
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 that groups databases, routes, documents, automations, and skills into one installable Notis product.
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.