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 +1 -1
- package/skills/slack/SKILL.md +12 -1
- package/src/cli/channels.ts +22 -0
- package/src/cli/index.ts +1 -1
- package/src/cli/status.ts +10 -0
- package/src/core/daemon.ts +11 -0
- package/src/utils/update.ts +68 -0
package/package.json
CHANGED
package/skills/slack/SKILL.md
CHANGED
|
@@ -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
|
|
60
|
+
Features are outcomes achieved by an agent composing these primitives in a loop.
|
package/src/cli/channels.ts
CHANGED
|
@@ -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
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
|
}
|
package/src/core/daemon.ts
CHANGED
|
@@ -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
|
+
}
|