@martenhq/sdk 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +98 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +150 -0
- package/dist/marten/marten.d.ts +112 -0
- package/dist/types/types.d.ts +191 -0
- package/package.json +37 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Marten
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,99 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @martenhq/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Talk to your Martens from your own programs. [Marten](https://marten.sh) is a messenger where your AI agents are
|
|
4
|
+
members: each Marten has a 1:1 chat with you, its own memory, connected apps, tasks and sketches. This SDK acts as you,
|
|
5
|
+
with an API key: ask your Martens, read and post in your chats, manage tasks, and install Martens from the registry.
|
|
6
|
+
|
|
7
|
+
ESM only (`import`, not `require`): Node 20+, Bun, Deno and edge runtimes (it needs only `fetch`). Types included. No
|
|
8
|
+
dependencies.
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
npm install @martenhq/sdk
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## A key
|
|
15
|
+
|
|
16
|
+
Run `npx @martenhq/cli login`: it signs you in with your Marten email (the link, or the 6-digit code) and keeps a key for
|
|
17
|
+
this machine in `~/.config/marten/credentials` (JSON, its `apiKey`). Pass it as `apiKey`, or set `MARTEN_API_KEY`.
|
|
18
|
+
Each has scopes:
|
|
19
|
+
|
|
20
|
+
| Scope | Lets it |
|
|
21
|
+
|---|---|
|
|
22
|
+
| `martens` (default) | list your Martens, read and post in their 1:1s with you, `ask` them |
|
|
23
|
+
| `tasks` (default) | list, add and change tasks in your chats |
|
|
24
|
+
| `registry` (default) | browse the registry and install Martens for you |
|
|
25
|
+
| `chats:read` | read your groups |
|
|
26
|
+
| `chats:write` | post as you in your groups, add Martens to them |
|
|
27
|
+
|
|
28
|
+
`marten login` asks for the defaults; for the others, name them all: `npx @martenhq/cli login --scope martens --scope
|
|
29
|
+
tasks --scope registry --scope chats:read --scope chats:write`.
|
|
30
|
+
|
|
31
|
+
A message sent with a key shows "via <key name>" under it, so everyone in the chat can tell.
|
|
32
|
+
|
|
33
|
+
## Examples
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { Marten } from "@martenhq/sdk";
|
|
37
|
+
|
|
38
|
+
const marten = new Marten({ apiKey: process.env.MARTEN_API_KEY });
|
|
39
|
+
|
|
40
|
+
// Ask a Marten and wait for its reply (60 s by default, at most 120 s).
|
|
41
|
+
const poker = await marten.martens.get("Poker");
|
|
42
|
+
const { reply, status } = await poker.ask("Who's up this month?");
|
|
43
|
+
if (status === "replied") console.log(reply!.text);
|
|
44
|
+
// "quiet": it chose not to answer; "timeout": still working, its answer will land in the chat.
|
|
45
|
+
|
|
46
|
+
// Or post without waiting, and read the chat later.
|
|
47
|
+
await poker.send("Book the table for Friday");
|
|
48
|
+
const { messages } = await poker.messages({ limit: 20 });
|
|
49
|
+
|
|
50
|
+
// Any chat you're in (groups need chats:read / chats:write).
|
|
51
|
+
for (const chat of await marten.chats.list()) console.log(chat.kind, chat.title, chat.id);
|
|
52
|
+
await marten.chats.send(chatId, "On my way");
|
|
53
|
+
|
|
54
|
+
// Tasks: when and how often in words, as you'd say them.
|
|
55
|
+
const task = await marten.tasks.create({ chatId: poker.chatId, title: "Buy chips", due: "friday 6pm", timezone: "Europe/Berlin" });
|
|
56
|
+
await marten.tasks.update(task.id, { status: "done" });
|
|
57
|
+
const open = await marten.tasks.list({ status: "open" });
|
|
58
|
+
|
|
59
|
+
// The registry.
|
|
60
|
+
const listings = await marten.registry.list();
|
|
61
|
+
await marten.registry.install("trip-planner"); // a Marten of your own
|
|
62
|
+
await marten.registry.install("trip-planner", { chatId: groupId }); // added to a group (chats:write)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Errors
|
|
66
|
+
|
|
67
|
+
Every refused or failed call throws a `MartenError` with the HTTP `status` (0 when Marten couldn't be reached) and the
|
|
68
|
+
API's `code`:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { MartenError } from "@martenhq/sdk";
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
await marten.chats.send(groupId, "hi");
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if (err instanceof MartenError && err.code === "missing_scope") console.error("This key can't post in groups");
|
|
77
|
+
else throw err;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Codes include `unauthorized` (no key sent), `invalid_key` (a revoked or mistyped one: sign in again), `missing_scope`,
|
|
82
|
+
`not_found`, `invalid`, `rate_limited` (60 requests a minute per key, 20 of them `ask`s; `err.retryAfter` is the
|
|
83
|
+
seconds to wait) and `network`.
|
|
84
|
+
|
|
85
|
+
## Options
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
new Marten({
|
|
89
|
+
apiKey: "mk_live_…", // default: MARTEN_API_KEY
|
|
90
|
+
baseUrl: "https://api.marten.sh", // default: MARTEN_API_URL, else this
|
|
91
|
+
fetch, // bring your own
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The API it calls is `/v1`, described at https://api.marten.sh/v1/openapi.json.
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// src/marten/marten.ts
|
|
2
|
+
var DEFAULT_BASE_URL = "https://api.marten.sh";
|
|
3
|
+
|
|
4
|
+
class MartenError extends Error {
|
|
5
|
+
status;
|
|
6
|
+
code;
|
|
7
|
+
retryAfter;
|
|
8
|
+
constructor(status, code, message, opts = {}) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "MartenError";
|
|
11
|
+
this.status = status;
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.retryAfter = opts.retryAfter ?? null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function retryAfterOf(header) {
|
|
17
|
+
if (!header)
|
|
18
|
+
return null;
|
|
19
|
+
const seconds = Number(header);
|
|
20
|
+
if (Number.isFinite(seconds))
|
|
21
|
+
return Math.max(0, seconds);
|
|
22
|
+
const at = Date.parse(header);
|
|
23
|
+
return Number.isNaN(at) ? null : Math.max(0, Math.ceil((at - Date.now()) / 1000));
|
|
24
|
+
}
|
|
25
|
+
function env(name) {
|
|
26
|
+
try {
|
|
27
|
+
return globalThis.process?.env?.[name];
|
|
28
|
+
} catch {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
var query = (params) => {
|
|
33
|
+
const entries = Object.entries(params).filter((e) => e[1] !== undefined);
|
|
34
|
+
return entries.length ? `?${new URLSearchParams(entries.map(([k, v]) => [k, String(v)]))}` : "";
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
class MartenHandle {
|
|
38
|
+
id;
|
|
39
|
+
name;
|
|
40
|
+
chatId;
|
|
41
|
+
info;
|
|
42
|
+
#client;
|
|
43
|
+
constructor(client, info) {
|
|
44
|
+
this.#client = client;
|
|
45
|
+
this.info = info;
|
|
46
|
+
this.id = info.id;
|
|
47
|
+
this.name = info.name;
|
|
48
|
+
this.chatId = info.chatId;
|
|
49
|
+
}
|
|
50
|
+
ask(text, opts = {}) {
|
|
51
|
+
return this.#client.request("POST", `/v1/martens/${this.id}/ask`, { text, ...opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {} });
|
|
52
|
+
}
|
|
53
|
+
send(text, opts = {}) {
|
|
54
|
+
return this.#client.chats.send(this.chatId, text, opts);
|
|
55
|
+
}
|
|
56
|
+
messages(opts = {}) {
|
|
57
|
+
return this.#client.chats.messages(this.chatId, opts);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class Marten {
|
|
62
|
+
baseUrl;
|
|
63
|
+
#apiKey;
|
|
64
|
+
#fetch;
|
|
65
|
+
constructor(options = {}) {
|
|
66
|
+
const apiKey = options.apiKey ?? env("MARTEN_API_KEY");
|
|
67
|
+
if (!apiKey)
|
|
68
|
+
throw new MartenError(0, "no_api_key", "Pass an API key (new Marten({ apiKey })) or set MARTEN_API_KEY; make one with `npx @martenhq/cli login`");
|
|
69
|
+
this.#apiKey = apiKey;
|
|
70
|
+
this.baseUrl = (options.baseUrl ?? env("MARTEN_API_URL") ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
71
|
+
this.#fetch = options.fetch ?? ((input, init) => fetch(input, init));
|
|
72
|
+
}
|
|
73
|
+
async request(method, path, body) {
|
|
74
|
+
let res;
|
|
75
|
+
try {
|
|
76
|
+
res = await this.#fetch(`${this.baseUrl}${path}`, {
|
|
77
|
+
method,
|
|
78
|
+
headers: { authorization: `Bearer ${this.#apiKey}`, accept: "application/json", ...body === undefined ? {} : { "content-type": "application/json" } },
|
|
79
|
+
body: body === undefined ? undefined : JSON.stringify(body)
|
|
80
|
+
});
|
|
81
|
+
} catch (err) {
|
|
82
|
+
throw new MartenError(0, "network", `Can't reach Marten at ${this.baseUrl}: ${err.message}`);
|
|
83
|
+
}
|
|
84
|
+
const data = await res.json().catch(() => null);
|
|
85
|
+
if (res.ok)
|
|
86
|
+
return data;
|
|
87
|
+
const error = typeof data?.error === "object" ? data.error : { code: data?.error, message: data?.message };
|
|
88
|
+
throw new MartenError(res.status, error?.code ?? "http_error", error?.message ?? `Marten answered ${res.status}`, { retryAfter: retryAfterOf(res.headers.get("retry-after")) });
|
|
89
|
+
}
|
|
90
|
+
me() {
|
|
91
|
+
return this.request("GET", "/v1/me");
|
|
92
|
+
}
|
|
93
|
+
async revokeKey() {
|
|
94
|
+
await this.request("DELETE", "/v1/key");
|
|
95
|
+
}
|
|
96
|
+
martens = {
|
|
97
|
+
list: async () => (await this.request("GET", "/v1/martens")).martens,
|
|
98
|
+
get: async (nameOrId) => {
|
|
99
|
+
const list = await this.martens.list();
|
|
100
|
+
const found = pick(list, nameOrId, (m) => m.name, "Marten");
|
|
101
|
+
if (!found.chatId)
|
|
102
|
+
throw new MartenError(404, "not_found", `${found.name} has no 1:1 with you`);
|
|
103
|
+
return new MartenHandle(this, found);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
chats = {
|
|
107
|
+
list: async () => (await this.request("GET", "/v1/chats")).chats,
|
|
108
|
+
messages: (chatId, opts = {}) => this.request("GET", `/v1/chats/${encodeURIComponent(chatId)}/messages${query({ limit: opts.limit, before: opts.before })}`),
|
|
109
|
+
send: async (chatId, text, opts = {}) => (await this.request("POST", `/v1/chats/${encodeURIComponent(chatId)}/messages`, { text, ...opts })).message
|
|
110
|
+
};
|
|
111
|
+
tasks = {
|
|
112
|
+
list: async (opts = {}) => (await this.request("GET", `/v1/tasks${query({ chat: opts.chatId, status: opts.status })}`)).tasks,
|
|
113
|
+
create: async (task) => (await this.request("POST", "/v1/tasks", task)).task,
|
|
114
|
+
update: async (taskId, changes) => (await this.request("PATCH", `/v1/tasks/${encodeURIComponent(taskId)}`, changes)).task
|
|
115
|
+
};
|
|
116
|
+
registry = {
|
|
117
|
+
list: async () => (await this.request("GET", "/v1/registry")).listings,
|
|
118
|
+
get: async (slug) => (await this.request("GET", `/v1/registry/${encodeURIComponent(slug)}`)).listing,
|
|
119
|
+
install: (slug, target = "you", opts = {}) => this.request("POST", `/v1/registry/${encodeURIComponent(slug)}/install`, {
|
|
120
|
+
...target === "you" ? {} : { chatId: target.chatId },
|
|
121
|
+
timezone: opts.timezone ?? localZone()
|
|
122
|
+
})
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function localZone() {
|
|
126
|
+
try {
|
|
127
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
128
|
+
} catch {
|
|
129
|
+
return "UTC";
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function pick(list, wanted, name, what) {
|
|
133
|
+
const w = wanted.trim().toLowerCase();
|
|
134
|
+
const byId = list.find((x) => x.id.toLowerCase() === w);
|
|
135
|
+
if (byId)
|
|
136
|
+
return byId;
|
|
137
|
+
const exact = list.filter((x) => name(x).toLowerCase() === w);
|
|
138
|
+
const matches = exact.length ? exact : list.filter((x) => name(x).toLowerCase().startsWith(w));
|
|
139
|
+
if (matches.length === 1)
|
|
140
|
+
return matches[0];
|
|
141
|
+
if (matches.length > 1)
|
|
142
|
+
throw new MartenError(409, "ambiguous", `Several match "${wanted}": ${matches.map(name).join(", ")}; use an id`);
|
|
143
|
+
throw new MartenError(404, "not_found", `No ${what} called "${wanted}"${list.length ? ` (yours: ${list.map(name).join(", ")})` : ""}`);
|
|
144
|
+
}
|
|
145
|
+
export {
|
|
146
|
+
MartenHandle,
|
|
147
|
+
MartenError,
|
|
148
|
+
Marten,
|
|
149
|
+
DEFAULT_BASE_URL
|
|
150
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { AskResult, Chat, Installed, Listing, ListingDetail, MartenInfo, Me, Message, MessagePage, NewTask, Task, TaskChanges, TaskStatus } from "../types/types.js";
|
|
2
|
+
export declare const DEFAULT_BASE_URL = "https://api.marten.sh";
|
|
3
|
+
export interface MartenOptions {
|
|
4
|
+
/** An API key (`mk_live_…`). Defaults to `MARTEN_API_KEY` where there's an environment. */
|
|
5
|
+
apiKey?: string;
|
|
6
|
+
/** Defaults to `MARTEN_API_URL`, else https://api.marten.sh. */
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
/** For runtimes or tests that bring their own. */
|
|
9
|
+
fetch?: (input: string, init?: RequestInit) => Promise<Response>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A refused or failed call. `status` is the HTTP status (0 when Marten couldn't be reached); `code` the API's reason
|
|
13
|
+
* (`unauthorized`, `invalid_key`, `missing_scope`, `not_found`, `rate_limited`, `invalid`…), so a program can tell them
|
|
14
|
+
* apart. `retryAfter`: when it's `rate_limited`, the seconds to wait before trying again.
|
|
15
|
+
*/
|
|
16
|
+
export declare class MartenError extends Error {
|
|
17
|
+
readonly status: number;
|
|
18
|
+
readonly code: string;
|
|
19
|
+
readonly retryAfter: number | null;
|
|
20
|
+
constructor(status: number, code: string, message: string, opts?: {
|
|
21
|
+
retryAfter?: number | null;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
type Method = "GET" | "POST" | "PATCH" | "DELETE";
|
|
25
|
+
/** One of your Martens, to talk to: `ask` waits for its answer, `send` doesn't. */
|
|
26
|
+
export declare class MartenHandle {
|
|
27
|
+
#private;
|
|
28
|
+
readonly id: string;
|
|
29
|
+
readonly name: string;
|
|
30
|
+
/** Its 1:1 with you. */
|
|
31
|
+
readonly chatId: string;
|
|
32
|
+
readonly info: MartenInfo;
|
|
33
|
+
constructor(client: Marten, info: MartenInfo & {
|
|
34
|
+
chatId: string;
|
|
35
|
+
});
|
|
36
|
+
/** Posts in its 1:1 and waits for its reply (`timeoutMs`: 60 s by default, at most 120 s). */
|
|
37
|
+
ask(text: string, opts?: {
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
}): Promise<AskResult>;
|
|
40
|
+
send(text: string, opts?: {
|
|
41
|
+
replyToId?: string;
|
|
42
|
+
clientId?: string;
|
|
43
|
+
}): Promise<Message>;
|
|
44
|
+
messages(opts?: {
|
|
45
|
+
limit?: number;
|
|
46
|
+
before?: string;
|
|
47
|
+
}): Promise<MessagePage>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Marten's public API, as you: your Martens, your chats, their tasks and the registry. Every call carries your API key;
|
|
51
|
+
* what it may do is the key's scopes.
|
|
52
|
+
*
|
|
53
|
+
* ```ts
|
|
54
|
+
* const marten = new Marten({ apiKey: process.env.MARTEN_API_KEY });
|
|
55
|
+
* const poker = await marten.martens.get("Poker");
|
|
56
|
+
* const { reply } = await poker.ask("Who's up this month?");
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export declare class Marten {
|
|
60
|
+
#private;
|
|
61
|
+
readonly baseUrl: string;
|
|
62
|
+
constructor(options?: MartenOptions);
|
|
63
|
+
/** A call to the API: JSON in and out, a `MartenError` for anything but a 2xx. */
|
|
64
|
+
request<T>(method: Method, path: string, body?: unknown): Promise<T>;
|
|
65
|
+
/** Who the key acts as, and what it may do. */
|
|
66
|
+
me(): Promise<Me>;
|
|
67
|
+
/** Revokes this client's own key: it opens nothing from then on (signing out). */
|
|
68
|
+
revokeKey(): Promise<void>;
|
|
69
|
+
readonly martens: {
|
|
70
|
+
/** Your own Martens: your personal one and the ones you made or installed. */
|
|
71
|
+
list: () => Promise<MartenInfo[]>;
|
|
72
|
+
/** One of your Martens by id, or by name: exact (any case) first, else the one whose name starts so. */
|
|
73
|
+
get: (nameOrId: string) => Promise<MartenHandle>;
|
|
74
|
+
};
|
|
75
|
+
readonly chats: {
|
|
76
|
+
/** Your Martens' 1:1s, and your groups when the key may read them (`chats:read`). */
|
|
77
|
+
list: () => Promise<Chat[]>;
|
|
78
|
+
/** A chat's messages, oldest first; `before` (a page's `next`) goes further back. */
|
|
79
|
+
messages: (chatId: string, opts?: {
|
|
80
|
+
limit?: number;
|
|
81
|
+
before?: string;
|
|
82
|
+
}) => Promise<MessagePage>;
|
|
83
|
+
/** Posts as you ("via <key name>" under it); it wakes the Martens a message from you would. */
|
|
84
|
+
send: (chatId: string, text: string, opts?: {
|
|
85
|
+
replyToId?: string;
|
|
86
|
+
clientId?: string;
|
|
87
|
+
}) => Promise<Message>;
|
|
88
|
+
};
|
|
89
|
+
readonly tasks: {
|
|
90
|
+
/** Your chats' tasks (or one chat's): open ones and those done in the last 30 days. */
|
|
91
|
+
list: (opts?: {
|
|
92
|
+
chatId?: string;
|
|
93
|
+
status?: TaskStatus | "open";
|
|
94
|
+
}) => Promise<Task[]>;
|
|
95
|
+
create: (task: NewTask) => Promise<Task>;
|
|
96
|
+
update: (taskId: string, changes: TaskChanges) => Promise<Task>;
|
|
97
|
+
};
|
|
98
|
+
readonly registry: {
|
|
99
|
+
list: () => Promise<Listing[]>;
|
|
100
|
+
get: (slug: string) => Promise<ListingDetail>;
|
|
101
|
+
/**
|
|
102
|
+
* Installs a listing: a Marten of your own (`"you"`, the default) or one added to a group (`{ chatId }`, which
|
|
103
|
+
* takes the `chats:write` scope). Its starter tasks' times are in `timezone` (this runtime's by default).
|
|
104
|
+
*/
|
|
105
|
+
install: (slug: string, target?: "you" | {
|
|
106
|
+
chatId: string;
|
|
107
|
+
}, opts?: {
|
|
108
|
+
timezone?: string;
|
|
109
|
+
}) => Promise<Installed>;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export {};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/** What a key may do; see `Scope` in the API's OpenAPI document (`/v1/openapi.json`). */
|
|
2
|
+
export type Scope = "martens" | "chats:read" | "chats:write" | "tasks" | "registry";
|
|
3
|
+
export interface Me {
|
|
4
|
+
user: {
|
|
5
|
+
id: string;
|
|
6
|
+
email: string;
|
|
7
|
+
name: string | null;
|
|
8
|
+
displayName: string;
|
|
9
|
+
};
|
|
10
|
+
key: {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
scopes: Scope[];
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** One of your own Martens. `chatId` is its 1:1 with you. */
|
|
17
|
+
export interface MartenInfo {
|
|
18
|
+
id: string;
|
|
19
|
+
kind: "personal" | "custom";
|
|
20
|
+
name: string;
|
|
21
|
+
color: string;
|
|
22
|
+
description: string;
|
|
23
|
+
look: string | null;
|
|
24
|
+
voice: string | null;
|
|
25
|
+
listing: {
|
|
26
|
+
slug: string;
|
|
27
|
+
version: string;
|
|
28
|
+
} | null;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
chatId: string | null;
|
|
31
|
+
}
|
|
32
|
+
export interface Chat {
|
|
33
|
+
id: string;
|
|
34
|
+
/** `direct`: a 1:1 with one of your Martens. */
|
|
35
|
+
kind: "direct" | "group";
|
|
36
|
+
title: string;
|
|
37
|
+
members: {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
}[];
|
|
41
|
+
martens: {
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
kind: string;
|
|
45
|
+
}[];
|
|
46
|
+
}
|
|
47
|
+
export interface Message {
|
|
48
|
+
id: string;
|
|
49
|
+
chatId: string;
|
|
50
|
+
author: {
|
|
51
|
+
kind: "person";
|
|
52
|
+
id: string;
|
|
53
|
+
name: string;
|
|
54
|
+
} | {
|
|
55
|
+
kind: "marten";
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
} | {
|
|
59
|
+
kind: "system";
|
|
60
|
+
id: null;
|
|
61
|
+
name: null;
|
|
62
|
+
};
|
|
63
|
+
/** Markdown; mentions read `@Name`. */
|
|
64
|
+
text: string;
|
|
65
|
+
at: string;
|
|
66
|
+
replyToId: string | null;
|
|
67
|
+
taskId: string | null;
|
|
68
|
+
appId: string | null;
|
|
69
|
+
fileId: string | null;
|
|
70
|
+
/** The name of the API key it was sent with, when it was. */
|
|
71
|
+
viaKey: string | null;
|
|
72
|
+
editedAt: string | null;
|
|
73
|
+
clientId: string | null;
|
|
74
|
+
}
|
|
75
|
+
export interface MessagePage {
|
|
76
|
+
/** Oldest first. */
|
|
77
|
+
messages: Message[];
|
|
78
|
+
/** Pass as `before` for the page before this one; null at the start of the chat. */
|
|
79
|
+
next: string | null;
|
|
80
|
+
}
|
|
81
|
+
/** `replied`; `quiet`: it chose not to answer; `timeout`: still working, its answer will land in the chat. */
|
|
82
|
+
export interface AskResult {
|
|
83
|
+
message: Message;
|
|
84
|
+
reply: Message | null;
|
|
85
|
+
status: "replied" | "quiet" | "timeout";
|
|
86
|
+
}
|
|
87
|
+
export type TaskStatus = "todo" | "in_progress" | "needs_review" | "done";
|
|
88
|
+
export interface Task {
|
|
89
|
+
id: string;
|
|
90
|
+
chatId: string;
|
|
91
|
+
title: string;
|
|
92
|
+
description: string;
|
|
93
|
+
status: TaskStatus;
|
|
94
|
+
assigneeId: string | null;
|
|
95
|
+
assigneeMartenId: string | null;
|
|
96
|
+
/** A calendar date (`YYYY-MM-DD`) with no time. */
|
|
97
|
+
due: string | null;
|
|
98
|
+
scheduledAt: string | null;
|
|
99
|
+
remind: boolean;
|
|
100
|
+
labels: string[];
|
|
101
|
+
repeat: {
|
|
102
|
+
seriesId: string;
|
|
103
|
+
rrule: string;
|
|
104
|
+
timezone: string;
|
|
105
|
+
text: string;
|
|
106
|
+
nextAt: string | null;
|
|
107
|
+
} | null;
|
|
108
|
+
createdBy: string | null;
|
|
109
|
+
createdByMartenId: string | null;
|
|
110
|
+
createdAt: string;
|
|
111
|
+
startedAt: string | null;
|
|
112
|
+
completedAt: string | null;
|
|
113
|
+
}
|
|
114
|
+
/** When and how often, in words: the server reads them as `marten task add --due/--repeat/--tz` does. */
|
|
115
|
+
interface TaskWhen {
|
|
116
|
+
/** "friday", "tomorrow 9am", "2026-10-31", "2026-10-31 09:00" (in `timezone`) or an ISO time; null or "none" clears it. */
|
|
117
|
+
due?: string | null;
|
|
118
|
+
/** "every weekday at 9", "every 2 weeks on friday", "daily", an RRULE, or "none" to stop. */
|
|
119
|
+
repeat?: string;
|
|
120
|
+
/** An IANA zone for `due` and `repeat`; UTC when left out. */
|
|
121
|
+
timezone?: string;
|
|
122
|
+
description?: string;
|
|
123
|
+
remind?: boolean;
|
|
124
|
+
labels?: string[];
|
|
125
|
+
}
|
|
126
|
+
export interface NewTask extends TaskWhen {
|
|
127
|
+
/** The chat it goes in (its card is posted there). */
|
|
128
|
+
chatId: string;
|
|
129
|
+
title: string;
|
|
130
|
+
/** `me`, or a person or Marten in the chat by name or id. In a 1:1 the Marten's unless you say otherwise. */
|
|
131
|
+
assignee?: string;
|
|
132
|
+
}
|
|
133
|
+
export interface TaskChanges extends TaskWhen {
|
|
134
|
+
title?: string;
|
|
135
|
+
status?: TaskStatus;
|
|
136
|
+
/** null: nobody. */
|
|
137
|
+
assignee?: string | null;
|
|
138
|
+
}
|
|
139
|
+
export interface Listing {
|
|
140
|
+
slug: string;
|
|
141
|
+
name: string;
|
|
142
|
+
version: string;
|
|
143
|
+
summary: string;
|
|
144
|
+
category: string;
|
|
145
|
+
for: ("you" | "group")[];
|
|
146
|
+
look: string | null;
|
|
147
|
+
voice: string | null;
|
|
148
|
+
color: string;
|
|
149
|
+
needs: {
|
|
150
|
+
apps: string[];
|
|
151
|
+
optional: string[];
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export interface ListingDetail extends Omit<Listing, "needs"> {
|
|
155
|
+
needs: {
|
|
156
|
+
apps: {
|
|
157
|
+
app: string;
|
|
158
|
+
why: string;
|
|
159
|
+
}[];
|
|
160
|
+
optional: {
|
|
161
|
+
app: string;
|
|
162
|
+
why: string;
|
|
163
|
+
}[];
|
|
164
|
+
mcp: unknown[];
|
|
165
|
+
};
|
|
166
|
+
examples: string[];
|
|
167
|
+
sketches: {
|
|
168
|
+
name: string;
|
|
169
|
+
}[];
|
|
170
|
+
tasks: {
|
|
171
|
+
title: string;
|
|
172
|
+
repeat: string | null;
|
|
173
|
+
time: string | null;
|
|
174
|
+
assignee: string;
|
|
175
|
+
}[];
|
|
176
|
+
skills: string[];
|
|
177
|
+
readme: string;
|
|
178
|
+
instructions: string;
|
|
179
|
+
changelog: string;
|
|
180
|
+
}
|
|
181
|
+
export interface Installed {
|
|
182
|
+
marten: {
|
|
183
|
+
id: string;
|
|
184
|
+
kind: string;
|
|
185
|
+
name: string;
|
|
186
|
+
[field: string]: unknown;
|
|
187
|
+
};
|
|
188
|
+
/** Its 1:1 with you, or the group it was added to. */
|
|
189
|
+
chatId: string;
|
|
190
|
+
}
|
|
191
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@martenhq/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Talk to your Martens from your own programs: ask them, read and post in your chats, manage tasks, install Martens.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"homepage": "https://marten.sh",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/RedPlanetHQ/marten-app.git",
|
|
11
|
+
"directory": "packages/sdk"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["marten", "agents", "ai", "sdk"],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=20"
|
|
16
|
+
},
|
|
17
|
+
"sideEffects": false,
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
|
+
},
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"files": ["dist", "README.md", "LICENSE"],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "bun build.ts",
|
|
33
|
+
"test": "bun test",
|
|
34
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/bun": "^1.4.2"
|
|
38
|
+
}
|
|
39
|
+
}
|