niahere 0.2.10 → 0.2.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "niahere",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "A personal AI assistant daemon — scheduled jobs, chat across Telegram and Slack, persona system, and visual identity.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -43,7 +43,18 @@ URL format: `https://<workspace>.slack.com/archives/<CHANNEL_ID>/p<THREAD_TS_NO_
43
43
  - **Channel ID:** the segment after `/archives/`
44
44
  - **Thread TS:** the `p` number with a `.` inserted before the last 6 digits
45
45
 
46
+ ## Beyond these primitives
47
+
48
+ These commands cover common operations but the Slack API has 200+ methods. If you need something not listed here (e.g. managing bookmarks, setting channel topics, pinning messages, scheduling messages, managing user groups):
49
+
50
+ 1. **Check `slack.py --help`** for available subcommands first.
51
+ 2. **Use the Slack API directly** via `curl` with the bot token from `slack_helper.load_slack_config()["token"]`. The pattern is always: `curl -H "Authorization: Bearer $TOKEN" https://slack.com/api/<method>`.
52
+ 3. **Consult the [Slack API docs](https://api.slack.com/methods)** to find the right method and required scopes.
53
+ 4. **Compose primitives** to build workflows — "summarize channel" is just `history` + LLM reasoning. "Find discussions about X" is `search` + `thread`.
54
+
55
+ The bot token and auth pattern are consistent across all Slack APIs. Don't limit yourself to what's explicitly in `slack.py` — treat it as a starting point, not a boundary.
56
+
46
57
  ## Design
47
58
 
48
59
  Each subcommand = one Slack API call. No bundled workflows.
49
- Features like "summarize channel" or "find discussions" are agent-level compositions of these primitives.
60
+ Features are outcomes achieved by an agent composing these primitives in a loop.
@@ -91,6 +91,28 @@ export async function slackCommand(): Promise<void> {
91
91
  console.log(` Workspace: ${config.channels.slack.workspace} (${config.channels.slack.workspace_url})`);
92
92
  console.log(` Bot: @${config.channels.slack.bot_name} (${config.channels.slack.bot_user_id})`);
93
93
  }
94
+ // Verify auth is working
95
+ try {
96
+ const resp = await fetch("https://slack.com/api/auth.test", {
97
+ headers: { Authorization: `Bearer ${config.channels.slack.bot_token}` },
98
+ });
99
+ const data = (await resp.json()) as Record<string, unknown>;
100
+ if (data.ok) {
101
+ console.log(` Auth: \u2713 valid`);
102
+ // Backfill workspace info if missing
103
+ if (!config.channels.slack.workspace) {
104
+ const enriched = await enrichSlackConfig(config.channels.slack.bot_token);
105
+ if (Object.keys(enriched).length > 0) {
106
+ updateRawConfig({ channels: { slack: enriched } });
107
+ console.log(" (workspace info backfilled)");
108
+ }
109
+ }
110
+ } else {
111
+ console.log(` Auth: \u2717 ${data.error}`);
112
+ }
113
+ } catch (err) {
114
+ console.log(` Auth: \u2717 could not reach Slack API`);
115
+ }
94
116
  } else {
95
117
  console.log("Slack: not configured");
96
118
  }
package/src/cli/index.ts CHANGED
@@ -219,7 +219,7 @@ switch (command) {
219
219
  }
220
220
 
221
221
  case "slack": {
222
- slackCommand();
222
+ await slackCommand();
223
223
  break;
224
224
  }
225
225
 
package/src/cli/status.ts CHANGED
@@ -7,6 +7,7 @@ import { Message, ActiveEngine, Job } from "../db/models";
7
7
  import type { ScheduleType, JobStateStatus, RoomStats } from "../types";
8
8
  import { withDb } from "../db/connection";
9
9
  import { errMsg } from "../utils/errors";
10
+ import { checkForUpdate } from "../utils/update";
10
11
 
11
12
  type StatusOptions = {
12
13
  json: boolean;
@@ -306,4 +307,13 @@ export async function statusCommand(argv: string[] = []): Promise<void> {
306
307
  } else {
307
308
  console.log("Tip: use --rooms N, --all, or --json for alternate views.");
308
309
  }
310
+
311
+ // Check for updates (non-blocking, cached 24h)
312
+ try {
313
+ const { version } = await import("../../package.json");
314
+ const update = await checkForUpdate(version);
315
+ if (update) {
316
+ console.log(`\n⚠ Update available: ${update.current} → ${update.latest} (run \`npm i -g niahere\` to update)`);
317
+ }
318
+ } catch {}
309
319
  }
@@ -162,6 +162,17 @@ export async function runDaemon(): Promise<void> {
162
162
  writePid(process.pid);
163
163
  log.info({ pid: process.pid }, "daemon started");
164
164
 
165
+ // Check for updates (non-blocking, logged only)
166
+ try {
167
+ const { checkForUpdate } = await import("../utils/update");
168
+ const { version } = await import("../../package.json");
169
+ const update = await checkForUpdate(version);
170
+ if (update) {
171
+ log.warn({ current: update.current, latest: update.latest }, "update available — run `npm i -g niahere` to update");
172
+ }
173
+ } catch {}
174
+
175
+
165
176
  // Startup recovery
166
177
  try {
167
178
  await runMigrations();
@@ -0,0 +1,68 @@
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
2
+ import { resolve, dirname } from "path";
3
+ import { getNiaHome } from "./paths";
4
+
5
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
6
+ const PACKAGE_NAME = "niahere";
7
+
8
+ type UpdateCache = {
9
+ latest: string;
10
+ checkedAt: number;
11
+ };
12
+
13
+ function cachePath(): string {
14
+ return resolve(getNiaHome(), "tmp/update-check.json");
15
+ }
16
+
17
+ function readCache(): UpdateCache | null {
18
+ const path = cachePath();
19
+ if (!existsSync(path)) return null;
20
+ try {
21
+ const data = JSON.parse(readFileSync(path, "utf8"));
22
+ if (data.latest && data.checkedAt && Date.now() - data.checkedAt < CACHE_TTL_MS) {
23
+ return data;
24
+ }
25
+ } catch {}
26
+ return null;
27
+ }
28
+
29
+ function writeCache(latest: string): void {
30
+ const path = cachePath();
31
+ mkdirSync(dirname(path), { recursive: true });
32
+ writeFileSync(path, JSON.stringify({ latest, checkedAt: Date.now() }));
33
+ }
34
+
35
+ async function fetchLatestVersion(): Promise<string | null> {
36
+ try {
37
+ const resp = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
38
+ signal: AbortSignal.timeout(3000),
39
+ });
40
+ if (!resp.ok) return null;
41
+ const data = await resp.json() as { version?: string };
42
+ return data.version ?? null;
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+
48
+ function isNewer(latest: string, current: string): boolean {
49
+ const [la, lb, lc] = latest.split(".").map(Number);
50
+ const [ca, cb, cc] = current.split(".").map(Number);
51
+ if (la !== ca) return la > ca;
52
+ if (lb !== cb) return lb > cb;
53
+ return lc > cc;
54
+ }
55
+
56
+ /** Check if a newer version is available. Returns update info or null. Non-blocking, cached 24h. */
57
+ export async function checkForUpdate(currentVersion: string): Promise<{ current: string; latest: string } | null> {
58
+ const cached = readCache();
59
+ if (cached) {
60
+ return isNewer(cached.latest, currentVersion) ? { current: currentVersion, latest: cached.latest } : null;
61
+ }
62
+
63
+ const latest = await fetchLatestVersion();
64
+ if (!latest) return null;
65
+
66
+ writeCache(latest);
67
+ return isNewer(latest, currentVersion) ? { current: currentVersion, latest } : null;
68
+ }