disk 0.8.18 → 0.8.19
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 +57 -10
- package/dist/cli.mjs +280 -0
- package/dist/disk-BLF7FFqG.d.cts +641 -0
- package/dist/disk-BLF7FFqG.d.mts +641 -0
- package/dist/index.cjs +1339 -1156
- package/dist/index.d.cts +207 -2148
- package/dist/index.d.mts +266 -0
- package/dist/index.mjs +2 -0
- package/dist/internal/tools.cjs +328 -0
- package/dist/internal/tools.d.cts +126 -0
- package/dist/internal/tools.d.mts +126 -0
- package/dist/internal/tools.mjs +326 -0
- package/dist/paths-1c4QqbOB.cjs +33 -0
- package/dist/paths-Br9snUhy.mjs +28 -0
- package/dist/src--b7LHPh_.mjs +1398 -0
- package/package.json +27 -22
- package/dist/cli.js +0 -1451
- package/dist/index.d.ts +0 -2207
- package/dist/index.js +0 -1203
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# disk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
SDK and CLI for [Archil](https://archil.com) disks. Create disks, list and inspect them, manage who can mount them, and run commands against them — all from scripts, CI, or an interactive terminal. It also ships **drop-in [filesystem tools](#filesystem-tools)** for AI SDK, Mastra, and Langchain.
|
|
4
4
|
|
|
5
5
|
`disk` talks to the Archil control plane over HTTPS and has no native dependencies. If you also want to mount a disk's data plane from Node (rare — most users want `disk exec` or the `archil` CLI), install [`@archildata/native`](https://www.npmjs.com/package/@archildata/native) alongside `disk`.
|
|
6
6
|
|
|
@@ -41,7 +41,7 @@ npx disk api-keys delete key-abc123
|
|
|
41
41
|
|
|
42
42
|
## Library
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
Setup:
|
|
45
45
|
|
|
46
46
|
```ts
|
|
47
47
|
import * as archil from "disk";
|
|
@@ -213,14 +213,6 @@ console.log(url); // https://control.…/api/shared/<token>
|
|
|
213
213
|
const weekLink = await d.share("reports/2026-01/summary.pdf", { expiresIn: 604800 });
|
|
214
214
|
```
|
|
215
215
|
|
|
216
|
-
### Named imports
|
|
217
|
-
|
|
218
|
-
If you prefer named imports over the namespace style, they work the same way:
|
|
219
|
-
|
|
220
|
-
```ts
|
|
221
|
-
import { createDisk, getDisk, listDisks, configure } from "disk";
|
|
222
|
-
```
|
|
223
|
-
|
|
224
216
|
### Multiple accounts or regions
|
|
225
217
|
|
|
226
218
|
For multi-tenant scripts, instantiate `Archil` directly instead of using the module-level `configure`:
|
|
@@ -235,6 +227,61 @@ const prodDisks = await prod.disks.list();
|
|
|
235
227
|
const stagingDisks = await staging.disks.list();
|
|
236
228
|
```
|
|
237
229
|
|
|
230
|
+
## Filesystem tools
|
|
231
|
+
|
|
232
|
+
Support for providing agents with a set of tools for using an Archil disk live in their own `@archildata/*` packages.
|
|
233
|
+
| Package | Framework |
|
|
234
|
+
| --- | --- |
|
|
235
|
+
| `@archildata/ai-sdk` | AI SDK |
|
|
236
|
+
| `@archildata/eve` | eve |
|
|
237
|
+
| `@archildata/mastra` | Mastra |
|
|
238
|
+
| `@archildata/langchain` | LangChain / LangGraph |
|
|
239
|
+
|
|
240
|
+
## Workspaces
|
|
241
|
+
For usage with multiple disks, you can create a workspace, which acts as a virtual disk where each mounted disk appears as a top-level directory:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
import { Archil } from "disk";
|
|
245
|
+
|
|
246
|
+
const archil = new Archil();
|
|
247
|
+
const source = await archil.disks.get(process.env.ARCHIL_SOURCE_DISK_ID!);
|
|
248
|
+
const output = await archil.disks.get(process.env.ARCHIL_OUTPUT_DISK_ID!);
|
|
249
|
+
|
|
250
|
+
const workspace = archil.workspace({
|
|
251
|
+
source: { disk: source, readOnly: true },
|
|
252
|
+
output,
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Workspace paths route to the right disk by their first segment. `readOnly` mounts
|
|
257
|
+
return an error from operations that mutate the disk.
|
|
258
|
+
|
|
259
|
+
Workspace mounts can also request delegations:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
const workspace = archil.workspace({
|
|
263
|
+
repo: {
|
|
264
|
+
disk: repoDisk,
|
|
265
|
+
checkoutPaths: ["src", "tmp/cache"],
|
|
266
|
+
queueMs: 5_000,
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
When `queueMs` is set without `checkoutPaths`, the mount root is acquired
|
|
272
|
+
during mount setup instead.
|
|
273
|
+
|
|
274
|
+
A `Workspace` is a full filesystem in its own right — it has the same object API
|
|
275
|
+
a `Disk` does (`getObject` / `putObject` / `deleteObject` / `listObjects` /
|
|
276
|
+
`grep` / `exec`; both implement the `FileSystem` interface), so you can use it
|
|
277
|
+
directly, and add or remove disks at runtime. A
|
|
278
|
+
workspace's keys carry the disk name as their first segment:
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
const data = await ws.getObject("data/reports/q1.csv"); // routes to the "data" disk
|
|
282
|
+
ws.addDisk("scratch", diskTmp); // mount another disk live; ws.removeDisk("scratch")
|
|
283
|
+
```
|
|
284
|
+
|
|
238
285
|
## Connecting to a disk's data plane
|
|
239
286
|
|
|
240
287
|
To run a command against a disk, use `Disk.exec()` — it returns stdout, stderr, and an exit code from an Archil-managed container with the disk pre-mounted. No local filesystem involved.
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { c as listDisks, i as deleteApiKey, n as createApiKey, o as getDisk, r as createDisk, s as listApiKeys, t as configure, y as ArchilApiError } from "./src--b7LHPh_.mjs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { Command, Option } from "commander";
|
|
5
|
+
//#region bin/cli.ts
|
|
6
|
+
const pkg = createRequire(import.meta.url)("../package.json");
|
|
7
|
+
const program = new Command();
|
|
8
|
+
program.name("disk").description("Manage Archil disks from the command line").version(pkg.version).addOption(new Option("-k, --api-key <key>", "Archil API key").env("ARCHIL_API_KEY")).addOption(new Option("-r, --region <region>", "Archil region").env("ARCHIL_REGION")).addOption(new Option("--base-url <url>", "Override control-plane base URL")).hook("preAction", () => {
|
|
9
|
+
const opts = program.opts();
|
|
10
|
+
try {
|
|
11
|
+
configure({
|
|
12
|
+
apiKey: opts.apiKey,
|
|
13
|
+
region: opts.region,
|
|
14
|
+
baseUrl: opts.baseUrl
|
|
15
|
+
});
|
|
16
|
+
} catch (err) {
|
|
17
|
+
fail(err);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
function fail(err) {
|
|
21
|
+
if (err instanceof ArchilApiError) console.error(`Error (${err.status}): ${err.message}`);
|
|
22
|
+
else console.error(err instanceof Error ? err.message : String(err));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
function isDiskId(s) {
|
|
26
|
+
return /^(dsk-|fs-)[0-9a-fA-F]+$/.test(s);
|
|
27
|
+
}
|
|
28
|
+
async function resolveDisk(idOrName) {
|
|
29
|
+
if (isDiskId(idOrName)) return getDisk(idOrName);
|
|
30
|
+
const matches = await listDisks({ name: idOrName });
|
|
31
|
+
if (matches.length === 0) throw new ArchilApiError(`No disk found with name '${idOrName}'`, 404);
|
|
32
|
+
if (matches.length > 1) throw new ArchilApiError(`Multiple disks match name '${idOrName}' — pass a disk id (dsk-...) instead`, 400);
|
|
33
|
+
return matches[0];
|
|
34
|
+
}
|
|
35
|
+
function formatBytes(n) {
|
|
36
|
+
if (n === void 0 || n === null) return void 0;
|
|
37
|
+
if (n < 1024) return `${n} B`;
|
|
38
|
+
const units = [
|
|
39
|
+
"KB",
|
|
40
|
+
"MB",
|
|
41
|
+
"GB",
|
|
42
|
+
"TB",
|
|
43
|
+
"PB"
|
|
44
|
+
];
|
|
45
|
+
let v = n / 1024;
|
|
46
|
+
let i = 0;
|
|
47
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
48
|
+
v /= 1024;
|
|
49
|
+
i++;
|
|
50
|
+
}
|
|
51
|
+
return `${v.toFixed(v >= 100 ? 0 : v >= 10 ? 1 : 2)} ${units[i]}`;
|
|
52
|
+
}
|
|
53
|
+
function renderTable(rows, headers) {
|
|
54
|
+
const all = headers ? [headers, ...rows] : rows;
|
|
55
|
+
if (all.length === 0) return "";
|
|
56
|
+
const cols = Math.max(...all.map((r) => r.length));
|
|
57
|
+
const widths = [];
|
|
58
|
+
for (let i = 0; i < cols; i++) widths[i] = Math.max(...all.map((r) => (r[i] ?? "").length));
|
|
59
|
+
const bar = (l, m, r) => l + widths.map((w) => "─".repeat(w + 2)).join(m) + r;
|
|
60
|
+
const line = (r) => "│ " + widths.map((w, i) => (r[i] ?? "").padEnd(w)).join(" │ ") + " │";
|
|
61
|
+
const out = [bar("╭", "┬", "╮")];
|
|
62
|
+
if (headers) {
|
|
63
|
+
out.push(line(headers));
|
|
64
|
+
out.push(bar("├", "┼", "┤"));
|
|
65
|
+
}
|
|
66
|
+
for (const r of rows) out.push(line(r));
|
|
67
|
+
out.push(bar("╰", "┴", "╯"));
|
|
68
|
+
return out.join("\n");
|
|
69
|
+
}
|
|
70
|
+
function section(title, body) {
|
|
71
|
+
console.log("");
|
|
72
|
+
console.log(title);
|
|
73
|
+
console.log(body);
|
|
74
|
+
}
|
|
75
|
+
function printDisk(d) {
|
|
76
|
+
console.log(`${d.organization}/${d.name} (${d.id})`);
|
|
77
|
+
const visible = [
|
|
78
|
+
["status", d.status],
|
|
79
|
+
["provider", d.provider],
|
|
80
|
+
["region", d.region],
|
|
81
|
+
["created", d.createdAt],
|
|
82
|
+
["last accessed", d.lastAccessed],
|
|
83
|
+
["data size", formatBytes(d.dataSize)],
|
|
84
|
+
["monthly usage", d.monthlyUsage]
|
|
85
|
+
].filter(([, v]) => v !== void 0 && v !== null && v !== "").map(([k, v]) => [k, String(v)]);
|
|
86
|
+
console.log("");
|
|
87
|
+
console.log(renderTable(visible));
|
|
88
|
+
if (d.mounts && d.mounts.length > 0) section("Mounts", renderTable(d.mounts.map((m) => [
|
|
89
|
+
m.type ?? "?",
|
|
90
|
+
m.name ?? m.path ?? "",
|
|
91
|
+
m.accessMode ?? ""
|
|
92
|
+
]), [
|
|
93
|
+
"type",
|
|
94
|
+
"location",
|
|
95
|
+
"mode"
|
|
96
|
+
]));
|
|
97
|
+
if (d.authorizedUsers && d.authorizedUsers.length > 0) section("Authorized users", renderTable(d.authorizedUsers.map((u) => {
|
|
98
|
+
if (u.type === "token") return [
|
|
99
|
+
"token",
|
|
100
|
+
u.nickname ?? "(no name)",
|
|
101
|
+
u.tokenSuffix ? `-${u.tokenSuffix}` : ""
|
|
102
|
+
];
|
|
103
|
+
return [
|
|
104
|
+
u.type ?? "?",
|
|
105
|
+
u.identifier ?? u.principal ?? "",
|
|
106
|
+
""
|
|
107
|
+
];
|
|
108
|
+
}), [
|
|
109
|
+
"type",
|
|
110
|
+
"name / identifier",
|
|
111
|
+
"suffix"
|
|
112
|
+
]));
|
|
113
|
+
if (d.connectedClients && d.connectedClients.length > 0) section("Connected clients", renderTable(d.connectedClients.map((c) => [
|
|
114
|
+
c.id ?? "",
|
|
115
|
+
c.ipAddress ?? "",
|
|
116
|
+
c.connectedAt ?? ""
|
|
117
|
+
]), [
|
|
118
|
+
"id",
|
|
119
|
+
"ip",
|
|
120
|
+
"connected at"
|
|
121
|
+
]));
|
|
122
|
+
}
|
|
123
|
+
program.command("list").description("List disks in the current region").option("--limit <n>", "Maximum number of disks to return", (v) => parseInt(v, 10)).option("-o, --output <format>", "Output format: table | json", "table").action(async (opts) => {
|
|
124
|
+
try {
|
|
125
|
+
const disks = await listDisks({ limit: opts.limit });
|
|
126
|
+
if (opts.output === "json") {
|
|
127
|
+
console.log(JSON.stringify(disks, null, 2));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (disks.length === 0) {
|
|
131
|
+
console.log("No disks found.");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const rows = disks.map((d) => [
|
|
135
|
+
d.id,
|
|
136
|
+
`${d.organization}/${d.name}`,
|
|
137
|
+
d.status
|
|
138
|
+
]);
|
|
139
|
+
console.log(renderTable(rows, [
|
|
140
|
+
"id",
|
|
141
|
+
"name",
|
|
142
|
+
"status"
|
|
143
|
+
]));
|
|
144
|
+
} catch (err) {
|
|
145
|
+
fail(err);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
program.command("get <id|name>").description("Show details for a disk (accepts a disk id or name)").option("-o, --output <format>", "Output format: table | json", "table").action(async (idOrName, opts) => {
|
|
149
|
+
try {
|
|
150
|
+
const d = await resolveDisk(idOrName);
|
|
151
|
+
if (opts.output === "json") console.log(JSON.stringify(d, null, 2));
|
|
152
|
+
else printDisk(d);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
fail(err);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
program.command("create <name>").description("Create a new disk").action(async (name) => {
|
|
158
|
+
try {
|
|
159
|
+
const result = await createDisk({ name });
|
|
160
|
+
console.log(`Created disk ${result.disk.organization}/${result.disk.name}`);
|
|
161
|
+
console.log(` id: ${result.disk.id}`);
|
|
162
|
+
console.log(` status: ${result.disk.status}`);
|
|
163
|
+
if (result.token) {
|
|
164
|
+
console.log("");
|
|
165
|
+
console.log("Disk token (save this — it cannot be retrieved again):");
|
|
166
|
+
console.log(` ${result.token}`);
|
|
167
|
+
if (result.tokenIdentifier) console.log(` identifier: ${result.tokenIdentifier}`);
|
|
168
|
+
}
|
|
169
|
+
} catch (err) {
|
|
170
|
+
fail(err);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
program.command("delete <id|name>").description("Delete a disk (accepts a disk id or name)").action(async (idOrName) => {
|
|
174
|
+
try {
|
|
175
|
+
const d = await resolveDisk(idOrName);
|
|
176
|
+
await d.delete();
|
|
177
|
+
console.log(`Deleted ${d.organization}/${d.name} (${d.id})`);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
fail(err);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
program.command("exec <id|name> <command...>").description("Run a command in a container with the disk mounted, return stdout/stderr/exit code (accepts a disk id or name)").action(async (idOrName, cmd) => {
|
|
183
|
+
try {
|
|
184
|
+
const result = await (await resolveDisk(idOrName)).exec(cmd.join(" "));
|
|
185
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
186
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
187
|
+
process.exit(result.exitCode);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
fail(err);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
program.command("grep <pattern> <target>").description("Constant-time parallel server-side grep across a disk. <target> is <id|name>[/<path>] — the path after the first '/' is the directory to search (defaults to the disk root). Accepts a disk id or name.").option("--no-recursive", "Search only the given directory, not its subdirectories (recursive is the default)").option("--max-duration <seconds>", "Wall-clock deadline for the whole search", (v) => parseInt(v, 10)).option("--concurrency <n>", "Max parallel grep workers (clamped to fleet capacity)", (v) => parseInt(v, 10)).option("--max-results <n>", "Stop once this many matches are collected", (v) => parseInt(v, 10)).option("-o, --output <format>", "Output format: text | json", "text").action(async (pattern, target, opts) => {
|
|
193
|
+
try {
|
|
194
|
+
const slash = target.indexOf("/");
|
|
195
|
+
const idOrName = slash === -1 ? target : target.slice(0, slash);
|
|
196
|
+
const directory = slash === -1 ? "" : target.slice(slash + 1);
|
|
197
|
+
const res = await (await resolveDisk(idOrName)).grep({
|
|
198
|
+
directory,
|
|
199
|
+
pattern,
|
|
200
|
+
recursive: opts.recursive !== false,
|
|
201
|
+
maxDurationSeconds: opts.maxDuration,
|
|
202
|
+
concurrency: opts.concurrency,
|
|
203
|
+
maxResults: opts.maxResults
|
|
204
|
+
});
|
|
205
|
+
if (opts.output === "json") console.log(JSON.stringify(res, null, 2));
|
|
206
|
+
else {
|
|
207
|
+
for (const m of res.matches) console.log(`${m.file}:${m.line}:${m.text}`);
|
|
208
|
+
console.error(`${res.matches.length} match${res.matches.length === 1 ? "" : "es"} · ${res.filesScanned} files scanned · ${res.containersDispatched} containers · ${res.durationMs}ms · ${res.stoppedReason}`);
|
|
209
|
+
if (res.stoppedReason !== "completed") console.error(`warning: search did not complete (${res.stoppedReason}); results may be partial`);
|
|
210
|
+
}
|
|
211
|
+
process.exit(res.matches.length > 0 ? 0 : 1);
|
|
212
|
+
} catch (err) {
|
|
213
|
+
fail(err);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
const keys = program.command("api-keys").description("Manage Archil API keys (account-level credentials)");
|
|
217
|
+
keys.command("list").description("List API keys").option("--limit <n>", "Maximum number of keys to return", (v) => parseInt(v, 10)).action(async (opts) => {
|
|
218
|
+
try {
|
|
219
|
+
const ks = await listApiKeys({ limit: opts.limit });
|
|
220
|
+
if (ks.length === 0) {
|
|
221
|
+
console.log("No API keys found.");
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
for (const k of ks) console.log(`${k.id ?? "-"}\t${k.name ?? "-"}\t${k.createdAt ?? ""}`);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
fail(err);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
keys.command("create <name>").description("Create a new API key (the key value is shown once)").option("--description <description>", "Optional description").action(async (name, opts) => {
|
|
230
|
+
try {
|
|
231
|
+
const k = await createApiKey({
|
|
232
|
+
name,
|
|
233
|
+
description: opts.description
|
|
234
|
+
});
|
|
235
|
+
console.log(`Created API key ${name} (${k.id ?? "?"})`);
|
|
236
|
+
if (k.token) {
|
|
237
|
+
console.log("");
|
|
238
|
+
console.log("API key value (save this — it cannot be retrieved again):");
|
|
239
|
+
console.log(` ${k.token}`);
|
|
240
|
+
}
|
|
241
|
+
} catch (err) {
|
|
242
|
+
fail(err);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
keys.command("delete <id>").description("Delete an API key").action(async (id) => {
|
|
246
|
+
try {
|
|
247
|
+
await deleteApiKey(id);
|
|
248
|
+
console.log(`Deleted API key ${id}`);
|
|
249
|
+
} catch (err) {
|
|
250
|
+
fail(err);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
function rewriteSugar(argv) {
|
|
254
|
+
const known = /* @__PURE__ */ new Set([
|
|
255
|
+
"list",
|
|
256
|
+
"get",
|
|
257
|
+
"create",
|
|
258
|
+
"delete",
|
|
259
|
+
"exec",
|
|
260
|
+
"grep",
|
|
261
|
+
"api-keys",
|
|
262
|
+
"help",
|
|
263
|
+
"--help",
|
|
264
|
+
"-h",
|
|
265
|
+
"--version",
|
|
266
|
+
"-V"
|
|
267
|
+
]);
|
|
268
|
+
const head = argv[2];
|
|
269
|
+
const next = argv[3];
|
|
270
|
+
if (head && next === "exec" && !known.has(head) && !head.startsWith("-")) return [
|
|
271
|
+
...argv.slice(0, 2),
|
|
272
|
+
"exec",
|
|
273
|
+
head,
|
|
274
|
+
...argv.slice(4)
|
|
275
|
+
];
|
|
276
|
+
return argv;
|
|
277
|
+
}
|
|
278
|
+
program.parseAsync(rewriteSugar(process.argv));
|
|
279
|
+
//#endregion
|
|
280
|
+
export {};
|