niahere 0.2.10 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "niahere",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
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": {
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: modal-cli
3
+ description: >
4
+ Modal CLI for serverless GPU/cloud compute. Use when user mentions modal, deploying to modal,
5
+ running GPU workloads, or serverless Python functions.
6
+ Check `which modal` first — if missing, offer: `pip install modal && modal setup`.
7
+ Covers run/serve/deploy, apps, containers, secrets, volumes, and workspace management.
8
+ ---
9
+
10
+ ## Prerequisites
11
+
12
+ Before any modal command, verify the CLI is available:
13
+
14
+ ```bash
15
+ which modal
16
+ ```
17
+
18
+ **If not found**, ask the user:
19
+
20
+ > Modal CLI not found. Install it?
21
+ >
22
+ > ```bash
23
+ > pip install modal
24
+ > ```
25
+
26
+ After install, run auth setup:
27
+
28
+ ```bash
29
+ modal setup
30
+ ```
31
+
32
+ This opens a browser for token auth. If `modal` still isn't on PATH after pip install, use:
33
+
34
+ ```bash
35
+ python -m modal setup
36
+ ```
37
+
38
+ **If found but not authed**, check with:
39
+
40
+ ```bash
41
+ modal token info
42
+ ```
43
+
44
+ If that errors, run `modal setup` to authenticate.
45
+
46
+ ## Core Commands
47
+
48
+ | Command | Purpose |
49
+ |---------|---------|
50
+ | `modal run <file>` | One-off execution of a script/function |
51
+ | `modal serve <file>` | Local dev with hot-reload |
52
+ | `modal deploy <file>` | Production deployment |
53
+ | `modal app list` | List deployed apps |
54
+ | `modal app logs <name>` | Stream logs for a deployed app |
55
+ | `modal app stop <name>` | Stop a running app |
56
+ | `modal shell` | Interactive shell in Modal environment |
57
+
58
+ ## Workflows
59
+
60
+ ### First-time setup
61
+ ```bash
62
+ pip install modal
63
+ modal setup
64
+ modal run hello.py # validate end-to-end
65
+ ```
66
+
67
+ ### Develop and deploy
68
+ ```bash
69
+ modal run my_app.py # test locally
70
+ modal serve my_app.py # hot-reload dev
71
+ modal deploy my_app.py # ship it
72
+ ```
73
+
74
+ ### Debug containers
75
+ ```bash
76
+ modal container list
77
+ modal container logs <id>
78
+ modal container exec <id> -- bash -lc "nvidia-smi"
79
+ ```
80
+
81
+ ### Manage resources
82
+ ```bash
83
+ modal secret create my-secret KEY=value
84
+ modal volume create my-vol
85
+ modal environment create dev
86
+ modal config set-environment dev
87
+ ```
88
+
89
+ ## Auth & Profiles
90
+
91
+ - `modal token info` — check current auth
92
+ - `modal token new` — create token via browser
93
+ - `modal token set` — set token credentials directly
94
+ - `modal profile list` / `activate` — switch workspaces
95
+ - `modal config show` — print effective config
96
+
97
+ Environment variables: `MODAL_TOKEN_ID`, `MODAL_TOKEN_SECRET`, `MODAL_ENVIRONMENT`, `MODAL_PROFILE`
98
+
99
+ ## Resource Management
100
+
101
+ - **Secrets**: `modal secret list/create/delete` (supports `.env` import)
102
+ - **Volumes**: `modal volume create/list/ls/put/get/rm/delete`
103
+ - **Dicts**: `modal dict create/list/items/get/clear/delete`
104
+ - **Queues**: `modal queue create/list/peek/len/clear/delete`
105
+ - **NFS**: `modal nfs list/create/ls/put/get/rm/delete`
106
+ - **Environments**: `modal environment list/create/delete/update`
107
+
108
+ ## Decision Points
109
+
110
+ - User says "deploy to modal" / "run on GPU" → `modal deploy` or `modal run`
111
+ - User says "check my modal apps" → `modal app list`
112
+ - User says "modal logs" → `modal app logs <name>`
113
+ - User needs interactive debug → `modal shell` or `modal container exec`
114
+ - modal not installed → offer install, don't silently fail
115
+ - modal not authed → run `modal setup`
116
+
117
+ ## References
118
+
119
+ - Guide: https://modal.com/docs/guide
120
+ - CLI reference: https://modal.com/docs/reference/cli/
121
+ - Status: https://status.modal.com
@@ -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
+ }