@archildata/just-bash 0.1.13 → 0.8.1

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/shell.js CHANGED
@@ -3,45 +3,183 @@
3
3
  // bin/shell.ts
4
4
  import * as readline from "readline";
5
5
  import { Command } from "commander";
6
- import { ArchilClient } from "@archildata/client";
6
+ import { ArchilClient, Archil } from "@archildata/client";
7
7
  import { Bash } from "just-bash";
8
- import { ArchilFs } from "@archildata/just-bash";
8
+ import { ArchilFs, createArchilCommand } from "@archildata/just-bash";
9
9
  var program = new Command();
10
- program.name("archil-shell").description("Interactive bash shell with Archil filesystem").version("0.1.0").argument("[region]", "Region identifier (e.g., aws-us-east-1)").argument("[disk]", "Disk name (e.g., myaccount/mydisk)").option("-r, --region <region>", "Region identifier (e.g., aws-us-east-1)").option("-d, --disk <disk>", "Disk name (e.g., myaccount/mydisk)").option("-t, --token <token>", "Auth token (defaults to IAM)").option("-l, --log-level <level>", "Log level: trace, debug, info, warn, error").addHelpText("after", `
10
+ program.name("archil-shell").description("Interactive bash shell with Archil filesystem").version("0.1.0").argument("[target]", "S3 bucket (s3://bucket) or region (e.g., aws-us-east-1)").argument("[disk]", "Disk name (e.g., myaccount/mydisk) - only when target is region").option("-r, --region <region>", "Region identifier (e.g., aws-us-east-1)").option("-d, --disk <disk>", "Disk name (e.g., myaccount/mydisk)").option("-k, --api-key <key>", "API key for disk management (required for S3 bucket mode)").option("-t, --token <token>", "Auth token for direct connection (defaults to IAM)").option("-l, --log-level <level>", "Log level: trace, debug, info, warn, error").option("--bucket-region <region>", "AWS region for the S3 bucket (default: us-east-1)").option("--bucket-prefix <prefix>", "Path prefix within the bucket").addHelpText("after", `
11
+ Quick Start (S3 bucket mode):
12
+ Connect directly to an S3 bucket - creates a disk if needed:
13
+ $ npx @archildata/just-bash s3://my-bucket --api-key adt_xxx
14
+ $ npx @archildata/just-bash s3://my-bucket --api-key adt_xxx --bucket-region us-west-2
15
+
16
+ AWS credentials are read from environment:
17
+ $ AWS_ACCESS_KEY_ID=xxx AWS_SECRET_ACCESS_KEY=xxx npx @archildata/just-bash s3://my-bucket -k adt_xxx
18
+
19
+ Traditional usage (direct connection):
20
+ $ npx @archildata/just-bash aws-us-east-1 myaccount/mydisk
21
+ $ npx @archildata/just-bash --region aws-us-east-1 --disk myaccount/mydisk
22
+ $ ARCHIL_TOKEN=xxx npx @archildata/just-bash aws-us-east-1 myaccount/mydisk
23
+
24
+ Subdirectory mounting (mount a subdirectory as the root):
25
+ $ npx @archildata/just-bash aws-us-east-1 myaccount/mydisk:/data/project
26
+
11
27
  Environment variables:
28
+ ARCHIL_API_KEY API key for disk management
12
29
  ARCHIL_REGION Fallback for --region
13
30
  ARCHIL_DISK Fallback for --disk
14
31
  ARCHIL_TOKEN Fallback for --token (recommended for secrets)
15
32
  ARCHIL_LOG_LEVEL Fallback for --log-level
16
-
17
- Examples:
18
- $ npx @archildata/just-bash aws-us-east-1 myaccount/mydisk
19
- $ npx @archildata/just-bash --region aws-us-east-1 --disk myaccount/mydisk
20
- $ ARCHIL_TOKEN=xxx npx @archildata/just-bash aws-us-east-1 myaccount/mydisk
21
- $ npx @archildata/just-bash -r aws-us-east-1 -d myaccount/mydisk -l debug
33
+ AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY S3 credentials for bucket mode
22
34
  `);
23
35
  program.parse();
24
36
  var opts = program.opts();
25
37
  var args = program.args;
26
- var region = args[0] || opts.region || process.env.ARCHIL_REGION;
27
- var diskName = args[1] || opts.disk || process.env.ARCHIL_DISK;
28
- var authToken = opts.token || process.env.ARCHIL_TOKEN;
29
- var logLevel = opts.logLevel || process.env.ARCHIL_LOG_LEVEL;
30
- if (!region || !diskName) {
31
- console.error("Error: region and disk are required\n");
32
- console.error("Usage:");
33
- console.error(" npx @archildata/just-bash <region> <disk>");
34
- console.error(" npx @archildata/just-bash --region <region> --disk <disk>");
35
- console.error("\nExamples:");
36
- console.error(" npx @archildata/just-bash aws-us-east-1 myaccount/mydisk");
37
- console.error(" ARCHIL_TOKEN=xxx npx @archildata/just-bash aws-us-east-1 myaccount/mydisk");
38
- console.error("\nRun with --help for more options.");
39
- process.exit(1);
38
+ var isS3Bucket = args[0]?.startsWith("s3://");
39
+ function parseS3Url(url) {
40
+ const match = url.match(/^s3:\/\/([^/]+)(\/.*)?$/);
41
+ if (!match) {
42
+ throw new Error(`Invalid S3 URL: ${url}`);
43
+ }
44
+ return {
45
+ bucket: match[1],
46
+ prefix: match[2]?.slice(1) || void 0
47
+ // Remove leading /
48
+ };
40
49
  }
41
- async function main() {
50
+ function diskNameFromBucket(bucket) {
51
+ return `s3-${bucket.replace(/[^a-zA-Z0-9-]/g, "-")}`;
52
+ }
53
+ function findDiskForBucket(disks, bucketName) {
54
+ return disks.find(
55
+ (disk) => disk.mounts?.some(
56
+ (mount) => mount.type === "s3" && mount.config?.bucketName === bucketName
57
+ )
58
+ );
59
+ }
60
+ async function runS3BucketMode() {
61
+ const s3Url = args[0];
62
+ const apiKey = opts.apiKey || process.env.ARCHIL_API_KEY;
63
+ const logLevel = opts.logLevel || process.env.ARCHIL_LOG_LEVEL;
64
+ const region = opts.region || process.env.ARCHIL_REGION || "aws-us-east-1";
65
+ const bucketRegion = opts.bucketRegion || "us-east-1";
66
+ if (!apiKey) {
67
+ console.error("Error: --api-key is required for S3 bucket mode");
68
+ console.error("");
69
+ console.error("Usage:");
70
+ console.error(" npx @archildata/just-bash s3://my-bucket --api-key adt_xxx");
71
+ console.error("");
72
+ console.error("Or set ARCHIL_API_KEY environment variable:");
73
+ console.error(" ARCHIL_API_KEY=adt_xxx npx @archildata/just-bash s3://my-bucket");
74
+ process.exit(1);
75
+ }
76
+ const awsAccessKeyId = process.env.AWS_ACCESS_KEY_ID;
77
+ const awsSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
78
+ if (!awsAccessKeyId || !awsSecretAccessKey) {
79
+ console.error("Error: AWS credentials required for S3 bucket mode");
80
+ console.error("");
81
+ console.error("Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables:");
82
+ console.error(" AWS_ACCESS_KEY_ID=xxx AWS_SECRET_ACCESS_KEY=xxx npx @archildata/just-bash s3://my-bucket -k adt_xxx");
83
+ process.exit(1);
84
+ }
85
+ const { bucket, prefix } = parseS3Url(s3Url);
86
+ const combinedPrefix = opts.bucketPrefix ? opts.bucketPrefix + (prefix ? "/" + prefix : "") : prefix;
87
+ console.log("Archil S3 Quick Start");
88
+ console.log("=====================");
89
+ console.log(` Bucket: ${bucket}`);
90
+ if (combinedPrefix) {
91
+ console.log(` Prefix: ${combinedPrefix}`);
92
+ }
93
+ console.log(` Region: ${region}`);
94
+ console.log(` Bucket Region: ${bucketRegion}`);
95
+ console.log("");
96
+ const archil = new Archil({
97
+ apiKey,
98
+ region
99
+ });
100
+ console.log("Checking for existing disk...");
101
+ let disk;
102
+ try {
103
+ const disks = await archil.disks.list();
104
+ disk = findDiskForBucket(disks, bucket);
105
+ if (disk) {
106
+ console.log(`Found existing disk: ${disk.name} (${disk.id})`);
107
+ }
108
+ } catch (err) {
109
+ console.error("Failed to list disks:", err instanceof Error ? err.message : err);
110
+ process.exit(1);
111
+ }
112
+ if (!disk) {
113
+ const diskName = diskNameFromBucket(bucket);
114
+ console.log(`Creating new disk: ${diskName}`);
115
+ try {
116
+ disk = await archil.disks.create({
117
+ name: diskName,
118
+ mounts: [
119
+ {
120
+ type: "s3",
121
+ bucketName: bucket,
122
+ bucketPrefix: combinedPrefix,
123
+ accessKeyId: awsAccessKeyId,
124
+ secretAccessKey: awsSecretAccessKey
125
+ }
126
+ ]
127
+ });
128
+ console.log(`Created disk: ${disk.name} (${disk.id})`);
129
+ } catch (err) {
130
+ console.error("Failed to create disk:", err instanceof Error ? err.message : err);
131
+ process.exit(1);
132
+ }
133
+ }
134
+ console.log("Connecting...");
135
+ let client;
136
+ try {
137
+ client = await disk.mount({ authToken: apiKey, logLevel });
138
+ console.log("Connected!");
139
+ } catch (err) {
140
+ console.error("Failed to connect:", err instanceof Error ? err.message : err);
141
+ process.exit(1);
142
+ }
143
+ return { client, diskName: disk.name, subdirectory: void 0 };
144
+ }
145
+ async function runDirectMode() {
146
+ const region = args[0] || opts.region || process.env.ARCHIL_REGION;
147
+ const rawDisk = args[1] || opts.disk || process.env.ARCHIL_DISK;
148
+ const authToken = opts.token || process.env.ARCHIL_TOKEN;
149
+ const logLevel = opts.logLevel || process.env.ARCHIL_LOG_LEVEL;
150
+ let diskName;
151
+ let subdirectory;
152
+ if (rawDisk) {
153
+ const colonIdx = rawDisk.indexOf(":/");
154
+ if (colonIdx !== -1) {
155
+ diskName = rawDisk.substring(0, colonIdx);
156
+ const subdir = rawDisk.substring(colonIdx + 1).replace(/^\/+|\/+$/g, "");
157
+ if (subdir) {
158
+ subdirectory = subdir;
159
+ }
160
+ } else {
161
+ diskName = rawDisk;
162
+ }
163
+ }
164
+ if (!region || !diskName) {
165
+ console.error("Error: region and disk are required\n");
166
+ console.error("Usage:");
167
+ console.error(" npx @archildata/just-bash <region> <disk>");
168
+ console.error(" npx @archildata/just-bash --region <region> --disk <disk>");
169
+ console.error("\nQuick start with S3 bucket:");
170
+ console.error(" npx @archildata/just-bash s3://my-bucket --api-key adt_xxx");
171
+ console.error("\nExamples:");
172
+ console.error(" npx @archildata/just-bash aws-us-east-1 myaccount/mydisk");
173
+ console.error(" ARCHIL_TOKEN=xxx npx @archildata/just-bash aws-us-east-1 myaccount/mydisk");
174
+ console.error("\nRun with --help for more options.");
175
+ process.exit(1);
176
+ }
42
177
  console.log("Connecting to Archil...");
43
178
  console.log(` Region: ${region}`);
44
179
  console.log(` Disk: ${diskName}`);
180
+ if (subdirectory) {
181
+ console.log(` Subdirectory: ${subdirectory}`);
182
+ }
45
183
  console.log(` Auth: ${authToken ? "token" : "IAM"}`);
46
184
  if (logLevel) {
47
185
  console.log(` Log level: ${logLevel}`);
@@ -60,8 +198,12 @@ async function main() {
60
198
  console.error("Failed to connect:", err instanceof Error ? err.message : err);
61
199
  process.exit(1);
62
200
  }
63
- const fs = new ArchilFs(client);
64
- const bash = new Bash({ fs });
201
+ return { client, diskName, subdirectory };
202
+ }
203
+ async function main() {
204
+ const { client, diskName, subdirectory } = isS3Bucket ? await runS3BucketMode() : await runDirectMode();
205
+ const fs = await ArchilFs.create(client, { subdirectory });
206
+ const bash = new Bash({ fs, customCommands: [createArchilCommand(client, fs)] });
65
207
  let cleaningUp = false;
66
208
  const cleanup = async (signal) => {
67
209
  if (cleaningUp) return;
@@ -87,6 +229,8 @@ Received ${signal}, cleaning up...`);
87
229
  process.on("SIGHUP", () => cleanup("SIGHUP"));
88
230
  console.log("");
89
231
  console.log("=== Archil just-bash shell ===");
232
+ console.log(`Connected to: ${diskName}${subdirectory ? ":" + subdirectory : ""}`);
233
+ console.log("");
90
234
  console.log("Type bash commands to interact with the filesystem.");
91
235
  console.log("Special commands:");
92
236
  console.log(" archil checkout [--force] <path> - Acquire write delegation");
@@ -106,82 +250,6 @@ Received ${signal}, cleaning up...`);
106
250
  rl.prompt();
107
251
  };
108
252
  prompt();
109
- const resolvePath = (path) => {
110
- if (path.startsWith("/")) {
111
- return path;
112
- }
113
- return cwd === "/" ? "/" + path : cwd + "/" + path;
114
- };
115
- const handleArchilCommand = async (command) => {
116
- const parts = command.split(/\s+/);
117
- if (parts[0] !== "archil") {
118
- return false;
119
- }
120
- const subcommand = parts[1];
121
- const targetPath = parts[2];
122
- if (subcommand === "checkout") {
123
- const args2 = parts.slice(2);
124
- const force = args2.includes("--force") || args2.includes("-f");
125
- const pathArg = args2.find((a) => !a.startsWith("-"));
126
- if (!pathArg) {
127
- console.error("Usage: archil checkout [--force|-f] <path>");
128
- return true;
129
- }
130
- const fullPath = resolvePath(pathArg);
131
- try {
132
- const inodeId = await fs.resolveInodeId(fullPath);
133
- await client.checkout(inodeId, { force });
134
- console.log(`Checked out: ${fullPath} (inode ${inodeId})${force ? " (forced)" : ""}`);
135
- } catch (err) {
136
- console.error(`Failed to checkout ${fullPath}:`, err instanceof Error ? err.message : err);
137
- }
138
- return true;
139
- }
140
- if (subcommand === "checkin") {
141
- if (!targetPath) {
142
- console.error("Usage: archil checkin <path>");
143
- return true;
144
- }
145
- const fullPath = resolvePath(targetPath);
146
- try {
147
- const inodeId = await fs.resolveInodeId(fullPath);
148
- await client.checkin(inodeId);
149
- console.log(`Checked in: ${fullPath} (inode ${inodeId})`);
150
- } catch (err) {
151
- console.error(`Failed to checkin ${fullPath}:`, err instanceof Error ? err.message : err);
152
- }
153
- return true;
154
- }
155
- if (subcommand === "list-delegations" || subcommand === "delegations") {
156
- try {
157
- const delegations = client.listDelegations();
158
- if (delegations.length === 0) {
159
- console.log("No delegations held");
160
- } else {
161
- console.log("Delegations:");
162
- for (const d of delegations) {
163
- console.log(` inode ${d.inodeId}: ${d.state}`);
164
- }
165
- }
166
- } catch (err) {
167
- console.error("Failed to list delegations:", err instanceof Error ? err.message : err);
168
- }
169
- return true;
170
- }
171
- if (subcommand === "help" || !subcommand) {
172
- console.log("Archil commands:");
173
- console.log(" archil checkout [--force|-f] <path> - Acquire write delegation");
174
- console.log(" archil checkin <path> - Release write delegation");
175
- console.log(" archil list-delegations - Show held delegations");
176
- console.log(" archil help - Show this help message");
177
- console.log("");
178
- console.log("The --force flag revokes any existing delegation from other clients.");
179
- return true;
180
- }
181
- console.error(`Unknown archil command: ${subcommand}`);
182
- console.error("Run 'archil help' for available commands");
183
- return true;
184
- };
185
253
  rl.on("line", async (line) => {
186
254
  const trimmed = line.trim();
187
255
  if (trimmed === "exit" || trimmed === "quit") {
@@ -194,12 +262,6 @@ Received ${signal}, cleaning up...`);
194
262
  }
195
263
  rl.pause();
196
264
  try {
197
- if (trimmed.startsWith("archil")) {
198
- await handleArchilCommand(trimmed);
199
- rl.resume();
200
- prompt();
201
- return;
202
- }
203
265
  const result = await bash.exec(trimmed, {
204
266
  cwd,
205
267
  env: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archildata/just-bash",
3
- "version": "0.1.13",
3
+ "version": "0.8.1",
4
4
  "description": "Archil filesystem adapter for just-bash",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -26,7 +26,7 @@
26
26
  "shell": "tsx bin/shell.ts"
27
27
  },
28
28
  "dependencies": {
29
- "@archildata/client": "^0.1.13",
29
+ "@archildata/client": "^0.8.1",
30
30
  "commander": "^14.0.3",
31
31
  "debug": "^4.3.4",
32
32
  "just-bash": "^2.7.0"