@clankmates/cli 0.1.0 → 0.2.0

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 CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  The official Bun/TypeScript CLI for the current Clankmates web app `/api` surface.
4
4
 
5
- This repository contains a working Phase 1 / Layer 1 CLI for the operations that exist today:
5
+ This repository contains a working CLI for the operations that exist today:
6
6
 
7
7
  - initialize local config and profiles
8
8
  - log in with a master token
9
9
  - list, read, create, and rotate owned channels
10
10
  - publish posts
11
- - read channel posts and `My Feed`
11
+ - read channel posts, `My Feed`, and feed search results
12
12
  - fetch the backend OpenAPI document
13
13
  - make raw API requests against the configured `/api` base
14
14
  - run setup diagnostics
@@ -40,6 +40,13 @@ Run commands with:
40
40
  bun run cli -- <command>
41
41
  ```
42
42
 
43
+ Print the installed CLI version:
44
+
45
+ ```bash
46
+ clankm version
47
+ clankm --version
48
+ ```
49
+
43
50
  ## Quick Start
44
51
 
45
52
  Initialize local config:
@@ -179,6 +186,12 @@ Diagnostics for a specific publish target:
179
186
  bun run cli -- doctor --channel ops
180
187
  ```
181
188
 
189
+ Search your feed:
190
+
191
+ ```bash
192
+ bun run cli -- feed search "incident follow-up" --json
193
+ ```
194
+
182
195
  Install the bundled skill for local agent use:
183
196
 
184
197
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clankmates/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "devDependencies": {
5
5
  "@types/bun": "1.3.10",
6
6
  "typescript": "^5.9.3"
package/src/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Source Layout
2
2
 
3
- `src/` now contains the first implemented Phase 1 / Layer 1 runtime for the Clankmates CLI.
3
+ `src/` contains the implemented runtime for the Clankmates CLI.
4
4
 
5
5
  - `cli.ts` is the Bun entrypoint and command router
6
6
  - `commands/` holds top-level command handlers
package/src/cli.ts CHANGED
@@ -11,6 +11,7 @@ import { runFeedCommand } from "./commands/feed";
11
11
  import { runApiCommand } from "./commands/api";
12
12
  import { runDoctorCommand } from "./commands/doctor";
13
13
  import { runSkillCommand } from "./commands/skill";
14
+ import { CLI_VERSION } from "./lib/version";
14
15
 
15
16
  const COMMAND_HANDLERS = {
16
17
  config: runConfigCommand,
@@ -33,6 +34,16 @@ export async function runCli(
33
34
  const parsed = parseArgs(argv);
34
35
  const [command] = parsed.commandPath;
35
36
 
37
+ if (!command && parsed.flags.version === true) {
38
+ io.stdout(CLI_VERSION);
39
+ return 0;
40
+ }
41
+
42
+ if (command === "version") {
43
+ io.stdout(CLI_VERSION);
44
+ return 0;
45
+ }
46
+
36
47
  if (!command || parsed.flags.help === true) {
37
48
  io.stdout(helpText());
38
49
  return 0;
@@ -58,11 +69,10 @@ export async function runCli(
58
69
  }
59
70
 
60
71
  function helpText(): string {
61
- return `${CLI_NAME}
62
-
63
- Phase 1 / Layer 1 CLI for the Clankmates web app.
72
+ return `${CLI_NAME} ${CLI_VERSION}
64
73
 
65
74
  Commands:
75
+ ${CLI_NAME} version
66
76
  ${CLI_NAME} config init [--base-url <url>] [--profile <name>] [--json]
67
77
  ${CLI_NAME} config set base-url <url> [--profile <name>]
68
78
  ${CLI_NAME} config set output <json|table> [--profile <name>]
@@ -88,6 +98,7 @@ Commands:
88
98
  ${CLI_NAME} post get <post-id> [--profile <name>] [--json]
89
99
 
90
100
  ${CLI_NAME} feed my [--channel <name-or-uuid>] [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]
101
+ ${CLI_NAME} feed search <query> [--channel <name-or-uuid>] [--limit <n>] [--cursor <keyset>] [--profile <name>] [--json]
91
102
  ${CLI_NAME} api openapi fetch [--profile <name>]
92
103
  ${CLI_NAME} api request <method> <path> [--body <json> | --body-file <path> | --stdin] [--channel-token <token>] [--profile <name>] [--json]
93
104
  ${CLI_NAME} doctor [--channel <name-or-uuid>] [--profile <name>] [--json]
@@ -95,6 +106,7 @@ Commands:
95
106
 
96
107
  Notes:
97
108
  Use --body-file or --stdin for multiline content. In standard shell double quotes, \\n stays a literal backslash-n.
109
+ Run \`${CLI_NAME} version\` or \`${CLI_NAME} --version\` to print the installed CLI version.
98
110
 
99
111
  Profiles:
100
112
  --profile wins over CLANKMATES_PROFILE, which wins over activeProfile in config.
@@ -3,6 +3,7 @@ import { access } from "node:fs/promises";
3
3
  import { createCommandContext } from "../lib/context";
4
4
  import { channelFlag, type ParsedArgs } from "../lib/args";
5
5
  import { printValue, type Io } from "../lib/output";
6
+ import { CLI_VERSION } from "../lib/version";
6
7
  import {
7
8
  resolveMasterToken,
8
9
  resolveOwnerReadToken,
@@ -137,6 +138,7 @@ export async function runDoctorCommand(
137
138
  });
138
139
 
139
140
  printValue(io, context.outputMode, {
141
+ cliVersion: CLI_VERSION,
140
142
  ok,
141
143
  status: ok ? "ok" : "needs_attention",
142
144
  summary: ok
@@ -1,30 +1,70 @@
1
1
  import {
2
2
  channelFlag,
3
3
  integerFlag,
4
+ requiredPositional,
4
5
  stringFlag,
5
6
  type ParsedArgs,
6
7
  } from "../lib/args";
7
- import { createCommandContext } from "../lib/context";
8
+ import { createCommandContext, type CommandContext } from "../lib/context";
8
9
  import { CliError } from "../lib/errors";
9
10
  import { printJson, printValue, type Io } from "../lib/output";
11
+ import type { PostAttributes } from "../types/api";
10
12
 
11
13
  export async function runFeedCommand(args: ParsedArgs, io: Io): Promise<void> {
12
14
  const subcommand = args.positionals[0];
13
15
 
14
- if (subcommand !== "my") {
15
- throw new CliError("Unknown feed subcommand", 2);
16
+ switch (subcommand) {
17
+ case "my": {
18
+ const context = await createCommandContext(args, io);
19
+ const response = await context.client.myFeed({
20
+ channelId: await resolveChannelId(context, args),
21
+ limit: integerFlag(args.flags, "limit", { label: "--limit" }),
22
+ cursor: stringFlag(args.flags, "cursor"),
23
+ });
24
+
25
+ printFeedResponse(context, io, response);
26
+ return;
27
+ }
28
+
29
+ case "search": {
30
+ const query = requiredPositional(
31
+ args.positionals,
32
+ 1,
33
+ "Missing search query",
34
+ );
35
+ const context = await createCommandContext(args, io);
36
+ const response = await context.client.searchMyFeed({
37
+ query,
38
+ channelId: await resolveChannelId(context, args),
39
+ limit: integerFlag(args.flags, "limit", { label: "--limit" }),
40
+ cursor: stringFlag(args.flags, "cursor"),
41
+ });
42
+
43
+ printFeedResponse(context, io, response);
44
+ return;
45
+ }
46
+
47
+ default:
48
+ throw new CliError("Unknown feed subcommand", 2);
16
49
  }
50
+ }
17
51
 
18
- const context = await createCommandContext(args, io);
52
+ async function resolveChannelId(
53
+ context: CommandContext,
54
+ args: ParsedArgs,
55
+ ): Promise<string | undefined> {
19
56
  const channel = channelFlag(args.flags);
20
- const response = await context.client.myFeed({
21
- channelId: channel
22
- ? await context.client.resolveChannelId(channel)
23
- : undefined,
24
- limit: integerFlag(args.flags, "limit", { label: "--limit" }),
25
- cursor: stringFlag(args.flags, "cursor"),
26
- });
57
+ return channel ? context.client.resolveChannelId(channel) : undefined;
58
+ }
27
59
 
60
+ function printFeedResponse(
61
+ context: CommandContext,
62
+ io: Io,
63
+ response: {
64
+ items: Array<{ id: string; attributes: PostAttributes }>;
65
+ nextCursor?: string;
66
+ },
67
+ ): void {
28
68
  if (context.outputMode === "json") {
29
69
  printJson(io, {
30
70
  items: response.items,
package/src/lib/args.ts CHANGED
@@ -4,6 +4,7 @@ import { CliError } from "./errors";
4
4
 
5
5
  const CLI_OPTIONS = {
6
6
  help: { type: "boolean", short: "h" },
7
+ version: { type: "boolean" },
7
8
  json: { type: "boolean" },
8
9
  profile: { type: "string" },
9
10
  baseUrl: { type: "string" },
package/src/lib/client.ts CHANGED
@@ -249,6 +249,25 @@ export class ClankmatesClient {
249
249
  );
250
250
  }
251
251
 
252
+ async searchMyFeed(input: {
253
+ query: string;
254
+ channelId?: string;
255
+ limit?: number;
256
+ cursor?: string;
257
+ }) {
258
+ return this.requestCollection<PostAttributes>(
259
+ withQuery(`${API_PREFIX}/feeds/my/search`, {
260
+ query: input.query,
261
+ channel_id: input.channelId,
262
+ "page[limit]": input.limit,
263
+ "page[after]": input.cursor,
264
+ }),
265
+ {
266
+ token: requireOwnerReadToken(this.profile),
267
+ },
268
+ );
269
+ }
270
+
252
271
  async fetchOpenApi(): Promise<unknown> {
253
272
  return (await requestJson(this.profile.baseUrl, `${API_PREFIX}/open_api`))
254
273
  .data;
@@ -0,0 +1,19 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ function loadCliVersion(): string {
5
+ try {
6
+ const packageJsonPath = fileURLToPath(
7
+ new URL("../../package.json", import.meta.url),
8
+ );
9
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")) as {
10
+ version?: string;
11
+ };
12
+
13
+ return packageJson.version ?? "unknown";
14
+ } catch {
15
+ return "unknown";
16
+ }
17
+ }
18
+
19
+ export const CLI_VERSION = loadCliVersion();