@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
110 lines (109 loc) • 4.75 kB
JavaScript
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import { follow as slackFollow, readMany as slackReadMany, readRange as slackReadRange, readStream as slackReadStream, } from "../../core/slack/read.js";
import { readdir as slackReaddir } from "../../core/slack/readdir.js";
import { filesContaining as slackFilesContaining } from "../../core/slack/search.js";
import { stat as slackStat } from "../../core/slack/stat.js";
import {} from "../../io/types.js";
import { BaseVFS } from "../base.js";
export const VFS_MD = `\`\`\`text
{prefix}
channels/
<channel-name>__<channel-id>/
<yyyy-mm-dd>/
chat.jsonl # messages for that date
files/ # attachments shared that day
<name>__<F-id>.<ext> # cat downloads the bytes
dms/
<user-name>__<dm-id>/
<yyyy-mm-dd>/
chat.jsonl
files/
<name>__<F-id>.<ext>
users/
<username>__<user-id>.json # user profile
\`\`\`
- A channel, DM or user name is \`<display-name>__<id>\`. The display name
keeps Slack's spelling; only \`/\` becomes \`∕\` (U+2215). \`ls\` the parent
first to find exact names, and quote a name that has spaces.
- Messages are JSONL; use \`jq\` for fields such as \`.text\`, \`.user\`,
\`.ts\` and \`.files\`. A day's \`files/\` may be empty.
- \`.user\` is a user ID (\`U…\`), not a username; resolve it through
\`users/<username>__<user-id>.json\` and filter messages by the ID.
- Without \`start_time\`/\`end_time\`, a channel lists the latest 90 days up
to its newest message; a date glob finds earlier days.
- With \`start_time\`/\`end_time\`, listings cover that scope; messages and
attachments outside it are absent, direct paths included, and \`grep\`
and \`rg\` see only the in-scope files. Users and channel metadata stay
available as context.
- Unless \`walk_limit\` is -1, \`grep -rw\` and \`rg -w\` read only the channel
and DM days Slack search names; attachments under \`files/\` are always
read.`;
/**
* Slack's functions over its accessor, which the node and browser
* packages build over their own transport.
*/
export class SlackVFS extends BaseVFS {
readsRanges = true;
wholeWordSearch = true;
handed = {
readdir: ['entry'],
read: ['entry', 'parent'],
follow: ['entry', 'parent'],
readMany: ['entry', 'parent'],
stat: ['entry', 'parent'],
};
// Slack search names channel and DM days; users and file blobs are read as
// usual.
searchable = [
'channels/*/*/chat.jsonl',
'dms/*/*/chat.jsonl',
];
readdir(path, handed) {
return slackReaddir(this.accessor, path, handed?.entry, null, handed?.limit);
}
read(path, handed, offset = 0, size = null, signal) {
const entry = handed?.entry ?? null;
const parent = handed?.parent ?? null;
if (offset === 0 && size === null)
return slackReadStream(this.accessor, path, handed?.entry, handed?.parent, signal);
return slackReadRange(this.accessor, path, entry, parent, size === null ? { offset } : { offset, size });
}
readMany(paths, limit, handed) {
return slackReadMany(this.accessor, paths, limit, handed);
}
follow(path, n, interval, handed) {
return slackFollow(this.accessor, path, n, interval, handed?.entry, handed?.parent);
}
stat(path, handed) {
return slackStat(this.accessor, path, handed?.entry, handed?.parent);
}
/**
* Whether this VFS answers `name`; with no walk limit it reads every
* file rather than ask the service's search.
*/
supports(name) {
if (name === 'files_containing' && this.walkLimit < 0)
return false;
return super.supports(name);
}
filesContaining(path, text, opts) {
if (!opts.wholeWord ||
this.walkLimit < 0 ||
!(this.accessor.transport.searchAvailable?.() ?? true))
return Promise.resolve(null);
return slackFilesContaining(this.accessor, text, [path]);
}
}
//# sourceMappingURL=slack.js.map