artisense 0.1.0 → 0.1.2
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/dist/index.js +169 -57
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,21 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/commands/
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
// src/commands/login.ts
|
|
4
|
+
import { createServer } from "node:http";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { exec } from "node:child_process";
|
|
6
7
|
|
|
7
8
|
// src/config.ts
|
|
8
9
|
import { readFile } from "node:fs/promises";
|
|
9
10
|
import { resolve } from "node:path";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
|
|
12
|
+
// src/credentials.ts
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { writeFile, mkdir } from "node:fs/promises";
|
|
15
|
+
import { join, dirname } from "node:path";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
function getCredentialsPath() {
|
|
18
|
+
const configDir = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
19
|
+
return join(configDir, "artisense", "credentials.json");
|
|
20
|
+
}
|
|
21
|
+
async function saveCredentials(data) {
|
|
22
|
+
const filePath = getCredentialsPath();
|
|
23
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
24
|
+
await writeFile(filePath, JSON.stringify(data, null, 2), {
|
|
25
|
+
encoding: "utf-8",
|
|
26
|
+
mode: 384
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function loadCredentials() {
|
|
30
|
+
try {
|
|
31
|
+
const content = readFileSync(getCredentialsPath(), "utf-8");
|
|
32
|
+
const parsed = JSON.parse(content);
|
|
33
|
+
if (parsed.apiKey && parsed.baseUrl)
|
|
34
|
+
return parsed;
|
|
35
|
+
return null;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
17
38
|
}
|
|
18
|
-
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/config.ts
|
|
42
|
+
var DEFAULT_BASE_URL = "https://artisense.org";
|
|
43
|
+
function getApiKey() {
|
|
44
|
+
const envKey = process.env.ARTISENSE_API_KEY;
|
|
45
|
+
if (envKey)
|
|
46
|
+
return envKey;
|
|
47
|
+
const creds = loadCredentials();
|
|
48
|
+
if (creds?.apiKey)
|
|
49
|
+
return creds.apiKey;
|
|
50
|
+
console.error("Error: Not authenticated.\nRun `artisense login` or set ARTISENSE_API_KEY.");
|
|
51
|
+
process.exit(1);
|
|
19
52
|
}
|
|
20
53
|
async function getBaseUrl() {
|
|
21
54
|
if (process.env.ARTISENSE_BASE_URL) {
|
|
@@ -29,10 +62,80 @@ async function getBaseUrl() {
|
|
|
29
62
|
return match[1].trim().replace(/\/$/, "");
|
|
30
63
|
}
|
|
31
64
|
} catch {}
|
|
65
|
+
const creds = loadCredentials();
|
|
66
|
+
if (creds?.baseUrl)
|
|
67
|
+
return creds.baseUrl;
|
|
32
68
|
return DEFAULT_BASE_URL;
|
|
33
69
|
}
|
|
34
70
|
|
|
71
|
+
// src/commands/login.ts
|
|
72
|
+
var TIMEOUT_MS = 120000;
|
|
73
|
+
async function loginCommand() {
|
|
74
|
+
const state = randomBytes(32).toString("hex");
|
|
75
|
+
const baseUrl = DEFAULT_BASE_URL;
|
|
76
|
+
const { key } = await new Promise((resolve2, reject) => {
|
|
77
|
+
const server = createServer((req, res) => {
|
|
78
|
+
const url = new URL(req.url ?? "/", `http://localhost`);
|
|
79
|
+
if (url.pathname !== "/callback") {
|
|
80
|
+
res.writeHead(404);
|
|
81
|
+
res.end("Not found");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const receivedState = url.searchParams.get("state");
|
|
85
|
+
const receivedKey = url.searchParams.get("key");
|
|
86
|
+
if (receivedState !== state) {
|
|
87
|
+
res.writeHead(400);
|
|
88
|
+
res.end("Invalid state parameter");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!receivedKey) {
|
|
92
|
+
res.writeHead(400);
|
|
93
|
+
res.end("Missing key");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
97
|
+
res.end(`<!DOCTYPE html>
|
|
98
|
+
<html><body style="font-family:system-ui;display:flex;justify-content:center;align-items:center;height:100vh;margin:0">
|
|
99
|
+
<div style="text-align:center">
|
|
100
|
+
<h2>✓ Authenticated</h2>
|
|
101
|
+
<p>You can close this tab and return to the terminal.</p>
|
|
102
|
+
</div>
|
|
103
|
+
</body></html>`);
|
|
104
|
+
server.close();
|
|
105
|
+
resolve2({ key: receivedKey });
|
|
106
|
+
});
|
|
107
|
+
server.listen(0, "127.0.0.1", () => {
|
|
108
|
+
const addr = server.address();
|
|
109
|
+
if (!addr || typeof addr === "string") {
|
|
110
|
+
reject(new Error("Failed to start local server"));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const port = addr.port;
|
|
114
|
+
const authUrl = `${baseUrl}/auth/cli?port=${port}&state=${state}`;
|
|
115
|
+
console.log("Opening browser for authentication...");
|
|
116
|
+
console.log(`If the browser doesn't open, visit: ${authUrl}
|
|
117
|
+
`);
|
|
118
|
+
openBrowser(authUrl);
|
|
119
|
+
});
|
|
120
|
+
const timer = setTimeout(() => {
|
|
121
|
+
server.close();
|
|
122
|
+
reject(new Error("Login timed out. Please try again."));
|
|
123
|
+
}, TIMEOUT_MS);
|
|
124
|
+
server.on("close", () => clearTimeout(timer));
|
|
125
|
+
});
|
|
126
|
+
await saveCredentials({ apiKey: key, baseUrl });
|
|
127
|
+
console.log("Authenticated successfully!");
|
|
128
|
+
console.log(`Credentials saved to ~/.config/artisense/credentials.json
|
|
129
|
+
`);
|
|
130
|
+
}
|
|
131
|
+
function openBrowser(url) {
|
|
132
|
+
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "${url}"` : `xdg-open "${url}"`;
|
|
133
|
+
exec(cmd, () => {});
|
|
134
|
+
}
|
|
135
|
+
|
|
35
136
|
// src/commands/skill.ts
|
|
137
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
|
|
138
|
+
import { resolve as resolve2, dirname as dirname2 } from "node:path";
|
|
36
139
|
var TARGETS = {
|
|
37
140
|
claude: {
|
|
38
141
|
path: ".claude/commands/artisense.md",
|
|
@@ -69,46 +172,39 @@ async function skillCommand(args) {
|
|
|
69
172
|
const content = await res.text();
|
|
70
173
|
const cwd = process.cwd();
|
|
71
174
|
const rootPath = resolve2(cwd, "SKILL.md");
|
|
72
|
-
await
|
|
175
|
+
await writeFile2(rootPath, content, "utf-8");
|
|
73
176
|
console.log(` Project root: ${rootPath}`);
|
|
74
|
-
|
|
177
|
+
const writeResults = await Promise.all(targetKeys.map(async (key) => {
|
|
75
178
|
const target = TARGETS[key];
|
|
76
179
|
const fullPath = resolve2(cwd, target.path);
|
|
77
180
|
try {
|
|
78
|
-
await
|
|
79
|
-
await
|
|
80
|
-
|
|
181
|
+
await mkdir2(dirname2(fullPath), { recursive: true });
|
|
182
|
+
await writeFile2(fullPath, target.wrap(content), "utf-8");
|
|
183
|
+
return ` ${target.label}: ${fullPath}`;
|
|
81
184
|
} catch {
|
|
82
|
-
|
|
185
|
+
return ` ${target.label}: skipped (cannot write)`;
|
|
83
186
|
}
|
|
84
|
-
}
|
|
187
|
+
}));
|
|
188
|
+
writeResults.forEach((msg) => console.log(msg));
|
|
85
189
|
console.log(`
|
|
86
190
|
Next: set your API key:
|
|
87
191
|
ARTISENSE_API_KEY="ask_..."
|
|
88
192
|
`);
|
|
89
193
|
}
|
|
90
194
|
function parseArgs(args) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const key = arg.slice(2);
|
|
101
|
-
if (key in TARGETS) {
|
|
102
|
-
targetKeys.push(key);
|
|
103
|
-
} else {
|
|
104
|
-
console.error(`Unknown option: ${arg}`);
|
|
105
|
-
printUsage();
|
|
106
|
-
process.exit(1);
|
|
107
|
-
}
|
|
108
|
-
} else {
|
|
109
|
-
baseUrl = arg.replace(/\/$/, "");
|
|
110
|
-
}
|
|
195
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
196
|
+
printUsage();
|
|
197
|
+
process.exit(0);
|
|
198
|
+
}
|
|
199
|
+
const unknownFlag = args.find((arg) => arg.startsWith("--") && arg !== "--all" && !(arg.slice(2) in TARGETS));
|
|
200
|
+
if (unknownFlag) {
|
|
201
|
+
console.error(`Unknown option: ${unknownFlag}`);
|
|
202
|
+
printUsage();
|
|
203
|
+
process.exit(1);
|
|
111
204
|
}
|
|
205
|
+
const targetKeys = args.includes("--all") ? ALL_TARGET_KEYS : args.filter((arg) => arg.startsWith("--") && (arg.slice(2) in TARGETS)).map((arg) => arg.slice(2));
|
|
206
|
+
const baseUrlArg = args.find((arg) => !arg.startsWith("--"));
|
|
207
|
+
const baseUrl = baseUrlArg ? baseUrlArg.replace(/\/$/, "") : DEFAULT_BASE_URL;
|
|
112
208
|
return { baseUrl, targetKeys };
|
|
113
209
|
}
|
|
114
210
|
function printUsage() {
|
|
@@ -175,29 +271,34 @@ async function documentsCommand(args) {
|
|
|
175
271
|
}
|
|
176
272
|
}
|
|
177
273
|
async function listDocuments() {
|
|
178
|
-
const
|
|
274
|
+
const apiKey = getApiKey();
|
|
275
|
+
const baseUrl = await getBaseUrl();
|
|
179
276
|
const result = await request(baseUrl, "/api/v1/documents", apiKey);
|
|
180
277
|
if (result.data.length === 0) {
|
|
181
278
|
console.log("No documents found.");
|
|
182
279
|
return;
|
|
183
280
|
}
|
|
184
|
-
|
|
281
|
+
const lines = result.data.map((doc) => {
|
|
185
282
|
const title = doc.title || "(Untitled)";
|
|
186
|
-
|
|
187
|
-
}
|
|
283
|
+
return `${doc.id} ${title} [${doc.type}] ${doc.updatedAt}`;
|
|
284
|
+
});
|
|
285
|
+
console.log(lines.join(`
|
|
286
|
+
`));
|
|
188
287
|
}
|
|
189
288
|
async function getDocument(id) {
|
|
190
289
|
if (!id) {
|
|
191
290
|
console.error("Usage: artisense docs get <document-id>");
|
|
192
291
|
process.exit(1);
|
|
193
292
|
}
|
|
194
|
-
const
|
|
293
|
+
const apiKey = getApiKey();
|
|
294
|
+
const baseUrl = await getBaseUrl();
|
|
195
295
|
const result = await request(baseUrl, `/api/v1/documents/${id}`, apiKey);
|
|
196
296
|
console.log(JSON.stringify(result.data, null, 2));
|
|
197
297
|
}
|
|
198
298
|
async function createDocument(args) {
|
|
199
299
|
const body = parseFlags(args, ["title", "type", "parentId"]);
|
|
200
|
-
const
|
|
300
|
+
const apiKey = getApiKey();
|
|
301
|
+
const baseUrl = await getBaseUrl();
|
|
201
302
|
const result = await request(baseUrl, "/api/v1/documents", apiKey, {
|
|
202
303
|
method: "POST",
|
|
203
304
|
body
|
|
@@ -214,8 +315,12 @@ async function updateDocument(id, args) {
|
|
|
214
315
|
console.error("Provide at least one of: --title, --content");
|
|
215
316
|
process.exit(1);
|
|
216
317
|
}
|
|
217
|
-
const
|
|
218
|
-
const
|
|
318
|
+
const apiKey = getApiKey();
|
|
319
|
+
const baseUrl = await getBaseUrl();
|
|
320
|
+
const result = await request(baseUrl, `/api/v1/documents/${id}`, apiKey, {
|
|
321
|
+
method: "PATCH",
|
|
322
|
+
body
|
|
323
|
+
});
|
|
219
324
|
console.log(`Updated: ${result.data.id} ${result.data.title || "(Untitled)"}`);
|
|
220
325
|
}
|
|
221
326
|
async function deleteDocument(id) {
|
|
@@ -223,24 +328,29 @@ async function deleteDocument(id) {
|
|
|
223
328
|
console.error("Usage: artisense docs delete <document-id>");
|
|
224
329
|
process.exit(1);
|
|
225
330
|
}
|
|
226
|
-
const
|
|
331
|
+
const apiKey = getApiKey();
|
|
332
|
+
const baseUrl = await getBaseUrl();
|
|
227
333
|
await request(baseUrl, `/api/v1/documents/${id}`, apiKey, {
|
|
228
334
|
method: "DELETE"
|
|
229
335
|
});
|
|
230
336
|
console.log(`Deleted: ${id}`);
|
|
231
337
|
}
|
|
232
338
|
function parseFlags(args, allowedKeys) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
339
|
+
return args.reduce((acc, arg) => {
|
|
340
|
+
if (acc.pendingKey) {
|
|
341
|
+
return {
|
|
342
|
+
result: { ...acc.result, [acc.pendingKey]: arg },
|
|
343
|
+
pendingKey: null
|
|
344
|
+
};
|
|
345
|
+
}
|
|
236
346
|
if (arg.startsWith("--")) {
|
|
237
347
|
const key = arg.slice(2);
|
|
238
|
-
if (allowedKeys.includes(key)
|
|
239
|
-
|
|
348
|
+
if (allowedKeys.includes(key)) {
|
|
349
|
+
return { ...acc, pendingKey: key };
|
|
240
350
|
}
|
|
241
351
|
}
|
|
242
|
-
|
|
243
|
-
|
|
352
|
+
return acc;
|
|
353
|
+
}, { result: {}, pendingKey: null }).result;
|
|
244
354
|
}
|
|
245
355
|
function printUsage2() {
|
|
246
356
|
console.log(`
|
|
@@ -261,6 +371,9 @@ Commands:
|
|
|
261
371
|
var args = process.argv.slice(2);
|
|
262
372
|
var command = args[0];
|
|
263
373
|
switch (command) {
|
|
374
|
+
case "login":
|
|
375
|
+
await loginCommand();
|
|
376
|
+
break;
|
|
264
377
|
case "skill":
|
|
265
378
|
await skillCommand(args.slice(1));
|
|
266
379
|
break;
|
|
@@ -286,14 +399,13 @@ artisense — CLI for Artisense
|
|
|
286
399
|
Usage: artisense <command>
|
|
287
400
|
|
|
288
401
|
Commands:
|
|
402
|
+
login Authenticate via browser
|
|
289
403
|
skill Download SKILL.md for AI agents
|
|
290
404
|
docs Manage documents (list, get, create, update, delete)
|
|
291
405
|
help Show this help
|
|
292
406
|
|
|
293
|
-
Environment:
|
|
294
|
-
ARTISENSE_API_KEY Your API key (required for docs commands)
|
|
295
|
-
|
|
296
407
|
Examples:
|
|
408
|
+
artisense login
|
|
297
409
|
artisense skill --claude
|
|
298
410
|
artisense docs list
|
|
299
411
|
artisense docs get <id>
|